0、前言
目前很多便宜的单片机都没有标准的串行通讯口UART,甚至没有IIC、SPI等接口,MCU外围硬件接口不够。但有时又需要和其它设备或者器件进行简单的通讯,速度要求不是很高,又或者说受硬件限制,只能提供一根通讯线来通讯,此时可以尝试使用SIF协议进行通讯。
SIF协议因为它的简单,低成本,适用一些需要不高的场景。最近在调充电器,与电动车充电时需要和车子的BMS进行一线通讯。充电器DC端使用的三芯插头,一正极,一负极,一通信。电动车上的电池包作为主机,充电器作为从机,充电器一线通的那个脚要求带有上拉电阻。一线通上拉电阻值统一要求 5V上拉2.2K,3.3V上拉1K,充电器上拉电压给出,电池发送一线通报文。
1、硬件接线示意图
接线方式:
主从双方采用单线单工通讯方式,即只需要一根传输线路,电动车电池BMS为数据发送方,充电器CHG为数据接收方。
波特率:
主机和从机制定协议之前可以双方约定好,也可以主机随意,从机根据主机发送的同步信号,进行自适应解析。
2、通讯规则
2.1、数据帧组成
一次传输一帧数据,每帧数据由 同步信号 + 数据信号(8bit * 12个数据位)+ 结束信号 3个部分组成。 数据的电平遵守 TTL 规范。
1、同步信号为发送主报文的前导信号;
2、数据信号为需要发送的有效数据内容,按一定占空比进行发送;
3、结束信号代表一帧完整的数据发送结束的标志信号
2.2、同步信号
同步信号:>992Tosc 的低电平 + 32Tosc 的高电平
2.3、数据信号
采用高低电平占空比的方式进行数据报文发送。
备注:1、32Tosc 范围为 0.5ms,考虑到响应速度,不要超过1ms
2、空闲位时间>15ms(0.5ms*992/32=15ms)
3、数据波形举例
发送 2字节数据:11000010,11000010(二进制)
提示:1、数据位逻辑 1 符合 高电平时间 > 低电平时间 + 0.5ms;
2、数据位逻辑 0 符合 低电平时间 > 高电平时间 + 0.5ms;
3、一般使用 0.5ms 和 1ms 的比例
4、代码实现—纯定时器扫描方式
/*******************************************************************************
*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;
}
}
5、补充:代码优化
说明一:对于协议的理解
再看这个逻辑“0”和逻辑“1”的说明
DATA(0) 由 64Tosc 的低电平 + 32Tosc 的高电平组成
DATA(1) 由 32Tosc 的高电平 + 64Tosc 的低电平组成
【共同点一】都是低电平在前,高电平在后
【共同点二】周期都是 32Tosc + 64Tosc = 96Tosc
【共同点三】高低电平持续时间有一定的倍数关系,即 64Tosc = 2 * 32Tosc
所以说,一个逻辑电平的周期 96Tosc = 3 * 32Tosc,分成3段,我们可以将判断逻辑“0”还是逻辑“1”放在中间段进行判断,即>32Tosc 且 <64Tosc 这个区间,但是这个区间范围有点大,容易误判,导致解析的数据错误,所以优化的方法如下:
方法一:把一个逻辑周期 96Tosc 一分为二,即取中间点,48Tosc,但是判断方法需要注意,不要先判断时间再判断接收引脚的高低电平,这种以时间驱动的判断方式误差率比较高。应该采用,先判断高低电平,再判断时间的方式。由于一个逻辑电平都是以低电平开始的,一旦同步信号过后有低电平来了,说明一个逻辑电平的接收就开始了,开始接收后,一直判断接收脚上的电平是否变高,如果变高了,再进行时间的判断,如果此时定时器计数次数>48Tosc,那么就是DATA(0),如果<48Tosc,那么就是DATA(1)。【说的简单点就是,DATA(0)至少要>48Tosc才会接收到高电平,而DATA(1)则要 < 48Tosc 就要接收到高电平】
方法二:把 32Tosc 再按等比例进行划分,比如分成 4分,即 32Tosc = 4 * 8Tosc;那么判断 DATA(0) 和 DATA(1) 的中间段区间可以细分为 > 4 * 8Tosc 且 < 8 * 8Tosc;由于 4 和 8 都是偶数,取中间点,一分为二就是 2 和 4,相当于在>32Tosc 且 <64Tosc 这个区间又取了中间区间进行判断,确保此时肯定是可以判断 DATA(0) 和 DATA(1) 的,如下图
说明二:上述案例代码存在的问题
其它函数没问题,主要是一线通解析函数 Receive_Data_Baud_Adjust_Bit_Handle()中存在问题:
问题一:接收的数据放在数组中还未被判断就可能被下一帧数据覆盖
case INITIAL_STATE 分支判断下面,只判断了 if(DATA_REV_PIN==LOW) 一旦有同步低电平信号,就会开始接收数据,如果上一帧数据还没处理,可能存在覆盖现象,所以应该改成 if(read_success==0 && DATA_REV_PIN==LOW)
问题二:在解析DATA(0)还是DATA(1)时,先判断计数时间,后判断的高低电平,这种方式要求双方时间非常精确,一旦有时间偏差就会导致接收到的是错误数据
case DATA_REV_STATE分支判断下面,先判断的计数时间是否到了,然后去判断当前接收引脚上的高低电平,此种方式存在弊端,应该先看高电平是否来了,有高电平后,再看计数时间来判断是DATA(0)还是DATA(1)
优化后的代码:
/*******************************************************************************
*Copyright (c) GeekYang
*@文件名 : main.c
*@作 者 : GeekYang
*@时 间 : 2021-07-30 16: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说明 ===================================
本案例分两种状态的波形图进行编码说明:
1、不带校准位,即不带波特率自适应,波形确定的,此 Demo 设定 t=1ms
2、带校准位
由于有些单片机的外设资源比较缺乏,没有外部中断,但一般定时器都是有的,所以案例都采用
定时器扫描的方式进行波形解析,读取数据,即 利用 定时器 + 一个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 HALF_LOGIC_CYCLE 48 //一个逻辑周期的1/2,即 (SHORT_TIME_NUM + LONG_TIME_NUM)/2
//方法二用
//#define Mid_MIN 40
//#define Mid_MAX 56
//#define WAIT_LOGIC 80
#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 //5us定时器
{
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: //初始状态,未接收到同步信息,进行同步判断
//判断上一帧数据是否已经处理完成,read_success==0,可以接收新一帧数据
//判断接收引脚的电平状态,当读到低电平时,开始接收同步信号低电平
if (read_success==0 && DATA_REV_PIN == LOW)
{
receive_bit_num = REV_BIT_NUM; //重置bit位计数器
receive_data_num = 0; //接收数据个数清0
//receive_data_num = REV_DATA_NUM; //如果从高字节开始接收
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 (DATA_REV_PIN == HIGH) //判断接收引脚的电平状态,当读到高电平时
{
if (H_L_Level_time_cnt > SYNC_TIME_NUM*Tosc) //如果低电平时间>SYNC_TIME_NUM*Tosc=992*3*5us
{ //同步状态空闲时间大于15ms
H_L_Level_time_cnt = 0; //高低电平计时变量清0
receive_state = SYNC_H_STATE; //进入读取同步信号高电平状态
}
else //接收同步低电平错误
{
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
}
break;
case SYNC_H_STATE: //在读取同步高电平信号期间
if (DATA_REV_PIN == LOW) //同步信号高电平检测期间读到低电平
{
//如果高电平时间 >= LONG_TIME_NUM * Tosc=64 * 3 * 5us,接收错误
if (H_L_Level_time_cnt >= LONG_TIME_NUM*Tosc)
{
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
else
{
//在同步信号高电平检测期间读到低电平可能有如下状态:
//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
has_read_bit = 0; //标记为还没有读取一个bit位
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时,则为DATA(0)
//如果定时器计数 < 48Tosc时,则为DATA(1)
//如果还没有读取一个bit位
if (has_read_bit==0)
{
if (DATA_REV_PIN == HIGH) //等待接收IO口变高电平
{
if (H_L_Level_time_cnt<(HALF_LOGIC_CYCLE * Tosc)) //如果时间计数已经<48Tosc
{
receive_data_buf[receive_data_num] |= 0x01; //逻辑“1”
}
else
{
receive_data_buf[receive_data_num] &= 0xFE; //逻辑“0”
}
has_read_bit = 1; //标记为已读取一个bit位
}
}
else //如果已经读取一个bit位
{
if(DATA_REV_PIN == LOW) //等待IO口变低电平,如果低电平来了,一个逻辑周期过去了
{
has_read_bit = 0; //清0,读取下一个bit位
H_L_Level_time_cnt = 0; //高低电平计时变量清0
receive_bit_num--; //接收的bit数--
if (receive_bit_num==0) //如果一个字节8个bit位接收完成
{
receive_bit_num = REV_BIT_NUM; //重置接收bit位个数
receive_data_num++; //接收的数据个数++
//receive_data_num--; //如果从高字节开始接收
// if (receive_data_num == 0) //如果从高字节开始接收
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;
}
}
}
//方法二:
//不管是逻辑“0”还是逻辑“1”,高低电平维持时间都是以 32Tosc 为基数,
//64Tosc = 2 * 32Tosc,所以一个逻辑周期 96Tosc = 64Tosc + 32Tosc = 3 * 32Tosc
//所以可以取一个逻辑周期的中间时间段进行判断,即>32Tosc 且 <64Tosc 这段时间内判断
//但是32Tosc 和 64Tosc 正好是在电平跳变的临界点,容易误判,所以在>32Tosc 且 <64Tosc 这段时间内
//在取个中间区间段,可以把32Tosc 等分成4分,则 32Tosc = 4 * 8Tosc,>32Tosc 且 <64Tosc 这段时间
//区间差了32Tosc = 4 * 8Tosc,所以>32Tosc 且 <64Tosc 这段时间的再取中间时间段为 5*8Tosc~7*8Tosc
//即40Tosc ~ 56Tosc
//如果还没有读取一个bit位
// if (has_read_bit==0)
// {
// //且计数时间已经到达中心区间
// if(H_L_Level_time_cnt>=(Mid_MIN*Tosc) && H_L_Level_time_cnt<=(Mid_MAX*Tosc))
// {
// if(DATA_REV_PIN == HIGH) //读取此时输入引脚电平,如果是高电平
// {
// receive_data_buf[receive_data_num] |= 0x01; //逻辑“1”
// }
// else
// {
// receive_data_buf[receive_data_num] &= 0xFE; //逻辑“0”
// }
// has_read_bit = 1; //标记为已读取一个bit位
// }
// }
// else //如果已经读取一个bit位
// {
// //且计数时间已经>=64Tosc+16Tosc,取一个逻辑周期高电平的中间值
// if(H_L_Level_time_cnt>=(WAIT_LOGIC*Tosc))
// {
// if(DATA_REV_PIN == LOW) //读取此时输入引脚电平,如果是低电平,则一个逻辑周期过去了
// {
// has_read_bit = 0; //清0,读取下一个bit位
// H_L_Level_time_cnt = 0; //高低电平计时变量清0
// receive_bit_num--; //接收的bit数--
//
// if (receive_bit_num==0) //如果一个字节8个bit位接收完成
// {
// receive_bit_num = REV_BIT_NUM; //重置接收bit位个数
// receive_data_num++; //接收的数据个数++
// //receive_data_num--; //如果从高字节开始接收
//
// // if (receive_data_num == 0) //如果从高字节开始接收
// 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;
}
}