当前位置: 首页 > news >正文

基于CH582M实现CRC-16校验的串口/RS485协议

文章目录

    • 一、 核心逻辑
      • 1.核心算法:CRC-16 的 C 语言实现与逻辑拆解
      • 2. 发送端(主机)逻辑:“算余数,贴封条”
      • 3. 接收端(从机)逻辑:“掐头去尾,重新计算”
      • 4. 极简自定义协议帧结构
    • 二、 【发送端】核心逻辑与完整代码(主控机)
      • 1. 发送端逻辑:“算余数,贴封条”
      • 2. 发送端完整 `Main.c`
    • 三、 【接收端】核心逻辑与完整代码(从机/充电板)
      • 1. 接收端逻辑:“掐头去尾,重新计算”
      • 2. 接收端完整 Main.c

一、 核心逻辑

1.核心算法:CRC-16 的 C 语言实现与逻辑拆解

无论是发送端还是接收端,生成和校验防伪标签的核心都是下面这个函数。很多人看到按位异或(^=)和移位(>>=)就头疼,其实它的逻辑非常规律:

// ==========================================// 【基础算法】:标准 Modbus CRC-16 计算// ==========================================uint16_tCalc_CRC16(uint8_t*pBuf,uint8_tlen){uint16_tcrc=0xFFFF;// 1. 预置 16 位寄存器为全 1for(uint8_ti=0;i<len;i++){crc^=pBuf[i];// 2. 把新拉进来的字节与 CRC 寄存器进行异或for(uint8_tj=0;j<8;j++)// 3. 循环 8 次,处理这一个字节的 8 个位{if(crc&0x0001){// 判断最低位是否为 1 (是否够除)crc>>=1;// 先将整体向右移 1 位crc^=0xA001;// 再与工业多项式 0xA001 进行异或}else{crc>>=1;// 最低位为 0,不够除,直接右移跳过}}}returncrc;}

代码通俗解析:
为什么初始值是 0xFFFF?
工业界为了防止全零数据段导致校验失效,强制在开始计算前给寄存器“垫”一个全 1 的底(类似电子秤的预先去皮)。

为什么要判断 crc & 0x0001 并全部向右移(>>= 1)?
正常的数学除法是从左往右算的。但在真实的单片机串口(UART / RS485)通信中,硬件规则是低位先行(LSB First)。最右边的位会最先发到线缆上。所以代码必须顺应硬件脾气,盯紧最右边(最低位),并不断把数据往右边推。

0xA001 是什么?
它是国际标准 CRC-16 的生成多项式在十六进制下反转过来的“暗号”。当最低位为 1 时,说明遇到了硬骨头,就拿这个暗号去做一次异或运算(相当于除法里的做减法)。

2. 发送端(主机)逻辑:“算余数,贴封条”

  1. 主机首先准备好要发送的核心数据(如:包头 + 地址 + 功能码)。
  2. 将这串核心数据丢进 CRC-16 算法中,计算出一个 16位(2字节)的校验码(余数)。
  3. 把这 2 个字节的校验码(通常是低位在前,高位在后)拼接在核心数据的尾部。
  4. 加上包尾,一并发送出串口。

3. 接收端(从机)逻辑:“掐头去尾,重新计算”

  1. 从机通过串口接收到一整帧完整的数据。
  2. 检查长度、包头、包尾是否合法。
  3. 关键点:从机提取出核心数据部分(不含接收到的 CRC 和包尾),用一模一样的 CRC 算法自己重新算一遍。
  4. 将自己算出的 CRC 与数据帧里带过来的 CRC 进行比对。相等则执行动作,不等则直接丢弃(说明数据在传输路上被干扰了,绝不瞎回复)。

4. 极简自定义协议帧结构

本例不使用冗长复杂的标准 Modbus 寄存器读写规则,而是针对具体动作(如控制继电器启停)设计了以下 6 字节极简自定义协议:

名称字节数示例说明
包头10xAA固定起始符
地址10x01目标设备地址
功能码10x010x01:开启充电,0x02:结束充电
CRC低位10xXXCRC校验码的低 8 位
CRC高位10xXXCRC校验码的高 8 位
包尾10x55固定结束符

二、 【发送端】核心逻辑与完整代码(主控机)

1. 发送端逻辑:“算余数,贴封条”

主机的任务非常单纯,不需要处理复杂的中断,只需像打包员一样按规矩发货:

  1. 准备好要发送的核心数据(包头AA+ 目标地址01+ 功能码01)。
  2. 将这串核心数据丢进 CRC-16 算法中,计算出一个 16位(2字节)的校验码。
  3. 把这 2 个字节的校验码(低位在前,高位在后)拼接在核心数据的尾部。
  4. 加上包尾55,一并轰出串口。

2. 发送端完整Main.c

将以下代码放入你的主机工程中,直接调用Send_Charge_Command()函数即可指哪打哪。

#include"CH58x_common.h"// ==========================================// 【基础算法】:标准 Modbus CRC-16 计算// ==========================================uint16_tCalc_CRC16(uint8_t*pBuf,uint8_tlen){uint16_tcrc=0xFFFF;for(uint8_ti=0;i<len;i++){crc^=pBuf[i];for(uint8_tj=0;j<8;j++){// 注意:串口是低位先行,所以统一右移if(crc&0x0001){crc>>=1;crc^=0xA001;}else{crc>>=1;}}}returncrc;}// ==========================================// 【发送动作】:打包并发送指令// ==========================================voidSend_Charge_Command(uint8_ttarget_addr,uint8_tcmd){uint8_tsend_buf[10];uint8_tlen=0;// 1. 组装数据前半截send_buf[0]=0xAA;// 包头send_buf[1]=target_addr;// 目标地址send_buf[2]=cmd;// 功能码len=3;// 2. 召唤 CRC 算法,生成防伪标签uint16_tmy_crc=Calc_CRC16(send_buf,len);// 3. 将 CRC 附加在数据后 (小端模式:低位在前,高位在后)send_buf[len]=my_crc&0xFF;send_buf[len+1]=(my_crc>>8)&0xFF;// 4. 封上包尾send_buf[len+2]=0x55;// 5. 串口发送 (总长6字节)UART2_SendString(send_buf,6);}// ==========================================// 【主机主函数】// ==========================================intmain(){SetSysClock(CLK_SOURCE_PLL_60MHz);/* --- 串口2硬件初始化 --- */GPIOPinRemap(ENABLE,RB_PIN_UART2);GPIOB_SetBits(GPIO_Pin_23);GPIOB_ModeCfg(GPIO_Pin_22,GPIO_ModeIN_PU);GPIOB_ModeCfg(GPIO_Pin_23,GPIO_ModeOut_PP_5mA);UART2_DefInit();UART2_BaudRateCfg(9600);while(1){// 测试:每隔5秒向 0x01 地址发送开启(0x01)指令mDelaymS(5000);Send_Charge_Command(0x01,0x01);}}

三、 【接收端】核心逻辑与完整代码(从机/充电板)

1. 接收端逻辑:“掐头去尾,重新计算”

接收端处于被动状态,它的核心任务是“防伪对账”,绝不能瞎回复:

  1. 通过串口中断(结合超时机制)接收一整帧数据。

  2. 检查数据总长度(至少6字节),并核对包头和包尾。

  3. 关键点:提取出核心数据部分(不含接收到的 CRC 和包尾),用一模一样的 CRC 算法自己重新算一遍。

  4. 将单片机自己算的 CRC,与发过来的 CRC 进行比对。相等则执行动作并回复,不等则直接丢弃数据(说明在路上被干扰了)。

2. 接收端完整 Main.c

将以下代码放入你的从机(充电板)工程中。

#include"CH58x_common.h"uint8_tRxBuff[100];volatileuint8_trecv_len=0;volatileuint8_trecv_flag=0;uint8_ttrigB;// ==========================================// 【基础算法】:标准 Modbus CRC-16 计算// ==========================================uint16_tCalc_CRC16(uint8_t*pBuf,uint8_tlen){uint16_tcrc=0xFFFF;for(uint8_ti=0;i<len;i++){crc^=pBuf[i];for(uint8_tj=0;j<8;j++){if(crc&0x0001){crc>>=1;crc^=0xA001;}else{crc>>=1;}}}returncrc;}// ==========================================// 【从机主函数:业务解析大脑】// ==========================================intmain(){SetSysClock(CLK_SOURCE_PLL_60MHz);/* --- 串口2硬件初始化 --- */GPIOPinRemap(ENABLE,RB_PIN_UART2);GPIOB_SetBits(GPIO_Pin_23);GPIOB_ModeCfg(GPIO_Pin_22,GPIO_ModeIN_PU);GPIOB_ModeCfg(GPIO_Pin_23,GPIO_ModeOut_PP_5mA);UART2_DefInit();UART2_BaudRateCfg(9600);/* --- 开启接收超时中断 --- */UART2_ByteTrigCfg(UART_7BYTE_TRIG);trigB=7;UART2_INTCfg(ENABLE,RB_IER_RECV_RDY|RB_IER_LINE_STAT);PFIC_EnableIRQ(UART2_IRQn);while(1){if(recv_flag==1){// 1. 长度校验 (带2字节CRC,协议最短6字节)if(recv_len>=6){// 2. 包头包尾特征字校验if(RxBuff[0]==0xAA&&RxBuff[recv_len-1]==0x55){// 3. 计算本地 CRC (recv_len - 3 代表仅取核心数据)uint16_tmy_crc=Calc_CRC16(RxBuff,recv_len-3);// 提取主机发来的 CRCuint16_trecv_crc=RxBuff[recv_len-3]|(RxBuff[recv_len-2]<<8);// 4. 核心对账!if(my_crc==recv_crc){// --- 校验通过,提取控制指令 ---uint8_tdev_addr=RxBuff[1];uint8_tfunc_cmd=RxBuff[2];uint8_treply_buf[20];uint8_treply_len=0;reply_buf[0]=0xBB;reply_buf[1]=dev_addr;reply_buf[2]=func_cmd;switch(func_cmd){case0x01:// 收到开启指令// TODO: 加入 GPIO 驱动继电器闭合的代码reply_buf[3]=0x01;reply_len=7;break;case0x02:// 收到结束指令// TODO: 加入 GPIO 驱动继电器断开的代码reply_buf[3]=0x01;reply_len=7;break;default:reply_len=0;break;}// --- 给主机的应答数据也贴上 CRC 封条 ---if(reply_len>0){uint16_tsend_crc=Calc_CRC16(reply_buf,reply_len-3);reply_buf[reply_len-3]=send_crc&0xFF;reply_buf[reply_len-2]=(send_crc>>8)&0xFF;reply_buf[reply_len-1]=0x55;UART2_SendString(reply_buf,reply_len);}}}}// 无论对错,强制清空接收状态,避免死锁recv_len=0;recv_flag=0;}}}// ==========================================// 【底层苦力:串口中断服务函数】// ==========================================__INTERRUPT __HIGH_CODEvoidUART2_IRQHandler(void){volatileuint8_ti;switch(UART2_GetITFlag()){caseUART_II_RECV_RDY:// 接收达到触发点for(i=0;i!=trigB;i++){RxBuff[recv_len++]=UART2_RecvByte();}break;caseUART_II_RECV_TOUT:// 接收超时(断包,一帧接收完毕)i=UART2_RecvString(&RxBuff[recv_len]);recv_len+=i;recv_flag=1;// 通知主循环开始解析break;}}
http://www.jsqmd.com/news/874267/

相关文章:

  • 大气层Atmosphere系统深度解析:解锁Switch潜能的终极技术指南
  • 小白必看!轻松搞懂ChatGPT背后的Transformer,附收藏版深度解析
  • 2026年论文党必备:降AI率软件测评与推荐大全
  • 2026年Q2香榧种植园评测:天然榧塑膳食、安徽香榧种植园、岳西香榧产业园、岳西香榧种植园、植物榧塑膳食、榧塑膳食产品选择指南 - 优质品牌商家
  • 担保被告律师哪个好?陈杰律师:担保责任减免优秀律师 - 外贸老黄
  • 面向创意生成 Agent 的 Harness 随机种子管理
  • 04-系统技术架构师必备——设计模式在系统架构中的应用
  • Python数据库设计模式:从ORM到数据层架构
  • Keil µVision库模块选择问题解决方案
  • 2026年管道预制件成品公司精选推荐,品质与服务双保障
  • 终极指南:如何一键检测微信单向好友,告别隐形删除困扰 [特殊字符]
  • 2026年5月济南装修采购,为何山东山高照明成为马桶供应商优选? - 2026年企业推荐榜
  • 2026技术复盘:告别“易碎”代码,实在Agent重塑企业自动化底座
  • 如何用UI-TARS智能助手解放你的双手?5个核心功能深度解析
  • 鸿蒙PC:鸿蒙electron跨端框架PC链接雷达实战:把本地收藏夹升级成可巡检的链接管理面板
  • 08-系统技术架构师必备——分布式系统理论与数据一致性
  • Python异步编程深度解析:从asyncio到实战应用
  • 收藏!小白程序员这样学大模型,从入门到精通全攻略!
  • 2026年厨下净热一体机厂家实力排行及地址盘点:中央净水机、全能冰泉机、厨下反渗透净水机、厨下净热一体机、大流量净水机选择指南 - 优质品牌商家
  • 2026年当前浙江省单位食堂承包深度选型:为何食润康餐饮成为全链条服务标杆? - 2026年企业推荐榜
  • ES 模块:JavaScript 模块化的标准方案
  • 小波分析多尺度数据融合算法应用【附算法】
  • Harness与Agent SDK的边界划分:最佳实践
  • 学 Simulink—— 双定子永磁同步电机(DS‑PMSM)的协同控制与转矩提升仿真(带 MATLAB 脚本(直接运行))
  • 2026年5月陕西控制电缆采购聚焦:西安华联电力电缆有限公司为何成为优选 - 2026年企业推荐榜
  • 回归模型.
  • 2026酒店民宿装修设计优质服务商推荐指南:厂房装修设计、商业空间装修设计、四川公装公司、四川公装装修公司、展厅装修设计选择指南 - 优质品牌商家
  • 5分钟搞定视频号批量下载:开源工具让效率提升20倍
  • 如何高效使用Obsidian Text Generator插件:实战进阶指南
  • 国曙GOSHINE正式亮相:一家人力资源服务机构的“长期主义”转向!