news 2026/9/7 12:01:35

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零吃透 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 的异同。

读完本章你将掌握:

  1. 线圈、离散输入、保持寄存器、输入寄存器四类对象区别
  2. 05 功能码写单个线圈原理 + STM32 裸机代码
  3. 0F 功能码批量写多个线圈原理 + STM32 裸机代码
  4. 线圈位操作、字节打包、掩码处理工程实现
  5. Modbus‑TCP 报文结构、和 RTU 的核心差异

<hr>

1 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 → 线圈地址 0;bit1 →线圈地址 1 … bit7 →线圈地址 7。

同样注意手册大多1 起始,代码数组下标使用0 起始,地址需要减 1。

<hr>

2 05 功能码:强制单个线圈(写单路开关输出)

2.1 协议帧格式

主机请求帧:

表格

从机地址功能码 0x05线圈地址高线圈地址低状态高字节状态低字节CRC 低CRC 高

线圈状态约定:

  • 置 ON:0xFF 0x00
  • 置 OFF:0x00 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] |= (1<<bit_idx); } else { coil_buf[byte_idx] &= ~(1<<bit_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_len=0; 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_addr>>8)&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_len=0; //地址越界检查 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(i=0; 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 开发的区别

  1. RTU 依靠 3.5 字符空闲判断帧结束;TCP 是流式字节流,依靠 MBAP 头长度字段解析完整帧。
  2. RTU 需要 CRC 校验;TCP 不需要 CRC。
  3. RTU 总线一问一答;TCP 可以建立长连接,支持多请求。

工程建议:已经写好的 RTU 功能码处理函数,移植 Modbus‑TCP 时,只需要剥离 CRC,增加 MBAP 包头解析,业务逻辑几乎不用改动。

5 线圈开发工程避坑总结

  1. 05 功能码不是写入 1 和 0,必须使用 0xFF00/0x0000,很多新手这里出错。
  2. 线圈是 bit 存储,注意字节内 bit 顺序,低位对应低线圈地址
  3. 批量写线圈 0F,线圈总数不是 8 倍数时,最后字节高位无效 bit 直接丢弃,不要写入。
  4. 线圈地址同样区分手册 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 的复用关系。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 12:01:30

Emacs浏览器接入LLM:把杂乱的网页变成结构化知识

先从一个很具体的场景说起。你在 Emacs 里用 EWW 打开一篇技术文章&#xff0c;页面确实能显示&#xff0c;链接也能点&#xff0c;但你的第一反应大概率是&#xff1a;这排版也太朴素了。图片基本缺席&#xff0c;侧边栏被拍平&#xff0c;广告和导航区块混在文本里&#xff0…

作者头像 李华
网站建设 2026/9/4 13:47:42

自研Python自动化渗透测试工具:架构设计、模块实现与合规实践

简介&#xff1a;这是一套面向网络安全初学者与渗透测试实践者的Python自动化渗透测试工具集&#xff0c;聚焦Web资产探测、漏洞扫描与报告生成等核心场景&#xff0c;助力安全人员高效开展红队演练与系统加固。压缩包共22个文件&#xff0c;含11个Python脚本&#xff08;如web…

作者头像 李华
网站建设 2026/9/5 23:15:25

STC15并口驱动ST7735彩屏:告别模拟SPI卡顿,实战记录

简介&#xff1a;本资源是面向嵌入式初学者与51单片机开发者的STC15系列驱动ST7735S彩色TFT液晶屏的完整并口驱动工程&#xff0c;专为解决小尺寸彩屏在资源受限MCU上的高效显示问题而设计&#xff0c;适用于智能仪表、IoT终端、教学实验等低功耗嵌入式场景。压缩包共14个文件&…

作者头像 李华
网站建设 2026/9/5 21:20:12

财务数字化——解读德勤 -财务趋势2026 驾驭不断拓展的财务边界【附全文阅读】

这份德勤《财务趋势 2026》白皮书是财务数字化、CFO 战略转型类咨询方案推介权威支撑材料,具备极强行业说服力与落地参考价值。报告基于全球 1326 家大型企业财务高管调研数据,提炼五大核心财务变革趋势,贴合国资委一流财务、金税四期国内监管导向,可直接用于投标、高管汇报…

作者头像 李华
网站建设 2026/9/6 1:02:55

掌纹识别系统设计:从图像预处理到特征提取与分类实战

简介&#xff1a;本资源是一套基于MATLAB实现的掌纹识别完整技术方案&#xff0c;面向生物特征识别方向的学习者、科研人员及图像处理初学者&#xff0c;聚焦掌纹图像预处理、特征提取与匹配三大核心环节&#xff0c;解决身份验证场景下的算法复现与工程实践问题。压缩包共9个文…

作者头像 李华
网站建设 2026/9/4 22:54:29

宁夏靠谱的CMA资质服务商哪家专业

本文将围绕宁夏地区CMA、CNAS实验室资质认证认可咨询服务展开&#xff0c;介绍资质认证相关知识&#xff0c;如申报条件、流程等&#xff0c;还会提及企业面临的痛点&#xff0c;最后为企业实验室负责人在选择本地咨询服务商方面给出建议&#xff0c;其中精益至简&#xff08;宁…

作者头像 李华