You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32F103 PWM+DMA驱动WS2812中断频繁触发数据异常

STM32F103 循环DMA驱动WS2812故障排查方案

核心根因

故障与“中断持续触发”的猜想完全无关,问题出在HAL_TIM_PWM_Stop_DMA的调用时机错误:

  1. HAL_TIM_PWM_PulseFinishedCallback的触发点是DMA把最后一个比较值写入定时器CCR寄存器的瞬间,此时最后一个PWM脉冲还没完成输出,差整整一个定时器周期才会从IO口送出。这时候直接调用停止函数,会直接关断定时器和DMA输出,导致后续bit被截断,就是你看到的72bit只发27bit的现象。
  2. 循环DMA模式下,传输到缓冲区末尾时硬件会自动重载传输计数寄存器,你在全完成回调里直接停止DMA,会打断缓冲区的分段填充流程,DMA读到未更新的旧内存值,自然会出现数据错乱、顺序颠倒。
  3. 测试引脚一直是低电平不是中断太频繁,是你进回调刚把引脚拉高就执行停止函数,高电平只持续几个CPU周期(几十ns),普通逻辑分析仪采样率不够根本抓不到,看起来就像一直是低电平。注释掉停止函数后输出恢复正常,恰恰证明中断触发频率是对的,没有过度触发的问题。

分步修复方案

  • 修正DMA停止时机
    禁止在半传输/全传输完成回调中直接调用HAL_TIM_PWM_Stop_DMA。打开定时器更新中断(计数器溢出中断),等所有bit数据全部填入循环缓冲区后标记待完成状态,等最后一个PWM脉冲输出完成、触发更新中断时,再在HAL_TIM_PeriodElapsedCallback中执行DMA停止操作,保证最后一个bit完整输出。
  • 核对循环缓冲区配置
    • 不要将循环缓冲区长度设置为与总发送bit数(72bit)一致,建议设置为16/32个半字长度(对应16/32个PWM比较值),通过半满、全满中断分段填充数据,既节省内存也能避免循环重载时的数据重复发送问题。
    • 确认DMA数据宽度配置为半字(16bit),和STM32F1系列定时器CCR寄存器位宽匹配,避免字节传输导致的比较值写入错误。
  • 修正中断测试逻辑
    测试中断触发频率时,引脚拉高后插入1~2个__NOP()空指令再拉低,保证高电平宽度足够被逻辑分析仪捕获,不要在翻转操作后立刻执行寄存器级操作类的长耗时逻辑。
  • 补充复位时序
    停止DMA输出后,保持IO低电平至少50us,满足WS2812的帧复位要求,避免后续数据传输异常。

参考实现逻辑

// 发送状态定义
typedef enum {
  WS2812_IDLE,
  WS2812_SENDING,
  WS2812_WAIT_RESET
} WS2812_StateDef;
volatile WS2812_StateDef ws2812_state = WS2812_IDLE;
uint16_t circ_buf[32]; // 32长度半字循环缓冲区,对应32个bit的PWM比较值
uint8_t bit_send_cnt = 0;
const uint16_t PWM_1 = 59;  // 匹配实际时序的1码比较值
const uint16_t PWM_0 = 29;  // 匹配实际时序的0码比较值

// 半传输完成回调:填充缓冲区前半段
void HAL_TIM_PWM_PulseFinishedHalfCpltCallback(TIM_HandleTypeDef *htim) {
  if(htim->Instance != TIM2 || ws2812_state != WS2812_SENDING) return;
  for(uint8_t i=0; i<16; i++){
    if(bit_send_cnt < 72){ // 72bit为单帧总长度
      circ_buf[i] = (get_frame_bit(bit_send_cnt) ? PWM_1 : PWM_0);
      bit_send_cnt++;
    }else{
      circ_buf[i] = 0;
      ws2812_state = WS2812_WAIT_RESET;
    }
  }
}

// 全传输完成回调:填充缓冲区后半段
void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) {
  if(htim->Instance != TIM2) return;
  // 中断测试逻辑,加空指令保证电平可被采样
  HAL_GPIO_WritePin(TEST_GPIO_Port, TEST_Pin, GPIO_PIN_SET);
  __NOP();__NOP();
  HAL_GPIO_WritePin(TEST_GPIO_Port, TEST_Pin, GPIO_PIN_RESET);

  if(ws2812_state != WS2812_SENDING) return;
  for(uint8_t i=16; i<32; i++){
    if(bit_send_cnt < 72){
      circ_buf[i] = (get_frame_bit(bit_send_cnt) ? PWM_1 : PWM_0);
      bit_send_cnt++;
    }else{
      circ_buf[i] = 0;
      ws2812_state = WS2812_WAIT_RESET;
    }
  }
}

// 定时器更新中断:最后一个脉冲输出完成后再停止DMA
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
  if(htim->Instance == TIM2 && ws2812_state == WS2812_WAIT_RESET){
    HAL_TIM_PWM_Stop_DMA(htim, TIM_CHANNEL_1);
    ws2812_state = WS2812_IDLE;
    bit_send_cnt = 0;
  }
}

内容的提问来源于stack exchange,提问作者Marius

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 23:39:35