SIF协议----一线通协议
电动车,电动车控制器的仪表信息需要用到一线通协议。
SIF因为它的简单,低成本,适用一些需求不高的场景。
物理层
单线,主从单工模式。
波特率可以像UART一样预先约定好,也可以由主机发送的同步信号,让从机自适应解析。
协议层
实现方法
1.定时器
要开个定时器,它的频率倍数就能正好对应着一线通的高低电平时间。
比如以短500us、长1000us为例,我就用一个100us的中断定时器。
2.波形调制
自己拿代码变量慢慢调吧
举例:发送 2字节数据:11000010,11000010(二进制)
提示:1、数据位逻辑 1 符合 高电平时间 > 低电平时间 + 0.5ms;
2、数据位逻辑 0 符合 低电平时间 > 高电平时间 + 0.5ms;
3、一般使用 0.5ms 和 1ms 的比例
代码实现—纯定时器扫描方式
/******************************************************************************* *Copyright (c) GeekYang *@文件名 : main.c *@作 者 : GeekYang *@时 间 : 2021-06-12 10:00:00 *@摘 要 : 主程序文件 *@芯 片 : STC8G1K08-TSSOP-20 *@晶 振 : 33MHz/1 *@版本号 : 1.0 *@芯 片 : * ------------- * T2/ECI/SS/ADC2/P1.2 -丨01 20丨- P1.1/ADC1/TxD2/CCP0 * T2CLKO/MOSI/ADC3/P1.3 -丨02 19丨- P1.0/ADC0/RxD2/CCP1 * I2CSDA/MISO/ADC4/P1.4 -丨03 18丨- P3.7/INT3/TxD_2/CCP2_2/CCP2/CMP+ * I2CSCL/SCLK/ADC5/P1.5 -丨04 17丨- P3.6/ADC14/INT2/RxD_2/CCP1_2/CMP- * XTALO/MCLKO_2/RxD_3/ADC6/P1.6 -丨05 16丨- P3.5/ADC13/T1/T0CLKO/CCP0_2/SS_4 * XTALI/TxD_3/ADC7/P1.7 -丨06 15丨- P3.4/ADC12/T0/T1CLKO/ECI_2/CMPO/MOSI_4 * MCLKO/RST/P5.4 -丨07 14丨- P3.3/ADC11/INT1/MISO_4/I2CSDA_4 * Vcc/AVcc/ADC_VRef+ -丨08 13丨- P3.2/ADC10/INT0/SCLK_4/I2CSCL_4 * P5.5 -丨09 12丨- P3.1/ADC9/TxD * Gnd/AGnd -丨10 11丨- P3.0/ADC8/RxD/INT4 * ------------- *******************************************************************************/ /*================================= Demo说明 =================================== 本案例带波特率自适应 由于有些单片机的外设资源比较缺乏,没有外部中断,但一般定时器都是有的,所以案例都采用 定时器扫描的方式进行波形解析,读取数据,即 利用 定时器 + 一个GPIO口进行通讯数据读取 ==============================================================================*/ /* 包含的头文件 ---------------------------------------------------------------*/ #include "STC8G.H" /* 宏定义 ---------------------------------------------------------------------*/ #define LOW 0 //低电平 #define HIGH 1 //高电平 #define DATA_REV_PIN P10 //定义数据接收引脚(根据实际项目进行更改) #define SYNC_TIME_NUM 992 //992Tosc中的992 #define SHORT_TIME_NUM 32 //一个逻辑周期中短的时间:32Tosc中的32 #define LONG_TIME_NUM 64 //一个逻辑周期中长的时间:64Tosc中的64 #define LOGIC_CYCLE_NUM 96 //一个逻辑周期 SHORT_TIME_NUM + LONG_TIME_NUM #define HALF_LOGIC_CYCLE 48 //一个逻辑周期的1/2,即 LOGIC_CYCLE_NUM/2 #define REV_BIT_NUM 8 //接收的bit位个数,看是按字节接收还是按字接收,1字节=8bit,1字=2字节=16bit #define REV_DATA_NUM 12 //接收的数据个数 /* 类型定义 -------------------------------------------------------------------*/ typedef enum { INITIAL_STATE=0, //初始状态,等待接收同步信号 SYNC_L_STATE=1, //接收同步低电平信号状态 SYNC_H_STATE=2, //接收同步高电平信号状态 DATA_REV_STATE=3, //读取数据码电平状态 RESTART_REV_STATE=4 //接收过程出错重新接收状态 }REV_STATE_e; //接收数据状态枚举 /* 变量定义 -------------------------------------------------------------------*/ unsigned char receive_state=0; //接收数据状态 unsigned char receive_bit_num=0; //接收的bit位个数 unsigned char receive_data_num=0; //接收的数据个数 unsigned char receive_data_buf[REV_DATA_NUM]={0}; //接收数据缓存数组-如果一帧数据有多个数据打开注释 unsigned int H_L_Level_time_cnt=0; //高低电平时间计数 unsigned int Tosc = 3; //波形时基单元,一般带波特率自适应的,不会说明高低电平的时间,会用一个Tosc时基描述 //如上面的波形图,要求 32Tosc = 0.5ms = 500us //所以:一个Tosc = 500us/32 ≈ 15us,而定时器0单次定时时间为5us,所以实际一个Tosc = 15us/5us = 3 次 //可以理解为 5us 是人为设置的一个定时器单次定时时间,再这个定时时间的基础上又产生了一个实际时基Tosc,用在波形上,波形基于这个Tosc时基单元 //可以动态的调整高低电平的时间,只要Tosc改变,992Tosc和32Tosc以及64Tosc对应的时间也会随之改变;然后我们反过来思考,再不知道波特率的情况下, //去读取同步信号高电平的时间,将读到的时间计数H_L_Level_time_cnt * 15us是高电平的真实维持时间 = 32 * Tosc * 5us = SHORT_TIME_NUM * Tosc * 5us //Tosc = H_L_Level_time_cnt / SHORT_TIME_NUM //在本案例中,一帧数据 = 992Tosc+32Tosc+(64+32)*8*12Tosc = 10240*Tosc 次 //1秒钟 = 1000000us,单次定时 5us,则1s / 5us = 200000 次 //则1秒钟可以接收 200000 / (10240 * Tosc) = 200000/10240/Tosc = 19/Tosc 帧数据 bit start_H_L_Level_timming_flag=0; //开始高低电平计时标记 bit has_read_bit = 0; //1-已经读取一个bit位 bit read_success=0; //一帧数据是否读取成功,0-不成功,1-成功 /* 函数声明 -------------------------------------------------------------------*/ void GPIO_Init(void); //GPIO初始化函数 void Timer0_Init(void); //定时器0初始化函数 void Receive_Data_Baud_Adjust_Bit_Handle(void); //接收数据处理—带校准位,即波特率自适应 /* 函数定义 -------------------------------------------------------------------*/ /******************************************************************************* *函数名称 : main *函数功能 : 主函数入口 *输入参数 : void *输出返回 : void *******************************************************************************/ void main(void) { GPIO_Init(); //GPIO初始化,设置数据接收引脚P10为高阻输入,检测高低电平 Timer0_Init(); //定时器0初始化,定时周期为:5微秒@33.000MHz while(1) { Receive_Data_Baud_Adjust_Bit_Handle(); //如果主循环中处理的任务比较多也可以在定时中断服务函数中调用 if (read_success == 1) //如果成功读取一帧数据 { //一帧数据接收成功后先根据协议要求进行校验和,验证数据的正确性 //如果数据正确,根据接收的数据进行分析获取需要的内容 read_success = 0; //读取一帧数据清0 } } } /******************************************************************************* *函数名称 : GPIO_Init *函数功能 : 数据接收引脚初始化 *输入参数 : void *输出返回 : void *******************************************************************************/ void GPIO_Init(void) { P1M1 |= 0x01; //设置数据接收引脚P10为高阻输入模式 P1M0 &= 0xFE; P1PU &= 0xFE; //禁止P10端口内部的4.1K上拉电阻 P1NCS |= 0x01; //使能端口的施密特触发器 P1SR &= 0xFE; //电平转换速度快 P1DR |= 0x01; //控制端口驱动能力:0-增强驱动能力 1-一般驱动能力 P1IE |= 0x01; //使能数字信号输入 } /******************************************************************************* *函数名称 : Timer0_Init *函数功能 : 定时器0初始化 *输入参数 : void *输出返回 : void *******************************************************************************/ void Timer0_Init(void) { AUXR |= 0x80; //定时器时钟1T模式 TMOD &= 0xF0; //设置定时器模式:16位自动重载模式 TL0 = 0x5B; //设置定时初值低8位,5微秒@33.000MHz TH0 = 0xFF; //设置定时初值高8位 TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 } /******************************************************************************* *函数名称 : Timer0_isr *函数功能 : 定时器0中断处理函数 *输入参数 : void *输出返回 : void *******************************************************************************/ void Timer0_isr() interrupt 1 //500us定时器 { if (start_H_L_Level_timming_flag==1) { H_L_Level_time_cnt++; //高低电平维持时间计数变量 } // Receive_Data_Baud_Adjust_Bit_Handle(); //接收数据处理,波特率自适应 } /******************************************************************************* *函数名称 : Receive_Data_Baud_Adjust_Bit_Handle *函数功能 : 接收数据处理,波特率自适应 *输入参数 : void *输出返回 : void *******************************************************************************/ void Receive_Data_Baud_Adjust_Bit_Handle(void) { switch (receive_state) //检测当前接收数据状态 { case INITIAL_STATE: //初始状态,未接收到同步信息,进行同步判断 if (DATA_REV_PIN == LOW) //判断接收引脚的电平状态,当读到低电平时,开始计时 { receive_bit_num = REV_BIT_NUM; //重置bit位计数器 receive_data_num = 0; //重置接收数据个数 H_L_Level_time_cnt = 0; //高低电平计时变量清0 start_H_L_Level_timming_flag = 1; //开始高低电平计时 receive_state = SYNC_L_STATE; //进入读取同步低电平信号状态 } break; case SYNC_L_STATE: //在读取同步低电平信号期间 if (H_L_Level_time_cnt > SYNC_TIME_NUM*Tosc) //如果低电平时间>SYNC_TIME_NUM*Tosc=992*3*5us { //同步状态空闲时间大于15ms if (DATA_REV_PIN == HIGH) //判断接收引脚的电平状态,当读到高电平时 { H_L_Level_time_cnt = 0; //高低电平计时变量清0 receive_state = SYNC_H_STATE; //进入读取同步信号高电平状态 } } else { if (DATA_REV_PIN == HIGH) //同步信号低电平检测期间读到高电平重新计时 { receive_state = RESTART_REV_STATE; //进入重新接收状态 } } break; case SYNC_H_STATE: //在读取同步高电平信号期间 /* //代码写法一: if (H_L_Level_time_cnt >= SHORT_TIME_NUM*Tosc) //如果高电平时间 >= SHORT_TIME_NUM*Tosc=32 * 3 * 5us { if (DATA_REV_PIN == LOW) //>=同步信号高电平检测时间后读到低电平 { //进入这段逻辑有两种状态:1、高电平时间正好=32Tosc,2、高电平时间长,超过32Tosc Tosc = H_L_Level_time_cnt / SHORT_TIME_NUM; //调整 Tosc 的值 H_L_Level_time_cnt = 0; //高低电平计时变量清0 receive_state = DATA_REV_L_STATE; //进入读取数据码低电平状态 } } else { if (DATA_REV_PIN == LOW) //同步信号高电平检测期间读到低电平 { //高电平时间短,没有满32Tosc,自动调整检测周期 //H_L_Level_time_cnt * 5us 要求是 SHORT_TIME_NUM * Tosc * 5us,即 H_L_Level_time_cnt = SHORT_TIME_NUM * Tosc Tosc = H_L_Level_time_cnt / SHORT_TIME_NUM; //调整 Tosc 的值 H_L_Level_time_cnt = 0; //高低电平计时变量清0 receive_state = DATA_REV_L_STATE; //进入读取数据码低电平状态 } } */ //代码写法二: if (H_L_Level_time_cnt >= LOGIC_CYCLE_NUM*Tosc) //如果高电平时间超过了(32+64=96)个Tosc,则认为超时 { receive_state = RESTART_REV_STATE; //进入重新接收状态 } else { if (DATA_REV_PIN == LOW) //同步信号高电平检测期间读到低电平 { //在同步信号高电平检测期间读到低电平可能有如下状态: //1、高电平时间短,不满32Tosc //2、高电平时间正好=32Tosc //3、高电平时间长,超过32Tosc //不管何种状态,都要 调整 Tosc 的值达到波特率自适应 //H_L_Level_time_cnt * 5us 要求是 SHORT_TIME_NUM * Tosc * 5us, //即 H_L_Level_time_cnt = SHORT_TIME_NUM * Tosc Tosc = H_L_Level_time_cnt / SHORT_TIME_NUM; //调整 Tosc 的值 H_L_Level_time_cnt = 0; //高低电平计时变量清0 receive_state = DATA_REV_STATE; //进入读取数据码低电平状态 } } break; case DATA_REV_STATE: //在读取数据码电平期间 //逻辑“0”为 64Tosc低电平 + 32Tosc高电平 //逻辑“1”为 32Tosc低电平 + 64Tosc高电平 //如何判断当前为逻辑“0”还是逻辑“1”,关键在于寻找共同点 //方法一: //不管是逻辑“0”还是逻辑“1”,周期一样,都是32Tosc + 64Tosc = 96Tosc //可以取中间时间点进行判断,96Tosc / 2 = 48Tosc,当计数>=48Tosc时读取引脚电平 //如果还没有读取一个bit位,且时间计数已经>=48Tosc if ((has_read_bit==0) && (H_L_Level_time_cnt >= (HALF_LOGIC_CYCLE * Tosc))) { receive_data_buf[receive_data_num] |= DATA_REV_PIN; has_read_bit = 1; } //方法二: //不管是逻辑“0”还是逻辑“1”,高低电平维持时间都是以 32Tosc 为基数, //64Tosc = 2 * 32Tosc,所以一个逻辑周期 96Tosc = 64Tosc + 32Tosc = 3 * 32Tosc //所以可以取一个逻辑周期的中间时间端进行判断,即>32Tosc 且 <64Tosc 这段时间内判断 // if ((has_read_bit==0) && (H_L_Level_time_cnt > (SHORT_TIME_NUM * Tosc)) && (H_L_Level_time_cnt < (LONG_TIME_NUM * Tosc))) // { // receive_data_buf[receive_data_num] |= DATA_REV_PIN; // has_read_bit = 1; // } //如果已经读取一个bit位,且时间计数已经>=96Tosc,说明一个逻辑周期过去了 if ((has_read_bit==1) && (H_L_Level_time_cnt >= (LOGIC_CYCLE_NUM * Tosc))) { H_L_Level_time_cnt = 0; //高低电平计时变量清0 has_read_bit = 0; //清0,读取下一个bit位 receive_bit_num--; //接收的bit数-- if (receive_bit_num==0) //如果一个字节8个bit位接收完成 { receive_data_num++; //接收的数据个数++ receive_bit_num = REV_BIT_NUM; //重置接收bit位个数 if (receive_data_num == REV_DATA_NUM) //如果数据采集完毕 { read_success = 1; //一帧数据读取成功 start_H_L_Level_timming_flag = 0; //停止高低电平计时 H_L_Level_time_cnt = 0; //定时器计数值清0 receive_state = INITIAL_STATE; //接收状态清0 } } else //如果一个字节8个bit位还没有接收完成 { //将接收数据缓存左移一位,数据从高bit位开始接收 receive_data_buf[receive_data_num] = receive_data_buf[receive_data_num] << 1; } } break; case RESTART_REV_STATE: //重新接收数据状态 start_H_L_Level_timming_flag = 0; //停止高低电平计时 H_L_Level_time_cnt = 0; //定时器计数值清0 receive_state = INITIAL_STATE; //接收状态清0 break; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通