以“设备当前波特率为 9600,主站把保持寄存器 40002 写成 0x13,切换到 19200”为例,完整流程如下。
主站以旧波特率发送写命令
↓
FreeModbus 解析请求
↓
写入 holding_registers[波特率代码]
↓
设置 holding_dirty
↓
先以旧波特率发送成功响应
↓
响应发送完成
↓
setBaudrateCode() 更新 APP 配置
↓
停止旧 Modbus 栈
↓
按新波特率重新初始化
↓
延迟写入 Flash1. 主站发送写请求
波特率代码位于:
MODBUS_HOLDING_BAUDRATE_CODE = 1也就是:
- 显示地址:
40002 - Modbus PDU 地址:
1 - 允许值:
| 代码 | 波特率 |
|---|---|
0x11 | 4800 |
0x12 | 9600 |
0x13 | 19200 |
0x14 | 115200 |
假设当前设备使用 9600,主站通过功能码 06 写入:
从站地址:1
功能码:06
寄存器地址:0001
寄存器值:0013这条命令必须使用当前的旧波特率 9600 发送。
2. FreeModbus 收到完整帧
USART 中断逐字节接收数据,RTU 的 T3.5 定时器判断一帧结束,然后产生:
EV_FRAME_RECEIVED主循环持续执行:
while(1) {
(void)eMBPoll();
app_measurement_task();
modbus_app_task();
alarm_task();
}位置:main.c
eMBPoll() 收到完整帧后,检查从站地址,然后产生 EV_EXECUTE 事件。
3. 执行功能码 06
下一次 eMBPoll() 处理 EV_EXECUTE,找到功能码处理函数:
eMBFuncWriteHoldingRegister()FreeModbus 从请求帧中取出地址和值:
usRegAddress = ...;
usRegAddress++;这里 PDU 地址 1 加一后,以地址 2 传给应用回调:
eMBRegHoldingCB(buffer, 2, 1, MB_REG_WRITE);4. 应用回调解析数据
进入 modbus_app.c:
eMBErrorCode eMBRegHoldingCB(UCHAR *buffer,
USHORT address,
USHORT count,
eMBRegisterMode mode)首先转换成数组下标:
start = address - 1U;因此:
address = 2
start = 1正好对应:
holding_registers[MODBUS_HOLDING_BAUDRATE_CODE]然后把 Modbus 大端字节转换成 uint16_t:
values[i] = ((uint16_t)buffer[0] << 8) | buffer[1];得到:
values[0] = 0x0013;最后调用:
modbus_map_write_holding(1, 1, values);5. 校验并更新保持寄存器映像
进入 modbus_map_write_holding()。
它先检查地址范围,然后创建候选数组:
memcpy(candidate, holding_registers, sizeof(candidate));
memcpy(&candidate[start], values,
quantity * sizeof(uint16_t));这样做是为了先验证,不立即破坏原数组。
波特率代码校验逻辑是:
if(address == MODBUS_HOLDING_BAUDRATE_CODE) {
return value >= 0x11U && value <= 0x14U;
}0x13 合法,所以正式写入:
holding_registers[1] = 0x13;并设置脏标志:
holding_dirty |= DIRTY_BIT(1);相当于:
holding_dirty |= 1U << 1;此时要特别注意:
holding_registers[] 已经是 0x13
APP config 仍然是旧的 0x12
USART 仍然运行在旧波特率 9600这里只是更新了 Modbus 映像,还没有真正修改串口。
6. 先以旧波特率返回响应
回调成功后,FreeModbus 生成功能码 06 的响应。功能码 06 的正常响应基本是原请求回显:
从站地址:1
功能码:06
寄存器地址:0001
寄存器值:0013
CRC此时协议栈和 USART 仍然使用旧波特率 9600,所以响应也以 9600 发送。
开始发送后:
tx_active = TRUE;随后主循环虽然调用了 modbus_app_task(),但会被这里拦住:
if(!xMBPortSerialIsIdle()) {
return;
}因此响应没有发完之前,不会修改波特率。
这是流程中最关键的一点。否则设备可能发送到一半突然从 9600 切到 19200,主站就会收到乱码或半帧。
7. 响应发送完成
USART 发送中断把响应的所有字节发送完毕,随后:
vMBPortSerialEnable(TRUE, FALSE);这个函数会:
- 关闭发送空中断。
- 等待最后一个字节真正移出 USART。
- 将 RS485 切换回接收方向。
- 设置:
tx_active = FALSE;此时主站已经完整收到旧波特率下的成功响应。
8. modbus_map_task() 应用新配置
下一轮主循环再次进入:
modbus_app_task();现在串口已经空闲,所以继续执行:
actions = modbus_map_task();modbus_map_task() 调用:
actions = apply_holding_changes();apply_holding_changes() 发现波特率对应的 dirty 位:
if(dirty & DIRTY_BIT(MODBUS_HOLDING_BAUDRATE_CODE)) {
setBaudrateCode(
(uint8_t)holding_registers[
MODBUS_HOLDING_BAUDRATE_CODE
]
);
actions |= MODBUS_MAP_ACTION_RECONFIGURE;
}于是执行:
setBaudrateCode(0x13);9. 修改 APP 配置
setBaudrateCode() 在 APP.c 中:
void setBaudrateCode(uint8_t code)
{
if(code >= 0x11U &&
code <= 0x14U &&
config.baudrate_code != code) {
config.baudrate_code = code;
config_mark_dirty();
}
}执行后:
config.baudrate_code = 0x13
config_dirty = 1现在 APP 配置已经变成新波特率代码,但 USART 尚未重新初始化。
同时返回动作:
MODBUS_MAP_ACTION_RECONFIGURE10. 关闭旧协议栈
回到 modbus_app_task():
if(actions & MODBUS_MAP_ACTION_RECONFIGURE) {
(void)eMBDisable();
(void)start_stack();
}首先:
eMBDisable();它会停止旧 Modbus RTU 栈:
vMBPortSerialEnable(FALSE, FALSE);
vMBPortTimersDisable();也就是:
- 关闭 USART 接收中断
- 关闭 USART 发送中断
- 关闭 RTU 帧间隔定时器
- 将协议栈状态改成
STATE_DISABLED
11. 用新波特率启动协议栈
随后执行:
start_stack();其中:
status = eMBInit(
MB_RTU,
GetID(),
0U,
GetBaudrate(),
MB_PAR_NONE
);GetBaudrate() 会读取刚更新的:
config.baudrate_code == 0x13然后通过:
code2baudrate(0x13)得到:
19200最终重新初始化串口:
eMBInit(..., 19200, ...);
eMBEnable();从这一刻开始,设备只使用新波特率 19200 接收后续 Modbus 请求,主站也需要切换到 19200。
12. 延迟保存到 Flash
setBaudrateCode() 只是修改了 RAM 中的:
static Config_t config;并设置:
config_dirty = 1;在后面的循环里,app_config_task() 判断配置已经保持稳定至少 500 ms 后:
if(config_dirty &&
g_system_ticks - config_dirty_tick >= 500U) {
save_config(&config);
}这样会把新波特率保存到 Flash,保证下次重新上电仍然使用 19200。
所以存在一个短暂阶段:
新波特率已经生效
但尚未保存到 Flash如果这 500 ms 内突然断电,下次启动可能仍使用旧波特率。
非法值时的流程
如果主站写入:
0x15校验会失败:
value >= 0x11 && value <= 0x14不成立,因此:
- 不更新
holding_registers[] - 不设置
holding_dirty - 不调用
setBaudrateCode() - 不重启协议栈
- 不修改 Flash
- 主站收到 Modbus 异常码
03:非法数据值
异常响应仍然使用旧波特率发送。
最终,修改波特率采用的是一种很合理的“两阶段提交”:
第一阶段:校验并更新 Modbus 映像,使用旧波特率回复主站
第二阶段:回复完成后,把配置应用到 APP,再以新波特率重启协议栈
还没有评论,来抢沙发吧