nanoMODBUS 库

一、nanoMODBUS 库介绍

nanoMODBUS 是一款轻量级、跨平台的 MODBUS 协议实现库 ,专为嵌入式系统(如单片机、FPGA 嵌入式硬核、物联网设备)设计,核心特点是体积小、资源占用低、易移植、功能精简且够用,完美适配内存/Flash 受限的嵌入式场景。

核心特性
  1. 协议支持:实现 MODBUS RTU(串口)和 MODBUS TCP(以太网)核心功能,支持常用功能码(03 读保持寄存器、06 单寄存器写、16 多寄存器写等);
  2. 资源占用:代码量仅几千行,RAM 占用 < 1KB,Flash 占用 < 10KB,无依赖第三方库;
  3. 跨平台:纯 C 编写,可移植到 STM32、ESP32/8266、Arduino、FPGA 硬核处理器(如 Zynq)等几乎所有嵌入式平台;
  4. 易用性:API 简洁,仅需少量代码即可实现 MODBUS 主机(Master)/从机(Slave)功能;
  5. 可裁剪:支持按需关闭未使用的功能码、协议模式(如仅保留 RTU),进一步缩减体积。
典型应用场景
  • 嵌入式设备与工控屏/PLC 通信;
  • 传感器(温湿度、压力)通过 MODBUS 上报数据;
  • 单片机控制变频器、继电器等工业设备。

二、nanoMODBUS 核心结构与关键 API

1. 核心数据结构
c 复制代码
// MODBUS 上下文(核心结构体,存储协议配置、状态、缓冲区)
typedef struct {
    uint8_t *tx_buffer;    // 发送缓冲区
    uint16_t tx_buffer_len;// 发送缓冲区长度
    uint8_t *rx_buffer;    // 接收缓冲区
    uint16_t rx_buffer_len;// 接收缓冲区长度
    // 其他:地址、功能码、超时、错误码、传输模式(RTU/TCP)等
} nmbs_t;
2. 关键 API
函数名 功能
nmbs_init() 初始化 MODBUS 上下文
nmbs_set_slave_addr() 设置从机地址
nmbs_rtu_request_03() 构造 03 功能码(读保持寄存器)请求
nmbs_send_rtu() 发送 RTU 帧(含 CRC 校验)
nmbs_receive_rtu() 接收并解析 RTU 帧
nmbs_response_03() 从机构造 03 功能码响应帧
nmbs_get_error() 获取错误码(如 CRC 错误、功能码不支持)

三、实战示例(基于 STM32 的 MODBUS RTU)

以下以「STM32F103 作为 MODBUS 主机,读取从机(传感器)保持寄存器(地址 0x0000)的 2 个寄存器值」为例,演示 RTU 模式使用(串口波特率 9600,8N1)。

步骤 1:环境准备
  1. 下载 nanoMODBUS 源码:https://github.com/akospasztor/nanoMODBUS
  2. nmodbus.c/nmodbus.h 添加到 STM32 工程;
  3. 实现串口底层收发函数(nmbs_port.c,适配硬件)。
步骤 2:底层串口适配(关键)

nanoMODBUS 不直接操作硬件,需实现串口读写/延时接口:

c 复制代码
// nmbs_port.c(硬件适配层)
#include "nmodbus.h"
#include "usart.h"  // STM32 串口驱动

// 串口发送一个字节
void nmbs_port_serial_write(nmbs_t *ctx, uint8_t data) {
    HAL_UART_Transmit(&huart1, &data, 1, 100); // 串口1,超时100ms
}

// 串口读取一个字节(阻塞,超时返回 -1)
int16_t nmbs_port_serial_read(nmbs_t *ctx) {
    uint8_t data;
    if (HAL_UART_Receive(&huart1, &data, 1, 100) == HAL_OK) {
        return data;
    }
    return -1;
}

// 延时 ms(用于 RTU 帧间隔)
void nmbs_port_delay_ms(nmbs_t *ctx, uint32_t ms) {
    HAL_Delay(ms);
}
步骤 3:主机(Master)读取从机寄存器示例
c 复制代码
#include "nmodbus.h"
#include "usart.h"

// 定义缓冲区(RTU 帧最大长度 256 字节)
#define BUFFER_SIZE 256
static uint8_t tx_buffer[BUFFER_SIZE];
static uint8_t rx_buffer[BUFFER_SIZE];

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_USART1_UART_Init(); // 初始化串口1(9600 8N1)

    // 1. 初始化 MODBUS 上下文
    nmbs_t ctx;
    nmbs_init(&ctx, tx_buffer, BUFFER_SIZE, rx_buffer, BUFFER_SIZE);
    nmbs_set_slave_addr(&ctx, 0x01); // 目标从机地址 0x01

    uint16_t reg_data[2]; // 存储读取的 2 个寄存器值
    while (1) {
        // 2. 构造 03 功能码请求:读从机 0x0000 地址开始的 2 个保持寄存器
        nmbs_error_t err = nmbs_rtu_request_03(&ctx, 0x0000, 2);
        if (err != NMBS_ERROR_NONE) {
            // 构造请求失败(如缓冲区不足)
            continue;
        }

        // 3. 发送 RTU 帧(自动添加 CRC 校验)
        err = nmbs_send_rtu(&ctx);
        if (err != NMBS_ERROR_NONE) {
            continue;
        }

        // 4. 接收并解析响应帧
        err = nmbs_receive_rtu(&ctx);
        if (err == NMBS_ERROR_NONE) {
            // 5. 提取寄存器数据
            if (nmbs_response_03(&ctx, reg_data, 2) == NMBS_ERROR_NONE) {
                // 成功读取:reg_data[0]、reg_data[1] 为从机寄存器值
                printf("寄存器0x0000: %d, 0x0001: %d\r\n", reg_data[0], reg_data[1]);
            }
        }

        HAL_Delay(1000); // 每秒读取一次
    }
}
步骤 4:从机(Slave)响应主机请求示例

若需实现从机(如传感器设备),核心是监听串口、解析请求并返回响应:

c 复制代码
#include "nmodbus.h"
#include "usart.h"

#define BUFFER_SIZE 256
static uint8_t tx_buffer[BUFFER_SIZE];
static uint8_t rx_buffer[BUFFER_SIZE];
// 模拟从机保持寄存器(地址 0x0000~0x0001)
static uint16_t holding_regs[2] = {123, 456};

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_USART1_UART_Init();

    nmbs_t ctx;
    nmbs_init(&ctx, tx_buffer, BUFFER_SIZE, rx_buffer, BUFFER_SIZE);
    nmbs_set_slave_addr(&ctx, 0x01); // 本机从机地址 0x01

    while (1) {
        // 1. 接收主机 RTU 请求帧
        nmbs_error_t err = nmbs_receive_rtu(&ctx);
        if (err != NMBS_ERROR_NONE) {
            continue;
        }

        // 2. 解析请求并构造响应
        switch (nmbs_get_function_code(&ctx)) {
            case 0x03: // 主机请求读保持寄存器
                // 响应 03 功能码:返回 holding_regs 数据
                err = nmbs_response_03(&ctx, holding_regs, 2);
                break;
            default:
                // 不支持的功能码,返回异常响应
                err = nmbs_response_exception(&ctx, nmbs_get_function_code(&ctx), NMBS_EXCEPTION_ILLEGAL_FUNCTION);
                break;
        }

        // 3. 发送响应帧
        if (err == NMBS_ERROR_NONE) {
            nmbs_send_rtu(&ctx);
        }
    }
}

四、MODBUS TCP 示例(极简版)

若需使用 TCP 模式(如以太网通信),仅需替换传输层接口(以 lwIP 为例):

c 复制代码
// 替换串口读写为 TCP 套接字读写
void nmbs_port_tcp_write(nmbs_t *ctx, uint8_t data) {
    send(sock_fd, &data, 1, 0); // 向 TCP 套接字发送字节
}

int16_t nmbs_port_tcp_read(nmbs_t *ctx) {
    uint8_t data;
    int ret = recv(sock_fd, &data, 1, 0);
    return (ret > 0) ? data : -1;
}

// 主机构造 TCP 请求
nmbs_tcp_request_03(&ctx, 0x0000, 2);
nmbs_send_tcp(&ctx);
nmbs_receive_tcp(&ctx);

五、使用注意事项

  1. RTU 帧间隔:nanoMODBUS 自动处理 RTU 帧间延时(≥3.5 字符时间),无需手动控制;
  2. 缓冲区大小:建议至少 256 字节,满足最大 RTU 帧长度;
  3. 错误处理 :务必检查 nmbs_error_t 错误码,常见错误如 CRC 校验失败、寄存器地址越界;
  4. 移植要点:仅需适配底层读写(串口/TCP)和延时函数,核心逻辑无需修改;
  5. 功能码扩展 :若需支持其他功能码(如 06 写寄存器),调用 nmbs_rtu_request_06()/nmbs_response_06() 即可。

六、总结

nanoMODBUS 是嵌入式 MODBUS 开发的"轻量首选",无需复杂配置,仅需适配底层硬件即可快速实现主机/从机通信。上述示例覆盖了最常用的 RTU 模式读写寄存器场景,TCP 模式仅需替换传输层接口,适配成本极低。

附录

RAM(运行内存)Flash(存储内存) 在嵌入式系统中的本质区别:

  • RAM :是「临时运行空间」,掉电丢失,用于存放程序运行时的变量、缓冲区、上下文等动态数据
  • Flash :是「永久存储空间」,掉电不丢,用于存放程序的代码、常量、只读数据(编译后固化在Flash,运行时无需修改)。

nanoMODBUS 作为一个完整的库,既有"运行时需要动态操作的部分"(占RAM),也有"固定不变的代码/常量部分"(占Flash),二者缺一不可。

一、Flash :存储库的"固定代码+常量"

Flash 占用的是 nanoMODBUS 编译后的机器码只读常量 ,这部分是库的"骨架",只要程序要运行这个库,就必须把这些代码烧录到 Flash 中,运行时 CPU 从 Flash 读取指令执行。

具体占用 Flash 的内容包括:

  1. 核心函数代码 :如 nmbs_init()nmbs_rtu_request_03()nmbs_crc16() 等所有库函数的编译后指令;
  2. 只读常量:如 MODBUS 协议固定的异常码(01=非法功能码、02=非法寄存器地址)、CRC16 校验表(可选)、默认配置参数等;
  3. 字符串常量 (若开启调试):如错误提示字符串("CRC error")。
    这些内容是"死的"、不会在运行时修改,因此无需放在 RAM 中(RAM 空间更宝贵),而是固化在 Flash 中,CPU 执行时直接从 Flash 取指令,仅将需要修改的临时数据加载到 RAM。

二、RAM :存储程序运行时的"动态数据"

RAM 占用的是程序运行过程中需要实时读写、临时存储的数据,nanoMODBUS 运行时必须依赖这些动态数据,具体包括:

  1. 上下文结构体(nmbs_t):存储 MODBUS 通信的状态(如从机地址、当前功能码、错误码)、缓冲区指针等;
  2. 收发缓冲区:tx_buffer/rx_buffer(用于临时存放待发送/已接收的 MODBUS 帧数据,运行时不断更新);
  3. 局部变量:函数执行时的临时变量(如 CRC 计算的中间值、寄存器数据缓存);
  4. 栈空间:函数调用时的栈帧(如参数传递、返回地址存储)。

这些数据是"活的",必须放在可读写的 RAM 中,Flash 是只读的,无法用于存储运行时需要修改的数据。

三、核心对比

维度 Flash 占用(~10KB) RAM 占用(~1KB)
存储内容 库的代码、常量、只读配置(固定不变) 运行时的变量、缓冲区、上下文(动态变化)
读写特性 只读(运行时不修改,仅读取执行) 可读可写(运行时频繁修改)
必要性 无 Flash 存储,库的代码无法被 CPU 执行 无 RAM 存储,无法处理动态的通信数据
掉电特性 掉电保留(代码烧录后永久存在) 掉电丢失(每次上电重新分配)

举个通俗的例子:

  • Flash 相当于"课本",里面写好了 MODBUS 通信的"规则(代码)",你(CPU)需要时从课本里读规则;
  • RAM 相当于"草稿纸",你(CPU)按照课本规则处理数据时,需要在草稿纸上临时写/改中间结果;
相关推荐
无聊到发博客的菜鸟2 小时前
使用STM32对SD卡进行性能测试
stm32·单片机·rtos·sd卡·fatfs
许商3 小时前
【stm32】cmake脚本(一)
stm32·单片机·嵌入式硬件
polarislove02143 小时前
8.1 时钟树-嵌入式铁头山羊STM32笔记
笔记·stm32·嵌入式硬件
风行男孩4 小时前
stm32基础学习——OLED显示屏的基本使用
stm32·嵌入式硬件·学习
养一回月亮!4 小时前
FreeRTOS任务延迟:vTaskDelay与vTaskDelayUntil的深度对比
stm32·单片机·嵌入式硬件
Zeku5 小时前
20251202 - Linux输入子系统
stm32·freertos·嵌入式软件·linux驱动开发·linux应用开发
boneStudent8 小时前
Day39:智能家居环境监测系统
stm32·单片机·嵌入式硬件·智能家居
Zeku8 小时前
20251202 - Linux输入系统的基础知识 - tslib
stm32·freertos·linux驱动开发·linux应用开发
polarislove02149 小时前
5.8W25Q64 实验(下)-嵌入式铁头山羊STM32笔记
笔记·stm32·嵌入式硬件