janelia-arduino / TMC2209

The TMC2209 is an ultra-silent motor driver IC for two phase stepper motors with both UART serial and step and direction interfaces.
Other
151 stars 24 forks source link

Super low torque. #69

Open jkozniewski opened 1 month ago

jkozniewski commented 1 month ago

Hello - I'm testing the TMC2209 BIGTREETECH v 1.3 drivers without UART config and no matter which microstepping option I set via MS1/MS2 pins or what potentiometer value I set (tested max clockwise / counterclockwise positions) the motor has almost no torque I can stop the shaft by just gently grabbing / pressing it and any higher speed just makes the motor to vibrate instead of turning because of so low power.

I have tested the same stepper with A4988 driver and even with max microstepping it has much greater torque.

Then I have used this library and providing settings via UART and managed to get more torque setting PWM offset / gradient manually to max values:

stepper_driver.setPwmOffset(255);
stepper_driver.setPwmGradient(255);

although that makes stepper hot and much louder than with automatic settings:

stepper_driver.useInternalSenseResistors();
stepper_driver.enableAutomaticCurrentScaling();
stepper_driver.enableAutomaticGradientAdaptation();

that on the other hand give almost no torque - just like in standalone mode....

So is this default almost useless torque of TMC2209 BIGTREETECH v 1.3 something to be expected from those drivers or am I doing something terribly wrong ?

For the reference I'm using 200 steps / rev, 0.5A current 12v stepper, powered by 12v power supply rated for 3.0A and as for microcontrollers I have used Arduino UNO and Teensy 4.1 (and Pico PI in standalone only mode as there seem to be no library for TMC2209 on this platform unfortunately).

Anyone experienced same situation ? I'd be grateful for any hints of how ro optimally setup the drivers either via this lib or any hardware-related means ? BTW - I have also posted an issue at Bigtreetech github - https://github.com/bigtreetech/BIGTREETECH-TMC2209-V1.2/issues/19

aagum-bae commented 1 month ago

Check #66 and let me know if it helped

aagum-bae commented 1 month ago

Hi, I think I might know what the problem is, see datasheet pg no 38 and pg no 39 `https://www.analog.com/media/en/technical-documentation/data-sheets/TMC2209_datasheet_rev1.09.pdf

Especially pg no 39 For the automatic tuning to work properly, a tuning procedure is required first, morever are you setting the IRUNand IHOLDvalues properly in your code?

I think with your full code it will be easier to help you

jkozniewski commented 1 month ago

Hi - thanks for your feedback. In the end I've ditched UART mode, as for some reasons it started to work pretty decently in standalone mode (I guess I might did some stupid mistake with not setting ENABLE pin in the right state, curiously the motor runs regardless of wether ENABLE is high or low, which should not happen, and also in standalone mode it seems I can't really achieve true freewheeling behaviour - when disabled the the holding torque is just a bit less then when enabled....)

Another thing - I have switched to 24v since just now I've learned that I should not be worried about motor voltage rating and 12v can, and even should be driven by higher voltage - like 24v which substantially helped with the torque / speed...

Nonetheless in terms of UART and this library - i have encountered this "tuning procedure" but found none info of how should such procedure be performed - any insights regarding this matter @peterpolidoro ? As for IRUN / IHOLD values - didn't find any examples of how to set them via library is it related to this example - https://github.com/janelia-arduino/TMC2209/blob/main/examples/UnidirectionalCommunication/Standstill/Standstill.ino ? Seems that in standalone mode when disabled via ENABLE pin it defaults to something other than FREEWHEELING - my guess would be NORMAL or BRAKING ?

peterpolidoro commented 1 month ago

Hi, yes you almost always want to use a much higher voltage power supply than your motor voltage rating when the driver provides current control. The motor generates back-emf when it spins so you need higher power supply voltage to be able to supply enough current to the motor. The bigtreetech boards say they can go up to 28V, so using 28V will be able to provide the most current (torque) at higher speeds. Using a 12V power supply for a 12V motor will mean that you have almost no current available when you spin at speed so the torque will be super low and the motor might stop spinning altogether.

The tuning procedure is described in detail in the datasheet. I have found that sometimes it is easy to do properly and sometimes it is difficult, depending on the exact setup. So for this library, I default to disabling the automatic current scaling and automatic gradient adaptation because I have found they perform poorly in some setups. Anyone is free, however, to enable them and perform the tuning procedure and see if they can get it to work well.

You also have a good point about the library defaulting to low torque. Again, however, I do this on purpose to make it less likely that someone will blindly hook up a small motor and accidentally fry it. Again, anyone is free to increase the settings to use higher current as long as the power supply is up to the task.

You should be able to do anything in UART mode that you can do in standalone mode. I prefer UART mode since it gives total control over the driver, but you do have to know about the settings which can take a lot of datasheet reading. The freewheeling mode can be set with one of the library methods. The motor should freewheel when you set the hold current to zero: stepper_driver.setHoldCurrent(0); and you set the standstill mode to freewheeling: stepper_driver.setStandstillMode(stepper_driver.FREEWHEELING);

jkozniewski commented 1 month ago

Thanks for clarification - all makes sense. Though it would be great to have some code example of actually performing the automated tuning procedure (even if it's not performed by default by the library for good reason you have mentioned) - I've read the docs and it's seems to be indeed described in detail but I'm not sure I understand everything to be able to translate it into code directly and small details might probably derail whole procedure making it hard to test / debug.

Luro02 commented 1 month ago

That explains why my 2A stepper only draws around 0.5A, sometimes even less (not moving in that case) and recently it started drawing a lot more power IMG IMG_20240530_140355

I observed some weird behaviors that when I have the ESP32 connected via USB to the PC and use an external power supply the stepper is a lot slower than when I power the esp32 through a 12V -> 5V converter. All GND are connected through a ground plane on the pcb. I guess that this is related to me not doing the calibration procedure, resulting in random current draw.


From the datasheet I found this graphic, is this the calibration procedure for the current scaling? (This is for stealth chop? but the mentioned flags seem to suggest that it calibrates the current adaption?) Screenshot_2024-05-31-10-21-02-67_e2d5b3f32b79de1d45acd1fad96fbb0f

So to calibrate the stepper, you do the following:

  1. Call the enableAutomaticCurrentScaling() and enableAutomaticGradientAdaptation() (not sure if you have to enable stealth chop explicitly as well?)
  2. Then enable the driver through stepper_driver.enable() (this should charge the coils of the stepper motor, so it shouldn't move freely anymore?)
  3. Just in case, move the stepper a single step and then move it back to it's original position
  4. Wait for >130ms (I guess a delay(300) is enough?)
  5. Not sure about the PWM_GRAD_AUTO, I guess this is the enableAutomaticGradientAdaptation(), so one can skip the medium speed movement calibration?

Is this it or did I miss something?

aagum-bae commented 1 month ago

I have not tried the tuning procedure because for my application it is not required, however I have a few comments

  1. Nothing in the datasheet suggestes that stealthchop should be enlabed during tuning procedure, but stealthchop should be enabled to use it, also set TPWM_THRS to 0 to always work in stealthchop mode.
  2. Yes
  3. This step not required if standstill current redution is not enabled, stand still current reduction is enabled when the PDN_UART pin is grounded, which means you are operating the driver in stand alone mode
  4. I think it is better to do this calibration, for the TMC2209 to learn automatically the value of PWM_GRAD the function enableAutomaticGradientAdaptation() should be called

Will try it out myself when I get the time, for now I am trying to get the sensorless homing working.

Luro02 commented 4 weeks ago

I spent the last few days tweaking my code and reading the datasheet. Some things are still broken (the stepper is quite loud, even though I enabled StealthChop), but it is in a useable state.

Be careful about the current, I tried the code with a stepper motor that is rated for up to 2A (I am not even able to reach that current, likely because of the sense resistor on the pcb only working up to 1.77A). Therefore, it uses the entire range for the current from 0 to 100 (only the check_stall function would have to be adjusted and the constant for the run current).

Because you mentioned that you have low torque, I added my stall guard code that automatically increases/decreases the current when it does not have enough torque.

Not sure what your setup is, but I am pretty sure that you do not want to use the internal sense resistor. The pcbs with the chip on it, like this one image already have an external sense resistor on the pcb. image image

I get very low torque as well when I select the internal sense resistor. I haven't figured out how to configure it correctly, but the external sense resistor (selected by default) works fine.

sketch.ino ```c #include #include #include #include const uint8_t DIR_PIN = 6; const uint8_t STEP_PIN = 7; const uint8_t EN_PIN = 0; // ESP32-C6 #define RX_PIN 4 // = RX1 #define TX_PIN 5 // = TX1 // how many steps are required for a rotation (1.8° stepper requires 200) const uint16_t FULL_STEPS_FOR_ROTATION = 200; // how many microsteps should be between one step const uint16_t MICROSTEPS_PER_STEP = 256; // the number of full steps it should do per second (independent of the microsteps) const uint16_t MAX_STEPS_PER_SECOND = FULL_STEPS_FOR_ROTATION * 20; const uint16_t HALF_STEP_DURATION_MICROSECONDS = 10; // For tuning the stepper has to move fast enough to generate enough back EMF // and use the specified run current: const uint16_t FAST_STEPS_PER_SECOND = FULL_STEPS_FOR_ROTATION * 4; // How fast the stepper should accelerate to reach the specified speed. // // For example to reach a speed of 200 steps per second with an acceleration of 20, // requires 10 seconds. const float ACCELERATION_PER_STEP = 20.0; const uint8_t RUN_CURRENT_PERCENT = 40; const uint8_t HOLD_CURRENT_PERCENT = 20; // This is the default value used by the TMC2209 library. After running the calibration while connected to a PC, update this with the value PWM_OFFSET_AUTO. // // User defined PWM amplitude offset (0-255) related to full // motor current (CS_ACTUAL=31) in stand still. (Reset default=36) // // When using automatic scaling (pwm_autoscale=1) the // value is used for initialization, only. The autoscale // function starts with PWM_SCALE_AUTO=PWM_OFS and // finds the required offset to yield the target current // automatically. // // PWM_OFS = 0 will disable scaling down motor current // below a motor specific lower measurement threshold. // This setting should only be used under certain conditions, // i.e. when the power supply voltage can vary up and down // by a factor of two or more. It prevents the motor going // out of regulation, but it also prevents power down below // the regulation limit. // // PWM_OFS > 0 allows automatic scaling to low PWM duty // cycles even below the lower regulation threshold. This // allows low (standstill) current settings based on the // actual (hold) current scale (register IHOLD_IRUN). const uint8_t PWM_OFFSET = 36; // this will be used as a starting value that is adjusted with pwm_autoscale enabled // -> you can either leave it as is or read out the adjusted value (PWM_GRADIENT_AUTO) const uint8_t PWM_GRADIENT = 41; // CoolStep Duration Threshold // // This is the lower threshold velocity for switching on smart // energy CoolStep and StallGuard to DIAG output. (unsigned) // Set this parameter to disable CoolStep at low speeds, where it // cannot work reliably. The stall output signal become enabled // when exceeding this velocity. It becomes disabled again once // the velocity falls below this threshold. // TCOOLTHRS ≥ TSTEP > TPWMTHRS // - CoolStep is enabled, if configured (only with StealthChop) // - Stall output signal on pin DIAG is enabled const uint32_t TCOOLTHRS = 0; // minimum current for smart current control // // 0: 1/2 of current setting (IRUN) // Attention: use with IRUN≥10 // 1: 1/4 of current setting (IRUN) // Attention: use with IRUN≥20 const uint16_t SEMIN = 1; // If the StallGuard4 result is equal to or above // (SEMIN+SEMAX+1)*32, the motor current becomes // decreased to save energy. const uint8_t SEMAX = 0; // Sets the upper velocity for StealthChop voltage PWM mode. // TSTEP ≥ TPWMTHRS // - StealthChop PWM mode is enabled, if configured // When the velocity exceeds the limit set by TPWMTHRS, the driver // switches to SpreadCycle. // 0: Disabled const uint32_t TPWMTHRS = 0; // SGTHRS // Detection threshold for stall. The StallGuard value SG_RESULT // becomes compared to the double of this threshold. // A stall is signaled with SG_RESULT ≤ SGTHRS*2 // // Value between 0 - 255 const uint8_t SGTHRS = 45; HardwareSerial& serial_stream = Serial1; TMC2209 stepper_driver; AccelStepper stepper(AccelStepper::DRIVER, STEP_PIN, DIR_PIN); BlockNot stepper_communication_timer(500, MILLISECONDS); BlockNot led_blink_timer(300, MILLISECONDS); BlockNot stall_timer(100, MILLISECONDS); void lassert(bool value, String message) { if (!value) { Serial.println(message); while (true); } } void debug_info() { Serial.println("*************************"); TMC2209::Settings settings = stepper_driver.getSettings(); Serial.print("settings.irun_register_value = "); Serial.println(settings.irun_register_value); Serial.println("-----------------------"); Serial.print("settings.automatic_current_scaling_enabled = "); Serial.println(settings.automatic_current_scaling_enabled); Serial.print("settings.automatic_gradient_adaptation_enabled = "); Serial.println(settings.automatic_gradient_adaptation_enabled); Serial.print("settings.pwm_offset = "); Serial.println(settings.pwm_offset); Serial.print("settings.pwm_gradient = "); Serial.println(settings.pwm_gradient); Serial.print("PWM_SCALE_SUM = "); Serial.println(stepper_driver.getPwmScaleSum()); Serial.print("PWM_SCALE_AUTO = "); Serial.println(stepper_driver.getPwmScaleAuto()); Serial.print("PWM_OFFSET_AUTO = "); Serial.println(stepper_driver.getPwmOffsetAuto()); Serial.print("PWM_GRADIENT_AUTO = "); Serial.println(stepper_driver.getPwmGradientAuto()); TMC2209::Status status = stepper_driver.getStatus(); Serial.print("status.current_scaling = "); Serial.println(status.current_scaling); Serial.print("status.stealth_chop_mode = "); Serial.println(status.stealth_chop_mode); } int32_t mps2mpp(int32_t microsteps_per_second) { // convert the microsteps per second to microsteps per period (see readme.md), // the + 0.5 is for rounding the value. return (int32_t) (((float) microsteps_per_second) / 0.715 + 0.5); } void setup_stepper() { stepper_driver.setup(serial_stream); while (!serial_stream); stepper_driver.setHardwareEnablePin(EN_PIN); stepper_driver.enableCoolStep(SEMIN, SEMAX); stepper_driver.setCoolStepDurationThreshold(TCOOLTHRS); stepper_driver.enableStealthChop(); // enable at any speed for calibration (will be later updated to the constant value) stepper_driver.setStealthChopDurationThreshold(0); stepper_driver.setStallGuardThreshold(SGTHRS); // range: 0-100 stepper_driver.setRunCurrent(RUN_CURRENT_PERCENT); // for calibration they have to be the same stepper_driver.setHoldCurrent(RUN_CURRENT_PERCENT); stepper_driver.setMicrostepsPerStep(MICROSTEPS_PER_STEP); stepper_driver.moveUsingStepDirInterface(); stepper_driver.enableAutomaticCurrentScaling(); stepper_driver.disableAutomaticGradientAdaptation(); stepper_driver.setPwmOffset(PWM_OFFSET); stepper_driver.setPwmGradient(PWM_GRADIENT); stepper_driver.enable(); } void setup_calibration() { // To calibrate the automatic current adaption and stealthchop, // the stepper must be charged with the run current and not moving // for a few milliseconds. // // Normally the stepper is charged when the driver is enabled, but // I had one occurrence, where this was not the case. // // If that happened to you, uncomment the below, which will send a single // pulse to the stepper (after that it should be charged). /* // issue a single pulse for (uint32_t i = 0; i < 2; i++) { digitalWrite(STEP_PIN, !digitalRead(STEP_PIN)); delayMicroseconds(HALF_STEP_DURATION_MICROSECONDS); }*/ // For calibrating pwm_autograd (automatic gradient adaption) the following things have to be done before: // 1. PWM_OFS_AUTO has to be automatically initialized. This requires standstill at IRUN for >130ms to regulate -1 < PWM_SCALE_AUTO < 1 delay(200); lassert(stepper_driver.getSettings().irun_register_value == stepper_driver.getStatus().current_scaling, "irun_register_value must be equal to current_scaling"); // 2. Motor running and PWM_SCALE_SUM < 255 // and 1.5 * PWM_OFS_AUTO * (IRUN+1)/32 < PWM_SCALE_SUM < 4*PWM_OFS_AUTO*(IRUN+1)/32 // // Time required: About 8 fullsteps per change of +/-1? // // According to sheet 38: up to 400 fullsteps are necessary when starting from default value 14 // = 2 full rotations // start moving the motor at relatively fast speed: // stepper_driver.setMicrostepsPerStep(16); // 8 rotations in 8s // -> 200 * 16 steps for 1 rotation: stepper_driver.moveAtVelocity(mps2mpp(FAST_STEPS_PER_SECOND * MICROSTEPS_PER_STEP)); delay(1000); debug_info(); uint32_t start_time = millis(); stepper_driver.enableAutomaticGradientAdaptation(); Serial.println("Enabled automatic gradient adaption!"); while (true) { if (millis() - start_time > 8 * 1000) { Serial.println("Finished tuning!"); break; } if (stepper_communication_timer.TRIGGERED) { debug_info(); } } // stop moving, finished tuning stepper_driver.moveAtVelocity(0); debug_info(); } void setup() { pinMode(EN_PIN, OUTPUT); // hardware disable the driver initially (will be later enabled) digitalWrite(EN_PIN, HIGH); pinMode(LED_BUILTIN, OUTPUT); Serial.begin(115200); while (!Serial); pinMode(STEP_PIN, OUTPUT); stepper.setMaxSpeed(MAX_STEPS_PER_SECOND * MICROSTEPS_PER_STEP); stepper.setAcceleration(ACCELERATION_PER_STEP * MICROSTEPS_PER_STEP); while (!check_communication()) { setup_stepper(); delay(500); } setup_calibration(); stepper.setSpeed(FULL_STEPS_FOR_ROTATION * MICROSTEPS_PER_STEP); stepper_driver.setHoldCurrent(HOLD_CURRENT_PERCENT); stepper_driver.setStealthChopDurationThreshold(TPWMTHRS); } bool check_communication() { if (stepper_driver.isSetupAndCommunicating()) { //Serial.println("Stepper driver is setup and communicating!"); //Serial.println("Try turning driver power off to see what happens."); return true; } else if (stepper_driver.isCommunicatingButNotSetup()) { Serial.println("Stepper driver is communicating but not setup!"); Serial.println("Running setup again..."); Serial.println(); return false; } else { Serial.println("Stepper driver is not communicating!"); Serial.println("Try turning driver power on to see what happens."); Serial.println(); return false; } } uint8_t run_current = RUN_CURRENT_PERCENT; void check_stall() { // motor has stalled: if (stepper_driver.getStallGuardResult() <= SGTHRS * 2) { run_current = min(100, run_current + 2); stepper_driver.setRunCurrent(run_current); Serial.println("stepper has stalled"); } else if (run_current > (uint8_t) (RUN_CURRENT_PERCENT / 2)) { run_current = max((uint8_t) (RUN_CURRENT_PERCENT / 2), (uint8_t) (run_current - 1)); stepper_driver.setRunCurrent(run_current); } } void loop() { stepper.runSpeed(); if (stall_timer.TRIGGERED) { check_stall(); } if (stepper_communication_timer.TRIGGERED) { //debug_info(); Serial.print("SG_RESULT = "); Serial.println(stepper_driver.getStallGuardResult()); } if (led_blink_timer.TRIGGERED) { digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); } } ```