尧图精选

STM32F103通过UART配置TMC2208/TMC2209步进驱动实践

🕒 发布时间:2026/9/28 2:04:30 📁 来源:尧图网络
最近在给一台桌面级CNC做驱动升级手头同时拿到了TMC2208和TMC2209两种步进电机驱动芯片而且都需要通过UART模式接到STM32F103上控制。原本以为只是“一个贵一点、一个便宜一点”的区别结果翻数据手册、看寄存器、调通信时序折腾了整整两个晚上才把TMC2209与TMC2208的UART模式差异彻底理清楚也把F103的串口控制步进电机的代码跑稳定了。这篇文章就把整个实践过程写出来从芯片差异、硬件接线、标准库代码到实际踩坑尽量让同样在调驱动的朋友少走弯路。如果你是第一次接触TMC系列驱动或者已经在用TMC2208但想换TMC2209尤其是想用STM32F103这类MCU通过UART去配置电流、微步、读取状态这篇应该能帮上忙。开整。1. 为什么要在TMC2208和TMC2209之间纠结1.1 两颗芯片的定位和我的选型场景TMC2208和TMC2209都是德国Trinamic现在被ADI收购推出的两相步进电机驱动芯片主打静音、低发热、内建256微步细分。TMC2208是上一代“网红”驱动大量用在3D打印机主板上TMC2209则更像是针对TMC2208的痛点做了补强多了StallGuard2失速检测、地址选择引脚、更完整的寄存器状态读取能力。我的项目是一台三轴小型雕刻机原来用A4988驱动噪音大、电流要手动调电位器而且一旦电机电流设小了就容易丢步。换成TMC2208之后噪音确实小很多但在实际操作中发现TMC2208的UART模式下地址固定一根总线上只能挂一颗调试时没法把X轴、Y轴驱动都挂在同一条串行线上。后来注意到TMC2209的AD0引脚可以选地址一根UART总线最多挂两颗驱动这就方便多了于是决定把X/Y轴都用TMC2209Z轴暂时用TMC2208测试。1.2 UART模式到底解决了STEP/DIR模式解决不了什么问题很多人以为TMC2208/TMC2209只是“带UART接口的步进驱动”实际在电机运转层面它们依然需要STEP脉冲和DIR方向信号UART并不是用来发脉冲的。UART模式真正解决的是驱动参数在线配置的问题。STEP/DIR模式下电流大小靠电位器调微步细分靠MS1/MS2引脚的电平组合改一次参数就要动一次硬件。而UART模式下可以通过寄存器直接设置IHOLD_IRUN运行电流、CHOPCONF微步、PWMCONF斩波参数、GSTAT错误标志甚至实时读取失速检测结果。这点对于需要频繁调参的CNC和机器人项目来说效率提升是立竿见影的。另外UART模式下可以把TMC2209的StallGuard2功能用起来通过检测机械负载突变来实现归零节省一组限位开关。提示TMC2208支持UART但功能被砍掉了不少尤其没有StallGuard2不能直接把TMC2209的失速检测代码套上去。这个差异后面会详细说。2. 寄存器级对比TMC2209比TMC2208多了什么2.1 基础寄存器功能对比如果你在TMCL-IDE或手动写UART帧会发现TMC2208和TMC2209的寄存器地址有很大一部分是重合的比如GCONF地址都是0x00、IHOLD_IRUN地址都是0x10、CHOPCONF地址都是0x6C、PWMCONF地址都是0x70。也就是说给TMC2208写的电流配置、微步配置代码在TMC2209上基本也能跑这是两者的兼容性基础。但TMC2209增加了几组TMC2208完全没有的寄存器最重要的是功能TMC2208TMC2209地址选择固定0x00AD0引脚选0x00或0x01StallGuard2失速检测不支持支持SGTHRS寄存器配置TCOOLTHRS失速检测速度阈值不支持有需配置才启用SG_RESULT负载结果读取不支持有反馈当前机械负载DRV_STATUS错误细节基本错误标志更详细的开路/短路/温度标志两线式/单线式UART单线单线这里有个容易忽略的点TMC2208的UART地址是固定的但TMC2209需要额外读一下AD0引脚并且地址是包含在发送请求中的数据字节里的所以如果换了芯片没改地址参数驱动怎么叫都不应答。2.2 地址机制一颗2208总线上只能有一颗2209可以挂两颗TMC2208的数据手册写得很明确UART从机地址为0x00不支持地址修改。这意味着如果你在一条UART总线上同时接两个TMC2208它们会对0x00地址的请求同时响应总线冲突。在实际项目里就得给每个驱动单独拉一根UART引脚占用MCU的多个串口。TMC2209则多了一个AD0引脚接低电平时从机地址是0x00接高电平时从机地址是0x01。这样一条UART上可以接两个TMC2209只要软件里根据地址区分是X轴驱动还是Y轴驱动即可。如果项目有3个轴以上那就需要多个UART或加模拟开关分时复用。我的方案很简单STM32F103的USART1接X轴TMC2209AD00和Y轴TMC2209AD01USART2接Z轴TMC2208。2.3 失速检测带来的归零方式变化TMC2209的StallGuard2原理是监测电机反电动势和内部电流变化来判断负载突变。当机械结构运动到限位边界时负载会突然变大SG_RESULT数值会出现明显跳变。通过设置SGTHRS阈值可以让驱动器在失速时给出标志主控据此切断运动或跳回安全位置。这个功能对CNC、3D打印机来说最大价值就是可以省掉机械限位开关。原来归零时平台撞到限位开关开关触发电平变化MCU停止脉冲输出。用了TMC2209后不需要额外接线只需在固件里周期读取SG_RESULT当数值低于设置阈值时认为已经撞到边界停止发脉冲即可。TMC2208完全没有这个能力想归零只能继续用限位开关或者额外接光电/磁传感器。这是选型时最需要考虑的差异而不是纠结那几十毫欧的内阻差异。3. STM32F103与TMC驱动板的硬件接线3.1 PDN_UART单线半双工总线怎么搭TMC2208和TMC2209的UART模式都走PDN_UART引脚这是一根单线双向数据线硬件上类似1-Wire但电气特性和UART又不完全相同。官方例子通常是让MCU的TX和RX通过1k电阻并在一起再连到PDN_UART然后靠切换MCU的收发模式来工作。STM32F103的USART支持半双工模式在标准库中调用USART_HalfDuplexCmd(USART1, ENABLE)之后只需要接MCU的TX引脚即可因为半双工模式下TX和RX在芯片内部已经连在一起了。我实际用的接法如下STM32F103的PA9USART1_TX串一支1k电阻然后连接到TMC2209的PDN_UARTPDN_UART引脚外接一个10k上拉电阻到VCC_IO3.3VTMC2209的AD0引脚接GND或者接3.3V用来区分地址驱动板的地与STM32F103的地必须共地注意TMC2208/TMC2209的逻辑电压VCC_IO接3.3VPDN_UART上拉电阻也接3.3V不要接到电机电源电压上。TMC2209的VCC_IO在3.3V直接连F103没有电平兼容问题但如果驱动板上的VCC_IO接了5V就需要加一个双向电平转换电路。3.2 上拉、电容、这些容易翻车的小地方PDN_UART空闲时应该被上拉到高电平因为UART总线空闲为高。如果省掉10k上拉电阻通信稳定性会变差尤其是温度升高后经常第一帧发送失败。另外PDN_UART对地需要一颗100nF左右的滤波电容吗看具体驱动板设计很多模块已经自带建议先看模块原理图。如果PDN_UART到MCU的连线比较长超过20cm建议在靠近MCU侧加一个RC滤波R取1kC取100pF左右可以有效减少电机线耦合过来的噪声干扰。不要加大电容否则UART波特率高于115200时边沿会变缓导致通信失败。STM32F103的USART1_TX配置成复用开漏输出因为半双工模式下需要边输入边输出。标准库初始化时GPIO配置为GPIO_Mode_AF_OD速率GPIO_Speed_50MHz。如果配置成推挽输出接收外部数据时会出问题。4. STM32F103标准库实现TMC的UART帧4.1 通信帧格式和CRC8计算TMC2208/TMC2209的UART帧结构大致如下。写寄存器时主机发送0x05 | 从机地址 | 寄存器地址 | 数据data[3] | data[2] | data[1] | data[0] | CRC8其中从机地址、寄存器地址各占一个字节数据是大端序高字节在前。CRC8是前7个字节的校验值多项式为0x07或0x8C具体实现可以按官方推荐表来。读寄存器时主机发送0x07 | 从机地址 | 寄存器地址 | CRC8然后驱动会返回0x05 | 从机地址 | 寄存器地址 | 数据data[3] | data[2] | data[1] | data[0] | CRC8这里有个坑TMC2208和TMC2209的地址虽然在硬件上固定/可选但发送帧里的地址字段不是简单的0x00或0x01直接发很多资料会强调需要左移一位或与读写位组合。我建议各位以自己手里数据手册的“UART接口”章节为准我在代码里使用的是一个简单的地址值直接表示从机地址实测TMC2209 AD00时地址用0x00可以通信AD01时地址用0x01可以通信。CRC8的C代码我用的是查表法static const uint8_t tmc_crc_table[256] { 0x00, 0x91, 0xE3, 0x72, 0x07, 0x96, 0xE4, 0x75, 0x0E, 0x9F, 0xED, 0x7C, 0x09, 0x98, 0xEA, 0x7B, // ... 完整表略 }; uint8_t TMC_CRC8(const uint8_t *data, uint8_t len) { uint8_t crc 0; while (len--) { crc tmc_crc_table[(crc ^ *data) 0xFF]; } return crc; }实际项目里如果不方便把256字节表都拷进去也可以用位运算写但查表法在F103上更快占用Flash也不大。注意CRC的对象是从机地址字节开始包含寄存器地址和数据字节不包含sync字节0x05/0x07。4.2 初始化串口和写寄存器的代码下面这段是标准库风格的初始化使用USART1波特率1152008数据位、1停止位、无校验并使能半双工模式void TMC_UART_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_OD; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); USART_InitStructure.USART_BaudRate 115200; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode USART_Mode_TX | USART_Mode_RX; USART_Init(USART1, USART_InitStructure); USART_HalfDuplexCmd(USART1, ENABLE); USART_Cmd(USART1, ENABLE); }写寄存器函数的要点是先关掉接收中断避免发送数据时收到自己返回的内容干扰状态判断每发一个字节等TXE标志位置1全部发完后等TC标志位置1再留几十微秒延时再切换阶段。void TMC_WriteReg(uint8_t addr, uint8_t reg, uint32_t dat) { uint8_t buf[8]; buf[0] 0x05; buf[1] addr; buf[2] reg; buf[3] (dat 24) 0xFF; buf[4] (dat 16) 0xFF; buf[5] (dat 8) 0xFF; buf[6] dat 0xFF; buf[7] TMC_CRC8(buf[1], 6); for (int i 0; i 8; i) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, buf[i]); } while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); delay_us(50); }这里buf[7]的CRC计算长度是6从buf[1]到buf[6]正好包含地址、寄存器、4字节数据。如果数据手册上要求包含sync字节需要调整表范围为buf[0]~buf[6]。4.3 读寄存器时要注意的时序问题读寄存器比写寄存器麻烦因为要发完读请求后引脚立刻变输入等待驱动返回数据。标准库半双工模式下USART1的RX和TX在内部已经连接发完请求后如果直接启用了RX中断理论上可以收但实测有时会收到自己发送过来的字节因为半双工切换不干净。我的建议是读操作不用中断而是发送完请求后延时等待至少100微秒然后一个字节一个字节地轮询读取读满8字节或超时结束。这样代码简单逻辑也稳定。uint8_t TMC_ReadReg(uint8_t addr, uint8_t reg, uint32_t *dat) { uint8_t buf[4] {0x07, addr, reg, TMC_CRC8(addr, 2)}; uint8_t rx[12]; uint8_t rx_len 0; for (int i 0; i 4; i) { while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) RESET); USART_SendData(USART1, buf[i]); } while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); delay_us(200); while (rx_len 9) { uint8_t timeout 100; while (--timeout USART_GetFlagStatus(USART1, USART_FLAG_RXNE) RESET); if (timeout 0) break; rx[rx_len] USART_ReceiveData(USART1); } if (rx_len 9) { *dat ((uint32_t)rx[3] 24) | ((uint32_t)rx[4] 16) | ((uint32_t)rx[5] 8) | rx[6]; return 1; } return 0; }这里返回数据的位置是rx[3]~rx[6]是因为返回帧前两字节是sync和地址第三字节是寄存器地址之后才是数据。如果驱动返回帧不带寄存器地址需要对应调整。我在TMC2209实测是带第三字节的TMC2208也带。5. 用UART把电机转起来的完整流程5.1 上电后的寄存器初始化序列电机不是一上电就能跑的。TMC2209/TMC2208上电后默认寄存器值是“保守模式”电流、微步、PWM参数都属于最基础状态必须写入初始化配置。我习惯按这个顺序清GSTAT复位标志写0x01到0x00寄存器把上电复位状态位读走配置GCONF寄存器地址0x00把UART模式下需要用到的位打开比如内部SENSE电阻补偿配置IHOLD_IRUN寄存器地址0x10设置空闲电流和运行电流配置CHOPCONF寄存器地址0x6C设置微步细分和斩波模式如果是TMC2209再配置SGTHRS和TCOOLTHRS一个典型初始化代码TMC_WriteReg(0, 0x00, 0x00000001); // GCONF: 打开UART模式? 实际需确认 TMC_WriteReg(0, 0x10, 0x00030A10); // IHOLD_IRUN: 空闲电流10, 运行电流26 TMC_WriteReg(0, 0x6C, 0x000101D5); // CHOPCONF: 256微步, TOFF等这里给的寄存器值是一个参考具体每一位的含义必须自己对着数据手册二进制拆解。IHOLD_IRUN的低8位是空闲电流IHOLD接下来4位是IRUN运行电流再上面4位是IHOLDDELAY。CHOPCONF的MRES位决定了微步值为0时256微步值为4时16微步。如果抄别人代码不核对位很容易出现“写的电流没变化”的现象。5.2 STEP/DIR脉冲与UART调节的配合配置完成后给驱动板的STEP引脚输入脉冲电机就会按照配置的电流和微步运转。STM32F103要输出多少脉冲可以用定时器PWM或GPIO翻转这块我用的定时器1的PWM输出频率控制为500Hz到20kHz实际雕刻机常用10kHz左右。脉冲的每一路上升沿电机走一个微步。这里有个容易误解的地方UART配置完成之后电机运转完全是由STEP/DIR控制如果UART线断开电机照样能转因为电流和微步已经写进驱动芯片寄存器。UART的作用顶多相当于你带电换了一个“虚拟电位器”不是让电机通过UART命令来走位置。但你可以通过UART读取当前驱动状态、故障标志或者在运行时只要发一条寄存器写命令就能立刻调整运行电流这在实际调试中非常好用。如果想让电机按固定角度转动需要在STM32F103里规划脉冲个数。比如一圈需要200步电机驱动器细分为16那么走一圈需要3200个脉冲。代码里定时器中断里计数到了目标值就关PWM输出。5.3 实时读取运行状态和错误标志TMC2209比TMC2208强的一点是可以实时读取DRV_STATUS知道芯片是不是过温、是不是开路、是不是堵转。我用以下方式周期100ms读一次0x6F寄存器检查bit位。uint32_t status; if (TMC_ReadReg(0, 0x6F, status)) { if (status 0x00000001) { // 过温警告 } }如果是TMC2209通过读SG_RESULT寄存器可以拿到当前负载数字量。这个值不是直接报错而是需要设定阈值。我在CNC竖向轴调机时先把机械限位拆掉缓慢让丝杠顶到末端观察SG_RESULT的变化范围再设一个阈值之后每次顶到末端时数值就会跳到阈值之下MCU停止发脉冲。整个过程比限位开关调试直观很多。但要注意失速检测只有在电机速度较快时才有意义TMC2209数据手册中的TCOOLTHRS就是用来设定失速检测生效的最低速度速度太低时SG_RESULT不可靠。6. 实测中的坑通信失败、电机异响、丢步6.1 发送不返回地址和CRC是最常见的原因我一开始把TMC2209当TMC2208调地址写0x00结果驱动没响应拿示波器点PDN_UART能看到波形但驱动就是不应答。后来检查发现我把TMC2209的AD0接在了3.3V上应该用地址0x01。这算低级错误但很典型。第二个坑是CRC计算范围。一开始我从sync字节0x05开始参与CRC计算结果连写寄存器都没成功。翻官方说明后才发现sync字节不参与CRC计算应该从地址字节开始。这个细节在TMC2208和TMC2209数据手册里写得很明白但很多人习惯从数组下标0开始循环一错就是一片。如果判断自己的CRC没问题可以用逻辑分析仪抓一下看发送的数据是否和期望帧一致。PDN_UART空闲高电平发送时波形是标准的UART低电平起始位波特率可以从PC端计算。6.2 驱动器一直处于待机状态怎么查TMC2209有freewheeling和standstill两种状态。如果上电后什么都不动IHOLD空闲电流会把电机锁住但如果你发现电机完全软绵绵可能是没正确配置IHOLD_IRUN或者驱动进入了freewheeling寄存器里对应位被置位。我出现过一次初始化时往GCONF写了一个不兼容TMC2208的值结果TMC2208直接“傻了”电机不锁轴UART还能通信但STEP怎么发都不转。后来用读寄存器方式读回GCONF才发现数据根本没写进去原因是我初始化序列里第一步清GSTAT的写操作把GCONF配置覆盖了。正确做法是先写GCONF再清GSTAT或者分开延时。还要检查驱动板的EN引脚。很多模块默认不上拉MCU复位期间EN引脚为低驱动被禁用。我的板子上EN接了10k上拉到3.3V需要输出低电平才能使能这和我最初习惯的逻辑相反。建议硬件设计时把EN引脚用跳线引出方便调试。6.3 关于TMC2209“串口控制步进电机”的边界认知最后聊一个概念问题。搜“TMC2209 UART STM32F103 串口控制步进电机”能看到很多帖子但不少新手会误以为只要接一根串口线就能像发指令那样让电机转动指定角度。实际上TMC2208/TMC2209的UART只是配置通道不是运动控制接口真正的运动还是要靠STEP/DIR脉冲源。你依然需要在STM32F103里用定时器产生步进脉冲或者外接触摸屏控制器去发脉冲。UART能做的是在一个中央控制器上同时管理多颗驱动芯片中央控制器通过地址区分X轴、Y轴、Z轴然后各自下发电流、微步、失速检测阈值。运行时如果发现某轴负载异常可以动态降低那轴的电流或直接停车。这种设计比纯STEP/DIR模式高了不只一个level也是我这次升级驱动最核心的收获。这个项目完成后我再处理多轴设备一般优先考虑TMC2209而不是TMC2208不是因为迷信“买新不买旧”而是StallGuard2和地址选择确实能省线缆、省限位开关、调参方便。TMC2208在一些固定参数、不需要总线的场景仍然很合适毕竟便宜且稳定。但如果你的STM32F103串口资源紧张想一条UART管两颗电机TMC2209基本是唯一解。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →