你的判断是对的:比赛现场最值得熟悉的是“寄存器映射层 + 业务绑定层”,FreeModbus 协议栈本身一般不要临场修改。
整体调用链可以记成:
eMBPoll() → 四类寄存器回调 → modbus_map_*() → Get/Set业务函数 → ADC、RTC、DAC、Flash 等硬件功能
第一优先级:寄存器表定义
重点看 modbus_map.h。
这里的四个枚举直接决定四类寄存器的数量和顺序:
modbus_input_address_t:输入寄存器,功能码 04modbus_holding_address_t:保持寄存器,功能码 03/06/10modbus_coil_address_t:线圈,功能码 01/05/0Fmodbus_discrete_address_t:离散输入,功能码 02
每个枚举末尾的:
MODBUS_INPUT_COUNTMODBUS_HOLDING_COUNTMODBUS_COIL_COUNTMODBUS_DISCRETE_COUNT
就是对应区域的总数量。
现场增减、调整顺序,首先改这里。由于枚举地址连续递增,把一个项目插入中间,后面所有地址都会自动后移。
特别注意:
float和uint32_t占两个连续寄存器。- 必须保持
XXX_HI、XXX_LO相邻,且 HI 在前。 - 上位机 PDU 地址从 0 开始;显示地址通常从 30001/40001/00001/10001 开始。
第二优先级:映射内容与业务功能
核心文件是 modbus_map.c,这是比赛时最可能改动的文件。
输入寄存器、离散输入
看 update_measurement_snapshot():
GetTime()→ 时间输入寄存器GetCH0()/GetCH1()/GetCH2Temp()→ 测量值输入寄存器IsCH0Alarm()/IsCH1Alarm()→ 离散输入和报警位
新增只读测量数据时,一般需要:
- 在
modbus_map.h增加地址枚举。 - 在
update_measurement_snapshot()中把业务值写入对应寄存器。 - 如果是 32 位值,用
registers_set_u32()。 - 如果是浮点值,用
registers_set_float()。
保持寄存器
需要同时理清三个阶段。
初始化值在 modbus_map_init():
- 决定设备启动时保持寄存器显示什么。
- 从业务层的
GetID()、GetRatio0()等函数获取当前配置。
写入合法性在:
holding_scalar_is_valid():单寄存器范围,例如 ID、波特率、DAC。holding_candidate_is_valid():浮点值、组合字段的合法性检查。
写入后真正执行功能在 apply_holding_changes():
- 寄存器地址 →
setID() - 寄存器地址 →
setBaudrateCode() - 寄存器地址 →
setRatio0() - 寄存器地址 →
setThreshold0() - 寄存器地址 →
setDAC()
因此,新增一个可写保持寄存器时,通常至少要修改:
- 地址枚举。
modbus_map_init()初始值。- 合法性检查。
apply_holding_changes()中的业务函数调用。
线圈
看:
modbus_map_write_coils():线圈地址对应什么命令。modbus_map_task():命令在主循环中何时真正执行。
当前三个线圈分别设置:
reboot_pendingsleep_pendingapply_time_pending
当前设计是“命令线圈”:读回永远是 0,写 1 触发一次动作。新增动作时需要增加:
- 线圈枚举。
- pending 标志。
modbus_map_write_coils()的switch分支。modbus_map_task()中的实际处理。
第三优先级:四类寄存器回调
看 modbus_app.c:
eMBRegInputCB()eMBRegHoldingCB()eMBRegCoilsCB()eMBRegDiscreteCB()
这些函数负责:
- 将 FreeModbus 的地址转换成映射表地址。
- 处理寄存器高低字节。
- 打包、拆包线圈位。
- 调用
modbus_map_read_*()、modbus_map_write_*()。 - 将映射层错误转换成 Modbus 异常码。
只改变数量、顺序和功能时,这四个函数通常不需要修改,因为数组大小都引用了 MODBUS_*_COUNT。
但一定要理解这一句:
start = address - 1U;FreeModbus 调用回调时传入的是从 1 开始的内部地址,映射层使用从 0 开始的 PDU 地址。不要因为上位机写的是地址 0,就把这里的减 1 删除。
还要理解数据顺序:
32位数据:高16位寄存器 → 低16位寄存器
单个寄存器:高字节 → 低字节第四优先级:业务 Getter/Setter
这里决定寄存器最后对应哪个真实功能,例如:
GetCH0():读取测量结果setTime():设置 RTCsetDAC():设置 DACsetThreshold0():修改报警阈值reboot()/sleep():执行一次性动作
如果现场只要求“更换某个寄存器对应的功能”,通常就是修改 modbus_map.c 中的映射调用,再确认这里已有对应 Getter/Setter。
如果没有,就在 APP.h/.c 增加,尽量不要让 modbus_map.c 直接操作底层 ADC、RTC、GPIO。
持久化配置还要关注
如果新增的保持寄存器要求掉电保存,还要修改:
- app_config.h 中的
Config_t - app_config.c 中的默认值和加载合法性检查
APP.c中相应的 Getter/Setter,并在 Setter 中调用config_mark_dirty()
否则寄存器虽然可以写、功能也能执行,但重启后会恢复。
两个容易踩的限制
第一,当前保持寄存器脏标志是:
static uint32_t holding_dirty;
#define DIRTY_BIT(address) (1U << address)而且有 MODBUS_HOLDING_COUNT <= 32 的编译期限制。因此保持寄存器超过 32 个时,必须把脏标志改成数组、64 位整数或其他结构,不能只改 MODBUS_HOLDING_COUNT。
第二,增加持久化字段会改变 Config_t 的 Flash 数据布局。旧配置页可能被按照新结构错误解释。正式比赛前最好同步修改 CONFIG_MAGIC,让旧配置失效并重新加载默认值。
一般不需要临场修改
Modbus/freemodbus/:协议状态机、CRC、标准功能码。Modbus/port/:串口、RS-485 DE、T3.5 定时器。- mbconfig.h:只有需要启用/关闭功能码、RTU/ASCII/TCP 时才改。
start_stack():只有从站地址、波特率、校验方式发生规则变化时才重点看。- main.c:只要保持循环调用
eMBPoll()、测量任务和modbus_app_task()即可。
最实用的复习顺序是:
- 背熟
modbus_map.h四张地址表。 - 理清
update_measurement_snapshot()。 - 理清
modbus_map_init()。 - 理清
holding_candidate_is_valid()和apply_holding_changes()。 - 理清
modbus_map_write_coils()和modbus_map_task()。 - 最后理解四个
eMBReg*CB()的地址转换与字节序。
仓库里的 APP/README.md 已经列出了当前完整寄存器表,可以把它作为比赛现场的第一份对照表。
还没有评论,来抢沙发吧