Using a TMC2209 silent stepper motor driver with an arduino

You use one UART port, and different addresses. This is where you set the address, and the address is defined via the MS1 & MS2 pins. You pull them either high or low to set the address.

#define DRIVER_ADDRESS 0b00 // TMC2209 Driver address according to MS1 and MS2

Here are instructions on how its defined:

You probably confused the library by having basically two instances of it running.

@monkeyfist

Thank you.... AGAIN!!!

For the 2nd slave driver I connected MS1 to its own VIO and edited the top 15 lines and it works. Steps set correctly and the motors aren't running 4x under/4x over anymore.

I still need to use ESP.restart(). If I don't restart, the 4x faster/4x slower problem still exists. Also, the steps still won't set however, the steps for the last slave (0b01) do set correctly. So instead of last time where SERIAL1 wouldn't set the steps correctly, that problem moves to the first slave (0b00) not having the steps set correctly. I'm sure these are just personal observations/problems but they're happening :-/. Regardless, after ESP.restart() everything works fine.

I have a cheap 38pin "Inland" (Microcenter) devkit ("esp32dev" in platformio) and it always prints out garbage to SERIAL until I reset it, that seems to be the problem. The D1 Wemos ("wemos_d1_mini32") works fine however.

Thank you AGAIN!!!

The edited top 15 lines (the rest same as last post)

#include <Arduino.h>
#include <TMCStepper.h>
#include "FastAccelStepper.h"

// #define SERIAL_PORT_Z Serial1 // HardwareSerial port pins 9 & 10
#define SERIAL_PORT_X Serial2 // HardwareSerial port pins 16 & 17
#define DRIVER_ADDRESS 0b00 // TMC2209 Driver address according to MS1 and MS2
#define DRIVER_ADDRESS_Z 0b01 // shorted MS1 to VIO
#define R_SENSE 0.11f // Match to your driver
                      // SilentStepStick series use 0.11
                      // UltiMachine Einsy and Archim2 boards use 0.2
                      // Panucatt BSD2660 uses 0.1
                      // Watterott TMC5160 uses 0.075
TMC2209Stepper driver_x(&SERIAL_PORT_X, R_SENSE, DRIVER_ADDRESS);
TMC2209Stepper driver_z(&SERIAL_PORT_X, R_SENSE, DRIVER_ADDRESS_Z);

Try switching to FastAccelStepper, as its superior for ESP32 anyhow.

"This stepper driver uses mcpwm modules of the esp32: for the first three stepper motors mcpwm0, and mcpwm1 for the steppers four to six. "

As it knows how to use ESP32:s mcpwm modules and this makes it much lighter to run.

Here is my current test code using FastAccelStepper on a single motor, it has some extras but you can probably get what you need out of it. It reports actual mm/s speeds, currently its set to 4mm per revolution. So just change it in the serial reports and you get actual speeds.

Ok, now i realized that you were allready using FastAccelStepper :slight_smile:

I think your issues is probably about how and where you instruct the stepper to move. Are you using RTOS? If not, start using RTOS. In this example i also use RTOS to run the motor on a different core on its own loop, and serial reporting on another core on its own loop.

But this is just a really simple, move back & forth and set speeds & accelerations test code for my test jig.

/*
ESP32 ONLY! RUNS ON TWO CORES.
DOES NOT USE EN PIN, DIRECTLY SOLDERED TO GROUND TO KEEP IT SIMPLE.

SET SPEED, ACCELERATION & CURRENT IN SERIAL TERMINAL LIKE THIS

TO SET SPEED:
-------------
TO SET 1000, INPUT 1000
TO SET 2000, INPUT 2000
MAX 99999, INPUT 99999

ACCELERATION:
-------------
TO SET 1000, INPUT 101000
TO SET 2000, INPUT 102000
MAX 99999, INPUT 199999

CURRENT:
--------
TO SET 100mA, INPUT 200100
TO SET 1000mA, INPUT 201000;
MAX 2000mA, INPUT 202000;

*/

//LIBRARIES THAT YOU NEED
#include <TMCStepper.h>        // https://www.arduino.cc/reference/en/libraries/tmcstepper/
#include "FastAccelStepper.h" // https://www.arduino.cc/reference/en/libraries/fastaccelstepper/

//DEFINE YOUR STARTING VALUES!
int AMPS              = 2000; // SET STARTING CURRENT MAX 2000
int micro             =    0; // SET MICROSTEPS
int Maccell           = 1000; // SET STARTING ACCELERATION
int Mspeed            = 1000; // SET STARTING STEPS/S
int MMperRev          =    2; // SET MM PER REVOLUTION
int moveMM            =   57; // SET MOVEMENT IN MM
float StepsPerRoation =  200; // PHYSICAL STEPS OF MOTOR, NO MICROSTEPS INCLUDED

//SET THESE 3 PER YOUR CONNECTIONS, AND YOU ARE GOOD TO GO!:
#define DIR_PIN           4 // Direction
#define STEP_PIN          0 // Step
#define SERIAL_PORT Serial2 // HardwareSerial port

#define STALL_VALUE     255 // [0..255]
#define DRIVER_ADDRESS 0b00 // TMC2209 Driver address according to MS1 and MS2
#define R_SENSE       0.11f // 0.11 for MKS TMC2209

TMC2209Stepper driver(&SERIAL_PORT, R_SENSE, DRIVER_ADDRESS);
using namespace TMC2209_n;


FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *stepper = NULL;

unsigned long howLong=0;

TaskHandle_t Motor;
TaskHandle_t Print;
TaskHandle_t Input;

void setup() {
  
// INTITIALIZE SERIAL0 ITERFACE
Serial.begin(115200);
Serial.println(F("---------------------------------------------------"));
Serial.println(F("UART0 serial output interface intitialized @ 115200"));

// INITIALIZE SERIAL2 UART FOR TMC2209
SERIAL_PORT.begin(115200); //,SERIAL_8N1, 16, 17
Serial.println(F("UART2 interface for TMC2209 intitialized   @ 115200"));
Serial.println(F("---------------------------------------------------"));

//TMCSTEPPER SETTINGS
driver.begin();                                               

driver.toff(2);             // [1..15] enable driver in software
driver.blank_time(24);      // [16, 24, 36, 54]

driver.hysteresis_start(1); // [1..8]
driver.hysteresis_end(12);  // [-3..12]



driver.rms_current(AMPS, 0.01);  // motor RMS current "rms_current will by default set ihold to 50% of irun but you can set your own ratio with additional second argument; rms_current(1000, 0.3)."

driver.seimin(1);                // minimum current for smart current control 0: 1/2 of IRUN 1: 1/4 of IRUN

driver.semin(15);                // [0... 15] If the StallGuard4 result falls below SEMIN*32, the motor current becomes increased to reduce motor load angle.
driver.semax(15);                // [0... 15]  If the StallGuard4 result is equal to or above (SEMIN+SEMAX+1)*32, the motor current becomes decreased to save energy.

driver.sedn(4);                  // current down step speed 0-11%
driver.seup(2);                  // Current increment steps per measured StallGuard2 value 5 seup0 %00 … %11: 1, 2, 4, 8

driver.iholddelay(3);            // 0 - 15 smooth current drop

driver.TPWMTHRS(0);        // 0: Disabled, 0xFFFFF = 1048575 MAX TSTEP.
                                 // StealthChop PWM mode is enabled, if configured. When the velocity exceeds
                                 // the limit set by TPWMTHRS, the driver switches to SpreadCycle.

driver.TCOOLTHRS(0);             // 0-7 TSTEP
                                 // 0: TPWM_THRS= 0
                                 // 1: TPWM_THRS= 200
                                 // 2: TPWM_THRS= 300
                                 // 3: TPWM_THRS= 400
                                 // 4: TPWM_THRS= 500
                                 // 5: TPWM_THRS= 800
                                 // 6: TPWM_THRS= 1200
                                 // 7: TPWM_THRS= 4000
driver.pwm_autoscale(true);      // Needed for stealthChop
driver.en_spreadCycle(false);    // false = StealthChop / true = SpreadCycle
                                 
                                 
driver.microsteps(micro);        // microsteps
driver.shaft(false);              // direction
driver.intpol(true);             // interpolate to 256 microsteps
//driver.ihold(2);               // hold current  0=1/32 … 31=32/32
//driver.irun(31);  
driver.SGTHRS(STALL_VALUE);
//driver.I_scale_analog(0);   // if 5v vdd

//ACCELL STEPPER SPEED & ACCELERATION
engine.init();
stepper = engine.stepperConnectToPin(STEP_PIN);
stepper->setDirectionPin(DIR_PIN);
stepper->setSpeedInHz(Mspeed);  // STEPS PER SECOND
stepper->setAcceleration(Maccell);

if (micro!=0) {StepsPerRoation=StepsPerRoation*micro;};

xTaskCreatePinnedToCore(PrintTask, "Print", 2000,NULL,tskIDLE_PRIORITY,&Print,1);
xTaskCreatePinnedToCore(MotorTask, "Motor", 5000,NULL,5,&Motor,0);
xTaskCreatePinnedToCore(InputTask, "Input", 5000,NULL,2,&Input,1);

}

void loop() {
vTaskDelete(NULL);
}

//PRINT SERIAL--------------------------------------------------

void PrintTask(void*) {
int SSpeed;
int steppes;
float ActualSpeed;
float MaxSpeed;
for(;;) {

steppes     = driver.TSTEP();
SSpeed      = 12000000./(steppes*256.);
ActualSpeed = (SSpeed/StepsPerRoation)*MMperRev;

if (ActualSpeed > MaxSpeed) {MaxSpeed = ActualSpeed;};
if (ActualSpeed == 0)       {MaxSpeed = 0;};



Serial.print("ACTUAL");Serial.print(F(" / "));Serial.print("MAX");Serial.print(F(" / "));Serial.print("AVARAGE");Serial.print(F(" / "));Serial.println("REQUESTED");
Serial.print(ActualSpeed,2);Serial.print(F(" / "));Serial.print(MaxSpeed,2);Serial.print(F(" / "));Serial.print((moveMM/(howLong/1000.)),2);Serial.print(F(" / "));Serial.print((Mspeed/StepsPerRoation)*MMperRev,2);Serial.println(F(" MM/S"));
Serial.print(SSpeed);Serial.print(" / ");Serial.print(MaxSpeed/(MMperRev/StepsPerRoation),0);Serial.print(" / ");Serial.print((moveMM/(MMperRev/StepsPerRoation))/(howLong/1000.),0);Serial.print(" / "); Serial.print(Mspeed);Serial.println(F(" STEPS/S"));
Serial.print(60.*(SSpeed/StepsPerRoation),0);Serial.print(" / "); Serial.print(60.*((MaxSpeed/(MMperRev/StepsPerRoation))/StepsPerRoation),0);Serial.print(" / "); Serial.print((((moveMM/(MMperRev/StepsPerRoation))/(howLong/1000.))/StepsPerRoation)*60.,0);Serial.print(" / "); Serial.print(60.*(Mspeed/StepsPerRoation),0);Serial.println(F(" RPM"));
Serial.print(Maccell);Serial.println(F(" ACCELERATION"));
Serial.println("----------------------------------");
Serial.print(driver.cs2rms(driver.cs_actual()),DEC);Serial.print(" / ");Serial.print(AMPS);Serial.println(" MA MOTOR CURRENT"); 
Serial.print(stepper->getCurrentPosition());Serial.print(" / ");Serial.print(stepper->targetPos());Serial.println(" STEP POSITION");
Serial.print(driver.SG_RESULT());Serial.println(" STALLGUARD");
Serial.print(steppes);Serial.println(" TSTEP");
Serial.print(float(howLong/1000.),2);Serial.println("S LAST MOVEMENT DURATION");
Serial.print(driver.microsteps());Serial.println(F(" MICROSTEPS"));

Serial.println("----------------------------------");

vTaskDelay(100);

}
}

//CHECK FOR SERIAL COMMANDS--------------------------------------------------------

void InputTask(void*) {

for(;;)
{

int dataIn=0;

if (Serial.available()) {
dataIn = Serial.parseInt();

if (dataIn>0 && dataIn<10000) {
Mspeed=dataIn;
stepper->setSpeedInHz(Mspeed);
};

if (dataIn>=100000 && dataIn<200000 ) {
Maccell=dataIn-10000;
stepper->setAcceleration(Maccell);
};

if (dataIn>=200000 && dataIn<=202001 ) {
AMPS=dataIn-200000;
driver.rms_current(AMPS, 0.01);
};

};
vTaskDelay(5);
}

}

//MOTOR--------------------------------------------------------

void MotorTask(void*) {

for(;;)
{
  
unsigned long timeIs = millis();
stepper->moveTo(moveMM/(MMperRev/StepsPerRoation),true); // TRUE makes this a blocking function. Remove it to use it as non blocking.
howLong = millis()-timeIs;
vTaskDelay(2000);

stepper->moveTo(0,true); // 
vTaskDelay(2000);

}
}
1 Like

@spokes

I did a bit of testing on the current draw, and i'm starting to think that the lab supply does show it correctly. I simply limited the current until the motor stalled, and really dialed in the current. There is no way it was drawing anything more than that, as the current was limited.

And the max current was what the supply had shown as the max current draw.

@monkeyfist

That's my theory. The way I see it is that if the display on the supply was clipping, then the hold current wouldn't be displayed so consistently.

Maybe you could give a small write up sometime on the other driver options. I've read about all of them in the datasheet and most of them seem worth wild but I'm not 100% confident I'm using them correctly. Also, the advice to use the RTOS features seems extremely wise. I tried your code containing the RTOS apis but I'm not sure it ran correctly. Do forward declarations mess with the ESP32 RTOS? I had to put 3 above setup to get it to compile, but the motors ran erratically (or maybe that was the desired effect :-/). Either way, as you suggest, I'm going to start using the RTOS features.

@spokes
RTOS is a total game changer when you get the hang of it... no main loops anymore.

One thing to get started in those, that you in most cases need to add at least one delay to those loops, vTaskDelay(1); is usually enough. This lets other loops run while that particular loop is suspended. But just consider the amount of delay based on how much time you need for that particular loop, if it does not need to be running constantly.. just add a longer delay so other loops can run.

I recommended this because then you can for example run the motor on one core, and do all the other stuff on the other core and just set the motor targets vie a variable from the other core. This way nothing can inhibit the motor, and it has all the cpu time it needs.

In my example, the motor was set to run as a blocking mode.. hope you noticed that. Its simply because the example is so simple. Its just for me to test speeds, accelerations & currents. In actual use, you dont want to use blocking mode.

All the loops are started by these lines, so anything before them should not affect them?

xTaskCreatePinnedToCore(PrintTask, "Print", 2000,NULL,tskIDLE_PRIORITY,&Print,1);
xTaskCreatePinnedToCore(MotorTask, "Motor", 5000,NULL,5,&Motor,0);
xTaskCreatePinnedToCore(InputTask, "Input", 5000,NULL,2,&Input,1);

Though im not even sure what forward declarations are, and i'm in a bit of a fever.

Sorry I should of said function prototypes. I put the prototypes before setup(). The only other thing I seem to run consistently on the ESP32 is the AsyncWebserver. Thanks for the heads up with VTaskDelay().

Here's the prototypes I added...

void PrintTask(void*);
void InputTask(void*);
void MotorTask(void*);

You dont need those, all you need to do is declare the task handles:

TaskHandle_t Motor;
TaskHandle_t Print;
TaskHandle_t Input;

The handle is used to start or kill tasks.

The loops you can basically position anywhere in the code, they function with an infinite FOR loop. They are basically just the same as your basic main loop, expect now you can have several. Set their priorities, affine them on cores, kill & start them whenever you want etc. It gives a lot of flexibility in the code.

void MotorTask(void*) {

int loopspecificvariable=7 //you can put non global variables here for this loop before the FOR loop

for(;;)
{
loopy loop stuff
vTaskDelay(1);
}

}

You also no longer need your mainloop at all, you can just delete it:

void loop() {
vTaskDelete(NULL);
}

You can delete loops anytime you like, and start them anytime you like. So the loops that are started in the beginning, could be started at any point in time with the same commands.

BTW I updated by previous 2 core test code, its now commented a bit more to show how the serial inputs work. And possible to use microstepping, reports steps per second, mm per second and rpm. With current, max & average values.

Maybe its useful starting point or test code for people.

@monkeyfist

Have you configured StallGuard for a real world "homing" application? I've been playing with it for a few hours and it seems very hit and miss. In chapter 11 of the datasheet it only mentions using SGTHRS in tandum with SG_RESULT() to configure it. Chapter 12, "CoolStep" mentions using SG_RESULT() but it states for best results when homing to disable CoolStep, which I've done with SEMIN(0).

At the very low RPM's I'm using, SG_RESULT() is basically useless. The datasheet (11.5) does mention that too low or an erratically high RPM will not yield good results and it's not lying :-/ I'm using 0.125 RPM's and SG_RESULT() reads 0 or 2... just 0 or 2 all the time. However, the DIAG pin still fires so StallGuard is useable, just very literally touch and go. I guess I'm planning to run the motor at a higher current but when about to detect "home", drop the current down to 450 (450 is currently the sweet spot for me).

I dunno... basically it all seems very static and if you change anything at all, you have to re-calculate "home". I'm starting to wonder if I'd be better off sticking an encoder on the nema17's Vs. battling this. The application I'm using it for (picking up a CD) does have pretty tight margins. If I'm over >= .2 mm off, it wont' pick up the disc (about ~.16mm is the limit to be off).

Just curious, in the image shown, is the GND connection wrong on the ESP32?

@adouglas88 I'm trying to follow your steps here but it doesn't seem to be working. Any ideas what I'm I'm doing wrong? Nothing happens when I supply voltage to the esp and the motor. Pictures show the jumper soldering and hookup wiring to an esp32. Code was modified to communicate with Serial2.


Hi, @Adam1213
Welcome to the forum.

Can you please post a copy of your code?

Thanks.. Tom.. :smiley: :+1: :coffee: :australia:

#include <TMCStepper.h>

#define SERIAL_PORT Serial2 // HardwareSerial port 
#define DRIVER_ADDRESS 0b00 // TMC2209 Driver address according to MS1 and MS2

#define R_SENSE 0.11f // Match to your driver
                      // SilentStepStick series use 0.11
                      // UltiMachine Einsy and Archim2 boards use 0.2
                      // Panucatt BSD2660 uses 0.1
                      // Watterott TMC5160 uses 0.075

bool shaft = false;  // ONLY NEEDED FOR CHANGING DIRECTION VIA UART, NO NEED FOR DIR PIN FOR THIS

TMC2208Stepper driver(&SERIAL_PORT, R_SENSE);
int accel = 0;

void setup() {
SERIAL_PORT.begin(115200);      // INITIALIZE UART to TMC2208
Serial.begin(115200);
delay(500);
Serial.println(F("Serial Initialized"));

  driver.begin();                // Initialize driver
                           
  driver.toff(5);                 // Enables driver in software

  driver.rms_current(600);       // Set motor RMS current
  Serial.print("current set to:");
  Serial.println(driver.rms_current());
 
  driver.microsteps(256);           // Set microsteps to 1/2

  driver.pwm_autoscale(true);   // Needed for stealthChop
  driver.en_spreadCycle(true);   // Toggle spreadCycle for smooth & silent operation

}

void loop() {

uint16_t msread=driver.microsteps();
Serial.print(F("Read microsteps via UART to test UART receive : "));   
Serial.println(msread); 


driver.rms_current(600); 


//Serial.println(F("Wait 3sec and turn current low so you can turn the motor shaft"));
//driver.rms_current(10); 
//delay(3000);
Serial.println("move under tmc2208 uart interface control");
//driver.TPWMTHRS(47);//transition threshold from one pwm mode to the other one, stealthchop to spreadcyccle I think, in clock periods per 1/256 microstep, the internal clock is 12 mhz 

accel=500;
Serial.print("accel:");
Serial.println(accel);
long int spd = 102400;
Serial.print("spd:");
Serial.println(spd);
for (long int i = 0; i <=spd; i = i + accel){
driver.VACTUAL(i); //SET SPEED OF MOTOR
Serial.print("vactual set to:");
Serial.println(driver.VACTUAL());
delay(100);

}

for (long int i = spd; i >=0; i = i - accel){
driver.VACTUAL(i); //SET SPEED OF MOTOR
Serial.print("deccelerating, vactual set to:");
Serial.println(driver.VACTUAL());
delay(100);

}

shaft = !shaft; // REVERSE DIRECTION
driver.shaft(shaft); // SET DIRECTION

}

@Adam1213
Try using lower microsteps, so you can confirm is the UART interface working. It will report 256 when its not working, so if you are setting it at 256 its harder to know when the UART is not working.

Instead of using 256 microsteps, you can use interpolation:
driver.intpol(true); // interpolate to 256 microsteps
This greatly reduces noise, but does not reduce torque.

driver.pwm_autoscale(false);     // Needed for stealthChop
driver.en_spreadCycle(true);     // false = StealthChop / true = SpreadCycle

You can also turn autoscale off, by setting it at false when you are using SpreadCycle.

Also, make sure you are giving motor power before powering the ESP32 board. This is one of the best ways to waste a lot of time, as the UART connection simply won't work if you power the ESP32 first.

Hi, sorry for offtop, I control my tmc 2209 via UART serial port using ESP32 and TMC2209 library. Does the tmc2209 have a step counting function? Do you have any ideas how to implement it? Thank you.

@kolenval12

The driver itself does not count steps, you need a stepper library that does this.

Use this example, it includes FastAccelStepper library that does the step counting, acceleration & speed adjustment.

There is no way to completely control the driver via UART, you will need the STEP & DIR pins.
The UART interface is there for configuration purposes, to control the driver settings.

But I did find a way to use UART for the DIR pin in the FastAccelStepper library, will update this at some point to include this. So there would be need for only 2 wires, STEP & UART.

/*
ESP32 ONLY! RUNS ON TWO CORES.
DOES NOT USE EN PIN, DIRECTLY SOLDERED TO GROUND TO KEEP IT SIMPLE.

SET SPEED, ACCELERATION & CURRENT IN SERIAL TERMINAL LIKE THIS

TO SET SPEED:
-------------
TO SET 1000, INPUT 1000
TO SET 2000, INPUT 2000
MAX 99999, INPUT 99999

ACCELERATION:
-------------
TO SET 1000, INPUT 101000
TO SET 2000, INPUT 102000
MAX 99999, INPUT 199999

CURRENT:
--------
TO SET 100mA, INPUT 200100
TO SET 1000mA, INPUT 201000;
MAX 2000mA, INPUT 202000;

*/

//LIBRARIES THAT YOU NEED
#include <TMCStepper.h>        // https://www.arduino.cc/reference/en/libraries/tmcstepper/
#include "FastAccelStepper.h" // https://www.arduino.cc/reference/en/libraries/fastaccelstepper/

//DEFINE YOUR STARTING VALUES!
int AMPS              = 2000; // SET STARTING CURRENT MAX 2000
int micro             =    0; // SET MICROSTEPS
int Maccell           = 1000; // SET STARTING ACCELERATION
int Mspeed            = 1000; // SET STARTING STEPS/S
int MMperRev          =    2; // SET MM PER REVOLUTION
int moveMM            =   57; // SET MOVEMENT IN MM
float StepsPerRoation =  200; // PHYSICAL STEPS OF MOTOR, NO MICROSTEPS INCLUDED

//SET THESE 3 PER YOUR CONNECTIONS, AND YOU ARE GOOD TO GO!:
#define DIR_PIN           4 // Direction
#define STEP_PIN          0 // Step
#define SERIAL_PORT Serial2 // HardwareSerial port

#define STALL_VALUE     255 // [0..255]
#define DRIVER_ADDRESS 0b00 // TMC2209 Driver address according to MS1 and MS2
#define R_SENSE       0.11f // 0.11 for MKS TMC2209

TMC2209Stepper driver(&SERIAL_PORT, R_SENSE, DRIVER_ADDRESS);
using namespace TMC2209_n;


FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *stepper = NULL;

unsigned long howLong=0;

TaskHandle_t Motor;
TaskHandle_t Print;
TaskHandle_t Input;

void setup() {
  
// INTITIALIZE SERIAL0 ITERFACE
Serial.begin(115200);
Serial.println(F("---------------------------------------------------"));
Serial.println(F("UART0 serial output interface intitialized @ 115200"));

// INITIALIZE SERIAL2 UART FOR TMC2209
SERIAL_PORT.begin(115200); //,SERIAL_8N1, 16, 17
Serial.println(F("UART2 interface for TMC2209 intitialized   @ 115200"));
Serial.println(F("---------------------------------------------------"));

//TMCSTEPPER SETTINGS
driver.begin();                                               

driver.toff(2);             // [1..15] enable driver in software
driver.blank_time(24);      // [16, 24, 36, 54]

driver.hysteresis_start(1); // [1..8]
driver.hysteresis_end(12);  // [-3..12]



driver.rms_current(AMPS, 0.01);  // motor RMS current "rms_current will by default set ihold to 50% of irun but you can set your own ratio with additional second argument; rms_current(1000, 0.3)."

driver.seimin(1);                // minimum current for smart current control 0: 1/2 of IRUN 1: 1/4 of IRUN

driver.semin(15);                // [0... 15] If the StallGuard4 result falls below SEMIN*32, the motor current becomes increased to reduce motor load angle.
driver.semax(15);                // [0... 15]  If the StallGuard4 result is equal to or above (SEMIN+SEMAX+1)*32, the motor current becomes decreased to save energy.

driver.sedn(4);                  // current down step speed 0-11%
driver.seup(2);                  // Current increment steps per measured StallGuard2 value 5 seup0 %00 … %11: 1, 2, 4, 8

driver.iholddelay(3);            // 0 - 15 smooth current drop

driver.TPWMTHRS(0);        // 0: Disabled, 0xFFFFF = 1048575 MAX TSTEP.
                                 // StealthChop PWM mode is enabled, if configured. When the velocity exceeds
                                 // the limit set by TPWMTHRS, the driver switches to SpreadCycle.

driver.TCOOLTHRS(0);             // 0-7 TSTEP
                                 // 0: TPWM_THRS= 0
                                 // 1: TPWM_THRS= 200
                                 // 2: TPWM_THRS= 300
                                 // 3: TPWM_THRS= 400
                                 // 4: TPWM_THRS= 500
                                 // 5: TPWM_THRS= 800
                                 // 6: TPWM_THRS= 1200
                                 // 7: TPWM_THRS= 4000
driver.pwm_autoscale(true);      // Needed for stealthChop
driver.en_spreadCycle(false);    // false = StealthChop / true = SpreadCycle
                                 
                                 
driver.microsteps(micro);        // microsteps
driver.shaft(false);              // direction
driver.intpol(true);             // interpolate to 256 microsteps
//driver.ihold(2);               // hold current  0=1/32 … 31=32/32
//driver.irun(31);  
driver.SGTHRS(STALL_VALUE);
//driver.I_scale_analog(0);   // if 5v vdd

//ACCELL STEPPER SPEED & ACCELERATION
engine.init();
stepper = engine.stepperConnectToPin(STEP_PIN);
stepper->setDirectionPin(DIR_PIN);
stepper->setSpeedInHz(Mspeed);  // STEPS PER SECOND
stepper->setAcceleration(Maccell);

if (micro!=0) {StepsPerRoation=StepsPerRoation*micro;};

xTaskCreatePinnedToCore(PrintTask, "Print", 2000,NULL,tskIDLE_PRIORITY,&Print,1);
xTaskCreatePinnedToCore(MotorTask, "Motor", 5000,NULL,5,&Motor,0);
xTaskCreatePinnedToCore(InputTask, "Input", 5000,NULL,2,&Input,1);

}

void loop() {
vTaskDelete(NULL);
}

//PRINT SERIAL--------------------------------------------------

void PrintTask(void*) {
int SSpeed;
int steppes;
float ActualSpeed;
float MaxSpeed;
for(;;) {

steppes     = driver.TSTEP();
SSpeed      = 12000000./(steppes*256.);
ActualSpeed = (SSpeed/StepsPerRoation)*MMperRev;

if (ActualSpeed > MaxSpeed) {MaxSpeed = ActualSpeed;};
if (ActualSpeed == 0)       {MaxSpeed = 0;};



Serial.print("ACTUAL");Serial.print(F(" / "));Serial.print("MAX");Serial.print(F(" / "));Serial.print("AVARAGE");Serial.print(F(" / "));Serial.println("REQUESTED");
Serial.print(ActualSpeed,2);Serial.print(F(" / "));Serial.print(MaxSpeed,2);Serial.print(F(" / "));Serial.print((moveMM/(howLong/1000.)),2);Serial.print(F(" / "));Serial.print((Mspeed/StepsPerRoation)*MMperRev,2);Serial.println(F(" MM/S"));
Serial.print(SSpeed);Serial.print(" / ");Serial.print(MaxSpeed/(MMperRev/StepsPerRoation),0);Serial.print(" / ");Serial.print((moveMM/(MMperRev/StepsPerRoation))/(howLong/1000.),0);Serial.print(" / "); Serial.print(Mspeed);Serial.println(F(" STEPS/S"));
Serial.print(60.*(SSpeed/StepsPerRoation),0);Serial.print(" / "); Serial.print(60.*((MaxSpeed/(MMperRev/StepsPerRoation))/StepsPerRoation),0);Serial.print(" / "); Serial.print((((moveMM/(MMperRev/StepsPerRoation))/(howLong/1000.))/StepsPerRoation)*60.,0);Serial.print(" / "); Serial.print(60.*(Mspeed/StepsPerRoation),0);Serial.println(F(" RPM"));
Serial.print(Maccell);Serial.println(F(" ACCELERATION"));
Serial.println("----------------------------------");
Serial.print(driver.cs2rms(driver.cs_actual()),DEC);Serial.print(" / ");Serial.print(AMPS);Serial.println(" MA MOTOR CURRENT"); 
Serial.print(stepper->getCurrentPosition());Serial.print(" / ");Serial.print(stepper->targetPos());Serial.println(" STEP POSITION");
Serial.print(driver.SG_RESULT());Serial.println(" STALLGUARD");
Serial.print(steppes);Serial.println(" TSTEP");
Serial.print(float(howLong/1000.),2);Serial.println("S LAST MOVEMENT DURATION");
Serial.print(driver.microsteps());Serial.println(F(" MICROSTEPS"));

Serial.println("----------------------------------");

vTaskDelay(100);

}
}

//CHECK FOR SERIAL COMMANDS--------------------------------------------------------

void InputTask(void*) {

for(;;)
{

int dataIn=0;

if (Serial.available()) {
dataIn = Serial.parseInt();

if (dataIn>0 && dataIn<10000) {
Mspeed=dataIn;
stepper->setSpeedInHz(Mspeed);
};

if (dataIn>=100000 && dataIn<200000 ) {
Maccell=dataIn-100000;
stepper->setAcceleration(Maccell);
};

if (dataIn>=200000 && dataIn<=202001 ) {
AMPS=dataIn-200000;
driver.rms_current(AMPS, 0.01);
};

};
vTaskDelay(5);
}

}

//MOTOR--------------------------------------------------------

void MotorTask(void*) {

for(;;)
{
  
unsigned long timeIs = millis();
stepper->moveTo(moveMM/(MMperRev/StepsPerRoation),true); // TRUE makes this a blocking function. Remove it to use it as non blocking.
howLong = millis()-timeIs;
vTaskDelay(2000);

stepper->moveTo(0,true); // 
vTaskDelay(2000);

}
}

Here is an example of using only two wires, UART & STEP. It uses FastAccellStepper librarys setExternalCallForPin function instead of the DIR pin. For some reason, the shaft() does not always work so its been set to repeat until the change is registered so that its idiot proof.

All you need to do, is to define a function instead of the DIR pin. The currentDirection variable holds the starting direction:

stepper->setDirectionPin(128,currentDirection);
engine.setExternalCallForPin(DirectionChange);

FastAccellStepper goes to the declared function when it needs to change direction, and gives the pin number & direction. 128 and over are software pins.

bool DirectionChange(uint8_t pin, uint8_t value) {

if (pin==128) //DIR PIN 128
{
while (driver.shaft() != value)
{driver.shaft(value);vTaskDelay(1);}
}

return value;
}

So this is as close to total UART control i think we can get.

/*
ESP32 ONLY! RUNS ON TWO CORES.
DOES NOT USE EN PIN, DIRECTLY SOLDERED TO GROUND TO KEEP IT SIMPLE.

SET SPEED, ACCELERATION & CURRENT IN SERIAL TERMINAL LIKE THIS

TO SET SPEED:
-------------
TO SET 1000, INPUT 1000
TO SET 2000, INPUT 2000
MAX 99999, INPUT 99999

ACCELERATION:
-------------
TO SET 1000, INPUT 101000
TO SET 2000, INPUT 102000
MAX 99999, INPUT 199999

CURRENT:
--------
TO SET 100mA, INPUT 200100
TO SET 1000mA, INPUT 201000;
MAX 2000mA, INPUT 202000;

*/

//LIBRARIES THAT YOU NEED
#include <TMCStepper.h>        // https://www.arduino.cc/reference/en/libraries/tmcstepper/
#include "FastAccelStepper.h" // https://www.arduino.cc/reference/en/libraries/fastaccelstepper/

//DEFINE YOUR STARTING VALUES!
int AMPS              = 2000; // SET STARTING CURRENT MAX 2000
int micro             =    0; // SET MICROSTEPS
int Maccell           = 1000; // SET STARTING ACCELERATION
int Mspeed            = 1000; // SET STARTING STEPS/S
int MMperRev          =    2; // SET MM PER REVOLUTION
int moveMM            =   57; // SET MOVEMENT IN MM
float StepsPerRoation =  200; // PHYSICAL STEPS OF MOTOR, NO MICROSTEPS INCLUDED
bool currentDirection =   true; // SET MOTOR DIRECTION

//SET THESE 2 PER YOUR CONNECTIONS, AND YOU ARE GOOD TO GO!:
#define STEP_PIN          0 // Step PIN
#define SERIAL_PORT Serial2 // HardwareSerial port

#define STALL_VALUE     255 // [0..255]
#define DRIVER_ADDRESS 0b00 // TMC2209 Driver address according to MS1 and MS2
#define R_SENSE       0.11f // 0.11 for MKS TMC2209

TMC2209Stepper driver(&SERIAL_PORT, R_SENSE, DRIVER_ADDRESS);
using namespace TMC2209_n;

FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *stepper = NULL;

unsigned long howLong=0;

TaskHandle_t Motor;
TaskHandle_t Print;
TaskHandle_t Input;

void setup() {
  
// INTITIALIZE SERIAL0 ITERFACE
Serial.begin(115200);
Serial.println(F("---------------------------------------------------"));
Serial.println(F("UART0 serial output interface intitialized @ 115200"));

// INITIALIZE SERIAL2 UART FOR TMC2209
SERIAL_PORT.begin(500000); //,SERIAL_8N1, 16, 17
Serial.println(F("UART2 interface for TMC2209 intitialized   @ 500000"));
Serial.println(F("---------------------------------------------------"));

//TMCSTEPPER SETTINGS
driver.begin();                                               

driver.toff(2);             // [1..15] enable driver in software
driver.blank_time(24);      // [16, 24, 36, 54]

driver.hysteresis_start(1); // [1..8]
driver.hysteresis_end(12);  // [-3..12]



driver.rms_current(AMPS, 0.01);  // motor RMS current "rms_current will by default set ihold to 50% of irun but you can set your own ratio with additional second argument; rms_current(1000, 0.3)."

driver.seimin(1);                // minimum current for smart current control 0: 1/2 of IRUN 1: 1/4 of IRUN

driver.semin(15);                // [0... 15] If the StallGuard4 result falls below SEMIN*32, the motor current becomes increased to reduce motor load angle.
driver.semax(15);                // [0... 15]  If the StallGuard4 result is equal to or above (SEMIN+SEMAX+1)*32, the motor current becomes decreased to save energy.

driver.sedn(4);                  // current down step speed 0-11%
driver.seup(2);                  // Current increment steps per measured StallGuard2 value 5 seup0 %00 … %11: 1, 2, 4, 8

driver.iholddelay(3);            // 0 - 15 smooth current drop

driver.TPWMTHRS(0);        // 0: Disabled, 0xFFFFF = 1048575 MAX TSTEP.
                                 // StealthChop PWM mode is enabled, if configured. When the velocity exceeds
                                 // the limit set by TPWMTHRS, the driver switches to SpreadCycle.

driver.TCOOLTHRS(0);             // 0-7 TSTEP
                                 // 0: TPWM_THRS= 0
                                 // 1: TPWM_THRS= 200
                                 // 2: TPWM_THRS= 300
                                 // 3: TPWM_THRS= 400
                                 // 4: TPWM_THRS= 500
                                 // 5: TPWM_THRS= 800
                                 // 6: TPWM_THRS= 1200
                                 // 7: TPWM_THRS= 4000
driver.pwm_autoscale(true);      // Needed for stealthChop
driver.en_spreadCycle(false);    // false = StealthChop / true = SpreadCycle
                                 
                                 
driver.microsteps(micro);        // microsteps
driver.shaft(currentDirection);  // direction
driver.intpol(true);             // interpolate to 256 microsteps
//driver.ihold(2);               // hold current  0=1/32 … 31=32/32
//driver.irun(31);  
driver.SGTHRS(STALL_VALUE);
//driver.I_scale_analog(0);   // if 5v vdd

//ACCELL STEPPER SPEED & ACCELERATION
engine.init();
stepper = engine.stepperConnectToPin(STEP_PIN);
stepper->setDirectionPin(128,currentDirection);
engine.setExternalCallForPin(DirectionChange);
stepper->setSpeedInHz(Mspeed);  // STEPS PER SECOND
stepper->setAcceleration(Maccell);

if (micro!=0) {StepsPerRoation=StepsPerRoation*micro;};

xTaskCreatePinnedToCore(PrintTask, "Print", 2000,NULL,tskIDLE_PRIORITY,&Print,1);
xTaskCreatePinnedToCore(MotorTask, "Motor", 5000,NULL,5,&Motor,0);
xTaskCreatePinnedToCore(InputTask, "Input", 5000,NULL,2,&Input,1);

}

void loop() {
vTaskDelete(NULL);
}

//PRINT SERIAL--------------------------------------------------

void PrintTask(void*) {
int SSpeed;
int steppes;
float ActualSpeed;
float MaxSpeed;
for(;;) {

steppes     = driver.TSTEP();
SSpeed      = 12000000./(steppes*256.);
ActualSpeed = (SSpeed/StepsPerRoation)*MMperRev;

if (ActualSpeed > MaxSpeed) {MaxSpeed = ActualSpeed;};
if (ActualSpeed == 0)       {MaxSpeed = 0;};



Serial.print("ACTUAL");Serial.print(F(" / "));Serial.print("MAX");Serial.print(F(" / "));Serial.print("AVARAGE");Serial.print(F(" / "));Serial.println("REQUESTED");
Serial.print(ActualSpeed,2);Serial.print(F(" / "));Serial.print(MaxSpeed,2);Serial.print(F(" / "));Serial.print((moveMM/(howLong/1000.)),2);Serial.print(F(" / "));Serial.print((Mspeed/StepsPerRoation)*MMperRev,2);Serial.println(F(" MM/S"));
Serial.print(SSpeed);Serial.print(" / ");Serial.print(MaxSpeed/(MMperRev/StepsPerRoation),0);Serial.print(" / ");Serial.print((moveMM/(MMperRev/StepsPerRoation))/(howLong/1000.),0);Serial.print(" / "); Serial.print(Mspeed);Serial.println(F(" STEPS/S"));
Serial.print(60.*(SSpeed/StepsPerRoation),0);Serial.print(" / "); Serial.print(60.*((MaxSpeed/(MMperRev/StepsPerRoation))/StepsPerRoation),0);Serial.print(" / "); Serial.print((((moveMM/(MMperRev/StepsPerRoation))/(howLong/1000.))/StepsPerRoation)*60.,0);Serial.print(" / "); Serial.print(60.*(Mspeed/StepsPerRoation),0);Serial.println(F(" RPM"));
Serial.print(Maccell);Serial.println(F(" ACCELERATION"));
Serial.println("----------------------------------");
Serial.print(driver.cs2rms(driver.cs_actual()),DEC);Serial.print(" / ");Serial.print(AMPS);Serial.println(" MA MOTOR CURRENT"); 
Serial.print(stepper->getCurrentPosition());Serial.print(" / ");Serial.print(stepper->targetPos());Serial.println(" STEP POSITION");
Serial.print(driver.SG_RESULT());Serial.println(" STALLGUARD");
Serial.print(steppes);Serial.println(" TSTEP");
Serial.print(float(howLong/1000.),2);Serial.println("S LAST MOVEMENT DURATION");
Serial.print(driver.microsteps());Serial.println(F(" MICROSTEPS"));

Serial.println("----------------------------------");

vTaskDelay(100);

}
}

//CHECK FOR SERIAL COMMANDS--------------------------------------------------------

void InputTask(void*) {

for(;;)
{

int dataIn=0;

if (Serial.available()) {
dataIn = Serial.parseInt();

if (dataIn>0 && dataIn<10000) {
Mspeed=dataIn;
stepper->setSpeedInHz(Mspeed);
};

if (dataIn>=100000 && dataIn<200000 ) {
Maccell=dataIn-100000;
stepper->setAcceleration(Maccell);
};

if (dataIn>=200000 && dataIn<=202001 ) {
AMPS=dataIn-200000;
driver.rms_current(AMPS, 0.01);
};

};
vTaskDelay(5);
}

}

//MOTOR--------------------------------------------------------

void MotorTask(void*) {

for(;;)
{
  
unsigned long timeIs = millis();
stepper->moveTo(moveMM/(MMperRev/StepsPerRoation),true); // TRUE makes this a blocking function. Remove it to use it as non blocking.
howLong = millis()-timeIs;
vTaskDelay(2000);

stepper->moveTo(0,true); // 
vTaskDelay(2000);

}
}

//DIRECTION----------------------------------------------------

bool DirectionChange(uint8_t pin, uint8_t value) {

if (pin==128) //DIR PIN 128
{
while (driver.shaft() != value)
{driver.shaft(value);vTaskDelay(1);}
}

return value;
}
1 Like

Hi all!

I try to setup and use three TMC 2209 v3.1 with an ESP32 DevKitC controller.
Long story short: if i connect only one TMCs UART pin to ESP32 it works and i can change the microsteps and control speed and direction via VACTUAL command. But if i connect all the three TMC UART pins to ESP32 then nothing happens not a move and the serial monitor always report 256 microsteps (i know it's the sign that the ESP32 is not communicating with TMCs)
The connection cable is a cable with 1K resistor like in this video at 0:12 : Setting up TMC2209 in UART mode on MRR ESPA (Marlin 2.0 + ESP3D on ESP32) - YouTube
I Think it's not software error, but i need help to figure out what should i do to make it work with three TMCs.

Can you please help me figure out whats happening?

Thank you very much for every idea!

If you are running 3 drivers on single UART, you need to set different addresses for them via the MS1 & MS2 pins. And then set up individual instances of TMCstepper for all the drivers.

#define DRIVER_ADDRESS 0b00 Addresses 0-3 are available.
TMC2209Stepper driver(&SERIAL_PORT, R_SENSE, DRIVER_ADDRESS);

You can have upto 4 drivers on single UART, in binary after the 0b. So 00, 01, 10, 11 based on the MS1 & MS2.

https://www.trinamic.com/fileadmin/assets/Products/ICs_Documents/TMC2209_Datasheet_V103.pdf

1 Like

Thank you for the answer!
I set up the address via MS1 MS2 switches, if i try the arduino program with 0b00 or 0b01 it works with the addressed driver.

My problem is when i connect the above linked three way UART wire to more than one TMC the UART brakes and nothing works after.
I use this board for the TMCs, don't know maybe this is a source of the problem?
modul-stepper-driver-2