主機環(huán)境:Windows XP SP3
開發(fā)環(huán)境:MDK 5.20
目標芯片:STM32F030C8T6
前兩天在群里看到有人在詢問有關(guān)STM32 串口總線空閑檢測的事情,根據(jù)串口總線是否空閑來判斷一幀數(shù)據(jù)是否發(fā)送完成,之前使用串口一直沒怎么注意過這一串口特性,所以后來特意去看了下手冊中有關(guān)總線空閑檢測的指示,發(fā)現(xiàn)它的確是個好特性,之前都只是在串口中斷中接收數(shù)據(jù)在主循環(huán)中不斷的讀取數(shù)據(jù)然后檢測是否是一幀完整的數(shù)據(jù),之后再進行后續(xù)處理。這樣處理有一個不是很好的問題就是在主循環(huán)讀取串口數(shù)據(jù)時需要有個超時計數(shù)器來避免無串口數(shù)據(jù)時死等在那里,但如果使用串口總線空閑檢測的話,我們就不需要超時計數(shù)器了,只需要在檢測到串口總線空閑時把收到的數(shù)據(jù)全部讀走,然后檢測是否滿足一定的格式進而處理,這樣是的主循環(huán)的時間進一步減少,加速了系統(tǒng)的處理速度。
在STM32F030C8T6的參考手冊中串口中斷狀態(tài)寄存器USARTx_ISR中有一個IDLE位來表明是否檢測到總線空閑,如下圖所示:

并且給出了如何清除該標識,STM32F1系列芯片清除該標識的方法不同,可根據(jù)參考手冊來查詢,且該標識置位后就不再置位除非RXNE位再次置位,如果上位機一次性發(fā)送了1個字節(jié)數(shù)據(jù)則RXNE置位1次,IDLE置位1次,而如果上位機一次性發(fā)送了6個字節(jié)數(shù)據(jù),則RXNE置位6次,IDLE依然置位1次,只要在CR1寄存器中使能了串口總線空閑檢測就可以使用該特性了,使用標準庫編輯了一下測試代碼,uart頭文件如下
- #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的源碼文件如下
- #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 ; //設(shè)置波特率
- USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位數(shù)據(jù)位
- 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; //發(fā)送與接收兩種方式
- USART_Init(USARTx, &USART_InitStructure);
-
- USART_ITConfig(USARTx,USART_IT_RXNE,ENABLE); //使能接收中斷,在接收移位寄存器中有數(shù)據(jù)時產(chǎn)生
- 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)//判斷寄存器中是否有數(shù)據(jù)
- {
- buffer[cnt++]=USART_ReceiveData(USARTx); //讀取數(shù)據(jù),讀數(shù)據(jù)的同時清空了接收中斷標志;
- 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;
- }
這里檢測到IDLE標識置位后置位idle_detect變量,cnt變量標記了本次接收到的字節(jié)個數(shù),主函數(shù)測試代碼如下:
- #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[i]);
- }
- cnt = 0;
- }
- }
- }
代碼比較簡單,這里使用printf來輸出本次接收到的字節(jié)數(shù)以及字節(jié)內(nèi)容,運行結(jié)果如下:

可以看到檢測結(jié)果是正常的,只是在開機后有一次cnt為0的結(jié)果,應(yīng)該是上電之后總線默認就是空閑的,的確也沒有收到數(shù)據(jù),因此就想把cnt為0的結(jié)果去掉,這里我遇到了一個很糾結(jié)的問題,在判斷idle_detect變量的同時也檢測cnt是否大于0,測試代碼更改如下:
- #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[i]);
- }
- cnt = 0;
- }
- }
- }
感覺邏輯是對的,再次運行,結(jié)果如下:

這個時候發(fā)現(xiàn)cnt變量的值輸出一直為1,但輸出的字節(jié)內(nèi)容卻是對的,一直想不通這是為啥?思來想去邏輯是沒啥問題的,后來又把cnt是否為0的條件判斷不放在跟idle_detect變量檢測同一水平,而是放在它里面,更改測試代碼如下:
- #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[i]);
- }
- cnt = 0;
- }
- }
- }
這個時候再次運行代碼,結(jié)果如下:

這個時候輸出的結(jié)果是正確的,難道兩個條件檢測放在一起會有問題嗎?這個是不應(yīng)該的,因為idle_detect為0時CPU是不會再去檢測cnt變量的,只有idle_detect為1時才去檢測cnt變量,因此cnt的條件檢測放在里面和放在外面應(yīng)該是一樣的效果才對。后來想是否是printf引起的問題,就把printf去掉改成了自己的輸出函數(shù),更改測試代碼如下:
- #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,因此不影響測試結(jié)果,在編譯時把微庫去掉,運行結(jié)果如下:

結(jié)果發(fā)現(xiàn)在數(shù)據(jù)小于等于6時結(jié)果是正確的,而當數(shù)據(jù)大于6時cnt一直為6但字節(jié)內(nèi)容同樣是正確的,很是費解,在后來我又測試過當發(fā)送的字節(jié)為16時,輸出的數(shù)據(jù)內(nèi)容會少幾個字節(jié),總而言之把cnt變量的判斷放在idle_detect變量檢測的后面就會導致結(jié)果不對,而把cnt變量的判斷放在里面就不會有問題,所以應(yīng)該不是printf引起的問題,感覺像是if條件語句引起的問題,嘗試過很多方法也沒搞定該問題,雖然最后我們也能回避這個問題,但沒找到原因真糾結(jié),如果有人知道是啥問題的話,麻煩告知一下。
最后串口總線空閑檢測這一特性的確很有用,尤其是對收到的數(shù)據(jù)是不定長時更有效果,大家以后可以嘗試使用一下該特性。
PS:
今天下午不死心又進行了一些測試,還懷疑過是否是時序的問題,對比了一下cnt判斷所在位置不同的匯編代碼,對比結(jié)果如下:

左側(cè)是cnt判斷和idle_detect判斷在同一水平上,右側(cè)是cnt判斷放在了idle_detect條件檢測里面,兩者差別不大,再次證明我們的代碼邏輯是沒有問題的,后來又想起使用printf和使用我們自己的輸出函數(shù)cnt的值會有變化,在使用printf時cnt一直為1,而使用我們自己的輸出函數(shù)時cnt在數(shù)據(jù)長度小于等于6時正確,兩者的區(qū)別就是printf會使用微庫,延遲不同,所以增加了一個延遲函數(shù),如下:
- void delay(void)
- {
- uint32_t t = 5000;
- while(t)
- {
- __NOP();__NOP();__NOP();__NOP();__NOP();
- t-=1;
- }
- }
更改主函數(shù)的測試代碼,delay()函數(shù)可以有兩處放置,如下:
- int main (void)
- {
- uint8_t i = 0;
- uart_init(115200);
-
- while(1)
- {
- delay();
- if(1 == idle_detect && cnt > 0)
- {
- idle_detect = 0;
- //delay();
- printf("\r\ncnt:%X,ctx:",cnt);
- for(i = 0; i < cnt; i++)
- {
- printf("%02X ",buffer[i]);
- }
- cnt = 0;
- }
- }
- }
加上delay()函數(shù)后代碼運行的結(jié)果就正確了,區(qū)別是delay()函數(shù)如果放在if條件語句里面時當上位機發(fā)送的數(shù)據(jù)量多時需要的延遲就會長些才能保證結(jié)果的正確性,而delay()函數(shù)放在if條件語句的外面則不會這樣,總算找到問題了,但還是推薦把cnt的判斷放在if條件的里面,這樣就不需要增加延遲函數(shù)了,就這樣吧。
|