MaixPy tmc2209 单串口驱动使用介绍

Update history
Date Version Author Update content
2024-08-21 1.0.0 iawak9lkm 初版文档

Introduction to TMC2209

TMC2209 is a stepper motor driver chip produced by the German company Trinamic. It is designed specifically for 2-phase stepper motors, featuring low power consumption, high efficiency, and excellent noise suppression capabilities. TMC2209 supports currents up to 2.8A, making it suitable for various applications such as 3D printers, CNC machines, robots, etc.

Using TMC2209 to Drive Stepper Motors in MaixPy

  • Ensure that your stepper motor is a 2-phase 4-wire type, and confirm the step angle of your motor (step_angle), the microstepping resolution you need (micro_step), and the distance the load moves per revolution of the motor (screw_pitch or round_mm). This information will help us configure the driver parameters later.

  • Generally, TMC2209 driver boards on the market have the following pins (if you find it troublesome, you can purchase our TMC2209 driver board, link [not yet available,敬请期待]):

            ---------
         EN-|       |-VM
        MS1-|       |-GND
        MS2-|       |-2B
         RX-|       |-2A
         TX-|       |-1A
         NC-|       |-1B
       STEP-|       |-VDD
        DIR-|       |-GND
            ---------
    

    EN: EN is the enable pin. Connect this pin to GND to enable TMC2209 hardware-wise.

    MS1: MS1 is one of the microstepping selection pins, used in conjunction with the MS2 pin to set the microstepping mode of the stepper motor.

    MS2: MS2 is one of the microstepping selection pins, used in conjunction with the MS1 pin to set the microstepping mode of the stepper motor.

    This driver program only uses the UART mode of TMC2209. In this mode, the two microstep selection pins are respectively AD0 (originally MS1) and AD1 (originally MS2). The level states of these two pins together form the UART address of the TMC2209, ranging from 0x00 to 0x03.

    TX: TX is the serial communication transmit pin, used for communication with an external microcontroller via UART.

    RX: RX is the serial communication receive pin, used for communication with an external microcontroller via UART.

    When using both RX and TX on TMC2209, ensure there is a 1K ohm resistor between the RX of the TMC2209 driver board and the TX of the main control chip. Otherwise, communication data anomalies may occur.

    NC: NC is the no-connect pin, indicating that this pin does not need to be connected during normal use.

    STEP: STEP is the step signal input pin. Each pulse received advances the stepper motor by one step angle. Since this driver is purely UART-driven, this pin does not need to be connected and can be left floating.

    DIR: DIR is the direction signal input pin, used to control the rotation direction of the stepper motor. When DIR is high, the motor rotates clockwise; when DIR is low, the motor rotates counterclockwise. Since this driver is purely UART-driven, this pin does not need to be connected and can be left floating.

    VM: VM is the power input pin, connected to the positive terminal of the stepper motor's power supply.

    GND: GND is the ground pin, connected to the negative terminal of the power supply.

    2B, 2A, 1B, 1A: These pins are the phase output pins of the stepper motor, connected to the two phases of the motor's coils.

    VDD: VDD is the logic power input pin, providing power to the internal logic circuits of the chip.

  • Using TMC2209 Driver in MaixPy

As an example, let's consider a stepper motor with a step angle of 18, a microstep resolution of 256, and a screw pitch of 3mm:

from maix import pinmap, ext_dev, err, time

port = "/dev/ttyS1"
uart_addr = 0x00
uart_baudrate = 115200
step_angle = 18
micro_step = 256
screw_pitch = 3
speed = 6
use_internal_sense_resistors = True
run_current_per = 100
hold_current_per = 100


if port == "/dev/ttyS1":
    ret = pinmap.set_pin_function("A19", "UART1_TX")
    if ret != err.Err.ERR_NONE:
        print("Failed in function pinmap...")
        exit(-1)
    ret = pinmap.set_pin_function("A18", "UART1_RX")
    if ret != err.Err.ERR_NONE:
        print("Failed in function pinmap...")
        exit(-1)

slide = ext_dev.tmc2209.ScrewSlide(port, uart_addr, uart_baudrate,
                            step_angle, micro_step, screw_pitch, speed,
                            use_internal_sense_resistors, run_current_per, hold_current_per)

def reset_callback() -> bool:
    if 2 > 1:   # An event occurs (e.g., a sensor is triggered),
                # indicating that the slide has moved to the boundary and the motor needs to stop.
        print("Reset finish...")
        return True
    # Not occurred, no need to stop the motor.
    return False

def move_callback(per:float) -> bool:
    # per is the percentage of the current distance moved by move()
    # out of the total distance required for the current move(), ranging from 0 to 100.
    print(f"Slide moving... {per}")
    if per >= 50: # Example: Stop moving when 50% of the total distance for the current move() has been covered.
        print(f"{per} >= 50%, stop.")
        return True
    return False


slide.reset(reset_callback)

slide.move(screw_pitch*2, -1, move_callback)
slide.move(-screw_pitch)

while True:
    slide.move(screw_pitch*2)
    slide.move(-(screw_pitch*2))
    time.sleep_ms(100)

First, ensure that UART1 is enabled using pinmap in the program.

Then create a ScrewSlide object, using the internal reference resistor by default, and defaulting to 100% of the motor's running current and 100% of the motor's holding current. These parameters may need to be adjusted according to your motor.

Next, the routine declares a reset callback function and a move callback function, which are respectively passed into the reset() function and move() function. The reset() and move() functions call the callback functions periodically to confirm whether the motor needs to be stopped immediately (when the callback function returns True).

Both the move() and reset() functions are blocking functions, and they will only stop the motor and return when the callback function returns True (or when the specified length of movement is completed in the case of move()).

Using tmc2209 Driver for Stepper Motors with Constant Load in MaixPy

!!!Screw stepper motors with constant load should not be considered as stepper motors with constant load, because screw stepper motors have limit devices to ensure the direction of motion of the load on the rod is known, and the screw stepper motor often collides with the limit device during operation, causing the motor load to not be constant. Other cases can be deduced by analogy to know whether it is a stepper motor with constant load.

In some application scenarios, the load on the stepper motor is constant throughout, and only increases when it hits an edge and stalls. In such cases, you can use the Slide class instead of the ScrewSlide class, where Slide has stall detection functionality. Using ScrewSlide is also feasible, it does not have stall detection but is more flexible. Please choose between these two classes based on the usage scenario; this section only discusses the Slide class.

  • Implementation Principle

The TMC2209 has an internal register SG_RESULT, which stores data proportional to the remaining torque of the motor. If the motor load is constant, the variation in the register value is very small. When the motor stalls, the register value will rapidly decrease and maintain a lower value. By finding the running average value and stall average value of this register for the constant load motor, you can measure whether the motor is stalling at any given moment.

  • Obtaining the Average Value of the SG_RESULT Register

The maix.ext_dev.tmc2209 module provides a function to obtain and save this average value, maix.ext_dev.tmc2209.slide_scan.

example:

from maix import ext_dev, pinmap, err

port = "/dev/ttyS1"
uart_addr = 0x00
uart_baudrate = 115200
step_angle = 1.8
micro_step = 256
round_mm = 60
speed = 60
use_internal_sense_resistors = True
run_current_per = 100
hold_current_per = 100

if port == "/dev/ttyS1":
    ret = pinmap.set_pin_function("A19", "UART1_TX")
    if ret != err.Err.ERR_NONE:
        print("Failed in function pinmap...")
        exit(-1)
    ret = pinmap.set_pin_function("A18", "UART1_RX")
    if ret != err.Err.ERR_NONE:
        print("Failed in function pinmap...")
        exit(-1)

ext_dev.tmc2209.slide_scan(port, uart_addr, uart_baudrate,
                           step_angle, micro_step, round_mm, speed, True,
                           True, run_current_per, hold_current_per,
                           conf_save_path='./slide_scan_example.bin', force_update=False)

After configuring the serial port and driver parameters, call slide_scan. The last parameter of slide_scan, force_update, determines the behavior when the configuration file already exists:

If force_update is True, the old configuration will be overwritten with the new configuration.

If force_update is False, the running average value will be updated to the average of the new and old values, and the stall average value will be updated to the larger of the new and old stall average values (for example, if a slide has left and right boundaries, and the left boundary stall average value is less than the right boundary stall average value, meaning the right boundary is more prone to stalling than the left boundary, the easiest stalling average value will be saved).

After running this program, the stepper motor will continue to rotate forward until it encounters a stall. Wait about 300ms, then stop the program. The program will record the running average value of the SG_RESULT register and the stall average value to conf_save_path.

Subsequently, the Slide class can load this configuration file to stop the motor when a stall is detected.

  • Verifying the Configuration File Values

You may wonder if this configuration is actually usable. The maix.ext_dev.tmc2209 module provides a function to test this configuration file, slide_test.

First, ensure the motor is in a stalled state, then modify the parameters to match those used when calling slide_scan, and execute the following code.

example

from maix import ext_dev, pinmap, err

port = "/dev/ttyS1"
uart_addr = 0x00
uart_baudrate = 115200
step_angle = 1.8
micro_step = 256
round_mm = 60
speed = 60
use_internal_sense_resistors = True
run_current_per = 100
hold_current_per = 100

if port == "/dev/ttyS1":
    ret = pinmap.set_pin_function("A19", "UART1_TX")
    if ret != err.Err.ERR_NONE:
        print("Failed in function pinmap...")
        exit(-1)
    ret = pinmap.set_pin_function("A18", "UART1_RX")
    if ret != err.Err.ERR_NONE:
        print("Failed in function pinmap...")
        exit(-1)

ext_dev.tmc2209.slide_test(port, uart_addr, uart_baudrate,
                           step_angle, micro_step, round_mm, speed, True,
                           True, run_current_per, hold_current_per,
                           conf_save_path='./slide_scan_example.bin')

The motor will stop rotating instantly upon encountering a stall, and the program will end accordingly.

The stall stop logic for Slide.move() and Slide.reset() is the same.

  • Using Slide

The approach to using Slide is essentially the same as using ScrewSlide, except that Slide removes the callback function and adds stall stop logic.

If a configuration file is not passed when using Slide, it can still be used. The stall detection threshold is the average at the start of motor operation multiplied by Slide.stop_default_per()/100. The motor stops when the recent average operation number is lower than this value. You can obtain and modify this value through Slide.stop_default_per().

from maix import pinmap, ext_dev, err, time

port = "/dev/ttyS1"
uart_addr = 0x00
uart_baudrate = 115200
step_angle = 1.8
micro_step = 256
round_mm = 60
speed = 60
use_internal_sense_resistors = True
run_current_per = 100
hold_current_per = 100

if port == "/dev/ttyS1":
    ret = pinmap.set_pin_function("A19", "UART1_TX")
    if ret != err.Err.ERR_NONE:
        print("Failed in function pinmap...")
        exit(-1)
    ret = pinmap.set_pin_function("A18", "UART1_RX")
    if ret != err.Err.ERR_NONE:
        print("Failed in function pinmap...")
        exit(-1)

slide = ext_dev.tmc2209.Slide(port, uart_addr, uart_baudrate,
                              step_angle, micro_step, round_mm, speed,
                              cfg_file_path="./slide_conf.bin")

slide.reset()
slide.move(60)
slide.move(-60)

Notes

This driver is implemented purely through UART, offering the advantage of requiring fewer pins to drive up to 4 motors with relatively high precision. The downside is that it is not suitable for applications requiring extremely high precision.

Known Issues:

  • Do not use UART0 of MaixCAM as the driver's serial port, as it may cause MaixCAM to fail to boot properly.

!!! If you find any bugs, we welcome you to submit a PR to report them.

Disclaimer

This motor driver program (hereinafter referred to as the "Program") is developed by [Sipeed] based on the BSD-3 open source license repository janelia-arduino/TMC2209. The Program is intended for learning and research purposes only and is not guaranteed to work under all environmental conditions. Users assume all risks associated with the use of this Program.

[Sipeed] shall not be liable for any losses or damages arising from the use or inability to use the Program, including but not limited to direct, indirect, incidental, special, punitive, or consequential damages.

Users should conduct sufficient testing and validation to ensure that the Program meets their specific requirements and environment before using it in practical applications. [Sipeed] makes no express or implied warranties regarding the accuracy, reliability, completeness, or suitability of the Program.

Users are responsible for complying with all applicable laws and regulations when using the Program and ensuring that they do not infringe upon the legal rights of any third parties. [Sipeed] shall not be liable for any consequences resulting from users' violation of laws or infringement of third-party rights.

The interpretation of this disclaimer is reserved by [Sipeed], who also reserves the right to modify this disclaimer at any time.