串口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"即被第二次发送覆盖了。

图1 运行效果

故需设置相应的标志位,对每次发送的状态进行标记,若正在进行传输,则等待,实现如下:

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方式发送接收相关推荐

  1. 嵌入式作业STM32采用串口DMA方式发送数据

    目录 前言 要求 一.DMA的基本介绍 DMA的基本定义 DMA的主要特征 STM32F411x系列芯片DMA控制器 二.通过CubeMX配置项目 1.创建项目 2.选择芯片STM32F103C8T6 ...

  2. STM32F051串口DMA方式发送中的几点注意

    我的还没调试出来,先写写要注意的部分: 1.地址问题 STM32F051采用的是Cortex M0内核.在寄存器上与现在使用较多的Cortex M3内核的芯片还有点区别.在这里用到的应该是串口的数据寄 ...

  3. STM32使用串口DMA方式发送数据

    1.cubemx设置 1.1基本设置 1.2 打开DMA和中断 2.代码实现 HAL_UART_Transmit_DMA(&huart1,(uint8_t*)str,strlen(str)); ...

  4. GD32F303RET6 串口空闲中断+DMA数据发送接收+环形缓冲区方式保存数据

    GD32F303RET6 DMA 通道映射关系 串口 源文件 #include "uart.h" #include "stdio.h" #include &qu ...

  5. 关于RS485通讯中使用STM32串口以DMA方式发送数据丢失字节的问题

    1.开发平台 计算机操作系统:WIN7 64位: 开发环境:Keil MDK 5.14: MCU:STM32F407ZET6: STM32F4xx固件库:STM32F4xx_DSP_StdPeriph ...

  6. STM32F103C8T6核心开发板下,采用串口DMA方式向上位机连续发送数据的实例详解

    STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据 文章目录 STM32采用串口DMA方式,用115200bps或更高速率向上位机连续发送数据 前言 一.DMA应用初步 ...

  7. stm32的串口DMA空闲中断接收不等长数据,stm32F4的usart2-DMA-IDLE收发

    1. 串口为什么要使用DMA?好处? 提高系统实时性:stm32单片机的串口没有FIFO,使用字节中断的方式去接收,会频繁进入中断,影响系统实时性.好在stm32的串口可以级联DMA使用,在大数据量连 ...

  8. 单片机IO口模拟串口程序(发送+接收

    单片机IO口模拟串口程序(发送+接收)[转] qcmc 发表于 - 2011-6-23 0:42:00 前一阵一直在做单片机的程序,由于串口不够,需要用IO口来模拟出一个串口.经过若干曲折并参考了一些 ...

  9. ANO V7上位机协议程序(新版加入DMA形式发送接收)

    ANO V7上位机协议程序(新版加入DMA形式发送接收) 文章目录 ANO V7上位机协议程序(新版加入DMA形式发送接收) 前言 一.程序说明 1,移植说明 2,使用说明 a,发送数据 b,接收参数 ...

  10. STM32采用串口DMA方式实现发送数据

    文章目录 一.实验工具 二.DMA介绍 三.配置工程 1.新建工程 2.引脚配置 3.设置RCC 4.设置串口 5.DMA设置 6.时钟源设置 7.项目文件设置 8.创建代码 四.Keil中修改代码 ...

最新文章

  1. django language_Django基础学习-创建第一个Django项目
  2. Mac 解压Android NDK.bin文件
  3. No plugin found for prefix ‘scala‘ pom.xml
  4. Spring MVC面试题
  5. 5-Dockerfile文件
  6. hutool的定时任务不支持依赖注入怎么办_可调度定时任务在SpringBoot中的实践
  7. 第八章 JQuery操作DOM
  8. DotNet Framework 小技巧
  9. 倪光南院士:云计算发展须和云安全同步推进
  10. 带桭字的名字_带芙字的名字 好名字从出生就伴随我们一生 必须雅致
  11. linux定时备份Mysql
  12. easyUI替换非16x16的图标
  13. 微服务esb_深入理解微服务架构:银弹or焦油坑?
  14. c代码实现matlab中对矩阵fftshift思路
  15. 一、GeoLabel:全网最好用的遥感样本标注软件
  16. Centos7 固定 ip
  17. Unity3D MineCraft 我的世界 用Unity3D制作类似MineCraft我的世界的游戏:各种树
  18. Datawhale组队学习周报(第044周)
  19. 关于不同局域网内经Internet的P2P通信技术
  20. 股票历史数据-股票历史数据在线查询系统

热门文章

  1. 关于深度优先遍历和广度优先遍历的一些深入思考
  2. 学计算机专业开学要买笔记本电脑吗,大一开学需要买电脑吗 大学开学电脑买什么好...
  3. SAS硬盘和SATA硬盘的区别与介绍
  4. 真惭愧--连这样的小事都没有坚持下来
  5. 极域教师端和学生端链接不上,出现这种问题怎么解决
  6. NOJ [F] 懒惰的风纪委Elaine 求一堆数能组成多少个数小于等于n
  7. SLAM Cartographer(1)框架与安装
  8. 计算机发展变化英文作文60词,生活中的变化英文作文(精选4篇)
  9. 电商购物评论的情感分析
  10. 文本数据挖掘之文本信息抽取