如何用CMSIS CORE实现DMA数组拷贝?STM32F103RC调试异常求助
针对STM32F103RC这类芯片,用CMSIS CORE直接操作寄存器实现DMA内存到内存拷贝,步骤很清晰:
第一步:开启DMA时钟
首先得给DMA控制器上电,比如用DMA2(F1系列中DMA2原生支持内存到内存传输,DMA1也能模拟但步骤稍麻烦),通过RCC寄存器开启时钟:RCC->AHBENR |= RCC_AHBENR_DMA2EN; // 启用DMA2时钟第二步:配置DMA通道参数
选一个空闲的通道(比如DMA2_Channel1),先关闭通道再配置,避免配置过程中误触发:// 先关闭通道,确保配置安全 DMA2_Channel1->CCR &= ~DMA_CCR_EN; // 开启内存到内存模式 DMA2_Channel1->CCR |= DMA_CCR_MEM2MEM; // 设置数据宽度(比如数组是uint32_t就用32位,根据实际类型调整) DMA2_Channel1->CCR |= DMA_CCR_PSIZE_1 | DMA_CCR_PSIZE_0; // 外设端数据宽度32位 DMA2_Channel1->CCR |= DMA_CCR_MSIZE_1 | DMA_CCR_MSIZE_0; // 内存端数据宽度32位 // 开启地址自增:源和目标地址每次传输后自动偏移 DMA2_Channel1->CCR |= DMA_CCR_PINC | DMA_CCR_MINC; // 设置源数组和目标数组的地址 DMA2_Channel1->CPAR = (uint32_t)sourceArr; // 源地址(外设端地址,MEM2MEM模式下作为数据源) DMA2_Channel1->CMAR = (uint32_t)destArr; // 目标地址(内存端地址) // 设置要传输的元素个数 DMA2_Channel1->CNDTR = sizeof(sourceArr)/sizeof(sourceArr[0]);第三步:启动传输(可选中断/轮询)
如果不需要中断,直接轮询完成标志即可:// 开启DMA通道,开始传输 DMA2_Channel1->CCR |= DMA_CCR_EN; // 等待传输完成 while(!(DMA2->ISR & DMA_ISR_TCIF1)); // 清除完成标志,避免后续误触发 DMA2->IFCR |= DMA_IFCR_CTCIF1;如果需要用中断通知传输完成,还要配置NVIC和中断服务函数:
// 开启传输完成中断 DMA2_Channel1->CCR |= DMA_CCR_TCIE; // 配置NVIC,启用DMA2通道1中断 NVIC_EnableIRQ(DMA2_Channel1_IRQn); NVIC_SetPriority(DMA2_Channel1_IRQn, 1); // 合理设置优先级,避免被抢占 // 启动传输 DMA2_Channel1->CCR |= DMA_CCR_EN;对应的中断服务函数:
void DMA2_Channel1_IRQHandler(void) { if(DMA2->ISR & DMA_ISR_TCIF1) { // 这里写传输完成后的操作,比如你要的LED闪烁 GPIOx->ODR ^= GPIO_Pin_x; // 翻转LED引脚电平 // 必须清除中断标志!否则会一直触发中断 DMA2->IFCR |= DMA_IFCR_CTCIF1; } }
你说预设主函数4次、中断1次共5次闪烁,但实际只有2次,咱们可以从这些方向逐步查:
先确认主函数的闪烁逻辑是否正常
先把DMA相关代码注释掉,单独跑主函数里的LED闪烁,看能不能正常闪4次。如果这一步就有问题,那锅不在DMA,而是LED的GPIO配置、延时函数或者主循环逻辑出了问题。检查DMA时钟是否真的开了
很多人容易漏开DMA时钟!比如你用DMA1的话,要加RCC->AHBENR |= RCC_AHBENR_DMA1EN;,没开时钟的话DMA完全不会工作,中断更别谈触发。验证DMA通道配置的细节
- 内存到内存模式是否正确:如果用的是DMA1,它不支持原生MEM2MEM,得把传输方向设为内存到外设,再把源/目标都设为内存地址;如果是DMA2,要确认
CCR寄存器的MEM2MEM位已经置1。 - 地址自增是否开启:
PINC和MINC要是没开,DMA会一直读写同一个地址,不仅传输错误,还可能触发异常。 - 传输数量是否正确:
CNDTR的值要和数组元素个数一致,设小了传输提前结束,设大了会访问越界。 - 数据宽度是否匹配:比如数组是
uint8_t但你设成32位宽度,会一次传4个字节,导致传输次数计算错误。
- 内存到内存模式是否正确:如果用的是DMA1,它不支持原生MEM2MEM,得把传输方向设为内存到外设,再把源/目标都设为内存地址;如果是DMA2,要确认
排查中断配置的问题
- 确认DMA通道的
TCIE(传输完成中断)是否开启:CCR寄存器的TCIE位要设为1。 - NVIC是否正确配置:要调用
NVIC_EnableIRQ(对应通道的IRQn);,优先级设置也要合理,别被其他高优先级中断抢占导致中断函数没机会执行。 - 中断标志是否清除:中断服务函数里必须清除
TCIF标志,不然中断会一直触发,或者下次无法正常触发。
- 确认DMA通道的
确认DMA是否真的完成了传输
可以在主函数里轮询TCIF标志,或者调试时看DMA寄存器:- 看
CNDTR的值是否递减到0,如果一直不变,说明DMA根本没工作。 - 看
ISR寄存器的TCIF位是否置位,要是没置位,说明传输没完成,中断自然不会触发。
- 看
检查LED闪烁的冲突逻辑
比如主函数和中断用的是同一个LED引脚?会不会主函数的延时和中断触发时机重叠,导致闪烁次数看起来不对?可以给中断单独用一个LED引脚,或者加个全局计数变量(比如irq_trigger_count,中断触发时加1),主函数里打印这个变量的值,确认中断到底有没有触发。排查数组的存储位置问题
如果sourceArr用const修饰被存在Flash里,要确认DMA是否能正常访问Flash(F1的DMA是支持的,但配置要正确);另外可以给数组加volatile修饰,避免编译器优化把数组放到奇怪的位置,导致DMA访问出错。
内容的提问来源于stack exchange,提问作者Devjeet Mandal




