原文:http://www.openedv.com/posts/list/11804.htm
原理:大部分串口都是基於一字節、一字節傳輸,檢測到特定的字符(比如換行或者空格)才判定一幀數據結束,這樣的傳輸機制在自己調試時可以用,但實際運用其實用的很少,最大的壞處是cpu會“死等”特定字符,另外,若是由總線干擾出現的特定的字符,若程序同樣判定幀起始(或停止)符,這明顯是錯誤的。我們需要一幀一幀的傳輸,這樣,就需要字節超時處理了,即只要字符與字符之間間隔超過一定的時間,那么就判定字符是一幀的結束。
大部分教程沒有提到可能是為了降低大家的學習難度,這里提供一份參考代碼,STM32的所有串口都加進去了,全部測試通過,也經過實際項目(非精確嚴格要求)的檢驗。若不需要用到某些串口,只需要把app_conf.h,文件里的相關宏關閉即可。比如,不需要用到串口3、4、5,那么只需要注釋掉app_conf.h里的USING_USART3、USING_USART4、USING_USART5即可。同時,串口相關配置波特率、緩沖區大小等等也在此文件,大家看看注釋就明白了。默認全部開啟,字節超時時間可設,例如USART1_RECEIVE_OVERTIME這類名字的宏。最后一個配置是printf 輸出串口的選擇,默認為串口一。
這個實現五個串口公用一個systick,不需要每個串口需要單獨的硬件定時器;二級緩沖區以加大吞吐量,一個接受緩沖區,接受緩沖區負責接收;一個幀准備緩沖區,幀准備緩沖區有一個准備好的標志USART_ready_buf_ok,應用程序可檢測這個標志看是否有一幀數據存進來。還有一個幀長度USART_ready_buf_len以指示准備好的緩沖區的幀長度。代碼先發放出去,大家先試試,稍后有時間在說說具體事怎么實現的,年底比較事多,大家見諒。
對了,測試代碼是簡單地回發,即5個串口回發自己收到的數據幀,你可以做個測試,若字節超時時間設的比較長(在app_conf.h文件的USART1_RECEIVE_OVERTIME宏),那么你在串口調試助手里不停地點發送,等到你停下來,才會回發你剛發的內容。
還有,這個工程LED燈部分也有些意思,除了亮狀態和滅狀態,還有第三種狀態————閃爍,而且閃爍的次數與閃爍的間隔軟件可配置。若有興趣大家也可以試試,配置也在app_conf.h里,當然,你的板子與我的板子肯定不一樣,那么"AunonBoard_led.h"里的各個LED_PORT和LED_PIN宏定義也要修改。
(二)、軟件定時器
軟件定時器是由一個硬件定時器實現的多個定時器,在定時不要求非常精確的情況下可以用到,比如串口字節超時等等,特點是需要多少個定時器就可以擁有多少個定時器,不受硬件限制。這里的軟件定時器源代碼soft_timer.c和soft_timer.h兩個文件,在不做任何改動的情況在ARM和51下測試通過,其他平台未知,(沒有相應的硬件平台測試)。先放代碼和工程,后面有時間在細說。
使用方法(平台無關):
(1)硬件定時器初始化,中斷配置什么的不要忘了,具體怎么實現無所謂,只要能不停地周期性中斷(stm32的systick最合適了),中斷間隔也是軟件定時器的最小能分辨的間隔,然后將軟件定時器刷新函數void timer_periodic_refresh()--沒有參數--加到你的定時器中斷服務函數里。
(2)定義一個定時器,如:struct soft_timer timer,struct soft_timer 是軟件定時器的結構體,定義在soft_timer.h文件中,timer是你的定時器的名字。
(3)軟件定時器鏈表復位,soft_timer_list_reset(),無參數;
(4)然后添加剛才你定義的定時器timer添加到定時器鏈表add_timer(&timer,timer_over_proc,time_count),第一個參數是你要添加的定時器結構體的指針,第二個參數是定時間到了你要調用的超時函數,第三個參數是定時時間,單位是你的周期性中斷時間間隔。
(5)開始啟動定時器, start_timer(&timer),參數是你要啟動的定時器的結構體指針。
這樣,等超時后,就會自動調用timer_over_proc()函數,(像不像我們自己實現的軟件定時器中斷?)
注意,這個實現是一次性定時器,即一次超時后不再觸發,若需要周期性觸發,那么可以再timer_over_proc()函數里面重裝初值--reload_timer(&timer,time_count),第一個r參數是待啟動定時器結構體指針,time_count是重裝值,單位依舊是你的硬件定時器中斷時間間隔,然后再啟動定時器start_timer(timer)即可。
若還有使用上的問題,可參考兩個測試工程的用法,一個stm32,一個51。STM32測試工程以systick建了3個軟件定時器,分別以0.3s,0.5s,0.7s的時間間隔閃爍3個led燈。C51工程以定時器0建立3個軟件定時器閃爍led燈。具體代碼分析,后面有時間在細說。
STM32的測試結果視頻和代碼見附件。
(三)軟件定時器實現串口字節超時處理
前面第一個工程是以前的一個小項目上用的,原封不動地發了上來,結構除了自個兒比較清晰外可能不會有人知道我再干什么。。。。。。所以寫了這個軟件定時器的版本,並做了充分的中文注釋。歡迎指正!
同前面的一樣,每個串口用了兩個緩沖區,一個接收緩沖區,一個准備緩沖區。當串口中斷函數里面收到一個字符時,放入接收緩沖區,並開始啟動定時器,若在超時范圍內沒有收到下一個字符,超時函數會被自動調用,將接收緩沖區的數據轉移至准備緩沖區內,並將准備標志置位,以供應用程序查詢。
測試工程里面使用了串口1、2,簡單回發收到的數據幀,為了對比,這兩個串口的字節超時設置不一樣,一個50ms,一個500ms,所以串口2在發送比較快的情況會被認為數據沒有結束,直到至少停500ms才會回發。在我的板子上測試通過,由於手上沒有串口3、4、5的板子,所以就沒添加,需要的朋友可自己添加一下,還是比較容易的。
寫完了,不知道有人試過我這個軟件定時器么?以滿足下我小小的虛榮心啊,哈哈哈。
(四)
如果用過Modbus協議,它的判斷結束的唯一條件就是超時。
本人應用的部分代碼 共享下:
//檢測超時函數 供定時器中斷調用-1ms一次
// t為超時時間
__inline void Chk_TimeOut(u8 t)
{
if(!uartRMsg.rcOK && (uartRMsg.rcIndex>=8) && (++uartRMsg.rcS >=t) ) //超時之后,開始重新解碼
uartRMsg.rcOK = true;
}
//接收函數, 供USART中斷調用
__inline void Recieve_MSG(char ch)
{
uartRMsg.rcS = 0; //如果接收到數據,則清除超市檢測計數,
if(!uartRMsg.rcOK)
{
uartRMsg.Buf.buf[uartRMsg.rcIndex ++] = ch; //賦值
if(uartRMsg.rcIndex>=8 && uartRMsg.Buf.msg.cmd!=16)//其他條件成立的時候也可進入解碼程序<此程序為Modbus應用,可改為接收到結束符等>
uartRMsg.rcOK = true;
}
}
//供主函數調用
void MSGTransfer(void)
{
if(!uartRMsg.rcOK) return;
//......數據解析部分
}