第四篇、CubeMX | FreeModbus 从机 标准应用开发(无OS,裸机移植)
·
一、写在前面
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

更多推荐


所有评论(0)