摘要:大家好,我是你们的嵌入式老司机。今天我们要聊一个让很多初学者头疼,但学会了就能大幅提升系统性能的技术——DMA。
为什么你的嵌入式系统总是卡顿?可能是你没用好DMA这个神器!今天就来教你如何玩转DMA,让CPU从此不再"996"!
大家好,我是你们的嵌入式老司机。今天我们要聊一个让很多初学者头疼,但学会了就能大幅提升系统性能的技术—— DMA 。
DMA是什么?DMA(Direct Memory Access,直接存储器访问)是一种允许外设直接与内存进行数据交换,而 不需要CPU参与 的技术。
简单来说,DMA就像公司的 专职快递员 ,而CPU就是 高级工程师 。如果没有DMA,每次需要搬数据时,都得让高级工程师停下手中的重要工作去当搬运工,这显然是极大的浪费!
为什么DMA如此重要? // 没有DMA的情况:CPU亲自搬数据void copy_data_without_dma(void) { for(int i = 0; i DATA_SIZE; i++) { destination[i] = source[i]; // CPU在这里当搬运工 // 此时CPU不能做其他重要工作! }}// 有DMA的情况:CPU当监工void copy_data_with_dma(void) { setup_dma_transfer(source, destination, DATA_SIZE); // CPU可以继续处理其他任务 process_important_task; wait_for_dma_completion;} 1. 传输配置阶段CPU设置DMA控制器的参数:
源地址:数据从哪里来
目标地址:数据到哪里去
传输数量:要传输多少数据
传输模式:怎么传输
2. 数据传输阶段DMA控制器接管总线,直接在外设和内存之间传输数据,CPU可以继续执行其他任务。
3. 传输完成阶段DMA控制器产生中断通知CPU,CPU进行后续处理。
三、STM32中的DMA实战 // 配置DMA传输void DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; // 开启DMA时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 配置DMA DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tx_buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 内存到外设 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel4, &DMA_InitStructure); DMA_Cmd(DMA1_Channel4, ENABLE);}高级用法:双缓冲技术 // 双缓冲DMA配置,实现"乒乓操作"void Double_Buffer_DMA_Config(void) { // 设置两个缓冲区 uint8_t buffer1[BUFFER_SIZE]; uint8_t buffer2[BUFFER_SIZE]; // 配置DMA双缓冲模式 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)buffer1; DMA_InitStructure.DMA_Memory1BaseAddr = (uint32_t)buffer2; DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; DMA_DoubleBufferModeConfig(DMA1_Channel4, (uint32_t)buffer2, DMA_Memory_1); DMA_DoubleBufferModeCmd(DMA1_Channel4, ENABLE);}// DMA传输完成中断处理void DMA1_Channel4_IRQHandler(void) { if(DMA_GetITStatus(DMA1_IT_TC4)) { // 判断当前使用的是哪个缓冲区 if(DMA_GetCurrentMemoryTarget(DMA1_Channel4) == DMA_Memory_0) { // 处理buffer1中的数据 process_data(buffer1); } else { // 处理buffer2中的数据 process_data(buffer2); } DMA_ClearITPendingBit(DMA1_IT_TC4); }}// ADC使用DMA进行连续采样void ADC_DMA_Config(void) { // 配置ADC ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; ADC_InitStructure.ADC_DMAContinuousRequests = ENABLE; // 配置DMA DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = ADC_BUFFER_SIZE; // 启动传输 DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE);}场景2:内存到内存的快速拷贝 // 使用DMA加速内存拷贝void memory_copy_with_dma(uint32_t* src, uint32_t* dest, uint32_t size) { DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)src; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)dest; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = size; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_M2M = DMA_M2M_Enable; // 内存到内存模式 DMA_Init(DMA1_Channel6, &DMA_InitStructure); DMA_Cmd(DMA1_Channel6, ENABLE); // 等待传输完成 while(DMA_GetFlagStatus(DMA1_FLAG_TC6) == RESET);}场景3:SPI/I2C/UART数据收发 // UART使用DMA接收数据void UART_DMA_RX_Config(void) { // 配置UART USART_InitTypeDef USART_InitStructure; USART_InitStructure.USART_Mode = USART_Mode_Rx; // 配置DMA接收 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uart_rx_buffer; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = UART_RX_BUFFER_SIZE; USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE);}问题 :CPU和DMA同时访问同一块内存时可能出现数据不一致。
解决方案 :
// 使用内存屏障确保数据一致性void dma_transfer_safe(void* src, void* dest, uint32_t size) { // 刷新CPU缓存 SCB_CleanDCache; // 启动DMA传输 start_dma_transfer(src, dest, size); // 等待传输完成 while(!is_dma_done); // 无效化缓存,确保CPU读取最新数据 SCB_InvalidateDCache;}坑2:中断响应时机问题 :DMA传输完成中断来得太晚或太早。
解决方案 :
// 精确控制中断时机void precise_dma_control(void) { // 传输完成一半时产生中断 DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // 同时使能半传输和全传输中断 DMA_ITConfig(DMA1_Channel4, DMA_IT_HT | DMA_IT_TC, ENABLE);}// 使用对齐内存提升DMA性能__align(4) uint8_t aligned_buffer[1024]; // 4字节对齐void optimized_dma_transfer(void) { // 确保地址对齐 if(((uint32_t)src & 0x3) == 0 && ((uint32_t)dest & 0x3) == 0) { // 使用32位传输 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; }}技巧2:批量传输优化 // 使用突发传输提升效率void burst_transfer_config(void) { // 配置DMA突发传输 DMA_InitStructure.DMA_BurstSize = DMA_BurstSize_4; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_INC4; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_INC4;}// I2S音频DMA传输void audio_dma_init(void) { // 配置I2S I2S_InitTypeDef I2S_InitStructure; I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx; // 配置DMA双缓冲 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI2->DR; DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)audio_buffer1; DMA_InitStructure.DMA_Memory1BaseAddr = (uint32_t)audio_buffer2; DMA_InitStructure.DMA_BufferSize = AUDIO_BUFFER_SIZE; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 设置传输完成回调 DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);}// 音频数据处理void process_audio_data(uint16_t* buffer, uint32_t size) { // 应用音频效果:例如均衡器、混响等 for(uint32_t i = 0; i buffer[i] = apply_audio_effect(buffer[i]); }}掌握DMA技术是嵌入式开发从入门到精通的关键一步。通过合理使用DMA,你可以:
提升系统性能 :让CPU专注于计算任务
降低功耗 :减少CPU工作时间
提高实时性 :确保关键任务及时响应
简化编程模型 :异步处理数据流
记住 :DMA不是万能的,但对于数据密集型应用,它是必不可少的利器!
思考题 :在你的项目中,哪些地方可以用DMA来优化性能?欢迎在评论区分享你的想法!
下期预告 :《DMA与Cache的爱恨情仇:如何避免数据一致性问题》
来源:寂寞的咖啡