# TouchWin 自组 Modbus RTU 附加功能 ## 1. 功能定义 标准功能码仍为 `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` 范围内的奇数或偶数。 ## 2. TouchWin 地址 `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` 以上表示异常应答,低字节为异常码。 ## 3. 公共定义 ```c #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 功能码。 ## 4. `ReadExtendedRegister`:`0x41` 读取 `0x10000` 地址和数量固定,不在画面输入: ```c 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; ``` ## 5. `WriteExtendedRegister`:`0x42` 写入 `0x10000` 写入值从 `PSW351` 取得。请求和正常响应均为 10 字节,正常响应回显 32 位地址与 16 位写入值: ```c 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 备份区。 ## 6. `ClearExtendedResult`:清除读取显示 清除按钮绑定以下函数。该函数只将触摸屏上的读取结果清零,不发送通信帧,不修改 PLC 的 `0x10000`,也不触发掉电保存: ```c PSW[PSW_EXTENDED_RESULT] = 0; ``` 用于验证掉电保持时,操作顺序为:使用 `0x42` 写入非零值,调用清除函数,PLC 断电再上电,最后使用 `0x41` 读取;若读回原写入值,则掉电保持生效。 ## 7. `ReadOddCountRegisters`:`0x43` 读取奇数地址寄存器 起始基准地址可以是任意合法地址,PLC 会从第一个不小于该值的奇数地址开始,每次地址加 `2`。数量允许 `1..123` 范围内的奇数或偶数。响应容量为 251 字节,满足 123 个 WORD 的数据: ```c 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`。 ## 8. RTC 断电保持 不设置保存或恢复按钮。仅私有写操作 `0x42` 成功后,PLC 自动将固定寄存器 `0x10000` 写入 RTC 备份寄存器;启动时校验通过则自动恢复。保持寄存器 `0..31` 和线圈 `0..15` 均不保存、不恢复,也不会被 RTC 旧值覆盖。 STM32F407 的 RTC 备份区只有 20 个 32 位寄存器,不能保存完整的 10000 个寄存器和线圈。实际掉电保持还要求 PLC 板的 `VBAT` 电池已经接入;没有 VBAT 时,RTC 备份数据也会丢失。 ## 9. 联调帧 ```text 读取 0x10000:01 41 00 01 00 00 00 01 2C C3 写入 0x10000:01 42 00 01 00 00 CRC_L CRC_H 读取 1, 3, ..., 245:01 43 00 00 00 7B CRC_L CRC_H ``` `0x43` 每次按钮操作只发送一帧,最多处理 123 个奇数地址寄存器。