文章目錄

  • 前言
  • 一、數據流架構
  • 二、具體實現講解
  • 1.命令解析層
  • 2.配置處理層

前言

最近工作中遇到一個需求,在開發一個產品時,需要通過app去配置產品的一些功能開關、參數等,然後在這個同時,還可以通過電腦端應用去配置產品的功能開關、參數等,然後它們配置的flash有的是相同的,有的是不同的。另外這些參數配置,還可以通過用户手動組合鍵觸發去修改。

另外,app和電腦端應用的協議在同系列產品中是共用的,但是配置的flash存儲方式在產品中又不能固定下來。

當我想實現app、電腦應用的協議的模塊化時,我又沒法繞過flash配置,除非我app用一套固定的flash存儲結構,電腦應用也用一套固定的flash存儲結構。嗯,這是我之前的想法。

後來就想啊想。想法就從生活中來了。

我app的協議是固定的,每條協議應該配置什麼是固定的,那麼我就給app一套獨立的flash抽象結構,這好比一種語言,比如假設這是“英語”

我電腦應用端的協議也是固定的,每條協議應該配置什麼是固定的,那麼我就給電腦應用端一套獨立的flash抽象結構,這也好比一種語言,比如假設這是“法語”

ok,我實際用户的業務邏輯中的存儲配置是另一套,而且每個產品項目可能不同,假設這可能是中文,可能是粵語…

那麼我只需要在app協議的框架中,抽象出一個翻譯器,讓用户自己實現從中文轉換為英文,或從粵語轉換成英文。電腦端應用也是如此。

這樣我就無需關注或統一具體的業務邏輯底層的flash配置,而實現了將app、電腦應用、實際底層存儲三者之間統一起來。而且對app和電腦應用端的協議解析框架實現瞭解耦。

簡直完美!

開講,代碼隱去具體實現細節,只講解框架思路哈!


一、數據流架構

24.嵌入式部分框架設計與實現_#stm32

二、具體實現講解

1.命令解析層

命令解析層將數據流解析出來,輸出完整的一幀及長度,具體的實現細節可以參考我的一套開源模塊b_protocol_core,極其好用,我起碼在十幾個大大小小的不同格式協議的項目中應用了,兼容性相對還闊以。這裏就不細講了。總之就是獲取到完整的一幀數據及長度。

然後,丟到配置處理層去解析具體的命令

2.配置處理層

就我目前的項目需求而言,設計的命令處理函數原型如下

// 主配置數據結構,統一當前協議要操作的存儲數據及其格式,由具體協議而定。
typedef struct {
	// 設備基礎信息
	uint8_t mac[6];
	uint16_t hardware_version;
	uint16_t firmware_major;
	uint16_t firmware_minor;
	char device_name[21];
	uint8_t par1;
	uint8_t par2;
	....
} flash_raw_data_t;

// 命令處理函數類型
typedef result_t (*cmd_handler_t)(flash_raw_data_t *p_flash, 
                                  void* cmd_data, uint16_t len, 
                                  uint8_t* response, uint16_t* resp_len);

// 命令處理項
typedef struct {
    uint8_t main_cmd;
    cmd_handler_t handler;
    const char* cmd_name;
} cmd_entry_t;

// 框架入口函數
extern result_t cmd_framework_encode(uint8_t* data, uint16_t len);

將我們通過命令解析層解析出的一幀完整數據,交付給cmd_framework_encode函數進行處理,這個函數就負責翻譯存儲數據及命令分發匹配,匹配到的命令就按照協議統一的flash數據格式處理,處理之後在將存儲數據翻譯回去。

嗯,flash翻譯操作的思路大概是讀改寫。

我們來看具體的實現

首先三個弱函數

// 弱定義的數據發送接口
__weak result_t data_sender(uint8_t* data, uint16_t len)
{
    // 用户需要實現此函數來發送數據,用於響應數據發送
    return GS_SUCCESS;
}

// 弱定義的配置數據加載接口
__weak uint8_t flash_data_load(uint8_t profile_id, flash_raw_data_t *p_flash)
{
	// 用户需要實現此函數來從實際存儲加載數據到flash_raw_data_t結構體
	//由用户根據自己項目實際實現,所以可以很靈活..
	p_flash->hardware_version = HW_VERSION;
	p_flash->firmware_major   = SW_VERSION;
	p_flash->par1 = user_flash.parxxx;//user_flash及具體的flash存儲數據。
  return GS_SUCCESS;
}

// 弱定義的配置數據保存接口
__weak uint8_t flash_data_save(uint8_t profile_id, flash_raw_data_t *p_flash)
{
    // 用户需要實現此函數來保存gs_flash_raw_data_t結構體數據到實際存儲
    user_flash.parxxx = p_flash->par1;
    return GS_SUCCESS;
}

上面三個弱函數框架本身並不關心,完全交給用户實現。
flash_data_load就好比把用户業務邏輯的存儲格式翻譯成當前協議框架的存儲格式。相當於一個翻譯官,也是這個框架思路的核心思想。
同理,flash_data_save就是把協議框架存儲格式翻譯成用户業務邏輯的具體存儲格式。同樣相當於一個翻譯官。

一個英譯漢,一個漢譯英。
一箇中國人不會英語,一個英國人不會中文。怎麼讓他們兩個交流呢?那就是引入一個翻譯啦。是不是,所以敲代碼也來自與生活,哈哈。

//命令表,綁定命令和具體的執行回調函數。
static const cmd_entry_t cmd_table[] = {
    {MAIN_CMD_MISC, misc_device_info_opt, "MISC_DEVICE_INFO"},
    ....
};

result_t cmd_framework_encode(uint8_t* data, uint16_t len)
{
	frame_header_t *header = (frame_header_t *)data;
	uint8_t main_cmd = header->main_cmd & 0x7F; // 取主命令, 假如最高位1/0代表設置/查詢
	uint8_t profile_id = header->profile_id ;//支持多套參數修改
	result_t result = ERR_UNSUPPORTED_CMD;
    
	// 查找命令處理函數
	for (uint16_t i = 0; i < sizeof(cmd_table)/sizeof(cmd_entry_t); i++){
		if (cmd_table[i].main_cmd == main_cmd) {
			uint8_t response[64];
			uint16_t resp_len = 0;
			printf("%s\r\n",gs_cmd_table[i].cmd_name);
		
			//申請一個flash空間,可以堆分配也可以棧分配。如果結構體內容比較大,堆應該安全一些,如果用堆在flash_data_save之後記得釋放。
			flash_raw_data_t flash_data;
		
			//把用户的flash存儲格式通過翻譯器,翻譯成協議統一的存儲格式
			flash_data_load(profile_id, &flash_data);
			
			//匹配到命令,調用綁定的命令處理函數
			//通過協議統一的存儲格式去處理flash數據。
			result = gs_cmd_table[i].handler(
							&flash_data,
							data,len, 
							response, &resp_len
							);
		
			if(result == GS_SUCCESS) {
				//處理成功之後,比如有修改配置,則把flash數據再翻譯回用户的存儲格式
				flash_data_save(profile_id, p_flash_data);
			}else{
				printf("%s err %d",cmd_table[i].cmd_name, result);
			}
			// 發送響應數據
			if (result == GS_SUCCESS && resp_len > 0) {
				result_t send_result = data_sender(response, resp_len);
				if (send_result != GS_SUCCESS) {
					result = send_result;
				}
			}
			break;
		}
	}
	return result;
}

再拿個具體的命令講解,這個是查詢flash數據。修改也是類似的操作。只要根據命令去修改p_flash的內容即可。

// 雜項配置 - 設備基本信息 (0x00)
result_t misc_device_info_opt(flash_raw_data_t *p_flash,
                              void* cmd_data, uint16_t len, 
                              uint8_t* response, uint16_t* resp_len)
{
	cmd_device_info_t *cmd_info = (cmd_device_info_t *)cmd_data;
	cmd_device_info_t *resp_info= (cmd_device_info_t *)response;
	
	memcpy(cmd_info , resp_info, sizeof(cmd_device_info_t ))
	// 填充設備信息
	resp_info->hardware_version = p_flash->hardware_version;
	resp_info->firmware_major = p_flash->firmware_major;
	memcpy(resp_info->device_name, p_flash->device_name, sizeof(p_flash->device_name));
	// 計算校驗和
	response->checksum = check_sum(response, sizeof(cmd_device_info_t ) - 1);
	*resp_len = resp_header->length;
	return GS_SUCCESS;
}