串口DMA方式发送接收
串口DMA方式收发
笔者使用的是STM32F407VET6,共包含6路串口,页尾处程序已将全部串口的DMA收发配置完成,本文仅以串口1为例进行讲解。(查看代码可直接跳至第二节或页尾处下载)
1 STM32F4 DMA 简介
DMA,全称为:Direct Memory Access,即直接存储器访问。DMA 传输方式无需 CPU 直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路,能使 CPU 的效率大为提高。
STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。每个数据流总共可以有多达 8个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。
它可以处理一下事务:
- 外设到储存器的传输
- 储存器到外设的传输
- 储存器到储存器的传输
注意:DMA1 控制器 AHB 外设端口与 DMA2 控制器的情况不同,不连接到总线矩阵,因此,仅 DMA2 数据流能够执行存储器到
存储器的传输。
其中,数据流的多通道选择,是通过 DMA_SxCR 寄存器控制的,如图1所示:
图1 通道选择
上图可以看出,DMA_SxCR 控制数据流到底使用哪一个通道,每个数据流有 8 个通道可供选择,但每次只能选择其中一个通道进行 DMA 传输,DMA2 的各数据流通道映射表,如表 1 所示
表1 DMA2数据流映射表
上表就列出了 DMA2 所有可能的选择情况,来总共 64 种组合,比如本章我们要实现串口1的 DMA 发送,即USART1_TX,就必须选择 DMA2 的数据流 7,通道 4,来进行 DMA 传输。这里注意一下,有的外设(比如 USART1_RX)可能有多个通道可以选择,随意选择一个就可以。
重要寄存器简介
(1) DMA 中断状态寄存器
该寄存器总共有 2 个:DMA_LISR 和 DMA_HISR,每个寄存器管理 4 数据流(总共 8 个),DMA_LISR 寄存器用于管理数据流 0~3,而 DMA_HISR 用于管理数据流 4~7。如果开启了 DMA_LISR 中这些位对应的中断,则在达到条件后就会跳到中断服务函数里面去,即使没开启,也可以通过查询这些位来获得当前 DMA 传输的状态。这里常用的是 TCIFx位,即数据流 x 的 DMA 传输完成与否标志。
注意:此寄存器为只读寄存器,所以在这些位被置位之后,只能通过【中断标志清除寄存器】来清除。
(2)DMA 中断标志清除寄存器
该寄存器同样有 2 个:DMA_LIFCR 和 DMA_HIFCR,同样是每个寄存器控制 4 个数据流。该寄存器为只写寄存器,其各位就是用来清除 【中断状态寄存器】的对应位的,通过写 1 清除。
(3) DMA 数据流 x 配置寄存器(DMA_SxCR)
该寄存器控制着 DMA 的很多相关信息,包括数据宽度、外设及存储器的宽度、优先级、增量模式、传输方向、中断允许、使能等都是通过该寄存器来设置的。所以 DMA_ SxCR 是 DMA 传输的核心控制寄存器。
(4)DMA 数据流 x 数据项数寄存器(DMA_SxNDTR)
这个寄存器控制 DMA 数据流 x 的每次传输所要传输的数据量。其设置范围为 0~65535。并且该寄存器的值会随着传输的进行而减少,当该寄存器的值为 0 的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存器的值来知道当前DMA 传输的进度。
注意:这里是数据项数目,而不是指的字节数。比如设置数据位宽为 16 位,那么传输一次(一个项)就是 2 个字节
(5)DMA 数据流 x 的外设地址寄存器(DMA_SxPAR)
该寄存器用来存储 STM32F4 外设的地址,比如使用串口 1,那么该寄存器必须写入 0x40011004(其实就是&USART1_DR)。
(6) DMA 数据流 x 的存储器地址寄存器
由于 STM32F4 的 DMA 支持双缓存,所以存储器地址寄存器有两个:DMA_SxM0AR 和 DMA_SxM1AR,其中 DMA_SxM1AR 仅在双缓冲模式下,才有效。比如使用 USART1_TX_BUF[USART_LEN] 数组来做存储器,那么在DMA_SxM0AR 中写入 &USART1_TX_BUF 就可以了。
2 收发配置
2.1串口配置(使能DMA收发)
重点:使能串口1的接收、发送和串口1的DMA接收、发送并使能串口1的空闲中断
/*------------------------------------------------
* 函数名:void Init_USART1(u32 pclk2,u32 bound)
* 功 能:初始化IO 串口1
* 参 数:pclk2: PCLK2时钟频率(Mhz)bound: 波特率
* 返回值: 无
------------------------------------------------*/
void Init_USART1(u32 pclk2,u32 bound)
{ float temp;u16 mantissa;u16 fraction; temp=(float)(pclk2*1000000)/(bound*16);//得到USARTDIV@OVER8=0mantissa=temp; //得到整数部分fraction=(temp-mantissa)*16; //得到小数部分@OVER8=0 mantissa<<=4;mantissa+=fraction; RCC->AHB1ENR|=1<<0; //使能PORTA口时钟 RCC->APB2ENR|=1<<4; //使能串口1时钟 GPIO_Set(GPIOA,PIN9|PIN10,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PU);//PA9,PA10,复用功能,上拉输出GPIO_AF_Set(GPIOA,9,7); //PA9,AF7GPIO_AF_Set(GPIOA,10,7);//PA10,AF7 //波特率设置USART1->BRR=mantissa; //波特率设置 USART1->CR1&=~(1<<15); //设置OVER8=0 USART1->CR1|=1<<3; //串口发送使能 USART1->CR3|=1<<7; //使能串口1的DMA发送#if EN_USART1_RX //如果使能了接收 USART1->CR1|=1<<2; //串口接收使能USART1->CR3|=1<<6; //使能串口1的DMA接收 USART1->CR1|=1<<4; //使能空闲中断 MY_NVIC_Init(3,3,USART1_IRQn,2);//组2,最低优先级
#endifUSART1->CR1|=1<<13; //串口使能
}
2.2两个变量
发送和接收的数据都将以如下两个变量为指定储存器。
#define USART_LEN 50 //定义最大接收字节数 50
u8 USART1_TX_BUF[USART_LEN];
u8 USART1_RX_BUF[USART_LEN];
2.3 DMA配置
(1)使能DMA2时钟,并等待数据流可配置 。
(2)设置外设地址
(3)设置储存器地址
(4)设置传输数据量
(5)设置数据流7的配置信息
(6)开启数据流7的传输完成中断
/*------------------------------------------------
* 函数名:void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u8 chx,u32 par,u32 mar,u16 ndtr,u8 dir)
* 功 能:配置DMA
* 参 数:DMA_Streamx: DMA数据流(DMA1_Stream0~7/DMA2_Stream0~7)chx: DMA通道选择(范围:0~7)par: 外设地址mar: 存储器地址ndtr: 数据传输量dir: 数据传输方向(DMA_DIR_PeripheralToMemory / DMA_DIR_MemoryToPeripheral / DMA_DIR_MemoryToMemory)
* 返回值: 无
------------------------------------------------*/
void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u8 chx,u32 par,u32 mar,u16 ndtr,u8 dir)
{ DMA_TypeDef *DMAx;u8 streamx;if((u32)DMA_Streamx>(u32)DMA2)//得到当前stream是属于DMA2还是DMA1{DMAx=DMA2;RCC->AHB1ENR|=1<<22;//DMA2时钟使能 }else {DMAx=DMA1; RCC->AHB1ENR|=1<<21;//DMA1时钟使能 }while(DMA_Streamx->CR&0X01);//等待DMA可配置 streamx=(((u32)DMA_Streamx-(u32)DMAx)-0X10)/0X18; //得到stream通道号if(streamx>=6)DMAx->HIFCR|=0X3D<<(6*(streamx-6)+16); //清空之前该stream上的所有中断标志else if(streamx>=4)DMAx->HIFCR|=0X3D<<6*(streamx-4); //清空之前该stream上的所有中断标志else if(streamx>=2)DMAx->LIFCR|=0X3D<<(6*(streamx-2)+16);//清空之前该stream上的所有中断标志else DMAx->LIFCR|=0X3D<<6*streamx; //清空之前该stream上的所有中断标志DMA_Streamx->PAR=par; //DMA外设地址DMA_Streamx->M0AR=mar; //DMA存储器0地址DMA_Streamx->NDTR=ndtr; //n个数据项DMA_Streamx->CR=0; //先全部复位CR寄存器值 switch(dir){case DMA_DIR_PeripheralToMemory: //外设到存储器模式DMA_Streamx->CR&=~(1<<6);DMA_Streamx->CR&=~(1<<7);break;case DMA_DIR_MemoryToPeripheral: DMA_Streamx->CR|=1<<6;DMA_Streamx->CR&=~(1<<7);break;case DMA_DIR_MemoryToMemory: DMA_Streamx->CR&=~(1<<6);DMA_Streamx->CR|=1<<7;break;default:break;}DMA_Streamx->CR|=0<<8; //非循环模式(即使用普通模式)DMA_Streamx->CR|=0<<9; //外设非增量模式DMA_Streamx->CR|=1<<10; //存储器增量模式DMA_Streamx->CR|=0<<11; //外设数据长度:8位DMA_Streamx->CR|=0<<13; //存储器数据长度:8位DMA_Streamx->CR|=1<<16; //中等优先级DMA_Streamx->CR|=0<<21; //外设突发单次传输DMA_Streamx->CR|=0<<23; //存储器突发单次传输DMA_Streamx->CR|=(u32)chx<<25;//通道选择//DMA_Streamx->FCR=0X21; //FIFO控制寄存器DMA2_Stream7->CR|=1<<4; //使能传输完成中断MY_NVIC_Init(2,1,DMA2_Stream7_IRQn,2);
}
2.4 设置MDA状态标志
注意:如果连续运行两个发送函数,如下,则可能在第一个还未发送完成时就会直接执行第二次发送。
myDMAprintf(USART1,"usart = %d\tch = %f\r\n",1,1.567);
myDMAprintf(USART1,"usart = %d\tch = %f\r\n",1,1.567);
其运行效果如图1所示,第一次仅发送了"us"即被第二次发送覆盖了。
![](/assets/blank.gif)
故需设置相应的标志位,对每次发送的状态进行标记,若正在进行传输,则等待,实现如下:
typedef enum
{BUSY,IDLE
}DMA_Flag;volatile DMA_Flag DMA2_Stream7_Flag = IDLE; //USART1
2.5 DMA中断函数
每次传输完成(串口发送完成)后,都会触发一次中断,此时只需在中断函数中清除相应标志位并对发送状态进行标记即可。
//对应USART1发送
void DMA2_Stream7_IRQHandler(void)
{if((DMA2->HISR&(1<<27))){DMA2->HIFCR|=1<<27;DMA2_Stream7_Flag = IDLE;}
}
2.6 DMA初始化
查询手册可知,串口1发送为DMA2的数据流7,通道4,并为内存到外设模式,而串口1接收为DMA2的数据流5,通道4,并为外设到内存模式。
注意:此处需提前开启一次DMA接收,否则第一次接收会产生错误数据。
//USART1发送 --- DMA2,数据流7,CH4---USART1_TXD 外设为串口1,存储器为USART1_TX_BUF,长度为:USART_LEN
MYDMA_Config(DMA2_Stream7,4,(u32)&USART1->DR,(u32)USART1_TX_BUF,USART_LEN,DMA_DIR_MemoryToPeripheral);
//USART1接收 --- DMA2,数据流5,CH4---USART1_RXD 外设为串口1,存储器为USART1_RX_BUF,长度为:USART_LEN
MYDMA_Config(DMA2_Stream5,4,(u32)&USART1->DR,(u32)USART1_RX_BUF,USART_LEN,DMA_DIR_PeripheralToMemory);
MYDMA_Enable(DMA2_Stream5,(u32)USART1_RX_BUF,USART_LEN);//开始一次DMA传输!
2.6 开启一次串口DMA传输
配置DMA数据流、内存地址及传输量。
void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx, u32 mar, u16 ndtr)
{DMA_Streamx->CR&=~(1<<0); //关闭DMA传输 while(DMA_Streamx->CR&0X1); //确保DMA可以被设置 DMA_Streamx->M0AR=mar; //DMA存储器地址DMA_Streamx->NDTR=ndtr; //DMA传输数据量 DMA_Streamx->CR|=1<<0; //开启DMA传输
}
2.7 格式化发送
该部分已封装为类似printf()的发送函数,大致为3个部分:
(1)格式化数据为字符串;
(2)判断发送状态,若“忙”,则等待。
(3)设置好储存器地址,使能发送并设置发送状态;
/*------------------------------------------------
* 函数名:Status myDMAprintf(USART_TypeDef *USARTx, const char *format, ...)
* 功 能:仿 printf 函数
* 参 数:*USARTx: 串口号*pString: 打印内容... : 变量
* 返回值: 状态
------------------------------------------------*/
Status myDMAprintf(USART_TypeDef *USARTx, const char *format, ...)
{va_list args; u16 len;if(format == NULL)return 1;va_start(args, format);if(USARTx == USART1){len = vsnprintf((char *)USART1_TX_BUF, USART_LEN, format, args);while(DMA2_Stream7_Flag != IDLE);MYDMA_Enable(DMA2_Stream7,(u32)USART1_TX_BUF,len);DMA2_Stream7_Flag = BUSY;}else if(USARTx == USART2){len = vsnprintf((char *)USART2_TX_BUF, USART_LEN, format, args);while(DMA1_Stream6_Flag != IDLE);MYDMA_Enable(DMA1_Stream6,(u32)USART2_TX_BUF,len);DMA1_Stream6_Flag = BUSY;} else if(USARTx == USART3){len = vsnprintf((char *)USART3_TX_BUF, USART_LEN, format, args);while(DMA1_Stream3_Flag != IDLE);MYDMA_Enable(DMA1_Stream3,(u32)USART3_TX_BUF,len);DMA1_Stream3_Flag = BUSY;} else if(USARTx == UART4){len = vsnprintf((char *)UART4_TX_BUF, USART_LEN, format, args);while(DMA1_Stream4_Flag != IDLE);MYDMA_Enable(DMA1_Stream4,(u32)UART4_TX_BUF,len);DMA1_Stream4_Flag = BUSY;} else if(USARTx == UART5){len = vsnprintf((char *)UART5_TX_BUF, USART_LEN, format, args);while(DMA1_Stream7_Flag != IDLE);MYDMA_Enable(DMA1_Stream7,(u32)UART5_TX_BUF,len);DMA1_Stream7_Flag = BUSY;} else if(USARTx == USART6){len = vsnprintf((char *)USART6_TX_BUF, USART_LEN, format, args);while(DMA2_Stream6_Flag != IDLE);MYDMA_Enable(DMA2_Stream6,(u32)USART6_TX_BUF,len);DMA2_Stream6_Flag = BUSY;}elsereturn 2;va_end(args);return 0;
}
2.8串口DMA接收
当串口进入空闲状态时即开启一次DMA接收,下次数据到来时DMA会自动搬运数据至指定的储存器(此处为USART1_TX_BUF),搬运完成后会再次触发空闲中断,此时清除空闲中断标志位、DMA传输完成标志位和传输错误标志位,并清除接收内存,开启下一次接收。
void USART1_IRQHandler(void)
{u8 temp;u16 len;if(USART1->SR&(1<<4))//检测到线路空闲{
//软件序列清除IDLE标志位temp = USART1->SR;temp = USART1->DR;DMA2_Stream5->CR &=~(1<<0); //关闭DMA传输,准备重新配置DMA2->HIFCR|=1<<11; //清除DMA2_Steam5传输完成标志DMA2->HIFCR|=1<<9; //清除DMA2_Steam5传输错误标志len = USART_LEN - (uint16_t)(DMA2_Stream5->NDTR);myDMAprintf(USART1,"len = %d,data: %s",len,USART1_RX_BUF);}mymemset(USART1_RX_BUF,0,(u32)len);MYDMA_Enable(DMA2_Stream5,(u32)USART1_RX_BUF,USART_LEN);//开始一次DMA传输!
}
至此,串口1的发送和接收已全部配置完成,其他5个串口的配置类似。
程序链接
完整的6路串口DMA发送&接收程序:串口DMA发送&接收(v2.1)
参考文章
STM32—无需中断来实现使用DMA接收串口数据
串口1配合DMA接收不定长数据(空闲中断+DMA接收)
STM32 串口采用DMA方式收发
串口DMA方式发送接收相关推荐
- 嵌入式作业STM32采用串口DMA方式发送数据
目录 前言 要求 一.DMA的基本介绍 DMA的基本定义 DMA的主要特征 STM32F411x系列芯片DMA控制器 二.通过CubeMX配置项目 1.创建项目 2.选择芯片STM32F103C8T6 ...
- STM32F051串口DMA方式发送中的几点注意
我的还没调试出来,先写写要注意的部分: 1.地址问题 STM32F051采用的是Cortex M0内核.在寄存器上与现在使用较多的Cortex M3内核的芯片还有点区别.在这里用到的应该是串口的数据寄 ...
- STM32使用串口DMA方式发送数据
1.cubemx设置 1.1基本设置 1.2 打开DMA和中断 2.代码实现 HAL_UART_Transmit_DMA(&huart1,(uint8_t*)str,strlen(str)); ...
- GD32F303RET6 串口空闲中断+DMA数据发送接收+环形缓冲区方式保存数据
GD32F303RET6 DMA 通道映射关系 串口 源文件 #include "uart.h" #include "stdio.h" #include &qu ...
- 关于RS485通讯中使用STM32串口以DMA方式发送数据丢失字节的问题
1.开发平台 计算机操作系统:WIN7 64位: 开发环境:Keil MDK 5.14: MCU:STM32F407ZET6: STM32F4xx固件库:STM32F4xx_DSP_StdPeriph ...
- STM32F103C8T6核心开发板下,采用串口DMA方式向上位机连续发送数据的实例详解
STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据 文章目录 STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据 前言 一.DMA应用初步 ...
- stm32的串口DMA空闲中断接收不等长数据,stm32F4的usart2-DMA-IDLE收发
1. 串口为什么要使用DMA?好处? 提高系统实时性:stm32单片机的串口没有FIFO,使用字节中断的方式去接收,会频繁进入中断,影响系统实时性.好在stm32的串口可以级联DMA使用,在大数据量连 ...
- 单片机IO口模拟串口程序(发送+接收
单片机IO口模拟串口程序(发送+接收)[转] qcmc 发表于 - 2011-6-23 0:42:00 前一阵一直在做单片机的程序,由于串口不够,需要用IO口来模拟出一个串口.经过若干曲折并参考了一些 ...
- ANO V7上位机协议程序(新版加入DMA形式发送接收)
ANO V7上位机协议程序(新版加入DMA形式发送接收) 文章目录 ANO V7上位机协议程序(新版加入DMA形式发送接收) 前言 一.程序说明 1,移植说明 2,使用说明 a,发送数据 b,接收参数 ...
- STM32采用串口DMA方式实现发送数据
文章目录 一.实验工具 二.DMA介绍 三.配置工程 1.新建工程 2.引脚配置 3.设置RCC 4.设置串口 5.DMA设置 6.时钟源设置 7.项目文件设置 8.创建代码 四.Keil中修改代码 ...
最新文章
- django language_Django基础学习-创建第一个Django项目
- Mac 解压Android NDK.bin文件
- No plugin found for prefix ‘scala‘ pom.xml
- Spring MVC面试题
- 5-Dockerfile文件
- hutool的定时任务不支持依赖注入怎么办_可调度定时任务在SpringBoot中的实践
- 第八章 JQuery操作DOM
- DotNet Framework 小技巧
- 倪光南院士:云计算发展须和云安全同步推进
- 带桭字的名字_带芙字的名字 好名字从出生就伴随我们一生 必须雅致
- linux定时备份Mysql
- easyUI替换非16x16的图标
- 微服务esb_深入理解微服务架构:银弹or焦油坑?
- c代码实现matlab中对矩阵fftshift思路
- 一、GeoLabel:全网最好用的遥感样本标注软件
- Centos7 固定 ip
- Unity3D MineCraft 我的世界 用Unity3D制作类似MineCraft我的世界的游戏:各种树
- Datawhale组队学习周报(第044周)
- 关于不同局域网内经Internet的P2P通信技术
- 股票历史数据-股票历史数据在线查询系统
热门文章
- 关于深度优先遍历和广度优先遍历的一些深入思考
- 学计算机专业开学要买笔记本电脑吗,大一开学需要买电脑吗 大学开学电脑买什么好...
- SAS硬盘和SATA硬盘的区别与介绍
- 真惭愧--连这样的小事都没有坚持下来
- 极域教师端和学生端链接不上,出现这种问题怎么解决
- NOJ [F] 懒惰的风纪委Elaine 求一堆数能组成多少个数小于等于n
- SLAM Cartographer(1)框架与安装
- 计算机发展变化英文作文60词,生活中的变化英文作文(精选4篇)
- 电商购物评论的情感分析
- 文本数据挖掘之文本信息抽取