Trouble getting TMC2209 UART work with Arduino

Hi everyone,

I'm trying to get UART communication working between an Arduino and a TMC2209 stepper driver, but I can't seem to make it work.

Here’s how I’ve connected the pins:

  • PDN_UART is connected directly to Arduino RX (pin 5), and to TX (pin 6) through a 1k resistor
  • EN -> pin 7
  • STEP -> pin 4
  • DIR -> pin 3
  • MS1 and MS2 are left unconnected

I'm starting to suspect the issue might be with the driver module itself. So first, I’m attaching a couple of photos of the module, including a close-up of the PDN_UART pin under a microscope:

photos:




Below is the code I’m currently using. In this setup, I’m working with a custom PCB that has the driver soldered directly on it, along with an OLED screen and an ATmega328P.
Sorry if the code seems a bit messy — I’ve split it across several files to try and keep things organized.

main.ino:

#include <TMCStepper.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <SoftwareSerial.h>

// altre schede 
#include "oled_display.h"  
#include "motor_control.h"
#include "config.h"


#define TMC_RX_PIN    6 // Arduino TX -> TMC RX    (tramite resistenza 1Kohm)
#define TMC_TX_PIN    5 // Arduino RX <- TMC TX 

// OLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET   -1


// Setup SoftwareSerial (Rx, Tx)
SoftwareSerial tmcs(TMC_TX_PIN, TMC_RX_PIN); // RX, TX

#define R_SENSE 0.11f

TMC2209Stepper driver(&tmcs, R_SENSE, 0x00);  // Aggiunto indirizzo 0x00


void setup() {
  
  pinMode(EN_PIN, OUTPUT);
  pinMode(STEP_PIN, OUTPUT);
  pinMode(DIR_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  pinMode(TMC_RX_PIN, INPUT);
  pinMode(TMC_TX_PIN, OUTPUT);


  digitalWrite(EN_PIN, LOW); // Enable driver

  // OLED init
   setupOLED();  // Inizializza il display una sola volta

  // Setup SoftwareSerial UART a 115200 
  tmcs.begin(19200); // test 19200 prima di tornare a 115200
  delay(100);

  driver.begin();
  driver.toff(5);
  driver.rms_current(600);
  driver.en_spreadCycle(false);
  driver.pwm_autoscale(true);
  driver.microsteps(1);

 
}

unsigned long currentMillis;
unsigned long previousMillis = 0;
unsigned long oledPreviousMillis = 0; //--
const long interval = 500; // 500 ms led lampeggiante
bool ledState = false;
bool buttonPressed = false;

void loop() {
  currentMillis = millis();  

  // LED blink
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState);
  }

  // Aggiorna display
  if (currentMillis - oledPreviousMillis >= 300) {
    oledPreviousMillis = currentMillis;
    driver.microsteps(1);
    uint16_t currentMicrosteps = driver.microsteps(); // LEGGE valore dal driver
    updateOLED(currentMicrosteps);
  }

  // Pulsante
  if (digitalRead(BUTTON_PIN) == LOW && !buttonPressed) {
    buttonPressed = true;
    startMoveSteps(NUM_STEPS);
  }

  if (digitalRead(BUTTON_PIN) == HIGH) {
    buttonPressed = false;
  }

  // Gestione motore
  updateMotor();
}

config.h:

#ifndef CONFIG_H
#define CONFIG_H


// Pin
#define EN_PIN        7
#define STEP_PIN      4
#define DIR_PIN       3
#define BUTTON_PIN    9
#define LED_PIN       2
#define NUM_STEPS    2048  
#define TMC_TX_PIN    5

#endif


motor_control.cpp:

#include <Arduino.h>
#include "motor_control.h"
#include "config.h"

static int stepsToDo = 0;
static int stepsDone = 0;
static unsigned long lastStepTime = 0;
static const unsigned int stepDelayMicros = 1000; // 1ms per passo
static bool motorMoving = false;

void startMoveSteps(int steps) {
  stepsToDo = steps;
  stepsDone = 0;
  lastStepTime = micros();
  motorMoving = true;
}

void updateMotor() {
  if (!motorMoving) return;

  unsigned long now = micros();

  if (now - lastStepTime >= stepDelayMicros) {
    lastStepTime = now;

    // Un impulso
    digitalWrite(STEP_PIN, HIGH);
    delayMicroseconds(5); // breve HIGH per segnale valido
    digitalWrite(STEP_PIN, LOW);

    stepsDone++;
    if (stepsDone >= stepsToDo) {
      motorMoving = false;
    }
  }
}

bool isMotorMoving() {
  return motorMoving;
}

motor_control.h:

#ifndef MOTOR_CONTROL_H
#define MOTOR_CONTROL_H

void startMoveSteps(int steps);
void updateMotor();

bool isMotorMoving(); //

#endif

oled_display.cpp:

#include "oled_display.h"
#include "config.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>

// Config display
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1
#define OLED_ADDRESS  0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setupOLED() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
    Serial.println("ERRORE OLED!");
    return;
  }
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("OLED OK");
  display.display();
}

void updateOLED(uint16_t microsteps) {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.print("Microsteps:");
  display.setCursor(0, 30);
  display.print(microsteps);
  display.display();
}

oled_display.h:

#ifndef OLED_DISPLAY_H
#define OLED_DISPLAY_H

#include <Arduino.h>
#include <Adafruit_SSD1306.h>

extern Adafruit_SSD1306 display;

void setupOLED();
void updateOLED(uint16_t microsteps);

#endif

Any help or suggestions would be greatly appreciated. Thanks in advance!

Use a driver with better communication (SPI, I2C...). A software Serial is not very reliable.

Software serial has known problems and does not work well at any speed other then 9600. That is dependent on the oscillatory, generally not a crystal but a resonator. Take a scope and measure the timing and validate it is correct. Different Arduinos can and do operate at different speeds but are relative close to 16MHz if Uno or Nano. The TMC2209 may be subject to drift from your transmitter unless it checks baud often.

Unfortunately, I don't have an oscilloscope at the moment.
What worries me the most is the wiring. I've read different and sometimes conflicting things online about how to enable UART.

Some sources mention using a pull-up to 5V, others suggest a pull-down to GND, and there are even more variations. In other words, I'm not sure if my wiring is correct.

Could you please confirm if my current setup should work?
Thanks.

You could get by with a Logic analyzer. You can find an 8 bit unit on eBay for about $10. This is definitely a good thing to have.

Hi fedekr,

Did you ever get your tmc2209 working over uart. I have those same driver boards and haven’t been able to find any documentation on them other than the picture on aliexpress where I bought the boards. That board apparently is branded Tenstar from Tenstar Robot. Their AI chat bot didn't have any helpful information that I could get out of it. That picture shows that there isn't a pdn_uart pin but a PDN pin and a USART pin. I don't know what to make of that. If you've solved your problem please post it here. If I manage to get the boards working with uart I'll try to post my progress.

I recommend testing for communication first and only add motor motion after communication is verified. I can verify that UART does work on these driver boards and that they are controllable with SoftwareSerial. I post the sketch that I wrote just to verify the UART one wire line and the communication. For this test I have the EN pin on the TMC2209's wired to ground and do not control the TMC2209's with dedicated Nano pins.

For this Sketch, open your Serial Monitor in the Arduino IDE, load the program on the Nano and wait for the output on the Serial Monitor that verifies the write/read to the chip.

The two pins for the SoftwareSerial port will be wired together with a 1k resistor. Be sure to connect the correct pin on the Nano between the Nano and the Driver boards (I wasted time because of this). Also on my setup, the pin on the V985 boards that was listening for the UART was the PDN pin. I don't know why the pinout on their website calls it that. It seems like it should be marked UART but isn't.

/*This sketch was written to test and verify if the TMC2209 chips from 
Tenstar Aliexpress actually are capable of UART communication without 
modification. This program actually works on the chips that I received
from them. I also identified the pinout of the three holes next to the
VREF adjusting pot on the driver boards which are not identified by
the aliexpress vendors that sell these chips. These chips are marked:

 V985
TMC2209

This file sets up UART communication between the Arduino Nano and two 
TMC2209 driver boards. The communication uses the hardware serial port 
to communicate between the Arduino IDE to the Nano and then 
softWareserial UART one wire communication between the Nano and the two 
TMC2209 driver boards. The 2 driver boards are addressed over one wire 
using the addresses defined. It sets values in registers on the 
TMC2209's and then, after setting them, verifies the writing by reading 
the registers from the driver boards that the settings have been stored 
in. 

The registers are read by setting the value of a variable to the value 
of the register with a statement of the form (driver1_gconf_value = 
driver1.GCONF();). The statement uses the UART because (driver1) has 
been instanciated as using the one wire SoftwareSerial instance which 
guarantees that the variable has arrived from that source. The variables 
are created prior to the writing with a value set to 0 so that when they 
are read, if the value shows the intended set value, the writing is 
thereby verified.
*/
#include <TMCStepper.h>  //This file WON'T compile when this is commented out
#include <SoftwareSerial.h>
#include <TMC2209.h>   // This file WILL compile when this is commented out
#define R_SENSE 0.10f  // Driver's sense resistors
int BaudValue = 31250;


SoftwareSerial mySerial(11, 12);  // RX, TX
#define DRIVER1_ADDRESS 0b00      // TMC2209 Driver address according to MS1 and MS2
#define DRIVER2_ADDRESS 0b10      // TMC2209 Driver address according to MS1 and MS2
TMC2209Stepper driver1(&mySerial, R_SENSE, DRIVER1_ADDRESS);
TMC2209Stepper driver2(&mySerial, R_SENSE, DRIVER2_ADDRESS);




uint32_t driver1_gconf_value = 0;  // Initialize variables for testing
uint32_t driver1_Version_Value = 0;
uint32_t driver1_rms_current_Value = 0;
uint32_t driver1_microsteps_Value = 0;
uint32_t driver1_IHOLD_Value = 0;


uint32_t driver2_gconf_value = 0;
uint32_t driver2_Version_Value = 0;
uint32_t driver2_rms_current_Value = 0;
uint32_t driver2_microsteps_Value = 0;
uint32_t driver2_IHOLD_Value = 0;

void setup() {
  //Start Serial Ports
  Serial.begin(BaudValue);    // initialize hardware serial port
  mySerial.begin(BaudValue);  // initialize software serial port3

  driver1.begin();
  driver1.pdn_disable(true);  //This line doesn't make a difference when exclusively doing testing
                              //for UART comunication, but my guess is that it will become necessary
                              //when actually moving motors,
  driver1.rms_current(400);
  driver1.microsteps(8);
  driver1.ihold(0);

  driver2.begin();
  driver2.pdn_disable(true);  //This line doesn't make a difference when exclusively doing testing
                              //for UART comunication, but my guess is that it will become necessary
                              //when actually moving motors,
  driver2.rms_current(400);
  driver2.microsteps(8);
  driver2.ihold(0);

  //Set the values

  // --------------------------


  driver1_gconf_value = driver1.GCONF();
  delay(500);
  Serial.print(" The driver1_gconf_value is \"");
  Serial.print(driver1_gconf_value);
  Serial.println("\" Printed during setup.");

  driver1_Version_Value = driver1.version();
  delay(500);
  Serial.print(" The driver1_Version_Value is \"");
  Serial.print(driver1_Version_Value);
  Serial.println("\" Printed during setup.");

  driver1_rms_current_Value = driver1.rms_current();
  delay(500);
  Serial.print(" The driver1_rms_current_Value is \"");
  Serial.print(driver1_rms_current_Value);
  Serial.println("\" Printed during setup.");

  driver1_microsteps_Value = driver1.microsteps();
  delay(500);
  Serial.print(" The driver1_microsteps_Value is \"");
  Serial.print(driver1_microsteps_Value);
  Serial.println("\" Printed during setup.");

  driver1_IHOLD_Value = driver1.ihold();
  delay(500);
  Serial.print(" The driver1_IHOLD_Value is \"");
  Serial.print(driver1_IHOLD_Value);
  Serial.println("\" Printed during setup.");

  // ---------------------------
  Serial.println();
  // ---------------------------

  driver2_gconf_value = driver2.GCONF();
  delay(500);
  Serial.print(" The driver2_gconf_value is \"");
  Serial.print(driver2_gconf_value);
  Serial.println("\" Printed during setup.");

  driver2_Version_Value = driver2.version();
  delay(500);
  Serial.print(" The driver2_Version_Value is \"");
  Serial.print(driver2_Version_Value);
  Serial.println("\" Printed during setup.");

  driver2_rms_current_Value = driver2.rms_current();
  delay(500);
  Serial.print(" The driver2_rms_current_Value is \"");
  Serial.print(driver2_rms_current_Value);
  Serial.println("\" Printed during setup.");

  driver2_microsteps_Value = driver2.microsteps();
  delay(500);
  Serial.print(" The driver2_microsteps_Value is \"");
  Serial.print(driver2_microsteps_Value);
  Serial.println("\" Printed during setup.");

  driver2_IHOLD_Value = driver1.ihold();
  delay(500);
  Serial.print(" The driver2_IHOLD_Value is \"");
  Serial.print(driver2_IHOLD_Value);
  Serial.println("\" Printed during setup.");
}

void loop() {
}

@lordpeter You mentioned that you identified the pinout of the 3 pins next to the potentiometer. Please could you post the pinout of them?

I've been trying unsuccessfully to control this same board over UART using ESPHome and this custom component. I've tried connecting my ESP32's UART RX & TX+1kΩ resistor to the PDN pin, or the USART pin, but have not been able to establish communication so far.

Hi garywoo,
Here is the pinout on my TMC2209 boards that are labeled V985. Let me know if you have any problems. You can verify this with a volt meter. The VREF pin will be responsive to a change on the potentiometer. The index pin will read an intermediate voltage 2/3 volts when the stepper is running because of the voltage pulse only on the index of a full step. That may vary depending on the step setting when it is running, I don't know. It will be zero volts between full steps. The only other one is the DIAG pin which will be difficult to get any response out of until you have the UART working.

The way I had mine hooked up was a 1k resistor between the Nano SoftwareSerial RX and TX pins (In my case Pins D9 and D10 Not the Hardware RX and TX pins) and then single wires between the PDN pins and and the Nano TX pin. Do a simple read statement to make sure the UART is hooked up correctly before trying anything else. Make a simple sketch with the minimum of libraries and only one 2209 connected just for that purpose (and archive it) before you do anything else. I wasted a lot of time having the wires between the 2209 and the Nano on the RX pin instead of the TX pin.

This code will let you know when you're in business:

I don't think the delay is necessary, it was put in in case my connection was slow to give a chance for the communication to complete. I set the variable to 0 to start with so that if anything other than zero appeared I would know that there was communication. I tried the pins on the 2209 with different combinations of MS1 and MS2 and different Nano pins. Nothing worked till I put the direct wire between the Nano SoftwareSerial TX pin instead of the RX pin. I seem to have to try every single incorrect possible way of doing something before finding the correct way.

By the way SoftwareSerial is working just fine for me. I haven't had any detectable problems with it and the Nano clone.