MJKDZ PS2手柄控制OskarBot小車(三):STM32接收無線串口模塊的數據並處理

MJKDZ PS2手柄控制OskarBot小車(三):無線串口模塊接收數據並處理

【目錄】

 1、硬件與軟件設計思路

        - 1.1 硬件資源

        - 1.2 STM32串口接收數據的方法

 2、源代碼詳解

        - 2.1 串口中斷接收數據

        - 2.2 PS2手柄處理函數

        - 2.3 串口1發送數據

        - 2.4 運行結果

        - 2.5 主函數與中斷衝突:代碼優化

1、硬件與軟件設計思路

1.1 硬件資源

主控:STM32F103RCT6,主頻 72MHz

串口1:通過CH340G芯片和USB口連接電腦,用於ISP方式採用FlyMCU下載程序,以及打印串口數據在電腦上顯示。

串口2:連接MJKDZ的無線串口模塊,接收MJKDZ PS2無線手柄傳來的數據,傳送給STM32芯片。

1.2  STM32串口接收數據的方法

串口通信基礎知識:

【第20章 USART—串口通訊 - 野火_firege - 博客園】

參考: https://www.cnblogs.com/firege/p/9323114.html

 

STM32F1串口接收數據有多種方法,隨着數據越來越複雜,數據越長,數據量越大,速度越快,接收函數越複雜:

(1)少量數據,直接接收

【STM32串口中斷的4種接收數據的實現方式 - 小胖墩 - CSDN博客】

參考: https://blog.csdn.net/weibo1230123/article/details/80596220

STM32串口發送數據和接收數據方式總結 - qq_35281599的博客 - CSDN博客】

參考: https://blog.csdn.net/qq_35281599/article/details/80299770#

 

(2)有一定格式的數據,按幀接收(選擇此方式)

主要參考這篇文章【STM32串口中斷接收一個完整的數據幀 - cuishumao的專欄 - CSDN博客】

參考: https://blog.csdn.net/cuishumao/article/details/43701789

【串口中怎樣接收一個完整數據包的解析】

參考: https://blog.csdn.net/lpp0900320123/article/details/28239765

其中也有人提到了使用IDLE串口空閒中斷來判斷一幀讀取完畢,對於不定長度的數組,可能有用。

對於我這次傳輸的8個字節的數組,沒有效果,還不如自行判斷接收完畢。

【STM32學習筆記之-串口中斷接收不定數據buff - 一顆偏執的心 - CSDN博客】

參考:  https://blog.csdn.net/sinat_23338865/article/details/76599239

講得詳細,但是少提了一句,初始化的時候,要開啓IDLE空閒中斷。

【教你使用stm32接收串口的一幀數據!】

參考: https://blog.csdn.net/qq_35341807/article/details/79157437

 

(3)消息隊列 & 環形緩衝

【STM32——串口通信升級版(隊列方式) - 血染風采2018 - CSDN博客】

已剪輯自: https://blog.csdn.net/wqx521/article/details/69191025

 

(4)DMA接收

STM32之串口DMA接收不定長數據 - 知乎

已剪輯自: https://zhuanlan.zhihu.com/p/50767564

 

2、源代碼詳解

(1)串口波特率設置:

串口1,設置成9600波特率,才能正常發送數據在電腦上顯示;串口1在下載編譯文件時,可以選擇成115200波特率,加快下載速度,沒有影響。

串口2,設置成9600波特率。

(2)MJKDZ無線串口模塊,博通BK2461,2.4GHz。

串口設置要求:

數據位 8,波特率 9600,校驗位 N,停止位 1,空中速率 1Mbps。

時序要求:

1)上電大約  20ms 後纔可以正常通信。

2)從休眠到喚醒後  2-15ms 內可以接收和發射到數據。

3)從休眠到喚醒後  2ms 後可以發射數據。如果進行休眠工作輪詢,喚醒後延時  2ms 再給串口數據,數據給完後要延時一定時間(因爲無線還沒發完,根據數據長度延時,1 字節  1ms,保證數據的正確性)再進入睡眠,否則數據發不出去。

4)寫程序設置參數時,可以通過檢查返回指令數據來確保設置成功以及等待時間。

2.1  串口中斷接收數據

串口接收的數據格式爲,數組,8個字節的8位數據,首字符0x73,2個按鍵值,4個搖桿值,尾字符0x5A。

中斷處理減少if判斷,僅校驗首字符,存到第8個數就將數組位置清0,重新接收。

兩級緩存,第一個數組用來接收,第二個數組給主函數處理。

u8 USART2_RX_BUF[USART2_BUF_LEN]={0};//緩存串口2接收數據,存入數組
u8 USART2_RX_BUF_BCK[USART2_BUF_LEN]={0};//緩存接收數據,用於主函數處理,傳遞給 psx_buf[8]

//接收狀態
//bit0,        接收到0x73,首校驗
//bit7,        接收到0x5A,尾校驗
//bit1~6,接收到的有效字節數目

u8 U2RecSta = 0;//接收數據計數,接收到第幾個數據了

int USART2_IRQHandler(void)
{
        u8 Clear = Clear; //這種定義方法,用來消除編譯器的*沒有用到*提醒
        u8 UartBuf = 0;//臨時存儲,先校驗,再存入數據
       
        static u8 U2RecCnt = 0;//接收數據計數
       
/*中斷處理*/
        if(USART_GetITStatus(USART2,USART_IT_RXNE) == SET) //中斷+使能標誌,用於中斷函數內
        {
                USART_ClearFlag(USART2, USART_FLAG_RXNE); //接收數據寄存器非空標誌位
                USART_ClearITPendingBit(USART2, USART_IT_RXNE);//清中斷接收標誌
               
                UartBuf = USART_ReceiveData(USART2);//讀串口2接收緩存,臨時存儲
                                
                USART2_RX_BUF[U2RecCnt] = UartBuf;//所有數據均接收
               
                if(U2RecCnt==0)//幀頭0x73,只校驗首字符
                {
                        U2RecCnt = (0x73!=UartBuf)?0:U2RecCnt+1;//若爲真(不等),則U2RecCnt =0;若相等,則計數+1
                }
                else if(U2RecCnt > 0)//值value,不是首字符,當數據處理
                {
                        U2RecCnt++;
                        if(U2RecCnt == 8)//計數到第8個數,清零
                        {
                                U2RecCnt=0;
                                //uart1_send_nbyte(USART2_RX_BUF,8);//
                                memcpy(USART2_RX_BUF_BCK,USART2_RX_BUF,8);//二級緩存,第二個數組用於給主函數處理
                                U2RecSta=1;//標記狀態,一幀數據接收完,主循環處理
                        }        
                }
        }               
        return 0;               
}       

       

 

沒什麼用的溢出中斷,空閒中斷,以及各種清除異常中斷

//清除各種異常中斷
        if(USART_GetFlagStatus(USART2, USART_FLAG_PE) == SET)//奇偶錯誤標誌位
        {
                Clear =        USART_ReceiveData(USART2);  //接收到數據不處理,拋棄
                USART_ClearFlag(USART2, USART_FLAG_PE);
        }

if(USART_GetFlagStatus(USART2, USART_FLAG_ORE) == SET)//溢出錯誤標誌位
        {
                Clear =        USART_ReceiveData(USART2);  //接收到數據不處理,拋棄
                USART_ClearFlag(USART2, USART_FLAG_ORE);
        }

if(USART_GetFlagStatus(USART2, USART_FLAG_NE) == SET)//噪聲錯誤標誌
        {
                Clear =        USART_ReceiveData(USART2);  //接收到數據不處理,拋棄
                USART_ClearFlag(USART2, USART_FLAG_NE);
        }

if(USART_GetFlagStatus(USART2, USART_FLAG_FE) == SET)//幀錯誤標誌位
        {
                Clear =        USART_ReceiveData(USART2); 

}

 

if(USART_GetITStatus(USART2,USART_IT_IDLE) == SET)//空閒中線標誌位,一幀數據
        {
                Clear = USART2->SR; //讀SR寄存器
                Clear = USART2->DR; //讀DR寄存器(先讀SR再讀DR,就是爲了清除IDLE中斷)
        }

 

2.2 PS2手柄處理函數

校驗一幀接收完畢(數組長度8個字節),賦值給psx_buf[8].

u8 psx_buf[8]={0};        //存儲手柄按鍵信息

//接收手柄按鍵數據
void handle_button(void)
{
        if(U2RecSta ==1)//幀接收完畢標誌
        {
                U2RecSta = 0;//接收狀態計數,清零
                //uart1_send_nbyte(USART2_RX_BUF_BCK,8);//8個字節數據
               
                if(USART2_RX_BUF_BCK[0] == 0x73 && USART2_RX_BUF_BCK[7] == 0x5A)//首尾字符校驗 
                {  
                        memcpy(psx_buf,USART2_RX_BUF_BCK,8*sizeof(u8));//校驗完成,賦值給數組psx_buf[8]

//Test,8位數據接收OK,LED點亮一次
                        LED_ON;
                        delay_ms(90);
                        LED_OFF;
                }       
        }
       
        static unsigned char psx_button_bak[2] = {0};                       
       
        if((psx_button_bak[0] == psx_buf[1]) && (psx_button_bak[1] == psx_buf[2]))
        {               
        }
        else if((psx_buf[0] == 0)&& (psx_buf[1] == 0))//20ms自動查詢一次,handle_ps2()函數,更新psx_buf[]數組爲0
        {
                //uart1_send_nbyte(psx_buf,8);//串口1發送 按鍵數據(全0)
                //uart1_send_str(psx_buf);
               
                parse_psx_buf(psx_buf+1, psx_buf[0]);//按鍵的首地址psx_buf+1,按鍵模式類型psx_buf[0](數組順序與PSX手柄不同)
                psx_button_bak[0] = psx_buf[1];
                psx_button_bak[1] = psx_buf[2];
        }
        else if((psx_buf[0] == 0x73)&& (psx_buf[7] == 0x5A))//串口2中斷,無線串口模塊,更新psx_buf[]數組爲按鍵與搖桿數據
        {
                uart1_send_nbyte(psx_buf,8);//串口1發送,發送緩存數組,PSX按鍵值
               
                //Test,按鍵響應OK
                if(psx_buf[1] == 0x7F && psx_buf[2] == 0xFF)//第一組按鍵,左鍵按下,蜂鳴器響一聲                               
                {
                BEEP_ON;
                delay_ms(90);
                BEEP_OFF;
                }
                else if(psx_buf[1] == 0xFF && psx_buf[2] == 0x7F)//第二組按鍵,方形鍵按下,蜂鳴器響兩聲                               
                {                       
                //Test
                BEEP_ON;
                delay_ms(90);
                BEEP_OFF;
                delay_ms(90);       
                BEEP_ON;
                delay_ms(90);
                BEEP_OFF;
                }
               
                parse_psx_buf(psx_buf+1, psx_buf[0]);
                psx_button_bak[0] = psx_buf[1];
                psx_button_bak[1] = psx_buf[2];
        }
               
        if(psx_buf[0] == PS2_LED_GRN)
        {
//                joy_left_pwm = 0;
//                joy_right_pwm = 0;
        }
        else if(psx_buf[1]

 

2.3 串口1發送數據

 

void tb_usart1_send_nbyte(u8 *Data, u16 size)
{
        u16 i = 0;       
       
        //發送首個數據之前,先讀取一下USART_SR,那麼就不會出現首個數據丟失
        for(i=0; i<size; i++)
        {
        //發送一串數據的邏輯必須按照先檢測TC,再發送字符的順序進行
        //監測發送狀態位用TC而儘量不要使用TXE,TC是監測的是否數據發送完,而TXE只是監測緩存區是否移位到移位寄存器而已
                while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
                USART_SendData(USART1, Data[i]);
        }
        //提升代碼健壯性,避免其他部分代碼出現類似不檢測發送的問題
               while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET) ;       
        return;
}

 

2.4 運行結果

備註:(1PS2處理函數中有些會調用串口1發送字符 uart1_send_str()

(2)優化:主函數與終端

 

2.5 主函數與中斷衝突:代碼優化

就放上來的這段代碼,有個大問題。當主函數在處理數據時,中斷可能還在發生。所以你處理的數據,可能一半是上一次發的,一半是這一次發的。

這種問題有兩個方案:

1、做個全局互鎖開關,通訊完畢了打開開關,只要開關開着,就不能再接收數據。主函數判斷開關,如果開着就處理數據,處理完了就關掉開關。

2、做兩個緩存數組,一個用於實時接收,接收完畢了用memcpy函數拷貝到另一個數組。主函數永遠只能處理另一個數組的數據。如果緩存太大怕memcpy函數執行太久影響中斷,就做指針切換也行。接收完了指針就切換過去,主函數永遠通過這個指針去取數組。

補充說明一下,第二個方案只適合於主函數運行足夠快,能夠實時處理完每一幀數據的情況,即,在處理數據時,那邊又開始接受數據,這沒問題,但是在接收完成數據開始memcpy時,就得考慮主函數是否已經處理完了。如果這個無法保證,就必定會有衝突,只能使用第一個方案,放棄某些幀。如果主函數輪詢太慢,可以考慮設置軟中斷,接收完一幀後觸發軟中斷去處理數據,這樣最節約時間。

來自 <http://www.openedv.com/thread-103476-1-1.html>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章