STM32舵机控制实战:PWM时序、调试与电源设计
2026/9/16 11:59:46 网站建设 项目流程

简介:基于STM32F103单片机的6自由度机械手舵机驱动源码包,面向嵌入式初学者与机器人爱好者,解决多路PWM舵机控制及串口联调问题。工程采用标准库与USMART调试组件,含硬件初始化、舵机底层驱动及串口命令解析等模块,便于通过串口逐路调试6路PWM输出,可迁移至其他机械臂或云台项目。压缩包共86个文件,其中38个头文件与36个C源文件构成完整工程代码,另有启动汇编、Keil工程文件、hex固件与说明文档,整体仅695KB,结构紧凑。目前已有987人学习,适合配合正点原子或野火开发板快速验证,也可作为毕业设计或课程设计的参考蓝本。通过阅读源码可掌握STM32的RCC配置、PWM波生成、串口中断接收及舵机控制算法,是一份轻量实用的入门素材。

1. 从机械手项目理解STM32舵机控制的三个硬门槛

接手过机械臂项目的人大概都有过这种体验:6个舵机单独测试时动作干净利落,一旦全部装到机械手上,上电瞬间就像抽风一样乱抖,甚至直接卡死。问题往往不在舵机本身,而在PWM时序的初始化、调试手段的缺失,以及电源系统的设计。这份基于STM32F103的6自由度机械手驱动程序之所以值得拆,是因为它把三个最容易卡住新手的门槛都处理得比较完整:一是通过硬件初始化阶段关全局中断来保证外设配置不被串口数据打断,二是引入了USMART这种串口命令行调试组件,让舵机角度和脉宽的在线调整脱离「每改一次参数就重新烧录一次」的低效循环,三是6路PWM的定时器通道分配方式直接决定了后续扩展机械手控制算法时的资源边界。对于正在做舵机机械手、云台、仿生臂或者入门STM32运动控制的人,这套代码里的工程组织方式和调试思路,比单纯抄一遍PWM初始化更有参考价值。

2. 工程框架与底层初始化:时钟链路、USART1重定向与JTAG释放

2.1 基于固件库的工程组织:FWLib、SYSTEM、HARDWARE、APP分层

拿到源码包后,先看目录结构就能猜出这套工程沿用的是正点原子风格的固件库分层方式。FWLib目录下是标准的STM32F10x标准外设库,配合STM32F10xR.LIB这个预编译库文件使用;SYSTEM目录集中了delaysysusartmallocxprintf这些与具体业务无关的基础组件;HARDWARE目录放的是bsp.c/hsteer.c/h,前者负责板级初始化,后者专门封装舵机控制;USER目录则是main.c、启动文件startup_stm32f10x_hd.s和Keil工程文件。APP目录里的usmart组件独立于业务代码之外,这种分层的直接好处是:当你把这份驱动移植到自己的板子上时,只需要改HARDWARE层,SYSTEM层里的延时和串口函数基本可以原样复用。

启动文件用的是startup_stm32f10x_hd.s,对应大容量Flash的F103芯片。hd后缀说明这是512KB Flash或以上的型号,如果你用的是STM32F103C8T6这种中容量芯片,必须换成startup_stm32f10x_md.s,否则中断向量表顺序对不上,程序跑起来会出现莫名其妙的问题。系统中使用KeilDelAll.bat批处理来清理OBJ目录下的临时编译产物,遇到编译报错找不到头文件或者链接时符号冲突,先跑一遍这个脚本再做全量编译,能排除掉大部分陈旧依赖导致的问题。

2.2 关闭全局中断的初始化框架:为什么外设初始化需要原子化

项目里硬件初始化的主函数写得很有代表性,原样摘录如下:

void hw_init(void) { __SETPRIMASK(); // 关闭全局中断, 防止初始化中途被串口/定时器打断 { RCC_Configuration(); // 配置系统时钟: 72MHz 主频 + 外设时钟门控 delay_init(72); // 传入 72, 让延时函数基于 72MHz 主频校准 uart_init(9600); // 初始化串口1, 波特率 9600, 作为 USMART 调试通道 jtag_set(JTAG_SWD_DISABLE); // 释放 PA15/PB3/PB4, 这三个引脚用于舵机 PWM usmart_dev.init(72); // USMART 组件初始化, 参数 72 用于内部延时换算 steer_init(); // 初始化 6 路舵机对应的定时器和 PWM 通道 } __RESETPRIMASK(); // 重新打开全局中断 }

这段代码最值得琢磨的不是每一行调用了什么函数,而是最外层那对__SETPRIMASK()__RESETPRIMASK()。这两个操作分别对应Cortex-M3内核的PRIMASK寄存器置位和清零,作用是屏蔽和恢复所有可屏蔽中断。在初始化阶段关闭全局中断,是为了防止这样一种情况:串口数据恰好在上电瞬间到达,USART1的中断服务函数被触发,而此刻定时器PWM通道还没来得及配置,中断里如果对未初始化的外设寄存器做了读写,轻则配置被覆盖,重则直接进入HardFault。把整个初始化过程包成原子操作,是一种在工业控制代码里非常常见的防御性写法,尤其是在机械手这种上电瞬间就需要所有执行器处于已知状态的场景中。

delay_init(72)usmart_dev.init(72)这两个参数都是72,这个值必须与RCC_Configuration()中配置的系统主频一致。如果外部晶振是8MHz,通过PLL倍频到72MHz,那么这里填72是正确的;如果你的板子用的是25MHz晶振或者改成了48MHz主频,这两个地方不改的话,延时和USMART的定时逻辑会整体偏移,最典型的症状就是舵机PWM频率正确但角度响应明显变慢。

2.3 JTAG引脚释放与PA15、PB3、PB4复用代价

在STM32F103上,PA13、PA14、PA15、PB3、PB4默认被JTAG/SWD调试功能占用。F103的调试引脚默认模式是JTAG全功能加SWD,这导致如果不做处理,PA15、PB3、PB4这三个IO口根本无法作为普通GPIO输出PWM。机械手需要6路PWM,引脚资源紧张,所以代码里调用了jtag_set(JTAG_SWD_DISABLE),这个操作的含义是彻底关闭JTAG和SWD调试接口,把PA15、PB3、PB4完全释放给普通外设使用。

注意这里关的是JTAG_SWD_DISABLE,不是JTAG_SET(SWD_ENABLE),意味着连SWD也一起关掉了。也就是说,一旦运行了这行代码,你就无法再通过ST-Link或者J-Link以SWD方式连接芯片进行在线调试。这在开发阶段的代价是很高的,因为程序里如果出现运行期异常,你没办法通过断点去查。这也是为什么这个工程里要引入USMART串口调试组件——调试通道从SWD切换到了串口,所有运行期检查和参数修改都通过串口命令行来完成,两者是配合使用的设计,不是可选的加分项。如果你打算保留SWD调试能力继续开发,可以把这行改成jtag_set(JTAG_SWD_ENABLE),代价是PA15、PB3、PB4这路PWM需要换到其他定时器通道上。

2.4 RCC时钟配置与APB1定时器倍频规则

RCC_Configuration()内部做的事情可以归纳为一张典型配置表:

配置项典型值作用
HSE8MHz外部晶振系统时钟源
PLL倍频9倍8MHz×9=72MHz系统主频
AHB预分频1分频HCLK=72MHz
APB1预分频2分频PCLK1=36MHz,定时器时钟自动×2为72MHz
APB2预分频1分频PCLK2=72MHz,USART1、ADC、TIM1挂在此总线

有一个非常容易踩的坑藏在表格第四行:APB1预分频设置为2时,挂载在APB1上的通用定时器TIM2、TIM3、TIM4的时钟并不是36MHz,而是自动倍频到72MHz。这是STM32硬件的固定行为——当APB1分频系数不为1时,定时器时钟倍频系数为2。所以舵机PWM的计数频率拿到的是72MHz,而不是你以为的36MHz。很多人在计算PWM周期的时候少乘了这个2,导致输出的PWM频率直接翻倍,舵机发出尖锐的啸叫声。关于定时器PSC和ARR的具体计算公式,在第四章里会结合六路舵机的脉宽参数一起展开。

3. USMART串口调试组件:把寄存器操作变成在线命令

3.1 USMART的定位:嵌入式环境下的极简命令行解释器

在MCU开发场景中,调试手段无非几种:断点调试、printf打印、逻辑分析仪抓波形。但当你关闭了SWD接口之后,前两者都变得不太方便——断点没法用,printf基本靠串口。USMART组件解决的是更高一层的问题:它让你不用改代码、不用重新编译烧录,就能在运行状态下直接调用工程里的任意函数。说白了,它不是打印调试信息的工具,而是一个跑在MCU上的函数调用器,类似Python的交互式控制台,只不过函数列表是你事先注册进去的。

// usmart_config.c 中的函数注册表 struct _m_usmart_nametab usmart_nametab[] = { #if USMART_USE_WRFUNS == 1 {"steer_set_angle", (void(*)(void))steer_set_angle}, // 注册角度设置函数 {"steer_set_pulse", (void(*)(void))steer_set_pulse}, // 注册脉宽设置函数 {"read_steer_angle", (void(*)(void))read_steer_angle},// 注册角度读取函数 {"hw_init", (void(*)(void))hw_init}, // 注册硬件重初始化 #endif };

这段代码说明了一个关键机制:USMART把C语言里的函数指针和字符串绑定在一起。steer_set_angle这个名字本身对MCU来说毫无意义,但通过这张注册表,字符串"steer_set_angle"就被映射到了对应的函数入口地址上。当你在串口调试助手里发送steer_set_angle 1 90时,USMART的解析器会先按空格把字符串拆成三段,第一段是函数名,后两段是参数。函数名在这个链表里逐项匹配,匹配成功后,再按usmart_str.c里的解析规则把190这两个字符串转换成整型参数,通过函数指针完成调用。这里的难点在于把字符串参数动态转换成不同类型的数据,usmart_str.c内部维护了一套基于格式说明符的转换逻辑,对标C标准库里的sscanf,但做了裁剪以适配MCU的内存限制。

3.2 串口接收与命令解析的配合流程

USMART的串口数据接收并不依赖复杂的操作系统或DMA,而是通过usart.c中的串口接收中断来完成的。常规接法是这样的:串口收到一个字节就触发一次中断,中断服务函数把字节缓冲到FIFO里;USMART在main主循环中周期性检查这一批缓冲,当收到回车符\r或换行符\n时,认为一条命令输入完毕,开始进入解析流程。

// 串口中断服务函数中追加的 USMART 数据接收 void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t ch = (uint8_t)USART_ReceiveData(USART1); usmart_dev.read(ch); // 把字节交给 USMART 缓冲 } }

usmart_dev.read(ch)并不是直接把字节放进一个简单的数组,它内部维护了接收计数器和换行判断逻辑。如果串口助手设置的发送格式是「发送新行」,那么在命令末尾就会附带\n\r\n,USMART检测到换行后就会触发一次命令执行。这里有一个很容易让新手困惑的细节:如果串口助手没有勾选自动发送新行,命令永远不会被执行,因为USMART只会在收到换行符时才认为输入结束。正确操作是PC端串口调试助手发送命令时勾选「发送新行」选项,这样代码逻辑才能与PC端配合顺畅。

USMART连接PC上位机时,usmart_str.c内部对命令的解析顺序是:先检查命令名在注册表中是否存在,如果不存在,把整个字符串原样返回,提示Invalid Command;如果存在,再解析参数的个数。参数个数的校验会对上usmart_config.c中注册表项的参数长度描述,比如steer_set_angle注册时声明接收两个参数,你只传一个或者传三个,USMART会在串口里打出一条参数数量不匹配的错误信息,而不会真正执行函数调用。这一点很重要,因为C语言的函数指针类型已经被强制转换成void(*)(void)类型,参数类型的安全保障只能靠解析器的校验逻辑来兜底。

3.3 在线调参:机械手角度标定的加速器

如果你是在实验室调试六自由度机械手,最常做的事情就是试角度:让某一路舵机转到90度,观察机械臂姿态,觉得偏了就改成85度,再观察。不用USMART的常规做法是修改代码里角度常量、重新编译、烧录、上电、观察,一次调试循环维持在20秒左右。而通过USMART,整个过程被压缩为在串口调试助手里输入一条命令的时间:

steer_set_angle 1 90 steer_set_angle 1 85 steer_set_angle 2 45 read_steer_angle 1

第一行命令将1号舵机设置为90度。执行过程中,steer_set_angle函数内部会把角度值换算为对应PWM通道的比较寄存器CCR值,然后更新定时器。第二行命令将1号舵机角度改为85度,你可以直接观察机械臂末端位置有没有到达预期。read_steer_angle 1则会把1号舵机当前记录的脉冲宽度回传到串口终端,方便你反推当前实际角度。这种方式在处理机械手逆运动学参数标定时尤其高效,逐关节微调的角度值可以直接记录下来作为运动学模型的补偿量。

需要特别注意的是,USMART调用函数的运行上下文与主程序完全相同。这意味着你在steer_set_angle里访问的全局变量就是主程序里那个全局变量,USMART执行期间不会被高优先级中断抢占——除非对应中断的优先级本来就高于USMART执行位置。所以在机械手运行过程中,如果主循环正在执行复杂的轨迹插值算法,而此时你在串口里发了一条steer_set_angle命令,USMART会插队执行这次角度修改,可能造成机械臂动作的瞬间跳变。安全操作习惯是:在机械手处于静止状态下调整参数,运动过程中不要给USMART发命令。

3.4 串口打印组件xprintf与重定向方向的选择

工程里还有一个容易被忽略的组件:xprintf.c。这是把printf系函数裁剪后用于嵌入式串口输出的轻量实现,核心格式化输出函数是xprintf。它和标准库printf的最大区别是不依赖半主机模式,点对点直接操作串口发送寄存器。如果你的工程里已经接了uart_init(9600),那么xprintf("Current Angle: %d\n", angle)就会直接把数据从USART1的TX引脚发出去。在这个项目中,xprintf和USMART共用同一个串口外设,但二者并不冲突,因为USMART处理的是RX方向接收到的命令,而xprintf只是TX方向发送数据。PC端的串口调试助手同时承担了命令输入和日志输出的功能,这也是这种调试架构最省资源的地方——不需要额外占用一个串口做日志通道。

4. 六自由度舵机控制的PWM设计与运动学边界

4.1 舵机控制信号本质:20ms周期和1ms~2ms脉宽窗口

标准模拟舵机和数字舵机的控制信号本质上是同一类东西:周期为20ms的PWM波,高电平脉宽在0.5ms到2.5ms之间,对应舵机输出轴从0度转到180度。这里最重要的一点是,舵机不关心频率精度,它只关心高电平持续的时间长度,也就是脉宽。50Hz这个频率只是20ms周期的倒数,只要脉宽保持准确,即使PWM频率有微小漂移,也不会对舵机角度产生影响。这个特性给了设计者一个宽松的空间:不需要高精度的PWM时钟源,普通定时器的PWM输出就能满足要求。

具体的脉宽与角度对应关系如下表所示,这是一份可以直接写入驱动代码的映射标准:

目标角度高电平脉宽典型应用场景
0.5ms机械手关节回零位
45°1.0ms小范围调整
90°1.5ms舵机中位,机械臂水平状态
135°2.0ms大角度摆动
180°2.5ms机械手极限姿态

有相当一部分舵机支持的是0.5ms到2.5ms范围,但市面上一些标称180度的舵机实际线性区只到1ms到2ms之间,超过这个区间后脉宽和角度的线性关系变差,关节角度超过这个范围时关联到的只是死区或者极限位置的机械限位。所以在代码里对角度做限幅处理之外,最好还根据具体舵机型号微调脉冲上下限。这个参数的标定方式在第五章会有说明。

4.2 定时器通道分配与捕获比较寄存器计算

6路舵机意味着需要6路PWM输出。STM32F103的定时器资源中,TIM1和TIM2各有一组PWM输出通道,TIM3和TIM4同理,每个定时器最多4路通道。这个工程的做法是基于资源利用率优先原则,把6路PWM分配到两个定时器上,常见做法有两种:

方案通道分配特点
方案ATIM1_CH1~CH4 + TIM2_CH1~CH2TIM1是高级定时器,自带互补输出和刹车功能,但CH1~CH4分布在不同IO引脚
方案BTIM2_CH1~CH4 + TIM3_CH1~CH2全部使用通用定时器,逻辑简单,适合均匀负载

工程选用哪种方案取决于舵机控制板的接口定义。我在类似项目里更倾向于方案B,因为通用定时器在PWM模式下的初始化和中断配置比高级定时器简单,且不会引入TIM1的刹车信号误触风险。不管选哪种,核心都是配置定时器的预分频系数PSC、自动重装载值ARR,以及每个通道的比较值CCR。

舵机控制要求PWM周期为20ms,也就是50Hz。在APB1定时器时钟为72MHz的条件下,周期计算公式为:

PWM频率 = 72MHz / ((PSC + 1) * (ARR + 1))

假设取PSC=71,则计数频率为72MHz / 72 = 1MHz,即每个计数单位1微秒。要让周期为20ms,ARR + 1 = 20000(因为计数单位是微秒,20000个单位就是20000微秒)。此时PWM频率正好是1MHz / 20000 = 50Hz。这样设置的好处是CCR比较值的单位恰好是微秒,舵机脉宽1.5ms就把CCR设为1500,而脉宽0.5ms对应CCR=500,直观且不易出错。下面是完整的舵机PWM通道初始化代码思路:

void steer_pwm_init(TIM_TypeDef* TIMx, uint16_t channel) { TIM_TimeBaseInitTypeDef TIM_BaseInitStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 预分频 71, 定时器计数频率 = 72MHz / 72 = 1MHz (1us 一个计数单位) TIM_BaseInitStructure.TIM_Prescaler = 71; // 重装载值 19999, 周期 = 20000us = 20ms TIM_BaseInitStructure.TIM_Period = 19999; TIM_BaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIMx, &TIM_BaseInitStructure); // PWM 模式1, 计数值小于 CCR 时输出有效电平 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 1500; // 默认 1.5ms, 舵机中位 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OCInit(TIMx, channel, &TIM_OCInitStructure); }

这段代码里的TIM_Prescaler = 71TIM_Period = 19999必须配对出现,不少人只改了PSC忘了改ARR,结果PWM周期变成了10ms或者40ms,舵机在20ms周期之外的脉宽下工作一段时间后就会发烫甚至烧毁。PWM模式1的含义是:当定时器计数值小于CCR时,输出引脚为有效电平(这里配置为高电平);计数值超过CCR后输出翻转为低电平。所以CCR直接决定了高电平持续长度,也就是舵机脉宽。在后面的角度换算函数里,我们要做的基本操作就是把角度映射到500到2500这个CCR区间上。

4.3 多路PWM初始化时的相位对齐问题

六路舵机同时工作时,如果每路PWM的计数起点各不相同,出现的现象是:即使各路的脉宽都是1.5ms,机械手上电瞬间也会发生短暂的抖动,因为各路舵机的输出轴会先快速响应到各自脉宽对应的角度,之后才进入稳定状态。解决这个问题的常见做法是在定时器初始化完毕、启动计数之前,手动设置所有通道的CCR为同一个中间值,比如全部设为1500(对应1.5ms中位)。然后一次性调用TIM_Cmd()启动定时器,这样所有通道的高电平起点完全对齐,上电瞬间6路舵机都会保持在中位,机械手呈现一个姿态确定的静止状态,然后你再去控制它逐关节运动。

void steer_init(void) { uint8_t i; // 初始化所有通道 CCR 为中位值 for (i = 0; i < 6; i++) { steer_ch[i].tim = steer_ch[i].tim; // 每路指定对应 TIM 和通道 TIM_SetCompare(steer_ch[i].tim, steer_ch[i].channel, 1500); } // 所有定时器同时启动, 保证相位一致 TIM_Cmd(steer_ch[0].tim, ENABLE); TIM_Cmd(steer_ch[3].tim, ENABLE); }

各通道独立更新CCR时,如果不把定时器的预装载功能打开,CCR的新值会在下一个计数周期立即生效,这可能造成当前PWM周期内高电平宽度突然跳变。打开预装载后,CCR的更新会等到当前计数周期结束(即发生更新事件)时才生效,从而避免脉宽突变。在TIM_OCInitStructure中对应字段是TIM_OCPreload,一般建议在所有舵机控制项目里开启。这一点在做平滑运动控制时尤为重要:机械手轨迹插补要求每拍更新CCR,如果预装载关闭,脉宽突变积累下来会让关节产生肉眼可见的抖动。

4.4 电源电流峰值与PWM故障保护的设计边界

六自由度机械手在实际运行时,舵机负载变化最大的时刻就是各关节同时从静止加速到目标速度的瞬间。普通9g舵机堵转电流在500mA左右,而MG995这类大扭矩舵机堵转电流可以达到2A以上。6路舵机如果同时堵转,瞬时总电流超过10A,此时如果5V电源的峰值输出能力不够,输出电压会瞬间跌落,舵机控制芯片检测到欠压后可能进入复位或锁定保护状态,表现就是机械手运行中断断续续抖动。这个工程源码包里没有包含供电方案设计,但在实际搭建时,我一般会采用舵机电源与单片机电源完全隔离的做法:一路5V~6V大电流电源专门给舵机供电,另一路经过稳压芯片单独供给STM32最小系统板,两路电源只在GND处单点共地。这样既保证了舵机大电流不拉低MCU供电,又让PWM信号有了共同的参考地平面。

PWM故障保护也是这一节需要单独提的点。如果单片机因为程序跑飞或者其他原因停止输出PWM,舵机引脚会处于恒高或恒低电平状态,舵机随即失控。工程里没有专门实现看门狗与PWM联动,但你在实际项目中应该考虑:在main循环里喂IWDG独立看门狗,同时把舵机使能引脚接在某个IO上,一旦看门狗超时复位,初始化代码把所有舵机通道CCR恢复为1500中位值,保证机械手不会在失控状态下保持危险姿态。

5. 串口烧写与联调验证:从CH340驱动到多路舵机同步

5.1 串口烧写失败时先查DTR/RTS自动复位电路与boot0

因为工程初始化时已经关闭了SWD调试口,所以烧录只能走串口ISP方式。大多数ST-Link或者USB转串口模块在ISP下载时依赖DTR和RTS信号控制复位引脚和BOOT0引脚,如果连接失败,最先检查的是这两个引脚的接线方向——CH340模块的DTR/RTS输出逻辑电平与STM32复位电路要求的触发电平方向相反是很常见的接错点。正常的ISP烧录序列是:先让BOOT0拉高(进入Bootloader模式),然后复位,芯片启动后等待接收串口写入的固件。烧写完成后手动把BOOT0拉回低电平,再次复位进入用户程序。

另外一个高频坑是串口驱动没装好。无论设备管理器里是否显示CH340或FTDI设备,都建议去官网下载最新驱动重装一次,因为系统自带的通用串口驱动在某些操作系统版本上波特率9600下的时序与STM32F103内置Bootloader的要求存在偏差,表现为下载进度条停在0%。这里不需要改波特率,STM32F103内置Bootloader固定支持9600、57600等多种波特率,问题通常出现在USB转串口芯片的驱动时序上。建议直接用万用板飞线连接CH340的TXD、RXD、GND到STM32最小系统板的对应引脚,交叉接线:TXD接PA10(RX),RXD接PA9(TX)。

5.2 用xprintf打点验证串口链路与PWM时序

烧录成功后,验证串口链路是否正常的最快方式是在程序开头调用一次xprintf输出一个固定字符串。由于波特率设置是9600,PC端串口调试助手的波特率必须与此一致,否则看到的就是乱码。如果PC端串口画面没有反应,先确认PA9和PA10的复用功能是否开启——在RCC_Configuration里需要使能USART1时钟,同时把PA9配置为复用推挽输出,PA10配置为浮空输入。这两个引脚如果复用配置不对,USMART和xprintf都没有用武之地。

验证PWM波形时,临时把某一路舵机脉宽设定到固定值,比如steer_set_pulse 1 1500,再用示波器或者逻辑分析仪去抓对应引脚的波形。如果你手头没有示波器,一个土办法是把该路PWM输出直接接到一个LED串联电阻上:LED亮度随脉宽变化而变亮或变暗。这一招在验证PWM频率是否正确时很有用——50Hz的PWM信号驱动LED会看到明显闪烁,而如果周期错配到10ms,闪烁感会减弱甚至消失,这说明定时器时钟配错,需要回到第四章的参数重新核对。

5.3 舵机抖动排查:波形、共地、干扰三件套

六路舵机同步运行后如果出现抖动,排查顺序应该固定为:先看波形,再看共地,最后查串口干扰。波形检查用万用表无法判定脉宽,所以至少在PA15、PB3、PB4这三路释放出来的IO上接一个逻辑分析仪,确认输出脉宽是否与期望一致。共地问题最容易悄悄出现:舵机电源和单片机电源如果不共地,PWM信号的电平参考点与舵机控制板存在电压差,舵机内部的光耦或者晶体管无法可靠识别高电平,抖动几乎是必然的。而串口干扰是指调试用的USB转串口线在机械手电机动作时接收到脉冲噪声,导致USMART误判命令——这种情况把USB转串口线换成带磁环的屏蔽线,或者降低波特率到4800,一般就能解决。

最后还有一个实用小技巧:在验证新舵机的脉冲上下限时,不要直接发180度对应的2500us脉宽,先用steer_set_pulse命令发500us,舵机转到极限位置后手摸一下外壳温度,如果发烫明显,说明这个舵机的实际可用脉宽范围比标称窄,需要把限幅上限收窄到2200us左右。这个操作通过USMART输入一条命令就能完成,整个标定过程不超过两分钟。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询