DMA和UART的深刻认识--串口接收的3种工作方式(附STM32F4代码)

2019-07-22来源: eefocus关键字:DMA  UART  串口接收  工作方式

可能会遇到的问题:


1.能实现接收但不发送 注意是否是识别函数出错


2.DMA单次传输模式要求再初始化,否者出现第二次中断不执行。使用循环模式出现的问题是要结合配置公式:



3.DMA再次初始化不完全,会出现接收一次成功,再来一次不行。第三次能接收的问题


4.串口调试连续点击的次数太快,会使的里面的发送程序出错


一.串口uart中断接收


 遇到的问题:


1、串口调试接收引脚坏掉


2.接收数据识别,使用的库函数出错


串口设置的一般步骤可以总结为如下几个步骤:

1) 串口时钟使能, GPIO 时钟使能。

2) 设置引脚复用器映射:调用 GPIO_PinAFConfig 函数。

3) GPIO 初始化设置:要设置模式为复用功能。

4) 串口参数初始化:设置波特率,字长,奇偶校验等参数。

5) 开启中断并且初始化 NVIC,使能中断(如果需要开启中断才需要这个步骤)。

6) 使能串口。

7) 编写中断处理函数:函数名格式为 USARTxIRQHandler(x 对应串口号)。


其中串口中断服务程序的解析(正点原子):


  void USART1_IRQHandler(void)                //串口1中断服务程序

if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)

{

Res =USART_ReceiveData(USART1);//(USART1->DR); //读取接收到的数据

if((USART_RX_STA&0x8000)==0)//接收未完成 相当于一个循环 一开始肯定进入这里 因为赋初值为0

{

if(USART_RX_STA&0x4000)//接收到了0x0d

{

if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始

else USART_RX_STA|=0x8000; //接收完成了 

}

else //还没收到0X0D

{

if(Res==0x0d)USART_RX_STA|=0x4000;

else

{

USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;

USART_RX_STA++;

if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   

}

}

}

}

上面的程序接收部分为正点原子编写的一段程序,具体的思路是:


     定义了接收状态寄存器USART_RX_STA总共16位,由于串口接收到数据开始产生中断,在中断函数里启动接收指令,一次读取一个字节,根据标志位收到倒数第二个字节0x0d 和最后一个字节0x0a代表数据接收完。因此在上面的中断处理函数中它做的判断是:依据读取的数据判断接收是否完成,或者接收到数据了若接收到14位了表明之前已经判断是接收到0x0d,并将该位置位。若这次没有收到数据0x0a说明接收出错。所以两个变量的判断很关键:USART_RX_STA和Res(接收的单个字节);


如果接收的字节还没有到14位 而且当前的res不是0x0d,则接收缓冲区数组递增,通过USART_RX_STA++的方式。若是累加的数目要大于已知的数据长度,则说明多接收了,肯定出问题,但为什么是减一,原因是USART_RX_STA是从0开始的,也就是累加到2时已经代表3个数据了(C语言的知识)同时将USART_RX_STA=0,重新开始接收。


但是问题是:中断接收字符串,使用Res =USART_ReceiveData(USART1);只是读取一个字节?难道是每个字节的接收都会启动一次中断?


答:这和我们启动的中断方式有关,下图为串口中断的方式:


本次的实验使用的是:USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);因此数据寄存器非空即来中断。


因此每个字节的接收都会来一次中断。因此后面我们才会要使用DMA的方式。


发现这位作者写的不错http://blog.sina.com.cn/s/blog_776077610102vgqg.html 


在主函数内的操作:


1.中断优先级分组 NVIC_Configuration();


2.调用初始化函数 uart_init(9600);  


串口中断完成,接收数据正常。


为了方便查看正确的状态,启用串口发送模式


串口发送,在上面我们已经启动了收发模式




为此发送时判断上次传输完成以后:


while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);  //等待上次传输完成。


启动发送命令:


USART_SendData(USART1,(uint8_t)USART1_TX__ins_BUF[j]);        //发送数据到串口1


一次发一个字节。


判断发送数组的长度,并开启循环发送:如下所示:


void u3_printf(char* fmt,...) 

{  

u16 i,j;

va_list ap;

va_start(ap,fmt);

vsprintf((char*)USART1_TX__ins_BUF,fmt,ap);

va_end(ap);//以上的语句可以对输入的字符串按照一定的格式存储

i=strlen((const char*)USART1_TX__ins_BUF);//此次发送数据的长度

for(j=0;j

{

  while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET);  //等待上次传输完成 

USART_SendData(USART1,(uint8_t)USART1_TX__ins_BUF[j]); //发送数据到串口1

}

}

发送部分完成,可以结合接收的数据发送指定的字符;在这里就会涉及到对接收字符的识别问题:1.可利用正则表达式 2.库函数 3. 指定连续字符条件判断


其中库函数这个作者写的不错:https://blog.csdn.net/u013071074/article/details/27692933 


需要注意的是:strcmp库函数实现的是两个字符串的比较,相等才为0,为此串口发送的数据为带有回车换行0x0d 和0x0a。


二、串口DMA中断接收


配置成uart+DMA的方式,那么uart的收模式不配置中断,同时配置DMA中断,固定字节接收。(即是搬运数据满了才DMA中断)


1.uart的初始化去掉NVIC的配置



2.DMA配置


  void MYDMA_Config_Rx(DMA_Stream_TypeDef*DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr) 

  (1)使能DMA的时钟 并等待数据流可配置(这里注意你要使用的DMA时钟为哪个1或2)


  (2).设置外设地址


  (3).设置存储器地址


  (4).设置传输数据量


  (5).设置DMA数据流的配置信息


  (6)使能DMA数据流,启动传输


  (7)中断配置


 注意的是根据是发送和接收选择:外设到内存 or内存到外设方向


 正常传输模式和循环模式要注意,正常传输则传输完一次后DMA结束,下次要重启DMA的配置信息才能再次使用。可以选用循环模式,但是有一个问题是当接收的数据超过指定的接收长度时会出错。


中断的选择为:


DMA_ITConfig(DMA2_Stream2,DMA_IT_TC,ENABLE);//使能DMA2流2的传输完成中断

3.DMA的中断服务函数


void DMA2_Stream2_IRQHandler(void)

{

uint16_t pro=0;

Blue_receive_Flag = 1;

if(Blue_receive_Flag == 1)      //

{

 Receive_data_process();//接收的数据判断 DMA接收 下一步通过串口

 Blue_receive_Flag = 0;

//发送应答 同上面的DMA 发送

}  

if(DMA_GetITStatus(DMA2_Stream2,DMA_IT_TCIF2)!= RESET)     //DMA传输完成标志

    {

//        DMA_Cmd(DMA2_Stream2, DISABLE);         //关闭USART1 RX DMA2 所指示的通道  

//        pro =  DMA_GetCurrDataCounter(DMA2_Stream2);        //获取DMA通道的DMA缓存的大小

        DMA_Cmd(DMA2_Stream2, ENABLE);         //使能USART1 RX DMA2 所指示的通道        

        DMA_ClearITPendingBit(DMA2_Stream2,DMA_IT_TCIF2);      //清除中断标志  

  USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串口1的DMA接收    

      MYDMA_Enable(DMA2_Stream2,USART_REC_LEN);     //开始一次DMA传输!

    }

}

4.在主函数中需要初始化uart  MYDMA_Config_Rx


uart(9600);

MYDMA_Config_Rx(DMA2_Stream2,DMA_Channel_4,(u32)&USART1->DR,(u32)USART_RX_BUF,USART_REC_LEN);

5.启动uart 和DMA 


DMA_Cmd(DMA_Streamx, ENABLE);

 USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串口1的DMA接收

6.关闭DMA才可以设置


DMA_Cmd(DMA_Streamx, DISABLE);                      //关闭DMA传输 

while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){} //确保DMA可以被设置  

DMA_SetCurrDataCounter(DMA_Streamx,ndtr);          //数据传输量  

//设置完成再次启动即可


DMA_Cmd(DMA_Streamx, ENABLE); //开启DMA传输

直至完成了串口DMA接收的配置


而且在上面中指定DMA搬送的缓存区是USART_RX_BUF,字节长度USART_REC_LEN。因此在处理函数中可以直接操作此数组USART_RX_BUF例如识别数组中含有的字符串,并作出相应的判断比如发送接收的内容或者其它指定的字符。


三、不定长度数据的DMA接收


采用UART中断+DMA搬运


1.uart初始化


 加上uart的中断配置同时开启空闲中断


USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启空闲中断 修改使用的是空闲中断

Usart1NVIC 配置


NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;//串口1中断通道

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x00;//抢占优先级3

NVIC_InitStructure.NVIC_IRQChannelSubPriority =0x02; //子优先级3

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能

NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、

2.DMA配置去掉中断


 开启DMA_Cmd(DMA2_Stream2, ENABLE);  //正式驱动DMA传输

3.中断服务函数void USART1_IRQHandler(void) 


判断当uart产生空闲的中断时,从uart读值以清除中断标志


 (1)关键的一点是:


Usart1_Rec_Cnt = DMA_REC_LEN-DMA_GetCurrDataCounter(DMA2_Stream2); //算出接本帧数据长度

其中的DMA_GetCurrDataCounter获得了当前剩余缓冲区的大小


(2)并将接收的数据再次发送出去:


len为Usart1_Rec_Cnt buf[t]为缓冲区DMA_Rece_Buf


for(t=0;t

{    

while(USART_GetFlagStatus(USA

[1] [2]

关键字:DMA  UART  串口接收  工作方式

编辑:什么鱼 引用地址:http://news.eeworld.com.cn/mcu/ic468710.html
本网站转载的所有的文章、图片、音频视频文件等资料的版权归版权所有人所有,本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如果本网所选内容的文章作者及编辑认为其作品不宜公开自由传播,或不应无偿使用,请及时通过电子邮件或电话通知我们,以迅速采取适当措施,避免给双方造成不必要的经济损失。

上一篇:STM32F407串口配置
下一篇:STM32 Uart 实现printf函数

关注eeworld公众号 快捷获取更多信息
关注eeworld公众号
快捷获取更多信息
关注eeworld服务号 享受更多官方福利
关注eeworld服务号
享受更多官方福利

推荐阅读

CubeMX Stm32F407生成一定周期的占空比不同的方波DMA+定时器

先上图如图 是我生成的一个波形  这个波形的占空比在连续的四个周期内分别是10%,20%,30%,40%, 并且按照这个顺序循环这里大致介绍一下实现的方式。使用的软件是Cubemx(库函肯定也可以实现)第一步是器件选型  这一部分不做介绍  用的是Stm32F407第二是时钟配置接下来是定时器配置 第三步就是生成代码了这里 需要在生成的代码里面加上一行开始的代码。
发表于 2019-08-23
CubeMX Stm32F407生成一定周期的占空比不同的方波DMA+定时器

STM32学习日志--使用DMA功能自动更新PWM的输出

( RCC_SYSCLKSource_PLLCLK );        /* Wait till PLL is used as system clock source */    // 返回用作系统时钟的时钟源    while ( RCC_GetSYSCLKSource( ) != 0x08 )    {    }  }    // 使能或者失能AHB外设时钟  RCC_AHBPeriphClockCmd(    RCC_AHBPeriph_DMA
发表于 2019-08-23

STM32——使用PWM+DMA实现脉冲发送精确控制

之前用stm32写过脉冲发送的代码,用来控制步进电机,但是缺点明显,之前是用定时器中断做的,所以一但控制的电机多起来,MCU资源占用就很大,这在大多数情况下是不可接受的,更不用说多轴联动了。最近做的步进电机CAN总线控制系统,就想顺便重新写驱动。希望做到占用很少的MCU资源,实现脉冲发送的精确控制。既然是用来控制步进电机,那么脉冲的数量和频率一定要可控,要不然怎么实现电机的加减速曲线。于是就想到了DMA。DMA (直接存储器访问)DMA(Direct Memory Access,直接内存存取) 是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。否则,CPU 需要从来源把每一片段的资料
发表于 2019-08-22
STM32——使用PWM+DMA实现脉冲发送精确控制

stm32之ADC应用实例(单通道、多通道、基于DMA)

的引脚了。2个内部通道:一个是内部温度传感器,一个是内部参考电压。在某个项目中要用到芯片里面的AD转换器,那么要怎么写应用代码?(以下是代码讲解)芯片固件的库函数为我们提供了很多封装好的函数,只要运用它提供的函数接口就可以了,宏观上来讲就搞懂两个事情就行了:初始化(设置用的哪个引脚、单通道、还是多通道同时转换、是否使用DMA等配置)?怎么让转换器进行一次数据获取?以下分别讲述三种不同方式(单通道、多通道、基于DMA的多通道采集)的ADC应用实例:/*单通道的ADC采集*/void  Adc_Config(void){     /*定义两个初始化要用的结构体,下面给每个结构体成员赋值
发表于 2019-08-20
stm32之ADC应用实例(单通道、多通道、基于DMA)

HAL库之485+DMA通信(STM32F746)

1.首先一定要保证硬件正确,包括接线,工具正常;2.其次基本配置一定要正确,例如此次配置485的时候,可以实现DMA发送,却不能实现接收。而同样的串口参数配置使用中断方式和232接口都能实现发送接收,个人判断是是接收DMA部分配置有问题,结果卡了很久才发现是基本的管脚配置有误。485的三根引脚要配置成推挽输出,复用,复用模式。3.最后,还是要多总结经验,多看看源代码。硬件如下:其实本质上还是串口通信,只不过多了一个开关控制(485-DIR),用来控制是发送数据还是接收数据,因此是半双工模式。配置流程:时钟使能,引脚配置,串口配置(波特率,校验位等),DMA配置(需要配置串口中断)。void USART2_UART_Init
发表于 2019-08-16
HAL库之485+DMA通信(STM32F746)

13-HAL库DMA系统总结

1.间接DMA:Direct Memory Access,直接存储访问,实现数据在外设与存储器或存储器之间高速访问,数据移动过程无需CPU操作控制,因此可以大大解放CPU负担。外设(ADC、SPI、I2C、DCMI等外设的数据寄存器),存储器(片内SRAM、外部存储器、片内Flash等等)。外设到存储器,例如将AD转换的数据转移到所定义的存储区中。存储器到外设多用于外设的发送通信,例如串口不定长数据的输入输出。STM32F7xx系列有2个DMA控制器,每个控制器具有8个数据流(stream),每个数据流有8个通道(channel),对应如下:每个外设请求占用一个数据流通道,相同外设请求可以占用不同数据流通道。数据流的传输依靠仲裁器
发表于 2019-08-16
13-HAL库DMA系统总结

小广播

何立民专栏

单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2019 EEWORLD.com.cn, Inc. All rights reserved