文章目录[隐藏]
0、前言
目前很多便宜的单片机都没有标准的串行通讯口UART,甚至没有IIC、SPI等接口,MCU外围硬件接口不够。但有时又需要和其它设备或者器件进行简单的通讯,速度要求不是很高,又或者说受硬件限制,只能提供一根通讯线来通讯,此时可以尝试使用SIF协议进行通讯。
SIF协议因为它的简单,低成本,适用一些需要不高的场景。最近在调充电器,与电动车充电时需要和车子的BMS进行一线通讯。充电器DC端使用的三芯插头,一正极,一负极,一通信。电动车上的电池包作为主机,充电器作为从机,充电器一线通的那个脚要求带有上拉电阻。一线通上拉电阻值统一要求 5V上拉2.2K,3.3V上拉1K,充电器上拉电压给出,电池发送一线通报文。
1、硬件接线示意图
接线方式:
主从双方采用单线单工通讯方式,即只需要一根传输线路,电动车电池BMS为数据发送方,充电器CHG为数据接收方。
波特率:
主机和从机制定协议之前可以双方约定好,也可以主机随意,从机根据主机发送的同步信号,进行自适应解析。
2、通讯规则
2.1、数据帧组成
一次传输一帧数据,每帧数据由 同步信号 + 主报文 + 停止信号 3个部分组成。
1、同步信号为发送主报文的前导信号;
2、主报文为需要发送的有效数据内容,按一定占空比进行发送;
3、结束信号代表一帧完整的数据发送结束的标志信号
2.2、同步信号
同步信号:T1ms的低电平+T2ms的高电平
其中:T1 ≥ 10ms
T2 = 1ms±100us
2.3、主报文
采用高低电平占空比的方式进行数据报文发送。
一对低电平和高电平组成一个bit数据位。在高电平的下降沿计算高电平的占空比,根据占空 比判断本次电平是逻辑"1"或逻辑"0"。
占空比 η = T2 / T ( T2 为高电平时间,T=T1+T2 )。
2.3.1、逻辑“1”
如上图所示,T1和T2组成一个数据位,该数据位占空比η为75%,表示数据位为逻辑"1"。
注意:η = T2 / T =(75 ± 5)%
其中:T1 = 0.5ms ± 50μs
T2 = 1.5ms ± 150μs
T = T1 + T2 = 2ms ± 200μs
2.3.2、逻辑“0”
如上图所示,T1和T2组成一个数据位,该数据位占空比η为25%,表示数据位为逻辑"0"。
注意:η = T2 / T =(25 ± 5)%
其中:T1 = 1.5ms ± 150μs
T2 = 0.5ms ± 50μs
T = T1 + T2 = 2ms ± 200μs
2.4、结束信号
结束信号:T1ms低电平 + Nms高电平
其中:T1 = 5ms
2.5、通信间隔
1、连续两帧数据的发送时间间隔位 50ms
2、发送数据前,电池会检测总线电平,如果检测到总线电平位低电平,则认为总线未连接充
电器,或者充电器未上电,此时电池包将停止数据发送;如果低电平持续时间达到60秒,则电
池包进入低功耗模式。
3、电池包在低功耗模式下,可以被总线高电平唤醒。唤醒后,如果电池包持续检测到总线电平
为高电平,且高电平持续时间达到 1s ,则开始发送数据。电池包在正常发送下一帧报文之前,
需要检测总线电平为高电平才能启动发送。
3、主报文格式
3.1、一线通报文格式
一线通报文由报文ID、协议版本、数据内容、校验字节四部分组成。
报文ID: 包括初始报文、周期报文
协议版本:包括次通信协议版本、主通信协议版本
校验字节:为报文ID、协议版本及数据内容(B0~B47)的和校验
备注: 数据内容中未用到的字节,填充0xFF
4、应用报文格式(主报文格式中的数据内容)
4.1、单字节传输方式
应用报文每字节由 8 位二进制bit组成,b0 是字节的最低有效位,b7 是字节的最高有效位。
收发数据时,先传最低有效位 b0,再传次低有效位 b1,以此类推,最后传最高有效位 b7。
4.2、多字节传输方式
收发多字节信号时,默认采用 Intel_LSB 格式进行数据传输,如上表信号distance所示,先传最
低字节 B43,再传次低字节 B44,以此类推,最后传最高字节 B46。
5、代码实现—纯定时器扫描方式+数组接收
/*******************************************************************************
*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 DATA_REV_PIN P10 //定义数据接收引脚(根据实际项目进行更改)
#define LOW 0 //低电平
#define HIGH 1 //高电平
#define SYNC_L_TIME_NUM 200 //同步信号低电平时间:10ms = 10000us / 50us = 200
#define SYNC_H_TIME_NUM_MIN 18 //同步信号高电平最小时间:1ms-100us = 900us / 50us = 18
#define SYNC_H_TIME_NUM_MAX 22 //同步信号高电平最大时间:1ms+100us = 1100us / 50us = 22
#define SHORT_TIME_NUM_MIN 9 //一个逻辑周期中短的时间最小值:0.5ms-50us = 450us / 50us = 9
#define SHORT_TIME_NUM_MAX 11 //一个逻辑周期中短的时间最大值:0.5ms+50us = 550us / 50us = 11
#define LONG_TIME_NUM_MIN 27 //一个逻辑周期中长的时间最小值:1.5ms-150us = 1350us / 50us = 27
#define LONG_TIME_NUM_MAX 33 //一个逻辑周期中长的时间最大值:1.5ms+150us = 1650us / 50us = 33
#define LOGIC_CYCLE_NUM_MIN 36 //一个逻辑周期最小时间:2ms-200us = 1800us / 50us = 36
#define LOGIC_CYCLE_NUM_MAX 44 //一个逻辑周期最大时间:2ms+200us = 2200us / 50us = 44
#define HALF_LOGIC_CYCLE_MIN 18 //一个逻辑周期的1/2最小时间:1ms-100us = 900us / 50us = 18
#define HALF_LOGIC_CYCLE_MAX 22 //一个逻辑周期的1/2最大时间:1ms+100us = 1100us / 50us = 22
#define END_SIGNAL_TIME_NUM 100 //结束信号电平时间:5ms低电平 + Nms高电平,实际检测5ms低电平就行,一帧数据发送完成后检测5ms低电平就代表完成了,不发数据的时候上拉电阻拉高了
#define REV_BIT_NUM 8 //接收的bit位个数,看是按字节接收还是按字接收,1字节=8bit,1字=2字节=16bit
#define REV_DATA_NUM 51 //接收的数据个数
/* 类型定义 -------------------------------------------------------------------*/
typedef enum
{
INITIAL_STATE=0, //初始状态,等待接收同步信号
SYNC_L_STATE=1, //接收同步低电平信号状态
SYNC_H_STATE=2, //接收同步高电平信号状态
DATA_REV_STATE=3, //读取数据码电平状态
END_SIGNAL_STATE=4, //接收结束电平信号状态
RESTART_REV_STATE=5 //接收过程出错重新接收状态
}REV_STATE_e; //接收数据状态枚举
/* 变量定义 -------------------------------------------------------------------*/
unsigned char receive_state=0; //接收数据状态
unsigned char receive_bit_num=0; //接收的bit位个数
unsigned char receive_data_num=0; //接收的数据个数
//接收数据缓存数组-用一个数组来缓存数据,51个数据字节
unsigned char receive_data_buf[REV_DATA_NUM]={0};
unsigned int H_L_Level_time_cnt=0; //高低电平时间计数
bit start_H_L_Level_timming_flag=0; //开始高低电平计时标记
bit has_read_bit = 0; //1-已经读取一个bit位
bit check_OK = 0; //1-校验和正确,0-校验和失败
bit read_success=0; //一帧数据是否读取成功,0-不成功,1-成功
/* 函数声明 -------------------------------------------------------------------*/
void GPIO_Init(void); //GPIO初始化函数
void Timer0_Init(void); //定时器0初始化函数
void Receive_Data_Handle(void); //接收数据处理
void Check_Sum_Handle(void); //校验和处理
/* 函数定义 -------------------------------------------------------------------*/
/*******************************************************************************
*函数名称 : main
*函数功能 : 主函数入口
*输入参数 : void
*输出返回 : void
*******************************************************************************/
void main(void)
{
GPIO_Init(); //GPIO初始化,设置数据接收引脚P10为高阻输入,检测高低电平
Timer0_Init(); //定时器0初始化,定时周期为:5微秒@33.000MHz
while(1)
{
if (read_success == 1) //如果成功读取一帧数据
{
//一帧数据接收成功后先根据协议要求进行校验和,验证数据的正确性
Check_Sum_Handle();
//如果数据正确,根据接收的数据进行分析获取需要的内容
if (check_OK)
{
/* code */
}
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初始化,单次定时时间根据协议的最小公差±50us确定
*输入参数 : void
*输出返回 : void
*******************************************************************************/
void Timer0_Init(void)
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式:16位自动重载模式
TL0 = 0x8E; //设置定时初值低8位,50微秒@33.000MHz
TH0 = 0xF9; //设置定时初值高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_Handle(); //接收数据处理,波特率自适应
}
/*******************************************************************************
*函数名称 : Receive_Data_Handle
*函数功能 : 接收数据处理
*输入参数 : void
*输出返回 : void
*******************************************************************************/
void Receive_Data_Handle(void)
{
switch (receive_state) //检测当前接收数据状态
{
case INITIAL_STATE: //初始状态,未接收到同步信息,进行同步判断
if (DATA_REV_PIN == LOW) //判断接收引脚的电平状态,当读到低电平时,开始计时
{
receive_bit_num = 0; //重置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 (DATA_REV_PIN == HIGH) //同步信号低电平检测期间读到高电平
{
if (H_L_Level_time_cnt >= SYNC_L_TIME_NUM)//如果同步信号低电平时间>=SYNC_L_TIME_NUM
{ //同步信号低电平时间要>=10ms
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) //同步信号高电平检测期间读到低电平
{
//判断同步信号高电平时间是否在1ms±100us之间
if (H_L_Level_time_cnt >= SYNC_H_TIME_NUM_MIN && H_L_Level_time_cnt <= SYNC_H_TIME_NUM_MAX)
{
H_L_Level_time_cnt = 0; //高低电平计时变量清0
receive_state = DATA_REV_STATE; //进入读取数据状态
}
else
{
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
}
else //如果在同步信号高电平检测期间,时间超过2ms±200us,认为超时
{
//判断时间是否超时 2ms±200us
if (H_L_Level_time_cnt >= LOGIC_CYCLE_NUM_MAX)
{
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
}
break;
case DATA_REV_STATE: //在读取数据码电平期间
//逻辑“0”为 1.5ms±150us低电平 + 0.5ms±50us高电平
//逻辑“1”为 0.5ms±50us低电平 + 1.5ms±150us高电平
//如何判断当前为逻辑“0”还是逻辑“1”,关键在于寻找共同点
//方法一:
//不管是逻辑“0”还是逻辑“1”,周期一样,都是2ms ± 200us
//可以取中间时间点进行判断,(2ms ± 200us) / 2 = 1ms ± 100us
//如果还没有读取一个bit位,且时间计数已经>=900us 且 <=1100us
if ((has_read_bit==0) && (H_L_Level_time_cnt >= HALF_LOGIC_CYCLE_MIN && H_L_Level_time_cnt <= HALF_LOGIC_CYCLE_MAX))
{
receive_data_buf[receive_data_num] |= DATA_REV_PIN;
has_read_bit = 1;
}
//方法二:
//不管是逻辑“0”还是逻辑“1”,高低电平维持时间都是以 0.5ms±50us 为基数,
//1.5ms±150us = 3 * (0.5ms±50us),所以一个逻辑周期 2ms±200us = (1.5ms±150us) + (0.5ms±50us) = 4 * (0.5ms±50us)
//所以可以取一个逻辑周期的中间时间断进行判断,即>(0.5ms±50us) 且 <(1.5ms±150us) 这段时间内判断,大于去上公差,小于取下公差
//所以判断时间范围为 0.5ms+50us ~ 1.5ms-150us = 550us ~ 1350us
// if ((has_read_bit==0) && (H_L_Level_time_cnt > SHORT_TIME_NUM_MAX) && (H_L_Level_time_cnt < LONG_TIME_NUM_MIX))
// {
// receive_data_buf[receive_data_num] |= DATA_REV_PIN;
// has_read_bit = 1;
// }
//如果已经读取一个bit位,且时间计数已经>=2ms±200us,说明一个逻辑周期过去了
if ((has_read_bit==1) && (H_L_Level_time_cnt >= LOGIC_CYCLE_NUM_MIN && H_L_Level_time_cnt <= LOGIC_CYCLE_NUM_MAX))
{
H_L_Level_time_cnt = 0; //高低电平计时变量清0
has_read_bit = 0; //清0,读取下一个bit位
receive_bit_num++; //接收的bit数++
if (receive_bit_num==REV_BIT_NUM) //如果一个字节8个bit位接收完成
{
receive_data_num++; //接收的数据个数++
receive_bit_num = 0; //接收bit位个数清0重新接收
if (receive_data_num == REV_DATA_NUM) //如果数据采集完毕
{
receive_state = END_SIGNAL_STATE; //进入接收结束低电平信号状态
}
}
else //如果一个字节8个bit位还没有接收完成
{
//将接收数据缓存左移一位,数据从低bit位开始接收
receive_data_buf[receive_data_num] = receive_data_buf[receive_data_num] >> 1;
}
}
break;
case END_SIGNAL_STATE: //在接收结束信号低电平期间
if (DATA_REV_PIN == HIGH) //结束信号低电平检测期间读到高电平
{
if (H_L_Level_time_cnt >= END_SIGNAL_TIME_NUM) //如果读到低电平时间>=5ms
{
read_success = 1; //一帧数据读取成功
start_H_L_Level_timming_flag = 0; //停止高低电平计时
H_L_Level_time_cnt = 0; //定时器计数值清0
receive_state = INITIAL_STATE; //接收状态清0
}
else //如果低电平时间没有5ms
{
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
}
else //结束信号低电平检测期间一直为低
{
if (H_L_Level_time_cnt >= SYNC_L_TIME_NUM) //如果读到低电平时间>=10ms,认为超时
{ //一帧数据发送完成后需要间隔50ms才发送第二帧数据,期间肯定会被拉高
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
}
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;
}
}
/*******************************************************************************
*函数名称 : Check_Sum_Handle
*函数功能 : 校验和处理
*输入参数 : void
*输出返回 : void
*******************************************************************************/
void Check_Sum_Handle(void)
{
unsigned char i = 0, checkByte = 0;
unsigned long checkSum = 0;
for ( i = 0; i < (REV_DATA_NUM-1); i++)
{
checkSum += receive_data_buf[i];
}
checkByte = (unsigned char)checkSum;
if (checkByte == receive_data_buf[REV_DATA_NUM-1]) //校验和正确
{
check_OK = 1; //标记校验成功
}
else
{
check_OK = 0; //标记校验失败
}
}
版权声明:本文为CSDN博主「Geek YANG」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011350258/article/details/117960319
0、前言
目前很多便宜的单片机都没有标准的串行通讯口UART,甚至没有IIC、SPI等接口,MCU外围硬件接口不够。但有时又需要和其它设备或者器件进行简单的通讯,速度要求不是很高,又或者说受硬件限制,只能提供一根通讯线来通讯,此时可以尝试使用SIF协议进行通讯。
SIF协议因为它的简单,低成本,适用一些需要不高的场景。最近在调充电器,与电动车充电时需要和车子的BMS进行一线通讯。充电器DC端使用的三芯插头,一正极,一负极,一通信。电动车上的电池包作为主机,充电器作为从机,充电器一线通的那个脚要求带有上拉电阻。一线通上拉电阻值统一要求 5V上拉2.2K,3.3V上拉1K,充电器上拉电压给出,电池发送一线通报文。
1、硬件接线示意图
接线方式:
主从双方采用单线单工通讯方式,即只需要一根传输线路,电动车电池BMS为数据发送方,充电器CHG为数据接收方。
波特率:
主机和从机制定协议之前可以双方约定好,也可以主机随意,从机根据主机发送的同步信号,进行自适应解析。
2、通讯规则
2.1、数据帧组成
一次传输一帧数据,每帧数据由 同步信号 + 主报文 + 停止信号 3个部分组成。
1、同步信号为发送主报文的前导信号;
2、主报文为需要发送的有效数据内容,按一定占空比进行发送;
3、结束信号代表一帧完整的数据发送结束的标志信号
2.2、同步信号
同步信号:T1ms的低电平+T2ms的高电平
其中:T1 ≥ 10ms
T2 = 1ms±100us
2.3、主报文
采用高低电平占空比的方式进行数据报文发送。
一对低电平和高电平组成一个bit数据位。在高电平的下降沿计算高电平的占空比,根据占空 比判断本次电平是逻辑"1"或逻辑"0"。
占空比 η = T2 / T ( T2 为高电平时间,T=T1+T2 )。
2.3.1、逻辑“1”
如上图所示,T1和T2组成一个数据位,该数据位占空比η为75%,表示数据位为逻辑"1"。
注意:η = T2 / T =(75 ± 5)%
其中:T1 = 0.5ms ± 50μs
T2 = 1.5ms ± 150μs
T = T1 + T2 = 2ms ± 200μs
2.3.2、逻辑“0”
如上图所示,T1和T2组成一个数据位,该数据位占空比η为25%,表示数据位为逻辑"0"。
注意:η = T2 / T =(25 ± 5)%
其中:T1 = 1.5ms ± 150μs
T2 = 0.5ms ± 50μs
T = T1 + T2 = 2ms ± 200μs
2.4、结束信号
结束信号:T1ms低电平 + Nms高电平
其中:T1 = 5ms
2.5、通信间隔
1、连续两帧数据的发送时间间隔位 50ms
2、发送数据前,电池会检测总线电平,如果检测到总线电平位低电平,则认为总线未连接充
电器,或者充电器未上电,此时电池包将停止数据发送;如果低电平持续时间达到60秒,则电
池包进入低功耗模式。
3、电池包在低功耗模式下,可以被总线高电平唤醒。唤醒后,如果电池包持续检测到总线电平
为高电平,且高电平持续时间达到 1s ,则开始发送数据。电池包在正常发送下一帧报文之前,
需要检测总线电平为高电平才能启动发送。
3、主报文格式
3.1、一线通报文格式
一线通报文由报文ID、协议版本、数据内容、校验字节四部分组成。
报文ID: 包括初始报文、周期报文
协议版本:包括次通信协议版本、主通信协议版本
校验字节:为报文ID、协议版本及数据内容(B0~B47)的和校验
备注: 数据内容中未用到的字节,填充0xFF
4、应用报文格式(主报文格式中的数据内容)
4.1、单字节传输方式
应用报文每字节由 8 位二进制bit组成,b0 是字节的最低有效位,b7 是字节的最高有效位。
收发数据时,先传最低有效位 b0,再传次低有效位 b1,以此类推,最后传最高有效位 b7。
4.2、多字节传输方式
收发多字节信号时,默认采用 Intel_LSB 格式进行数据传输,如上表信号distance所示,先传最
低字节 B43,再传次低字节 B44,以此类推,最后传最高字节 B46。
5、代码实现—纯定时器扫描方式+数组接收
/*******************************************************************************
*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 DATA_REV_PIN P10 //定义数据接收引脚(根据实际项目进行更改)
#define LOW 0 //低电平
#define HIGH 1 //高电平
#define SYNC_L_TIME_NUM 200 //同步信号低电平时间:10ms = 10000us / 50us = 200
#define SYNC_H_TIME_NUM_MIN 18 //同步信号高电平最小时间:1ms-100us = 900us / 50us = 18
#define SYNC_H_TIME_NUM_MAX 22 //同步信号高电平最大时间:1ms+100us = 1100us / 50us = 22
#define SHORT_TIME_NUM_MIN 9 //一个逻辑周期中短的时间最小值:0.5ms-50us = 450us / 50us = 9
#define SHORT_TIME_NUM_MAX 11 //一个逻辑周期中短的时间最大值:0.5ms+50us = 550us / 50us = 11
#define LONG_TIME_NUM_MIN 27 //一个逻辑周期中长的时间最小值:1.5ms-150us = 1350us / 50us = 27
#define LONG_TIME_NUM_MAX 33 //一个逻辑周期中长的时间最大值:1.5ms+150us = 1650us / 50us = 33
#define LOGIC_CYCLE_NUM_MIN 36 //一个逻辑周期最小时间:2ms-200us = 1800us / 50us = 36
#define LOGIC_CYCLE_NUM_MAX 44 //一个逻辑周期最大时间:2ms+200us = 2200us / 50us = 44
#define HALF_LOGIC_CYCLE_MIN 18 //一个逻辑周期的1/2最小时间:1ms-100us = 900us / 50us = 18
#define HALF_LOGIC_CYCLE_MAX 22 //一个逻辑周期的1/2最大时间:1ms+100us = 1100us / 50us = 22
#define END_SIGNAL_TIME_NUM 100 //结束信号电平时间:5ms低电平 + Nms高电平,实际检测5ms低电平就行,一帧数据发送完成后检测5ms低电平就代表完成了,不发数据的时候上拉电阻拉高了
#define REV_BIT_NUM 8 //接收的bit位个数,看是按字节接收还是按字接收,1字节=8bit,1字=2字节=16bit
#define REV_DATA_NUM 51 //接收的数据个数
/* 类型定义 -------------------------------------------------------------------*/
typedef enum
{
INITIAL_STATE=0, //初始状态,等待接收同步信号
SYNC_L_STATE=1, //接收同步低电平信号状态
SYNC_H_STATE=2, //接收同步高电平信号状态
DATA_REV_STATE=3, //读取数据码电平状态
END_SIGNAL_STATE=4, //接收结束电平信号状态
RESTART_REV_STATE=5 //接收过程出错重新接收状态
}REV_STATE_e; //接收数据状态枚举
/* 变量定义 -------------------------------------------------------------------*/
unsigned char receive_state=0; //接收数据状态
unsigned char receive_bit_num=0; //接收的bit位个数
unsigned char receive_data_num=0; //接收的数据个数
//接收数据缓存数组-用一个数组来缓存数据,51个数据字节
unsigned char receive_data_buf[REV_DATA_NUM]={0};
unsigned int H_L_Level_time_cnt=0; //高低电平时间计数
bit start_H_L_Level_timming_flag=0; //开始高低电平计时标记
bit has_read_bit = 0; //1-已经读取一个bit位
bit check_OK = 0; //1-校验和正确,0-校验和失败
bit read_success=0; //一帧数据是否读取成功,0-不成功,1-成功
/* 函数声明 -------------------------------------------------------------------*/
void GPIO_Init(void); //GPIO初始化函数
void Timer0_Init(void); //定时器0初始化函数
void Receive_Data_Handle(void); //接收数据处理
void Check_Sum_Handle(void); //校验和处理
/* 函数定义 -------------------------------------------------------------------*/
/*******************************************************************************
*函数名称 : main
*函数功能 : 主函数入口
*输入参数 : void
*输出返回 : void
*******************************************************************************/
void main(void)
{
GPIO_Init(); //GPIO初始化,设置数据接收引脚P10为高阻输入,检测高低电平
Timer0_Init(); //定时器0初始化,定时周期为:5微秒@33.000MHz
while(1)
{
if (read_success == 1) //如果成功读取一帧数据
{
//一帧数据接收成功后先根据协议要求进行校验和,验证数据的正确性
Check_Sum_Handle();
//如果数据正确,根据接收的数据进行分析获取需要的内容
if (check_OK)
{
/* code */
}
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初始化,单次定时时间根据协议的最小公差±50us确定
*输入参数 : void
*输出返回 : void
*******************************************************************************/
void Timer0_Init(void)
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式:16位自动重载模式
TL0 = 0x8E; //设置定时初值低8位,50微秒@33.000MHz
TH0 = 0xF9; //设置定时初值高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_Handle(); //接收数据处理,波特率自适应
}
/*******************************************************************************
*函数名称 : Receive_Data_Handle
*函数功能 : 接收数据处理
*输入参数 : void
*输出返回 : void
*******************************************************************************/
void Receive_Data_Handle(void)
{
switch (receive_state) //检测当前接收数据状态
{
case INITIAL_STATE: //初始状态,未接收到同步信息,进行同步判断
if (DATA_REV_PIN == LOW) //判断接收引脚的电平状态,当读到低电平时,开始计时
{
receive_bit_num = 0; //重置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 (DATA_REV_PIN == HIGH) //同步信号低电平检测期间读到高电平
{
if (H_L_Level_time_cnt >= SYNC_L_TIME_NUM)//如果同步信号低电平时间>=SYNC_L_TIME_NUM
{ //同步信号低电平时间要>=10ms
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) //同步信号高电平检测期间读到低电平
{
//判断同步信号高电平时间是否在1ms±100us之间
if (H_L_Level_time_cnt >= SYNC_H_TIME_NUM_MIN && H_L_Level_time_cnt <= SYNC_H_TIME_NUM_MAX)
{
H_L_Level_time_cnt = 0; //高低电平计时变量清0
receive_state = DATA_REV_STATE; //进入读取数据状态
}
else
{
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
}
else //如果在同步信号高电平检测期间,时间超过2ms±200us,认为超时
{
//判断时间是否超时 2ms±200us
if (H_L_Level_time_cnt >= LOGIC_CYCLE_NUM_MAX)
{
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
}
break;
case DATA_REV_STATE: //在读取数据码电平期间
//逻辑“0”为 1.5ms±150us低电平 + 0.5ms±50us高电平
//逻辑“1”为 0.5ms±50us低电平 + 1.5ms±150us高电平
//如何判断当前为逻辑“0”还是逻辑“1”,关键在于寻找共同点
//方法一:
//不管是逻辑“0”还是逻辑“1”,周期一样,都是2ms ± 200us
//可以取中间时间点进行判断,(2ms ± 200us) / 2 = 1ms ± 100us
//如果还没有读取一个bit位,且时间计数已经>=900us 且 <=1100us
if ((has_read_bit==0) && (H_L_Level_time_cnt >= HALF_LOGIC_CYCLE_MIN && H_L_Level_time_cnt <= HALF_LOGIC_CYCLE_MAX))
{
receive_data_buf[receive_data_num] |= DATA_REV_PIN;
has_read_bit = 1;
}
//方法二:
//不管是逻辑“0”还是逻辑“1”,高低电平维持时间都是以 0.5ms±50us 为基数,
//1.5ms±150us = 3 * (0.5ms±50us),所以一个逻辑周期 2ms±200us = (1.5ms±150us) + (0.5ms±50us) = 4 * (0.5ms±50us)
//所以可以取一个逻辑周期的中间时间断进行判断,即>(0.5ms±50us) 且 <(1.5ms±150us) 这段时间内判断,大于去上公差,小于取下公差
//所以判断时间范围为 0.5ms+50us ~ 1.5ms-150us = 550us ~ 1350us
// if ((has_read_bit==0) && (H_L_Level_time_cnt > SHORT_TIME_NUM_MAX) && (H_L_Level_time_cnt < LONG_TIME_NUM_MIX))
// {
// receive_data_buf[receive_data_num] |= DATA_REV_PIN;
// has_read_bit = 1;
// }
//如果已经读取一个bit位,且时间计数已经>=2ms±200us,说明一个逻辑周期过去了
if ((has_read_bit==1) && (H_L_Level_time_cnt >= LOGIC_CYCLE_NUM_MIN && H_L_Level_time_cnt <= LOGIC_CYCLE_NUM_MAX))
{
H_L_Level_time_cnt = 0; //高低电平计时变量清0
has_read_bit = 0; //清0,读取下一个bit位
receive_bit_num++; //接收的bit数++
if (receive_bit_num==REV_BIT_NUM) //如果一个字节8个bit位接收完成
{
receive_data_num++; //接收的数据个数++
receive_bit_num = 0; //接收bit位个数清0重新接收
if (receive_data_num == REV_DATA_NUM) //如果数据采集完毕
{
receive_state = END_SIGNAL_STATE; //进入接收结束低电平信号状态
}
}
else //如果一个字节8个bit位还没有接收完成
{
//将接收数据缓存左移一位,数据从低bit位开始接收
receive_data_buf[receive_data_num] = receive_data_buf[receive_data_num] >> 1;
}
}
break;
case END_SIGNAL_STATE: //在接收结束信号低电平期间
if (DATA_REV_PIN == HIGH) //结束信号低电平检测期间读到高电平
{
if (H_L_Level_time_cnt >= END_SIGNAL_TIME_NUM) //如果读到低电平时间>=5ms
{
read_success = 1; //一帧数据读取成功
start_H_L_Level_timming_flag = 0; //停止高低电平计时
H_L_Level_time_cnt = 0; //定时器计数值清0
receive_state = INITIAL_STATE; //接收状态清0
}
else //如果低电平时间没有5ms
{
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
}
else //结束信号低电平检测期间一直为低
{
if (H_L_Level_time_cnt >= SYNC_L_TIME_NUM) //如果读到低电平时间>=10ms,认为超时
{ //一帧数据发送完成后需要间隔50ms才发送第二帧数据,期间肯定会被拉高
receive_state = RESTART_REV_STATE; //进入重新接收状态
}
}
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;
}
}
/*******************************************************************************
*函数名称 : Check_Sum_Handle
*函数功能 : 校验和处理
*输入参数 : void
*输出返回 : void
*******************************************************************************/
void Check_Sum_Handle(void)
{
unsigned char i = 0, checkByte = 0;
unsigned long checkSum = 0;
for ( i = 0; i < (REV_DATA_NUM-1); i++)
{
checkSum += receive_data_buf[i];
}
checkByte = (unsigned char)checkSum;
if (checkByte == receive_data_buf[REV_DATA_NUM-1]) //校验和正确
{
check_OK = 1; //标记校验成功
}
else
{
check_OK = 0; //标记校验失败
}
}
版权声明:本文为CSDN博主「Geek YANG」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011350258/article/details/117960319
暂无评论