STM32 串口采用DMA方式收发

摘要:
使用上述方法,您可以轻松发货,而无需直接处理快递。DMA处理方法与上述示例相同。如下图所示:二。STM32DMA配置中STM32如何实现DMA?让我们首先了解STM32的DMA配置。

FROM:https://blog.csdn.net/gdjason/article/details/51019219


什么是DMA —- Directional Memory Access, 直接存储器存取用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作

我们通过以下几方面学习串口DMA: 
一、如何理解DMA 
对于DMA,打个比方就很好理解: 
角色预设: 淘宝店主 —- STM32 MCU 
快递员 —- 外设(如UART,SPI) 
发货室 —- DMA 
1、首先你是一个淘宝店主,如果每次发货收货都要跟快递沟通交涉会很浪费时间和精力。 
2、然后你就自己建了一个发货室,发货室里有好多个货柜箱子,每个箱子上都写着快递名字(如果申通快递,顺丰快递等)。 
3、每次发什么快递,你就找到对应的货柜箱子,把货物放进去即可,然后跟快递通知一声。 
4、快递取走快件。 
5、如果是收货,快递直接把快件放到对应的柜子,然后通知你一下。 
6、你过来提取货物。

通过上面的方式,你可以不需要直接跟快递打交道,就可以轻松发货成功,DMA处理方式跟上面例子是一样的。 
如果下图: 
这里写图片描述

二、STM32 DMA 配置 
那么DMA在STM32上是具体怎么实现的呢? 我们先了解一下STM32关于DMA的相关配置。 
1、两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道) 
ps:对应我们例子,就是有两个大的发货室,一个有7个货柜,另个有5个货柜。

2、在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推) 
ps: 店主可以跟每个快递公司签订协议,可以在货柜前贴上加急(很高),很急(高),急(中),一般(低), 如果同时有几个快递员过来取货,优先根据上面的优先级先取件。

3、独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。 
ps: 指的是货件大小

4、支持循环的缓冲器管理(会把原来的数据覆盖)

5、每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求。 
ps: 送快递出现的异常情况(送到了一半,送完,快递出错)

解释到这里,不知道大家能不能理解呢。后面是具体的配置。 
1、DMA 对应通道如下图 
DMA1: 
这里写图片描述

DMA2: 
这里写图片描述

2、DMA配置 
1)数据传输的目的地和来源 
这里写图片描述 
对应我的例子,就是送快递还是取快递。

2)定义DMA通道的DMA缓存的大小 
ps: 即货柜大小,能存多少个快件

3)外设地址寄存器递增与否 
这里写图片描述

4)内存地址寄存器递增与否 
这里写图片描述

5)设定了外设数据宽度 
这里写图片描述

6)设定了内存数据宽度 
这里写图片描述

7)设置了DMA的工作模式 
这里写图片描述

8)DMA通道的软件优先级 
这里写图片描述

9)使能或关闭DMA通道的内存到内存传输 
这里写图片描述

三、 编程 
串口用DMA方式发送和接收,分以下几步: 
1)串口初始化 
2)DMA初始化 
3)发送数据 
4)接收数据

我们按部就班: 
1) 串口初始化 — 使用串口一

 1 #define  DMASIZE 1024
 2  
 3 // 配置串口一的发送和接收的GPIO口功能,以及中断
 4 static void _uart1_gpio_init(void)
 5 {
 6   NVIC_InitTypeDef NVIC_InitStructure;
 7   GPIO_InitTypeDef GPIO_InitStructure;
 8  
 9   RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA   |
10                      RCC_APB2Periph_USART1  |
11                          RCC_APB2Periph_AFIO, ENABLE) ;
12  
13   GPIOA->CRH&=0XFFFFF00F; 
14   GPIOA->CRH|=0X000008B0;//IO状态设置 10pin_上拉输入  9pin_推挽输出
15  
16   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
17     /* Configure USART1 Rx as input floating */
18   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
19   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
20   GPIO_Init(GPIOA, &GPIO_InitStructure);
21  
22   /* Configure USART1 Tx as alternate function push-pull */
23   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
24   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
25   GPIO_Init(GPIOA, &GPIO_InitStructure);
26  
27  
28   /* Enable the USART1 Interrupt */
29   NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQChannel;
30   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
31   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
32   NVIC_Init(&NVIC_InitStructure);
33  
34   USART_ClearFlag(USART1, USART_FLAG_TC); /* 清发送外城标志,Transmission Complete flag */
35  
36   USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);// 采用空闲中断,目的是在产生空闲中断时,说明接收或者发送已经结束,此时可以读取DMA中的数据了。
37   //USART_ITConfig(USART1, USART_IT_TC, ENABLE);
38   //USART_ITConfig(USART1, USART_IT_FE, ENABLE);
39 }
40 // 设置对应串口的波特率
41 static void _uart_setbaudrate(USART_TypeDef* USARTx,u32 value)
42 {
43   USART_InitTypeDef USART_InitStructure;
44   USART_InitStructure.USART_BaudRate =value;
45   USART_InitStructure.USART_WordLength = USART_WordLength_8b;
46   USART_InitStructure.USART_StopBits = USART_StopBits_1;
47   USART_InitStructure.USART_Parity = USART_Parity_No;
48   USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
49   USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
50   USART_Init(USARTx, &USART_InitStructure);
51   USART_Cmd(USARTx, ENABLE);
52 }

2)初始化DMA

 1 u8 sendbuf[1024];
 2 u8 receivebuf[1024];
 3 static void _uart1_dma_configuration()
 4 {
 5   DMA_InitTypeDef DMA_InitStructure;
 6  
 7   /* DMA1 Channel6 (triggered by USART1 Rx event) Config */
 8   RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 ,
 9                         ENABLE);
10  
11   /* DMA1 Channel5 (triggered by USART1 Rx event) Config */
12   DMA_DeInit(DMA1_Channel5);
13   DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;// 初始化外设地址,相当于“哪家快递”  
14   DMA_InitStructure.DMA_MemoryBaseAddr =(u32)receivebuf;// 内存地址,相当于几号柜
15   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//外设作为数据来源,即为收快递
16   DMA_InitStructure.DMA_BufferSize = DMASIZE ;// 缓存容量,即柜子大小
17   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增,即柜子对应的快递不变
18   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 内存递增
19   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //外设字节宽度,即快递运输快件大小度量(按重量算,还是按体积算) 
20   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存字节宽度,即店主封装快递的度量(按重量,还是按体质进行封装)
21   DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 正常模式,即满了就不在接收了,而不是循环存储
22   DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;// 优先级很高,对应快递就是加急
23   DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 内存与外设通信,而非内存到内存 
24   DMA_Init(DMA1_Channel5, &DMA_InitStructure);// 把参数初始化,即拟好与快递公司的协议
25  
26   DMA_Cmd(DMA1_Channel5, ENABLE);// 启动DMA,即与快递公司签订合同,正式生效
27  
28   /* DMA1 Channel4 (triggered by USART1 Tx event) Config */
29   DMA_DeInit(DMA1_Channel4);
30   DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;  // 外设地址,串口1, 即发件的快递
31   DMA_InitStructure.DMA_MemoryBaseAddr =(u32)sendbuf;// 发送内存地址
32   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 外设为传送数据目的地,即发送数据,即快递是发件
33   DMA_InitStructure.DMA_BufferSize = 0;  //发送长度为0,即未有快递需要发送
34   DMA_Init(DMA1_Channel4, &DMA_InitStructure);//初始化
35  
36   USART_ITConfig(USART1, USART_IT_TC, ENABLE);// 使能串口发送完成中断
37   USART_DMACmd(USART1, USART_DMAReq_Tx|USART_DMAReq_Rx, ENABLE);// 使能DMA串口发送和接受请求
38 }

3、数据发送 
流程:串口发送数据,全部数据发送完毕后,会产生一个发送中断,所以 
发送数据分为两部分, 
A、发送数据 
B、中断处理

 1 A、发送数据
 2 u16 Uart_Send_Data(void* buffer, u16 size)
 3 {
 4   if(!size) return 0;// 判断长度是否有效
 5   while (DMA_GetCurrDataCounter(DMA1_Channel4));// 检查DMA发送通道内是否还有数据
 6   if(buffer) memcpy(sendbuf, buffer,(size > 1024?1024:size));
 7   //DMA发送数据-要先关 设置发送长度 开启DMA
 8   DMA_Cmd(DMA1_Channel4, DISABLE);
 9   DMA1_Channel4->CNDTR = size;// 设置发送长度
10   DMA_Cmd(DMA1_Channel4, ENABLE);  // 启动DMA发送
11   return size;
12 }
13  
14 B、中断处理
15 1)中断处理相关准备工作
16 typedef enum _UartEvent_
17 {
18    E_uart_0 = 0,// 没有事件
19    E_uart_tc=0x40,                  //发送完成
20    E_uart_idle=0x80,               //接收完成
21 }UartEvent;
22 u16 receivelen = 0;// 声明接收数据长度
23 UartEvent event;//申明一个事件参数
24  
25 //清除DMA 缓存,并终止DMA
26 void Uart_Dma_Clr(void)
27 {
28     DMA_Cmd(DMA1_Channel4, DISABLE);
29     DMA1_Channel4->CNDTR=0;
30     DMA_Cmd(DMA1_Channel5, DISABLE);
31     DMA1_Channel5->CNDTR=DMASIZE ;
32     DMA_Cmd(DMA1_Channel5, ENABLE);
33 }
34 // 获取一个事件,事件分为发送完成事件和接收完成事件,可以根据事件进行进行处理
35 UartEvent Uart_Get_Event(void)
36 {
37   UartEvent e;
38   if(!DMA1_Channel5->CNDTR) Uart_Dma_Clr();// 如果产生一个事件后,接收数据通道已经没有了缓存空间,进行清除DMA清空
39   return event;
40 }
41 // 清除对应的事件
42 void Uart_Clr_Event(UartEvent event_in)
43 {
44     event&=~event_in;
45 }
46  
47  
48 2) 中断处理,当所有数据发送完毕,串口1产生一个发送完成中断
49 void Uatr1_Back_IRQHandler()
50 {
51   u8 tem;
52   if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)
53   {
54     tem=USART1->SR;//先读SR,然后读DR才能清除
55     tem=USART1->DR;
56     tem=tem;
57     Uart_Set_Event(E_uart_idle);
58     receivelen =DMASIZE - DMA1_Channel5->CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度
59     USART_ClearITPendingBit(USART1, USART_IT_IDLE);
60   } 
61  
62   **if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记**
63   {
64     USART_ClearITPendingBit(USART1, USART_IT_TC);   // 清除完成标记
65     DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA
66     DMA1_Channel4->CNDTR=0;          // 清除数据长度
67     Uart_Set_Event(E_uart_tc);     //设置发送完成事件
68   } 
69 }

4、接收数据 
这里写图片描述 
根据上图描述,流程如下: 
1、串口接收到数据 
2、DMA自动取走数据 
3、DMA把数据存到内存receive[1024]中 
这里写图片描述 
4、串口接收完毕后会产生一个空闲中断

根据上面流程,我们接收数据需要做到两步: 
1)串口产生一个空闲中断后,设置一个接收完成事件 
中断处理:

 1 void Uatr1_Back_IRQHandler()
 2 {
 3   u8 tem;
 4   **if(USART_GetITStatus(USART1,USART_IT_IDLE)!= RESET)**
 5   {
 6     tem=USART1->SR;//先读SR,然后读DR才能清除
 7     tem=USART1->DR;// 清除DR 
 8     tem=tem; // 防止编译器警告
 9     Uart_Set_Event(E_uart_idle);// 设置接收完成(空闲)事件
10     receivelen =DMASIZE - DMA1_Channel5->CNDTR;// 总的buf长度减去剩余buf长度,得到接收到数据的长度
11     USART_ClearITPendingBit(USART1, USART_IT_IDLE); // 清除空闲中断
12   } 
13  
14 if(USART_GetITStatus(USART1,USART_IT_TC)!= RESET) // 全部数据发送完成,产生该标记  
15 {
16     USART_ClearITPendingBit(USART1, USART_IT_TC);   // 清除完成标记
17     DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA
18     DMA1_Channel4->CNDTR=0;          // 清除数据长度
19     Uart_Set_Event(E_uart_tc);     //设置发送完成事件
20   } 
21 }

2)接收数据函数检测事件,如果发现是接收完成事件,取走数据,并且做相关清除操作

 1 u8 Uart_Receive_Data(u8*recbuf u16 *revLen)
 2 {
 3     u8 *str;
 4     if( event & E_uart_idle) // 是否产生空闲中断
 5     {
 6         str = Uart_Get_Data(revLen);    
 7         memcpy(recbuf,receivebuf,*revLen);
 8         Uart_Clr_Event(E_uart_idle);
 9         Uart_Dma_Clr();
10         return TRUE;
11     }
12     else
13     {
14         revLen = 0;
15         return FALSE;
16     }
17 }

好了,到此DMA已经讲完了,有点长!!! 
小结: 
1、DMA其实就是个自动缓存器,数据来了,缓存到指定位置。发送数据则把缓存数据发送出去。 
2、串口空闲中断,实测在接收完成数据后,空闲闲置时产生的,而发送数据不会产生该中断。 
3、串口发送完成中断,实测在全部数据发送成功后,才会产生中断。

上面这些知识是微小见识,欢迎拍砖!

免责声明:文章转载自《STM32 串口采用DMA方式收发》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇操作系统/应用程序、操作中的“并发”、线程和进程,python中线程和进程(GIL锁),python线程编写+锁Windows10 +Ubuntu 18.04双系统安装详细教程下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

【串口通信】labview实现串口通信

labview实现串口通信 学习labview有一段时间了,发现了其中功能的强大和编程的简洁性,高效性 现在初步实现了串口的通信,把其中的体会和实现过程写出来,权当抛砖引玉 其中的串口采用的是Rs-232,D型口,共九跟针,其中能用的就是三跟针脚,2,3,5,一个是发送一个是接受,一个是接地,如果做一个串口线延长线的话,只需连接这三根针脚即可,就是2...

【嵌入式开发】 Linux Kernel 下载 配置 编译 安装 及 驱动简介

作者 : 韩曙亮 转载请出名出处 : http://blog.csdn.net/shulianghan/article/details/38636827 一. Linux 内核简介  1. 内核功能简介 (1) 操作系统 和 内核 简介 操作系统 :  -- 功能 : 完成基本功能 和 系统管理; -- 组成 : 内核(kernel), 设备...

Linux12-内存管理

Linux内核第12章 内核不能像用户空间那样奢侈地使用内存,内核与用户空间不同,它不具备这种能力,它不支持简单便捷的内存分配方式。比如,内核一般不能睡眠,此外处理内存分配错误对内核来说也很困难。正是因为这些限制和内存分配机制不能太复杂,所以在内核中获取内存要比在用户空间复杂得多。 12.1 页 内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单...

stm32的fsmc

   液晶估计也就只能白话这么一点了。      Fsmc是stm32一种新型的存储器拓展技术,可根据系统的应用需要,方便的进行不同类型大容量静态存储器的拓展。      Fsmc芯片分为如下几个部分:     Fsmc能将AHB上的传输信号转换到适当的外部设备协议,从而完成相应的内存映射,fsmc管理的是stm32中60000000h到9fffffffh...

收录 Uboot 详解

--------------------------------------------------------------------------------------------------------  我们知道,bootloader是系统上电后最初加载运行的代码。它提供了处理器上电复位后最开始需要执行的初始化代码。     在PC机上引导程序一般...

你真的了解串口吗(示波器串口波形分析)

串口是最常用的外设了,串口基本都是单片机的标配。串口通信只需要3条线组成,分别为RX、TX、GND。下面将重点分析串口数据帧组成。 一、    串口通信帧  串口通信帧数据如此,每帧由空闲位、起始位、数据位、校验位、停止位组成 传输的数据是低位在前高位在后 l  空闲: 串口TX或RX数据线上没有传输任何数据时,则该线处于为空闲状态。空闲是TX和RX都是...