STM32之DMA让串口接收任意长度数据

您所在的位置:网站首页 串口buffer设置 STM32之DMA让串口接收任意长度数据

STM32之DMA让串口接收任意长度数据

2024-06-12 18:07| 来源: 网络整理| 查看: 265

DMA 简介

DMA(Direct Memory Access,直接存储器访问) ,DMA 传输是将数据从一个地址空间复制到另外一个地址空间。CPU 只负责初始化这个传输动作,传输动作本身是由 DMA 控制器来实行和完成。

原理

一个完整的DMA传输过程必须经过DMA请求、DMA响应、DMA传输、DMA结束4个步骤。

DMA请求 CPU对DMA控制器初始化,并向I/O接口发出操作命令,I/O接口提出DMA请求。DMA响应 DMA控制器对DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示DMA已经响应,通过DMA控制器通知I/O接口开始DMA传输。DMA传输 DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由DMA控制器输出读写命令,直接控制RAM与I/O接口进行DMA传输。 在DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过程中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。DMA结束 当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向I/O接口发出结束信号。当I/O接口收到结束信号后,一方面停 止I/O设备的工作,另一方面向CPU提出中断请求,使CPU从不介入的状态解脱,并执行一段检查本次DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。 STM32标准库编程 DMA初始化结构体 结构体定义 typedef struct { //指定DMAy信道的外设基址 uint32_t DMA_PeripheralBaseAddr; //指定DMAy信道的内存基地址。 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; //指定DMAy信道x的操作模式。 uint32_t DMA_Mode; //指定DMAy信道x的软件优先级 uint32_t DMA_Priority; //指定DMAy通道x是否将在内存到内存传输中使用 uint32_t DMA_M2M; } DMA_InitTypeDef; 结构体参数取值 DMA_DIR 指定这个外设是作为数据传输的目的地还是数据传输的来源 //外设作为数据传输的目的地 #define DMA_DIR_PeripheralDST ((uint32_t)0x00000010) //外设作为数据传输的来源 #define DMA_DIR_PeripheralSRC ((uint32_t)0x00000000) DMA_PeripheralInc 指定外设地址寄存器是否递增 //递增 #define DMA_PeripheralInc_Enable ((uint32_t)0x00000040) //不递增 #define DMA_PeripheralInc_Disable ((uint32_t)0x00000000) DMA_MemoryInc 指定内存地址寄存器是否递增 //递增 #define DMA_MemoryInc_Enable ((uint32_t)0x00000080) //不递增 #define DMA_MemoryInc_Disable ((uint32_t)0x00000000) DMA_PeripheralDataSize 指定外设数据宽度。 //一个字节(8位) #define DMA_PeripheralDataSize_Byte ((uint32_t)0x00000000) //半个字(16位) #define DMA_PeripheralDataSize_HalfWord ((uint32_t)0x00000100) //一个字(32位) #define DMA_PeripheralDataSize_Word ((uint32_t)0x00000200) DMA_MemoryDataSize 指定内存数据宽度。 //一个字节(8位) #define DMA_MemoryDataSize_Byte ((uint32_t)0x00000000) //半个字(16位) #define DMA_MemoryDataSize_HalfWord ((uint32_t)0x00000400) //一个字(32位) #define DMA_MemoryDataSize_Word ((uint32_t)0x00000800) DMA_Mode 指定DMAy信道x的操作模式。 //循环模式 #define DMA_Mode_Circular ((uint32_t)0x00000020) //普通模式 #define DMA_Mode_Normal ((uint32_t)0x00000000) DMA_Priority 指定DMAy信道x的软件优先级 //很高 #define DMA_Priority_VeryHigh ((uint32_t)0x00003000) //高 #define DMA_Priority_High ((uint32_t)0x00002000) //中等 #define DMA_Priority_Medium ((uint32_t)0x00001000) //低 #define DMA_Priority_Low ((uint32_t)0x00000000) DMA_M2M 指定DMAy通道x是否将在内存到内存传输中使用 //是 #define DMA_M2M_Enable ((uint32_t)0x00004000) //不是 #define DMA_M2M_Disable ((uint32_t)0x00000000) DMA串口编程的一般步骤 配置NVIC分组初始化串口 使能相应的串口以及对应的GPIO时钟配置NVIC结构体并应用配置GPIO结构体并应用配置Usart结构体并应用使能串口中断(看你需要什么中断,eg:使用USART_IT_IDLE)开启DMA接收使能串口 初始化DMA 使能相应的DMA时钟配置DMA结构体并应用使能DMA 编写串口中断函数 示例代码

根据以上步骤书写代码,因为分文件书写这样看着会有点麻烦,所以我就将示例写在一个文件中,望各位大佬见谅

说明: USART1与蓝牙进行连接,用于输入数据 USART2与PC进行连接,用于显示输入的数据

#include #include #include //缓冲区的大小 #define Buff_Size 1024 //此次接收结束的标志 volatile char rec_end_flag; //接收二级缓存中的数量 volatile unsigned short count; //一级缓存,即DMA直接搬运数据的目的地 volatile char usart1_recv_buff[Buff_Size]; /*二级缓存,即某次传输结束后将一级缓存的数据拷贝到此, 方便继续开启DMA使用一级缓存接收数据而数据不会被覆盖*/ volatile char recv_buff[Buff_Size]; //设置NVIC的优先级分组 void NVIC_config(void) { NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); } //初始化串口以及GPIO void usart1_init(void) { //用于GPIO初始化的结构体 GPIO_InitTypeDef GPIO_InitStructure; //用于串口初始化的结构体 USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStruct; //1. 初始化结构体 NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 2; //应用NVIC结构体 NVIC_Init(&NVIC_InitStruct); //2. 使能相应的串口以及对应的GPIO时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); //3. 配置GPIO结构体并应用 //初始化Usart1的Txd脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化Usart1的Rxd脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //应用GPIO结构体 GPIO_Init(GPIOA, &GPIO_InitStructure); //4. 配置Usart结构体并应用 //初始化Usart1结构体 USART_InitStructure.USART_BaudRate = 9600; 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_Tx | USART_Mode_Rx; USART_Init(USART1, &USART_InitStructure); //5. 使能串口中断(看你需要什么中断,eg:使用USART_IT_IDLE) USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); //6. 开启DMA接收 USART_DMACmd(USART1, USART_DMAReq_Rx, ENABLE); //7. 使能串口 USART_Cmd(USART1, ENABLE); } //使用Debug的显示 void usart2_init(void) { //用于GPIO初始化的结构体 GPIO_InitTypeDef GPIO_InitStructure; //用于串口初始化的结构体 USART_InitTypeDef USART_InitStructure; //开启该串口以及其对应GPIO的时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //初始化Usart2的Txd脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化Usart2的Rxd脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化Usart2结构体 USART_InitStructure.USART_BaudRate = 9600; 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_Tx | USART_Mode_Rx; USART_Init(USART2, &USART_InitStructure); //使能Usart1 USART_Cmd(USART2, ENABLE); } //初始化DMA void DMA1_init(void) { DMA_InitTypeDef DMA_InitStructure; //1.使能DMA1时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //将DMA的通道5的寄存器重设为缺省值 DMA_DeInit(DMA1_Channel5); //2. 配置DMA结构体并应用 //设置DMA源地址 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & (USART1->DR); //内存地址基地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)usart1_recv_buff; //数据传输方向,从外设读取发送到内存 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = 1024; //DMA通道的DMA缓存的大小 //外设地址寄存器不递增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //内存地址寄存器递增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //外设数据宽度为8位 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //内存数据宽度为8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //工作模式为正常模式 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA通道 x拥有中等优先级 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //此传输不是内存到内存传输 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //根据DMA_InitStruct中指定的参数初始化DMAy通道x。 DMA_Init(DMA1_Channel5, &DMA_InitStructure); //3. 使能DMA DMA_Cmd(DMA1_Channel5, ENABLE); } //串口1中断函数 void USART1_IRQHandler(void) { //判断是否为空闲中断 if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET) { //数据接收完毕标志置1 rec_end_flag = 1; //关闭DMA,准备重新配置 DMA_Cmd(DMA1_Channel5, DISABLE); //clear DMA1 Channel5 global interrupt. DMA_ClearITPendingBit(DMA1_IT_GL5); //计算接收数据长度 count = 1024 - DMA_GetCurrDataCounter(DMA1_Channel5); memcpy((void *)recv_buff, (void *)usart1_recv_buff, count); //重新配置 DMA_SetCurrDataCounter(DMA1_Channel5, 1024); DMA_Cmd(DMA1_Channel5, ENABLE); //清除IDLE标志位 USART1->SR; USART1->DR; } } //串口发送一个字符(调试使用) void usart_send_ch(USART_TypeDef *USARTx, char ch) { while (RESET == USART_GetFlagStatus(USARTx, USART_FLAG_TC)) ; USART_SendData(USARTx, ch); } //串口发送字符串(调试使用) void usart_send_str(USART_TypeDef *USARTx, char *str, int length) { int i; for (i = 0; i '\r', '\n'}; for (i = 0; i // 1. 配置NVIC分组 NVIC_config(); // 2. 初始化串口以及GPIO usart1_init(); //使用Debug的显示 usart2_init(); // 3. 初始化DMA DMA1_init(); // 4. 编写串口中断函数 //见USART1_IRQHandler while (1) { //如此次传输完成 if (rec_end_flag == 1) { //将二级缓存的数据发送到串口2进行显示输出 usart_send_str(USART2, (char *)recv_buff, count); //处理完数据后将标志置0等待下次传输结束 rec_end_flag = 0; } } } 结果展示 手机端发送

在这里插入图片描述发送了36个字符

PC端接收

在这里插入图片描述36个字符加\r\n刚好38个字符,成功 接着又发送了长一点的数据,也没有任何问题 在这里插入图片描述

总结

使用DMA的好处就是可以不用占用CPU的资源,另外CPU进入串口的空闲中断(USART_IT_IDLE)会比串口的接收中断(USART_IT_RXNE)的次数要少。而且使用空闲中断完美的解决了接收任意长度的数据,如若大佬们认为以上代码有任何Bug或错请在评论指出,我也会不断的完善。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3