7天快速入門Zigbee:自定義通信協議設計基礎
點擊左上角的
“關注”
,定期更新Zigbee最新資訊,總有你想要的信息!
目錄
- 概述
- 串口通信的數據幀結構
- 基於有限狀態機的幀同步方法
1. 概述
串口通信是目前單片機和DSP等嵌入式系統之間,以及嵌入式系統與 PC 機或無線模塊之間的一種非常重要且普遍使用的通信方式。在嵌入式系統的硬件結構中,通常只有一個8位或16位的CPU,不僅要完成主流程的工作,同時還要處理隨時發生的各種中斷,因而嵌入式系統中的串口通信程序設計與PC機有很大的不同。若嵌入式系統中的中斷服務子程序在系統運行過程中佔用了較多的時間,就有可能在中斷服務子程序正運行時,又產生一個同類型或其它類型的中斷,從而造成主程序得不到執行或後續中斷數據丟失等問題。所以嵌入式系統中的串口通信雖然看似簡單,但其中仍有許多問題值得研究,例如串口通信過程中的幀同步問題。
本文推薦一種“基於有限狀態機的幀同步方法”
給大家,可以快速有效地實現了串口通信的同步,而且程序結構清晰,便於維護,也易於向其它的串口通信協議移植。
2. 串口通信的數據幀結構
現在的單片機系統中,往往需要由多個獨立的控制模塊來共同完成功能。不同模塊之間可以通過串口,RS232,SPI等不同的通信接口來進行通信,在通信過程中我們可以加入一些通信協議,以提高系統的可靠性和穩定性;而要完成特定的通信協議,就得有一定的同步機制。下面介紹一下簡化的通信數據幀結構,以便分析說明嵌入式系統通信過程中的幀同步方法。
假定串口發送的數據幀結構爲:
幀頭 | 幀長度 | 幀類型 | 幀數據 | 校驗 |
---|
其中:“幀頭”
用於同步,一般是一個或多個字節,本文中假定數據幀同步頭有2字節(0xFE、0xEF);“幀長度”
表示數據包中除去“幀頭”和“幀長度”的字節數,即“幀類型”、“幀數據”和“校驗”的總長度;“幀類型”
爲通信協議裏規定的命令類型;“幀數據”
爲應發送的主要信息;“校驗”
可以採用簡單的單字節“異或”的方法,複雜點的可以採用“CRC校驗”。
3. 基於有限狀態機的幀同步方法
下面我們就來說說“基於有限狀態機的幀同步方法”,該方法是將數據幀的接收過程分爲若干個狀態:“接收幀頭HEAD1狀態”、“接收幀頭HEAD2狀態”、“接收幀長度狀態”、“接收幀類型狀態”、“接收數據狀態”及“接收校驗和狀態”。系統的初始狀態爲“接收幀頭HEAD1狀態”,各接收狀態間的狀態轉移圖如下圖所示。隨着串口接收中斷新數據的接收,系統的接收狀態依次爲 HEAD1→HEAD2→LEN→TYPE→DATA→CHECK。該方法也快速、有效地實現了同步;但是需要注意的是,在每一次接收完1幀完整的數據之後,必須把系統的接收狀態重新設置爲HEAD1,否則將會影響下一幀的數據接收。
此後,程序按照協議開始依次接收數據幀長度、命令類型、數據和校驗位。接收完後,重新設置系統接收狀態爲HEAD1,同時對該數據幀進行校驗。校驗正確後,利用消息機制通知主程序根據命令類型對數據幀進行處理或執行相應的命令操作。
下面給出該方法在CC2530裸機
中的實例程序:
#include "iocc2530.h"
#define TRUE 1
#define FALSE 0
// 狀態機狀態
#define HEAD1 0x00
#define HEAD2 0x01
#define LEN 0x02
#define TYPE 0x03
#define DATA 0x04
#define CHECK 0x05
// 命令
#define COMMAND1 0x01
#define COMMAND2 0x02
// 狀態機用到的全局變量
unsigned char g_datrev[48]; // 串口數據緩存
unsigned char g_cmd; // 接收的命令
unsigned char g_recok; // 串口事件標誌位
unsigned char g_recstate = HEAD1; // 接收狀態
unsigned char g_len = 0; // 已接收的數據長度
unsigned char g_check_sum = 0; // 校驗和
unsigned char g_lentotal = 0; // 包長
void main(void)
{
// 初始化程序
// 任務輪詢
while(1)
{
// 有新的事件發生
if(g_recok==TRUE)
{
// 清除串口事件標誌位
g_recok = FALSE;
// 處理命令
switch(g_cmd)
{
case COMMAND1:
{
// 處理命令1
}
break;
case COMMAND2:
{
// 處理命令2
}
break;
default:
break;
}
}
}
}
// 串口0中斷處理函數
#pragma vector = URX0_VECTOR
__interrupt void UART0_ISR(void)
{
// 接收串口數據
unsigned char recdata = 0;
// 清中斷標誌
URX0IF = 0;
// 接收串口0數據
recdata = U0DBUF;
// 進入接收狀態機
switch(g_recstate)
{
case HEAD1:
{
// 如果接收到HEAD1的值0xFE,則進入狀態HEAD2
if(recdata==0xFE)
{
g_recstate = HEAD2;
}
}
break;
case HEAD2:
{
// 如果接收到HEAD2的值0xEF,則進入狀態LEN接收幀數據長度
if(recdata==0xEF)
{
g_recstate = LEN;
}
// 如果接收到的還是HEAD1的數據0xFE,則不跳轉,等待下一個數據
else if(recdata==0xFE)
{
g_recstate = HEAD2;
}
// 如果沒有接收到HEAD1或HEAD2的數據則返回HEAD1狀態重新再來
else
{
g_recstate = HEAD1;
}
}
break;
case LEN:
{
// 將狀態機指向下一個狀態“TYPE”
g_recstate = TYPE;
// 接收幀數據長度
g_lentotal = recdata;
// 異或校驗,先校驗“HEAD1”和“HEAD2”
g_check_sum = 0xFE^0xEF;
}
break;
case TYPE:
{
// 將狀態機指向下一個狀態“DATA”
g_recstate = DATA;
// 接收幀命令數據
g_cmd = recdata;
// 校驗命令數據
g_check_sum = g_check_sum ^ recdata;
// 將下一狀態要用的“g_len”先清零
g_len = 0;
}
break;
case DATA:
{
// 將接收到的幀數據存儲在數據緩存數組中
g_datrev[g_len] = recdata;
// 校驗幀數據
g_check_sum = g_check_sum ^ recdata;
// 記錄接收了多少字節數據
g_len++;
// 接收總幀長度的數據後轉向“CHECK”狀態校驗整個幀是否正確
if(g_len>=g_lentotal)
{
g_recstate = CHECK;
}
}
break;
case CHECK:
{
// 檢測接收到的幀數據是否正確,正確則通知主函數有新的事件;錯誤則拋棄此幀,重置狀態機
if(g_check_sum==recdata)
{
g_recok = TRUE;
}
g_recstate = HEAD1;
}
break;
default:
{
g_recstate = HEAD1;
}
break;
}
}
由於採用了狀態機和消息機制的結構,上述設計思路快速有效地實現了串口通信的同步,而且程序結構清晰,便於維護,也易於向其他的串口通信協議移植。另外,串口中斷服務子程序中需要處理的工作很少,大大減輕了串口接收中斷服務程序的壓力,緩解了嵌入式系統有限資源與需求之問的矛盾,提高了嵌入式系統的穩定性。
大家的支持就是我分享技術的動力,希望大家需轉載時能附上原作者的博客:https://blog.csdn.net/u012993936,謝謝。