打印
[应用相关]

【转】STM32 串口总线空闲检测

[复制链接]
1368|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
傲视群熊|  楼主 | 2016-10-14 00:06 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
主机环境: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头文件如下
[cpp] view plain copy


  • #ifndef __UART_H__  
  • #define __UART_H__  
  • #include <stdint.h>  
  • #include "stm32f0xx.h"  
  • #include <stdio.h>  
  •   
  •   
  • #define USARTx                          USART1  
  • #define USARTx_GPIO_PORT                GPIOA  
  • #define USARTx_GPIO_CLK                 RCC_AHBPeriph_GPIOA  
  • #define USARTx_TX_PIN                   GPIO_Pin_9  
  • #define USARTx_TX_SOURCE                GPIO_PinSource9  
  • #define USARTx_TX_AF                    GPIO_AF_1  
  • #define USARTx_RX_PIN                   GPIO_Pin_10  
  • #define USARTx_RX_SOURCE                GPIO_PinSource10  
  • #define USARTx_RX_AF                    GPIO_AF_1  
  • #define USARTx_IRQn                     USART1_IRQn  
  • #define USARTx_IRQHandler               USART1_IRQHandler  
  • #define USARTx_CLK_ENABLE()             RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE)  
  •   
  • void uart_init (uint32_t baud);  
  •   
  • #endif  


uart的源码文件如下

[cpp] view plain copy


  • #include "uart.h"  
  • uint8_t buffer[100];  
  • uint8_t cnt = 0,idle_detect = 0;  
  •   
  • void uart_init (uint32_t baud)  
  • {  
  •     USART_InitTypeDef USART_InitStructure;     
  •     GPIO_InitTypeDef GPIO_InitStructure;      
  •     NVIC_InitTypeDef NVIC_InitStructure;  
  •   
  •     //初始化串口时钟以及串口端口时钟  
  •     RCC_AHBPeriphClockCmd(USARTx_GPIO_CLK, ENABLE);  
  •     USARTx_CLK_ENABLE();  
  •   
  •     GPIO_PinAFConfig(USARTx_GPIO_PORT, USARTx_TX_SOURCE, USARTx_TX_AF);  
  •     GPIO_PinAFConfig(USARTx_GPIO_PORT, USARTx_RX_SOURCE, USARTx_RX_AF);  
  •   
  •     GPIO_InitStructure.GPIO_Pin = USARTx_TX_PIN| USARTx_RX_PIN;                  
  •     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;         
  •     GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;      
  •     GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;         
  •     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_3;   
  •     GPIO_Init(USARTx_GPIO_PORT, &GPIO_InitStructure);   
  •   
  •     USART_InitStructure.USART_BaudRate            = baud ;            //设置波特率  
  •     USART_InitStructure.USART_WordLength          = USART_WordLength_8b;  //8位数据位  
  •     USART_InitStructure.USART_StopBits            = USART_StopBits_1;     //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(USARTx, &USART_InitStructure);         
  •   
  •     USART_ITConfig(USARTx,USART_IT_RXNE,ENABLE);    //使能接收中断,在接收移位寄存器中有数据时产生  
  •     USART_ITConfig(USARTx,USART_IT_PE,ENABLE);  
  •     USART_ITConfig(USARTx,USART_IT_ERR,ENABLE);  
  •     USART_ITConfig(USARTx,USART_IT_IDLE,ENABLE);    //使能总线空闲检测中断  
  •       
  •     /* 使能 USARTx 中断 */  
  •     NVIC_InitStructure.NVIC_IRQChannel = USARTx_IRQn;  
  •     NVIC_InitStructure.NVIC_IRQChannelPriority=0;  
  •     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
  •     NVIC_Init(&NVIC_InitStructure);   
  •   
  •     USART_Cmd(USARTx, ENABLE);   
  • }  
  •   
  •   
  • int fputc(int ch, FILE *f)  
  • {  
  •     USART_SendData(USARTx,(uint8_t)ch);  
  •     while(USART_GetFlagStatus(USARTx,USART_FLAG_TXE) != SET);  
  •     return ch;  
  • }  
  •   
  • void USARTx_IRQHandler(void)  
  • {  
  •     uint8_t temp = 0;  
  •       
  •     if(USART_GetFlagStatus(USARTx,USART_FLAG_ORE) != RESET)  
  •     {  
  •         temp = USART_ReceiveData(USARTx);  
  •         (void)temp;  
  •         USART_ClearFlag(USARTx,USART_FLAG_ORE);  
  •     }  
  •     if(USART_GetFlagStatus(USARTx,USART_FLAG_NE) != RESET)  
  •     {  
  •         USART_ClearFlag(USARTx,USART_FLAG_NE);  
  •     }  
  •     if(USART_GetFlagStatus(USARTx,USART_FLAG_FE) != RESET)  
  •     {  
  •         USART_ClearFlag(USARTx,USART_FLAG_FE);  
  •     }  
  •     if(USART_GetFlagStatus(USARTx,USART_FLAG_PE) != RESET)  
  •     {  
  •         USART_ClearFlag(USARTx,USART_FLAG_PE);  
  •     }  
  •       
  •     if(USART_GetITStatus(USARTx, USART_IT_RXNE) != RESET)//判断寄存器中是否有数据  
  •     {  
  •         buffer[cnt++]=USART_ReceiveData(USARTx);  //读取数据,读数据的同时清空了接收中断标志;  
  •         if(cnt >= 100)  
  •         {  
  •             cnt = 0;  
  •         }  
  •         USART_ClearITPendingBit(USARTx, USART_IT_RXNE);  
  •     }  
  •     if(USART_GetITStatus(USARTx, USART_IT_IDLE) != RESET)  
  •     {  
  •         //清除总线空闲中断标志位  
  •         USART_ClearITPendingBit(USARTx, USART_IT_IDLE);  
  •         idle_detect = 1;  
  •     }  
  •     return;  
  • }  


沙发
傲视群熊|  楼主 | 2016-10-14 00:12 | 只看该作者
这里检测到IDLE标识置位后置位idle_detect变量,cnt变量标记了本次接收到的字节个数,主函数测试代码如下:

[cpp] view plain copy


  • #include "uart.h"                    
  •   
  • extern uint8_t idle_detect,cnt;  
  • extern uint8_t buffer[100];  
  • int main (void)  
  • {  
  •     uint8_t i = 0;  
  •     uart_init(115200);  
  •   
  •     while(1)  
  •     {  
  •         if(1 == idle_detect)  
  •         {  
  •             idle_detect = 0;  
  •             printf("\r\ncnt:%X,ctx:",cnt);  
  •             for(i = 0; i < cnt; i++)  
  •             {  
  •                 printf("%02X ",buffer);  
  •             }  
  •             cnt = 0;  
  •         }  
  •     }  
  • }  

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


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


  • #include "uart.h"                    
  •   
  • extern uint8_t idle_detect,cnt;  
  • extern uint8_t buffer[100];  
  • int main (void)  
  • {  
  •     uint8_t i = 0;  
  •     uart_init(115200);  
  •   
  •     while(1)  
  •     {  
  •         if(1 == idle_detect && cnt > 0)  
  •         {  
  •             idle_detect = 0;  
  •             printf("\r\ncnt:%X,ctx:",cnt);  
  •             for(i = 0; i < cnt; i++)  
  •             {  
  •                 printf("%02X ",buffer);  
  •             }  
  •             cnt = 0;  
  •         }  
  •     }  
  • }  


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


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


  • #include "uart.h"                    
  •   
  • extern uint8_t idle_detect,cnt;  
  • extern uint8_t buffer[100];  
  • int main (void)  
  • {  
  •     uint8_t i = 0;  
  •     uart_init(115200);  
  •   
  •     while(1)  
  •     {  
  •         if(1 == idle_detect)  
  •         {  
  •             idle_detect = 0;  
  •             if(cnt == 0)  
  •                 continue;  
  •             printf("\r\ncnt:%X,ctx:",cnt);  
  •             for(i = 0; i < cnt; i++)  
  •             {  
  •                 printf("%02X ",buffer);  
  •             }  
  •             cnt = 0;  
  •         }  
  •     }  
  • }  


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


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


  • #include "uart.h"                    
  •   
  • extern uint8_t idle_detect,cnt;  
  • extern uint8_t buffer[100];  
  • int main (void)  
  • {  
  •     uint8_t i = 0;  
  •     uart_init(115200);  
  •   
  •     while(1)  
  •     {  
  •         if(1 == idle_detect && cnt > 0)  
  •         {  
  •             idle_detect = 0;  
  •             uart_puts("\r\ncnt:");  
  •             uart_char(0x30+cnt);  
  •             uart_puts(",ctx:");  
  •             uart_write(buffer,cnt);  
  •             cnt = 0;  
  •         }  
  •     }  
  • }  


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


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

使用特权

评论回复
板凳
mmuuss586| | 2016-10-14 10:52 | 只看该作者
谢谢分享;

使用特权

评论回复
地板
wudonghua| | 2016-10-14 14:43 | 只看该作者
1、重新定义一个变量 uint8_t cnt_temp = 0,
2、在空闲中断里赋值
if(USART_GetITStatus(USARTx, USART_IT_IDLE) != RESET)  
    {  
        //清除总线空闲中断标志位  
        USART_ClearITPendingBit(USARTx, USART_IT_IDLE);  
        cnt_temp  = cnt;
        idle_detect = 1;  
    }  
3、把while里的cnt更换成cnt_temp  
这样什么时候也不会出错的。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

51

主题

84

帖子

1

粉丝