在这里插入图片描述

一、写在前面

1.1 文章回顾

该文章对第三篇、使用DMA优化FreeModbus从机数据发送(无OS,裸机移植)的应用补充,主要讲modbusRTU从机demo.c的应用开发

1.2 FreeModbus从机 应用例程下载

应用例程下载戳我

   1. 芯片:STM32F103C8T6
   2. 串口引脚:USART1_TX(PB6)USART1_RX(PB7)
   3. RS485控制引脚:DIR1(PB8)
   4. 采用DMA+接收空闲中断+发送完成中断的串口数据的接收(中断量最少、效率高) 
   5. 使用STM32CubeMX生成的Hal库程序
   6. 未使用RTOS
   7. 完善的应用开发

二、补充FreeModbus Slave的应用程序

通过前面几篇博客成功移植FreeModbus的源码后,我们只需要完善BARE文件夹下的demo.c即可真正使用Modbus。

2.1 eMBRegInputCB(输入寄存器(Input Registers),功能码 04H)

2.1.1函数原型

eMBErrorCode eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
  • pucRegBuffer:输出参数,指向协议栈的发送缓冲区。应用层需将待返回的寄存器数据按格式写入该缓冲区。
  • usAddress:输入参数,主机请求读取的 起始寄存器地址(Modbus 协议地址,地址+1开始计数)。
  • usNRegs:输入参数,主机请求读取的 寄存器数量(最多 125 个,符合 Modbus 协议规范)。
  • 返回值:eMBErrorCode
    类型,指示操作结果(MB_ENOERR 表示成功,其他值表示错误,如 MB_ENOREG 表示地址无效)。

2.1.2 函数实现

  • 输入寄存器的只读性:输入寄存器通常用于存储 只读数据(如传感器采集值),因此 eMBRegInputCB 仅处理 “读请求”,无需实现写入逻辑(与保持寄存器的回调 eMBRegHoldingCB 不同,后者需处理读写两种操作)。
  • 数据格式:Modbus 协议规定寄存器数据采用 大端模式(高位字节在前),因此需要将 16 位数据拆分为高 8 位和低 8 位依次写入缓冲区。
  • 地址映射:协议中的寄存器地址是 “1 基地址”(如 1000、1001),而应用层数组通常是 “0 基索引”,需通过 usAddress - REG_INPUT_START 转换(REG_INPUT_START 是输入寄存器的起始地址,如 1000)。
/* ----------------------- 输入寄存器配置 (功能码04) ---------------------------*/
#define S_REG_INPUT_START  1000  // 输入寄存器起始地址(1基)
#define S_REG_INPUT_NREGS  100   // 输入寄存器总数

// 输入寄存器映射结构体
typedef struct {
    USHORT reg_addr;         // 寄存器地址(0基偏移)
    USHORT *data_ptr;        // 数据存储指针
} mb_input_reg_map_t;

// 测试数据
USHORT test_input_01 = 10;
USHORT test_input_02 = 20;
USHORT test_input_03 = 30;

// 输入寄存器映射表
static mb_input_reg_map_t input_reg_maps[S_REG_INPUT_NREGS] = {
    {0, &test_input_01},  
    {1, &test_input_02},  
    {2, &test_input_03},  
    // 其他输入寄存器映射...
};

// 输入寄存器回调函数
eMBErrorCode eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT iRegIndex = 0;
    mb_input_reg_map_t *p_reg = input_reg_maps;

    usAddress--;  // 1基地址转0基

    // 地址范围校验
    if ((usAddress >= S_REG_INPUT_START) && 
        (usAddress + usNRegs <= S_REG_INPUT_START + S_REG_INPUT_NREGS))
    {
        iRegIndex = usAddress - S_REG_INPUT_START;
        // 按Modbus格式填充数据(高8位在前)
        while (usNRegs--)
        {
            *pucRegBuffer++ = (UCHAR)((*(p_reg[iRegIndex].data_ptr)) >> 8);
            *pucRegBuffer++ = (UCHAR)((*(p_reg[iRegIndex].data_ptr)) & 0xFF);
            iRegIndex++;
        }
    }
    else
    {
        eStatus = MB_ENOREG;  // 地址无效
    }

    return eStatus;
}

2.2 eMBRegHoldingCB(保持寄存器(Holding Registers),功能码03H、06H、10H)

2.2.1 函数原型

eMBErrorCode eMBRegHoldingCB( UCHAR * pucRegBuffer, 
                             USHORT usAddress, 
                             USHORT usNRegs, 
                             eMBRegisterMode eMode )
  • pucRegBuffer:双向缓冲区

当 eMode 为 MB_REG_READ 时:从站需将保持寄存器数据写入该缓冲区(返回给主机);
当 eMode 为 MB_REG_WRITE 时:该缓冲区存储主机写入的数据(从站需读取并更新到本地)。

  • usAddress:主机请求的起始寄存器地址(Modbus 协议地址,1 基地址,如 40001)。
  • usNRegs:主机请求的 寄存器数量(最多 125 个,符合协议规范)。
  • eMode:操作模式

MB_REG_READ:主机读取保持寄存器;
MB_REG_WRITE:主机写入保持寄存器。

  • 返回值:eMBErrorCode 类型,指示操作结果(MB_ENOERR 表示成功,MB_ENOREG 表示地址无效等)。

2.2.2 函数实现

  • 保持寄存器具有可读写属性,
  • 有的参数可能需要回调函数、读写属性、默认值、最小值、最大值,因此保持寄存器的结构体内容应该要满足这些需求。
/* ----------------------- 保持寄存器配置 (功能码03/06/16) ----------------------*/
#define S_REG_HOLDING_START  1000  // 保持寄存器起始地址(1基)
#define S_REG_HOLDING_NREGS  100   // 保持寄存器总数

// 测试数据
USHORT test_holding_01 = 40;
USHORT test_holding_02 = 50;
USHORT test_holding_03 = 0;

// 保持寄存器回调函数示例
USHORT holding_reg_callback01(USHORT parm)
{
    test_holding_03++;  // 写入时自增计数
    return 0;
}

// 保持寄存器映射结构体
typedef struct {
    USHORT reg_addr;               // 寄存器地址(0基偏移)
    USHORT *data_ptr;              // 数据存储指针
    USHORT min;                    // 最小值限制
    USHORT max;                    // 最大值限制
    USHORT dft;                    // 默认值
    USHORT rw;                     // 读写属性(RO/RW)
    USHORT (*callback_ptr)(USHORT); // 写入回调函数
} mb_holding_reg_map_t;

// 读写属性枚举
enum {
    RO,  // 只读
    RW   // 读写
};

// 保持寄存器映射表
mb_holding_reg_map_t holding_reg_maps[S_REG_HOLDING_NREGS] = {
    {0, &test_holding_01, 0x0000, 0xffff, 40, RW, holding_reg_callback01},
    {1, &test_holding_02, 0x0000, 0xffff, 50, RW, MB_NULL},
    {2, &test_holding_03, 0x0000, 0xffff, 0,  RO, MB_NULL}
};

// 保持寄存器回调函数
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, 
                            USHORT usNRegs, eMBRegisterMode eMode)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT iRegIndex;
    USHORT Writ_Value;
  
    usAddress--;  // 1基地址转0基

    // 地址范围校验
    if ((usAddress >= S_REG_HOLDING_START) && 
        (usAddress + usNRegs <= S_REG_HOLDING_START + S_REG_HOLDING_NREGS))
    {
        iRegIndex = usAddress - S_REG_HOLDING_START;
        
        switch (eMode)
        {
            case MB_REG_READ:  // 读操作
                while (usNRegs--)
                {
                    *pucRegBuffer++ = (UCHAR)((*holding_reg_maps[iRegIndex].data_ptr) >> 8);
                    *pucRegBuffer++ = (UCHAR)((*holding_reg_maps[iRegIndex].data_ptr) & 0xFF);
                    iRegIndex++;
                }
                break;
                
            case MB_REG_WRITE:  // 写操作
                while (usNRegs--)
                {
                    if (holding_reg_maps[iRegIndex].rw == RW)  // 检查写权限
                    {
                        // 解析16位数据(高8位+低8位)
                        Writ_Value = (*pucRegBuffer << 8) | *(pucRegBuffer + 1);
                        pucRegBuffer += 2;

                        // 范围校验
                        if (Writ_Value >= holding_reg_maps[iRegIndex].min && 
                            Writ_Value <= holding_reg_maps[iRegIndex].max)
                        {
                            // 执行回调函数
                            if (holding_reg_maps[iRegIndex].callback_ptr != MB_NULL)
                            {
                                holding_reg_maps[iRegIndex].callback_ptr(Writ_Value);
                            }
                            *holding_reg_maps[iRegIndex].data_ptr = Writ_Value;
                            iRegIndex++;
                        }
                        else
                        {
                            eStatus = MB_EINVAL;  // 数据超出范围
                            break;
                        }
                    }
                    else
                    {
                        eStatus = MB_EINVAL;  // 无写权限
                        break;
                    }
                }
                break;
        }
    }
    else
    {
        eStatus = MB_ENOREG;  // 地址无效
    }
    return eStatus;
}

2.3 eMBRegCoilsCB( 线圈寄存器(Coils),功能码01H、05H、15H)

2.3.1 函数原型

eMBErrorCode eMBRegCoilsCB( UCHAR * pucRegBuffer, 
                           USHORT usAddress, 
                           USHORT usNCoils, 
                           eMBRegisterMode eMode )
  • pucRegBuffer:双向缓冲区(按 “每字节存储 8 个线圈状态” 的格式组织)
  • 读取(MB_REG_READ):从站将线圈状态写入缓冲区(bit0 对应第一个线圈,bit1 对应第二个,以此类推);
  • 写入(MB_REG_WRITE):缓冲区存储主机写入的线圈状态(从站需解析并更新本地状态)。
  • usAddress:主机请求的
  • 起始线圈地址(1 基地址,如 1、2、3…)。 usNCoils:主机请求的 线圈数量(最多 2000 个)。
  • eMode:操作模式(MB_REG_READ 读取,MB_REG_WRITE 写入)。
  • 返回值:eMBErrorCode 类型(MB_ENOERR 成功,MB_ENOREG 地址无效等)

2.3.2 函数实现

  • 线圈寄存器具有可读写属性
  • 给每一个线圈都增加一个可读写属性,以及回调函数属性
  • 这里将16个线圈设为一组,目的是为了方便其他功能码进行访问,如03H功能码直接进行访问
/* ----------------------- 线圈配置 (功能码01/05/15) ---------------------------*/
#define S_COIL_START       0  // 线圈起始地址(0基)
#define S_COIL_NCOILS      64 // 总线圈数量

// 计算16位存储单元数量(向上取整)
#if S_COIL_NCOILS % 16
#define S_COIL_16BIT_NUM  (S_COIL_NCOILS / 16) + 1
#else
#define S_COIL_16BIT_NUM  (S_COIL_NCOILS / 16)
#endif

// 线圈映射结构体(每个管理16个连续线圈)
typedef struct {
    USHORT start_addr;               // 起始地址(0基)
    USHORT *coil_buf;                // 16位缓冲区指针
    USHORT rw_mask;                  // 权限掩码(bit=1表示可写)
    BOOL (*callback)(UCHAR, BOOL);   // 状态变化回调(bit位置, 新状态)
} mb_coil_reg_map_t;

// 16位线圈缓冲区(每个USHORT存储16个线圈状态)
USHORT test_coils[S_COIL_16BIT_NUM] = {
    0x1111,  // 线圈0~15
    0x2222,  // 线圈16~31
    0x3333,  // 线圈32~47
    0x4444   // 线圈48~63
};

// 线圈0~15的回调函数
BOOL callback_coils_0_15(UCHAR bit_pos, BOOL state) {
    test_holding_03++;  // 状态变化时自增计数
    return TRUE;
}

// 线圈映射表
mb_coil_reg_map_t coil_reg_maps[S_COIL_16BIT_NUM] = {
    {0,  &test_coils[0], 0xFFFF, callback_coils_0_15},  // 0~15: 全可写
    {16, &test_coils[1], 0xFFFF, MB_NULL},              // 16~31: 全可写
    {32, &test_coils[2], 0x0000, MB_NULL},              // 32~47: 全只读
    {48, &test_coils[3], 0xFFFF, MB_NULL}               // 48~63: 全可写
};

// 线圈回调函数
eMBErrorCode eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress,
                          USHORT usNCoils, eMBRegisterMode eMode)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT usGlobalBitOffset;   // 相对起始地址的全局bit偏移
    USHORT usTotalProcessed = 0;// 已处理的bit总数
    USHORT usCurrByteIdx;       // 当前字节索引
    USHORT usBitInByte;         // 字节内bit位置(0-7)
    USHORT usProcessBits;       // 单次处理bit数
    USHORT usWrtMask;           // 写权限掩码
    UCHAR *pCoilByteBuf;        // 线圈缓冲区字节指针

    usAddress--;  // 1基地址转0基

    // 地址范围校验
    if (usAddress + usNCoils > S_COIL_START + S_COIL_NCOILS)
    {
        return MB_ENOREG;
    }

    usGlobalBitOffset = usAddress - S_COIL_START;

    // 处理所有请求的线圈
    while (usTotalProcessed < usNCoils)
    {
        usCurrByteIdx = usGlobalBitOffset / 8;    // 计算字节索引
        usBitInByte = usGlobalBitOffset % 8;      // 计算字节内bit位置

        // 获取当前操作的字节指针(拆分16位缓冲区为字节访问)
        pCoilByteBuf = (UCHAR*)coil_reg_maps[usCurrByteIdx / 2].coil_buf + (usCurrByteIdx % 2);

        // 计算单次最大可处理bit数(不超过当前字节剩余空间)
        usProcessBits = 8 - usBitInByte;
        if (usProcessBits > usNCoils - usTotalProcessed)
        {
            usProcessBits = usNCoils - usTotalProcessed;
        }

        switch (eMode)
        {
            case MB_REG_READ:  // 读线圈
                *pucRegBuffer++ = xMBUtilGetBits(pCoilByteBuf, usBitInByte, (UCHAR)usProcessBits);
                break;

            case MB_REG_WRITE:  // 写线圈
                // 生成写权限掩码
                usWrtMask = ((1 << usProcessBits) - 1) << usBitInByte;
                if ((coil_reg_maps[usCurrByteIdx / 2].rw_mask & usWrtMask) != usWrtMask)
                {
                    return MB_EINVAL;  // 存在只读bit
                }

                // 写入线圈状态
                xMBUtilSetBits(pCoilByteBuf, usBitInByte, (UCHAR)usProcessBits, *pucRegBuffer++);

                // 触发回调函数
                if (coil_reg_maps[usCurrByteIdx / 2].callback != MB_NULL)
                {
                    USHORT usBitInInt = (usCurrByteIdx % 2) * 8 + usBitInByte;
                    BOOL bState = (*coil_reg_maps[usCurrByteIdx / 2].coil_buf >> usBitInInt) & 0x01;
                    coil_reg_maps[usCurrByteIdx / 2].callback(usBitInInt, bState);
                }
                break;

            default:
                return MB_EINVAL;
        }

        // 更新处理进度
        usTotalProcessed += usProcessBits;
        usGlobalBitOffset += usProcessBits;
    }

    return eStatus;
}

2.4 eMBRegDiscreteCB(离散输入(Discrete Inputs),功能码 02H)

2…4.1 函数原型

eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
  • pucRegBuffer:输出参数,指向协议栈的发送缓冲区。应用层需将待返回的寄存器数据按格式写入该缓冲区。
  • usAddress:输入参数,主机请求读取的 起始寄存器地址(Modbus 协议地址,地址+1开始计数)。
  • usNDiscrete:输入参数,需要读取的离散输入数量(bit 数)。
  • 返回值:eMBErrorCode
    类型,指示操作结果(MB_ENOERR 表示成功,其他值表示错误,如 MB_ENOREG 表示地址无效)。

2.4.2 函数实现

  • 离散输入寄存是只读寄存器,因此只有读请求
/* ----------------------- 离散输入配置 (功能码02) ---------------------------*/
#define S_DISCRETE_START      0  // 离散输入起始地址(0基)
#define S_DISCRETE_NCOILS     64 // 总离散输入数量

// 计算16位存储单元数量(向上取整)
#if S_DISCRETE_NCOILS % 16
#define S_DISCRETE_16BIT_NUM  (S_DISCRETE_NCOILS / 16) + 1
#else
#define S_DISCRETE_16BIT_NUM  (S_DISCRETE_NCOILS / 16)
#endif

// 离散输入状态存储(每个USHORT存储16个输入状态)
USHORT test_discrete_01 = 0xAAAA;  // 离散输入0~15
USHORT test_discrete_02 = 0x5555;  // 离散输入16~31
USHORT test_discrete_03 = 0x3333;  // 离散输入32~47
USHORT test_discrete_04 = 0x1111;  // 离散输入48~63

// 离散输入映射结构体
typedef struct {
    USHORT start_bit_addr;  // 起始bit地址(0基)
    USHORT *data_ptr;       // 16位数据存储指针
} mb_discrete_reg_map_st;

// 离散输入映射表
mb_discrete_reg_map_st discrete_reg_maps[S_DISCRETE_16BIT_NUM] = {
    {0,  &test_discrete_01},
    {16, &test_discrete_02},
    {32, &test_discrete_03},
    {48, &test_discrete_04}
};

// 离散输入回调函数
eMBErrorCode eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
{
    eMBErrorCode eStatus = MB_ENOERR;
    USHORT usAddr0 = usAddress - 1;               // 1基地址转0基
    USHORT usGlobalBitOff = usAddr0 - S_DISCRETE_START;  // 全局bit偏移
    USHORT usTotalProcessed = 0;
    USHORT usMapIdx, usBitInMap;
    USHORT usCurrByteIdx = 0;
    UCHAR ucTempByte = 0;

    // 地址范围校验
    if (usAddr0 + usNDiscrete > S_DISCRETE_START + S_DISCRETE_NCOILS)
    {
        return MB_ENOREG;
    }

    // 处理所有请求的离散输入
    while (usTotalProcessed < usNDiscrete)
    {
        usMapIdx = usGlobalBitOff / 16;       // 映射表索引
        usBitInMap = usGlobalBitOff % 16;     // 16位单元内bit位置

        // 读取bit状态
        BOOL bBitState = (*discrete_reg_maps[usMapIdx].data_ptr >> usBitInMap) & 0x01;

        // 打包为字节(8个bit一组)
        ucTempByte |= (bBitState << (usTotalProcessed % 8));

        // 每8个bit或处理完毕时写入缓冲区
        if (((usTotalProcessed + 1) % 8 == 0) || (usTotalProcessed + 1 == usNDiscrete))
        {
            pucRegBuffer[usCurrByteIdx++] = ucTempByte;
            ucTempByte = 0;
        }

        usTotalProcessed++;
        usGlobalBitOff++;
    }

    return eStatus;
}

三、完整代码

demo.c

/*
 * FreeModbus Libary: BARE Demo Application
 * Copyright (C) 2006 Christian Walter <wolti@sil.at>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

/* ----------------------- 头文件包含 ----------------------------------------*/
#include "mb.h"
#include "mbport.h"
#include "mbconfig.h"

// <<< Use Configuration Wizard in Context Menu >>>
// <h> Modbus 整体配置
// <i> 整合核心配置与寄存器配置,支持图形化开关控制
// <h> 核心功能使能 (关联 mbconfig.h 配置)
// <q> 启用输入寄存器功能 (依赖 MB_FUNC_READ_INPUT_ENABLED)
#define MB_INPUT_REG_ENABLE       (MB_FUNC_READ_INPUT_ENABLED)
#define MB_HOLDING_REG_ENABLE     ((MB_FUNC_READ_HOLDING_ENABLED) || (MB_FUNC_WRITE_HOLDING_ENABLED) || (MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED))
#define MB_COIL_ENABLE            ((MB_FUNC_READ_COILS_ENABLED) || (MB_FUNC_WRITE_COIL_ENABLED) || (MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED))
#define MB_DISCRETE_ENABLE        (MB_FUNC_READ_DISCRETE_INPUTS_ENABLED)

// <h> 寄存器配置 (仅对应功能启用时生效)
// <h> 输入寄存器(功能码04)
#if MB_INPUT_REG_ENABLE == 1
// <o> 起始地址(1-based) <1-65535>
#define S_REG_INPUT_START         1000
// <o> 寄存器数量 <1-1000>
#define S_REG_INPUT_NREGS         100
// <i> 仅支持读操作,数量需与映射表匹配
#else
#define S_REG_INPUT_START         0
#define S_REG_INPUT_NREGS         0
#endif
// </h>

// <h> 保持寄存器(功能码03/06/16)
#if MB_HOLDING_REG_ENABLE == 1
// <o> 起始地址(1-based) <1-65535>
#define S_REG_HOLDING_START       1000
// <o> 寄存器数量 <1-1000>
#define S_REG_HOLDING_NREGS       100
// <i> 支持读写操作,权限由映射表控制
#else
#define S_REG_HOLDING_START       0
#define S_REG_HOLDING_NREGS       0
#endif
// </h>

// <h> 线圈(功能码01/05/15)
#if MB_COIL_ENABLE == 1
// <o> 起始地址(0-based) <0-65535>
#define S_COIL_START              0
// <o> 线圈数量 <1-1000>
#define S_COIL_NCOILS             64
// <i> 每个USHORT存储16个线圈状态
#else
#define S_COIL_START              0
#define S_COIL_NCOILS             0
#endif
// </h>

// <h> 离散量输入(功能码02)
#if MB_DISCRETE_ENABLE == 1
// <o> 起始地址(0-based) <0-65535>
#define S_DISCRETE_START          0
// <o> 离散量输入数量 <1-1000>
#define S_DISCRETE_NCOILS         64
// <i> 仅支持读操作,数量需与存储单元匹配
#else
#define S_DISCRETE_START          0
#define S_DISCRETE_NCOILS         0
#endif
// </h>
// </h>
// <<< end of configuration section >>>

/* ----------------------- 常量定义 (全局) ------------------------------------*/
#define MB_NULL                   (void*)0

/* ----------------------- 辅助宏定义 (全局) ----------------------------------*/
// 线圈16位存储单元数量(自动计算)
#if S_COIL_NCOILS > 0
#define S_COIL_16BIT_NUM          ((S_COIL_NCOILS + 15) / 16)  // 向上取整
#else
#define S_COIL_16BIT_NUM          0
#endif

// 离散量输入16位存储单元数量(自动计算)
#if S_DISCRETE_NCOILS > 0
#define S_DISCRETE_16BIT_NUM      ((S_DISCRETE_NCOILS + 15) / 16)  // 向上取整
#else
#define S_DISCRETE_16BIT_NUM      0
#endif

/* ----------------------- 输入寄存器相关定义 (全局) --------------------------*/
#if MB_INPUT_REG_ENABLE == 1
// 输入寄存器映射结构体
typedef struct {
    USHORT reg_addr;         // 寄存器地址(0偏移)
    USHORT *data_ptr;        // 数据存储指针
} mb_input_reg_map_t;

// 测试数据
USHORT test_input_01 = 10;
USHORT test_input_02 = 20;
USHORT test_input_03 = 30;

// 输入寄存器映射表
static mb_input_reg_map_t input_reg_maps[S_REG_INPUT_NREGS] = {
    {0, &test_input_01},  
    {1, &test_input_02},  
    {2, &test_input_03},  
    // 可添加更多输入寄存器映射...
};
#endif

/* ----------------------- 保持寄存器相关定义 (全局) --------------------------*/
#if MB_HOLDING_REG_ENABLE == 1
// 读写属性枚举
enum {
    RO,  // 只读
    RW   // 读写
};

// 保持寄存器映射结构体
typedef struct {
    USHORT reg_addr;               // 寄存器地址(0偏移)
    USHORT *data_ptr;              // 数据存储指针
    USHORT min;                    // 最小值限制
    USHORT max;                    // 最大值限制
    USHORT dft;                    // 默认值
    USHORT rw;                     // 读写属性(RO/RW)
    USHORT (*callback_ptr)(USHORT); // 写操作回调函数
} mb_holding_reg_map_t;

// 测试数据
USHORT test_holding_01 = 40;
USHORT test_holding_02 = 50;
USHORT test_holding_03 = 0;

// 保持寄存器回调函数示例
USHORT holding_reg_callback01(USHORT parm)
{
    test_holding_03++;  // 写入时自增计数
    return 0;
}

// 保持寄存器映射表
static mb_holding_reg_map_t holding_reg_maps[S_REG_HOLDING_NREGS] = {
    {0, &test_holding_01, 0x0000, 0xffff, 40, RW, holding_reg_callback01},
    {1, &test_holding_02, 0x0000, 0xffff, 50, RW, MB_NULL},
    {2, &test_holding_03, 0x0000, 0xffff, 0,  RO, MB_NULL}
};
#endif

/* ----------------------- 线圈相关定义 (全局) --------------------------------*/
#if MB_COIL_ENABLE == 1
// 线圈映射结构体(每个单元16个线圈)
typedef struct {
    USHORT start_addr;               // 起始地址(0基)
    USHORT *coil_buf;                // 16位数据缓冲区指针
    USHORT rw_mask;                  // 权限掩码(bit=1表示可写)
    BOOL (*callback)(UCHAR, BOOL);   // 状态变化回调(bit位置, 新状态)
} mb_coil_reg_map_t;

// 16位线圈数据缓冲区(每个USHORT存储16个线圈状态)
static USHORT test_coils[S_COIL_16BIT_NUM] = {
    0x1111,  // 线圈0~15
    0x2222,  // 线圈16~31
    0x3333,  // 线圈32~47
    0x4444   // 线圈48~63
};

// 线圈0~15的回调函数
BOOL callback_coils_0_15(UCHAR bit_pos, BOOL state) {
    #if MB_HOLDING_REG_ENABLE == 1
        test_holding_03++;  // 状态变化时自增计数(依赖保持寄存器启用)
    #endif
    return TRUE;
}

// 线圈映射表
static mb_coil_reg_map_t coil_reg_maps[S_COIL_16BIT_NUM] = {
    {0,  &test_coils[0], 0xFFFF, callback_coils_0_15},  // 0~15: 全可写
    {16, &test_coils[1], 0xFFFF, MB_NULL},              // 16~31: 全可写
    {32, &test_coils[2], 0x0000, MB_NULL},              // 32~47: 全只读
    {48, &test_coils[3], 0xFFFF, MB_NULL}               // 48~63: 全可写
};
#endif

/* ----------------------- 离散量输入相关定义 (全局) --------------------------*/
#if MB_DISCRETE_ENABLE == 1
// 离散量输入映射结构体
typedef struct {
    USHORT start_bit_addr;  // 起始bit地址(0基)
    USHORT *data_ptr;       // 16位数据存储指针
} mb_discrete_reg_map_st;

// 离散量输入状态存储(每个USHORT存储16个输入状态)
static USHORT test_discrete_01 = 0xAAAA;  // 离散量输入0~15
static USHORT test_discrete_02 = 0x5555;  // 离散量输入16~31
static USHORT test_discrete_03 = 0x3333;  // 离散量输入32~47
static USHORT test_discrete_04 = 0x1111;  // 离散量输入48~63

// 离散量输入映射表
static mb_discrete_reg_map_st discrete_reg_maps[S_DISCRETE_16BIT_NUM] = {
    {0,  &test_discrete_01},
    {16, &test_discrete_02},
    {32, &test_discrete_03},
    {48, &test_discrete_04}
};
#endif

/* ----------------------- 回调函数实现 --------------------------------------*/
/* 输入寄存器回调函数 (功能码04) */
eMBErrorCode eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{
    eMBErrorCode eStatus = MB_ENOERR;

    #if MB_INPUT_REG_ENABLE == 1
        USHORT iRegIndex = 0;
        mb_input_reg_map_t *p_reg = input_reg_maps;

        usAddress--;  // 1基地址转0基

        // 地址范围验证
        if ((usAddress >= S_REG_INPUT_START) && 
            (usAddress + usNRegs <= S_REG_INPUT_START + S_REG_INPUT_NREGS))
        {
            iRegIndex = usAddress - S_REG_INPUT_START;
            // 按Modbus格式填充数据(高8位在前)
            while (usNRegs--)
            {
                if (p_reg[iRegIndex].data_ptr != MB_NULL)
                {
                    *pucRegBuffer++ = (UCHAR)((*(p_reg[iRegIndex].data_ptr)) >> 8);
                    *pucRegBuffer++ = (UCHAR)((*(p_reg[iRegIndex].data_ptr)) & 0xFF);
                }
                else
                {
                    *pucRegBuffer++ = 0x00;
                    *pucRegBuffer++ = 0x00;
                }
                iRegIndex++;
            }
        }
        else
        {
            eStatus = MB_ENOREG;  // 地址无效
        }
    #else
        eStatus = MB_ENOREG;  // 功能未启用
    #endif

    return eStatus;
}

/* 保持寄存器回调函数 (功能码03/06/16) */
eMBErrorCode eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, 
                            USHORT usNRegs, eMBRegisterMode eMode)
{
    eMBErrorCode eStatus = MB_ENOERR;

    #if MB_HOLDING_REG_ENABLE == 1
        USHORT iRegIndex;
        USHORT Writ_Value;
  
        usAddress--;  // 1基地址转0基

        // 地址范围验证
        if ((usAddress >= S_REG_HOLDING_START) && 
            (usAddress + usNRegs <= S_REG_HOLDING_START + S_REG_HOLDING_NREGS))
        {
            iRegIndex = usAddress - S_REG_HOLDING_START;
            
            switch (eMode)
            {
                case MB_REG_READ:  // 读操作
                    while (usNRegs--)
                    {
                        if (holding_reg_maps[iRegIndex].data_ptr != MB_NULL)
                        {
                            *pucRegBuffer++ = (UCHAR)((*holding_reg_maps[iRegIndex].data_ptr) >> 8);
                            *pucRegBuffer++ = (UCHAR)((*holding_reg_maps[iRegIndex].data_ptr) & 0xFF);
                        }
                        else
                        {
                            *pucRegBuffer++ = 0x00;
                            *pucRegBuffer++ = 0x00;
                        }
                        iRegIndex++;
                    }
                    break;
                    
                case MB_REG_WRITE:  // 写操作
                    while (usNRegs--)
                    {
                        if (holding_reg_maps[iRegIndex].rw == RW)  // 检查写权限
                        {
                            // 合成16位数据(高8位+低8位)
                            Writ_Value = (*pucRegBuffer << 8) | *(pucRegBuffer + 1);
                            pucRegBuffer += 2;

                            // 范围验证
                            if (Writ_Value >= holding_reg_maps[iRegIndex].min && 
                                Writ_Value <= holding_reg_maps[iRegIndex].max)
                            {
                                // 执行回调函数
                                if (holding_reg_maps[iRegIndex].callback_ptr != MB_NULL)
                                {
                                    holding_reg_maps[iRegIndex].callback_ptr(Writ_Value);
                                }
                                *holding_reg_maps[iRegIndex].data_ptr = Writ_Value;
                                iRegIndex++;
                            }
                            else
                            {
                                eStatus = MB_EINVAL;  // 数据超出范围
                                break;
                            }
                        }
                        else
                        {
                            eStatus = MB_EINVAL;  // 无写权限
                            break;
                        }
                    }
                    break;
            }
        }
        else
        {
            eStatus = MB_ENOREG;  // 地址无效
        }
    #else
        eStatus = MB_ENOREG;  // 功能未启用
    #endif

    return eStatus;
}

/* 线圈回调函数 (功能码01/05/15) */
eMBErrorCode eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress,
                          USHORT usNCoils, eMBRegisterMode eMode)
{
    eMBErrorCode eStatus = MB_ENOERR;

    #if MB_COIL_ENABLE == 1
        USHORT usGlobalBitOffset;   // 当前起始地址的全局bit偏移
        USHORT usTotalProcessed = 0;// 已处理的bit数量
        USHORT usCurrByteIdx;       // 当前字节索引
        USHORT usBitInByte;         // 字节内bit位置(0-7)
        USHORT usProcessBits;       // 本次处理bit数
        USHORT usWrtMask;           // 写权限掩码
        UCHAR *pCoilByteBuf;        // 线圈缓冲区字节指针

        usAddress--;  // 1基地址转0基

        // 地址范围验证
        if (usAddress + usNCoils > S_COIL_START + S_COIL_NCOILS)
        {
            return MB_ENOREG;
        }

        usGlobalBitOffset = usAddress - S_COIL_START;

        // 循环处理所有线圈
        while (usTotalProcessed < usNCoils)
        {
            usCurrByteIdx = usGlobalBitOffset / 8;    // 计算字节索引
            usBitInByte = usGlobalBitOffset % 8;      // 计算字节内bit位置

            // 获取当前处理的字节指针(将16位缓冲区视为字节数组)
            pCoilByteBuf = (UCHAR*)coil_reg_maps[usCurrByteIdx / 2].coil_buf + (usCurrByteIdx % 2);

            // 计算本次可处理的bit数(不超过当前字节剩余空间)
            usProcessBits = 8 - usBitInByte;
            if (usProcessBits > usNCoils - usTotalProcessed)
            {
                usProcessBits = usNCoils - usTotalProcessed;
            }

            switch (eMode)
            {
                case MB_REG_READ:  // 读线圈
                    *pucRegBuffer++ = xMBUtilGetBits(pCoilByteBuf, usBitInByte, (UCHAR)usProcessBits);
                    break;

                case MB_REG_WRITE:  // 写线圈
                    // 检查写权限掩码
                    usWrtMask = ((1 << usProcessBits) - 1) << usBitInByte;
                    if ((coil_reg_maps[usCurrByteIdx / 2].rw_mask & usWrtMask) != usWrtMask)
                    {
                        return MB_EINVAL;  // 包含只读bit
                    }

                    // 写入线圈状态
                    xMBUtilSetBits(pCoilByteBuf, usBitInByte, (UCHAR)usProcessBits, *pucRegBuffer++);

                    // 执行回调函数
                    if (coil_reg_maps[usCurrByteIdx / 2].callback != MB_NULL)
                    {
                        USHORT usBitInInt = (usCurrByteIdx % 2) * 8 + usBitInByte;
                        BOOL bState = (*coil_reg_maps[usCurrByteIdx / 2].coil_buf >> usBitInInt) & 0x01;
                        coil_reg_maps[usCurrByteIdx / 2].callback(usBitInInt, bState);
                    }
                    break;

                default:
                    return MB_EINVAL;
            }

            // 更新处理计数
            usTotalProcessed += usProcessBits;
            usGlobalBitOffset += usProcessBits;
        }
    #else
        eStatus = MB_ENOREG;  // 功能未启用
    #endif

    return eStatus;
}

/* 离散量输入回调函数 (功能码02) */
eMBErrorCode eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
{
    eMBErrorCode eStatus = MB_ENOERR;

    #if MB_DISCRETE_ENABLE == 1
        USHORT usAddr0 = usAddress - 1;               // 1基地址转0基
        USHORT usGlobalBitOff = usAddr0 - S_DISCRETE_START;  // 全局bit偏移
        USHORT usTotalProcessed = 0;
        USHORT usMapIdx, usBitInMap;
        USHORT usCurrByteIdx = 0;
        UCHAR ucTempByte = 0;

        // 地址范围验证
        if (usAddr0 + usNDiscrete > S_DISCRETE_START + S_DISCRETE_NCOILS)
        {
            return MB_ENOREG;
        }

        // 循环处理所有离散量输入
        while (usTotalProcessed < usNDiscrete)
        {
            usMapIdx = usGlobalBitOff / 16;       // 映射表索引
            usBitInMap = usGlobalBitOff % 16;     // 16位单元内bit位置

            // 读取bit状态
            BOOL bBitState = (*discrete_reg_maps[usMapIdx].data_ptr >> usBitInMap) & 0x01;

            // 组合为字节(8个bit一组)
            ucTempByte |= (bBitState << (usTotalProcessed % 8));

            // 每8个bit或处理完毕时写入缓冲区
            if (((usTotalProcessed + 1) % 8 == 0) || (usTotalProcessed + 1 == usNDiscrete))
            {
                pucRegBuffer[usCurrByteIdx++] = ucTempByte;
                ucTempByte = 0;
            }

            usTotalProcessed++;
            usGlobalBitOff++;
        }
    #else
        eStatus = MB_ENOREG;  // 功能未启用
    #endif

    return eStatus;
}

mbconfig.h

/* 
 * FreeModbus Libary: A portable Modbus implementation for Modbus ASCII/RTU.
 * Copyright (c) 2006-2018 Christian Walter <cwalter@embedded-solutions.at>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#ifndef _MB_CONFIG_H
#define _MB_CONFIG_H

#ifdef __cplusplus
PR_BEGIN_EXTERN_C
#endif

// <<< Use Configuration Wizard in Context Menu >>>
// <h> FreeModbus 核心配置
// <i> Modbus 协议栈配置文件,支持图形化勾选/修改参数
// <i> 未启用的功能会被编译器自动剔除,节省存储空间

// <h> 传输模式配置
// <i> 选择 Modbus 传输模式(ASCII/RTU/TCP),仅能启用一种
// <q> 启用 Modbus ASCII 模式
#define MB_ASCII_ENABLED                        0
// <q> 启用 Modbus RTU 模式
#define MB_RTU_ENABLED                          1
// <q> 启用 Modbus TCP 模式
#define MB_TCP_ENABLED                          0
// </h>

// <h> ASCII 模式专用配置 (仅 ASCII 模式启用时生效)
#if MB_ASCII_ENABLED == 1
// <o> Modbus ASCII 字符超时时间(秒) <0-10>
// <i> 配置网络最大预期延迟时间,ASCII 模式专用
#define MB_ASCII_TIMEOUT_SEC                    0

// <o> ASCII 模式发送前等待时间(毫秒) <0-100>
// <i> 启用发送器前的延迟,避免接收方未准备好导致响应丢失
#define MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS    0
#else
// ASCII 模式未启用时,使用默认值避免编译警告
#define MB_ASCII_TIMEOUT_SEC                    0
#define MB_ASCII_TIMEOUT_WAIT_BEFORE_SEND_MS    0
#endif
// </h>

// <h> 功能码支持配置
// <i> 勾选需要支持的 Modbus 功能码,未勾选则不编译对应功能
// <q> 启用 报告从站ID 功能 (0x11)
#define MB_FUNC_OTHER_REP_SLAVEID_ENABLED       1
// <q> 启用 读输入寄存器 功能 (0x04)
#define MB_FUNC_READ_INPUT_ENABLED              1
// <q> 启用 读保持寄存器 功能 (0x03)
#define MB_FUNC_READ_HOLDING_ENABLED            1
// <q> 启用 写单个保持寄存器 功能 (0x06)
#define MB_FUNC_WRITE_HOLDING_ENABLED           1
// <q> 启用 写多个保持寄存器 功能 (0x10)
#define MB_FUNC_WRITE_MULTIPLE_HOLDING_ENABLED  1
// <q> 启用 读线圈 功能 (0x01)
#define MB_FUNC_READ_COILS_ENABLED              1
// <q> 启用 写单个线圈 功能 (0x05)
#define MB_FUNC_WRITE_COIL_ENABLED              1
// <q> 启用 写多个线圈 功能 (0x0F)
#define MB_FUNC_WRITE_MULTIPLE_COILS_ENABLED    1
// <q> 启用 读离散量输入 功能 (0x02)
#define MB_FUNC_READ_DISCRETE_INPUTS_ENABLED    1
// <q> 启用 读写多个保持寄存器 功能 (0x17)
#define MB_FUNC_READWRITE_HOLDING_ENABLED       1
// </h>

// <h> 功能码处理器配置
// <o> 最大支持的功能码数量 <8-32>
// <i> 需大于所有启用的功能码数量 + 自定义功能码数量,避免添加功能失败
#define MB_FUNC_HANDLERS_MAX                    16

// <o> 报告从站ID 功能数据缓冲区大小(字节) <16-128>
// <i> 仅在 MB_FUNC_OTHER_REP_SLAVEID_ENABLED 启用时生效
#define MB_FUNC_OTHER_REP_SLAVEID_BUF           32
// </h>

// </h>
// <<< end of configuration section >>>

#ifdef __cplusplus
    PR_END_EXTERN_C
#endif
#endif

在这里插入图片描述

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐