裸机开发中DMA传输配置与中断优先级分组实战
扫描二维码
随时随地手机看文章
在STM32等MCU的裸机开发中,DMA(直接存储器访问)是释放CPU、实现高速数据传输的核心。然而,DMA常与中断(如传输完成、半传输)配合使用,一旦中断优先级分组(NVIC)配置不当,极易导致“数据还没传完就被打断”或“中断嵌套死锁”。本文将直击DMA配置的实战痛点。
一、DMA配置:从外设到内存的“高速公路”
以STM32 HAL库的ADC+DMA为例,正确的初始化流程是成功的一半。
1. 关键初始化代码
// DMA句柄
DMA_HandleTypeDef hdma_adc1;
ADC_HandleTypeDef hadc1;
void MX_DMA_Init(void) {
__HAL_RCC_DMA2_CLK_ENABLE();
hdma_adc1.Instance = DMA2_Stream0;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
// 方向:外设到内存
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
// 外设地址不变,内存地址递增
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
// 数据宽度:半字(16位ADC)
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
// 模式:循环(Circular)或普通(Normal)
hdma_adc1.Init.Mode = DMA_CIRCULAR;
// 优先级:高(当有多个DMA流时)
hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_adc1);
// 将DMA与ADC句柄绑定
__HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}
避坑点:DMA_CIRCULAR模式下,若不开启半传输中断(HT)和传输完成中断(TC),将无法得知数据更新时机。
二、中断优先级分组:NVIC的“金字塔”结构
STM32使用NVIC(嵌套向量中断控制器)管理中断优先级。其核心是优先级分组(Priority Grouping),决定了抢占优先级和子优先级的位数。
1. HAL库中的分组设置
在main()函数初始化阶段调用:
int main(void) {
HAL_Init();
// 设置优先级分组为 Group 4
HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);
// ... 其他初始化
}
NVIC_PRIORITYGROUP_4意味着:4位抢占优先级,0位子优先级。这是裸机开发中最推荐的设置,因为它提供了16级抢占优先级,逻辑最简单。
2. 抢占优先级 vs 子优先级
• 抢占优先级(Preemption Priority):高抢占优先级的中断可以打断低抢占优先级的中断。
• 子优先级(Subpriority):仅在两个中断同时挂起时,决定谁先执行,不能互相打断。
三、DMA中断与用户中断的优先级实战
假设我们有一个高实时性任务(如电机PWM)和一个DMA传输完成中断(数据处理)。
1. 配置DMA传输完成中断
// 在DMA初始化后,设置中断优先级
// 假设DMA2_Stream0_IRQn是ADC DMA的中断号
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
这里的2是抢占优先级(0最高,15最低)。
2. 配置高实时性外设中断(如TIM1)
// TIM1更新中断(用于电机控制)
HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 1, 0); // 抢占优先级为1
HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn);
3. 中断抢占逻辑分析
• TIM1 (抢占优先级=1) 可以打断 DMA (抢占优先级=2)。
• DMA 不能打断 TIM1。
• 结果:电机控制不会被数据处理延迟,保证了系统的实时性。
四、裸机下的DMA+中断协作模式
在裸机中,通常采用“中断通知+后台处理”模式。
// DMA传输完成中断回调函数(HAL库)
void HAL_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma) {
if (hdma->Instance == DMA2_Stream0) {
// 仅设置一个标志位,不做耗时操作!
g_dma_done_flag = 1;
}
}
int main(void) {
// ... 初始化 ...
while (1) {
if (g_dma_done_flag) {
g_dma_done_flag = 0;
// 在while(1)中处理数据
process_adc_data();
}
// 其他后台任务
led_blink();
}
}
黄金法则:ISR(中断服务函数)越短越好。不要在DMA中断里做数据处理,只发信号。
五、常见错误与排查
1. 优先级分组不一致:在CubeMX生成的代码中,分组可能在HAL_Init()中被修改。确保所有HAL_NVIC_SetPriority()调用都在分组设置之后。
2. DMA中断不进:忘记调用HAL_NVIC_EnableIRQ(),或者DMA Stream的XferCpltCallback未启用。
3. HardFault:在DMA中断中访问了非法的内存地址,通常是DMA目标缓冲区定义在了栈(Stack)中,应定义为全局数组。
六、结语
裸机开发中,DMA是“搬运工”,中断是“调度员”。DMA配置决定了传输效率,NVIC优先级分组决定了系统的实时响应层级。牢记“抢占优先级用于关键时序,子优先级用于非实时排队”,并坚持“ISR只发信号,while(1)里干活”的原则,你的裸机系统将既高效又稳定。





