历史上的今天
返回首页

历史上的今天

今天是:2025年07月22日(星期二)

正在发生

2019年07月22日 | DMA和UART的深刻认识--串口接收的3种工作方式(附STM32F4代码)

2019-07-22 来源:eefocus

可能会遇到的问题:


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(USART1, USART_FLAG_TC) == RESET);   

 USART_SendData(USART1,buf[t]);

}

(3)由于是单次传输,因此传输完成一次,要再次初始化DMA并清除中断标志


USART_ClearITPendingBit(USART1, USART_IT_IDLE);         //清除中断标志

MYDMA_Enable(DMA2_Stream2,DMA_REC_LEN);//可以实现实时调节数据传输量

MYDMA_Config_Rx(DMA2_Stream2,DMA_Channel_4,(u32)&USART1->DR,(u32)DMA_Rece_Buf, DMA_REC_LEN);

4.在主函数中初始化DMA和uart即可


完成


结果:其中可看到接收到22字节 和29字节的,原因是先使用串口调试助手发送给MCU,MCU依据接收的值再发送回串口。乱码为本程序的其它操作可不比在意。


参考http://www.openedv.com/thread-63849-1-1.html 改进,这里的程序单次可接收中断,但是下一次时不行,原因是没有再次初始化DMA。


推荐阅读

史海拾趣

BeagleBoard公司的发展小趣事

作为一家以开源硬件为核心的公司,BeagleBoard深知教育和人才培养的重要性。他们积极与学校、教育机构合作,提供教育支持和培训服务,帮助更多的学生了解嵌入式系统开发的魅力。同时,公司还设立了奖学金和竞赛项目,鼓励年轻开发者积极参与创新实践,为行业培养了一批批优秀的人才。

CSR plc(剑桥硅晶无线电)公司的发展小趣事

2009年,CSR plc以1.36亿美元成功收购了SiRF技术控股公司。这一收购为CSR plc带来了GPS和A-GPS的知识产权组合、航位推算和位置中心平台等重要技术资源,使其在世界无晶圆半导体厂商中的地位得到了进一步巩固。此次收购也让CSR plc的客户群体得到了极大的扩展,包括世界排名前七位的手机制造商中的六家,以及世界前五位的个人导航设备制造商等。

骅讯(Cmedia)公司的发展小趣事

为了进一步提升品牌影响力和市场竞争力,骅讯积极寻求与知名品牌的合作。通过与音频市场上的主要品牌建立合作关系,骅讯成功将其先进的音频技术应用于各种音频产品中,为消费者带来了更为优质的音频体验。这种合作模式不仅促进了骅讯技术的推广和应用,也为其带来了更为广阔的市场空间。

Amprobe公司的发展小趣事

骅讯(Cmedia)公司成立于1991年,起初是一家专注于音频芯片设计的公司。在成立初期,骅讯就凭借其PCI多通道芯片在市场上取得了初步的成功。这种芯片为电脑提供了更为丰富的音频体验,从而帮助骅讯在音频芯片设计领域崭露头角。

B&K Precision公司的发展小趣事

随着业务的不断发展,科恩和班意识到电子测试和测量领域的巨大潜力。1951年,他们将公司更名为B&K Precision,并开始扩展业务,涉足其他电子测试和测量领域。工程师们凭借在电视测试设备领域的丰富经验,不断研发新产品,获得了多项专利,并推动公司迅速成为全球电子测量领域的领导者。

泽耀科技(Ashining)公司的发展小趣事

随着市场的不断变化和消费者需求的升级,泽耀科技(Ashining)意识到只有不断创新才能在竞争中立于不败之地。公司加大了对研发的投入,引进了一批高素质的研发人才,并建立了完善的研发体系。经过多次尝试和实验,泽耀科技成功突破了某项关键技术,并推出了升级版的产品。这些新产品不仅性能更加优越,而且具有更高的性价比,深受消费者的喜爱。

问答坊 | AI 解惑

赶个时髦,也做个光轨迹作画的“设备”

以前在报纸上看到的,、 东西很简单,一个光源,最好是点光源, 一个相机就可以了 光源本来想用个高亮发光管来用,后来因为还要焊接, 懒得动弹 就翻出来小时候玩的那个激光管来,用USB供电,正好 环境最好找个全黑的地方, 相机吧快门调节 ...…

查看全部问答>

嵌入式问题,我咋就不会呢?急!!

刚开始学嵌入式,这两个题硬是不会做!! 3.radiusLib.c中定义了一个全局变量g_user_point,请增加关于用户信息读取和设置的网关命令 4.自己编写一个接口,调用radius认证请求和计费请求,并完成radius认证,认证成功或者失败给出打印信息 还请 ...…

查看全部问答>

GPIO中断

我的平台是pxa255+wce4.2,添加自定义中断时,首先是硬件上连接,也即中断源连接到pxa255的某个GPIO,并把该GPIO设置为输入方式。中断发生时,GPIO有上升沿输入,然后pxa255的中断寄存器ICIP中的某位就自动被设置为1,ISR通过判断ICIP寄存器的某位 ...…

查看全部问答>

请求各位能推荐一些linux下的散热软件

系统环境为fc8 硬件环境为Dell Inspiron 640m 笔记本,Intel Core 双核 不知道怎么回师,一到夏天,我的机器便会毫无征兆的自动重启,没有关机过程,如同按了台式机的reset按钮一样。 严重影响的了正常工作。 去年在免费上门服务区,描述了相关 ...…

查看全部问答>

tcpmp界面的问题

在TCPMP的static LRESULT CALLBACK Proc(HWND Wnd, UINT Msg, WPARAM wParam, LPARAM lParam)函数下面画的位图始终被别的窗口给盖住,哪位知道是什么原因吗?谢谢!     case WM_PAINT:       hdc=BeginPaint(p- ...…

查看全部问答>

如何获取物理硬盘数目?

我查了MSDN,找到CLUSCTL_RESOURCE_TYPE_STORAGE_GET_AVAILABLE_DISKS可以查到,但是用起来实在太烦,有没有像_getdrives这样一句话搞定的函数?…

查看全部问答>

再问版主关于使用AD处理触摸屏的设计

我们想用STM32F103的AD进行触摸屏的设计(即省掉触摸屏驱动IC),请问这样做是否可行,ST目前有这方面的设计吗? 谢谢!…

查看全部问答>

聘通信英才,有木有?

三维通信股份有限公司(简称三维通信,股票代码:002115)成立于1993年5月,总部坐落于杭州滨江,是专业提供无线产品和网络覆盖解决方案的国家重点高新技术企业和软件企业。公司集研发、生产、工程服务为一体,是一家通过全球通信行业质量管理体系 ...…

查看全部问答>

关于LCD12864显示图片会出现很多杂点的清除……

LCD12864显示图片时四周会有很多杂点,对于杂点的清除高手们分别有什么好的建议,关于程序开头延时和清屏都试过没啥效果………

查看全部问答>

stm32cubemx生成nucleo l053的点亮led程序

刚刚通过团购获得一块sm32l053的nucleo开发板,发现板上竟然没有HSE晶振,刚好有一个8MHz的晶振便顺手焊上去了。下载了几个官方的例程,调试了 一下发现时钟竟然只有2MHz,不明白例程里面为什么把时钟设置成这么低,自己摸索着将时钟设到最高主频32 ...…

查看全部问答>