|
這段代碼實(shí)現(xiàn)了一個基于狀態(tài)機(jī)的串口數(shù)據(jù)包解析器,結(jié)構(gòu)基本正確,但存在幾個嚴(yán)重且典型的問題,在實(shí)際項(xiàng)目中可能導(dǎo)致數(shù)據(jù)錯亂、死循環(huán)甚至程序崩潰。下面逐一分析。 1. 代碼功能簡述協(xié)議格式:幀頭 0x5A + 長度(len) + 數(shù)據(jù)(len字節(jié)) + 校驗(yàn)和(累加和) 校驗(yàn)方式:累加和(包含幀頭、長度、全部數(shù)據(jù)字節(jié)) 狀態(tài)機(jī):WAIT_HEADER → WAIT_LEN → WAIT_DATA → WAIT_CHECKSUM → 回到 WAIT_HEADER 成功解析后:發(fā)送 buf_data[0] 和 buf_data[1](假定長度至少為2)
2. 存在的關(guān)鍵問題① 主循環(huán)與中斷完全無同步 —— 數(shù)據(jù)競爭 / 重復(fù)解析 / 丟字節(jié)if(RI){ RI=0; tempsbuf=SBUF;}// main loopif(parser_input_byte(&p,tempsbuf)){ ...}
tempsbuf 在中斷中被寫入,主循環(huán)隨時讀取,沒有任何互斥或標(biāo)志位。 主循環(huán)運(yùn)行極快,會在同一個字節(jié)上反復(fù)調(diào)用 parser_input_byte,導(dǎo)致狀態(tài)機(jī)被多次喂入相同數(shù)據(jù),完全打亂解析邏輯。 同時,當(dāng)新字節(jié)到來時,tempsbuf 可能正在被主循環(huán)讀取,造成數(shù)據(jù)競爭。
正確做法:使用 volatile 標(biāo)志位,中斷置位,主循環(huán)檢測后清除并處理。 volatile bit hasNewByte = 0;// 中斷中:tempsbuf = SBUF;hasNewByte = 1;// 主循環(huán):if(hasNewByte) { hasNewByte = 0; if(parser_input_byte(&p, tempsbuf)) { ... }}
② 數(shù)據(jù)緩沖區(qū) buf_data[3] 沒有長度保護(hù),極易溢出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; // 長度非法,復(fù)位 break; } ...
③ 同步恢復(fù)能力極差,錯誤字節(jié)極易造成永久失步當(dāng)前狀態(tài)機(jī)在 WAIT_HEADER 下只認(rèn) 0x5A,任何非幀頭字節(jié)都被直接丟棄。 一旦因干擾或丟字節(jié)導(dǎo)致狀態(tài)機(jī)偏離,數(shù)據(jù)區(qū)出現(xiàn)的 0x5A 會被誤認(rèn)為幀頭,從而產(chǎn)生一整幀錯誤解析,且無校驗(yàn)失敗后的顯式復(fù)位(雖然校驗(yàn)失敗后會回到 WAIT_HEADER,但已錯誤地消費(fèi)了若干字節(jié),后續(xù)幀頭完全錯位)。 更健壯的做法:增加幀尾、轉(zhuǎn)義機(jī)制,或超時復(fù)位。
④ parser_init(&p) 位置錯誤 —— C 語言不允許在文件作用域執(zhí)行函數(shù)Parser p;parser_init(&p); // ← 這行不能在全局作用域調(diào)用void main() { ... }
這種寫法在某些編譯器下可能被當(dāng)作聲明甚至報錯。必須移到 main 內(nèi)部: void main() { parser_init(&p); while(1) { ... }}
⑤ 校驗(yàn)和在失敗后未清除,且校驗(yàn)包含幀頭本身p->checksum 初始為 0,在收到 0x5A 時置為 0x5A,然后累加長度和數(shù)據(jù)。最后與收到的校驗(yàn)字節(jié)比較。 這要求發(fā)送方的校驗(yàn)和也必須包含幀頭,否則永遠(yuǎn)校驗(yàn)失敗。這一點(diǎn)不常見(通常校驗(yàn)只覆蓋長度+數(shù)據(jù)),需確認(rèn)協(xié)議雙方一致。 校驗(yàn)失敗后,p->checksum 未清零,下次 WAIT_HEADER 會重新賦值為 0x5A,所以無累積問題,但邏輯上最好在校驗(yàn)分支內(nèi)做明確清理以增強(qiáng)可讀性。
⑥ main 函數(shù)缺少串口初始化⑦ 發(fā)送函數(shù) SendByte 未定義,且 while(1) 內(nèi)無其他保護(hù) 3. 改進(jìn)后的代碼框架(關(guān)鍵部分)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(); // 假設(shè)已實(shí)現(xiàn) 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;}
總結(jié)代碼展現(xiàn)了狀態(tài)機(jī)解析思想,但缺少實(shí)時系統(tǒng)中的同步機(jī)制、無邊界檢查、初始化位置錯誤,是典型的“能跑但一有干擾就崩”的嵌入式代碼。上述幾點(diǎn)修正后,才能在實(shí)際串口通信中穩(wěn)定工作。
|