1解
1. 問題及現(xiàn)象
使用USART_SendData()函數(shù)非連續(xù)發(fā)送單個字符是沒有問題的;當連續(xù)發(fā)送字符時(兩個字符間沒有延時),就會發(fā)現(xiàn)發(fā)送緩沖區(qū)有溢出現(xiàn)象。若發(fā)送的數(shù)據(jù)量很小時,此時串口發(fā)送的只是最后一個字符,當發(fā)送數(shù)據(jù)量大時,就會導(dǎo)致發(fā)送的數(shù)據(jù)莫名其妙的丟失。
如:
1
2for(TxCounter = 0;TxCounter < RxCounter; TxCounter++)
USART_SendData(USART1, RxBuffer[TxCounter]);
2. 原因
此API函數(shù)不完善,函數(shù)體內(nèi)部沒有一個判斷一個字符是否發(fā)送完畢的語句,而是把數(shù)據(jù)直接放入發(fā)送緩沖區(qū),當連續(xù)發(fā)送數(shù)據(jù)時,由于發(fā)送移位寄存器的速度限制(與通信波特率有關(guān)),導(dǎo)致發(fā)送緩沖區(qū)的數(shù)據(jù)溢出,老的數(shù)據(jù)還未及時發(fā)送出去,新的數(shù)據(jù)又把發(fā)送緩沖區(qū)的老數(shù)據(jù)覆蓋了。
3. 解決方法
發(fā)送后等待一段時間延遲的方法就不說了,等待時間不確定,此為下下策。提供下面2種方案:
方案1. 在每一個字符發(fā)送后檢測狀態(tài)位
USART_SendData(USART1, RxBuffer[TxCounter]);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET){} //等待發(fā)送緩沖區(qū)空才能發(fā)送下一個字符
方案2. 修改庫函數(shù)
修改USART_SendData()函數(shù),在其內(nèi)部加入發(fā)送緩沖區(qū)的USART_FLAG_TXE狀態(tài)檢測語句,確保一個字符完全發(fā)送出去,才進行下一個字符的發(fā)送。
實現(xiàn)方法:每發(fā)送一個字符都檢測狀態(tài)寄存器,確保數(shù)據(jù)已經(jīng)發(fā)送完畢。具體操作步驟如下所示。
修改前的函數(shù)定義體
void USART_SendData(USART_TypeDef* USARTx, u16 Data)
{
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_DATA(Data));
USARTx->DR = (Data & (u16)0x01FF);
}
修改后的函數(shù)定義體
void USART_SendData(USART_TypeDef* USARTx, u16 Data)
{
assert_param(IS_USART_ALL_PERIPH(USARTx));
assert_param(IS_USART_DATA(Data));
USARTx->DR = (Data & (u16)0x01FF);
while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET){} //等待發(fā)送緩沖區(qū)空才能發(fā)送下一個字符
}
可能有人認為,為什么不預(yù)先在庫函數(shù)中處理這個問題,而把解決方法拋給用戶。個人認為ST這么做的原因是:使用發(fā)送中斷功能。
4. TXE和TC標志位詳細說明
在USART的發(fā)送端有2個寄存器,一個是程序可以看到的USART_DR寄存器,另一個是程序看不到的移位寄存器。
對應(yīng)USART數(shù)據(jù)發(fā)送有兩個標志,一個是TXE=發(fā)送數(shù)據(jù)寄存器空,另一個是TC=發(fā)送結(jié)束;當TDR中的數(shù)據(jù)傳送到移位寄存器后,TXE被設(shè)置,此時移位寄存器開始向TX信號線按位傳輸數(shù)據(jù),但因為TDR已經(jīng)變空,程序可以把下一個要發(fā)送的字節(jié)(操作USART_DR)寫入TDR中,而不必等到移位寄存器中所有位發(fā)送結(jié)束,所有位發(fā)送結(jié)束時(送出停止位后)硬件會設(shè)置TC標志。
另一方面,在剛剛初始化好USART還沒有發(fā)送任何數(shù)據(jù)時,也會有TXE標志,因為這時發(fā)送數(shù)據(jù)寄存器是空的。
TXEIE和TCIE的意義很簡單,TXEIE允許在TXE標志為'1'時產(chǎn)生中斷,而TCIE允許在TC標志為'1'時產(chǎn)生中斷。
至于什么時候使用哪個標志,需要根據(jù)你的需要自己決定。但我認為TXE允許程序有更充裕的時間填寫TDR寄存器,保證發(fā)送的數(shù)據(jù)流不間斷。TC可以讓程序知道發(fā)送結(jié)束的確切時間,有利于程序控制外部數(shù)據(jù)流的時序。
2解
stm32 串口發(fā)送數(shù)據(jù)第一字節(jié)丟失
使用stm32f10x調(diào)試串口通訊時,發(fā)現(xiàn)一個出錯的現(xiàn)象,硬件復(fù)位重啟之后,發(fā)送測試數(shù)據(jù)0x01 0x02 0x03 0x04..接收端收到的數(shù)據(jù)為:0x02 0x03 0x04,第一個數(shù)據(jù)丟失。
查閱stm32f10x參考手冊,找到這樣一句話:
TC:發(fā)送完成
當包含有數(shù)據(jù)的一幀發(fā)送完成后,由硬件將該位置位。如果USART_CR1中的TCIE為1,則產(chǎn)生中斷。由軟件序列清除該位(先讀USART_SR,然后寫入USART_DR)。TC位也可以通過寫入0來清除,只有在多緩存通訊中才推薦這種清除程序。
0:發(fā)送還未完成;
1:發(fā)送完成。
注意到這一句:由軟件序列清除該位(先讀USART_SR,然后寫入USART_DR)。 也就是說,要先read USART_SR,然后write USART_DR,才能完成TC狀態(tài)位的清除。而硬件復(fù)位后,串口發(fā)送的首個數(shù)據(jù)之前沒有read SR的操作,是直接write DR,也就是說,TC沒有被清除掉。
硬件復(fù)位后,串口發(fā)送首個數(shù)據(jù)之前,先讀取一下USART_SR,則能夠保證首個數(shù)據(jù)發(fā)送時,不出現(xiàn)覆蓋的情況。當然,也有別的方法,比如先清除TC狀態(tài)位,USART_ClearFlag(USART1, USART_FLAG_TC);或USART1->SR&=~(1<<7);
3解
stm32串口第一字節(jié)丟失問題分析
STM32串口發(fā)送必須先檢測狀態(tài),否則第一個字節(jié)無法發(fā)出,發(fā)送完畢,必須檢測發(fā)送狀態(tài)是否完成,否則,發(fā)送不成功,
使用stm32f10x調(diào)試串口通訊時,發(fā)現(xiàn)一個出錯的現(xiàn)象,硬件復(fù)位重啟之后,發(fā)送測試數(shù)據(jù)0x010x020x030x04..接收端收到的數(shù)據(jù)為:0x020x030x04,第一個數(shù)據(jù)丟失。換成發(fā)送別的數(shù)值的數(shù)據(jù),如0x060x0ff,則接收到0x0ff,0x06丟失。錯誤依舊。
故障排除過程:
1、剛開始懷疑是接收端的錯誤,我是使用電腦串口,運行串口輔助調(diào)試工具接收,換成其他軟件后,發(fā)現(xiàn)故障依舊,而且電腦軟件一直是開啟狀態(tài),不像和電腦軟件有關(guān)。
2、使用單步調(diào)試,單步運行各個發(fā)送指令,都正常。能收到0x010x020x030x04的數(shù)據(jù)。間接的排除了不是電腦軟件的問題,而是其他的錯誤。
3、單步調(diào)試運行雖然正常了,但連續(xù)運行時,錯誤依舊,F(xiàn)在有點摸不到頭緒了,單步運行正常,看起來編程沒有出錯,那故障在哪里呢?測試程序如下
USART_SendData(USART2,0x01);//A
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//B
USART_SendData(USART2,0x02);//C
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
USART_SendData(USART2,0x03);
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
USART_SendData(USART2,0x04);
while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);
4、猜測,也許是因為某個特殊原因,使第二個數(shù)據(jù)覆蓋了首個數(shù)據(jù),使得首個數(shù)據(jù)丟失。假設(shè):在執(zhí)行B指令時,USART的TC狀態(tài)位==SET,那么就會緊接著執(zhí)行C指令,也就有可能發(fā)生數(shù)據(jù)的覆蓋。于是,在A指令前,加入如下指令:USART_ClearFlag(USART2,USART_FLAG_TC);
5、加入上一條指令后,運行,錯誤消失了。說明上一個假設(shè),應(yīng)該是成立的。
6、查閱stm32f10x參考手冊,找到這樣一句話:TC:發(fā)送完成
當包含有數(shù)據(jù)的一幀發(fā)送完成后,由硬件將該位置位。如果USART_CR1中的TCIE為1,則產(chǎn)生中斷。由軟件序列清除該位(先讀USART_SR,然后寫入USART_DR)。TC位也可以通過寫入0來清除,只有在多緩存通訊中才推薦這種清除程序。0:發(fā)送還未完成;1:發(fā)送完成。
7、注意到這一句:由軟件序列清除該位(先讀USART_SR,然后寫入USART_DR)。也就是說,要先readUSART_SR,然后writeUSART_DR,才能完成TC狀態(tài)位的清除。而硬件復(fù)位后,串口發(fā)送的首個數(shù)據(jù)之前沒有readSR的操作,是直接writeDR,也就是說,TC沒有被清除掉。說明第4步的猜測是對的。
8、那么,應(yīng)該把指令A(yù)前面加的USART_ClearFlag(USART2,USART_FLAG_TC);改為USART_GetFlagStatus(USART2,USART_FLAG_TC);,應(yīng)該也能消除錯誤。測試后證實,確實如此,在發(fā)送首個數(shù)據(jù)之前,先讀取一下USART_SR,那么就不會出現(xiàn)首個數(shù)據(jù)丟失的情況了。
9、總結(jié):硬件復(fù)位后,串口發(fā)送首個數(shù)據(jù)之前,先讀取一下USART_SR,則能夠保證首個數(shù)據(jù)發(fā)送時,不出現(xiàn)覆蓋的情況。當然,也有別的方法,比如先清除TC狀態(tài)位,或是,在writeUSART_DR之后,加入一個小延時,讓數(shù)據(jù)發(fā)送完畢,應(yīng)該也能間接排除這個錯誤。
|