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

STM32 MCU使用DMA控制PWM时中断失效问题排查

关于STM32 LL库DMA传输PWM无半/全传输中断的问题排查

问题背景

我基于STM32 MCU使用LL库控制TIM2输出PWM信号,驱动SK6812RGBW LED。为提升LED刷新速度、释放MCU算力,配置DMA向TIM2_CH3的比较输出寄存器(CCR3)传输PWM比较值。已完成以下配置:

  • 将TIM2配置为PWM比较输出模式(CH3通道)
  • 启用DMA通道并与TIM2对接
  • 配置GPIOA2为复用功能输出PWM(用于调试)

核心问题

DMA的半传输(HT)和全传输(TC)中断完全无法触发,已验证PWM与定时器配置无误,怀疑DMA未被正确初始化或触发。当前依赖定时器自动重载时的更新事件,触发外设向DMA发送数据请求,将内存中aCCValue数组的内容传输到CCR3寄存器。

关键代码

DMA初始化函数

static void MX_DMA_Init(void)
{

  /* Init with LL driver */
  /* DMA controller clock enable */
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  NVIC_SetPriority(DMA1_Channel1_IRQn, 0);
  NVIC_EnableIRQ(DMA1_Channel1_IRQn);

  LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
  LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1);
  LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);

}

TIM2初始化函数(含PWM及DMA配置)

static void MX_TIM2_Init(void)
{

  /* USER CODE BEGIN TIM2_Init 0 */
  static int autoreloadVal = 17000;

  aCCValue[0] = (uint32_t)(((uint32_t) 75 * (autoreloadVal - 1)) / 100);
  /* Compute compare value to generate a duty cycle at 50% */
  aCCValue[1] = (uint32_t)(((uint32_t) 50 * (autoreloadVal - 1)) / 100);
  /* Compute compare value to generate a duty cycle at 25% */
  aCCValue[2] = (uint32_t)(((uint32_t) 25 * (autoreloadVal - 1)) / 100);

  /* USER CODE END TIM2_Init 0 */

  LL_TIM_InitTypeDef TIM_InitStruct = {0};
  LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* Peripheral clock enable */
  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_TIM2);

  /* TIM2 DMA Init */

  /* TIM2_CH3 Init */
  LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMA_REQUEST_8);

  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);

  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_HIGH);

  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);

  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_HALFWORD); //different sizes from example but consistent sizing between mem and peripheral

  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_HALFWORD);

  LL_DMA_ConfigAddresses(DMA1, LL_DMA_CHANNEL_1, (uint32_t)&aCCValue, (uint32_t)&TIM2->CCR3, LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1));

  LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, CC_VALUE_NB);

  /* USER CODE BEGIN TIM2_Init 1 */

  /* USER CODE END TIM2_Init 1 */
  TIM_InitStruct.Prescaler = 0;
  TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
  TIM_InitStruct.Autoreload = autoreloadVal-LL_TIM_IC_FILTER_FDIV1_N2;
  TIM_InitStruct.ClockDivision = LL_TIM_CLOCKDIVISION_DIV1;
  LL_TIM_Init(TIM2, &TIM_InitStruct);
  LL_TIM_EnableARRPreload(TIM2);
  LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL);
  LL_TIM_OC_EnablePreload(TIM2, LL_TIM_CHANNEL_CH3);
  TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
  TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE;
  TIM_OC_InitStruct.CompareValue = 5;
  TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH;
  LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH3, &TIM_OC_InitStruct);
  LL_TIM_OC_EnableFast(TIM2, LL_TIM_CHANNEL_CH3);
  LL_TIM_SetTriggerOutput(TIM2, LL_TIM_TRGO_UPDATE);
  LL_TIM_DisableMasterSlaveMode(TIM2);

  /* USER CODE BEGIN TIM2_Init 2 */

  LL_TIM_EnableDMAReq_UPDATE(TIM2);
  LL_TIM_EnableDMAReq_CC3(TIM2);
  LL_TIM_EnableCounter(TIM2);
  LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH3);

  NVIC_SetPriority(TIM2_IRQn, 0);
  NVIC_EnableIRQ(TIM2_IRQn);
  LL_TIM_EnableIT_UPDATE(TIM2);
  /* USER CODE END TIM2_Init 2 */

  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
  /**TIM2 GPIO Configuration
  PA2   ------> TIM2_CH3
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_2;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;
  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;
  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  GPIO_InitStruct.Alternate = LL_GPIO_AF_2;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN TIM2_Init 3 */
  /* Force update generation */
   LL_TIM_GenerateEvent_UPDATE(TIM2);
  /* USER CODE END TIM2_Init 3 */

}

潜在原因分析

  • 半传输中断未启用:当前DMA初始化仅开启了全传输(TC)和传输错误(TE)中断,未调用LL_DMA_EnableIT_HT开启半传输中断,这会直接导致半传输中断无法触发。
  • DMA通道启用顺序错误:在MX_DMA_Init中提前启用了DMA通道,后续MX_TIM2_Init又修改了DMA的地址、数据长度等核心参数。DMA通道启用后修改配置可能导致状态异常,正确顺序应为先完成所有DMA参数配置,最后再调用LL_DMA_EnableChannel启用通道。
  • DMA请求源与触发信号不匹配:同时开启了LL_TIM_EnableDMAReq_UPDATE和LL_TIM_EnableDMAReq_CC3,但DMA请求配置为LL_DMA_REQUEST_8(对应TIM2_CH3的DMA请求)。若依赖定时器更新事件触发DMA,需确认该请求号是否对应TIM2_UPDATE的DMA请求,而非CH3的请求(需查阅STM32参考手册核对)。
  • 循环模式下传输长度不匹配:配置了循环模式,但需确保CC_VALUE_NB的值与aCCValue数组的元素数量(3)一致,否则会导致传输长度异常,中断无法正常触发。
  • TIM2 DMA请求触发条件异常:开启了ARR预加载,但需确认UPDATE事件是否能正常生成;手动触发LL_TIM_GenerateEvent_UPDATE时,DMA通道可能尚未完成配置,导致无法触发第一次DMA传输。
  • 数据对齐与内存有效性问题:配置了半字对齐,需确保aCCValue数组的类型为uint16_t;同时aCCValue需为全局或静态数组,避免DMA传输时内存被释放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 21:34:53