这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【let'sdo|2026年第1期】静音步进电机控制实践——成果帖

共1条 1/1 1 跳转至

【let'sdo|2026年第1期】静音步进电机控制实践——成果帖

专家
2026-06-18 00:28:18     打赏

● 简介

     自打上次分享过程贴【let'sdo|2026年第1期】静音步进电机控制实践过程帖以来,已经过去一段充足时间了,晚上也举行了开班仪式,今天来分享一下从简单的驱动带滑台的两相四线步进电机到通过串口发送特定指令控制滑台的位移位置。滑台步进电机是笔者从淘宝上购得的,静音驱动效果不错,稳定可靠。

● 电机简介

    电机虽然比较小巧,但结构都差不多,用来实验测试完全够用,电机参数如下:

      电压:12V

      电流:140mA

      电阻:83欧

      相数:2相4线〈A+B+ A-B-〉

      行程:30mm

      扭矩:30gf(12.5mm/s)

      步进角:18°

      电机的结构与引脚介绍图如下:

滑台电机.png

引脚分布.png

丝印.png

硬件连线

     正如上面所述,电机的接口是通过FPC排线引出的,但为了更好得与TMC2209电机驱动模块连接,笔者使用另外几根杜邦线将其管脚引出。如下图所示:

步进电机焊接引线.jpg

     关于TMC2209电机驱动模块与树莓派pico的管脚对应关系,与上期过程贴中的设置一致。

Dir  ---->    GP16

Step ---->  GP17

En  ---->    GP18

硬件连接.jpg

程序编写

    首先编写基于TMC2209模块的驱动程序tmc2209_controller.py,源码如下:

import machine
import utime
import math

class TMC2209Controller:
    """TMC2209 步进电机驱动控制器
    充分利用 STEP/DIR 控制和 UART 配置接口
    """
    MOTOR_STEP_ANGLE = 18       # 步进角: 18度/步
    SCREW_PITCH_MM = 30 / 100   # 丝杆导程: 30mm / 100步 = 0.3mm/转

    def __init__(self, step_pin, dir_pin, en_pin,
                 uart_instance=None, microsteps=8, current_ma=140):
        """
        初始化 TMC2209 控制器
        参数:
            step_pin: STEP 引脚编号
            dir_pin:  DIR 引脚编号
            en_pin:   EN  引脚编号
            uart_instance: 已初始化的 machine.UART 对象
            microsteps:     微步细分 (1, 2, 4, 8, 16, 32, 64, 128, 256)
            current_ma:     电机运行电流 (mA)
        """
        # ---- 基础控制引脚 ----
        self.step = machine.Pin(step_pin, machine.Pin.OUT)
        self.dir = machine.Pin(dir_pin, machine.Pin.OUT)
        self.en = machine.Pin(en_pin, machine.Pin.OUT)

        # ---- 高级UART配置 ----
        self.uart = uart_instance
        self.node_address = 0x00   # 默认地址, 由MS1/MS2引脚状态决定: 00, 01, 10, 11
        self.microsteps = microsteps

        # ---- 运动参数 ----
        self.current_pos_mm = 0.0

        # ========== 1. 初始化硬件状态 ==========
        self.en.value(1)       # 初始失能
        self.dir.value(0)      # 初始正向
        self.step.value(0)
        utime.sleep_ms(10)
        self.en.value(0)       # 使能驱动

        # ========== 2. 通过 UART 配置驱动器 (若提供) ==========
        if self.uart is not None:
            print("TMC2209: 正在通过UART配置驱动器...")
            self._setup_uart_config(current_ma, microsteps)
        else:
            print("TMC2209: 警告!未提供UART, 使用硬件跳帽配置。")

        print(f"TMC2209: 初始化完成。微步: {self.microsteps}, 丝杆导程: {self.SCREW_PITCH_MM:.3f}mm/步")

    def _send_uart_data(self, register, data_bytes):
        # 构建数据包: 同步字节 (0x05) + 从机地址(高5位) + 读写位 + 寄存器地址 + 数据 + CRC
        datagram = bytearray()
        datagram.append(0x05)  # 同步字节
        datagram.append((self.node_address << 1) | 0)  # 写操作
        datagram.append(register)
        datagram.extend(data_bytes)

        # 计算 CRC8 (多项式 0x07, 初始值 0x00)
        crc = self._calculate_crc8(datagram[1:])  # 从地址字节开始计算
        datagram.append(crc)

        self.uart.write(datagram)
        utime.sleep_ms(1)

    def _calculate_crc8(self, data):
        crc = 0x00
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x80:
                    crc = (crc << 1) ^ 0x07
                else:
                    crc <<= 1
                crc &= 0xFF
        return crc

    def _setup_uart_config(self, current_ma, microsteps):
        """通过UART设置驱动电流和微步细分
            - IHOLD_IRUN Register (0x10): 设置运行/保持电流
            - CHOPCON Register (0x6C): 设置微步细分和斩波模式
        """
        # 1. 配置运行电流 IRUN
        # 计算公式: IRUN = (I_rms * sqrt(2) * (R_sense + 0.02)) / 0.18
        # 一般模块使用 R_sense = 0.11Ω
        r_sense = 0.11
        i_rms = current_ma / 1000.0
        i_peak = i_rms * 1.414

        # 根据典型计算公式, IRUN 为 0-31 数值
        # 对于 140mA, 经验值大约在 2-3,为安全微调
        irun_value = int(round((i_peak / 2.5) * 31))   # 2.5V参考电压
        irun_value = max(0, min(31, irun_value))       # 限幅
        print(f"TMC2209: 计算运行电流码值: {irun_value}")

        # IHOLD 设为 IRUN 的一半 (降低保持力矩,节能)
        ihold_value = irun_value // 2

        # 构建 IHOLD_IRUN 寄存器数据 (4 字节)
        # 格式: [0x00, 0x00, IHOLD[4:0], IRUN[4:0]]
        ihold_irun_val = (ihold_value & 0x1F) | ((irun_value & 0x1F) << 8)
        ihold_irun_bytes = ihold_irun_val.to_bytes(4, 'little')
        self._send_uart_data(0x10, ihold_irun_bytes)

        # 2. 配置微步细分
        # MRES 值: 0=256, 1=128, 2=64, 3=32, 4=16, 5=8, 6=4, 7=2, 8=1
        mres_lut = {256:0, 128:1, 64:2, 32:3, 16:4, 8:5, 4:6, 2:7, 1:8}
        mres = mres_lut.get(microsteps, 5)   # 默认 1/8
        self.microsteps = microsteps

        # CHOPCON 寄存器 (0x6C) 的数据:
        # 主要设置 TOFF, HSTRT, HEND, MRES 等
        chopconf_val = 0
        chopconf_val |= 0x00004  # 设置 TOFF=4  (典型值)
        chopconf_val |= (mres << 24)  # 高位字节设置细分
        chopconf_bytes = chopconf_val.to_bytes(4, 'little')
        self._send_uart_data(0x6C, chopconf_bytes)

    def _calculate_pulses(self, distance_mm):
        """计算给定距离需要的脉冲数"""
        steps_per_mm = (360.0 / self.MOTOR_STEP_ANGLE) * self.microsteps / self.SCREW_PITCH_MM
        return int(distance_mm * steps_per_mm)

    def move_absolute(self, target_mm, speed_mm_s=2.0):
        """绝对位置移动 (s曲线加速简化版)
        参数:
            target_mm: 目标位置 (mm)
            speed_mm_s: 速度 (mm/s)
        """
        if not (0.0 <= target_mm <= 30.0):
            print("TMC2209: 目标位置超出滑台行程 (0-30mm)")
            return

        delta_mm = target_mm - self.current_pos_mm
        if abs(delta_mm) < 0.001:
            print("TMC2209: 已在目标位置")
            return

        # 设置方向
        self.dir.value(1 if delta_mm > 0 else 0)

        # 计算脉冲参数
        pulses = self._calculate_pulses(abs(delta_mm))
        pulse_interval_us = int((self.SCREW_PITCH_MM / self.microsteps) / speed_mm_s * 1_000_000)
        pulse_interval_us = max(50, pulse_interval_us)  # 硬件最小周期限制

        print(f"TMC2209: 移动 {delta_mm:.3f}mm -> {pulses} 脉冲, 间隔 {pulse_interval_us:.0f}us")
        self._generate_pulses(pulses, pulse_interval_us)
        self.current_pos_mm = target_mm

    def move_relative(self, delta_mm, speed_mm_s=2.0):
        """相对位置移动"""
        self.move_absolute(self.current_pos_mm + delta_mm, speed_mm_s)

    def _generate_pulses(self, pulses, interval_us):
        interval_us = int(interval_us)   # 确保是整数
        half = interval_us // 2
        """生成精准脉冲信号"""
        for _ in range(pulses):
            self.step.value(1)
            utime.sleep_us(half)
            self.step.value(0)
            utime.sleep_us(half)

    def enable(self):
        """使能电机 (输出力矩)"""
        self.en.value(0)

    def disable(self):
        """失能电机 (完全无电流,可自由转动)"""
        self.en.value(1)

    def set_direction(self, direction):
        """设置方向 (0: 反向/负向, 1: 正向)"""
        self.dir.value(direction)

    def get_current_position(self):
        """返回当前位置 (mm)"""
        return self.current_pos_mm

    def zero_current_position(self):
        """将当前位置设为参考零点"""
        self.current_pos_mm = 0.0
        print("TMC2209: 位置归零")

    def config_stealthchop(self, enable=True):
        """通过UART配置StealthChop2超静音模式
        参考 TMC2209 Datasheet (Rev 1.09) - GCONF Register (0x00)
        """
        if self.uart is None:
            print("TMC2209: 未提供 UART, 无法配置 StealthChop")
            return

        # 读 GCONF 寄存器
        # 写使能 EN_PWM_MODE 位
        gconf_val = 0x00000008 if enable else 0x00000000
        gconf_bytes = gconf_val.to_bytes(4, 'little')
        self._send_uart_data(0x00, gconf_bytes)
        mode = "启用" if enable else "禁用"
        print(f"TMC2209: StealthChop2 模式{mode}")

      接着在tmc2209_controller.py文件同一文件夹下,编写基本的驱动测试代码main_basic.py,实现滑台的正向与反向平移。

import machine
import time
from tmc2209_controller import TMC2209Controller

motor = TMC2209Controller(step_pin=17, dir_pin=16, en_pin=18, uart_instance=None)

motor.enable()

while True:
    # 1. 正向移动 5mm
    motor.set_direction(1)
    print("\n正向移动 5mm...")
    motor.move_relative(5.0, speed_mm_s=15.0)
    time.sleep(2)

    # 2. 反向移动 5mm
    motor.set_direction(0)
    print("反向移动 5mm...")
    motor.move_relative(-5.0, speed_mm_s=15.0)
    time.sleep(2)
    
    # 3. 正向移动 5mm
    motor.set_direction(1)
    print("\n正向移动 5mm...")
    motor.move_relative(10.0, speed_mm_s=30.0)
    time.sleep(2)

    # 4. 反向移动 5mm
    motor.set_direction(0)
    print("反向移动 5mm...")
    motor.move_relative(-10.0, speed_mm_s=30.0)
    time.sleep(2)

● 基础驱动视频

    程序实现了静音驱动滑台步进电机的正向与反向平移,稳定实时高效。

● 增加串口硬件连线

   使用Pico的GP4(TX)连接到TMC2209的RX上,实物连接只需在上面连线的基础上增加一个杜邦线,如下图所示:

带串口硬件连续.jpg

● 带串口应用程序

     基于tmc2209_controller.py驱动模块源码的封装,编写基于树莓派Pico的串口应用程序main_serial.py。

import sys
import utime
import machine
from tmc2209_controller import TMC2209Controller

# ========== 1. 初始化电机 ==========
# 使用UART配置,连接Pico的GP4(TX)到TMC2209的RX,并传入UART实例
motor = TMC2209Controller(
    step_pin=17,
    dir_pin=16,
    en_pin=18,
    uart_instance=None,      # 若不需UART配置,保持None
    microsteps=8,            # 硬件跳帽决定,此处仅用于计算
    current_ma=140
)

motor.enable()  # 使能驱动
print("TMC2209 电机已就绪。")

# ========== 2. 设置串口(USB CDC) ==========
# MicroPython默认将USB虚拟串口映射到 sys.stdin / sys.stdout
# 用 sys.stdin.read() 可读取主机发送的数据(需以换行符结尾)

def read_command():
    """从USB串口读取一行命令(非阻塞)"""
    if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
        # 有数据可读
        line = sys.stdin.readline()
        if line:
            return line.strip()
    return None

# 引入select(用于非阻塞读取)
import select

print("\n========================================")
print("串口控制已启动。可用指令:")
print("  F <距离>    : 正向移动 (mm)  例如 F 5")
print("  B <距离>    : 反向移动 (mm)  例如 B 2")
print("  S           : 停止电机 (失能)")
print("  E           : 使能电机")
print("  P           : 打印当前位置")
print("  Z           : 归零 (当前位置设为0)")
print("  H           : 显示帮助")
print("========================================")

# ========== 3. 主循环 ==========
while True:
    cmd = read_command()
    if cmd:
        parts = cmd.split()
        if len(parts) == 0:
            continue
        action = parts[0].upper()
        
        try:
            if action == 'F' and len(parts) == 2:
                dist = float(parts[1])
                if dist <= 0:
                    print("距离必须为正数")
                else:
                    print(f"正向移动 {dist:.2f} mm")
                    motor.move_relative(dist, speed_mm_s=10.0)
                    print(f"当前位置: {motor.get_current_position():.3f} mm")
            
            elif action == 'B' and len(parts) == 2:
                dist = float(parts[1])
                if dist <= 0:
                    print("距离必须为正数")
                else:
                    print(f"反向移动 {dist:.2f} mm")
                    motor.move_relative(-dist, speed_mm_s=10.0)
                    print(f"当前位置: {motor.get_current_position():.3f} mm")
            
            elif action == 'S':
                motor.disable()
                print("电机已失能,可自由转动")
            
            elif action == 'E':
                motor.enable()
                print("电机已使能")
            
            elif action == 'P':
                pos = motor.get_current_position()
                print(f"当前位置: {pos:.3f} mm")
            
            elif action == 'Z':
                motor.zero_current_position()
                print("当前位置已归零")
            
            elif action == 'H':
                print("可用指令:F <距离>, B <距离>, S, E, P, Z, H")
            
            else:
                print(f"未知指令: {cmd},输入 H 查看帮助")
        
        except ValueError:
            print("参数错误,请输入数字")
        except Exception as e:
            print(f"执行错误: {e}")
    
    # 短延时避免占用CPU过高
    utime.sleep_ms(10)

● 串口控制电机视频

      通过Thonny的串口打印窗口,输入预设的特定指令,实现对滑台步进电机的位移位置控制。控制精准,电机静音平稳移动。视频演示如下,此次项目分享到此结束,感谢EEPW与DigiKey举办"Let's do活动",通过此次活动加深了对步进电机控制的理解,熟悉了TMC2209电机驱动模块的高效应用。


共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]