标准功能码仍为 0x01、0x03、0x05、0x06、0x0F、0x10。线圈和保持寄存器地址范围均为 0x0000..0x270F。
以下三个功能码是本训练项目的私有扩展,不属于 Modbus 国标,只能由本项目 TouchWin 与 PLC 配套使用:
| 功能码 | 用途 | 请求数据 | 限制 |
|---|---|---|---|
0x41 |
读取 0x00010000 |
32 位地址、16 位数量 | 地址固定为 0x10000,数量固定为 1 |
0x42 |
写入 0x00010000 |
32 位地址、16 位写入值 | 地址固定为 0x10000,正常响应回显地址和值 |
0x43 |
读取奇数地址的寄存器 | 16 位起始基准地址、16 位数量 | 数量为 1..123,奇数和偶数均可 |
0x43 按地址 +2 跳读,只返回奇数地址的值。起始基准地址会自动向上取奇数:基准地址为 0、数量为 123 时,读取地址 1, 3, 5, ..., 245;基准地址为 10 时,读取 11, 13, 15, ...。数量表示返回值个数,允许 1..123 范围内的奇数或偶数。
PSW319 为状态码,PSW320 为实际接收长度。附加画面使用:
| 用途 | PSW |
|---|---|
0x41 读取结果 |
PSW350 |
0x42 写入值 |
PSW351 |
0x43 起始基准地址、数量 |
PSW370、PSW371 |
0x43 结果 |
PSW372..PSW494,共 123 个 WORD |
PSW371 可以输入 1..123,奇数和偶数均可。PSW370 = 0 时,PSW372 对应 PLC 的 D1,PSW373 对应 D3,依次类推;PSW370 = 10 时,结果依次对应 D11、D13、D15。若要在一个画面显示全部批量读取结果,需要 123 个数值显示组件,依次绑定 PSW372 至 PSW494。用于演示时,可以只放前 8 或前 16 个显示组件,全部 123 个结果仍会写入 PSW。
状态码:0 成功;1 输入错误;2 发送失败;3 接收失败;4 CRC 错误;5 应答错误;0x8000 以上表示异常应答,低字节为异常码。
#define MODBUS_SLAVE_ADDRESS (1)
#define MODBUS_FUNCTION_READ_EXTENDED_REGISTER (0x41)
#define MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER (0x42)
#define MODBUS_FUNCTION_READ_ODD_COUNT_REGS (0x43)
#define MODBUS_ODD_COUNT_MAX (123)
#define MODBUS_MIN_RESPONSE_LENGTH (5)
#define MODBUS_ADDRESS_MAX (0x270F)
#define PSW_STATUS (319)
#define PSW_RECEIVE_LENGTH (320)
#define PSW_EXTENDED_RESULT (350)
#define PSW_EXTENDED_WRITE_VALUE (351)
#define PSW_ODD_START_ADDRESS (370)
#define PSW_ODD_COUNT (371)
#define PSW_ODD_RESULTS (372)
以下示例使用已有的 ModbusAppendCrc、ModbusSendReceive 和 ModbusCheckResponse 公共函数。
TouchWin 粘贴规则:以上公共定义和公共函数写入“公共函数”页。以下四段代码写入“函数功能块”中新建的功能函数编辑区,只粘贴代码块内部内容,不要写
void 函数名(void),也不要写最外层的{、}。功能函数名称必须是合法 C 标识符,不能使用0x41、0x42、0x43作为函数名。请依次新建并命名为:
ReadExtendedRegister、WriteExtendedRegister、ClearExtendedResult、ReadOddCountRegisters。其中0x41、0x42、0x43只用于request[1]中的 Modbus 功能码。
ReadExtendedRegister:0x41 读取 0x10000地址和数量固定,不在画面输入:
WORD receiveLength;
WORD status;
BYTE request[10];
BYTE response[7];
request[0] = MODBUS_SLAVE_ADDRESS;
request[1] = MODBUS_FUNCTION_READ_EXTENDED_REGISTER;
request[2] = 0x00;
request[3] = 0x01;
request[4] = 0x00;
request[5] = 0x00;
request[6] = 0x00;
request[7] = 0x01;
ModbusAppendCrc(request, 8);
receiveLength = 0;
status = ModbusSendReceive(request, 10, response, 7, &receiveLength);
if (status == MODBUS_STATUS_OK)
{
status = ModbusCheckResponse(response, receiveLength,
MODBUS_FUNCTION_READ_EXTENDED_REGISTER);
}
if ((status == MODBUS_STATUS_OK) &&
((receiveLength != 7) || (response[2] != 2)))
{
status = MODBUS_STATUS_RESPONSE_ERROR;
}
if (status == MODBUS_STATUS_OK)
{
PSW[PSW_EXTENDED_RESULT] = ((WORD)response[3] << 8) | response[4];
}
PSW[PSW_STATUS] = status;
PSW[PSW_RECEIVE_LENGTH] = receiveLength;
WriteExtendedRegister:0x42 写入 0x10000写入值从 PSW351 取得。请求和正常响应均为 10 字节,正常响应回显 32 位地址与 16 位写入值:
WORD receiveLength;
WORD status;
WORD writeValue;
BYTE request[10];
BYTE response[10];
writeValue = PSW[PSW_EXTENDED_WRITE_VALUE];
request[0] = MODBUS_SLAVE_ADDRESS;
request[1] = MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER;
request[2] = 0x00;
request[3] = 0x01;
request[4] = 0x00;
request[5] = 0x00;
request[6] = (BYTE)(writeValue >> 8);
request[7] = (BYTE)writeValue;
ModbusAppendCrc(request, 8);
receiveLength = 0;
status = ModbusSendReceive(request, 10, response, 10, &receiveLength);
if (status == MODBUS_STATUS_OK)
{
status = ModbusCheckResponse(response, receiveLength,
MODBUS_FUNCTION_WRITE_EXTENDED_REGISTER);
}
if ((status == MODBUS_STATUS_OK) &&
((receiveLength != 10) || (response[2] != request[2]) ||
(response[3] != request[3]) || (response[4] != request[4]) ||
(response[5] != request[5]) || (response[6] != request[6]) ||
(response[7] != request[7])))
{
status = MODBUS_STATUS_RESPONSE_ERROR;
}
PSW[PSW_STATUS] = status;
PSW[PSW_RECEIVE_LENGTH] = receiveLength;
PLC 收到并成功处理 0x42 后,会将 0x10000 的最新值保存到 RTC 备份区。
ClearExtendedResult:清除读取显示清除按钮绑定以下函数。该函数只将触摸屏上的读取结果清零,不发送通信帧,不修改 PLC 的 0x10000,也不触发掉电保存:
PSW[PSW_EXTENDED_RESULT] = 0;
用于验证掉电保持时,操作顺序为:使用 0x42 写入非零值,调用清除函数,PLC 断电再上电,最后使用 0x41 读取;若读回原写入值,则掉电保持生效。
ReadOddCountRegisters:0x43 读取奇数地址寄存器起始基准地址可以是任意合法地址,PLC 会从第一个不小于该值的奇数地址开始,每次地址加 2。数量允许 1..123 范围内的奇数或偶数。响应容量为 251 字节,满足 123 个 WORD 的数据:
WORD startAddress;
WORD firstOddAddress;
WORD registerCount;
WORD registerOffset;
WORD receiveLength;
WORD expectedLength;
WORD status;
BYTE request[8];
BYTE response[251];
startAddress = PSW[PSW_ODD_START_ADDRESS];
firstOddAddress = (WORD)(startAddress | 0x0001);
registerCount = PSW[PSW_ODD_COUNT];
receiveLength = 0;
status = MODBUS_STATUS_INPUT_ERROR;
if ((registerCount != 0) && (registerCount <= MODBUS_ODD_COUNT_MAX) &&
(firstOddAddress <= MODBUS_ADDRESS_MAX) &&
(registerCount <= (((MODBUS_ADDRESS_MAX - firstOddAddress) / 2) + 1)))
{
request[0] = MODBUS_SLAVE_ADDRESS;
request[1] = MODBUS_FUNCTION_READ_ODD_COUNT_REGS;
request[2] = (BYTE)(startAddress >> 8);
request[3] = (BYTE)startAddress;
request[4] = (BYTE)(registerCount >> 8);
request[5] = (BYTE)registerCount;
ModbusAppendCrc(request, 6);
expectedLength = MODBUS_MIN_RESPONSE_LENGTH + (registerCount * 2);
status = ModbusSendReceive(request, 8, response, expectedLength,
&receiveLength);
if (status == MODBUS_STATUS_OK)
{
status = ModbusCheckResponse(response, receiveLength,
MODBUS_FUNCTION_READ_ODD_COUNT_REGS);
}
if ((status == MODBUS_STATUS_OK) &&
((receiveLength != expectedLength) ||
(response[2] != (BYTE)(registerCount * 2))))
{
status = MODBUS_STATUS_RESPONSE_ERROR;
}
if (status == MODBUS_STATUS_OK)
{
for (registerOffset = 0; registerOffset < registerCount;
registerOffset++)
{
PSW[PSW_ODD_RESULTS + registerOffset] =
((WORD)response[3 + (registerOffset * 2)] << 8) |
response[4 + (registerOffset * 2)];
}
}
}
PSW[PSW_STATUS] = status;
PSW[PSW_RECEIVE_LENGTH] = receiveLength;
示例请求:起始基准地址 0、数量 123 的请求数据为 00 00 00 7B,响应数据字节数为 246,返回地址为 1, 3, 5, ..., 245。数量 122 也是合法请求,返回地址 1, 3, 5, ..., 243;数量为 0 或 124 时 PLC 返回异常码 03。若最后一个奇数地址越过 0x270F,返回异常码 02。
不设置保存或恢复按钮。仅私有写操作 0x42 成功后,PLC 自动将固定寄存器 0x10000 写入 RTC 备份寄存器;启动时校验通过则自动恢复。保持寄存器 0..31 和线圈 0..15 均不保存、不恢复,也不会被 RTC 旧值覆盖。
STM32F407 的 RTC 备份区只有 20 个 32 位寄存器,不能保存完整的 10000 个寄存器和线圈。实际掉电保持还要求 PLC 板的 VBAT 电池已经接入;没有 VBAT 时,RTC 备份数据也会丢失。
读取 0x10000:01 41 00 01 00 00 00 01 2C C3
写入 0x10000:01 42 00 01 00 00 <value_hi> <value_lo> CRC_L CRC_H
读取 1, 3, ..., 245:01 43 00 00 00 7B CRC_L CRC_H
0x43 每次按钮操作只发送一帧,最多处理 123 个奇数地址寄存器。