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 運行結果
備註:(1)PS2處理函數中有些會調用串口1發送字符 uart1_send_str();
(2)優化:主函數與終端
2.5 主函數與中斷衝突:代碼優化
就放上來的這段代碼,有個大問題。當主函數在處理數據時,中斷可能還在發生。所以你處理的數據,可能一半是上一次發的,一半是這一次發的。
這種問題有兩個方案:
1、做個全局互鎖開關,通訊完畢了打開開關,只要開關開着,就不能再接收數據。主函數判斷開關,如果開着就處理數據,處理完了就關掉開關。
2、做兩個緩存數組,一個用於實時接收,接收完畢了用memcpy函數拷貝到另一個數組。主函數永遠只能處理另一個數組的數據。如果緩存太大怕memcpy函數執行太久影響中斷,就做指針切換也行。接收完了指針就切換過去,主函數永遠通過這個指針去取數組。
補充說明一下,第二個方案只適合於主函數運行足夠快,能夠實時處理完每一幀數據的情況,即,在處理數據時,那邊又開始接受數據,這沒問題,但是在接收完成數據開始memcpy時,就得考慮主函數是否已經處理完了。如果這個無法保證,就必定會有衝突,只能使用第一個方案,放棄某些幀。如果主函數輪詢太慢,可以考慮設置軟中斷,接收完一幀後觸發軟中斷去處理數據,這樣最節約時間。
|
來自 <http://www.openedv.com/thread-103476-1-1.html>