现在很多厂家都支持一线通的通信协议,控制器呀,仪表呀,BMS呀,通通的提供了一线通的接口,这几乎成了两轮车行业的一个标准了。
第一次看到一线协议蛮惊讶的,因为这居然是一个国际标准,比如:
似乎每一家的协议文档的第一部分,都声称自己采用的是国际标准SIF通信协议。
这么看来,我们不得不了解一下,这个大名鼎鼎的国际标准是个什么标准,哪几个组织联合发布的。
我帮大家搜过了,Google搜索记录一共3页,除了像上面那样介绍自家协议的引用外,关于SIF国际标准的关键字都是医学,或者视频行业的词条。看来Google也是孤陋寡闻了,GPT也是干瞪眼。
我翻到了自行车电动车协会发布的团标,里面引用了一线通,并没有说明一线通是什么国际标准,只是简单介绍了其通信架构。
从上面的一线通架构中,可以看出,这个一线通主要是一发多收的半双工机制。跟半个Uart一样。我之前为了方便,也是采用一根线做Uart通信,只不过我的MCU支持Tx和Rx调换一下,因此可以做到收发一体。
一线通的具体协议
相信很多朋友第一次接触单线通信都是从DS18B20开始的,这是一种单总线结构,它支持主机发送查询命令,然后接收各个子设备的信息。
那么两轮车的这个一线通呢?其实就是砍掉一半,主机只发送,不接受,就像串口uart的Tx一样,自己按照固定的波特率发送,线上的所有监听者按照协定的波特率接收。
区别在于,uart的电平高代表1,电平低代表0,这里指的TTL电平。而一线通该表了这个策略,它使用了固定周期,不同占空比的PWM波形来表示0和1。
这样抗干扰的能力就强了很多,看上去也很眼熟是不是? 这跟我们常常使用的彩色LED,WS2812的通信非常的相似,下图是WS2812的时序波形图。
其实它们都是采用的单极性归零码,归零码是一种数字信号编码方式,在每个时钟周期内,信号电平都会返回到零电平。
与WS2812所不同的是,一线通采用的同步机制并不是一长段的低电平,而是一长段的低电平后面跟上一个高电平,这样有一个好处,就是可以让通信更健壮,兼容性更佳。
有了同步帧和数据帧的定义,然后再把每一个数据周期定义下来,我们就可以接收到整个完整的数据包了,这里,一线通的数据周期定义为12*8bit,也就是在同步帧后,持续的接收96个bit。
一线通解析方法
网络上看到很多解析一线通的方法,所示配置一个定时器,不断地读取GPIO的电平进行采样,然后根据个数来计算电平的持续时间,通用性还是可以的,只不过进入中断的次数比较多,不过抗干扰会比较好。
我使用的单片机都是有外部中断的,所以我决定采用中断配合定时器计时的方法来接收一线通数据。只需要定义两个边沿都触发,然后再触发中断里面根据触发源,或者再次读取一下IO电平就可以判断出上一次计时器中的计时是高电平还是低电平。
逻辑很简单,但是写起来还得费点功夫,我肯定是想一两分钟搞定的,毕竟有AI加持了,谁还自己敲代码呢。我把波形图和GPIO中断的函数喂给了AI。告诉他,将收到的96个数据存到一个12字节的数组里面。
还记得刚刚说的同步帧吗? 同步帧后面有一个高电平的。我跟AI说,这个同步帧的高电平其实就是一个单位时间,后面跟来的数据帧都是按同步帧中高电平来计算的,这样做,我们就可以一套代码适用不同厂家的一线通了,AI绝对的聪明,同时他也肯定了我的理解。
瞅瞅,不单看明白了我的意图,还给我增加了允许误差,提高程序的健壮性,代码也不含糊
voidone_wire_int_process(void){int curr_level = HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_3);HAL_TIM_Base_Stop(&g_hTIMx);uint16_t pulse_time = __HAL_TIM_GET_COUNTER(&g_hTIMx);// 重新计时,准备捕获下一个电平__HAL_TIM_SET_COUNTER(&g_hTIMx, 0);HAL_TIM_Base_Start(&g_hTIMx);if (curr_level == 0){last_high_time = pulse_time;}else{last_low_time = pulse_time;}// 采集到一对高低电平后,直接解析if (last_high_time){if (state == WAIT_SYNC){if ((last_low_time > LOW_SYNC_MIN && last_low_time < LOW_SYNC_MAX) &&(last_high_time > HIGH_SYNC_MIN && last_high_time < HIGH_SYNC_MAX)){t_unit = last_high_time;state = RECEIVE_DATA;bit_idx = 0;for (int i = 0; i < 12; i++)data_buf[i] = 0;}}elseif (state == RECEIVE_DATA){int t1 = t_unit;int t2 = t_unit * 2;int tol1 = TOLERANCE_INT(t1);int tol2 = TOLERANCE_INT(t2);// DATA(0): 低2T高1Tif (abs(last_low_time - t2) < tol2 && abs(last_high_time - t1) < tol1){data_buf[bit_idx / 8] <<= 1;bit_idx++;}// DATA(1): 低1T高2Telseif (abs(last_low_time - t1) < tol1 && abs(last_high_time - t2) < tol2){data_buf[bit_idx / 8] <<= 1;data_buf[bit_idx / 8] |= 1;bit_idx++;}else{state = WAIT_SYNC;}if (bit_idx >= DATA_BITS_TOTAL){state = WAIT_SYNC;// TODO 你可以在这里从数组获取数据}}// 处理完一对高低电平后,必须立即清零last_low_time = 0;last_high_time = 0;}}
上面是中断里面的处理函数,很多宏定义和变量初始化的部分,AI会帮我添加到头文件,我这里就不拷贝了。
Cursor是真的棒,大家一定要多多体验!
8975