diff --git a/project/Spoolend/README.md b/project/Spoolend/README.md new file mode 100644 index 0000000..7de76db --- /dev/null +++ b/project/Spoolend/README.md @@ -0,0 +1,537 @@ +# Spoolold → Spoolend 移植计划 + +## 一、Spoolold 功能汇总 + +Spoolold 是一个 **线缆卷筒末端控制器**(STM32G431),核心功能如下: + +| 功能模块 | 说明 | +|---------|------| +| **Modbus RTU 从机** | UART1, 地址 0x34, 支持 FC03/FC06/FC10, 12 个保持寄存器 | +| **Modbus RTU 主机** | UART2, 向 DMK 电机驱动器发送速度/方向指令 | +| **4 路继电器输出** | K1-K4 独立开关控制,由 Modbus 寄存器 [0]-[3] 驱动 | +| **电源管理** | K5 继电器,支持手动/自动(超时)模式,含极性反转配置 | +| **通信超时保护** | 无通信活动时自动停止电机、关闭电源 | +| **DMK 电机控制** | 3 相状态机(模式→速度→方向),100ms/相,通过 Modbus FC06 发送 | +| **Flash 参数持久化** | 12 个 int16_t 参数存储在 Flash 最后一页,写入 55 触发保存 | +| **IWDG 看门狗** | 主循环喂狗,死机自动复位 | + +### 核心数据结构:holdingRegisters[12] + +| 索引 | 默认值 | 功能 | +|------|--------|------| +| [0]-[3] | 50 | K1-K4 继电器控制(0=关, 非0=开)| +| [4] | 0 | 电源极性(0=正逻辑, 非0=反转)| +| [5] | 0 | 电源模式(0=自动超时, 非0=手动)| +| [6] | 0 | 手动电源开关(0=关, 非0=开)| +| [7] | 0 | 通信超时阈值(ms) | +| [8] | 2000 | 电机速度 | +| [9] | 0 | 电机状态(0=停, 1=正转, 2=反转)| +| [10] | 0 | Flash 保存触发(55=保存)| +| [11] | 0 | 保留 | + +--- + +## 二、新旧架构对比 + +| 维度 | Spoolold (旧) | Spoolend (新) | +|------|--------------|---------------| +| UART 驱动 | 直接调 HAL,逐字节中断接收 | `bsp_uart.h` + `TComCtrl` 抽象层,环形缓冲区 | +| Modbus 协议 | 自带 `modbus.c`(从机+主机)| 无,需移植 | +| 电机控制 | 自带 `dmk.c`(3 相状态机)| 无,需移植 | +| Flash 操作 | 自带 `flash_operation.c`(直接操作 Flash)| 可用 `bspMCU/stmflash.h` | +| GPIO 控制 | 直接 HAL_GPIO_WritePin | 同样方式,无需改变 | +| 发送方式 | DMA 发送 | `TComCtrl` 的 `rd_ComSendProc` + 自定义 send 回调 | + +--- + +## 三、移植方案 + +### 3.1 文件结构规划 + +在 `robot/project/Spoolend/` 下新增: + +``` +Spoolend/ +├── CMakeLists.txt (已有,需更新源文件列表) +├── Spoolend.c (已有,需重写) +├── include/ +│ ├── Spoolend.h (新建 - 公共接口) +│ ├── modbus.h (从 Spoolold 移植,适配 TComCtrl) +│ ├── dmk.h (从 Spoolold 移植) +│ └── flash_operation.h (从 Spoolold 移植,适配 stmflash) +├── modbus.c (从 Spoolold 移植,重写收发层) +├── dmk.c (从 Spoolold 移植,适配新 Modbus 接口) +└── flash_operation.c (从 Spoolold 移植,适配 stmflash API) +``` + +### 3.2 各模块移植要点 + +#### A. Modbus 协议栈 (`modbus.c/h`) + +**保留:** +- CRC-16 计算逻辑 +- FC03/FC06/FC10 帧解析与响应构造 +- 异常响应机制 +- 寄存器读写逻辑 + 可写性检查 + +**重写:** +- **接收层**:不再逐字节中断接收,改为通过 `TComCtrl` 的 `check`/`decode` 回调机制 + - `check_RS485_1()` → 做 Modbus 帧完整性校验(CRC + 长度),返回帧长或 0 + - `decode_RS485_1()` → 调用 `Modbus_Process()` 解析帧内容 +- **发送层**:`Modbus_SendResponse()` 改为调用 `rd_ComWrite(g_ptRS485_1, ...)` +- **主机发送**:`Modbus_Master_Send()` 改为调用 `rd_ComWrite(g_ptRS485_2, ...)` +- **超时检测**:不再依赖 `Rx_Pre_time`,改用 `TComCtrl` 的 `m_uiLastRecvTime` + +**关键适配:** +```c +// 旧:HAL_UART_Transmit_DMA(&huart1, data, len) +// 新:rd_ComWrite(g_ptRS485_1, (char*)data, len) + +// 旧:逐字节 HAL_UART_Receive_IT + HAL_UART_RxCpltCallback +// 新:check 回调做帧校验,decode 回调做帧处理 +``` + +#### B. DMK 电机控制 (`dmk.c/h`) + +**保留:** +- 3 相状态机逻辑(模式→速度→方向,100ms/相) +- 电机方向映射(0=停, 1=正转→0x01, 2=反转→0x14) + +**重写:** +- `MB_WriteHoldingReg()` 调用改为新的 Modbus 主机接口 +- 超时检测逻辑适配 `TComCtrl` 时间戳 + +#### C. Flash 参数存储 (`flash_operation.c/h`) + +**方案选择:** 使用 `bspMCU/stmflash.h` 提供的 API + +**适配:** +```c +// 旧:直接 HAL_FLASH_Unlock / HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, ...) +// 新:STMFLASH_Write(WriteAddr, pBuffer, NumToWrite) +// STMFLASH_Read(ReadAddr, pBuffer, NumToRead) +// STMFLASH_EraseByAddress(Address) +``` + +注意:`stmflash.h` 有 `#ifdef USE_ONCHIP_FLASH` 保护,需确保 CMake 中启用该宏。 + +#### D. 主逻辑 (`Spoolend.c`) + +**SpoolendInit():** +1. 初始化 RS485_1(已有)→ 绑定 Modbus 从机 check/decode +2. 初始化 RS485_2(已有)→ 绑定 Modbus 主机 send +3. 初始化 FDCAN1(保留,可选) +4. 调用 `Flash_ReadParameters()` 恢复参数 +5. 设置 holdingRegisters[8] = 2000(默认速度) +6. IWDG 初始化(如 CubeMX 未配置) + +**SpoolendTask():** +1. `HAL_IWDG_Refresh()` 喂狗 +2. `rd_ComRead` + `Modbus_Process()` 处理 Modbus 帧 +3. K1-K4 GPIO 控制(holdingRegisters[0..3]) +4. 通信超时判断 + 电机停止 +5. `handle_dmk_motor()` 电机控制 +6. `handle_power_on_off()` 电源管理 +7. Flash 保存检查(holdingRegisters[10]==55) +8. `rd_ComSendProc()` 发送处理 + +### 3.3 CMakeLists.txt 更新 + +```cmake +set(SRC_LIST + Spoolend.c + modbus.c + dmk.c + flash_operation.c +) +``` + +依赖关系保持不变:`target_link_libraries(${TARGET_NAME} PUBLIC bspMCU)` + +--- + +## 四、移植顺序 + +1. **flash_operation.c/h** → 最独立,先移植并验证 Flash 读写 +2. **modbus.c/h** → 核心协议,移植从机部分先(FC03/06/10),再移植主机部分 +3. **dmk.c/h** → 依赖 Modbus 主机接口 +4. **Spoolend.c** → 整合所有模块,替换 demo 逻辑为正式控制逻辑 + +--- + +## 五、验证方案 + +1. **Flash 验证**:写入参数 → 重启 → 读取比对 +2. **Modbus 从机验证**:用 Modbus Poll 等工具连接 RS485_1,读写 12 个寄存器 +3. **Modbus 主机验证**:连接 DMK 驱动器,发送速度/方向命令,观察响应 +4. **GPIO 验证**:通过 Modbus 写 K1-K4 寄存器,用万用表测引脚电平 +5. **电源管理验证**:设置超时阈值,断开通信,观察 K5 自动关闭 +6. **看门狗验证**:故意死循环,观察系统自动复位 + +--- + +## 六、关键文件路径 + +| 文件 | 路径 | +|------|------| +| Spoolold 主逻辑 | `robot/project_old/Spoolold/Spoolold.c` | +| Modbus 旧实现 | `robot/project_old/Spoolold/modbus.c` | +| DMK 旧实现 | `robot/project_old/Spoolold/dmk.c` | +| Flash 旧实现 | `robot/project_old/Spoolold/flash_operation.c` | +| Spoolend 当前代码 | `robot/project/Spoolend/Spoolend.c` | +| TComCtrl 通信抽象 | `robot/peripheral/include/com.h` | +| BSP UART 驱动 | `robot/bspMCU/include/bsp_uart.h` | +| BSP Flash 驱动 | `robot/bspMCU/include/stmflash.h` | +| 根 CMakeLists | `robot/CMakeLists.txt` | + +# Spoolend 线轴端控制板 — 测试用例文档 + +## 一、测试环境 + +| 项目 | 说明 | +|------|------| +| 硬件 | STM32G431 控制板 + DMK 电机驱动器 + 5路继电器模块 | +| 上位机工具 | Modbus Poll / Modbus Slave / 自定义串口工具 | +| 连接 | RS485_1 (USART1) → 上位机主机, RS485_2 (USART3) → DMK 驱动器 | +| 波特率 | 115200, 8N1 | +| Modbus 从机地址 | 0x34 (52) | + +--- + +## 二、Modbus 从机通信测试 + +### TC-MODBUS-01: FC03 读保持寄存器 — 正常读取全部寄存器 + +| 项目 | 内容 | +|------|------| +| 前置条件 | 系统正常启动 | +| 操作 | 发送: `34 03 0000 000C CRC` | +| 预期响应 | `34 03 18 [24字节寄存器数据] CRC` | +| 验证点 | 返回 12 个寄存器值,与 holdingRegisters[0..11] 一致 | + +### TC-MODBUS-02: FC03 读保持寄存器 — 读取单个寄存器 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送: `34 03 0000 0001 CRC` (读 K1 状态) | +| 预期响应 | `34 03 02 [K1值高] [K1值低] CRC` | + +### TC-MODBUS-03: FC03 读保持寄存器 — 地址越界 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送: `34 03 000B 0002 CRC` (从地址11开始读2个) | +| 预期响应 | `34 83 02 CRC` (异常码 0x02: 非法数据地址) | + +### TC-MODBUS-04: FC06 写单个寄存器 — 写 K1 控制 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送: `34 06 0000 0000 CRC` (K1 设为 0) | +| 预期响应 | `34 06 0000 0000 CRC` (原帧回显) | +| 验证点 | K1 引脚变为低电平;再发 `34 06 0000 0001 CRC`,K1 恢复高电平 | + +### TC-MODBUS-05: FC06 写单个寄存器 — 地址越界 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送: `34 06 000C 0000 CRC` (地址 12 超出范围) | +| 预期响应 | `34 86 02 CRC` (异常码 0x02) | + +### TC-MODBUS-06: FC10 写多个寄存器 — 批量写入 K1-K4 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送: `34 10 0000 0004 08 0001 0000 0001 0000 CRC` | +| 预期响应 | `34 10 0000 0004 CRC` | +| 验证点 | K1=1, K2=0, K3=1, K4=0;GPIO 引脚电平对应变化 | + +### TC-MODBUS-07: FC10 写多个寄存器 — 数据长度错误 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送: `34 10 0000 0004 04 0001 0000 CRC` (byteCount=4 但 registerCount=4 需要 8 字节) | +| 预期响应 | `34 90 03 CRC` (异常码 0x03) | + +### TC-MODBUS-08: FC10 写多个寄存器 — 只读寄存器保护 + +| 项目 | 内容 | +|------|------| +| 前置条件 | registerWritable[i] = 0 的寄存器 | +| 操作 | 尝试写入只读寄存器 | +| 预期响应 | 正常响应,但只读寄存器值不变 | + +### TC-MODBUS-09: CRC 校验 — 错误 CRC + +| 项目 | 内容 | +|------|------| +| 操作 | 发送: `34 03 0000 0001 0000` (CRC 错误) | +| 预期响应 | 无响应(静默丢弃) | + +### TC-MODBUS-10: 不支持的功能码 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送: `34 01 0000 0001 CRC` (FC01 不支持) | +| 预期响应 | `34 81 01 CRC` (异常码 0x01) | + +--- + +## 三、GPIO 继电器控制测试 + +### TC-GPIO-01: K1-K4 独立控制 + +| 项目 | 内容 | +|------|------| +| 操作 | 通过 FC06 依次写 holdingRegisters[0..3] 为非零值 | +| 预期 | K1→K4 引脚依次变为高电平 (GPIO_PIN_SET) | +| 操作 | 写为 0 | +| 预期 | K1→K4 引脚依次变为低电平 (GPIO_PIN_RESET) | + +### TC-GPIO-02: K1-K4 默认状态 + +| 项目 | 内容 | +|------|------| +| 操作 | 系统上电,不发送任何 Modbus 命令 | +| 预期 | holdingRegisters[0..3] 默认值为 50 (0x32),K1-K4 均为高电平 | + +### TC-GPIO-03: LED1 指示灯 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送任意 Modbus 命令到本机地址 (0x34) | +| 预期 | LED1 翻转一次 | + +--- + +## 四、电源管理测试 (K5) + +### TC-PWR-01: 自动模式 — 通信正常时电源开启 + +| 项目 | 内容 | +|------|------| +| 前置条件 | holdingRegisters[5]=0 (自动), [4]=0 (正极性), [7]=5000 (5秒超时) | +| 操作 | 持续发送 Modbus 命令(间隔 < 5秒) | +| 预期 | K5 引脚保持低电平(电源开启,正极性时 RESET=开) | + +### TC-PWR-02: 自动模式 — 通信超时关闭电源 + +| 项目 | 内容 | +|------|------| +| 前置条件 | 同上 | +| 操作 | 停止发送 Modbus 命令,等待超过 5 秒 | +| 预期 | K5 引脚变为高电平(电源关闭) | + +### TC-PWR-03: 自动模式 — 通信恢复重新开启 + +| 项目 | 内容 | +|------|------| +| 操作 | 超时后重新发送 Modbus 命令 | +| 预期 | K5 引脚恢复低电平(电源重新开启) | + +### TC-PWR-04: 手动模式 — 开启电源 + +| 项目 | 内容 | +|------|------| +| 操作 | FC06 写 holdingRegisters[5]=1 (手动), [6]=1 (开) | +| 预期 | K5 引脚变为低电平(电源开启) | + +### TC-PWR-05: 手动模式 — 关闭电源 + +| 项目 | 内容 | +|------|------| +| 操作 | FC06 写 holdingRegisters[6]=0 (关) | +| 预期 | K5 引脚变为高电平(电源关闭) | + +### TC-PWR-06: 极性反转 + +| 项目 | 内容 | +|------|------| +| 操作 | FC06 写 holdingRegisters[4]=1 (反极性) | +| 预期 | K5 引脚电平逻辑反转(SET=关, RESET=开) | + +--- + +## 五、DMK 电机控制测试 + +### TC-DMK-01: 电机正转 + +| 项目 | 内容 | +|------|------| +| 操作 | FC06 写 holdingRegisters[9]=1 (正转), [8]=2000 (速度) | +| 预期 | RS485_2 周期性 (300ms) 向 DMK 发送: 模式→速度→方向(0x01) | +| 验证点 | 用串口监听 RS485_2,确认 3 相 Modbus FC06 帧正确 | + +### TC-DMK-02: 电机反转 + +| 项目 | 内容 | +|------|------| +| 操作 | FC06 写 holdingRegisters[9]=2 | +| 预期 | 方向寄存器 0x033 写入值 0x14 | + +### TC-DMK-03: 电机停止 + +| 项目 | 内容 | +|------|------| +| 操作 | FC06 写 holdingRegisters[9]=0 | +| 预期 | 方向寄存器 0x033 写入值 0x00 | + +### TC-DMK-04: 速度设置 + +| 项目 | 内容 | +|------|------| +| 操作 | FC06 写 holdingRegisters[8]=3000 | +| 预期 | 速度寄存器 0x06A 写入值 3000 | + +### TC-DMK-05: 3 相时序验证 + +| 项目 | 内容 | +|------|------| +| 操作 | 电机运行中,监听 RS485_2 输出 | +| 预期 | 每 300ms 周期: 相0(0-100ms)写模式=0, 相1(100-200ms)写速度, 相2(200-300ms)写方向 | + +### TC-DMK-06: 默认速度 + +| 项目 | 内容 | +|------|------| +| 操作 | 系统上电 | +| 预期 | holdingRegisters[8] 硬编码为 2000(即使 Flash 中存储了其他值) | + +--- + +## 六、通信超时保护测试 + +### TC-TIMEOUT-01: 超时停止电机 + +| 项目 | 内容 | +|------|------| +| 前置条件 | holdingRegisters[7]=5000, 电机正在运行 | +| 操作 | 停止 Modbus 通信超过 5 秒 | +| 预期 | RS485_2 发送停止命令 `MB_WriteHoldingReg(1, 0x33, 0x0)` | + +### TC-TIMEOUT-02: 超时阈值为 0 时禁用超时 + +| 项目 | 内容 | +|------|------| +| 操作 | FC06 写 holdingRegisters[7]=0 | +| 预期 | 通信超时不会触发停止(阈值为 0 时立即超时,但需验证行为) | + +--- + +## 七、Flash 参数持久化测试 + +### TC-FLASH-01: 参数保存 + +| 项目 | 内容 | +|------|------| +| 操作 | 修改 K1-K4 为自定义值,FC06 写 holdingRegisters[10]=55 | +| 预期 | 参数写入 Flash 最后一页;holdingRegisters[10] 自动变为 1 | +| 验证 | 发送 FC03 读取确认 [10]=1 | + +### TC-FLASH-02: 参数恢复 + +| 项目 | 内容 | +|------|------| +| 前置条件 | TC-FLASH-01 已执行 | +| 操作 | 系统复位/重新上电 | +| 预期 | 从 Flash 读取参数,K1-K4 恢复为保存时的值 | + +### TC-FLASH-03: 保存后速度覆盖 + +| 项目 | 内容 | +|------|------| +| 操作 | 保存时 holdingRegisters[8]=3000,重启 | +| 预期 | Flash 读取后 [8]=3000,但 SpoolendInit 硬编码覆盖为 2000 | + +### TC-FLASH-04: 默认参数 + +| 项目 | 内容 | +|------|------| +| 操作 | 全新芯片首次启动(Flash 为空白 0xFF) | +| 预期 | holdingRegisters[0..3] 读取为 0xFFFF (非零=开),K1-K4 默认开启 | + +--- + +## 八、看门狗测试 + +### TC-WDG-01: 正常运行不复位 + +| 项目 | 内容 | +|------|------| +| 操作 | 系统正常运行 | +| 预期 | SpoolendTask 每次循环喂狗,系统持续运行不复位 | + +### TC-WDG-02: 死机自动复位 + +| 项目 | 内容 | +|------|------| +| 操作 | 在 SpoolendTask 中插入 `while(1);` 死循环 | +| 预期 | IWDG 超时后系统自动复位 | + +--- + +## 九、FDCAN 测试 + +### TC-CAN-01: CAN 数据透传 + +| 项目 | 内容 | +|------|------| +| 操作 | 向 FDCAN1 发送任意 CAN 帧 | +| 预期 | 数据通过 rd_ComIDWrite 回传(echo) | + +### TC-CAN-02: CAN 错误恢复 + +| 项目 | 内容 | +|------|------| +| 操作 | 制造 CAN 总线错误 | +| 预期 | HAL_FDCAN_ErrorStatusCallback 触发 MX_FDCAN1_Init 重新初始化 | + +--- + +## 十、边界条件与异常测试 + +### TC-EDGE-01: Modbus 帧不完整 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送不完整的 Modbus 帧(如只有地址字节 `34`) | +| 预期 | 等待后续数据或超时丢弃,不崩溃 | + +### TC-EDGE-02: 非本机地址帧 + +| 项目 | 内容 | +|------|------| +| 操作 | 发送地址非 0x34 的 Modbus 帧 | +| 预期 | 静默忽略,不响应 | + +### TC-EDGE-03: 高频 Modbus 请求 + +| 项目 | 内容 | +|------|------| +| 操作 | 以最大频率连续发送 FC03 读取请求 | +| 预期 | 每帧正确响应,不丢帧不乱序 | + +### TC-EDGE-04: 同时读写寄存器 + +| 项目 | 内容 | +|------|------| +| 操作 | 一边用 FC03 读取,一边用 FC06 写入 | +| 预期 | 读取返回写入前或写入后的值(无中间态撕裂) | + +--- + +## 十一、测试用例统计 + +| 类别 | 用例数 | +|------|--------| +| Modbus 从机通信 | 10 | +| GPIO 继电器控制 | 3 | +| 电源管理 | 6 | +| DMK 电机控制 | 6 | +| 通信超时保护 | 2 | +| Flash 参数持久化 | 4 | +| 看门狗 | 2 | +| FDCAN | 2 | +| 边界与异常 | 4 | +| **合计** | **39** | + diff --git a/project/Spoolend/Spoolend.c b/project/Spoolend/Spoolend.c index 10e56b3..c4181d1 100644 --- a/project/Spoolend/Spoolend.c +++ b/project/Spoolend/Spoolend.c @@ -7,50 +7,43 @@ 版 本 号 : 初稿 作 者 : radkil 生成日期 : 2026年6月10日 - 最近修改 : - 功能描述 : 线轴端控制板 - + 最近修改 : 移植自 Spoolold + 功能描述 : 线轴端控制板 — Modbus 从机/主机 + DMK 电机 + 继电器 + 电源管理 + 修改历史 : 1.日 期 : 2026年6月10日 作 者 : radkil 修改内容 : 创建文件 + 2.日 期 : 2026年7月2日 + 作 者 : radkil + 修改内容 : 从 Spoolold 移植完整功能 ******************************************************************************/ #include "bsp_uart.h" #include "bsp_CAN.h" #include "com.h" #include "fdcan.h" -/*----------------------------------------------* - * 外部变量说明 * - *----------------------------------------------*/ - -/*----------------------------------------------* - * 外部函数原型说明 * - *----------------------------------------------*/ - -/*----------------------------------------------* - * 内部函数原型说明 * - *----------------------------------------------*/ +#include "modbus.h" +#include "dmk.h" +#include "flash_operation.h" +#include "iwdg.h" +#include "gpio.h" /*----------------------------------------------* * 全局变量 * *----------------------------------------------*/ -TComCtrl *g_ptRS485_1; -TComCtrl *g_ptRS485_2; +TComCtrl *g_ptRS485_1; /* Modbus 从机 */ +TComCtrl *g_ptRS485_2; /* Modbus 主机(连 DMK 驱动器)*/ +TComCtrl *g_ptFDCAN1; -TComCtrl *g_ptFDCAN1; /*----------------------------------------------* * 模块级变量 * *----------------------------------------------*/ +static uint32_t s_uwRxPreTime = 0; /*----------------------------------------------* - * 常量定义 * + * RS485 硬件发送函数 * *----------------------------------------------*/ - -/*----------------------------------------------* - * 宏定义 * - *----------------------------------------------*/ - static int RS485_1_Send(char *_pBuffer, uint32_t _iSize) { HAL_GPIO_WritePin(RS485_1_DIR_GPIO_Port, RS485_1_DIR_Pin, GPIO_PIN_SET); @@ -69,54 +62,40 @@ static int RS485_2_Send(char *_pBuffer, uint32_t _iSize) return iRet; } +/*----------------------------------------------* + * UART 接收中断回调 * + *----------------------------------------------*/ #ifndef CONFIG_UART_IT_IDLEDMA void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) - { - UART_RX_IRQHandler(g_ptRS485_1); - } + { + UART_RX_IRQHandler(g_ptRS485_1); + } else if (huart->Instance == USART3) { UART_RX_IRQHandler(g_ptRS485_2); } else if (huart->Instance == LPUART1) { - //UART_RX_IRQHandler(g_ptRS485_2); + // 预留 } } #endif -static int check_RS485_1(char *_pBuffer, uint32_t _iSize) -{ - return _iSize; -} - -static int check_RS485_2(char *_pBuffer, uint32_t _iSize) -{ - return _iSize; -} - +/*----------------------------------------------* + * CAN 回调 * + *----------------------------------------------*/ static int check_Spoolend(char *_pBuffer, uint32_t _iSize) { - return _iSize; -} - -static void decode_RS485_1(const char *_pBuffer, uint32_t _iSize) -{ - //rd_ComWrite(g_ptRS485_1, (char *)_pBuffer, _iSize); -} - -static void decode_RS485_2(const char *_pBuffer, uint32_t _iSize) -{ - //rd_ComWrite(g_ptRS485_2, (char *)_pBuffer, _iSize); + return _iSize; } static void decode_Spoolend(const char *_pBuffer, uint32_t _iSize) { TCANUserData *ptCANUserData = (TCANUserData *)g_ptFDCAN1->m_pUserData; - uint32_t iCANId = (uint32_t)ptCANUserData->m_canrx->Identifier; - rd_ComIDWrite(g_ptFDCAN1, iCANId, (char *)_pBuffer, _iSize); + uint32_t iCANId = (uint32_t)ptCANUserData->m_canrx->Identifier; + rd_ComIDWrite(g_ptFDCAN1, iCANId, (char *)_pBuffer, _iSize); } static int FDCAN1_Send(char *_pBuffer, uint32_t _iSize) @@ -126,96 +105,161 @@ static int FDCAN1_Send(char *_pBuffer, uint32_t _iSize) void HAL_FDCAN_ErrorStatusCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t ErrorStatusITs) { - if (hfdcan->Instance == FDCAN1) - { - MX_FDCAN1_Init(); - } + if (hfdcan->Instance == FDCAN1) + { + MX_FDCAN1_Init(); + } } void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs) { - if (hfdcan->Instance == FDCAN1) - { - CAN_RX_IRQHandler(g_ptFDCAN1); - } + if (hfdcan->Instance == FDCAN1) + { + CAN_RX_IRQHandler(g_ptFDCAN1); + } } -//void Myprint_Init(void) -//{ -// TUartUserData *ptUartUserData = UART_userdata_init(3, 115200, 4096); -// g_ptRS485_2 = rd_ComCreate(NULL, NULL, RS485_1_Send, ptUartUserData->m_buf_size, ptUartUserData); -// UART_IT_init(g_ptRS485_2); -//} -// -//int Myprint_putchar(uint8_t ch) -//{ -// char tx_buf[2]; -// -// if (ch == '\n') -// { -// // 遇到换行,发送 \r\n -// tx_buf[0] = '\r'; -// tx_buf[1] = '\n'; -// if (rd_ComSend(g_ptRS485_2, tx_buf, 2) < 0) return EOF; -// return 2; -// } -// else -// { -// // 普通字符 -// tx_buf[0] = ch; -// if (rd_ComSend(g_ptRS485_2, tx_buf, 1) < 0) return EOF; -// return 1; -// } -//} +/*----------------------------------------------* + * 电源管理 * + *----------------------------------------------*/ +static void handle_power_on_off(void) +{ + if (holdingRegisters[5] != 0) /* 手动模式 */ + { + if (holdingRegisters[6] == 0) + { + if (holdingRegisters[4] == 0) + HAL_GPIO_WritePin(K5_GPIO_Port, K5_Pin, GPIO_PIN_SET); + else + HAL_GPIO_WritePin(K5_GPIO_Port, K5_Pin, GPIO_PIN_RESET); + } + else + { + if (holdingRegisters[4] == 0) + HAL_GPIO_WritePin(K5_GPIO_Port, K5_Pin, GPIO_PIN_RESET); + else + HAL_GPIO_WritePin(K5_GPIO_Port, K5_Pin, GPIO_PIN_SET); + } + return; + } + + /* 自动模式:根据通信超时判断 */ + if (HAL_GetTick() >= s_uwRxPreTime && + HAL_GetTick() - s_uwRxPreTime > holdingRegisters[7]) + { + if (holdingRegisters[4] == 0) + HAL_GPIO_WritePin(K5_GPIO_Port, K5_Pin, GPIO_PIN_SET); + else + HAL_GPIO_WritePin(K5_GPIO_Port, K5_Pin, GPIO_PIN_RESET); + } + else + { + if (holdingRegisters[4] == 0) + HAL_GPIO_WritePin(K5_GPIO_Port, K5_Pin, GPIO_PIN_RESET); + else + HAL_GPIO_WritePin(K5_GPIO_Port, K5_Pin, GPIO_PIN_SET); + } +} +/*----------------------------------------------* + * 初始化 * + *----------------------------------------------*/ void SpoolendInit(void) { + /* RS485_1: Modbus 从机 — check/decode 回调由 rd_ComRead 自动调用 */ TUartUserData *ptUartUserData = UART_userdata_init(1, 115200, 512); - g_ptRS485_1 = rd_ComCreate(NULL, NULL, RS485_1_Send, ptUartUserData->m_buf_size, ptUartUserData); // 仿照My_print,check和decode传NULL + g_ptRS485_1 = rd_ComCreate(Modbus_Slave_Check, Modbus_Slave_Decode, + RS485_1_Send, ptUartUserData->m_buf_size, ptUartUserData); UART_IT_init(g_ptRS485_1); - TUartUserData *ptUartUserData1 = UART_userdata_init(3, 115200, 512); - g_ptRS485_2 = rd_ComCreate(NULL, NULL, RS485_2_Send, ptUartUserData1->m_buf_size, ptUartUserData1); // 仿照My_print,check和decode传NULL + + /* RS485_2: Modbus 主机(DMK 驱动器)*/ + TUartUserData *ptUartUserData2 = UART_userdata_init(3, 115200, 512); + g_ptRS485_2 = rd_ComCreate(NULL, NULL, RS485_2_Send, + ptUartUserData2->m_buf_size, ptUartUserData2); UART_IT_init(g_ptRS485_2); + Modbus_Master_Init(g_ptRS485_2); + + /* FDCAN1 */ TCANUserData *ptCANUserData = CAN_userdata_init(&hfdcan1, 8, 0); - g_ptFDCAN1 = rd_ComCreate(check_Spoolend, decode_Spoolend, FDCAN1_Send, CONFIG_UART_BUFFER_SIZE, ptCANUserData); + g_ptFDCAN1 = rd_ComCreate(check_Spoolend, decode_Spoolend, + FDCAN1_Send, CONFIG_UART_BUFFER_SIZE, ptCANUserData); CAN_IT_init(g_ptFDCAN1); + + /* 从 Flash 恢复参数 */ + ReadParametersFromFlash((int16_t *)holdingRegisters); + holdingRegisters[8] = 2000; /* 默认电机速度 */ + + s_uwRxPreTime = HAL_GetTick(); } +/*----------------------------------------------* + * 主任务 * + *----------------------------------------------*/ void SpoolendTask(void) { - static int iCount = 0; - char pcBuffer1[52] = {0}; - char pcBuffer2[52] = {0}; - char pcBuffer3[52] = {0}; - __disable_irq(); - int i485_1 = rd_ComRead(g_ptRS485_1, pcBuffer1, sizeof(pcBuffer1)); - int i485_2 = rd_ComRead(g_ptRS485_2, pcBuffer2, sizeof(pcBuffer2)); - rd_ComRead(g_ptFDCAN1, pcBuffer3, sizeof(pcBuffer3)); - __enable_irq(); - - if (i485_1 > 0) rd_ComWrite(g_ptRS485_1, pcBuffer1, i485_1); - if (i485_2 > 0) rd_ComWrite(g_ptRS485_2, pcBuffer2, i485_2); -// rd_ComIDWrite(g_ptFDCAN1, 1, "Hello\n", 6); - - if (iCount > 50000) - { - HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); - HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin); - HAL_GPIO_TogglePin(LED3_GPIO_Port, LED3_Pin); - HAL_GPIO_TogglePin(K1_GPIO_Port, K1_Pin); - HAL_GPIO_TogglePin(K2_GPIO_Port, K2_Pin); - HAL_GPIO_TogglePin(K3_GPIO_Port, K3_Pin); - HAL_GPIO_TogglePin(K4_GPIO_Port, K4_Pin); - HAL_GPIO_TogglePin(K5_GPIO_Port, K5_Pin); - iCount = 0; - } - else - { - iCount++; - } + char pcBuffer1[52] = {0}; + char pcBuffer2[52] = {0}; + + /* 喂狗 */ + HAL_IWDG_Refresh(&hiwdg); + + /* 读取 Modbus 从机帧(rd_ComRead 内部自动调用 Modbus_Slave_Decode)*/ + __disable_irq(); + int i485_1 = rd_ComRead(g_ptRS485_1, pcBuffer1, sizeof(pcBuffer1)); + int i485_2 = rd_ComRead(g_ptRS485_2, pcBuffer2, sizeof(pcBuffer2)); + rd_ComRead(g_ptFDCAN1, pcBuffer2, sizeof(pcBuffer2)); + __enable_irq(); + (void)i485_1; + (void)i485_2; + + /* K1-K4 继电器控制 */ + HAL_GPIO_WritePin(K1_GPIO_Port, K1_Pin, + holdingRegisters[0] ? GPIO_PIN_SET : GPIO_PIN_RESET); + HAL_GPIO_WritePin(K2_GPIO_Port, K2_Pin, + holdingRegisters[1] ? GPIO_PIN_SET : GPIO_PIN_RESET); + HAL_GPIO_WritePin(K3_GPIO_Port, K3_Pin, + holdingRegisters[2] ? GPIO_PIN_SET : GPIO_PIN_RESET); + HAL_GPIO_WritePin(K4_GPIO_Port, K4_Pin, + holdingRegisters[3] ? GPIO_PIN_SET : GPIO_PIN_RESET); + + /* 通信接收标志处理 */ + if (RS485_1_RX) + { + RS485_1_RX = 0; + s_uwRxPreTime = HAL_GetTick(); + HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); + } + + if (RS485_1_Host_Existed) + { + RS485_1_Host_Existed = 0; + s_uwRxPreTime = HAL_GetTick(); + } + + /* 通信超时判断 + 电机控制 */ + if (HAL_GetTick() >= s_uwRxPreTime && + HAL_GetTick() - s_uwRxPreTime > holdingRegisters[7]) + { + MB_WriteHoldingReg(1, 0x033, 0x00); /* 超时停止电机 */ + } + else + { + handle_dmk_motor(holdingRegisters[8], holdingRegisters[9]); + } + + /* 电源管理 */ + handle_power_on_off(); + + /* Flash 保存触发 */ + if (holdingRegisters[10] == 55) + { + WriteParametersToFlash((int16_t *)holdingRegisters); + holdingRegisters[10] = 1; + } + + /* 发送处理 */ rd_ComSendProc(g_ptRS485_1); rd_ComSendProc(g_ptRS485_2); rd_ComSendProc(g_ptFDCAN1); } - diff --git a/project/Spoolend/dmk.c b/project/Spoolend/dmk.c new file mode 100644 index 0000000..662b6e2 --- /dev/null +++ b/project/Spoolend/dmk.c @@ -0,0 +1,35 @@ +#include "dmk.h" +#include "modbus.h" + +uint16_t Is_First_Run = 0; + +void handle_dmk_motor(uint16_t velocity, int state) +{ + char ms_100_state = HAL_GetTick() / 300 % 3; + + switch (ms_100_state) + { + case 1: + MB_WriteHoldingReg(1, 0x06A, velocity); + break; + + case 2: + if (state == 1) + { + MB_WriteHoldingReg(1, 0x033, 0x01); /* 正转 */ + } + else if (state == 0) + { + MB_WriteHoldingReg(1, 0x033, 0x00); /* 停止 */ + } + else if (state == 2) + { + MB_WriteHoldingReg(1, 0x033, 0x14); /* 反转 */ + } + break; + + case 0: + MB_WriteHoldingReg(1, 0x000, 0); /* 设置模式为0 */ + break; + } +} diff --git a/project/Spoolend/flash_operation.c b/project/Spoolend/flash_operation.c new file mode 100644 index 0000000..e3e0296 --- /dev/null +++ b/project/Spoolend/flash_operation.c @@ -0,0 +1,83 @@ +#include "flash_operation.h" + +static void Flash_InitInternal(void) +{ + HAL_FLASH_Unlock(); + __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_4); + __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS); +} + +static uint32_t GetFlashPage(uint32_t address) +{ + return (address - FLASH_BASE_ADDRESS) / FLASH_PAGE_SIZE; +} + +HAL_StatusTypeDef WriteParametersToFlash(int16_t *parameters) +{ + HAL_StatusTypeDef status = HAL_OK; + uint32_t flash_address = DATA_FLASH_ADDRESS; + uint32_t primask_bit; + + primask_bit = __get_PRIMASK(); + __disable_irq(); + + Flash_InitInternal(); + + /* 擦除Flash页 */ + FLASH_EraseInitTypeDef erase_init; + uint32_t page_error = 0; + + erase_init.TypeErase = FLASH_TYPEERASE_PAGES; + erase_init.Banks = FLASH_BANK_1; + erase_init.Page = GetFlashPage(DATA_FLASH_ADDRESS); + erase_init.NbPages = 1; + + if (HAL_FLASHEx_Erase(&erase_init, &page_error) != HAL_OK) + { + HAL_FLASH_Lock(); + if (!primask_bit) __enable_irq(); + return HAL_ERROR; + } + + /* 每次写入64位数据(4个int16_t)*/ + for (int i = 0; i < PARAM_COUNT; i++) + { + if (i % 4 == 0) + { + uint64_t combined_data = 0; + + for (int j = 0; j < 4 && (i + j) < PARAM_COUNT; j++) + { + combined_data |= ((uint64_t)((uint16_t)parameters[i + j]) << (j * 16)); + } + + if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, + flash_address + (i * sizeof(int16_t)), + combined_data) != HAL_OK) + { + status = HAL_ERROR; + break; + } + } + } + + HAL_FLASH_Lock(); + + if (!primask_bit) __enable_irq(); + + return status; +} + +void ReadParametersFromFlash(int16_t *parameters) +{ + uint32_t flash_address = DATA_FLASH_ADDRESS; + + Flash_InitInternal(); + + for (int i = 0; i < PARAM_COUNT; i++) + { + parameters[i] = *(int16_t *)(flash_address + (i * sizeof(int16_t))); + } + + HAL_FLASH_Lock(); +} diff --git a/project/Spoolend/include/dmk.h b/project/Spoolend/include/dmk.h new file mode 100644 index 0000000..68c43c5 --- /dev/null +++ b/project/Spoolend/include/dmk.h @@ -0,0 +1,10 @@ +#ifndef INC_DMK_H_ +#define INC_DMK_H_ + +#include + +extern uint16_t Is_First_Run; + +void handle_dmk_motor(uint16_t velocity, int state); + +#endif /* INC_DMK_H_ */ diff --git a/project/Spoolend/include/flash_operation.h b/project/Spoolend/include/flash_operation.h new file mode 100644 index 0000000..9318f57 --- /dev/null +++ b/project/Spoolend/include/flash_operation.h @@ -0,0 +1,16 @@ +#ifndef FLASH_OPERATION_H +#define FLASH_OPERATION_H + +#include "stm32g4xx_hal.h" +#include + +/* 定义Flash存储地址 - 使用最后一页 */ +#define FLASH_BASE_ADDRESS 0x08000000 +#define DATA_FLASH_ADDRESS (FLASH_BASE_ADDRESS + FLASH_SIZE - FLASH_PAGE_SIZE) +#define PARAM_COUNT 12 + +/* 函数声明 */ +HAL_StatusTypeDef WriteParametersToFlash(int16_t *parameters); +void ReadParametersFromFlash(int16_t *parameters); + +#endif /* FLASH_OPERATION_H */ diff --git a/project/Spoolend/include/modbus.h b/project/Spoolend/include/modbus.h new file mode 100644 index 0000000..6121e3d --- /dev/null +++ b/project/Spoolend/include/modbus.h @@ -0,0 +1,40 @@ +#ifndef __MODBUS_SLAVE_H +#define __MODBUS_SLAVE_H + +#include "stm32g4xx_hal.h" +#include +#include +#include "com.h" + +/* Modbus 相关参数 */ +#define MODBUS_SLAVE_ADDRESS 0x34 +#define MODBUS_FUNCTION_CODE_03 0x03 +#define MODBUS_FUNCTION_CODE_06 0x06 +#define MODBUS_FUNCTION_CODE_10 0x10 +#define MODBUS_BUFFER_SIZE 256 +#define MODBUS_TIMEOUT_MS 2 +#define MODBUS_CRC_OFF 0 /* ==1 关闭CRC校验 */ + +/* 寄存器相关参数 */ +#define REGISTER_START_ADDRESS 0x0000 +#define REGISTER_COUNT 12 + +/* 全局变量声明 */ +extern uint16_t holdingRegisters[REGISTER_COUNT]; +extern uint8_t RS485_1_RX; +extern uint8_t RS485_1_Host_Existed; +extern char dmk_read_state; + +/* TComCtrl 回调 — 注册到 RS485_1,由 rd_ComRead 自动调用 */ +int Modbus_Slave_Check(char *_pBuffer, uint32_t _iSize); +void Modbus_Slave_Decode(const char *_pBuffer, uint32_t _iSize); + +/* Modbus 主机接口 */ +void Modbus_Master_Init(TComCtrl *ptRS485); +void MB_WriteHoldingReg(uint8_t _addr, uint16_t _reg, uint16_t _data); +void calculate_dmk(void); + +/* CRC 计算 */ +uint16_t CalculateCRC(uint8_t *data, uint8_t length); + +#endif /* __MODBUS_SLAVE_H */ diff --git a/project/Spoolend/modbus.c b/project/Spoolend/modbus.c new file mode 100644 index 0000000..b457600 --- /dev/null +++ b/project/Spoolend/modbus.c @@ -0,0 +1,342 @@ +#include "modbus.h" +#include + +/*----------------------------------------------* + * 全局变量 * + *----------------------------------------------*/ +uint16_t holdingRegisters[REGISTER_COUNT] = { + 0x0032, 0x0032, 0x0032, 0x0032, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000 +}; + +uint8_t registerWritable[REGISTER_COUNT] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +}; + +uint8_t RS485_1_RX = 0; +uint8_t RS485_1_Host_Existed = 0; +char dmk_read_state = 0; + +/*----------------------------------------------* + * 内部变量 * + *----------------------------------------------*/ +static TComCtrl *s_ptMasterCom = NULL; /* Modbus 主机通信对象 */ +static uint8_t response[256]; +static uint8_t master_tx_buf[256]; + +/*----------------------------------------------* + * 内部函数声明 * + *----------------------------------------------*/ +static void Modbus_HandleFunction03(uint16_t startAddress, uint16_t registerCount); +static void Modbus_HandleFunction06(uint16_t registerAddress, uint16_t value); +static void Modbus_HandleFunction10(uint16_t startAddress, uint16_t registerCount, uint8_t *data); +static void Modbus_SendExceptionResponse(uint8_t exceptionCode); +static void Modbus_SendResponse(TComCtrl *ptCom, uint8_t *data, uint8_t length); + +/*----------------------------------------------* + * CRC 计算 * + *----------------------------------------------*/ +uint16_t CalculateCRC(uint8_t *data, uint8_t length) +{ + uint16_t crc = 0xFFFF; + + for (uint8_t i = 0; i < length; i++) + { + crc ^= (uint16_t)data[i]; + + for (uint8_t j = 0; j < 8; j++) + { + if ((crc & 0x0001) != 0) + { + crc >>= 1; + crc ^= 0xA001; + } + else + { + crc >>= 1; + } + } + } + + return crc; +} + +/*----------------------------------------------* + * TComCtrl check 回调 * + * 返回 >0: 帧长, =0: 数据不足, <0: 错误 * + *----------------------------------------------*/ +int Modbus_Slave_Check(char *_pBuffer, uint32_t _iSize) +{ + if (_iSize < 4) return 0; + + uint8_t func = (uint8_t)_pBuffer[1]; + uint16_t expected_len = 0; + + switch (func) + { + case MODBUS_FUNCTION_CODE_03: + case MODBUS_FUNCTION_CODE_06: + expected_len = 8; + break; + case MODBUS_FUNCTION_CODE_10: + if (_iSize >= 7) + { + uint8_t byteCount = (uint8_t)_pBuffer[6]; + expected_len = 7 + byteCount + 2; + } + else + { + return 0; + } + break; + default: + return -1; + } + + if (_iSize < expected_len) return 0; + + /* CRC 校验 */ + if (!MODBUS_CRC_OFF) + { + uint16_t crcReceived = ((uint8_t)_pBuffer[expected_len - 1] << 8) + | (uint8_t)_pBuffer[expected_len - 2]; + uint16_t crcCalculated = CalculateCRC((uint8_t *)_pBuffer, expected_len - 2); + if (crcReceived != crcCalculated) return -1; + } + + return expected_len; +} + +/*----------------------------------------------* + * TComCtrl decode 回调 * + * 由 rd_ComRead 内部调用,处理完整 Modbus 帧 * + *----------------------------------------------*/ +void Modbus_Slave_Decode(const char *_pBuffer, uint32_t _iSize) +{ + uint16_t startAddress = 0; + uint16_t registerCount = 0; + + RS485_1_Host_Existed = 1; + + if ((uint8_t)_pBuffer[0] != MODBUS_SLAVE_ADDRESS) return; + + RS485_1_RX = 1; + + switch ((uint8_t)_pBuffer[1]) + { + case MODBUS_FUNCTION_CODE_03: + startAddress = ((uint8_t)_pBuffer[2] << 8) | (uint8_t)_pBuffer[3]; + registerCount = ((uint8_t)_pBuffer[4] << 8) | (uint8_t)_pBuffer[5]; + Modbus_HandleFunction03(startAddress, registerCount); + break; + + case MODBUS_FUNCTION_CODE_06: + startAddress = ((uint8_t)_pBuffer[2] << 8) | (uint8_t)_pBuffer[3]; + { + uint16_t value = ((uint8_t)_pBuffer[4] << 8) | (uint8_t)_pBuffer[5]; + Modbus_HandleFunction06(startAddress, value); + } + break; + + case MODBUS_FUNCTION_CODE_10: + startAddress = ((uint8_t)_pBuffer[2] << 8) | (uint8_t)_pBuffer[3]; + registerCount = ((uint8_t)_pBuffer[4] << 8) | (uint8_t)_pBuffer[5]; + { + uint8_t byteCount = (uint8_t)_pBuffer[6]; + if (byteCount == registerCount * 2 && _iSize >= 7 + byteCount + 2) + { + Modbus_HandleFunction10(startAddress, registerCount, (uint8_t *)&_pBuffer[7]); + } + else + { + Modbus_SendExceptionResponse(0x03); + } + } + break; + + default: + Modbus_SendExceptionResponse(0x01); + break; + } +} + +/*----------------------------------------------* + * Modbus FC03 - 读保持寄存器 * + *----------------------------------------------*/ +static void Modbus_HandleFunction03(uint16_t startAddress, uint16_t registerCount) +{ + if (startAddress < REGISTER_START_ADDRESS + || startAddress + registerCount > REGISTER_START_ADDRESS + REGISTER_COUNT + || registerCount > 0x7D) + { + Modbus_SendExceptionResponse(0x02); + return; + } + + uint8_t byteCount = registerCount * 2; + uint8_t index = 0; + + response[index++] = MODBUS_SLAVE_ADDRESS; + response[index++] = MODBUS_FUNCTION_CODE_03; + response[index++] = byteCount; + + for (uint16_t i = 0; i < registerCount; i++) + { + uint16_t regValue = holdingRegisters[(startAddress - REGISTER_START_ADDRESS) + i]; + response[index++] = (regValue >> 8) & 0xFF; + response[index++] = regValue & 0xFF; + } + + uint16_t crc = CalculateCRC(response, index); + response[index++] = crc & 0xFF; + response[index++] = (crc >> 8) & 0xFF; + + /* Modbus 从机响应通过 g_ptRS485_1 发送(由 Spoolend 传入)*/ + /* 这里使用 s_ptMasterCom 临时存储从机 com,后面在 Init 中设置 */ + /* 实际发送通过 decode 上下文中的 com 对象 */ + /* 由于 decode 回调没有传入 com 指针,需要一个全局引用 */ + extern TComCtrl *g_ptRS485_1; + Modbus_SendResponse(g_ptRS485_1, response, index); +} + +/*----------------------------------------------* + * Modbus FC06 - 写单个寄存器 * + *----------------------------------------------*/ +static void Modbus_HandleFunction06(uint16_t registerAddress, uint16_t value) +{ + if (registerAddress < REGISTER_START_ADDRESS + || registerAddress >= REGISTER_START_ADDRESS + REGISTER_COUNT) + { + Modbus_SendExceptionResponse(0x02); + return; + } + + holdingRegisters[registerAddress - REGISTER_START_ADDRESS] = value; + + uint8_t index = 0; + response[index++] = MODBUS_SLAVE_ADDRESS; + response[index++] = MODBUS_FUNCTION_CODE_06; + response[index++] = (registerAddress >> 8) & 0xFF; + response[index++] = registerAddress & 0xFF; + response[index++] = (value >> 8) & 0xFF; + response[index++] = value & 0xFF; + + uint16_t crc = CalculateCRC(response, index); + response[index++] = crc & 0xFF; + response[index++] = (crc >> 8) & 0xFF; + + extern TComCtrl *g_ptRS485_1; + Modbus_SendResponse(g_ptRS485_1, response, index); +} + +/*----------------------------------------------* + * Modbus FC10 - 写多个寄存器 * + *----------------------------------------------*/ +static void Modbus_HandleFunction10(uint16_t startAddress, uint16_t registerCount, uint8_t *data) +{ + if (startAddress < REGISTER_START_ADDRESS + || startAddress + registerCount > REGISTER_START_ADDRESS + REGISTER_COUNT + || registerCount == 0 || registerCount > 0x7B) + { + Modbus_SendExceptionResponse(0x02); + return; + } + + for (uint16_t i = 0; i < registerCount; i++) + { + uint16_t regIndex = (startAddress - REGISTER_START_ADDRESS) + i; + uint16_t value = (data[i * 2] << 8) | data[i * 2 + 1]; + + if (registerWritable[regIndex]) + { + holdingRegisters[regIndex] = value; + } + } + + uint8_t index = 0; + response[index++] = MODBUS_SLAVE_ADDRESS; + response[index++] = MODBUS_FUNCTION_CODE_10; + response[index++] = (startAddress >> 8) & 0xFF; + response[index++] = startAddress & 0xFF; + response[index++] = (registerCount >> 8) & 0xFF; + response[index++] = registerCount & 0xFF; + + uint16_t crc = CalculateCRC(response, index); + response[index++] = crc & 0xFF; + response[index++] = (crc >> 8) & 0xFF; + + extern TComCtrl *g_ptRS485_1; + Modbus_SendResponse(g_ptRS485_1, response, index); +} + +/*----------------------------------------------* + * 发送异常响应 * + *----------------------------------------------*/ +static void Modbus_SendExceptionResponse(uint8_t exceptionCode) +{ + extern TComCtrl *g_ptRS485_1; + + response[0] = MODBUS_SLAVE_ADDRESS; + response[1] = 0x80; + response[2] = exceptionCode; + + uint16_t crc = CalculateCRC(response, 3); + response[3] = crc & 0xFF; + response[4] = (crc >> 8) & 0xFF; + + Modbus_SendResponse(g_ptRS485_1, response, 5); +} + +/*----------------------------------------------* + * 发送响应(通过 TComCtrl 异步发送) * + *----------------------------------------------*/ +static void Modbus_SendResponse(TComCtrl *ptCom, uint8_t *data, uint8_t length) +{ + if (ptCom) + { + rd_ComWrite(ptCom, (char *)data, length); + } +} + +/*----------------------------------------------* + * Modbus 主机初始化 * + *----------------------------------------------*/ +void Modbus_Master_Init(TComCtrl *ptRS485) +{ + s_ptMasterCom = ptRS485; +} + +/*----------------------------------------------* + * Modbus 主机 - 写单个寄存器 * + *----------------------------------------------*/ +void MB_WriteHoldingReg(uint8_t _addr, uint16_t _reg, uint16_t _data) +{ + uint16_t TxCount = 0; + uint16_t crc = 0; + + master_tx_buf[TxCount++] = _addr; + master_tx_buf[TxCount++] = 0x06; + master_tx_buf[TxCount++] = _reg >> 8; + master_tx_buf[TxCount++] = _reg; + master_tx_buf[TxCount++] = _data >> 8; + master_tx_buf[TxCount++] = _data; + + crc = CalculateCRC(master_tx_buf, TxCount); + master_tx_buf[TxCount++] = crc & 0xFF; + master_tx_buf[TxCount++] = (crc >> 8) & 0xFF; + + if (s_ptMasterCom) + { + rd_ComWrite(s_ptMasterCom, (char *)master_tx_buf, TxCount); + } + dmk_read_state = 0; +} + +/*----------------------------------------------* + * DMK 响应超时检测 * + *----------------------------------------------*/ +void calculate_dmk(void) +{ + dmk_read_state = 1; +}