星標公眾號,讓嵌入式知識 “投喂” 不停歇!
在工業自動化和物聯網設備開發中,Modbus協議憑借其簡單可靠的特性,成為了嵌入式系統通信的首選。
然而,傳統的Modbus協議棧往往體積龐大,動輒幾萬行代碼,對于資源受限的微控制器來說顯得過于臃腫。
最近,我在GitHub上發現了一個名為nanoMODBUS的開源項目,它用僅僅2000行C代碼就實現了完整的Modbus RTU/TCP協議棧,這種精簡而高效的設計理念讓我眼前一亮。

nanoMODBUS是由意大利開發者debevv創建的輕量級Modbus協議棧,專為嵌入式系統設計。項目的核心特性包括:
項目地址:https://github.com/debevv/nanoMODBUS
在分析nanoMODBUS的架構時,我首先被其平臺抽象層的設計所吸引。作者通過一個簡潔的接口設計,實現了硬件無關的協議棧實現。
typedef struct {
nmbs_transport transport; // 傳輸類型:RTU或TCP
nmbs_read_func read; // 平臺相關的讀函數
nmbs_write_func write; // 平臺相關的寫函數
void* arg; // 用戶自定義參數
} nmbs_platform_conf;
這種設計的巧妙之處在于,它將協議棧的核心邏輯與底層硬件完全解耦。在移植到STM32平臺時,只需要實現兩個簡單的函數:
int32_t my_transport_read(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
UART_HandleTypeDef* huart = (UART_HandleTypeDef*)arg;
HAL_StatusTypeDef status = HAL_UART_Receive(huart, buf, count, timeout_ms);
return (status == HAL_OK) ? count : -1;
}
int32_t my_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
UART_HandleTypeDef* huart = (UART_HandleTypeDef*)arg;
HAL_StatusTypeDef status = HAL_UART_Transmit(huart, (uint8_t*)buf, count, timeout_ms);
return (status == HAL_OK) ? count : -1;
}
這種weak函數式的回調設計,讓我想起了Linux內核中的虛擬文件系統,通過統一的接口屏蔽底層差異,是一種非常優雅的架構模式。
深入研究nanoMODBUS的協議解析邏輯后,我發現它采用了一個精巧的狀態機設計。不同于傳統的阻塞式解析,nanoMODBUS使用非阻塞的狀態機來處理協議幀的接收和解析。

在代碼實現上,狀態機的核心邏輯非常簡潔:
nmbs_error nmbs_server_poll(nmbs_t* nmbs) {
switch (nmbs->state) {
case NMBS_SERVER_STATE_LISTENING:
return server_receive_request(nmbs);
case NMBS_SERVER_STATE_PROCESSING:
return server_process_request(nmbs);
case NMBS_SERVER_STATE_RESPONDING:
return server_send_response(nmbs);
default:
nmbs->state = NMBS_SERVER_STATE_LISTENING;
return NMBS_ERROR_INVALID_STATE;
}
}
這種設計的優勢在于,它避免了阻塞等待,特別適合在RTOS環境下使用。我在實際項目中發現,這種非阻塞的設計可以讓主循環保持高效運行,不會因為通信阻塞而影響系統的實時性。
狀態機用于協議解析是一種很常用的方法, 之前我們分享的嵌入式中輕量級通信協議利器!協議數據接收解析也是采用狀態機的方式。
在嵌入式開發中,內存管理往往是最頭疼的問題。動態分配容易導致內存碎片,而靜態分配又可能造成內存浪費。nanoMODBUS通過一種巧妙的方式解決了這個問題。
typedef struct nmbs {
uint8_t msg[NMBS_PDU_MAX_SIZE]; // 固定大小的消息緩沖區
uint16_t msg_length; // 當前消息長度
nmbs_state_t state; // 當前狀態
nmbs_platform_conf platform; // 平臺配置
// ... 其他必要字段
} nmbs_t;
我注意到,作者使用了一個固定大小的緩沖區來存儲Modbus消息。這個設計看似簡單,但實際上非常巧妙:
嵌入式系統設計的一個重要原則:在滿足功能的前提下,盡可能簡化設計。
為了驗證nanoMODBUS的可移植性,我將它移植到了ESP32平臺。整個過程出乎意料的順利,主要步驟如下:
void init_uart() {
uart_config_t uart_config = {
.baud_rate = 9600,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
};
uart_param_config(UART_NUM_1, &uart_config);
uart_driver_install(UART_NUM_1, 256, 256, 0, NULL, 0);
}
int32_t esp32_transport_read(uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
int len = uart_read_bytes(UART_NUM_1, buf, count, timeout_ms / portTICK_RATE_MS);
return len > 0 ? len : -1;
}
int32_t esp32_transport_write(const uint8_t* buf, uint16_t count, int32_t timeout_ms, void* arg) {
int len = uart_write_bytes(UART_NUM_1, (const char*)buf, count);
return len == count ? len : -1;
}
nmbs_platform_conf platform_conf = {
.transport = NMBS_TRANSPORT_RTU,
.read = esp32_transport_read,
.write = esp32_transport_write,
.arg = NULL
};
nmbs_t nmbs;
nmbs_server_create(&nmbs, 1, &platform_conf); // 從站地址為1
整個移植過程不到50行代碼,這充分說明了nanoMODBUS設計的優秀。
在多線程環境中使用nanoMODBUS時,需要特別注意線程安全問題。雖然協議棧本身使用靜態內存,但狀態變量的訪問仍然需要保護:
// 在FreeRTOS中使用互斥鎖保護
SemaphoreHandle_t modbus_mutex;
void modbus_task(void* param) {
while (1) {
if (xSemaphoreTake(modbus_mutex, portMAX_DELAY) == pdTRUE) {
nmbs_server_poll(&nmbs); // 處理Modbus通信
xSemaphoreGive(modbus_mutex);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
nanoMODBUS用最簡潔的代碼實現了最完整的功能,體現了"Less is More"的設計哲學。對于我們嵌入式開發者來說,這個項目給出了幾個重要啟示:
猜你喜歡:
適用于嵌入式的輕量級環緩沖區管理庫!
Git 交互式變基修改commit描述
單例模式:嵌入式全局狀態一致性的守護者
嵌入式領域:Linux 與 RTOS 的巔峰對決!
嵌入式軟件進階指南,一起來進階!