简介:
最近在学习stm32的外设初始化过程中,学到DMA这个外设的时候,还是花费了不少时间,特此记录一下。
实验:配置DMA搬运UART1的数据 ,串口调试工具给单片机发送数据,然后单片机回发给串口调试工具。
一、DMA简介
DMA:Direct memory access controller,即直接内存访问控制器,简单点来说,dma的功能就是将数据从A搬运到B,而不需要cpu的参与。A和B均可以是内存和外设。即有内存到内存、外设到外设、外设到内存、内存到外设四种情况。
今天我们的实验用到的就是内存到外设的情况,和外设到内存的两种情况。参考手册上有寄存器说明,但是操作寄存器的工作有hal库帮我们做了,所以我们重点放在理解初始化dma的结构体上,配合代码,方便理解和上手,代码读懂了,在结合寄存器手册,思路就非常清晰了。
①stm32系统框图
从系统框图中可以看到,STM32有DMA1和DMA2,DMA1有7个通道,DMA2只有5个通道,且默认DMA1的优先级高于DMA2,也可以通过寄存器配置修改。
②DMA请求映射图和表
这个图和表在参考手册中都有,DMA章节的DMA request mapping小节中有。此处只是简单罗列,
可以看到串口UART1的收发对应的DMA1的通道5和通道4,故配置的时候选择通道4和通道5.
二、实验代码
实验内容:串口调试工具给单片机发送数据,单片机收到后回发给串口调试工具。
实验平台:野火指南者开发板
其实这个功能不需要DMA也能完成,此处只是加了DMA来搬运数据的配置。
1.配置流程
①配置串口 :
将PA9和PA10配置成了串口1的输入输和串口中断优先级配置
//完成配置:将PA9和PA10配置成了串口1的输入输和串口中断优先级配置 void USART1_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStruct; // 打开串口GPIO的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 打开串口外设的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); NVIC_InitStruct.NVIC_IRQChannel = DEBUG_USART_IRQ; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3; NVIC_Init(&NVIC_InitStruct); // 将USART Tx的GPIO配置为推挽复用模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // 将USART Rx的GPIO配置为浮空输入模式 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置串口的工作参数 // 配置波特率 USART_InitStructure.USART_BaudRate = 115200; // 配置 针数据字长 USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 配置停止位 USART_InitStructure.USART_StopBits = USART_StopBits_1; // 配置校验位 USART_InitStructure.USART_Parity = USART_Parity_No ; // 配置硬件流控制 USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 配置工作模式,收发一起 USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 完成串口的初始化配置 USART_Init(USART1, &USART_InitStructure); //使能空闲中断 USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); // 使能串口 USART_Cmd(USART1, ENABLE); }
②配置DMA:
//配置目的:让dma将串口数据寄存器里面的内容搬运到ReceiveBuff里面
//配置目的:让dma将串口数据寄存器里面的内容搬运到ReceiveBuff里面 void USART1_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; // DMA挂接在AHB总线上,开启DMA是时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 设置DMA源地址:串口数据寄存器地址*/ DMA_InitStructure.DMA_PeripheralBaseAddr = USART1->DR; // 内存地址(要传输的变量的指针) DMA_InitStructure.DMA_MemoryBaseAddr = (u32)ReceiveBuff; // 方向:从外设到内存,即从 USART1->DR搬运到ReceiveBuff中 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 传输大小 DMA_InitStructure.DMA_BufferSize = 5000; // 外设地址不增 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模式,一次或者循环模式 // DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 优先级:中 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; // 禁止内存到内存的传输 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 配置DMA通道 DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure); // 使能DMA DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE); }
③中断服务函数:
单片机收到串口1的数据后将其回发给串口调试工具
void USART1_IRQHandler(void) { uint16_t t; if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE) == SET) //检查中断是否发生 { DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE); //关闭DMA传输 t = DMA_GetCurrDataCounter(USART_TX_DMA_CHANNEL); //获取剩余的数据数量 Usart_SendArray(DEBUG_USARTx,ReceiveBuff,RECEIVEBUFF_SIZE-t); //向电脑返回数据(接收数据数量 = SENDBUFF_SIZE - 剩余未传输的数据数量) DMA_SetCurrDataCounter(USART_TX_DMA_CHANNEL,RECEIVEBUFF_SIZE); //重新设置传输的数据数量 DMA_Cmd(USART_TX_DMA_CHANNEL,ENABLE); //开启DMA传输 USART_ReceiveData(DEBUG_USARTx); //读取一次数据,不然会一直进中断 USART_ClearFlag(DEBUG_USARTx,USART_FLAG_IDLE); //清除串口空闲中断标志位 } }
④主函数编写:
int main(void) { /* 初始化USART */ USART1_Config(); /* 配置使用DMA模式 */ USART1_DMA_Config(); printf("\r\n串口调试工具发送数据到单片机,单片机回发给串口调试工具\r\n"); /* USART1 向 DMA发出RX请求 */ USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); //用电脑向开发板串口发送数据,数据会返回到电脑。 while(1) { } }
⑤实验下载验证:
⑥总结
其实DMA简单点理解就是和memcpy是一样的功能,只是memcpy需要cpu是搬运,而DMA帮cpu完成了这个搬运,所以cpu空下来了就可以干其他事情。其它的小细节还需要继续看手册。