尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从零吃透 Modbus 通信|第 6 章:线圈功能码 05/0F 实现 Modbus‑TCP 基础入门

从零吃透 Modbus 通信|第 6 章:线圈功能码 05/0F 实现  Modbus‑TCP 基础入门 博客定位扩展 Modbus 离散 IO 功能完成 RTU 全部常用功能码对比 RTU 与 TCP 差异建立以太网 Modbus 认知可直接发布 CSDN。前言前面章节我们完成保持寄存器03/06/16主机与从机、异常码、故障排查。在工业现场除了寄存器存储数值还有大量开关量继电器输出、光耦输入、阀门开关Modbus 使用线圈 (Coil)与离散输入 (Discrete Input)处理开关信号。很多教程只讲保持寄存器跳过线圈功能码实际 IO 板、PLC、继电器模块大量使用 05、0F 功能码。本章手写实现05 强制单线圈、0F 强制多线圈同时简单介绍 Modbus‑TCP 协议对比 RTU 与 TCP 的异同。读完本章你将掌握线圈、离散输入、保持寄存器、输入寄存器四类对象区别05 功能码写单个线圈原理 STM32 裸机代码0F 功能码批量写多个线圈原理 STM32 裸机代码线圈位操作、字节打包、掩码处理工程实现Modbus‑TCP 报文结构、和 RTU 的核心差异hr1 Modbus 四类存储对象完整区分Modbus 协议定义 4 种独立地址空间地址互相独立不能混用很多调试故障来源于混淆四类空间。对象功能码读写属性用途数据宽度线圈 Coils01 读线圈 / 05 写单线圈 / 0F 写多线圈可读可写继电器输出、阀门、开关输出1bit开关 0/1离散输入 Discrete Inputs02 读离散输入只读外部光耦输入、按键状态、限位信号1bit开关 0/1保持寄存器 Holding Registers03 读 /06 写单 /10 写多可读可写参数、转速、温度、电压数值16bit输入寄存器 Input Registers04 读输入寄存器只读传感器实时采集值16bit重点线圈是按 bit 操作。8 个线圈打包成 1 个字节。低位代表低地址线圈。 例如 1 个字节bit0 → 线圈地址 0bit1 →线圈地址 1 … bit7 →线圈地址 7。同样注意手册大多1 起始代码数组下标使用0 起始地址需要减 1。hr2 05 功能码强制单个线圈写单路开关输出2.1 协议帧格式主机请求帧表格从机地址功能码 0x05线圈地址高线圈地址低状态高字节状态低字节CRC 低CRC 高线圈状态约定置 ON0xFF 0x00置 OFF0x00 0x00注意不是写入 1 和 0协议规定固定使用0xFF00代表打开0x0000代表关闭。从机正常应答原样返回主机请求帧参数非法返回异常帧。2.2 从机线圈数据结构定义#define COIL_MAX_NUM 16 //最大线圈数量16路开关输出 uint8_t coil_buf[(COIL_MAX_NUM 7)/8]; //位存储每字节存8路线圈 // 设置指定线圈 void Modbus_Coil_Set(uint16_t coil_addr, uint8_t val) { uint16_t byte_idx coil_addr / 8; uint8_t bit_idx coil_addr % 8; if(coil_addr COIL_MAX_NUM) return; if(val ! 0) { coil_buf[byte_idx] | (1bit_idx); } else { coil_buf[byte_idx] ~(1bit_idx); } } // 读取指定线圈状态 uint8_t Modbus_Coil_Get(uint16_t coil_addr) { uint16_t byte_idx coil_addr /8; uint8_t bit_idx coil_addr %8; if(coil_addr COIL_MAX_NUM) return 0; return (coil_buf[byte_idx] bit_idx) 0x01; }2.3 从机 05 功能码处理函数uint8_t Slave_Write_Single_Coil(void) { uint16_t coil_addr; uint16_t coil_cmd; uint8_t tx_len0; coil_addr (rx_buf[2] 8) | rx_buf[3]; coil_cmd (rx_buf[4] 8) | rx_buf[5]; //地址越界判断 if(coil_addr COIL_MAX_NUM) { Slave_Return_Exception(0x02); return 5; } //校验合法命令只允许0xFF00或者0x0000 if(coil_cmd ! 0xFF00 coil_cmd !0x0000) { Slave_Return_Exception(0x03); return 5; } //设置线圈状态 if(coil_cmd 0xFF00) { Modbus_Coil_Set(coil_addr,1); //业务可以在这里控制IO输出继电器 //Relay_ON(coil_addr); } else { Modbus_Coil_Set(coil_addr,0); //Relay_OFF(coil_addr); } //正常应答原样返回请求帧 tx_buf[tx_len] SLAVE_ADDR; tx_buf[tx_len] 0x05; tx_buf[tx_len] rx_buf[2]; tx_buf[tx_len] rx_buf[3]; tx_buf[tx_len] rx_buf[4]; tx_buf[tx_len] rx_buf[5]; Modbus_Append_CRC(tx_buf,tx_len); return tx_len; }2.4 主机 05 发送函数//写单个线圈slave_addr从机地址coil_addr线圈地址val 1打开 0关闭 void Modbus_Master_Write_Single_Coil(uint8_t slave_addr,uint16_t coil_addr,uint8_t val) { tx_len 0; modbus_tx_buf[tx_len] slave_addr; modbus_tx_buf[tx_len] 0x05; modbus_tx_buf[tx_len] (coil_addr8)0xFF; modbus_tx_buf[tx_len] coil_addr 0xFF; if(val) { modbus_tx_buf[tx_len] 0xFF; modbus_tx_buf[tx_len] 0x00; } else { modbus_tx_buf[tx_len] 0x00; modbus_tx_buf[tx_len] 0x00; } Modbus_Append_CRC(modbus_tx_buf,tx_len); rx_len 0; modbus_tick HAL_GetTick(); USART_Send_Data(modbus_tx_buf,tx_len); }3 0F 功能码强制多线圈批量写多路开关3.1 协议说明 0F 实现一次性批量设置多个线圈状态。主机请求帧从机地址 0x0F 起始地址高 起始地址低 线圈数量高 线圈数量低 字节数 N个字节数据 CRC低 CRC高每一个 bit 代表一路线圈字节最低 bit 对应线圈起始地址。从机应答返回从机地址 0x0F 起始地址 2 字节 线圈数量 2 字节 CRC。不返回线圈数据。3.2 从机 0F 批量写线圈实现uint8_t Slave_Write_Multi_Coil(void) { uint16_t start_addr (rx_buf[2]8)|rx_buf[3]; uint16_t coil_num (rx_buf[4]8)|rx_buf[5]; uint8_t byte_cnt rx_buf[6]; uint16_t i; uint8_t bit_pos; uint8_t tx_len0; //地址越界检查 if(start_addr coil_num COIL_MAX_NUM) { Slave_Return_Exception(0x02); return 5; } //字节数校验 uint8_t expect_byte (coil_num 7)/8; if(byte_cnt ! expect_byte) { Slave_Return_Exception(0x03); return 5; } //循环批量写入线圈 for(i0; i coil_num; i) { uint8_t data_byte rx_buf[7 i/8]; bit_pos i%8; uint8_t bit_val (data_byte bit_pos) 0x01; Modbus_Coil_Set(start_addr i, bit_val); //业务层同步控制IO } //组装应答帧 tx_buf[tx_len] SLAVE_ADDR; tx_buf[tx_len] 0x0F; tx_buf[tx_len] rx_buf[2]; tx_buf[tx_len] rx_buf[3]; tx_buf[tx_len] rx_buf[4]; tx_buf[tx_len] rx_buf[5]; Modbus_Append_CRC(tx_buf,tx_len); return tx_len; }常见踩坑线圈数量不是 8 整数倍时最后字节高位的无效 bit设备需要忽略不处理。4 Modbus‑TCP 基础入门4.1 RTU 与 TCP 对比Modbus‑RTU串口 485 物理层帧尾携带 CRC 校验一主多从波特率控制通信速度。 Modbus‑TCP以太网物理层基于 TCP 协议默认端口502。没有 CRC 校验可靠性依靠 TCP。Modbus‑TCP 报文头部增加MBAP 头7 字节MBAP 头结构事务标识 2B协议标识 2B长度 2B单元标识 1B后面紧跟 PDU和 RTU 除去 CRC 的 PDU 完全一样。事务标识主机自增序号匹配请求应答协议标识固定 0长度后面字节总数量单元标识相当于 RTU 的从机地址。核心Modbus‑TCP 的 PDU 部分和 RTU 去掉 CRC 后的 PDU 一模一样业务逻辑代码可以大量复用只需要处理 MBAP 包头。4.2 RTU 与 TCP 开发的区别RTU 依靠 3.5 字符空闲判断帧结束TCP 是流式字节流依靠 MBAP 头长度字段解析完整帧。RTU 需要 CRC 校验TCP 不需要 CRC。RTU 总线一问一答TCP 可以建立长连接支持多请求。工程建议已经写好的 RTU 功能码处理函数移植 Modbus‑TCP 时只需要剥离 CRC增加 MBAP 包头解析业务逻辑几乎不用改动。5 线圈开发工程避坑总结05 功能码不是写入 1 和 0必须使用 0xFF00/0x0000很多新手这里出错。线圈是 bit 存储注意字节内 bit 顺序低位对应低线圈地址。批量写线圈 0F线圈总数不是 8 倍数时最后字节高位无效 bit 直接丢弃不要写入。线圈地址同样区分手册 1 起始代码 0 起始。 5.Modbus‑TCP 无 CRC不要把 RTU 的 CRC 带到 TCP 报文中。6 本章总结1. 完整区分 Modbus 四类地址空间线圈、离散输入、保持寄存器、输入寄存器。2. 实现 05 写单线圈、0F 批量写多线圈主机与从机代码可以直接控制继电器开关量 IO。3. 理解 bit 打包字节的处理逻辑工业 IO 板开发必备。4. 了解 Modbus‑TCP 报文 MBAP 头部明白 RTU 与 TCP 的复用关系。
返回列表