自定义单线通信协议解析
一、单线通讯协议定义
/***************************************************************************************
单线bit定义如下://-------+ +---+ +--------+ +----------------------// | | | | | |// +-------------+ +--------+ +---+// 2ms 0.4ms 0.8ms//空闲 起始位同步低电平 1 0 结束单线协议数据如下:命令字 数据0 数据1 crc8校验 命令字解析0x5A 气体值 温度值 校验 校零值 0xA5 气体值 温度值 校验 校气值规则:大端模式,低位在前***************************************************************************************/
二、解析单线通讯协议原理
利用IO中断的双边延检测和定时器的计数值来解析协议
2.1. 硬件设计
单线连接: 设备与主机之间通过单根信号线进行通信,信号线连接到微控制器的某个GPIO引脚上,该引脚需要支持输入和输出功能,以便在发送和接收数据时切换。
上拉电阻: 在单线信号线上连接一个上拉电阻,确保在空闲状态下信号线保持高电平。这是因为单线通信协议中,空闲状态为高电平,而数据传输时通过拉低信号线来表示数据位。
2.2. 软件设计
初始化
GPIO配置: 将单线信号线对应的GPIO引脚配置为输入模式,用于接收数据;在发送数据时,切换为输出模式。
中断配置: 配置GPIO中断,使能双边沿检测(上升沿和下降沿),以便在信号线状态变化时触发中断。
定时器配置:配置定时器用于测量信号线的电平持续时间,以便区分起始位、数据位和结束位。
三、相关代码
3.1 中断接收相关数据
//中断接收单线协议4字节数据
void onewire_isr_rec_4bytes(void)
{ static uint16_t last = 0;static bool start_ok = false; // 起始位是否有效uint16_t now = TIM8->CNT; // 这里定时器为每1us加1,也可以采用其它定时器方式uint16_t width;LPTIM2_Data.onewire_clr_cnt = 0; // 每次接收数据后if(now>last){width = (now - last) & 0xFFFF;} else{width = (0xFFFF - last) + now + 1;}last = now;system_info.flags.onewire_flag = 1; //防止休眠导致计数器错误,置1后每3s清理一次标志/* 首位宽度检查:1600 ±200 */if (!start_ok) {if (width >= 1400 && width <= 1800) { // 误差 ±200printf("首位\r\n");start_ok = true;edge_cnt = 0; // 重新计数edge_buf[edge_cnt++] = width; // 记录起始位}return; // 未检测到起始位直接退出}/* 起始位已有效,继续记录后续脉宽 */if (edge_cnt < 70) {edge_buf[edge_cnt++] = width;} if (edge_cnt >= 65) { // 等待接收4个字节数据结束 start_ok = false;
// printf("中断%d\r\n",edge_cnt);for(int i = 0;i<= 70;i++){
// printf("第%d个数据%d\r\n",i,edge_buf[i]);} }
}
3.2 解析单线接收数据值
//解析单线接收数据值
bool onewire_recv_4bytes(void)
{uint8_t bit,idx;uint16_t high ;// 高段宽度uint16_t low ;// 低段宽度bool bit_val ; uint8_t data[4] = {0}; //接收数据 /* 等待 65 段:起始位 + 32 bit × 2 段/bit */if (edge_cnt <= 64) {return false;}edge_cnt = 0;for(int i = 0;i<= 66;i++){
// printf("结果-第%d个数据%d\r\n",i,edge_buf[i]);}/* 第 0 段:起始位 2 ms 低电平(根据实测值) */if (!onewire_range(edge_buf[0], 1580))return false;/* 每 2 段 = 1 bit,共 32 bit */for (bit = 0; bit < 32; ++bit){idx = 1 + bit * 2;high = edge_buf[idx]; // 高段宽度low = edge_buf[idx + 1]; // 低段宽度if(abs(high - low)<200) // 两者数据相等{printf("错误1:%d\r\n",idx);if(decode_bit_with_ec(&high,&low) == 255){printf("错误两段%d\r\n",idx);return false;} }/* 宽度合法性检查(840 / 420)(根据实测值) */if (!((onewire_range(high, 840) && onewire_range(low, 420)) ||(onewire_range(high, 420) && onewire_range(low, 840)))){printf("错误%d\r\n",idx);if(decode_bit_with_ec(&high,&low) == 255){printf("错误两段%d\r\n",idx);return false;}}/* 判断 bit 值:高 > 低 → 0,高 < 低 → 1 */bit_val = (high > low) ? 0 : 1;/* LSB first,写入对应位 */data[bit / 8] |= (bit_val << (bit % 8));}/* 可选:打印调试 */printf("收到帧:%02X %02X %02X %02X\r\n", data[0], data[1], data[2], data[3]);/* 校验期望帧 */if(1 == CalculateCRC_8(data,sizeof(data))) //校验成功{memcpy((uint8_t*)onewire_DATA, data,sizeof(onewire_DATA));printf("999\r\n");edge_cnt = 0; //清空数据return true;}memset((void*)edge_buf, 0, sizeof(edge_buf));printf("888\r\n");return false;
}
3.3 接收数据纠错机制
/* 返回 0/1,或 255 表示无法纠错 */
static uint8_t decode_bit_with_ec(uint16_t *high, uint16_t *low)
{#define NOM_420 420u#define NOM_840 840u/* ±15 % 容错带 */#define GOOD_420_MIN (NOM_420*85/100) // 357#define GOOD_420_MAX (NOM_420*115/100) // 483#define GOOD_840_MIN (NOM_840*85/100) // 714#define GOOD_840_MAX (NOM_840*115/100) // 966 /* 检查输入指针是否有效 */if (!high || !low) {return 255; // 如果指针无效,直接返回无法纠错}bool h_ok420 = (((*high) >= GOOD_420_MIN) && ((*high) <= GOOD_420_MAX));bool h_ok840 = (((*high) >= GOOD_840_MIN) && ((*high) <= GOOD_840_MAX));bool l_ok420 = (((*low ) >= GOOD_420_MIN) && ((*low ) <= GOOD_420_MAX));bool l_ok840 = (((*low ) >= GOOD_840_MIN) && ((*low ) <= GOOD_840_MAX));/* 纠错组合 5:两者都为低 */if (h_ok420 && l_ok420 ){printf("1\r\n");printf("数据差:%x\r\n",abs(*high - 420));printf("数据差:%x\r\n",abs(*low - 420));if(abs(*high - 420) > abs(*low - 420)){*high = 840;*low = 420;printf("2\r\n");return 0;}printf("3\r\n");*high = 420;*low = 840;return 0;} /* 纠错组合 1:高 840 好,低 420 坏 → 0 */if (h_ok840 && !l_ok420) {*high = 840;*low = 420;return 0;}/* 纠错组合 2:高 420 好,低 840 坏 → 1 */if (h_ok420 && !l_ok840) {*high = 420;*low = 840;return 1;}/* 纠错组合 3:低 840 好,高 420 坏 → 1 */if (!h_ok420 && l_ok840) {*high = 420;*low = 840;return 1;}/* 纠错组合 4:低 420 好,高 840 坏 → 0 */if (!h_ok840 && l_ok420){*high = 840;*low = 420;return 0;}/* 两段都坏 → 无法纠错 */return 255;
}
四、经验
4.1 低功耗引起的问题
低功耗模式可能导致定时器关闭或进入低频运行状态,从而使得计时不准,影响单线协议数据的准确解析。针对此问题,可以采用以下两种解决方案:
方案一:临时关闭低功耗模式
在单线协议数据传输期间,临时关闭低功耗模式,确保定时器能够正常工作,从而保证计时的准确性。数据传输完成后,再重新进入低功耗模式以节省能耗。这种方法的优点是计时精度高,能够准确解析数据;缺点是在数据传输期间会增加能耗。
方案二:采用低功耗定时器
使用低功耗定时器来解决此问题。低功耗定时器可以在低功耗模式下继续工作,从而避免因定时器关闭导致的计时不准问题。然而,需要注意的是,低功耗定时器的计时精度可能相对较低,可能会对数据解析的准确性产生一定影响。在实际应用中,可以根据具体的计时精度要求和能耗需求来选择合适的低功耗定时器,并在必要时对计时结果进行适当的校准或补偿。
4.2 干扰引起的问题
单线信号线容易受到电磁干扰,导致数据异常,如数据位丢失、错误或校验失败等。为了提高系统的可靠性和抗干扰能力,可以根据具体情况增加纠错机制,常见的纠错机制包括:
奇偶校验
在发送数据时,根据数据位的奇偶性添加一个校验位,接收端在接收到数据后,通过检查校验位来判断数据是否出错。这种方法简单易实现,但只能检测到单个错误,对于多个错误无法准确检测。
CRC校验
采用循环冗余校验(CRC)算法对数据进行校验。CRC校验具有较高的错误检测能力,能够检测出多种类型的错误,包括单个错误、多个错误以及突发错误等。在发送数据时,根据数据内容计算出一个CRC校验码,并将其附加在数据帧的末尾;接收端在接收到数据后,根据接收到的数据重新计算CRC校验码,并与接收到的CRC校验码进行比较,如果两者一致,则认为数据正确,否则认为数据出错并进行相应的处理,如请求重传等。
重复传输
对于重要的数据,可以采用重复传输的方式,即发送端将同一数据多次发送,接收端根据多次接收到的数据进行比对和判断,从而提高数据的可靠性。这种方法虽然会增加数据传输量和传输时间,但在干扰较强的情况下,能够有效降低数据出错的概率。
信号滤波
在硬件层面,可以对单线信号进行滤波处理,如采用低通滤波器、带通滤波器等,滤除信号中的高频干扰成分,从而降低干扰对数据传输的影响。此外,还可以采用差分信号传输方式,通过差分信号的特性来提高信号的抗干扰能力。
通过以上纠错机制的引入,可以有效提高单线协议通信的可靠性和稳定性,降低干扰对数据传输的影响。在实际应用中,可以根据具体的干扰情况和系统要求,选择一种或多种纠错机制进行组合使用,以达到最佳的抗干扰效果。