TMC 2209 driver

I’ve just finished a successful arduino project using the TMC2209 stepper driver and it works fabulously. The stallguard feature and the silent drive is really impressive. I’d like to think I might be able to port it over to esphome but have not found a lot after multiple searches. I did find a github home here:

But I’d like to talk to people who have actually done it with esphome and preferably the 8266 chip/Wemos d1 mini…

1 Like

The custom component you linked will no longer work with standard ESPHome (without some mucking around). Custom components were depreciated at the start of this year - they have been replaced by External Components.

You can read about writing your own here:

1 Like

Hello, I am currently facing the same task. The only thing I have found is what you have. So it should actually work. Currently I have the problem that the Uart communication does not work… but I have to try again with the d1 mini and the tmc2209 directly via arduino. Maybe I have an error (solder bridge?) etc. I also saw that adafruit has a stepper board with the 2209…
Unfortunately I haven’t had time the last few days… but according to your link, it should work. Have you managed it yet?
I had already thought about using an arduino in between via Uart or i2c, which changes the commands for esphome d1 mini, resp. enables communication.
I had done this with a sensor where i2c no longer worked.

I am surprised that there is still no official integration for the TMC2209… there are many who are looking for it and the chip has been around for a long time.

Best regards

with arduino, I got it running once I had uart set up correctly.

Hy, did you get it to work with the d1 mini? I tried it all day today, the communication via uart doesn’t really work for me… I can send, but sometimes no information arrives and is not processed… I have tried various things, other pins, baud rates etc… I suspect the chip is too slow… or I am simply doing something wrong (gpt is no help there either).
I also installed the gidhub files locally, but left it for today. Do you have a code that works?
Or did you use an esp32?

Greetings

I have not progressed on this issue - too many other esphome devices causing trouble.
I got it working fine on arduino. I’ve heard of people getting it to work on the esp32 - but not the d1_mini.

Yesterday I also got it via arduino with the nodemuc-esp32, because I thought I could do it (there are some instructions) with tmcstepper (I think it was called that) it worked except for the stallguard communication via uart… via seriel I got it working, but the serial monitor only had values between 33-80 and sometimes 0… when I stopped nothing happened. I also used the trinamic stepper motor from a kit and was no better… then I used a different library, but it didn’t work any better either… but I didn’t really get uart to deliver clean values, but I think it was my code… do you have a working code for the arduino? Can test it this week with the muc… should work…

thoughts on stallguard:
it requires the right combination of speed, velocity and sensitivity to work properly. Too slow and it doesn’t work at all.
I used several libraries to get it all working, and pay attention to the values it sets for the motor speed etc.

I used the following sketch:


#include <Arduino.h>
#include <FastAccelStepper.h>
#include <HardwareSerial.h>
#include <TMCStepper.h>
#include <SPI.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Motor Pins
#define STEP_PIN 8
#define DIR_PIN 7


#define ENABLE_PIN 9
#define STALLGUARD 3
#define RXD2 17
#define TXD2 16

// Switch Pins
#define UP_SWITCH 18
#define DOWN_SWITCH 19
#define E_STOP_SWITCH 12

// LED Pins
#define RED_LED 10
#define GREEN_LED 14

#define R_SENSE 0.11f
#define DRIVER_ADDRESS 0b00

const long set_velocity = 30000;
const int set_acceleration = 8000;
const int set_current = 650;
const int set_stall = 2;
const long set_tcools = 150;
const int motor_microsteps = 64;
const int full_steps_per_revolution = 200;

// Leadscrew pitch
const float LEADSCREW_PITCH_MM = 4.0; // 4mm per revolution
const float MM_TO_INCH = 0.0393701;
const float DISTANCE_PER_MICROSTEP_MM = LEADSCREW_PITCH_MM / (full_steps_per_revolution * motor_microsteps); // 0.0003125mm/microstep

// LCD Setup - 4x20 LCD
LiquidCrystal_I2C lcd(0x27, 20, 4);

volatile bool stalled_motor = false;
static bool moving_up = false;
static bool moving_down = false;
static long current_position = 0;
static float position_inches = 0.0;

enum LastSwitch { NONE, UP, DOWN, ESTOP };
static LastSwitch last_switch_pressed = NONE;

enum State { INIT_TOP, MOVE_BOTTOM, MOVE_TOP, IDLE };
static State current_state = INIT_TOP;

#define SERIAL_PORT_2 Serial2
FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *stepper = NULL;
TMC2209Stepper driver(&SERIAL_PORT_2, R_SENSE, DRIVER_ADDRESS);

void stalled_position() {
  stalled_motor = true;
  Serial.println("STALL DETECTED!");
}

void setup() {
  Serial.begin(115200);
  SERIAL_PORT_2.begin(115200);

  pinMode(RED_LED, OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  digitalWrite(RED_LED, HIGH);
  digitalWrite(GREEN_LED, LOW);

  Wire.begin();
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Starting...");
  delay(1000);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Ready");

  pinMode(UP_SWITCH, INPUT_PULLUP);
  pinMode(DOWN_SWITCH, INPUT_PULLUP);
  pinMode(E_STOP_SWITCH, INPUT_PULLUP);

  pinMode(ENABLE_PIN, OUTPUT);
  pinMode(STALLGUARD, INPUT);
  attachInterrupt(digitalPinToInterrupt(STALLGUARD), stalled_position, RISING);

  driver.begin();
  driver.toff(4);
  driver.blank_time(24);
  driver.I_scale_analog(false);
  driver.internal_Rsense(false);
  driver.mstep_reg_select(true);
  driver.microsteps(motor_microsteps);
  driver.TPWMTHRS(0);
  driver.semin(0);
  driver.shaft(true);
  driver.en_spreadCycle(false);
  driver.pdn_disable(true);
  driver.VACTUAL(0);
  driver.rms_current(set_current);
  driver.SGTHRS(set_stall);
  driver.TCOOLTHRS(set_tcools);

  engine.init();
  stepper = engine.stepperConnectToPin(STEP_PIN);
  stepper->setDirectionPin(DIR_PIN);
  stepper->setEnablePin(ENABLE_PIN);
  stepper->setAutoEnable(true);
  stepper->setSpeedInHz(set_velocity);
  stepper->setAcceleration(set_acceleration);

  digitalWrite(RED_LED, LOW);
  Serial.println("Initialization complete. Moving to top...");
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Moving to Top");
  stepper->runBackward(); // Move up
  moving_up = true;
}

void loop() {
  static bool firstLoop = true;
  if (firstLoop) {
    digitalWrite(GREEN_LED, HIGH);
    firstLoop = false;
  }

  bool up_state = digitalRead(UP_SWITCH);
  bool down_state = digitalRead(DOWN_SWITCH);
  bool estop_state = digitalRead(E_STOP_SWITCH);

  // Update position
  current_position = stepper->getCurrentPosition();
  float distance_mm = current_position * DISTANCE_PER_MICROSTEP_MM;
  position_inches = distance_mm * MM_TO_INCH;
  if (position_inches < 0) position_inches = -position_inches; // Absolute value

  // Debug position during motion
  if (moving_up || moving_down) {
    Serial.print("Steps: ");
    Serial.print(current_position);
    Serial.print(", Distance (in): ");
    Serial.println(position_inches, 4);
  }

  // State machine
  switch (current_state) {
    case INIT_TOP:
      lcd.setCursor(0, 2);
      lcd.print("Pos: ");
      lcd.print(position_inches, 2);
      lcd.print(" in      ");
      if (stalled_motor) {
        stepper->forceStop();
        stepper->setCurrentPosition(0);
        current_position = 0;
        moving_up = false;
        stalled_motor = false;
        Serial.println("Top reached. Zero set. Moving to bottom...");
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Moving to Bottom");
        lcd.setCursor(0, 2);
        lcd.print("Pos: ");
        lcd.print(position_inches, 2);
        lcd.print(" in");
        stepper->runForward(); // Move down
        moving_down = true;
        current_state = MOVE_BOTTOM;
      }
      break;

    case MOVE_BOTTOM:
      lcd.setCursor(0, 2);
      lcd.print("Pos: ");
      lcd.print(position_inches, 2);
      lcd.print(" in      ");
      if (stalled_motor) {
        stepper->forceStop();
        long bottom_position = stepper->getCurrentPosition();
        float distance_traveled_mm = bottom_position * DISTANCE_PER_MICROSTEP_MM;
        float distance_traveled_in = distance_traveled_mm * MM_TO_INCH;
        if (distance_traveled_in < 0) distance_traveled_in = -distance_traveled_in;
        Serial.print("Bottom reached. Distance traveled: ");
        Serial.print(distance_traveled_in, 4);
        Serial.println(" inches");
        Serial.print("Steps traveled: ");
        Serial.println(bottom_position);
        stepper->setCurrentPosition(0);
        current_position = 0;
        moving_down = false;
        stalled_motor = false;
        Serial.println("Moving back to top...");
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Moving to Top");
        lcd.setCursor(0, 2);
        lcd.print("Pos: ");
        lcd.print(position_inches, 2);
        lcd.print(" in");
        stepper->runBackward(); // Move up
        moving_up = true;
        Serial.println("Transitioning to MOVE_TOP state");
        current_state = MOVE_TOP;
      }
      break;

    case MOVE_TOP:
      lcd.setCursor(0, 2);
      lcd.print("Pos: ");
      lcd.print(position_inches, 2);
      lcd.print(" in      ");
      if (stalled_motor) {
        stepper->forceStop();
        moving_up = false;
        stalled_motor = false;
        Serial.println("Top reached again. Awaiting instructions...");
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Ready");
        lcd.setCursor(0, 2);
        lcd.print("Pos: ");
        lcd.print(position_inches, 2);
        lcd.print(" in");
        current_state = IDLE;
      }
      break;

    case IDLE:
      lcd.setCursor(0, 0);
      lcd.print("Ready           "); // Extra spaces to clear previous text
      lcd.setCursor(0, 2);
      lcd.print("Pos: ");
      lcd.print(position_inches, 2);
      lcd.print(" in      ");
      static bool last_estop_state = HIGH;
      if (estop_state == LOW && last_estop_state == HIGH && !stalled_motor && last_switch_pressed != ESTOP) {
        stepper->forceStop();
        moving_up = false;
        moving_down = false;
        last_switch_pressed = ESTOP;
        Serial.println("Emergency Stop Activated!");
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("ESTOP");
        lcd.setCursor(0, 2);
        lcd.print("Pos: ");
        lcd.print(position_inches, 2);
        lcd.print(" in");
        while (digitalRead(E_STOP_SWITCH) == LOW) {
          delay(100);
        }
        Serial.println("Emergency Stop Released. Awaiting instructions...");
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Ready");
        lcd.setCursor(0, 2);
        lcd.print("Pos: ");
        lcd.print(position_inches, 2);
        lcd.print(" in");
      } else if (estop_state == LOW && last_estop_state == HIGH && last_switch_pressed == ESTOP) {
        Serial.println("Repeated E-Stop press ignored...");
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("ESTOP Ignored");
        lcd.setCursor(0, 2);
        lcd.print("Pos: ");
        lcd.print(position_inches, 2);
        lcd.print(" in");
        while (digitalRead(E_STOP_SWITCH) == LOW) {
          delay(100);
        }
        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("Ready");
        lcd.setCursor(0, 2);
        lcd.print("Pos: ");
        lcd.print(position_inches, 2);
        lcd.print(" in");
      }
      last_estop_state = estop_state;

      if (estop_state == HIGH) {
        if (up_state == LOW && !moving_up && !moving_down) {
          Serial.println("UP switch pressed. Starting upward motion...");
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Going Up");
          lcd.setCursor(0, 2);
          lcd.print("Pos: ");
          lcd.print(position_inches, 2);
          lcd.print(" in");
          stalled_motor = false;
          moving_up = true;
          last_switch_pressed = UP;
          stepper->runBackward();
        } else if (down_state == LOW && !moving_down && !moving_up) {
          Serial.println("DOWN switch pressed. Starting downward motion...");
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print("Going Down");
          lcd.setCursor(0, 2);
          lcd.print("Pos: ");
          lcd.print(position_inches, 2);
          lcd.print(" in");
          stalled_motor = false;
          moving_down = true;
          last_switch_pressed = DOWN;
          stepper->runForward();
        } else if (moving_up) {
          if (down_state == LOW || stalled_motor) {
            stepper->forceStop();
            moving_up = false;
            last_switch_pressed = (down_state == LOW) ? DOWN : NONE;
            Serial.println(down_state == LOW ? "DOWN switch pressed. Stopping..." : "Stalled. Stopping...");
            lcd.clear();
            lcd.setCursor(0, 0);
            lcd.print("Ready");
            lcd.setCursor(0, 2);
            lcd.print("Pos: ");
            lcd.print(position_inches, 2);
            lcd.print(" in");
          }
        } else if (moving_down) {
          if (up_state == LOW || stalled_motor) {
            stepper->forceStop();
            moving_down = false;
            last_switch_pressed = (up_state == LOW) ? UP : NONE;
            Serial.println(up_state == LOW ? "UP switch pressed. Stopping..." : "Stalled. Stopping...");
            lcd.clear();
            lcd.setCursor(0, 0);
            lcd.print("Ready");
            lcd.setCursor(0, 2);
            lcd.print("Pos: ");
            lcd.print(position_inches, 2);
            lcd.print(" in");
          }
        }
      }
      break;
  }

  if (moving_up || moving_down) {
    Serial.print("SG_RESULT: "); Serial.println(driver.SG_RESULT());
    Serial.print("TSTEP: "); Serial.println(driver.TSTEP());
  }

  delay(10);
}

void moveUp() {}
void moveDown() {}

Hi! I’m the author of the linked component for esphome/tmc2209. Everything from the repo/component will continue to work as it is meant to be linked as an ‘external component’ and not imported as a ‘custom component’

You are correct on ‘custom components’ are deprecated, but that isn’t relevant here.

1 Like