聯合體原來是這么用的!
不管是線程間、還是設備間通信,都需制定一個通信協議,規定數據的格式、內容等。
線程間通信因為在芯片內部傳輸,基本可以排除數據干擾導致的異常,所以通常會設計的比較簡單,但是設備間的通信(不管是無線還是有線)就會復雜一些,一般都包含幀頭、校驗位之類的,因此魚鷹在一篇文章中介紹了一個基本的串口協議框架《如何寫一個健壯且高效的串口接收程序?》。
因為當時剛畢業沒多久,所以雖然從大的方向介紹了基本協議內容,但在細節處理上還不夠好,比如可維護性、可讀性等方面。
后來,魚鷹在學習開源的飛控源碼時,發現里面使用了聯合體+結構體的方式,大大提高了程序的可維護性和可讀性。
比如,我們的協議中有這樣三條命令,心跳包、獲取固件版本號、獲取序列號。
初級版本:
直接使用基本的類型聲明所需要的數據結構。
uint8_t heartbeat;char version[6]; // "2.0.4" char sn[7]; // "654321"
一般這樣寫的,大概率是工作一兩年,當然也不排除工作好多年的也可能這樣寫。看似簡單,但可維護性、可讀性都非常差。
中級版本:
使用結構體的形式聲明各種消息內容。
typedef struct { uint8_t heart_nbr;}msg_heartbeat_def;
typedef struct { char version[sizeof("2.0.4") + 1];}msg_version_def;
typedef struct { char sn[sizeof("123456") + 1]; }msg_serial_number_def;
這種方式,一般是工作兩三年以上的,開始使用結構體容納數據內容,可擴展性比較強,可維護性、可讀性也不錯。比如假設后面版本號這個消息里面希望同時獲取編譯時間,那直接在里面增加即可。
typedef struct { char version[sizeof("2.0.4") + 1]; char compile_time[sizeof("2022-12-12, 12:00:00") + 1]; }msg_version_def;
如果其它代碼寫的比較好,甚至不需要多大改動,即可完成一次擴展。
高級版本:
在中級版本的基礎上,使用聯合體容納前面的所有消息類型。
typedef union { msg_heartbeat_def heartbeat; // 心跳包 msg_version_def version_nbr; // 版本號 msg_serial_number_def serial_number; // 產品序列號}msg_data_def;
當你需要發送消息的時候,可以這樣發送:
typedef enum { MSG_ID_HEARTBEAT, MSG_ID_VERSION, MSG_ID_SN,}msg_id_def;
void msg_uart_send(msg_id_def id, msg_data_def *msg_data, uint32_t size){// 這里加入幀頭、消息ID、校驗之類的再發送出去#define FRAME_FIX_SIZE_MIN 10 // 組成一幀數據的最小空間,包含幀頭之類的 uint8_t send_buff[sizeof(msg_data_def) + FRAME_FIX_SIZE_MIN]; }void msg_send_vesion(void){ msg_data_def data; strcpy(data.version_nbr, "1.0.1"); msg_uart_send(MSG_ID_VERSION, &data, sizeof(data.version_nbr));}
因為現在大部分 IDE 都有代碼提示功能,所以當你需要發送數據的時候,可以根據提示選擇你需要的消息進行發送,相當方便快捷,也不容易出錯。
接收消息時,可以使用 switch(id) 之類的解析對應數據。
在這個例子中,我們利用聯合體的特性,將所有消息類型集成在一起,這樣當你需要發送一條消息時,很容易就能找到想要的數據類型。并且在空間占用上也是非常合理的。當需要開辟緩存容納數據幀格式,利用了聯合體空間占用特性(找最大),這樣你開辟的空間一定是剛剛好,不多也不少,節省了空間使用。
所以,當我們需要傳輸一類消息,但這些消息不會同時存在時,不如使用聯合體吧。
*博客內容為網友個人發布,僅代表博主個人觀點,如有侵權請聯系工作人員刪除。