01、单线通讯—IO口实现单线单工通讯(纯定时器扫描方式)

0、前言


  许多便宜的单片机都没有标准的串行通讯口。因此,我们常用单片机的 IO 口来模拟串行通讯。下面,给出一个简单的通讯方案。
  在这个方案中,主机平时处于睡眠状态,从机处于工作状态。在需要的时候,由从机通过通讯接口唤醒主机。主机被唤醒后不断向从机发送数据,从机接收通讯数据并进行解读,执行相关功能。

1、硬件连接示意图


在这里插入图片描述

2、单线单工通讯协议


  通讯线平时空闲时处于低电平,由一个 100ms 的高电平作为引导码来开始接收数据。对于从机来说,接受完 8 位数据,或检测到一个连续 5t 的低电平(结束码)即可认为通讯结束。完毕后通讯线恢复到空闲状态的低电平。
在这里插入图片描述

3、关于波特率自适应的处理


  在大多数情况下,单片机都使用内部RC振荡源。因此存在一个频率误差的问题,当系统频率变化时,可能无法正确识别 【“ t ”】值。为了能够消除该误差,可以考虑采用波特率自适应技术。

  实现方法:在引导码之后,数据码之前,增加一位波特率校准位。该校准位由一个低电平和高电平构成,低电平和高电平分别=1t。在检测到引导码结束后,先对校准位进行时间测量。为后续数据码的识别提供标准时间(“t”值)。

修改后的波形图如下所示:
在这里插入图片描述

4、数据接收程序流程图


说明:

  • 从机在被高电平引导码唤醒后,调用接收程序。

  • 通讯接收程序包括以下几个部分:
    1、等待引导码结束
    2、检测校准位,求 t 值
    3、检测数据位
    4、检测结束码,退出

  • 当发生以下情况时,判为通讯失败:

    1、除引导码之外的任何一个低电平或者高电平计时超时;

    2、数据位的高电平计时值 ≠1t,也 ≠3t;

    3、8位数据位接收完毕后,再次检测到高电平。

  • 通讯完毕后,接收到的数据被存储在接收缓存中,供上级程序使用。当通讯失败时,接收缓存清0

流程图如下:
在这里插入图片描述

5、数据接收程序的代码实现(纯定时器扫描)

5.1、不带校准位的代码实现—即不带波特率自适应

/*******************************************************************************
 *Copyright (c) GeekYang
 *@文件名 : main.c
 *@作  者 : GeekYang
 *@时  间 : 2021-06-11 13:30: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 REV_MODE_SELECT 0       //0-接收一帧数据,1-接收多帧数据

#define LOW             0       //低电平
#define HIGH            1       //高电平

#define DATA_REV_PIN    P10     //定义数据接收引脚(根据实际项目进行更改)

#define REV_BIT_NUM     8       //接收的bit位个数,看是按字节接收还是按字接收,1字节=8bit,1字=2字节=16bit

#if REV_MODE_SELECT             //如果接收多帧数据
#define REV_DATA_NUM    12      //接收的数据个数
#endif

/* 类型定义 -------------------------------------------------------------------*/
typedef enum
{
    INITIAL_STATE=0,            //初始状态,未接收到引导码时
    BOOT_CODE_STATE=1,          //读取引导码状态
    DATA_REV_L_STATE=2,         //读取数据码低电平状态
    DATA_REV_H_STATE=3,         //读取数据码高电平状态
    END_CODE_STATE=4,           //读取结束码状态
    RESTART_REV_STATE=5         //接收过程出错重新接收状态
}REV_STATE_e;                   //接收数据状态枚举

/* 变量定义 -------------------------------------------------------------------*/
unsigned char receive_state=0;      //接收数据状态
unsigned char receive_bit_num=0;    //接收的bit位个数
#if REV_MODE_SELECT                 //如果接收多帧数据
unsigned char receive_data_num=0;   //接收的数据个数
#endif
unsigned char receive_data=0;       //接收的数据,看是按字节接收还是按字接收,按字接收就定义为 unsigned int
#if REV_MODE_SELECT                 //如果接收多帧数据
unsigned char receive_data_buf[REV_DATA_NUM]={0};   //接收数据缓存数组-如果一帧数据有多个数据打开注释
#endif
unsigned int  H_L_Level_time_cnt=0; //高低电平时间计数

bit start_H_L_Level_timming_flag=0; //开始高低电平计时标记
bit read_success=0;                 //一帧数据是否读取成功,0-不成功,1-成功

/* 函数声明 -------------------------------------------------------------------*/
void GPIO_Init(void);           //GPIO初始化函数
void Timer0_Init(void);         //定时器0初始化函数
void Receive_Data_No_Adjust_Bit_Handle(void);   //接收数据处理—无校准位,即没有波特率自适应,假设t=1ms

/* 函数定义 -------------------------------------------------------------------*/
/*******************************************************************************
 *函数名称 : main
 *函数功能 : 主函数入口
 *输入参数 : void
 *输出返回 : void
*******************************************************************************/
void main(void)
{
    GPIO_Init();        //GPIO初始化,设置数据接收引脚P10为高阻输入,检测高低电平
    Timer0_Init();      //定时器0初始化,定时周期为:500微秒@33.000MHz

    while(1)
    {
        Receive_Data_No_Adjust_Bit_Handle();    //如果主循环中处理的任务比较多也可以在定时中断服务函数中调用
        
        if (read_success == 1)              //如果成功读取一帧数据
        {
            #if REV_MODE_SELECT
            if (receive_data_buf[0]==0x01 && receive_data_buf[1]==0x02 && receive_data_buf[2]==0x03)    //根据接收到的数据进行相应处理
            {
                /* code */
            }
            #else
            if (receive_data == 0xA5)       //根据接收的数据进行相应处理
            {
                /* code */
            }
            #endif

            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 = 0x1C;		    //设置定时初值低8位,100微秒@33.000MHz
	TH0 = 0xF3;		    //设置定时初值高8位
	TF0 = 0;		    //清除TF0标志
	TR0 = 1;		    //定时器0开始计时
}

/*******************************************************************************
 *函数名称 : Timer0_isr
 *函数功能 : 定时器0中断处理函数
 *输入参数 : void
 *输出返回 : void
*******************************************************************************/
void Timer0_isr() interrupt 1 //100us定时器
{
    if (start_H_L_Level_timming_flag==1)
    {
        H_L_Level_time_cnt++;     //高低电平维持时间计数变量
    }
}

/*******************************************************************************
 *函数名称 : Receive_Data_No_Adjust_Bit_Handle
 *函数功能 : 接收数据处理—无校准位,即没有波特率自适应,假设t=1ms
 *输入参数 : void
 *输出返回 : void
*******************************************************************************/
void Receive_Data_No_Adjust_Bit_Handle(void)
{
    switch (receive_state)          //检测当前接收数据状态
    {
    case INITIAL_STATE:             //初始状态,未接收到引导码时
        if (DATA_REV_PIN == HIGH)   //判断接收引脚的电平状态,当读到高电平时,说明引导码来了
        {
            receive_bit_num = REV_BIT_NUM;  //重置bit位计数器
            #if REV_MODE_SELECT     //如果接收多帧数据
            receive_data_num = 0;   //接收数据个数
            #endif
            receive_data = 0;       //接收数据缓存清0
            H_L_Level_time_cnt = 0; //高低电平计时变量清0
            start_H_L_Level_timming_flag = 1;   //开始高低电平计时
            receive_state = BOOT_CODE_STATE;    //进入读取引导码状态
        }
        break;
    
    case BOOT_CODE_STATE:           //在读取引导码期间
        //引导码时间为100ms左右,给个±0.5m的误差,误差根据实际情况调整
        //定时器0单次定时为100us,所以99.5ms ~ 100.5ms 的 计数为995~1005
        //引导码不需要判断具体时间,只要等待引导码结束(接收电平变低就行)
        //但也不能无限等待低电平的到来,需要超时判断
        if (H_L_Level_time_cnt > 1005) //引导码高电平超时
        {
            receive_state = RESTART_REV_STATE;    //进入重新接收状态
        }
        else    //如果引导码在没有超时前
        {
            if (DATA_REV_PIN == LOW)    //判断接收引脚的电平状态,当读到低电平时,开始接收数据码低电平
            {
                H_L_Level_time_cnt = 0; //高低电平计时变量清0
                receive_state = DATA_REV_L_STATE;    //进入读取数据码低电平状态
            }
        }
        break;

    case DATA_REV_L_STATE:          //在读取数据码低电平期间
        //不管时逻辑“0”还是逻辑“1”,数据码低电平都为1t,即1ms,给个±0.5ms的误差
        //误差根据实际情况调整,不需要实际测量低电平是否为准确的1ms,只要判断是否超时
        //如果接收到的低电平超过1.5ms了,那就不正常了
        if (H_L_Level_time_cnt > 15)
        {
            receive_state = RESTART_REV_STATE;    //进入重新接收状态
        }
        else    //如果数据码低电平计时在没有超时前
        {
            if (DATA_REV_PIN == HIGH)   //判断接收引脚的电平状态,当读到高电平时,开始接收数据码高电平
            {
                H_L_Level_time_cnt = 0; //高低电平计时变量清0
                receive_state = DATA_REV_H_STATE;    //进入读取数据码高电平状态
            }  
        } 
        break;

    case DATA_REV_H_STATE:          //在读取数据码高电平期间
        //判断数据码高电平的计时时间是否超时,如果高电平时间比逻辑“1”所需的
        //高电平时间3t,即3ms还长了,那说明数据错误了
        //给个±0.5ms的误差,即超过3.5ms就算超时了
        if (H_L_Level_time_cnt > 35)
        {
            receive_state = RESTART_REV_STATE;    //进入重新接收状态
        }
        else    //如果数据码高电平计时在没有超时前
        {
            if (DATA_REV_PIN == LOW)    //判断接收引脚的电平状态,当读到低电平时,说明一个逻辑位先低后高读完了
            {
                //判断数据码高电平的计时时间
                //如果高电平时间在2.5ms~3.5ms之间,认为是逻辑 1
                if (H_L_Level_time_cnt >=25 && H_L_Level_time_cnt<=35)
                {
                    receive_data = receive_data>>1;           //先右移一位
                    receive_data = receive_data | 0x80;       //从bit0开始接收
                    receive_bit_num--;                        //接收bit位个数--
                }
                //如果高电平时间在0.5ms~1.5ms之间,认为是逻辑 0
                else if (H_L_Level_time_cnt >=5 && H_L_Level_time_cnt<=15)
                {
                    receive_data = receive_data>>1;           //直接右移一位补0
                    receive_bit_num--;                        //接收bit位个数--
                }
                //如果高电平时间 ≠1ms 也 ≠3ms
                else
                {
                    receive_state = RESTART_REV_STATE;          //进入重新接收状态
                }

                if (receive_bit_num == 0)                       //如果一个字节的数据bit位接收完成
                {
                    #if REV_MODE_SELECT                         //如果接收多帧数据
                    receive_bit_num = REV_BIT_NUM;              //重置bit位计数器

                    if (receive_data_num < REV_DATA_NUM)        //如果接收数据的个数还没满
                    {
                        receive_data_buf[receive_data_num] = receive_data;  //将接收的多个数据放入缓存
                        receive_data_num++;
                    }
                    if (receive_data_num == REV_DATA_NUM)       //如果接收数据个数满了
                    {
                        receive_state = END_CODE_STATE;         //进入读取结束码状态
                    }
                    #else										 //如果接收一帧数据
                    receive_state = END_CODE_STATE;             //进入读取结束码状态
                    #endif
                }
                else                                            //如果没有接收完成
                {
                    receive_state = DATA_REV_L_STATE;           //继续读取下一个逻辑值
                }  
                H_L_Level_time_cnt = 0;                         //高低电平计时值清0  
            }   
        }
        break;

        case END_CODE_STATE:            //读取结束码状态
            //如果结束码低电平持续时间 >= 5t,即 >=5ms
            if (H_L_Level_time_cnt >= 50)
            {
                read_success = 1;                   //一帧数据读取成功
                start_H_L_Level_timming_flag = 0;   //停止高低电平计时
           		receive_state = INITIAL_STATE;      //接收状态清0
           		H_L_Level_time_cnt = 0;             //定时器计数值清0
            }
            else                    //如果在结束码低电平持续时间<5ms内
            {
                if (DATA_REV_PIN == HIGH)       //判断接收引脚的电平状态,当读到高电平时,说明数据接收错误
                {
                    receive_state = RESTART_REV_STATE;          //进入重新接收状态
                }
                
            } 
            break;

        case RESTART_REV_STATE:                 //重新接收数据状态
            receive_state = INITIAL_STATE;      //接收状态清0
           	H_L_Level_time_cnt = 0;             //定时器计数值清0
            break;
    }
}

5.2、带校准位的代码实现—即波特率自适应

/*******************************************************************************
 *Copyright (c) GeekYang
 *@文件名 : main.c
 *@作  者 : GeekYang
 *@时  间 : 2021-06-11 13:30: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 REV_MODE_SELECT 0       //0-接收一帧数据,1-接收多帧数据

#define LOW             0       //低电平
#define HIGH            1       //高电平

#define DATA_REV_PIN    P10     //定义数据接收引脚(根据实际项目进行更改)

#define SHORT_TIME_NUM  1       //1t中的1,即1Tosc中的1 
#define LONG_TIME_NUM   3       //3t中的3,即3Tosc中的3 

#define REV_BIT_NUM     8       //接收的bit位个数,看是按字节接收还是按字接收,1字节=8bit,1字=2字节=16bit

#if REV_MODE_SELECT             //如果接收多帧数据
#define REV_DATA_NUM    12      //接收的数据个数
#endif

/* 类型定义 -------------------------------------------------------------------*/
typedef enum
{
    INITIAL_STATE=0,            //初始状态,未接收到引导码时
    BOOT_CODE_STATE=1,          //读取引导码状态
    ADJ_BIT_L_STATE=2,          //校准位低电平状态
    ADJ_BIT_H_STATE=3,          //校准位高电平状态
    DATA_REV_L_STATE=4,         //读取数据码低电平状态
    DATA_REV_H_STATE=5,         //读取数据码高电平状态
    END_CODE_STATE=6,           //读取结束码状态
    RESTART_REV_STATE=7         //接收过程出错重新接收状态
}REV_STATE_e;                   //接收数据状态枚举

/* 变量定义 -------------------------------------------------------------------*/
unsigned char receive_state=0;      //接收数据状态
unsigned char receive_bit_num=0;    //接收的bit位个数
#if REV_MODE_SELECT                 //如果接收多帧数据
unsigned char receive_data_num=0;   //接收的数据个数
#endif
unsigned char receive_data=0;       //接收的数据,看是按字节接收还是按字接收,按字接收就定义为 unsigned int
#if REV_MODE_SELECT                 //如果接收多帧数据
unsigned char receive_data_buf[REV_DATA_NUM]={0};   //接收数据缓存数组-如果一帧数据有多个数据打开注释
#endif
unsigned int  H_L_Level_time_cnt=0; //高低电平时间计数

unsigned int Tosc = 10;             //波形时基单元,一般带波特率自适应的,不会说明高低电平的时间,会用一个Tosc时基描述
                                    //如上面的波形图,校准位为 1Tosc低电平 + 1Tosc高电平;逻辑“0”为 1Tosc低电平 + 1Tosc高电平;逻辑“1”为 1Tosc低电平 + 3Tosc高电平
                                    //校准位高电平计时时间为:H_L_Level_time_cnt * 100us(单片机单次定时时间) = SHORT_TIME_NUM * Tosc * 100us
                                    //可以理解为 100us 是人为设置的一个定时器单次定时时间,再这个定时时间的基础上又产生了一个实际时基Tosc,用再波形上,波形基于这个Tosc时基单元
                                    //可以动态的调整高低电平的时间,只要Tosc改变,1t和3t对应的时间也会随之改变;然后我们反过来思考,再不知道波特率的情况下,去读取校准位高电平的
                                    //时间,将读到的时间计数H_L_Level_time_cnt * 100us是高电平的真实时间 = 1 * Tosc * 100us = SHORT_TIME_NUM * Tosc * 100us
                                    //Tosc = H_L_Level_time_cnt / SHORT_TIME_NUM

bit start_H_L_Level_timming_flag=0; //开始高低电平计时标记
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初始化,定时周期为:500微秒@33.000MHz

    while(1)
    {
        Receive_Data_Baud_Adjust_Bit_Handle();    //如果主循环中处理的任务比较多也可以在定时中断服务函数中调用
        
        if (read_success == 1)              //如果成功读取一帧数据
        {
            #if REV_MODE_SELECT
            if (receive_data_buf[0]==0x01 && receive_data_buf[1]==0x02 && receive_data_buf[2]==0x03)    //根据接收到的数据进行相应处理
            {
                /* code */
            }
            #else
            if (receive_data == 0xA5)       //根据接收的数据进行相应处理
            {
                /* code */
            }
            #endif

            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 = 0x1C;		    //设置定时初值低8位,100微秒@33.000MHz
	TH0 = 0xF3;		    //设置定时初值高8位
	TF0 = 0;		    //清除TF0标志
	TR0 = 1;		    //定时器0开始计时
}

/*******************************************************************************
 *函数名称 : Timer0_isr
 *函数功能 : 定时器0中断处理函数
 *输入参数 : void
 *输出返回 : void
*******************************************************************************/
void Timer0_isr() interrupt 1 //100us定时器
{
    if (start_H_L_Level_timming_flag==1)
    {
        H_L_Level_time_cnt++;     //高低电平维持时间计数变量
    }
}

/*******************************************************************************
 *函数名称 : 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 == HIGH)   //判断接收引脚的电平状态,当读到高电平时,说明引导码来了
        {
            receive_bit_num = REV_BIT_NUM;  //重置bit位计数器
            #if REV_MODE_SELECT     //如果接收多帧数据
            receive_data_num = 0;   //接收数据个数
            #endif
            receive_data = 0;       //接收数据缓存清0
            H_L_Level_time_cnt = 0; //高低电平计时变量清0
            start_H_L_Level_timming_flag = 1;   //开始高低电平计时
            receive_state = BOOT_CODE_STATE;    //进入读取引导码状态
        }
        break;
    
    case BOOT_CODE_STATE:           //在读取引导码期间
        //引导码时间为100t左右
        //引导码不需要判断具体时间,只要等待引导码结束(接收电平变低就行)
        //但也不能无限等待低电平的到来,需要超时判断
        if (H_L_Level_time_cnt > 100*Tosc) //引导码高电平超时
        {
            receive_state = RESTART_REV_STATE;    //进入重新接收状态
        }
        else    //如果引导码在没有超时前
        {
            if (DATA_REV_PIN == LOW)    //判断接收引脚的电平状态,当读到低电平时,开始接收数据码低电平
            {
                H_L_Level_time_cnt = 0; //高低电平计时变量清0
                receive_state = ADJ_BIT_L_STATE;    //进入读取校准位低电平状态
            }
        }
        break;

    case ADJ_BIT_L_STATE:           //在读取校准位低电平期间
        //判断低电平时间是否在 2t 内,如果超过 2t 了,那就不正常了
        if (H_L_Level_time_cnt > (LONG_TIME_NUM-SHORT_TIME_NUM)*Tosc)
        {
            receive_state = RESTART_REV_STATE;    //进入重新接收状态
        }
        else
        {
            if (DATA_REV_PIN == HIGH)   //判断接收引脚的电平状态,当读到高电平时,开始接收校准位高电平
            {
                H_L_Level_time_cnt = 0; //高低电平计时变量清0
                receive_state = ADJ_BIT_H_STATE;    //进入读取校验位高电平状态
            }
        }
        break;

    case ADJ_BIT_H_STATE:           //在读取校准位高电平期间
        //判断高电平时间是否在 2t 内,如果超过 2t 了,那就不正常了
        if (H_L_Level_time_cnt >= (LONG_TIME_NUM-SHORT_TIME_NUM)*Tosc)
        {
            receive_state = RESTART_REV_STATE;    //进入重新接收状态
        }
        else
        {
            if (DATA_REV_PIN == LOW)   //判断接收引脚的电平状态,当读到低电平时,开始接收数据码低电平
            {
                Tosc = H_L_Level_time_cnt/SHORT_TIME_NUM;   //调整时基Tosc以适应发送数据的波特率 
                H_L_Level_time_cnt = 0; //高低电平计时变量清0
                receive_state = DATA_REV_L_STATE;    //进入读取数据码电平状态
            }
        }
        break;

    case DATA_REV_L_STATE:          //在读取数据码低电平期间
        //不管时逻辑“0”还是逻辑“1”,数据码低电平都为1t
        //不需要实际测量低电平是否为准确的1t,只要判断是否超时
        //如果接收到的低电平超过2t了,那就不正常了
        if (H_L_Level_time_cnt >= (LONG_TIME_NUM-SHORT_TIME_NUM)*Tosc)
        {
            receive_state = RESTART_REV_STATE;    //进入重新接收状态
        }
        else    //如果数据码低电平计时在没有超时前
        {
            if (DATA_REV_PIN == HIGH)   //判断接收引脚的电平状态,当读到高电平时,开始接收数据码高电平
            {
                H_L_Level_time_cnt = 0; //高低电平计时变量清0
                receive_state = DATA_REV_H_STATE;    //进入读取数据码高电平状态
            }  
        } 
        break;

    case DATA_REV_H_STATE:          //在读取数据码高电平期间
        //判断数据码高电平的计时时间是否超时,如果高电平时间比逻辑“1”所需的
        //高电平时间3t还长了,那说明数据错误了
        if (H_L_Level_time_cnt > LONG_TIME_NUM*Tosc)
        {
            receive_state = RESTART_REV_STATE;    //进入重新接收状态
        }
        else    //如果数据码高电平计时在没有超时前
        {
            if (DATA_REV_PIN == LOW)    //判断接收引脚的电平状态,当读到低电平时,说明一个逻辑位先低后高读完了
            {
                //判断数据码高电平的计时时间
                //如果高电平时间>=2t 且 <=3t,认为是逻辑 1
                if (H_L_Level_time_cnt >= (LONG_TIME_NUM-SHORT_TIME_NUM)*Tosc && H_L_Level_time_cnt <= LONG_TIME_NUM*Tosc)
                {
                    receive_data = receive_data>>1;           //先右移一位
                    receive_data = receive_data | 0x80;       //从bit0开始接收
                    receive_bit_num--;                        //接收bit位个数--
                }
                //如果高电平时间>=1t 且 <2t,认为是逻辑 0
                else if (H_L_Level_time_cnt >= SHORT_TIME_NUM*Tosc && H_L_Level_time_cnt < (LONG_TIME_NUM-SHORT_TIME_NUM)*Tosc)
                {
                    receive_data = receive_data>>1;           //直接右移一位补0
                    receive_bit_num--;                        //接收bit位个数--
                }
                //如果高电平时间 ≠1t 也 ≠3t
                else
                {
                    receive_state = RESTART_REV_STATE;          //进入重新接收状态
                }

                if (receive_bit_num == 0)                       //如果一个字节的数据bit位接收完成
                {
                    #if REV_MODE_SELECT                         //如果接收多帧数据
                    receive_bit_num = REV_BIT_NUM;              //重置bit位计数器

                    if (receive_data_num < REV_DATA_NUM)        //如果接收数据的个数
                    {
                        receive_data_buf[receive_data_num] = receive_data;  //将接收的多个数据放入缓存
                        receive_data_num++;
                    }
                    if (receive_data_num == REV_DATA_NUM)       //如果接收数据个数满了
                    {
                        receive_state = END_CODE_STATE;         //进入读取结束码状态
                    }
                    #else
                    receive_state = END_CODE_STATE;             //进入读取结束码状态
                    #endif
                }
                else                                            //如果没有接收完成
                {
                    receive_state = DATA_REV_L_STATE;           //继续读取下一个逻辑值
                }  
                H_L_Level_time_cnt = 0;                         //高低电平计时值清0  
            }   
        }
        break;

        case END_CODE_STATE:            //读取结束码状态
            //如果结束码低电平持续时间 >= 5t
            if (H_L_Level_time_cnt >= 5*Tosc)
            {
                read_success = 1;                   //一帧数据读取成功
                start_H_L_Level_timming_flag = 0;   //停止高低电平计时
           		receive_state = INITIAL_STATE;      //接收状态清0
           		H_L_Level_time_cnt = 0;             //定时器计数值清0
            }
            else                    //如果在结束码低电平持续时间<5ms内
            {
                if (DATA_REV_PIN == HIGH)       //判断接收引脚的电平状态,当读到高电平时,说明数据接收错误
                {
                    receive_state = RESTART_REV_STATE;          //进入重新接收状态
                }    
            } 
            break;

        case RESTART_REV_STATE:                 //重新接收数据状态
            receive_state = INITIAL_STATE;      //接收状态清0
           	H_L_Level_time_cnt = 0;             //定时器计数值清0
            break;
    }
}
  • 5
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Geek@Yang

码字不易,来点鼓励~~~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值