ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32通过URT-1控制飞特SM40BL总线舵机:从接线到代码实战

STM32通过URT-1控制飞特SM40BL总线舵机:从接线到代码实战 前阵子做一个小型机械臂项目需要同时驱动多个飞特舵机SM40BL一开始我想偷懒直接用STM32的串口引脚去接舵机总线结果折腾半天一点反应都没有。后来老老实实把URT-1模块加进去十分钟就跑通了。这个坑我觉得值得写出来因为“STM32控制舵机”和“STM32通过URT-1控制飞特舵机”其实是两件完全不同的事前者你查到的多半是PWM舵机的教程后者才是总线舵机的正确打开方式。这篇文章就以SM40BL舵机为例完整记录我从接线到代码跑通的全部过程适合手里有STM32开发板、想上手飞特总线舵机但还没搞懂协议和硬件链路的同学。很多第一次接触总线舵机的人最容易卡住的地方不是代码而是压根没搞懂串口舵机的通信链路。普通舵机靠PWM脉宽控制角度接三根线就能转总线舵机走的是串口协议控制指令以数据帧的形式发到舵机总线舵机通过ID识别哪条指令是自己的。URT-1就是STM32和舵机总线之间的桥梁它帮你把电平、方向、协议转换这些脏活累活都处理掉了。1. 为什么是URT-1先搞懂总线舵机与普通舵机的区别1.1 普通PWM舵机 vs 串口总线舵机很多玩过航模或者做过小车的人接触的第一颗舵机大概率是SG90或者MG995这种PWM舵机。控制方式很简单给一个20ms周期的脉冲脉宽1ms对应0度1.5ms对应90度2ms对应180度。这种舵机的优点是人人都能跑通缺点是如果你想控制8个舵机就得用8路PWM而且舵机角度和脉宽的对应关系还得慢慢校准。飞特SM40BL这种串口总线舵机就不一样。它内部有控制板你通过串口发一帧数据比如“把ID为1的舵机转到1000位置”舵机收到后自己去闭环控制。多个舵机可以并联在同一条总线上靠ID区分线材从每个舵机6根线缩减到一进一出两根信号线加两根电源线。机械结构清爽很多而且还能回读舵机的电压、温度、当前位置这是PWM舵机完全做不到的。不过代价也很明显通信协议需要自己啃硬件链路比PWM舵机多一个转换模块。很多教程默认你会用STM32的串口却不说清楚串口TTL电平直接怼舵机总线为什么不行——电平不匹配是一方面更关键的是半双工方向控制。STM32的UART是异步全双工TXD和RXD分开舵机总线是半双工一根信号线既要发又要收谁来控制这根线的方向这就是URT-1存在的意义。1.2 URT-1到底干了什么活URT-1是飞特官方出的一个转换模块看起来像个USB转串口的小板子实际上它把两件事揉在了一起。第一种用法是接电脑USB口插上电脑另一头接舵机总线然后在飞特官方调试软件里直接配置舵机ID、波特率、中位这些参数非常方便很多人买它就是用来当USB调试器的。第二种用法才是我们今天的主角把它当成STM32和舵机总线之间的物理层转换器。STM32的串口TXD、RXD接到URT-1的TTL侧URT-1的输出接到舵机总线它能自动处理半双工方向切换和电平转换。也就是说你在STM32里只需要像普通串口发送一样把数据帧丢给URT-1URT-1会在合适的时机把数据放到舵机总线上。这里有一个非常容易踩的坑URT-1有两种工作模式一种是配合上位机软件的“指令模式”一种是直接转发串口数据的“舵机模式”。用STM32控制时一定要确保模块处于舵机模式或者直接透传模式否则你发的数据会被模块自己拦截处理根本到不了舵机。不同版本的URT-1切换方式不一样有的是拨码开关有的是靠发送特定指令切换拿到模块第一件事就是看说明书确认模式。1.3 物料清单与供电规划准备材料这件事看起来简单但供电没规划好后面问题会非常折磨人。我的物料清单如下STM32开发板一块我用的是STM32F103C8T6最小系统板串口1PA9/PA10作为舵机控制口URT-1模块一个飞特SM40BL舵机一颗用于调试舵机专用电源或者可调稳压电源建议至少能提供5V/3A以上电流杜邦线若干用来连接STM32和URT-1飞特官方调试软件“Feetech Debugger”用于初始配置舵机参数关于供电我多说一句。SM40BL是大扭矩舵机堵转电流能冲到好几安培如果用电脑USB口供电一旦舵机吃劲电压瞬间被拉低轻则舵机抖动重则STM32直接复位。更好的做法是给舵机单独一路电源供电而且电源地一定要和STM32的地共地否则串口信号电平乱飘你查一晚上都查不出来。2. 接线和硬件准备2.1 接线方式接线是整个流程里最简单但最容易出错的一步。STM32的串口TXD要接URT-1的TTL输入RXD接URT-1的TTL输出两个地接在一起。然后把URT-1的总线侧接到舵机上舵机的信号线、电源线、地线依次对应接好。我的实际接法是这样STM32 PA9 (TXD1) -- URT-1 TTL_RX STM32 PA10 (RXD1) -- URT-1 TTL_TX STM32 GND -- URT-1 GND URT-1 BUS_SIG -- SM40BL 信号线黄色 外部5V电源正极 -- URT-1 电源输入正或舵机电源线红色 外部5V电源负极 -- URT-1 GND共地有人可能会问为什么要让STM32的TXD接URT-1的RXD这不是串口交叉接法的常识吗对串口设备之间本来就是交叉连接发送对接收。但URT-1这个模块上丝印标注有时候会误导人它上面的TX/RX是针对USB串口芯片视角的你如果按照“同名相连”去接数据直接走丢。我第一次接反了发指令舵机纹丝不动排查半天才发现是交叉问题。2.2 舵机ID和波特率检查方法飞特舵机出厂ID默认一般是1波特率默认可能是115200或者1000000具体看产品批次。这个初始值必须查清楚因为如果STM32和舵机的波特率对不上你发多少数据舵机都当噪音处理。初始化舵机参数最稳妥的方式是使用飞特官方的上位机软件。把URT-1插到电脑USB口进入舵机模式打开调试软件软件会自动扫描总线上的舵机。扫描到之后先确认两件事ID改成自己方便记忆的值波特率改成和STM32一致的数值。这里特别提醒一点改波特率这个操作要谨慎。你把舵机波特率改成某个值后上位机软件如果还按原来的波特率去扫是扫不到舵机的。所以改完波特率要在软件里同步修改通信波特率设置再重新扫描一次确认舵机在线。我见过不止一个人在这个地方卡住以为舵机坏了其实只是软件没同步。如果手头没有上位机软件也可以用USB转TTL模块配合串口助手直接发指令。但新手不建议这么干因为协议帧容易写错还没有反馈信息排查起来很痛苦。先上图传软件把舵机配好再用STM32跑成功率会高很多。3. 看懂飞特舵机的串口协议3.1 帧格式与校验和计算飞特舵机的总线协议不同系列会有细微差别但总体思路都是“帧头 ID 数据长度 指令 参数 校验和”。以SM40BL这类较新的串口总线舵机为例帧格式通常是0xFF 0xFF 0xFF | ID | LEN | CMD | DATA... | SUM帧头固定三个0xFF用来让舵机识别一帧数据的开始ID是目标舵机地址范围0到2530xFE表示广播所有舵机LEN是参数长度加固定的几个字节具体加几要看协议表的定义不同系列略有差异CMD是指令码比如0x03是写数据0x04是读数据0x05是异步写等DATA是要写入的寄存器地址和数据内容SUM是校验和计算方式是对ID、LEN、CMD以及所有DATA逐字节累加然后取反只保留低8位校验和的计算是很多人写代码时最容易出bug的地方。它的公式是SUM ~(ID LEN CMD DATA1 DATA2 ... DATAn) 0xFF注意三点第一帧头三个0xFF不参与校验第二累加过程用整个字节相加溢出不用管最后截取低8位再取反第三校验结果的类型要是uint8_t否则你可能算出一个负数发出去永远不对。3.2 常用指令码与参数地址飞特舵机内部有很多寄存器用来存位置、速度、电压、温度等数据。控制舵机转动本质就是往位置寄存器里写一个目标值。下面这几个参数是上手阶段一定会用到的目标位置寄存器地址通常是0x2A数据长度4字节范围要看舵机型号运行速度寄存器地址0x20附近控制舵机转动速度当前位置寄存器用于回读当前角度配合状态查询使用不同舵机系列寄存器地址不完全一样甚至同一系列不同型号都可能不一样。所以最保险的做法是查官方协议表或者在调试软件里点开舵机信息确认寄存器地址和数值范围。SM40BL的位置范围需要以手册为准别想当然套用其他型号的值。写位置指令的CMD码是0x03后面跟起始寄存器地址0x2A然后是4字节的位置数据低字节在前小端模式。这一点特别容易弄反STM32发送的时候要把位置值拆成低字节、次低字节、次高字节、高字节依次放入数据区。3.3 怎么自己抓一帧来验证如果你对协议理解还不透别急着写代码先干一件事用上位机软件控制舵机转到一个角度同时打开串口监听功能看一下软件实际发出去的数据帧长什么样。这个操作非常有价值能省掉你瞎猜协议的时间。我当时就是这么干的。先用调试软件控制舵机转到位置1000软件日志里出现了一帧类似这样的数据FF FF FF 01 07 03 2A E8 03 00 00 ...逐字节拆开看FF FF FF是帧头01是舵机ID07是数据长度03是写指令2A是目标位置寄存器地址E8 03 00 00就是1000的小端表示校验和跟在后面。这一帧数据成了我后面写STM32代码的“模板”无论遇到什么奇怪的型号先抓一帧官方数据照葫芦画瓢就能跑通。4. STM32下位机代码实现4.1 串口初始化与GPIO配置我用的是STM32标准库串口1波特率1152008位数据位1位停止位无校验。HAL库的写法也类似核心配置就是这三个参数。需要注意的一点是STM32的串口TXD、RXD默认推挽输出即可不需要配置成开漏因为电平转换和方向控制都交给URT-1处理了。串口初始化代码大致如下void UART1_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_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOA, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; 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_Rx | USART_Mode_Tx; USART_Init(USART1, USART_InitStructure); USART_Cmd(USART1, ENABLE); }PA9配置成复用推挽PA10配置成浮空输入这是标准串口的配置方式没什么花活。有一点要注意如果你用的是其他串口对应的GPIO引脚要改而且不同型号STM32的USART引脚映射可能不一样建议先查数据手册。4.2 发送一帧控制指令核心代码就是构帧 发送。我封装了一个函数传入舵机ID和目标位置自动计算长度和校验和然后通过串口发送出去。以SM40BL的协议帧为例参考代码如下void Servo_WritePos(uint8_t id, uint32_t position) { uint8_t buf[11]; uint8_t checksum 0; uint8_t i 0; buf[0] 0xFF; // 帧头1 buf[1] 0xFF; // 帧头2 buf[2] 0xFF; // 帧头3 buf[3] id; // 舵机ID buf[4] 0x07; // LENSM系列写4字节数据时长度一般是7 buf[5] 0x03; // CMD写指令 buf[6] 0x2A; // 起始寄存器地址目标位置 // 位置值小端拆分比如位置1000 0x000003E8 buf[7] position 0xFF; buf[8] (position 8) 0xFF; buf[9] (position 16) 0xFF; buf[10] (position 24) 0xFF; // 计算校验和ID LEN CMD 地址 数据 for (i 3; i 11; i) { checksum buf[i]; } buf[11] (~checksum) 0xFF; // 通过串口1发送12字节数据 USART_SendString(USART1, buf, 12); }这里有一个很多人都会忽略的细节。不同系列的飞特舵机LEN字段的计算规则不一样。有的是“参数个数加2”有的是“参数个数加3”如果LEN算错了舵机会认为帧不完整直接丢弃。所以我一开始就强调先用上位机抓一帧官方数据对着帧把LEN理解透了再写代码。还有一点USART_SendString这个函数是我自己封装的内部循环调用USART_SendData并等待发送完成标志位。发送完一整帧后最好加一个小的延时给舵机留出处理时间。实测下来连续发位置指令时两帧之间至少间隔20毫秒否则舵机容易丢指令表现就是偶尔动一下、偶尔不动。4.3 实现多位置动作序列单片机的优势就在于可以按预设逻辑连续控制舵机。我写了一个简单的动作序列让舵机先转到位置100停一会儿再转到位置800再回到位置450如此循环。核心思路就是循环调用Servo_WritePos每次调用后延时一段时间。while (1) { Servo_WritePos(1, 100); delay_ms(1000); Servo_WritePos(1, 800); delay_ms(1000); Servo_WritePos(1, 450); delay_ms(1000); }这个代码虽然简单但已经能说明问题只要你会构帧控制一个舵机和控制十个舵机没有本质区别无非是把ID改一下、把位置值改一下。如果你想做动作更平滑的机械臂可以在这个基础上用插值算法计算中间点让舵机从A点平滑过渡到B点而不是直接跳变。再提醒一个细节舵机的位置值不是任意填的。超过舵机机械限位的位置值会导致舵机堵转轻则嗡嗡响重则烧毁驱动。写代码之前一定确认一下这颗舵机的有效位置范围把目标值做一次钳位保护。我在产品代码里加了一句if (position SERVO_MAX_POS) position SERVO_MAX_POS; if (position SERVO_MIN_POS) position SERVO_MIN_POS;别小看这个保护它能避免很多误操作带来的麻烦。5. 常见问题排查与避坑实录5.1 组合排查速查表从接线到代码我列一下整个调试过程中最常遇到的几个问题现象可能原因排查方法舵机完全无反应波特率不匹配确认STM32串口波特率与舵机一致舵机完全无反应舵机ID不对用上位机软件扫描实际ID舵机完全无反应URT-1模式不对确认模块处于透传/舵机模式舵机偶尔动一下帧间隔太短两帧之间增加20ms以上延时舵机抖动不转位置值超出范围检查目标位置是否在有效区间舵机电压被拉低供电不足换大电流电源检查共地舵机发烫负载过重或堵转减小负载检查机械限位只有一个舵机动供电或ID冲突检查总线供电和ID分配这张表是我踩坑经验的浓缩版基本覆盖了90%的新手问题。5.2 最隐蔽的3个坑第一个坑是关于URT-1供电的。URT-1本身能从USB取电但如果STM32不给URT-1的TTL侧供电只是接了信号线模块内部的电平转换电路可能会工作在不稳定状态。稳妥的做法是让STM32和URT-1共用一个电源系统至少确保两边都有稳定的参考地。第二个坑是串口助手的干扰。很多人在调试时先插着URT-1的USB线用上位机软件操作舵机然后又同时用STM32的串口去发指令。两条链路同时挂在一条总线上数据互相干扰舵机行为会变得非常奇怪。每次切换控制源之前把另一条链路的连接物理断开或者在软件里退出连接状态。第三个坑是延时位置设置不合理。舵机从一个位置转到另一个位置需要时间如果你在舵机还没到位时就发下一个指令舵机可能来不及响应感觉像是“不听话”。解决方法是根据转速和角度差估算转动时间再在代码里设置足够的延时或者回读当前位置确认到位后再执行下一个动作。6. 进一步优化与扩展玩法6.1 从“能动”到“动得优雅”跑通“写位置”指令后大部分项目的基础需求已经满足了。但如果想让舵机动作更顺滑可以考虑使用速度控制。SM40BL这类总线舵机支持设置运行速度你可以在写位置之前先写目标速度寄存器舵机会以设定的速度匀速转到目标角度而不是满速猛冲。这个特性在机械臂轨迹规划场景下特别有用能明显减少机构的冲击和抖动。另一种平滑控制思路是插值。假如舵机要从位置100转到位置800分10步走每次递增70每步之间延时50毫秒。这样虽然每一步还是阶跃跳变但整体看起来已经接近匀速运动。代码实现就是在for循环里不断调用Servo_WritePos每次修改目标值并短暂延时。6.2 使用回读功能做闭环总线舵机最大的优势是可以回读状态。SM40BL支持读取当前位置、电压、温度等寄存器数据这就让单片机有能力判断舵机是否真的到达了目标位置而不是盲目等待固定时间。实现读取功能的思路和写位置类似只是CMD码换成0x04后面跟要读取的寄存器起始地址和读取长度。舵机接收到读指令后会返回一帧包含当前值的响应数据STM32收到后按同样的校验规则解析即可。我在实际项目里常用电压回读来检测供电状态一旦发现电压跌到警戒值以下就停止动作防止舵机烧毁。6.3 多舵机并联注意事项如果你要做多舵机项目总线舵机的布线优势就体现出来了。多个舵机的信号线并联到同一条总线上每颗舵机用不同ID区分单片机发指令时指定ID即可。这种架构下ID规划变得很重要我习惯用1到8编号关节舵机9到12编号夹爪和辅助机构避免混淆。多舵机供电要特别上心。串联总线上的每一颗舵机都可能吃电流总电流等于所有舵机电流之和。所以多舵机项目不能指望单颗舵机的小电源建议根据舵机数量和最大堵转电流算出总电流余量再配置合适的开关电源。电源线也要适当加粗长距离串联时线损会导致末端舵机电压不足表现为动作无力或抖动。6.4 把调试经验沉淀为代码模板这次调试过程中最有价值的收获是总结了一套可复用的串口舵机驱动模板。函数框架包括串口初始化、构帧发送、校验和计算、位置钳位、延时控制。后面如果再换其他型号的飞特舵机只需要对照协议改寄存器地址和参数范围框架不用大动。如果你用的是STM32CubeMX生成的HAL工程代码迁移也很简单把USART_SendData改成HAL_UART_Transmit延时函数改成HAL_Delay即可。核心的构帧逻辑和校验和算法是跟协议走的和具体库无关。我在实际使用中发现只要能控制好电源和帧间隔这套方案跑起来非常稳定长时间运行也不会丢帧。URT-1这个模块虽然看起来只是“一根转换线”但它把半双工方向管理和电平匹配都处理好了省掉了我自己用GPIO模拟方向切换的麻烦。如果你以后要做更复杂的舵机控制不妨也从这个小模块开始先把协议跑顺再逐步加上速度控制、状态回读和多舵机联动你会发现总线舵机的潜力远超你最初的想象。
返回列表