本文主要是介绍STM32中的DMA,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
概念
全称是Direct Memory Access,中文意思为直接存储器访问。
DMA可用于实现外设与存储器之间或者存储器与存储器之间数据传输的高效性。之所以称为高效,是因为DMA传输数据移动过程无需CPU直接操作,这样节省的 CPU 资源就可供其它操作使用。从硬件层面来理解,DMA就好像是RAM与I/O设备间数据传输的通路,外设与存储器之间或者存储器与存储器之间可以直接在这条通路上进行数据传输。这里说的外设一般指外设的数据寄存器,比如ADC、 SPI、 I2C等外设的数据寄存器,存储器一般是指片内SRAM、外部存储器、片内 Flash 等。
利用DMA获取数据的流程
- UART控制器读取外设发送的数据
- DMA硬件上自动读取UART数据寄存器的数据
- DMA硬件上自动将获取的数据搬移到内存中
- UART控制器给CPU发送中断信号通知CPU数据读取完毕
- CPU可以访问内存中的数据
总结:CPU无需频繁的访问数据寄存器,CPU只关心内存
利用DMA发送数据的流程
- CPU将数据写入指定的内存中
- DMA硬件上自动从指定的内存中获取要发送的数据
- DMA硬件上自动将数据搬移到数据寄存器中
- UART控制器硬件上自动将数据发送出去
- DMA给CPU发送中断信号通知数据发送完毕
结论:CPU无需频繁的访问数据寄存器,CPU只关心内存
DMA的特性
- STM32F103有 2 个 DMA 控制器,分别是DMA1和DMA2
- DMA1 有 7 个通道
- DMA2 有 5个通道
- 每个通道专门用来管理来自于一个或多个外设对存储器访问的请求
在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4
代码实现
标准库函数和结构体
typedef struct
{uint32_t DMA_PeripheralBaseAddr; uint32_t DMA_MemoryBaseAddr; uint32_t DMA_DIR; uint32_t DMA_BufferSize; uint32_t DMA_PeripheralInc; uint32_t DMA_MemoryInc; uint32_t DMA_PeripheralDataSize; uint32_t DMA_MemoryDataSize; uint32_t DMA_Mode; uint32_t DMA_Priority; uint32_t DMA_M2M;
}DMA_InitTypeDef;
初始化
采用DMA发送数据完成后,使用中断发送给CPU结束信号,需要匹配中断和DMA已经NVIC控制器
void My_DMA_Init(void)
{// 1.打开DMA1控制器时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);// 2.配置DMA1通道4作为串口1发送 : 内存 -> 寄存器DMA_InitTypeDef DMA_Config;DMA_Config.DMA_MemoryBaseAddr = (u32)UART1DMA_TxBuff; // 内存缓冲区首地址DMA_Config.DMA_PeripheralBaseAddr = (u32)&USART1->DR; // 寄存器首地址DMA_Config.DMA_BufferSize = UART1DMA_TXBUFF_SIZE; // 内存缓冲区大小DMA_Config.DMA_DIR = DMA_DIR_PeripheralDST; // 内存->寄存器DMA_Config.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 寄存器不自增DMA_Config.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存自增DMA_Config.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 内存搬移字节为单位DMA_Config.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 寄存器字节位单位DMA_Config.DMA_Mode = DMA_Mode_Normal; // 普通模式DMA_Config.DMA_Priority = DMA_Priority_Medium; // 中等DMA_Config.DMA_M2M = DMA_M2M_Disable; // 禁止内存之间拷贝DMA_Init(DMA1_Channel4, &DMA_Config);// 3.配置DMA1通道4支持DMA_IT_TC中断 DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);// 4.配置NVIC支持DMA1通道4中断 NVIC_InitTypeDef NVIC_Config;NVIC_Config.NVIC_IRQChannel = DMA1_Channel4_IRQn; // DMA1通道4中断 NVIC_Config.NVIC_IRQChannelPreemptionPriority = 0;NVIC_Config.NVIC_IRQChannelSubPriority = 2;NVIC_Config.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_Config);
}
测试函数
// 串口1发送测试函数
void UART1_DMA_Tx_Test(void)
{// 1.初始化内存缓冲区 u32 i;for(i = 0; i < UART1DMA_TXBUFF_SIZE; i++)UART1DMA_TxBuff[i] = 'A';// 2.关闭DMA1通道4DMA_Cmd(DMA1_Channel4, DISABLE);// 3.配置串口1支持DMA的发送 USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);// 4.设置搬移数据的长度DMA_SetCurrDataCounter(DMA1_Channel4, UART1DMA_TXBUFF_SIZE);// 5.打开DMA1通道4DMA_Cmd(DMA1_Channel4, ENABLE);// 打开该功能后, DMA1通道4就会自动从内存缓冲区中搬移数据到串口1的DR中// 搬移完成会触发DMA1通道4 TC中断DMA_TcFlag = 0; // 标识数据还没发完// 6.采用中断的方式判断数据是否搬移完毕 ??while(1){if(DMA_TcFlag){ // printf("\n DMA TX SUCCESS\n");break;}// 做其它业务 ...LED0 = !LED0;delay_ms(200);}
}
中断处理函数
// DMA1通道4触发的中断
// 发送完成触发中断
void DMA1_Channel4_IRQHandler(void){// 1.判断是哪个中断 if(DMA_GetITStatus(DMA1_IT_TC4) != RESET){// 2.清除中断到来位 DMA_ClearITPendingBit(DMA1_IT_TC4);// 3.关闭DMA1通道4DMA_Cmd(DMA1_Channel4, DISABLE);// 4.标志数据发送完毕DMA_TcFlag = 1;}
}
初始化函数和cmd函数中需要将DMA相关添加进来
init.c中
static PINIT_T init_func[] = {LED_Init, // led灯初始化BEEP_Init, // beep初始化 Systick_init, // 滴答定时器初始化KEY_Init, // 按键初始化My_EXTI_Init, // 中断初始化 UART_Init, // 串口初始化AT24C02_Init, // AT24C02初始化DS18B20_Init, // 温度传感器的初始化My_DMA_Init, // DMA初始化0
};cmd.c中
cmd_t cmd[] = {{"led on", LED_On},{"led off", LED_Off},{"beep on", BEEP_On},{"beep off", BEEP_Off},{"EEPROM R", AT24C02_ReadOne}, // 读取单字节{"EEPROM W", AT24C02_WriteOne}, //写入单字节{"EEPROM RS", AT24C02_ReadMul}, // 读取多字节{"EEPROM WS", AT24C02_WriteMul}, // 写入多字节{"temp", DS18B20_Test}, // 获取温度命令{"rom", DS18B20_ReadRom}, // 读取ROM值命令{"dma tx", UART1_DMA_Tx_Test} // DMA发送数据命令
};
实验结果
在串口工具中发送dma tx,串口工具能够显示通过内存写入到寄存器中的数据,本文中是写了1024和'A'
这篇关于STM32中的DMA的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!