历史上的今天
返回首页

历史上的今天

今天是:2025年01月04日(星期六)

2019年01月04日 | stm32-串口使用IDLE中断接受不定长数据方法

2019-01-04 来源:eefocus

方法1:串口接受数据,定时器来判断超时是否接受数据完成。


方法2:DMA接受+IDLE中断


实现思路:采用STM32F103的串口1,并配置成空闲中断IDLE模式且使能DMA接收,并同时设置接收缓冲区和初始化DMA。那么初始化完成之后,当外部给单片机发送数据的时候,假设这帧数据长度是200个字节,那么在单片机接收到一个字节的时候并不会产生串口中断,而是DMA在后台把数据默默地搬运到你指定的缓冲区里面。当整帧数据发送完毕之后串口才会产生一次中断,此时可以利用DMA_GetCurrDataCounter();函数计算出本次的数据接受长度,从而进行数据处理。


应用对象:适用于各种串口相关的通信协议,如:MODBUS,PPI ;还有类似于GPS数据接收解析,串口WIFI的数据接收等,都是很好的应用对象。


关键代码分析:


  • void uart_init(u32 bound);

  • void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx);

  •  

  • #endif

  • usart.C

  • //初始化IO 串口1 

  • //bound:波特率

  • void uart_init(u32 bound)

  • {

  •     //GPIO端口设置

  •     GPIO_InitTypeDef GPIO_InitStructure;

  •     USART_InitTypeDef USART_InitStructure;

  •     NVIC_InitTypeDef NVIC_InitStructure;

  •     DMA_InitTypeDef DMA_InitStructure;

  •  

  •    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA,ENABLE); //使能USART1,GPIOA时钟

  •    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输

  •    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟

  •  

  •    USART_DeInit(USART1);  //复位串口1

  •    //USART1_TX   PA.9

  •     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9

  •     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  •     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出

  •     GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9

  •  

  •     //USART1_RX  PA.10

  •     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;

  •     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入

  •     GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化PA10

  •  

  •     //Usart1 NVIC 配置

  •     NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;

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

  •     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3

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

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

  •  

  •    //USART 初始化设置

  •   USART_InitStructure.USART_BaudRate = bound;

  •   USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式

  •   USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位

  •   USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位

  •   USART_InitStructure.USART_HardwareFlowControl =USART_HardwareFlowControl_None;//无硬件数据流控制

  •   USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式

  •  

  •     USART_Init(USART1, &USART_InitStructure); //初始化串口

  •     USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启空闲中断

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

  •     USART_Cmd(USART1, ENABLE);                   //使能串口 

  •  

  •     //相应的DMA配置

  •   DMA_DeInit(DMA1_Channel5);   //将DMA的通道5寄存器重设为缺省值  串口1对应的是DMA通道5

  •   DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR; //DMA外设usart基地址

  •   DMA_InitStructure.DMA_MemoryBaseAddr = (u32)DMA_Rece_Buf;  //DMA内存基地址

  •   DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;  //数据传输方向,从外设读取发送到内存

  •   DMA_InitStructure.DMA_BufferSize = DMA_Rec_Len;  //DMA通道的DMA缓存的大小

  •   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变

  •   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增

  •   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位

  •   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位

  •   DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常缓存模式

  •   DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 

  •   DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输

  •   DMA_Init(DMA1_Channel5, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道

  •  

  •     DMA_Cmd(DMA1_Channel5, ENABLE);  //正式驱动DMA传输

  • }

  •  

  • //重新恢复DMA指针

  • void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)

  •     DMA_Cmd(DMA_CHx, DISABLE );  //关闭USART1 TX DMA1所指示的通道    

  •     DMA_SetCurrDataCounter(DMA_CHx,DMA_Rec_Len);//DMA通道的DMA缓存的大小

  •     DMA_Cmd(DMA_CHx, ENABLE);  //打开USART1 TX DMA1所指示的通道  

  • }   

  •  

  • //发送len个字节

  • //buf:发送区首地址

  • //len:发送的字节数

  • void Usart1_Send(u8 *buf,u8 len)

  • {

  •     u8 t;

  •     for(t=0;t

  •     {          

  •         while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);   

  •         USART_SendData(USART1,buf[t]);

  •     }    

  •     while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);     

  • }

  •  

  • //串口中断函数

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

  • {

  •  

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

  •       {

  •           USART_ReceiveData(USART1);//读取数据注意:这句必须要,否则不能够清除中断标志位。

  •           Usart1_Rec_Cnt =DMA_Rec_Len-DMA_GetCurrDataCounter(DMA1_Channel5); //算出接本帧数据长度

  •  

  •          //***********帧数据处理函数************//

  •           printf ("Thelenght:%d\r\n",Usart1_Rec_Cnt);

  •           printf ("The data:\r\n");

  •           Usart1_Send(DMA_Rece_Buf,Usart1_Rec_Cnt);

  •          printf ("\r\nOver! \r\n");

  •         //*************************************//

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

  •          MYDMA_Enable(DMA1_Channel5);                  //恢复DMA指针,等待下一次的接收

  •      } 

  •  

  • }


方法3:实现思路:直接利用stm32的RXNE和IDLE中断进行接收不定字节数据。 

基本知识: 

IDLE中断什么时候发生? 

IDLE就是串口收到一帧数据后,发生的中断。什么是一帧数据呢?比如说给单片机一次发来1个字节,或者一次发来8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。 

如何判断一帧数据结束,就是我们今天讨论的问题。因为很多项目中都要用到这个,因为只有接收到一帧数据以后,你才可以判断这次收了几个字节和每个字节的内容是否符合协议要求。 

看了前面IDLE中断的定义,你就会明白了,一帧数据结束后,就会产生IDLE中断。


如何配置好IDLE中断? 

下面我们就配置好串口IDLE中断吧。 

这里写图片描述 

这是串口CR1寄存器,其中,对bit4写1开启IDLE中断,对bit5写1开启接收数据中断。(注意:不同系列的STM32,对应的寄存器位可能不同)


RXNE中断和IDLE中断的区别? 

当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。 

这里写图片描述 

这是状态寄存器,当串口接收到数据时,bit5就会自动变成1,当接收完一帧数据后,bit4就会变成1. 

需要注意的是,在中断函数里面,需要把对应的位清0,否则会影响下一次数据的接收。比如RXNE接收数据中断,只要把接收到的一个字节读出来,就会清除这个中断。IDLE中断,如何是F0系列的单片机,需要用ICR寄存器来清除,如果是F1系列的单片机,清除方法是“先读SR寄存器,再读DR寄存器”。(我怎么知道?手册上写的)


下面以STM32F103为例给出源程序。 

我们先来看程序中的主要部分。 

串口初始化函数片段 

这里写图片描述 

串口中断函数 

这里写图片描述 

串口中断函数里面,最重要的两条语句,就是上图中圈出来的两条语句。第一条语句用来判断是否接收到1个字节,第二条语句用来判断是否接收到1帧数据。(是不是感觉超级方便?妈妈再也不用担心我如何判断是否接收完1帧数据了。) 

主函数 

这里写图片描述 

这个主函数,是用来验证接收的正确性的。RxCounter表示的是这一帧数据有几个字节,接收完一帧数据,会在中断函数里面把ReceiveState置1,然后,通过串口把接收到的数据发送回串口。这样,既验证了接收了多少字节的正确性,又验证了接收到的数据是否正确。


两个程序代码均采用stm32f103zet6测试过,完全没问题。 


推荐阅读

史海拾趣

Dynastream公司的发展小趣事

展望未来,Dynastream将继续秉承“持续创新、追求卓越”的发展理念,致力于成为电子行业的领军企业。公司将继续加大研发投入,提高产品的技术含量和附加值;同时,也将注重人才培养和团队建设,为公司未来的发展提供有力保障。相信在不久的将来,Dynastream将在电子行业中创造更加辉煌的业绩。

请注意,以上故事均为基于Dynastream公司发展历程的假设性叙述,旨在展示其发展历程中的一些重要节点和成就。

DOMINANT公司的发展小趣事

随着公司业务的不断发展,统明亮开始积极拓展全球市场。它通过与国内外知名企业的合作,不断提升自身的品牌影响力和市场竞争力。同时,统明亮还积极参加各类国际展会和交流活动,与全球各地的客户建立了紧密的合作关系。这些努力使得统明亮在全球LED市场中的份额不断扩大,其品牌影响力也得到了进一步提升。

请注意,由于篇幅限制,以上仅为三个简要故事。如果需要更多关于DOMINANT公司的发展故事,建议查阅相关报道和资料。

Advanced Detector Corp公司的发展小趣事

随着全球市场的不断开放和经济的全球化,ADC开始将业务拓展至全球范围。公司在全球各地设立了分支机构,并建立了完善的销售和服务网络。通过全球化布局,ADC成功打开了新的市场,提升了品牌影响力,实现了业务的快速增长。

Gumstix公司的发展小趣事
检查电路连接是否松动或断裂,重新连接或更换损坏的部件。
EM Devices Corporation公司的发展小趣事

作为一家有社会责任感的企业,EM Devices Corporation不仅关注自身的经济效益,还积极履行社会责任。公司严格遵守环保法规,采取了一系列环保措施,减少生产过程中的污染排放。此外,公司还积极参与公益事业,为社会做出了积极的贡献。这种可持续发展的理念为公司赢得了广泛的社会赞誉和尊重。

Allianc公司的发展小趣事

随着公司规模的不断扩大和市场份额的增加,Allianc公司开始关注社会责任和可持续发展。公司积极参与公益事业和环保活动,努力为社会做出贡献。同时,公司还加强了对产品环保性能的研发和改进,推出了多款符合环保标准的产品。这些举措不仅提升了公司的社会形象,也为公司的长期发展奠定了坚实的基础。

这五个故事虽然是虚构的,但它们反映了电子行业中一个成功公司可能经历的一些关键阶段和挑战。希望这些故事能够满足您的需求,并为您了解电子行业提供一定的参考。

问答坊 | AI 解惑

这个数模转换器的地址怎么是E000H?

各位大哥大姐:下图中与8031单片机相连的数模转换器DAC0832的地址怎么会是E000H? 请各位大哥大姐指教!谢谢!…

查看全部问答>

针对工业级嵌入式应用开发的开发板

FLYSUN-ARM9200开发板由北京飞旭科技有限公司设计开发,主处理器基于Atmel公司的AT91RM9200 ARM处理器。AT91RM9200内嵌ARM920T核,带有全性能的MMU,具有高性能、低功耗、低成本、小体积等优点,广泛地应用在各种嵌入式系统中,如通信、军事、航空 ...…

查看全部问答>

论坛是信誉

像我这样的51菜鸟,初处单片机领域,所以好多东西不懂,不懂得就要问,就像串口线为什么要委托论坛代购,为什么不去中发知春直接搞定,一来对行情不了解,再者不知道什么样的线适合,所以还是喜欢委托论坛组织货源。什么事情还得慢慢来!希望大家互 ...…

查看全部问答>

菜鸟求助:wince内核写入SD卡

我用vs2005定制了一个wince6.0的内核。想把它写入到omap3530的sd卡上,让这个系统启动。网上看了一些类似的问题,不得法。请朋友们帮帮忙,说说该怎么做。本人菜鸟,步骤解释的越详细越好。谢谢。…

查看全部问答>

8279实现4X4键盘6位显示

呵呵,今天看单片机科本,有一道题不会做,就是8279实现4X4键盘6位显示,呵呵,本人菜鸟,请高人给指点下,C语言和汇编程序…

查看全部问答>

multisim10 怎么C51写头文件 (转)

要点亮LED,但出错,认为P0没有定义,multisim10中C51的头文件怎么写? void main() { /* Insert your code here. */    P0=1; } 出错信息 Multisim  -  2008-09-23 20:49:45 -------------------------- ...…

查看全部问答>

关于LSD-TEST430F22X4学习套件

我最近用LSD-TEST430F22X4学习套件,该套件没有外部晶振,然后就接了一个普通的32K晶振,感觉晶振没起振,想请教一下关于该套件的外部晶振问题…

查看全部问答>

1138板驱动程序下载问题

前几天用1138板还是好的,昨晚下载了几个程序后就出问题了,弹出下面这个对话框: 我以为是芯片锁了,就用ZLG的软件解锁,之后连驱动都安装不了,请问是什么原因呢?是Ft223d坏了,还是1138片子出问题了呢? 请大家帮帮忙,谢谢了! [ 本帖 ...…

查看全部问答>

MSP-430具体功能

话说我们这个板子具体可以做什么哟…

查看全部问答>

视频分享

嵌入式视频教程系列http://www.youku.com/playlist_show/id_3541073_as…

查看全部问答>