標題: 串行通信數據包解析不正常 [打印本頁]
作者: samxon 時間: 2026-6-6 17:01
標題: 串行通信數據包解析不正常
通過PC機串口發送hex數據 5A 02 01 02 5F。返回給PC的數據不穩定,有時是5A 02, 有時是5A 01, 并且每次都返回四次。請大家幫忙分析一下原因。拋磚引玉。謝謝。
unsigned char tempsbuf;
typedef enum{
WAIT_HEADER,
WAIT_LEN,
WAIT_DATA,
WAIT_CHECKSUM
} ParserState;
typedef struct Parser{
ParserState state; //當前狀態
unsigned char len; //數據長度
unsigned char buf_data[3]; //數據緩沖區
unsigned char index; //數據接收計數
unsigned char checksum; //檢驗和
} Parser;
void parser_init(Parser *p){
p->state=WAIT_HEADER;
p->len=0;
p->index=0;
p->checksum=0;
}
int parser_input_byte(Parser *p,unsigned char byte){
switch(p->state){
//1、等待幀頭0x5a
case WAIT_HEADER:
if(byte==0x5A){
p->checksum=byte; //累加校驗
p->state=WAIT_LEN;
}
break;
//2、等待長度
case WAIT_LEN:
p->len=byte;
p->checksum+=byte;
p->index=0;
p->state=WAIT_DATA;
break;
//3、等待數據
case WAIT_DATA:
p->buf_data[p->index++]=byte;
//p->index++;
p->checksum+=byte;
if(p->index>=p->len){p->state=WAIT_CHECKSUM;}
break;
//4、等待校驗
case WAIT_CHECKSUM:
p->state=WAIT_HEADER; //解析完成回到初始狀態
if(byte==p->checksum){
return 1;
}
break;
}
return 0;
}
void Uart() interrupt 4 //串行中斷函數
{
if(TI)
{
TI=0; //清楚TI傳送完成標記
}
if(RI){
RI=0;
tempsbuf=SBUF;
}
Parser p;
parser_init(&p);
void main(){
while(1)
{
if(parser_input_byte(&p,tempsbuf)){
SendByte(p.buf_data[0]);
SendByte(p.buf_data[1]);
}
}
}
作者: lkc8210 時間: 2026-6-8 13:16
啥芯片?晶振多少?串口初始化代碼呢?
作者: 人中狼 時間: 2026-6-8 16:02
接收數據的判斷應該在串口接收中斷里判斷
作者: WL0123 時間: 2026-6-8 20:00
樓主說明下通訊協議
作者: joyb 時間: 2026-6-9 08:38
串口初始化,誤差大
作者: joyb 時間: 2026-6-9 08:47
這段代碼實現了一個基于狀態機的串口數據包解析器,結構基本正確,但存在幾個嚴重且典型的問題,在實際項目中可能導致數據錯亂、死循環甚至程序崩潰。下面逐一分析。
1. 代碼功能簡述協議格式:幀頭 0x5A + 長度(len) + 數據(len字節) + 校驗和(累加和)
校驗方式:累加和(包含幀頭、長度、全部數據字節)
狀態機:WAIT_HEADER → WAIT_LEN → WAIT_DATA → WAIT_CHECKSUM → 回到 WAIT_HEADER
成功解析后:發送 buf_data[0] 和 buf_data[1](假定長度至少為2)
2. 存在的關鍵問題① 主循環與中斷完全無同步 —— 數據競爭 / 重復解析 / 丟字節if(RI){ RI=0; tempsbuf=SBUF;}// main loopif(parser_input_byte(&p,tempsbuf)){ ...}
tempsbuf 在中斷中被寫入,主循環隨時讀取,沒有任何互斥或標志位。
主循環運行極快,會在同一個字節上反復調用 parser_input_byte,導致狀態機被多次喂入相同數據,完全打亂解析邏輯。
同時,當新字節到來時,tempsbuf 可能正在被主循環讀取,造成數據競爭。
正確做法:使用 volatile 標志位,中斷置位,主循環檢測后清除并處理。
volatile bit hasNewByte = 0;// 中斷中:tempsbuf = SBUF;hasNewByte = 1;// 主循環:if(hasNewByte) { hasNewByte = 0; if(parser_input_byte(&p, tempsbuf)) { ... }}
② 數據緩沖區 buf_data[3] 沒有長度保護,極易溢出unsigned char buf_data[3;...p->buf_data[p->index++ = byte;if(p->index >= p->len) { p->state = WAIT_CHECKSUM; }
case WAIT_LEN: p->len = byte; if(p->len > sizeof(p->buf_data)) { p->state = WAIT_HEADER; // 長度非法,復位 break; } ...
③ 同步恢復能力極差,錯誤字節極易造成永久失步④ parser_init(&p) 位置錯誤 —— C 語言不允許在文件作用域執行函數Parser p;parser_init(&p); // ← 這行不能在全局作用域調用void main() { ... }
這種寫法在某些編譯器下可能被當作聲明甚至報錯。必須移到 main 內部:
void main() { parser_init(&p); while(1) { ... }}
⑤ 校驗和在失敗后未清除,且校驗包含幀頭本身p->checksum 初始為 0,在收到 0x5A 時置為 0x5A,然后累加長度和數據。最后與收到的校驗字節比較。
這要求發送方的校驗和也必須包含幀頭,否則永遠校驗失敗。這一點不常見(通常校驗只覆蓋長度+數據),需確認協議雙方一致。
校驗失敗后,p->checksum 未清零,下次 WAIT_HEADER 會重新賦值為 0x5A,所以無累積問題,但邏輯上最好在校驗分支內做明確清理以增強可讀性。
⑥ main 函數缺少串口初始化⑦ 發送函數 SendByte 未定義,且 while(1) 內無其他保護
3. 改進后的代碼框架(關鍵部分)volatile bit hasNewByte = 0;unsigned char tempsbuf;// ... Parser 定義、parser_init、parser_input_byte(加入長度檢查) ...void Uart() interrupt 4 { if(TI) { TI = 0; } if(RI) { RI = 0; tempsbuf = SBUF; hasNewByte = 1; }}void main() { Parser p; parser_init(&p); Uart_Init(); // 假設已實現 while(1) { if(hasNewByte) { hasNewByte = 0; if(parser_input_byte(&p, tempsbuf)) { SendByte(p.buf_data[0); SendByte(p.buf_data[1); } } }}
并且在 WAIT_LEN 中添加:
if(byte > sizeof(p->buf_data)) { p->state = WAIT_HEADER; break;}
總結代碼展現了狀態機解析思想,但缺少實時系統中的同步機制、無邊界檢查、初始化位置錯誤,是典型的“能跑但一有干擾就崩”的嵌入式代碼。上述幾點修正后,才能在實際串口通信中穩定工作。
作者: samxon 時間: 2026-6-9 09:46
對的。在接收中斷內判定是好很多,但還是有誤碼問題。
| 歡迎光臨 (http://www.denmoz.com/bbs/) |
Powered by Discuz! X3.1 |