Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * teensy_tmc2209_test2.ino
- *
- * Teensy 4.1 + ST7735_t3 + TMC2209 + Adafruit seesaw (quad encoder)
- * - Double-buffered framebuffer (DMA) for flicker-free updates
- * - Per-microstep on-screen position updates during continuous sweep
- */
- #include <TMC2209.h>
- #include <string.h>
- #include <stdlib.h>
- #include <SPI.h>
- #include <Adafruit_GFX.h>
- #include <ST7735_t3.h>
- #include "Adafruit_seesaw.h"
- #include <seesaw_neopixel.h>
- #include <climits>
- #define TFT_CS 10
- #define TFT_DC 9
- #define TFT_RST 8
- #define TFT_BACKLIGHT 6
- // Display resolution (after rotation 3 treat width=160, height=128)
- static constexpr int16_t TFT_W = 160;
- static constexpr int16_t TFT_H = 128;
- static constexpr size_t FB_PIXELS = (size_t)TFT_W * (size_t)TFT_H;
- static constexpr size_t FB_BYTES = FB_PIXELS * sizeof(uint16_t);
- ST7735_t3 tft = ST7735_t3(TFT_CS, TFT_DC, TFT_RST);
- #define SEESAW_ADDR 0x49
- #define SS_NEO_PIN 18
- uint8_t switch_pins[4] = {12, 14, 17, 9};
- Adafruit_seesaw ss = Adafruit_seesaw(&Wire);
- seesaw_NeoPixel pixels = seesaw_NeoPixel(4, SS_NEO_PIN, NEO_GRB + NEO_KHZ800);
- int32_t enc_positions[4] = {0, 0, 0, 0};
- // Maximum number of motors supported (limited by Serial1-8)
- static constexpr int MAX_MOTORS = 7;
- // Teensy 4.1 Serial1-8 TX/RX pin mappings (standard defaults)
- const int8_t tx_pins[9] = {-1, 1, 8, 15, 17, 21, 25, 29, 35}; // Index 1-8
- const int8_t rx_pins[9] = {-1, 0, 7, 14, 16, 20, 24, 28, 34};
- // Default pins for STEP, DIR, and UART per motor (0-6 for motors 1-7)
- const int8_t default_step_pins[7] = {31, 36, 12, 18, 22, 26, 30};
- const int8_t default_dir_pins[7] = {32, 37, 13, 19, 23, 27, 31};
- const int8_t default_uart_numbers[7] = {7, 8, 3, 4, 5, 6, 1};
- // Global input buffer
- const int INPUT_BUFFER_SIZE = 256;
- char user_input[INPUT_BUFFER_SIZE];
- /*───────────────────────────────────────────────────────────────────────────
- DMA framebuffer glue (double-buffered, non-blocking pushes)
- ───────────────────────────────────────────────────────────────────────────*/
- // 2 full-size RGB565 framebuffers in non-cacheable RAM for DMA
- DMAMEM static uint16_t fb_a[FB_PIXELS];
- DMAMEM static uint16_t fb_b[FB_PIXELS];
- static bool tft_fb_ok = false;
- static uint16_t *draw_fb = nullptr; // buffer we currently render into
- inline void tft_fb_prepare_draw() {
- if (tft_fb_ok) {
- tft.setFrameBuffer(draw_fb);
- }
- }
- inline void tft_fb_wait_idle() {
- if (tft_fb_ok) {
- tft.waitUpdateAsyncComplete();
- }
- }
- // Start a DMA push if bus is idle; swap draw buffer and seed it with the last frame
- inline void tft_fb_push_if_idle() {
- if (!tft_fb_ok) {
- return;
- }
- if (!tft.asyncUpdateActive()) {
- // Send the frame we just finished drawing
- uint16_t *sending = draw_fb;
- // Switch draw_fb to the other buffer first
- draw_fb = (draw_fb == fb_a) ? fb_b : fb_a;
- // Start non-blocking DMA transfer from 'sending'
- tft.setFrameBuffer(sending);
- tft.updateScreenAsync(false);
- // Point drawing back at draw_fb and COPY the previous frame into it
- // so all static UI/artwork persists across buffer flips.
- tft.setFrameBuffer(draw_fb);
- memcpy(draw_fb, sending, FB_BYTES);
- }
- }
- // For legacy call sites that expect a push, allow optional blocking push.
- inline void tft_fb_push(bool block = false) {
- tft_fb_push_if_idle();
- if (block && tft_fb_ok) {
- tft.waitUpdateAsyncComplete();
- }
- }
- /*───────────────────────────────────────────────────────────────────────────
- GFX helpers: controlled line spacing + minimal DRY wrappers
- ───────────────────────────────────────────────────────────────────────────*/
- constexpr int16_t EXTRA_LINE_SPACING_PX = 2; // adjust spacing here
- static uint8_t gfx_text_size_x = 1;
- static uint8_t gfx_text_size_y = 1;
- static int16_t gfx_home_x = 0;
- static int16_t gfx_home_y = 0;
- inline int16_t gfx_text_height() {
- return 8 * gfx_text_size_y; // built-in 5x7 font uses 8px line step
- }
- inline int16_t gfx_line_height() {
- return gfx_text_height() + EXTRA_LINE_SPACING_PX;
- }
- inline void gfx_set_text_size(uint8_t s) {
- if (s < 1) s = 1;
- gfx_text_size_x = s;
- gfx_text_size_y = s;
- tft.setTextSize(s);
- }
- inline void gfx_set_cursor(int16_t x, int16_t y) {
- gfx_home_x = x;
- gfx_home_y = y;
- tft.setCursor(x, y);
- }
- inline void gfx_newline() {
- tft.setCursor(gfx_home_x, tft.getCursorY() + gfx_line_height());
- }
- inline void gfx_print(const String &s) { tft.print(s); }
- inline void gfx_print(const char *s) { tft.print(s); }
- inline void gfx_print(long v) { tft.print(v); }
- inline void gfx_println(const String &s) { tft.print(s); gfx_newline(); }
- inline void gfx_println(const char *s) { tft.print(s); gfx_newline(); }
- inline void gfx_println(long v) { tft.print(v); gfx_newline(); }
- /*───────────────────────────────────────────────────────────────────────────
- UI: shared menu text
- ───────────────────────────────────────────────────────────────────────────*/
- const char *MENU_TITLE = "Menu:";
- const char *MENU_ITEMS[] = {
- "1. Config Stepping",
- "2. Continuous Sweep",
- "3. Config Modes",
- "4. Set Velocity",
- "5. Test Encoders"
- };
- constexpr int MENU_ITEM_COUNT = 5;
- /*───────────────────────────────────────────────────────────────────────────
- Stepper structure
- ───────────────────────────────────────────────────────────────────────────*/
- struct StepperMotor {
- TMC2209 tmc;
- uint8_t step_pin;
- uint8_t dir_pin;
- uint8_t uart_number; // 1 to 8 for Serial1-8
- long full_steps_per_rev = 200; // Default full steps per revolution
- uint16_t microsteps = 1; // Default to full stepping
- long speed_steps_per_sec = 200; // Default steps per second
- long last_start_pos = 0; // Default start position
- long last_stop_pos = 200; // Default to one revolution (full_steps * microsteps)
- long current_position = 0; // Track position in microsteps
- long current_velocity = 0; // For velocity mode
- bool velocity_mode = false; // false: position (STEP/DIR), true: velocity (VActual)
- bool constant_current = true; // true: fixed current, false: automatic (CoolStep)
- bool initialized = false;
- };
- StepperMotor motors[MAX_MOTORS];
- int motor_count = 2;
- /*───────────────────────────────────────────────────────────────────────────
- Prototypes
- ───────────────────────────────────────────────────────────────────────────*/
- void update_display(bool full_update = false);
- long get_numeric_input(String prompt, long default_val, long min_val = LONG_MIN, long max_val = LONG_MAX);
- long ask_value(const String &prompt, long def, long min_val = LONG_MIN, long max_val = LONG_MAX);
- HardwareSerial* get_serial(uint8_t uart_number);
- void initialize_motors();
- void move_to_position(int motor_index, long target_position, long microsteps_per_sec);
- void configure_stepping();
- void configure_modes();
- void set_velocity();
- void continuous_sweep();
- void test_encoders();
- void print_menu();
- uint32_t wheel(byte wheel_pos);
- inline void update_display_microstep(int motor_index);
- /*───────────────────────────────────────────────────────────────────────────
- DRY helper: ask numeric value (Serial echo included)
- ───────────────────────────────────────────────────────────────────────────*/
- long ask_value(const String &prompt, long def, long min_val, long max_val) {
- long v = get_numeric_input(prompt, def, min_val, max_val);
- Serial.println(String(v));
- return v;
- }
- /*───────────────────────────────────────────────────────────────────────────
- Serial port selector
- ───────────────────────────────────────────────────────────────────────────*/
- HardwareSerial* get_serial(uint8_t uart_number) {
- switch (uart_number) {
- case 1: return &Serial1;
- case 2: return &Serial2;
- case 3: return &Serial3;
- case 4: return &Serial4;
- case 5: return &Serial5;
- case 6: return &Serial6;
- case 7: return &Serial7;
- case 8: return &Serial8;
- default: return nullptr;
- }
- }
- /*───────────────────────────────────────────────────────────────────────────
- Motor init
- ───────────────────────────────────────────────────────────────────────────*/
- void initialize_motors() {
- for (int i = 0; i < motor_count; i++) {
- HardwareSerial* ser = get_serial(motors[i].uart_number);
- if (ser == nullptr) {
- Serial.println("Invalid UART number for motor " + String(i + 1) + ". Skipping.");
- continue;
- }
- motors[i].tmc.setup(*ser, 115200, TMC2209::SERIAL_ADDRESS_0);
- pinMode(motors[i].step_pin, OUTPUT);
- pinMode(motors[i].dir_pin, OUTPUT);
- digitalWrite(motors[i].step_pin, LOW);
- digitalWrite(motors[i].dir_pin, LOW);
- // Driver configuration
- motors[i].tmc.setMicrostepsPerStep(motors[i].microsteps);
- motors[i].tmc.setRunCurrent(83);
- motors[i].tmc.setHoldCurrent(25);
- motors[i].tmc.setHoldDelay(50);
- motors[i].tmc.enableStealthChop();
- if (motors[i].constant_current) {
- motors[i].tmc.disableAutomaticCurrentScaling();
- } else {
- motors[i].tmc.enableCoolStep();
- }
- if (motors[i].velocity_mode) {
- motors[i].tmc.moveAtVelocity(motors[i].current_velocity);
- } else {
- motors[i].tmc.moveUsingStepDirInterface();
- }
- motors[i].tmc.disable(); // Start disabled
- motors[i].initialized = true;
- Serial.println("Motor " + String(i + 1) + " initialized.");
- // Output the 4 pins for this motor
- Serial.println("Motor " + String(i + 1) + " pins:");
- Serial.print("STEP: "); Serial.println(motors[i].step_pin);
- Serial.print("DIR: "); Serial.println(motors[i].dir_pin);
- Serial.print("TX: "); Serial.println(tx_pins[motors[i].uart_number]);
- Serial.print("RX: "); Serial.println(rx_pins[motors[i].uart_number]);
- }
- }
- /*───────────────────────────────────────────────────────────────────────────
- Movement (per-microstep HUD updates, non-blocking DMA pushes)
- ───────────────────────────────────────────────────────────────────────────*/
- void move_to_position(int motor_index, long target_position, long microsteps_per_sec) {
- if (!motors[motor_index].initialized) {
- Serial.println("Motor not initialized.");
- return;
- }
- motors[motor_index].tmc.enable();
- long current_pos = motors[motor_index].current_position;
- long distance = labs(target_position - current_pos);
- if (distance == 0) {
- return;
- }
- bool dir_high = (target_position > current_pos);
- const long step_delta = dir_high ? 1 : -1;
- digitalWrite(motors[motor_index].dir_pin, dir_high ? HIGH : LOW);
- unsigned long delay_us = 1000000UL / (microsteps_per_sec ? microsteps_per_sec : 1);
- if (delay_us < 2) delay_us = 2;
- unsigned long half_delay = delay_us / 2;
- if (half_delay < 1) half_delay = 1;
- for (long i = 0; i < distance; i++) {
- if (Serial.available()) {
- Serial.read();
- Serial.println("Movement interrupted.");
- break;
- }
- // One microstep pulse
- digitalWrite(motors[motor_index].step_pin, HIGH);
- delayMicroseconds(half_delay);
- digitalWrite(motors[motor_index].step_pin, LOW);
- delayMicroseconds(delay_us - half_delay);
- // Update model and HUD
- current_pos += step_delta;
- motors[motor_index].current_position = current_pos;
- update_display_microstep(motor_index); // draw into off-screen buffer + async push (if idle)
- }
- motors[motor_index].tmc.disable();
- // Final touch-up (in case of early break)
- update_display_microstep(motor_index);
- }
- /*───────────────────────────────────────────────────────────────────────────
- Numeric input via Serial or encoder 0 (with TFT prompt)
- ───────────────────────────────────────────────────────────────────────────*/
- long get_numeric_input(String prompt, long default_val, long min_val, long max_val) {
- Serial.print(prompt);
- // Header
- tft_fb_wait_idle();
- tft_fb_prepare_draw();
- tft.fillScreen(ST7735_BLACK);
- tft.setTextColor(ST7735_WHITE, ST7735_BLACK);
- gfx_set_text_size(1);
- gfx_set_cursor(0, 0);
- gfx_println(prompt);
- long current = default_val;
- // Value line
- const int16_t value_y = gfx_line_height();
- tft.setCursor(0, value_y);
- tft.print("Value: ");
- tft.print(current);
- tft_fb_push(false);
- String serial_buf = "";
- int32_t last_enc0 = ss.getEncoderPosition(0);
- bool confirmed = false;
- while (!confirmed) {
- if (Serial.available()) {
- int c = Serial.read();
- if (c == '\r' || c == '\n') {
- if (serial_buf.length() > 0) {
- current = atol(serial_buf.c_str());
- }
- confirmed = true;
- } else {
- serial_buf += (char)c;
- }
- }
- int32_t new_enc0 = ss.getEncoderPosition(0);
- long delta = new_enc0 - last_enc0;
- if (delta != 0) {
- // ++tmw - add acceleration
- if (delta > 7) {
- delta = 100;
- }
- else if (delta > 2) {
- delta = 10;
- }
- new_enc0 = last_enc0 + delta;
- current += delta;
- if (min_val != LONG_MIN) current = max(current, min_val);
- if (max_val != LONG_MAX) current = min(current, max_val);
- last_enc0 = new_enc0;
- // Update value line (opaque text, single line)
- tft_fb_prepare_draw();
- tft.fillRect(0, value_y, tft.width(), gfx_text_height(), ST7735_BLACK);
- tft.setCursor(0, value_y);
- tft.print("Value: ");
- tft.print(current);
- tft_fb_push(false);
- }
- if (!ss.digitalRead(switch_pins[0])) {
- delay(50);
- if (!ss.digitalRead(switch_pins[0])) {
- confirmed = true;
- while (!ss.digitalRead(switch_pins[0])) {
- /* wait for button release */
- }
- delay(10);
- }
- }
- delay(5);
- }
- return current;
- }
- /*───────────────────────────────────────────────────────────────────────────
- Configuration UIs
- ───────────────────────────────────────────────────────────────────────────*/
- void configure_stepping() {
- long new_motor_count = ask_value(
- "Enter number of attached stepper motors (1 to " + String(MAX_MOTORS) + "): ",
- motor_count, 1, MAX_MOTORS
- );
- motor_count = new_motor_count;
- for (int i = 0; i < motor_count; i++) {
- long step_pin = ask_value(
- "For motor " + String(i + 1) + ", enter STEP pin (default " + String(default_step_pins[i]) + "): ",
- default_step_pins[i], 0, 40
- );
- motors[i].step_pin = step_pin;
- long dir_pin = ask_value(
- "For motor " + String(i + 1) + ", enter DIR pin (default " + String(default_dir_pins[i]) + "): ",
- default_dir_pins[i], 0, 40
- );
- motors[i].dir_pin = dir_pin;
- long uart_number = ask_value(
- "For motor " + String(i + 1) + ", enter UART number (default " + String(default_uart_numbers[i]) + "): ",
- default_uart_numbers[i], 1, 8
- );
- motors[i].uart_number = uart_number;
- long full_steps_per_rev = ask_value(
- "For motor " + String(i + 1) + ", enter full steps per revolution (default 200): ",
- 200
- );
- motors[i].full_steps_per_rev = full_steps_per_rev;
- long microsteps = ask_value(
- "For motor " + String(i + 1) + ", enter microsteps (default 1): ",
- 1, 1, 256
- );
- motors[i].microsteps = microsteps;
- long speed_steps_per_sec = ask_value(
- "For motor " + String(i + 1) + ", enter speed steps per second (default 200): ",
- 200
- );
- motors[i].speed_steps_per_sec = speed_steps_per_sec;
- if (motors[i].last_stop_pos == 200) {
- motors[i].last_stop_pos = motors[i].full_steps_per_rev * motors[i].microsteps;
- }
- motors[i].current_position = 0;
- motors[i].initialized = false;
- }
- initialize_motors();
- }
- void configure_modes() {
- for (int i = 0; i < motor_count; i++) {
- long vel = ask_value(
- "For motor " + String(i + 1) + ", velocity mode (1=yes,0=no): ",
- motors[i].velocity_mode ? 1 : 0, 0, 1
- );
- motors[i].velocity_mode = (vel == 1);
- long constc = ask_value(
- "For motor " + String(i + 1) + ", constant current mode (1=yes,0=no): ",
- motors[i].constant_current ? 1 : 0, 0, 1
- );
- motors[i].constant_current = (constc == 1);
- }
- initialize_motors();
- }
- void set_velocity() {
- bool any_velocity = false;
- for (int i = 0; i < motor_count; i++) {
- if (motors[i].velocity_mode) {
- any_velocity = true;
- long vel = ask_value(
- "Enter velocity for motor " + String(i + 1) + " (microsteps/sec, signed): ",
- motors[i].current_velocity
- );
- motors[i].current_velocity = vel;
- motors[i].tmc.moveAtVelocity(vel);
- }
- }
- if (!any_velocity) {
- Serial.println("No motors in velocity mode.");
- }
- }
- /*───────────────────────────────────────────────────────────────────────────
- Continuous sweep
- ───────────────────────────────────────────────────────────────────────────*/
- void continuous_sweep() {
- if (motor_count == 0) {
- Serial.println("No motors configured.");
- return;
- }
- int motor_num = ask_value(
- "Enter motor number to sweep (1 to " + String(motor_count) + ", or >" + String(motor_count) + " for all): ",
- 1, 1
- );
- bool sweep_all = (motor_num > motor_count);
- if (!sweep_all) {
- int motor_index = motor_num - 1;
- if (motor_index < 0 || motor_index >= motor_count || !motors[motor_index].initialized) {
- Serial.println("Invalid motor.");
- return;
- }
- if (motors[motor_index].velocity_mode) {
- Serial.println("Cannot sweep in velocity mode.");
- return;
- }
- long pos1 = ask_value(
- "Enter first position (microsteps, default " + String(motors[motor_index].last_start_pos) + "): ",
- motors[motor_index].last_start_pos
- );
- motors[motor_index].last_start_pos = pos1;
- long pos2 = ask_value(
- "Enter second position (microsteps, default " + String(motors[motor_index].last_stop_pos) + "): ",
- motors[motor_index].last_stop_pos
- );
- motors[motor_index].last_stop_pos = pos2;
- // Render full status before movement (no intermediate black push)
- update_display(true);
- Serial.println("Starting continuous sweep. Press any key to stop.");
- bool sweeping = true;
- while (sweeping) {
- move_to_position(motor_index, pos1 + 1, motors[motor_index].speed_steps_per_sec * motors[motor_index].microsteps);
- if (Serial.available()) { Serial.read(); sweeping = false; }
- move_to_position(motor_index, pos2 + 1, motors[motor_index].speed_steps_per_sec * motors[motor_index].microsteps);
- if (Serial.available()) { Serial.read(); sweeping = false; }
- }
- Serial.println("Sweep stopped.");
- } else {
- bool any_velocity = false;
- for (int i = 0; i < motor_count; i++) {
- if (motors[i].velocity_mode) { any_velocity = true; break; }
- }
- if (any_velocity) {
- Serial.println("Cannot sweep all; some motors in velocity mode.");
- return;
- }
- long pos1[MAX_MOTORS];
- long pos2[MAX_MOTORS];
- for (int i = 0; i < motor_count; i++) {
- pos1[i] = ask_value(
- "For motor " + String(i + 1) + ", enter first position (microsteps, default " + String(motors[i].last_start_pos) + "): ",
- motors[i].last_start_pos
- );
- motors[i].last_start_pos = pos1[i];
- pos2[i] = ask_value(
- "For motor " + String(i + 1) + ", enter second position (microsteps, default " + String(motors[i].last_stop_pos) + "): ",
- motors[i].last_stop_pos
- );
- motors[i].last_stop_pos = pos2[i];
- }
- update_display(true);
- Serial.println("Starting continuous sweep for all motors. Press any key to stop.");
- bool sweeping = true;
- while (sweeping) {
- for (int i = 0; i < motor_count; i++) {
- move_to_position(i, pos1[i], motors[i].speed_steps_per_sec * motors[i].microsteps);
- if (Serial.available()) { Serial.read(); sweeping = false; break; }
- }
- for (int i = 0; i < motor_count; i++) {
- move_to_position(i, pos2[i], motors[i].speed_steps_per_sec * motors[i].microsteps);
- if (Serial.available()) { Serial.read(); sweeping = false; break; }
- }
- }
- Serial.println("Sweep stopped.");
- }
- }
- /*───────────────────────────────────────────────────────────────────────────
- Encoder test
- ───────────────────────────────────────────────────────────────────────────*/
- void test_encoders() {
- Serial.println("Test Encoders. Press any button to exit.");
- tft_fb_wait_idle();
- tft_fb_prepare_draw();
- tft.fillScreen(ST7735_BLACK);
- gfx_set_cursor(0, 0);
- tft.setTextColor(ST7735_MAGENTA, ST7735_BLACK);
- gfx_set_text_size(1);
- gfx_println("Test Encoders");
- tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
- tft_fb_push(false);
- const int16_t list_y = gfx_line_height();
- bool done = false;
- while (!done) {
- bool changed = false;
- for (int e = 0; e < 4; e++) {
- int32_t new_pos = ss.getEncoderPosition(e);
- if (new_pos != enc_positions[e]) {
- Serial.print("Enc"); Serial.print(e); Serial.print(": "); Serial.println(new_pos);
- enc_positions[e] = new_pos;
- pixels.setPixelColor(e, wheel((new_pos * 4) & 255));
- pixels.show();
- changed = true;
- }
- if (!ss.digitalRead(switch_pins[e])) {
- delay(50);
- if (!ss.digitalRead(switch_pins[e])) {
- Serial.print("Button "); Serial.print(e); Serial.println(" pressed");
- done = true;
- }
- }
- }
- if (changed) {
- tft_fb_prepare_draw();
- tft.fillRect(0, list_y, tft.width(), tft.height() - list_y, ST7735_BLACK);
- gfx_set_cursor(0, list_y);
- for (int e = 0; e < 4; e++) {
- tft.print("Enc"); tft.print(e); tft.print(": "); tft.print(enc_positions[e]);
- gfx_newline();
- }
- tft_fb_push(false);
- }
- delay(10);
- }
- }
- /*───────────────────────────────────────────────────────────────────────────
- Menu printing (Serial + TFT)
- ───────────────────────────────────────────────────────────────────────────*/
- void print_menu() {
- // Serial menu
- Serial.println();
- Serial.println(MENU_TITLE);
- for (int i = 0; i < MENU_ITEM_COUNT; ++i) {
- Serial.println(MENU_ITEMS[i]);
- }
- Serial.print("Enter choice: ");
- // TFT menu
- tft_fb_wait_idle();
- tft_fb_prepare_draw();
- tft.fillScreen(ST7735_BLACK);
- gfx_set_cursor(0, 0);
- tft.setTextColor(ST7735_MAGENTA, ST7735_BLACK);
- gfx_set_text_size(1);
- gfx_println(MENU_TITLE);
- tft.setTextColor(ST7735_WHITE, ST7735_BLACK);
- for (int i = 0; i < MENU_ITEM_COUNT; ++i) {
- gfx_println(MENU_ITEMS[i]);
- }
- tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
- gfx_println("Modes:");
- for (int i = 0; i < motor_count; i++) {
- tft.print("M");
- tft.print(i + 1);
- tft.print(": ");
- tft.print(motors[i].velocity_mode ? "Vel" : "Pos");
- tft.print(" ");
- tft.print(motors[i].constant_current ? "Const" : "Auto");
- gfx_newline();
- }
- tft_fb_push(false);
- }
- /*───────────────────────────────────────────────────────────────────────────
- Status display
- ───────────────────────────────────────────────────────────────────────────*/
- void update_display(bool full_update) {
- gfx_set_text_size(1);
- tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
- tft_fb_prepare_draw();
- if (full_update) {
- tft.fillScreen(ST7735_BLACK);
- gfx_set_cursor(0, 0);
- tft.setTextColor(ST7735_MAGENTA, ST7735_BLACK);
- gfx_println("Motor Status:");
- tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
- for (int i = 0; i < motor_count; i++) {
- tft.print("M"); tft.print(i + 1); tft.print(": Pos ");
- char buf[16];
- sprintf(buf, "%4ld", motors[i].current_position);
- tft.print(buf);
- tft.setCursor(tft.getCursorX() + 2, tft.getCursorY());
- tft.print(" ");
- if (motors[i].velocity_mode) {
- tft.print("Vel "); tft.print(motors[i].current_velocity);
- } else {
- tft.print("Pos");
- }
- tft.print(" ");
- tft.print(motors[i].constant_current ? "Const" : "Auto");
- gfx_newline();
- }
- tft_fb_push_if_idle();
- } else {
- const int16_t first_line_y = gfx_line_height();
- for (int i = 0; i < motor_count; i++) {
- int16_t y = first_line_y + i * gfx_line_height();
- // Position field (opaque text overwrites cleanly)
- tft.setCursor(48, y);
- char buf[16];
- sprintf(buf, "%4ld", motors[i].current_position);
- tft.print(buf);
- // Velocity field (if applicable)
- if (motors[i].velocity_mode) {
- tft.setCursor(90, y);
- tft.print("Vel ");
- tft.print(motors[i].current_velocity);
- }
- }
- tft_fb_push_if_idle();
- }
- }
- /*───────────────────────────────────────────────────────────────────────────
- Microstep HUD update: rewrite only the numeric position for motor i
- ───────────────────────────────────────────────────────────────────────────*/
- inline void update_display_microstep(int motor_index) {
- const int16_t first_line_y = gfx_line_height(); // after "Motor Status:" header
- const int16_t y = first_line_y + motor_index * gfx_line_height();
- tft_fb_prepare_draw();
- tft.setTextColor(ST7735_YELLOW, ST7735_BLACK);
- tft.setCursor(48, y);
- char buf[16];
- sprintf(buf, "%5ld", motors[motor_index].current_position);
- tft.print(buf);
- if (motors[motor_index].velocity_mode) {
- tft.setCursor(90, y);
- tft.print("Vel ");
- tft.print(motors[motor_index].current_velocity);
- }
- tft_fb_push_if_idle(); // queue non-blocking DMA when idle
- }
- /*───────────────────────────────────────────────────────────────────────────
- Color wheel for seesaw neopixels
- ───────────────────────────────────────────────────────────────────────────*/
- uint32_t wheel(byte wheel_pos) {
- wheel_pos = 255 - wheel_pos;
- if (wheel_pos < 85) {
- return pixels.Color(255 - wheel_pos * 3, 0, wheel_pos * 3);
- }
- if (wheel_pos < 170) {
- wheel_pos -= 85;
- return pixels.Color(0, wheel_pos * 3, 255 - wheel_pos * 3);
- }
- wheel_pos -= 170;
- return pixels.Color(wheel_pos * 3, 255 - wheel_pos * 3, 0);
- }
- /*───────────────────────────────────────────────────────────────────────────
- Setup / Loop
- ───────────────────────────────────────────────────────────────────────────*/
- void setup() {
- Serial.begin(9600);
- while (!Serial) {}
- Serial.println("Teensy 4.1 Stepper Motor Control with TMC2209");
- memset(user_input, 0, sizeof(user_input));
- // TFT init
- tft.initR(INITR_BLACKTAB);
- tft.setRotation(3); // landscape
- tft.setTextWrap(false);
- // Bind framebuffers without heap, enable FB mode
- memset(fb_a, 0, FB_BYTES);
- memset(fb_b, 0, FB_BYTES);
- tft.setFrameBuffer(fb_a);
- tft_fb_ok = (tft.useFrameBuffer(true) != 0);
- draw_fb = fb_a;
- tft.fillScreen(ST7735_BLACK);
- if (tft_fb_ok) {
- tft.updateScreen(); // initial blocking push to prime the panel
- }
- // Backlight
- pinMode(TFT_BACKLIGHT, OUTPUT);
- analogWrite(TFT_BACKLIGHT, 96);
- // seesaw init
- if (!ss.begin(SEESAW_ADDR) || !pixels.begin(SEESAW_ADDR)) {
- Serial.println("Couldn't find seesaw!");
- while (1) {
- delay(10);
- }
- }
- Serial.println("seesaw started");
- for (int e = 0; e < 4; e++) {
- ss.pinMode(switch_pins[e], INPUT_PULLUP);
- enc_positions[e] = ss.getEncoderPosition(e);
- }
- pixels.setBrightness(255);
- pixels.show();
- // Default motor configuration
- motor_count = 2;
- // Motor 1
- motors[0].step_pin = default_step_pins[0];
- motors[0].dir_pin = default_dir_pins[0];
- motors[0].uart_number = default_uart_numbers[0];
- motors[0].full_steps_per_rev = 200;
- motors[0].microsteps = 64;
- motors[0].speed_steps_per_sec = 50;
- motors[0].last_start_pos = 0;
- motors[0].last_stop_pos = 3200;
- // Motor 2
- motors[1].step_pin = default_step_pins[1];
- motors[1].dir_pin = default_dir_pins[1];
- motors[1].uart_number = default_uart_numbers[1];
- motors[1].full_steps_per_rev = 200;
- motors[1].microsteps = 1;
- motors[1].speed_steps_per_sec = 400;
- motors[1].last_start_pos = 0;
- motors[1].last_stop_pos = 200;
- initialize_motors();
- // Splash / ready
- tft.setTextColor(ST7735_MAGENTA, ST7735_BLACK);
- gfx_set_text_size(1);
- gfx_set_cursor(0, 0);
- gfx_println("Teensy TMC2209 Control");
- gfx_println("Ready");
- tft_fb_push(false);
- print_menu();
- }
- void loop() {
- int choice = ask_value("Enter choice: ", 1, 1, 5);
- switch (choice) {
- case 1:
- configure_stepping();
- update_display(true);
- break;
- case 2:
- continuous_sweep();
- break;
- case 3:
- configure_modes();
- update_display(true);
- break;
- case 4:
- set_velocity();
- update_display(true);
- break;
- case 5:
- test_encoders();
- break;
- default:
- Serial.println("Invalid choice.");
- break;
- }
- update_display(false);
- print_menu();
- }
Advertisement