正点原子STM32串口通讯实验详解
這幾天看完了正點原子STM32的串口通訊部分的內(nèi)容,總感覺很多東西似是而非,前后花了好幾天研究了下,這篇博客很多內(nèi)容是從其他博客上整理來的,并非完全原創(chuàng),由于前后查了幾天好多篇博客,摘抄的誰的也不好找了,看到的可以提醒一下,只希望自己整理的內(nèi)容能幫到其他的初學(xué)者。
經(jīng)提醒第二部分來源于這篇博客?,感興趣的可以去原文看看。
1、實驗內(nèi)容梳理
首先結(jié)合串口調(diào)試助手對實驗進(jìn)行說明,以便后續(xù)結(jié)合代碼熟悉整個流程。整個實驗其實就是通過串口調(diào)試助手向單片機發(fā)送數(shù)據(jù),然后單片機將接收到的數(shù)據(jù)返回給上位機并加以顯示。
簡單來串口調(diào)試助手說其實就是用于上位機和下位機通信用的一個橋梁軟件,功能主要有兩個這也是本實驗的兩個步驟:
關(guān)于串口調(diào)試助手,還應(yīng)知道:
- 發(fā)送英文字符需要用一個字符即8位,發(fā)送漢字需要兩個字符即16位,如上圖,發(fā)送漢字“宋”實際是發(fā)送“CB(1100 1011)CE(1100 1110)”而發(fā)送英文字符S實際是發(fā)送“53(0101 0011)”,本質(zhì)上沒有太大區(qū)別;
 - 勾選了下方“發(fā)送新行”后,XCOM就會再你輸入的需要發(fā)送的數(shù)據(jù)后自動加上一個回車(0X0D+0X0A),如果不勾選則我們在手動輸入完“宋S”后還需敲一個回車鍵只有這樣點擊發(fā)送后,調(diào)試助手上方窗體才能將其顯示,這是因為我們在程序的串口中斷中自定義了一個數(shù)據(jù)接收協(xié)議,即只有當(dāng)接受的數(shù)據(jù)以回車結(jié)尾(0X0D+0X0A),串口才認(rèn)可數(shù)據(jù)接受完畢。
 
2、對于串口中斷函數(shù)(自定義數(shù)據(jù)接收協(xié)議)的理解
正點原子的例程中通過語句USART_ITConfig(USART1, USART_IT_RXNE, ENABLE)開啟相關(guān)中斷,當(dāng)讀數(shù)據(jù)寄存器非空,即單片機一接收到數(shù)據(jù)時,便會觸發(fā)串口1的中斷函數(shù)。
改協(xié)議的核心是定義了一個16位的變量USART_RX_STA,該變量的0-13位用于存儲接收到的數(shù)據(jù),最后的14、15兩位作用在于,當(dāng)14、15位依次接收到da0x0d和0x0a時,依次將這兩位置1,作為判斷數(shù)據(jù)是否接收完的標(biāo)志位。
以下是我看到的一個注釋比較詳細(xì)的代碼,和一個實例,結(jié)合兩者就可以很好的理解這個過程和代碼,
void USART1_IRQHandler(void) //串口1中斷服務(wù)程序{u8 Res; //定義unsigned char型字符Resif(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷(接收到的數(shù)據(jù)必須是0x0d 0x0a結(jié)尾)//這里判斷發(fā)送接收完成的依據(jù)就是串口數(shù)據(jù)0x0d 0x0a,//0x0d是CR(carriage return)回車的意思,光標(biāo)回到最左邊,//0x0a是LF(line feed)換行的意思,光標(biāo)到達(dá)下一行,//但是在PC上回車和換行是在一起的就是按下回車按鍵//當(dāng)然可以更改程序使用其他進(jìn)行判斷例如使用0x2a也就是*進(jìn)行結(jié)束判斷{Res =USART_ReceiveData(USART1);//(USART1->DR); //讀取接收到的數(shù)據(jù),存放到變量Res中if((USART_RX_STA&0x8000)==0)//判斷接收是否未完成//接收完成未清除標(biāo)志位,還是會不斷進(jìn)入到接收中斷,所以使用標(biāo)志進(jìn)行判斷,//當(dāng)接收完成便不會跳入到判斷,從而不執(zhí)行任何指令,空等待//使用條件判斷是否已經(jīng)接收完數(shù)據(jù),這里判斷接收完的依據(jù)就是收到了0x0a;//具體判斷在后面{if(USART_RX_STA&0x4000)//如果接收到了0x0d,那么再進(jìn)一步執(zhí)行是否接收到0x0a的判斷{if(Res!=0x0a)USART_RX_STA=0;//沒有接收到0x0a那么說明,數(shù)據(jù)未正確傳輸或者接收錯誤,重新開始判斷,//但是這里沒有將接收到的數(shù)據(jù)進(jìn)行清空,也沒有退出接收中斷,此程序只是從頭開始執(zhí)行接收判斷else USART_RX_STA|=0x8000; //接收完成了,收到了0x0a那么標(biāo)志位USART_RX_STA將會變成0x8000,將不再進(jìn)行數(shù)據(jù)檢測與存儲}else //還沒收到0X0D,說明數(shù)據(jù)還未發(fā)送結(jié)束繼續(xù)進(jìn)行數(shù)據(jù)的檢測與存儲{ if(Res==0x0d)USART_RX_STA|=0x4000;//收到了數(shù)據(jù)0x0d,標(biāo)志位USART_RX_STA變成0x4000else{//如果沒有接收到數(shù)據(jù)0x0d,執(zhí)行判斷是否存儲數(shù)組已滿,已滿則重新開始接收USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;//將接收到的數(shù)據(jù)寫進(jìn)數(shù)組,標(biāo)志位USART_RX_STA與上0X3FFF清除前兩位以防止標(biāo)志位與8000和4000沖突USART_RX_STA++;//數(shù)組地址加一,向后排if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收數(shù)據(jù)錯誤,超出數(shù)組大小,又開始接收向數(shù)組重新寫 } }} } }假設(shè)我們發(fā)送的數(shù)據(jù)是“abcd”經(jīng)過串口調(diào)試助手加上0X0D+0X0A后發(fā)送給單片機,即單片機要接受的數(shù)據(jù)是“abcd”+“0X0D+0X0A”
(1)當(dāng)接收到“a”時讀寄存器非空,RXNE為1,第一次進(jìn)入串口中斷處理函數(shù),我們先判斷是否接是因為USART1接受到了數(shù)據(jù)產(chǎn)生的中斷,如果是,則將USART1接受到的一位數(shù)據(jù)“a”存入變量Res里(Res = “a”)
if(USART_GetFlagStatus(USART1, USART_IT_RXNE) != RESET)(2)然后我們來判斷接收的一系列數(shù)據(jù)是否沒接收完(即)。(當(dāng)然沒有啦,我們還有b、c、d三個數(shù)據(jù)沒有接收)。這個時候呢,USART_RX_STA——這個在全部函數(shù)之間實現(xiàn)消息傳遞的變量的值仍然為0,和0x8000相與以后為0,那么執(zhí)行該if語句的內(nèi)層函數(shù)。
if((USART_RX_STA&0x8000)==0) //接收未完成(3)進(jìn)入該if語句的內(nèi)層語句后判斷語句如下,這里判斷USART_RX_STA的第14位是否為1,如果我們接收到了回車,即0x0d那么USART_RX_STA的第14位會置1。在我們接收第一個數(shù)據(jù)a時USART_RX_STA當(dāng)然還為0,(我們后面的的b、c、d、3個數(shù)據(jù)還都沒接收了,當(dāng)然不會收到0x0d)USART_RX_STA和0x400相與為0,該判斷語句就為假,執(zhí)行下面的else語句。
if(USART_RX_STA&0x4000)//接收到了0x0d(4)該else語句的內(nèi)層語句是一個if-else語句
if(Res==0x0d)USART_RX_STA|=0x4000;這里我就不再贅述,我們接收的是數(shù)據(jù)a,還沒有接收到0x0d,執(zhí)行else語句。
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; // 0x3ff = 0011 1111 1111 1111USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收數(shù)據(jù)錯誤,重新開始接收USART_RX_STA的bit0~bit13代表的是接收到的有效數(shù)據(jù)個數(shù),這里USART_RX_STA值仍為0,USART_RX_STA & 0X3FFF = 0 ,然后USART_RX_BUF[USART_RX_STA&0X3FFF]=Res,意思就是將Res里的數(shù)據(jù)存放到USART_RX_BUF[0]里了,并且USART_RX_STA自增1。
此時USART_RX_STA = 1這樣在接收到下一個數(shù)據(jù)b后USART_RX_STA&0X3FFF = 1,將b存入到了USART_RX_BUF[1]里,一直循環(huán)下去,直到我們接收到了0x0d(Res = 0x0d)。
我們可以從上面程序里找到如下代碼:
else //還沒收到0X0D{ if(Res==0x0d)USART_RX_STA|=0x4000; //再次判斷這次接收到的是不是0x0d當(dāng)接收到0x0d并且程序執(zhí)行到這一步一時USART_RX_STA = 4,此時該if語句成立,執(zhí)行USART_RX_STA|=0x4000,即0000 0000 0000 0100 | 0100 0000 0000 0000 = 0100 0000 0000 0100 ,我們可以清晰的看到bit13~0位是4,代表接收到了4個數(shù)據(jù)(a,b,c,d),第14位為1,是因為接收到了數(shù)據(jù)0x0d,也和最上面給的表對上了,然后程序向下執(zhí)行,接收到了0x0d(回車),那下一個就是接收0x0a(換行)了,Res = 0x0a:?????????
if(USART_RX_STA&0x4000)//接收到了0x0d 0100 0000 0000 0000{if(Res!=0x0a)USART_RX_STA=0;//接收錯誤,重新開始--接收到了0X0d但是沒有接受到0x0aelse USART_RX_STA|=0x8000; //接收完成了--0x0d后面是0x0a }這里第一個判斷語句if(USART_RX_STA&0x4000),當(dāng)然為真,因為Res = 0x0a,那么執(zhí)行else語句USART_RX_STA|=0x8000,即0100 0000 0000 0100 | 1000 0000 0000 0000 = 1100 0000 0000 0100。到了這一步,就說明這一串?dāng)?shù)據(jù)已經(jīng)完完全全的接收完了USART_RX_STA = 1100 0000 0000 0100,最高位為1:代表接收到了0x0a,第十四位為1:代表接收到了0x0d,第0位到第13位為4,代表接收到了4位有效數(shù)據(jù)(a、b、c、d)
3、對main函數(shù)的理解
main函數(shù)如下,大致的執(zhí)行流程為:
int main(void) { u8 t;u8 len; u16 times=0; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//設(shè)置系統(tǒng)中斷優(yōu)先級分組2delay_init(168); //延時初始化 uart_init(115200); //串口初始化波特率為115200LED_Init(); //初始化與LED連接的硬件接口 while(1){if(USART_RX_STA&0x8000){ len=USART_RX_STA&0x3fff;//得到此次接收到的數(shù)據(jù)長度printf("\r\n您發(fā)送的消息為:\r\n");while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);for(t=0;t<len;t++){USART_SendData(USART1, USART_RX_BUF[t]); //向串口1發(fā)送數(shù)據(jù)while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發(fā)送結(jié)束}printf("\r\n\r\n");//插入換行USART_RX_STA=0;}else{times++;if(times%300==0){printf("\r\nALIENTEK 探索者STM32F407開發(fā)板 串口實驗\r\n");printf("正點原子@ALIENTEK\r\n\r\n");}if(times%200==0)printf("請輸入數(shù)據(jù),以回車鍵結(jié)束\r\n"); if(times%30==0)LED0=!LED0;//閃爍LED,提示系統(tǒng)正在運行.delay_ms(10); }} }注意:重定向的printf()函數(shù)本質(zhì)上還是通過USART_SendData()向上位機發(fā)送數(shù)據(jù),且此處發(fā)送的“\r\n”與中斷函數(shù)里的需要作為接受完成標(biāo)志位的“\r\n”(0X0D+0X0A),只是單純的表示換行的轉(zhuǎn)義字符,電腦上位機接收到后會將光標(biāo)下移兩行,視覺上就是空一行顯示在串口調(diào)試助手上。
4、實驗異?,F(xiàn)象及解決辦法
上面有提到過圖片標(biāo)號1的區(qū)域出現(xiàn)了異常實驗現(xiàn)象,即單片機利用printf("\r\n您發(fā)送的消息為:\r\n")語句顯示的結(jié)果中語句末尾的“\r\n”并沒有起到作用,即讓光標(biāo)移至下一行后再顯示“逆光”。
這種現(xiàn)象的原因是因為printf語句中的“\r\n”發(fā)送未完成,寄存器又被后面要發(fā)送的數(shù)據(jù)覆蓋,解決方法就是在USART_SendData(USART1, USART_RX_BUF[t]);前面在多加一句 while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發(fā)送結(jié)束
for(t=0;t<len;t++){while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);USART_SendData(USART1, USART_RX_BUF[t]); //向串口1發(fā)送數(shù)據(jù)while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發(fā)送結(jié)束}修改后實驗現(xiàn)象恢復(fù)正常如下:
我嘗試過在printf語句的后面利用延時的方式解決問題,但是只有第一次能夠正常顯示,后面再次發(fā)送,現(xiàn)實的數(shù)據(jù)就會存在亂碼的問題。
5、USART-FLAG-TXE與USART-FLAG-TC標(biāo)志位
我覺得這兩個標(biāo)志位的使用應(yīng)當(dāng)嚴(yán)格區(qū)分開,當(dāng)個附加知識記錄一下:
USART-FLAG-TXE發(fā)送緩沖區(qū)空標(biāo)志:說明可以往數(shù)據(jù)寄存器寫入數(shù)據(jù)了,但并不代碼數(shù)據(jù)發(fā)送完成了。
USART-FLAG-TC發(fā)送完成標(biāo)志:這個才是代表USART在緩沖區(qū)的數(shù)據(jù)發(fā)送完成了,即從機接收到了數(shù)據(jù)。
這兩個標(biāo)志的區(qū)別在于:它們分別表示數(shù)據(jù)在發(fā)送過程中,在兩個不同的階段中的完成情況.TXE表示數(shù)據(jù)被從發(fā)送緩沖區(qū)中取走,轉(zhuǎn)移到的移位寄存器中,此時發(fā)送緩沖是空的,可以向其中補充新的數(shù)據(jù)了。而 TC則表示最后放入發(fā)送緩沖區(qū)的數(shù)據(jù)已經(jīng)完成了從移位寄存器向發(fā)送信號線Tx上的轉(zhuǎn)移。所以,判定數(shù)據(jù)最終發(fā)送完成的標(biāo)志是TC,而不是IXE.
總結(jié)
以上是生活随笔為你收集整理的正点原子STM32串口通讯实验详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 《DSP using MATLAB》示例
 - 下一篇: YUV图像