STM32 HAL库实战:PWM输出在写Flash时如何避免舵机抖动?一个真实案例的解决思路
STM32 HAL库实战:PWM输出在写Flash时如何避免舵机抖动?一个真实案例的解决思路
在嵌入式开发中,PWM(脉冲宽度调制)信号的控制精度直接影响到舵机等执行机构的稳定性。当STM32在进行Flash写入操作时,由于中断被临时关闭,可能导致PWM信号异常,进而引发舵机抖动。本文将深入分析这一问题的根源,并提供几种经过验证的解决方案。
1. 问题现象与根源分析
最近在一个舵机控制项目中遇到了一个棘手的问题:当STM32执行Flash写入操作时,舵机会出现明显的抖动现象。通过逻辑分析仪捕获的波形显示,正常情况下稳定的PWM信号在写Flash期间变成了20ms高电平、20ms低电平的异常波形。
问题根源可以归结为以下几点:
中断关闭的影响:Flash写入操作需要关闭全局中断(
__disable_irq()),而PWM的比较值更新通常是在定时器中断中完成的。当中断被关闭,比较值无法及时更新,导致PWM信号异常。硬件特性限制:STM32的定时器在中断被禁用期间会继续运行,但无法响应比较匹配事件,造成输出信号锁定在最后状态或出现不可预测的跳变。
舵机控制特性:舵机对PWM信号的稳定性非常敏感,特别是信号中的高电平持续时间决定了舵机位置。异常信号会导致舵机不断尝试调整位置,表现为抖动。
2. HAL库中PWM控制的几种方式
在STM32 HAL库中,控制PWM输出有多种方法,每种方法适用于不同的场景:
| 方法 | 函数调用 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|---|
| 修改比较值 | __HAL_TIM_SET_COMPARE() | 动态调整占空比 | 响应快,不中断PWM输出 | 需要中断配合 |
| 停止定时器 | HAL_TIM_OC_Stop() | 完全停止PWM输出 | 彻底停止输出 | 重启需要时间 |
| 强制输出电平 | HAL_TIM_OC_ConfigChannel() | 紧急状态控制 | 立即生效 | 配置复杂 |
| 禁用通道 | TIM_CCxCmd(TIMx, TIM_CHANNEL_x, DISABLE) | 临时关闭特定通道 | 精确控制 | 需要直接寄存器操作 |
提示:在选择解决方案时,需要考虑系统实时性要求、中断关闭时间以及对其他外设的影响。
3. 解决方案一:等待低电平点同步
这种方法的核心思想是在执行Flash写入前,确保PWM信号处于低电平状态,然后锁定输出。
3.1 实现步骤
检测当前电平状态:
while(HAL_GPIO_ReadPin(TIMCH_Buff[i].GPIOx, TIMCH_Buff[i].GPIO_Pin) != GPIO_PIN_RESET) { // 等待直到引脚变为低电平 }设置比较值为最大值:
__HAL_TIM_SET_COMPARE(&TIMCH_Buff[i].htim, TIMCH_Buff[i].channel, 64000);执行Flash写入:
__disable_irq(); Write_Flash_Buf(FLASH_RF_PIN_MODE_ADR, (uint8_t *)modebuff, sizeof(modebuff)); Write_Flash_Buf(FLASH_RF_PIN_DATA_ADR, Slave_re_Pindata_temp, sizeof(Slave_re_Pindata_temp)); __enable_irq();
3.2 定时器中断中的处理
为了配合这种方法,需要在定时器中断中添加特殊处理:
if(HAL_GPIO_ReadPin(timch->GPIOx, timch->GPIO_Pin) == GPIO_PIN_SET) { // 高电平时的处理 timch->count = __HAL_TIM_GET_COMPARE(&timch->htim, timch->channel) + (4864+Buf[rps[timch->rpspin].mode]-2048); timch->count = (timch->count > TIM_PERIOD) ? (timch->count-TIM_PERIOD) : timch->count; __HAL_TIM_SET_COMPARE(&timch->htim, timch->channel, timch->count); } else { // 低电平时的处理 __HAL_TIM_SET_COMPARE(&timch->htim, timch->channel, 64000); }4. 解决方案二:定时器启停控制
这种方法不依赖中断,而是直接控制定时器的启停:
// 等待低电平 while(HAL_GPIO_ReadPin(TIMCH_Buff[i].GPIOx, TIMCH_Buff[i].GPIO_Pin) != GPIO_PIN_RESET){} // 停止定时器输出 HAL_TIM_OC_Stop_IT(&TIMCH_Buff[i].htim, TIMCH_Buff[i].channel); // 执行Flash写入 Write_Flash_Buf(FLASH_RF_PIN_MODE_ADR,(uint8_t *) modebuff, sizeof(modebuff)); Write_Flash_Buf(FLASH_RF_PIN_DATA_ADR, Slave_re_Pindata_temp, sizeof(Slave_re_Pindata_temp)); // 重新启动定时器 HAL_TIM_OC_Start_IT(&TIMCH_Buff[i].htim, TIMCH_Buff[i].channel);优点:
- 不依赖中断状态
- 实现简单直接
缺点:
- 定时器重启需要一定时间
- 可能引入微小的时序偏差
5. 进阶优化:DMA辅助的Flash写入
对于更高要求的应用,可以考虑使用DMA来减少CPU干预和中断关闭时间:
配置DMA通道:
hdma_flash.Instance = DMA1_Channel1; hdma_flash.Init.Direction = DMA_MEMORY_TO_MEMORY; hdma_flash.Init.PeriphInc = DMA_PINC_ENABLE; hdma_flash.Init.MemInc = DMA_MINC_ENABLE; hdma_flash.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_flash.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_flash.Init.Mode = DMA_NORMAL; hdma_flash.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_flash);使用DMA写入Flash:
HAL_DMA_Start(&hdma_flash, (uint32_t)src, (uint32_t)dst, size); HAL_DMA_PollForTransfer(&hdma_flash, HAL_DMA_FULL_TRANSFER, 100);
这种方法可以显著减少中断关闭时间,但需要更复杂的配置和对DMA资源的合理管理。
6. 实际项目中的经验分享
在多个实际项目中应用这些解决方案后,总结出以下几点经验:
等待时间优化:在等待低电平的循环中,可以加入超时机制,避免死锁:
uint32_t timeout = 1000; // 1ms超时 while(HAL_GPIO_ReadPin(GPIOx, GPIO_Pin) != GPIO_PIN_RESET && timeout--) { // 适当延时 }多通道协调:当需要控制多个PWM通道时,应该:
- 按顺序等待每个通道进入低电平
- 批量设置所有通道的比较值
- 执行Flash操作
- 恢复所有通道
性能考量:对于高频PWM信号,等待低电平可能引入不可接受的延迟,此时应考虑:
- 使用更高优先级的定时器
- 采用硬件PWM生成方式
- 优化Flash写入频率和大小
在最近的一个机械臂控制项目中,采用方案一结合DMA辅助写入,成功将舵机抖动控制在不可感知的范围内,同时保证了系统响应速度。
