news 2026/10/31 2:12:15

ModBus

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ModBus

CRC16校验算法

private void crc16(byte[] data) { int[] temdata = new int[data.Length + 2]; int xda, xdapoly; int i, j, xdabit; xda = 0xFFFF; xdapoly = 0xA001; for (i = 0; i < data.Length - 2; i++) { xda ^= data[i]; for (j = 0; j < 8; j++) { xdabit = (int)(xda & 0x01); xda >>= 1; if (xdabit == 1) xda ^= xdapoly; } } data[data.Length - 2] = (byte)(xda & 0xFF); data[data.Length - 1] = (byte)(xda >> 8); }

检查CRC

private bool checkCRC(byte[] data) { int len = data.Length; byte[] crc = new byte[data.Length]; Array.Copy(data, crc, data.Length - 2); crc16(crc); return (crc[len - 1] == data[len - 1]) && (crc[len - 2] == data[len - 2]); }

示例

/// <summary> /// 0x03 功能码 读多路寄存器输入 /// </summary> /// <param name="addr_reg">要读的起始寄存器地址,比如 12</param> /// <param name="len">要读几个寄存器(LEN 要小于128)</param> /// <returns>读回来的无符号16位整数数组,长度 = len</returns> public UInt16[] ReadRegister(UInt16 addr_reg, UInt16 len) { try { // 8 个字节的分配:地址1 + 功能码1 + 地址2 + 个数2 + CRC校验2 byte[] bytesSend = new byte[8]; // 创建一个长度 = len 的结果数组 用于函数返回 UInt16[] data = new UInt16[len]; // 填"从站地址",要跟哪台仪器通讯 bytesSend[0] = 1; // "功能码" 0x03,"读保持寄存器" bytesSend[1] = 0x03; unchecked { //读取的地址 //地址高8位 bytesSend[2] = (byte)(addr_reg >> 8); //地址低8位 bytesSend[3] = (byte)addr_reg; //读取的数量 //读取的高8位 bytesSend[4] = (byte)(len >> 8); //读取的低8位 bytesSend[5] = (byte)len; } // 调用 CRC16 校验算法,它会"直接修改"bytesSend 数组 crc16(bytesSend); // 通过串口把这封信发出去,然后等着收回答复 byte[] rx_bytes = mComPort.QueryBytes(bytesSend, len * 2 + 5, 50); // 检查收回来的数据的 CRC 校验码对不对 if (!checkCRC(rx_bytes)) { // 校验不通过,说明数据在传输过程中出错了(干扰、丢包等) throw new Exception("crc error"); } // 创建一个长度 = len × 2 的字节数组,专门装"纯数据部分" // 因为每个寄存器是 2 个字节,len 个寄存器就是 len*2 个字节 byte[] des = new byte[len * 2]; // 从收到的 rx_bytes 里,把纯数据抠出来放进 des Array.Copy(rx_bytes, 3, des, 0, des.Length); // 循环 len 次,把每 2 个字节拼成 1 个 UInt16 数字 for (int i = 0; i < len; i++) { // 高8位 + 低8位 → | 拼接 把它们粘回一个完整的 16 位数字 data[i] = (UInt16)(des[2 * i] << 8 | des[2 * i + 1]); } return data; } catch (Exception) { throw; } }

人脸模组实战

/// <summary> /// 通用自定义命令发送方法 /// </summary> /// <param name="Model">消息 ID</param> /// <param name="Readdata">具体数据</param> /// <returns></returns> public string SendDataRead(byte Model, byte[] Readdata) { int DataSize = 0; if (Readdata != null) { DataSize = Readdata.Length; } byte[] sendData = new byte[DataSize + 6]; sendData[0] = 0xEF; sendData[1] = 0xAA; sendData[2] = Model; int jsonSize = DataSize; sendData[3] = (byte)((jsonSize >> 8) & 0xFF); sendData[4] = (byte)(jsonSize & 0xFF); if (DataSize > 0) { Array.Copy(Readdata, 0, sendData, 5, DataSize); } sendData[sendData.Length - 1] = XOR(sendData.Skip(2).ToArray());//除去 EF AA 的XOR校验 return SendData(sendData); } /// <summary> /// 通用自定义命令接收方法 /// </summary> /// <param name="Readdata">缓存区</param> /// <param name="validLen">缓存区有效的数据长度</param> /// <returns></returns> public string ReadDataRead(byte[] Readdata,ref int validLen) { if (Readdata == null || validLen <= 0) { addLOG?.Invoke("缓存区为空"); return "FAIL"; } int DataSyncWord = -1; for (int i = 0; i < validLen-1; i++) { if (Readdata[i]==0xEF&& Readdata[i+1]== 0xAA) { DataSyncWord = i; break; } } if (DataSyncWord == -1) { // 保留末尾可能是帧头首字节的那个 0xEF,其余丢弃因为前面的数据都是无效的 bool keep = Readdata[validLen - 1] == 0xEF; if (keep) Readdata[0] = 0xEF; validLen = keep ? 1 : 0; addLOG?.Invoke("未找到数据帧头"); return "FAIL"; } // 清除帧头之前的无效数据 if (DataSyncWord > 0) { Array.Copy(Readdata, DataSyncWord, Readdata, 0, validLen - DataSyncWord); validLen -= DataSyncWord; // 同步扣减有效长度 DataSyncWord = 0; } //清除帧头之前的无效数据后,重新计算长度 半包判断 if (validLen < 6) { addLOG?.Invoke("数据帧不完整"); return "WAIT"; } //获取数据长度 int length = (Readdata[3] << 8) | Readdata[4]; int frameLen = 6 + length; //完整信息的长度 //检查是否为完整数据帧 if (validLen < frameLen) { addLOG?.Invoke("数据帧不完整"); return "WAIT"; } //完整信息的长度 = 帧头(2) + Model(1) + Size(2) + Data(length) + XOR(1) byte[] data = new byte[frameLen]; //提取完整信息 Array.Copy(Readdata, 0, data, 0, frameLen); //检查XOR校验 if (!CheckXOR(data.Skip(2).ToArray())) { addLOG?.Invoke("XOR 校验失败,丢弃该帧"); Array.Copy(Readdata, 1, Readdata, 0, validLen - 1); validLen -= 1; return "FAIL"; } // 删除已提取的完整信息:前移 + 尾部清零 + 更新有效长度 int remain = validLen - frameLen; if (remain > 0) { Array.Copy(Readdata, frameLen, Readdata, 0, remain); } Array.Clear(Readdata, remain, frameLen); // 清残留,否则下次把垃圾当数据 validLen = remain; sendmodel?.Invoke(data); return "PASS"; } bool CheckXOR(byte[] data) { byte[] bytes= new byte[data.Length - 1]; Array.Copy(data, 0, bytes, 0, data.Length - 1); byte xor = XOR(bytes); return xor == data[data.Length - 1]; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/31 2:12:12

激光技术从实验室走向万物互联的历程

理论奠基 01 量子理论的诞生 1900年&#xff1a;马克斯普朗克提出能量量子化假说&#xff0c;解释了黑体辐射问题&#xff0c;为量子力学奠基。1917年&#xff1a;爱因斯坦在《关于辐射的量子理论》中提出受激辐射&#xff08;Stimulated Emission&#xff09;概念&#xff0c;…

作者头像 李华
网站建设 2026/10/31 2:11:07

2026年公司文件共享服务器搭建指南:告别Windows共享和NAS的维护噩梦

文章目录一、传统文件服务器的三大痛点二、方案对比&#xff1a;四种文件服务器的总拥有成本三、坚果云「零维护文件服务器」搭建流程3.1 第一步&#xff1a;创建公司文件架构3.2 第二步&#xff1a;按部门创建群组3.3 第三步&#xff1a;设置文件夹权限3.4 第四步&#xff1a;…

作者头像 李华
网站建设 2026/10/31 2:11:01

接口返回成功,为什么业务却没完成?

栈外&#xff0c;就是跳出代码栈。不只盯着代码本身&#xff0c;同时去看项目落地、技术选型、行业现状&#xff0c;以及开发者容易踩的那些坑。接口返回了成功&#xff0c;用户却迟迟拿不到结果。这类问题在业务系统里并不少见。 比如&#xff0c;用户提交了一份导出任务&…

作者头像 李华
网站建设 2026/10/31 2:10:04

企业微信二次开发如何实现客户消息触发业务查询并自动返回结果

在企业微信二次开发项目里&#xff0c;客户经常会通过单聊或外部群发起业务查询。 例如客户发送“查订单”“查物流”“查询售后进度”&#xff0c;如果每次都需要人工查询再回复&#xff0c;处理效率会比较低。 通过星云API接入消息能力后&#xff0c;可以将客户消息与内部业…

作者头像 李华
网站建设 2026/10/31 2:09:49

Cortex-R52与TriCore详细对比

文章目录架构哲学&#xff1a;通用实时 vs. 垂直整合实时性与中断响应&#xff1a;不同的优化路径功能安全&#xff1a;ASIL-D 的两种实现路径内存、虚拟化与混合关键性性能参数概览软件与开发生态选型判断Cortex-R52 与 TriCore 1.6.2 是两款设计哲学截然不同的车规级实时内核…

作者头像 李华