历史上的今天
返回首页

历史上的今天

今天是:2025年06月08日(星期日)

正在发生

2018年06月08日 | STM32 串口总线空闲检测

2018-06-08 来源:eefocus

主机环境:Windows XP  SP3

开发环境:MDK 5.20

目标芯片:STM32F030C8T6

前两天在群里看到有人在询问有关STM32 串口总线空闲检测的事情,根据串口总线是否空闲来判断一帧数据是否发送完成,之前使用串口一直没怎么注意过这一串口特性,所以后来特意去看了下手册中有关总线空闲检测的指示,发现它的确是个好特性,之前都只是在串口中断中接收数据在主循环中不断的读取数据然后检测是否是一帧完整的数据,之后再进行后续处理。这样处理有一个不是很好的问题就是在主循环读取串口数据时需要有个超时计数器来避免无串口数据时死等在那里,但如果使用串口总线空闲检测的话,我们就不需要超时计数器了,只需要在检测到串口总线空闲时把收到的数据全部读走,然后检测是否满足一定的格式进而处理,这样是的主循环的时间进一步减少,加速了系统的处理速度。

在STM32F030C8T6的参考手册中串口中断状态寄存器USARTx_ISR中有一个IDLE位来表明是否检测到总线空闲,如下图所示:


并且给出了如何清除该标识,STM32F1系列芯片清除该标识的方法不同,可根据参考手册来查询,且该标识置位后就不再置位除非RXNE位再次置位,如果上位机一次性发送了1个字节数据则RXNE置位1次,IDLE置位1次,而如果上位机一次性发送了6个字节数据,则RXNE置位6次,IDLE依然置位1次,只要在CR1寄存器中使能了串口总线空闲检测就可以使用该特性了,使用标准库编辑了一下测试代码,uart头文件如下

  1. #ifndef __UART_H__  

  2. #define __UART_H__  

  3. #include   

  4. #include "stm32f0xx.h"  

  5. #include   

  6.   

  7.   

  8. #define USARTx                          USART1  

  9. #define USARTx_GPIO_PORT                GPIOA  

  10. #define USARTx_GPIO_CLK                 RCC_AHBPeriph_GPIOA  

  11. #define USARTx_TX_PIN                   GPIO_Pin_9  

  12. #define USARTx_TX_SOURCE                GPIO_PinSource9  

  13. #define USARTx_TX_AF                    GPIO_AF_1  

  14. #define USARTx_RX_PIN                   GPIO_Pin_10  

  15. #define USARTx_RX_SOURCE                GPIO_PinSource10  

  16. #define USARTx_RX_AF                    GPIO_AF_1  

  17. #define USARTx_IRQn                     USART1_IRQn  

  18. #define USARTx_IRQHandler               USART1_IRQHandler  

  19. #define USARTx_CLK_ENABLE()             RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE)  

  20.   

  21. void uart_init (uint32_t baud);  

  22.   

  23. #endif  


uart的源码文件如下

  1. #include "uart.h"  

  2. uint8_t buffer[100];  

  3. uint8_t cnt = 0,idle_detect = 0;  

  4.   

  5. void uart_init (uint32_t baud)  

  6. {  

  7.     USART_InitTypeDef USART_InitStructure;     

  8.     GPIO_InitTypeDef GPIO_InitStructure;       

  9.     NVIC_InitTypeDef NVIC_InitStructure;  

  10.   

  11.     //初始化串口时钟以及串口端口时钟  

  12.     RCC_AHBPeriphClockCmd(USARTx_GPIO_CLK, ENABLE);  

  13.     USARTx_CLK_ENABLE();  

  14.   

  15.     GPIO_PinAFConfig(USARTx_GPIO_PORT, USARTx_TX_SOURCE, USARTx_TX_AF);  

  16.     GPIO_PinAFConfig(USARTx_GPIO_PORT, USARTx_RX_SOURCE, USARTx_RX_AF);  

  17.   

  18.     GPIO_InitStructure.GPIO_Pin = USARTx_TX_PIN| USARTx_RX_PIN;                  

  19.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;         

  20.     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      

  21.     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;         

  22.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;    

  23.     GPIO_Init(USARTx_GPIO_PORT, &GPIO_InitStructure);    

  24.   

  25.     USART_InitStructure.USART_BaudRate            = baud ;            //设置波特率  

  26.     USART_InitStructure.USART_WordLength          = USART_WordLength_8b;  //8位数据位  

  27.     USART_InitStructure.USART_StopBits            = USART_StopBits_1;     //1位停止位  

  28.     USART_InitStructure.USART_Parity              = USART_Parity_No;     //无校验位  

  29.     USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //无硬件控制  

  30.     USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx;   //发送与接收两种方式  

  31.     USART_Init(USARTx, &USART_InitStructure);         

  32.   

  33.     USART_ITConfig(USARTx,USART_IT_RXNE,ENABLE);    //使能接收中断,在接收移位寄存器中有数据时产生  

  34.     USART_ITConfig(USARTx,USART_IT_PE,ENABLE);  

  35.     USART_ITConfig(USARTx,USART_IT_ERR,ENABLE);  

  36.     USART_ITConfig(USARTx,USART_IT_IDLE,ENABLE);    //使能总线空闲检测中断  

  37.       

  38.     /* 使能 USARTx 中断 */  

  39.     NVIC_InitStructure.NVIC_IRQChannel = USARTx_IRQn;  

  40.     NVIC_InitStructure.NVIC_IRQChannelPriority=0;  

  41.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  

  42.     NVIC_Init(&NVIC_InitStructure);   

  43.   

  44.     USART_Cmd(USARTx, ENABLE);   

  45. }  

  46.   

  47.   

  48. int fputc(int ch, FILE *f)  

  49. {  

  50.     USART_SendData(USARTx,(uint8_t)ch);  

  51.     while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) != SET);  

  52.     return ch;  

  53. }  

  54.   

  55. void USARTx_IRQHandler(void)  

  56. {  

  57.     uint8_t temp = 0;  

  58.       

  59.     if(USART_GetFlagStatus(USARTx,USART_FLAG_ORE) != RESET)  

  60.     {  

  61.         temp = USART_ReceiveData(USARTx);  

  62.         (void)temp;  

  63.         USART_ClearFlag(USARTx,USART_FLAG_ORE);  

  64.     }  

  65.     if(USART_GetFlagStatus(USARTx,USART_FLAG_NE) != RESET)  

  66.     {  

  67.         USART_ClearFlag(USARTx,USART_FLAG_NE);  

  68.     }  

  69.     if(USART_GetFlagStatus(USARTx,USART_FLAG_FE) != RESET)  

  70.     {  

  71.         USART_ClearFlag(USARTx,USART_FLAG_FE);  

  72.     }  

  73.     if(USART_GetFlagStatus(USARTx,USART_FLAG_PE) != RESET)  

  74.     {  

  75.         USART_ClearFlag(USARTx,USART_FLAG_PE);  

  76.     }  

  77.       

  78.     if(USART_GetITStatus(USARTx, USART_IT_RXNE) != RESET)//判断寄存器中是否有数据  

  79.     {  

  80.         buffer[cnt++]=USART_ReceiveData(USARTx);  //读取数据,读数据的同时清空了接收中断标志;  

  81.         if(cnt >= 100)  

  82.         {  

  83.             cnt = 0;  

  84.         }  

  85.         USART_ClearITPendingBit(USARTx, USART_IT_RXNE);  

  86.     }  

  87.     if(USART_GetITStatus(USARTx, USART_IT_IDLE) != RESET)  

  88.     {  

  89.         //清除总线空闲中断标志位  

  90.         USART_ClearITPendingBit(USARTx, USART_IT_IDLE);  

  91.         idle_detect = 1;  

  92.     }  

  93.     return;  

  94. }  


这里检测到IDLE标识置位后置位idle_detect变量,cnt变量标记了本次接收到的字节个数,主函数测试代码如下:


  1. #include "uart.h"                    

  2.   

  3. extern uint8_t idle_detect,cnt;  

  4. extern uint8_t buffer[100];  

  5. int main (void)  

  6. {  

  7.     uint8_t i = 0;  

  8.     uart_init(115200);  

  9.   

  10.     while(1)  

  11.     {  

  12.         if(1 == idle_detect)  

  13.         {  

  14.             idle_detect = 0;  

  15.             printf("\r\ncnt:%X,ctx:",cnt);  

  16.             for(i = 0; i < cnt; i++)  

  17.             {  

  18.                 printf("%02X ",buffer[i]);  

  19.             }  

  20.             cnt = 0;  

  21.         }  

  22.     }  

  23. }  

代码比较简单,这里使用printf来输出本次接收到的字节数以及字节内容,运行结果如下:



可以看到检测结果是正常的,只是在开机后有一次cnt为0的结果,应该是上电之后总线默认就是空闲的,的确也没有收到数据,因此就想把cnt为0的结果去掉,这里我遇到了一个很纠结的问题,在判断idle_detect变量的同时也检测cnt是否大于0,测试代码更改如下:

  1. #include "uart.h"                    

  2.   

  3. extern uint8_t idle_detect,cnt;  

  4. extern uint8_t buffer[100];  

  5. int main (void)  

  6. {  

  7.     uint8_t i = 0;  

  8.     uart_init(115200);  

  9.   

  10.     while(1)  

  11.     {  

  12.         if(1 == idle_detect && cnt > 0)  

  13.         {  

  14.             idle_detect = 0;  

  15.             printf("\r\ncnt:%X,ctx:",cnt);  

  16.             for(i = 0; i < cnt; i++)  

  17.             {  

  18.                 printf("%02X ",buffer[i]);  

  19.             }  

  20.             cnt = 0;  

  21.         }  

  22.     }  

  23. }  


感觉逻辑是对的,再次运行,结果如下:



这个时候发现cnt变量的值输出一直为1,但输出的字节内容却是对的,一直想不通这是为啥?思来想去逻辑是没啥问题的,后来又把cnt是否为0的条件判断不放在跟idle_detect变量检测同一水平,而是放在它里面,更改测试代码如下:

  1. #include "uart.h"                    

  2.   

  3. extern uint8_t idle_detect,cnt;  

  4. extern uint8_t buffer[100];  

  5. int main (void)  

  6. {  

  7.     uint8_t i = 0;  

  8.     uart_init(115200);  

  9.   

  10.     while(1)  

  11.     {  

  12.         if(1 == idle_detect)  

  13.         {  

  14.             idle_detect = 0;  

  15.             if(cnt == 0)  

  16.                 continue;  

  17.             printf("\r\ncnt:%X,ctx:",cnt);  

  18.             for(i = 0; i < cnt; i++)  

  19.             {  

  20.                 printf("%02X ",buffer[i]);  

  21.             }  

  22.             cnt = 0;  

  23.         }  

  24.     }  

  25. }  


这个时候再次运行代码,结果如下:



这个时候输出的结果是正确的,难道两个条件检测放在一起会有问题吗?这个是不应该的,因为idle_detect为0时CPU是不会再去检测cnt变量的,只有idle_detect为1时才去检测cnt变量,因此cnt的条件检测放在里面和放在外面应该是一样的效果才对。后来想是否是printf引起的问题,就把printf去掉改成了自己的输出函数,更改测试代码如下:

  1. #include "uart.h"                    

  2.   

  3. extern uint8_t idle_detect,cnt;  

  4. extern uint8_t buffer[100];  

  5. int main (void)  

  6. {  

  7.     uint8_t i = 0;  

  8.     uart_init(115200);  

  9.   

  10.     while(1)  

  11.     {  

  12.         if(1 == idle_detect && cnt > 0)  

  13.         {  

  14.             idle_detect = 0;  

  15.             uart_puts("\r\ncnt:");  

  16.             uart_char(0x30+cnt);  

  17.             uart_puts(",ctx:");  

  18.             uart_write(buffer,cnt);  

  19.             cnt = 0;  

  20.         }  

  21.     }  

  22. }  


这里cnt的输出是有问题的,但我测试时保证cnt不会大于10,因此不影响测试结果,在编译时把微库去掉,运行结果如下:



结果发现在数据小于等于6时结果是正确的,而当数据大于6时cnt一直为6但字节内容同样是正确的,很是费解,在后来我又测试过当发送的字节为16时,输出的数据内容会少几个字节,总而言之把cnt变量的判断放在idle_detect变量检测的后面就会导致结果不对,而把cnt变量的判断放在里面就不会有问题,所以应该不是printf引起的问题,感觉像是if条件语句引起的问题,尝试过很多方法也没搞定该问题,虽然最后我们也能回避这个问题,但没找到原因真纠结,如果有人知道是啥问题的话,麻烦告知一下。

最后串口总线空闲检测这一特性的确很有用,尤其是对收到的数据是不定长时更有效果,大家以后可以尝试使用一下该特性。

PS:

今天下午不死心又进行了一些测试,还怀疑过是否是时序的问题,对比了一下cnt判断所在位置不同的汇编代码,对比结果如下:


左侧是cnt判断和idle_detect判断在同一水平上,右侧是cnt判断放在了idle_detect条件检测里面,两者差别不大,再次证明我们的代码逻辑是没有问题的,后来又想起使用printf和使用我们自己的输出函数cnt的值会有变化,在使用printf时cnt一直为1,而使用我们自己的输出函数时cnt在数据长度小于等于6时正确,两者的区别就是printf会使用微库,延迟不同,所以增加了一个延迟函数,如下:

  1. void delay(void)  

  2. {  

  3.     uint32_t t = 5000;  

  4.     while(t)  

  5.     {  

  6.         __NOP();__NOP();__NOP();__NOP();__NOP();  

  7.         t-=1;  

  8.     }  

  9. }  


更改主函数的测试代码,delay()函数可以有两处放置,如下:

  1. int main (void)  

  2. {  

  3.     uint8_t i = 0;  

  4.     uart_init(115200);  

  5.   

  6.     while(1)  

  7.     {  

  8.         delay();  

  9.         if(1 == idle_detect && cnt > 0)   

  10.         {  

  11.             idle_detect = 0;  

  12.             //delay();  

  13.             printf("\r\ncnt:%X,ctx:",cnt);  

  14.             for(i = 0; i < cnt; i++)  

  15.             {  

  16.                 printf("%02X ",buffer[i]);  

  17.             }  

  18.             cnt = 0;  

  19.         }  

  20.     }  

  21. }  


加上delay()函数后代码运行的结果就正确了,区别是delay()函数如果放在if条件语句里面时当上位机发送的数据量多时需要的延迟就会长些才能保证结果的正确性,而delay()函数放在if条件语句的外面则不会这样,总算找到问题了,但还是推荐把cnt的判断放在if条件的里面,这样就不需要增加延迟函数了,就这样吧。


推荐阅读

史海拾趣

Advanced Fibreoptic Engineering Ltd公司的发展小趣事

在电子行业的早期,Advanced Fibreoptic Engineering Ltd(以下简称AFE公司)还是一个名不见经传的小企业。然而,随着技术的不断进步,AFE公司凭借其在光纤技术领域的深厚积累,成功研发出了一种具有划时代意义的新型光纤材料。这种材料不仅传输速度快,而且损耗极低,极大地提高了数据传输的效率和质量。这一技术突破迅速为AFE公司赢得了市场认可,公司的订单量激增,业绩逐年攀升。

随着技术的推广和应用,AFE公司的光纤产品逐渐在通信、医疗、工业等多个领域得到广泛应用。公司不仅在国内市场占据了一席之地,还积极拓展海外市场,与国际知名企业建立了稳定的合作关系。凭借卓越的产品性能和良好的市场口碑,AFE公司逐渐在电子行业中崭露头角,成为了光纤技术领域的佼佼者。

以上是第一个故事的示例,若您想要探索更多关于AFE公司的发展故事,请输入继续。

(注:由于我无法实时获取具体公司的实际发展故事,以上故事为虚构内容,仅用于展示故事编写风格和结构。如果您需要真实、具体的故事,请提供更多关于AFE公司的信息,以便我能为您编写更贴近实际的内容。)

Diconex公司的发展小趣事

在电子行业中,Diconex公司始终坚持以客户需求为导向的市场定位策略。公司深入调研市场需求,针对不同客户群体推出定制化解决方案。这种精准的市场定位使得Diconex的产品能够更好地满足客户需求,赢得了客户的信任和忠诚。同时,公司还积极拓展国际市场,与全球知名企业建立了长期稳定的合作关系。

General Industrial Controls ( GIC )公司的发展小趣事
确保所有元件均为合格产品,无损坏或老化现象。特别是电感器,其载流能力、Q值和工作频率等参数需满足要求。
Harbour Industries公司的发展小趣事

背景:随着全球化的深入发展,电子行业也迎来了国际化的浪潮。Handok意识到,要想在激烈的国际竞争中立于不败之地,必须加强与国际市场的联系和合作。

发展:Handok积极寻求与国际知名电子企业的合作机会,通过技术引进、联合研发等方式不断提升自身的技术实力和产品品质。同时,公司还加大了对国际市场的开拓力度,通过参加国际展会、设立海外分支机构等方式扩大品牌影响力。

影响:国际合作的成功为Handok带来了更多的发展机遇和市场空间,公司的国际知名度和竞争力显著提升。

ACT [Advanced Crystal Technology]公司的发展小趣事

背景:随着信息技术的快速发展和工业互联网的兴起,电子行业正经历着深刻的数字化转型和智能化升级。Handok紧跟时代步伐,积极推动公司的数字化转型和智能化升级。

发展:Handok引入了先进的信息化管理系统和智能制造技术,实现了生产过程的自动化、智能化和可视化。同时,公司还加强了与上下游企业的协同合作,构建了更加紧密、高效的产业链生态系统。

影响:数字化转型和智能化升级不仅提高了Handok的生产效率和产品质量,还降低了运营成本和市场风险。此外,这一战略还有助于公司在未来的市场竞争中保持灵活性和创新性。

请注意,以上故事是基于一般性的商业逻辑和行业趋势构建的,并非Handok公司的实际发展历程。由于直接关于Handok在电子行业发展的详细故事资料有限,这些故事仅供参考和启发。

Ampire Co Ltd公司的发展小趣事

背景:20世纪末,随着电子技术的飞速发展,Handok敏锐地捕捉到了半导体行业的崛起机遇。公司决定投入大量研发资源,专注于开发新型半导体材料和技术。

发展:经过数年的潜心研究,Handok成功研发出一种具有更高导电性和稳定性的新型半导体材料,这一创新成果迅速获得了业界的广泛关注。公司迅速将这项技术应用于生产,推出了一系列高性能的半导体产品,迅速占领了市场的一席之地。

影响:Handok的技术革新不仅推动了公司自身的快速发展,还对整个半导体行业产生了深远的影响,促进了整个产业链的升级和转型。

问答坊 | AI 解惑

protuse中文教程

超详细的中文教程,很好啊,…

查看全部问答>

直流调速

本帖最后由 paulhyde 于 2014-9-15 09:07 编辑 直流调速  …

查看全部问答>

EVC4.0编程环境,如何把变量文件名传入fopen?

两个菜鸟问题: 1. 找到别人的代码: int   order=0;      AnsiString   fileName,path=\"c:\\\\temp\\\\\";      FILE   *fp;      fileName= ...…

查看全部问答>

2440板ROM剩余空间不准

使用QQ2440v3开发板,原机配置系统中可以看到FRIENDLYFlash盘符有16M的空间,自己定制系统后FRIENDLYFlash名称变为ResidentFlash,容量只有1.96M,不知道怎么回事,如何实现完全将所有剩余空间生成一个盘符…

查看全部问答>

uCOS-II 在keil 下能不能仿真

uCOS-II  在keil 下能不能仿真 有什么地方要注意. 谢谢!…

查看全部问答>

MPC8260与FPGA在VXWORKS下的通信

各位大哥,我刚接触VXWORKS不久,请大家指教。实验室有块powerpc104开发板(含有MPC8260+FPGA),8260与FPGA通过64位数据总线和32位地址总线(好像就是60X总线)连接,购买板子已自带BSP,怎么实现8260与FPGA在VXWORKS下的通信(中断,数据传递)啊 ...…

查看全部问答>

LM3S9B96的i2c设置问题?

各位用过的说说怎么设置,我设置的没有反应。              SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C1);           &nbs ...…

查看全部问答>

关于调整op07集成运放的精度

小弟最近用OP07做了一块信号调理的板子,但是输入信号和输出信号的电压不成比例关系,测得的数据离散性很大,望各位前辈不吝赐教。…

查看全部问答>

launchpad学习心得随便聊聊

今天学习了EEWORLD论坛中的launchpad课程,感觉讲得蛮详细的,介绍的很全面,学到了不少东西。可惜我还只是一个新手,有些东西还没彻底弄明白。好吧,就这些了,希望有高手能帮忙指导,谢谢。…

查看全部问答>

求大神帮我分析一下这两个电路

看资料说是什么巴特沃斯型滤波器,但是我找不到他们的截止频率的算法~~ 求各位大神帮忙分析一下~~~…

查看全部问答>