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
相关产品推荐
相关产品推荐

