IO口模拟UART串口通信,量产代码分享

有些产品需要用到多个UART串口,而单片机UART数量不足,这时需要用IO口模拟UART通讯。

本文采用IO口+定时器+外部中断的方法,模拟UART时序,实现串口接收和发送的功能,程序已经在量产产品上应用。

实现原理:

1、发送部分,通过定时器控制波特率,改变IO口电平来发送数据,比较简单。

2、接收部分,通过外部中断,识别起始位,定时器控制波特率,使IO口恰好在电平信号的中间位置读取数据,减少其他中断的影响,大大降低误码率。

注意事项:

1、目前本程序只适用于接收数据定长的情况,不定长数据不适用。

2、在串口发送和接收过程中,尽量减少中断,频繁的中断打断程序运行,会产生误码。

3、每个数据包帧头是0xAA,帧尾0x0D,校验值是除了帧尾所有数据的累加和,帧头和帧尾可以根据用户自己的需求修改。

全部代码如下:

//UART波特率是9600,1 bit≈104us,
//1个起始位,1个停止位,8个数据位
//每个数据包帧头是0xAA,帧尾0x0D,校验值是除了帧尾所有数据的累加和。
//通讯方式采用一问一答形式,每500ms发送一次数据并接收应答数据,
//接收数据总共22byte,22*(1+1+8)*104us=22.88ms,即接收每帧数据的时间长度.

#define d_head  0xAA
#define d_tail  0x0D

volatile bit RXFLAG = 0;//外部中断标志位,在下降沿中断中置1;
u8	Rx_Tim=0;    //接收计时器,每次接收到数据时重置倒计时。倒计时为0则认为上一帧结束,        
                 //开始接收下一帧数据,复位接收计数器。
#define Set_Clear_RxTim()        Rx_Tim=50    //50ms倒计时,我的产品接收数据时间长度22.88ms
                                              //理论上23ms就可以了,不过要考虑时序存在误差。
                
void Uart_IO_GPIO_Init(void)//模拟串口的IO口初始化
{
    PORTA|= 0B00000001;		//Tx输出1
    TRISA &= 0B11111110;    //PA0-Output_Tx  PA1-Input_Rx
    WPUA  |= 0B00000010;    //PA1上拉电阻
}

void WaitTF0(void)//延时等待控制UART时序
{
     while(T2UIE);//定时器开始计时后,在定时器中断中清零T2UIE
     T2UIE=1;
}

void WByte(u8 input)//发送1 Byte
{
	u8 i=8;
	TXIO=0;     //Tx置0,发送起始位
    T2UIF = 1;  //清零定时器2中断标志位
	T2CEN =0;   //失能定时器2
	TIM2CNTRH=0;//清零定时器2计数值高位
	TIM2CNTRL=0;//清零定时器2计数值低位
	T2CEN=1;    //启动定时器2
	T2UIE =1;   //使能定时器2
	WaitTF0();  //等待定时器中断,等待时间取决于定时器2的装载值。这里的定时时间是104us
	while(i--)
	{
		if(input&0x01) //逐个bit发送,先传低位
			TXIO=1;
		else
			TXIO = 0;   
		WaitTF0(); //等待延时104us
		input=input>>1; 
	}
	TXIO=1;      //发送结束位
	WaitTF0();
	T2UIE=0;     //发送1 byte完成了     
}


u8 RByte()//接收1 Byte
{
	u8 Output=0;
	u8 i=8;
    T2UIF = 1;    //清零定时器2中断标志位
    T2CEN=0;      //失能定时器2
	TIM2CNTRH=0;  //清零定时器2计数值高位
	TIM2CNTRL=0;  //清零定时器2计数值低位
    TIM2ARRH =0x09; //设定时器2装载值
	TIM2ARRL =0x00;	//起始位1.5bit(156us)后再读取电平,确保在第一bit中间位置读取数据
    T2CEN=1;        //启动定时器2                
    T2UIE =1;       //使能定时器2
	WaitTF0();      //等待104us+52us=156us,刚好在第一个bit的中间位置读取数据
    T2CEN=0;        //失能定时器2
  	TIM2ARRH =0x06; //重新装载104us,确保在后续的bit中间位置读取数据
	TIM2ARRL =0x80; 
    T2CEN=1;        //使能定时器2
	while(i--)
	{
		Output =Output>>1;
		if(RXIO) 
        {
        	Output   |=0x80;      			//先接收收低位
        }
		WaitTF0();                 			//位间延时104us
	}
	T2UIE=0;                          		//停止Timer0
	return Output;                          //返回接收到的byte
}


u8 Rx_buff[Rx_size]={0};
volatile bit B_Rx_decode;
void UART_IO_RxProcess(vu8 *buff,vu8 size)//接收size长度的数据放在buff中
{
    u8 rdata;
    static u8 cnt,checksum;
	if(RXFLAG)							//RXFLAG在下降沿外部中断函数中置1
    {
        rdata=RByte();                  //有下降沿中断说明有起始位,开始接收数据
        if(!Rx_Tim)//倒计时结束,说明超过50ms没有接收到数据,清零接收计数值,开始接收下一帧.
        {
          cnt=0;
          checksum=0;
        }
        Set_Clear_RxTim();//接收到数据,复位倒计时
        buff[cnt]=rdata;
        if(cnt<(Rx_size-2))
            checksum+=buff[cnt];//计算数据校验和,与接收到的校验和对比。
         if(cnt==(Rx_size-1))//计数值与数据长度相等,说明接收完一帧数据。
        {
            if((buff[0]==d_head)&&(buff[Rx_size-1]==d_tail)&&(checksum==buff[Rx_size-2]))
				B_Rx_decode=1;//帧头,帧尾,校验和,全部正确,接收数据有效。
        }
		cnt++;
        EPIE0   = 0B00000010;//接收完一个数据,重新使能外部下降沿中断,准备接收下一个数据
		RXFLAG = 0;          //标志位清零
    } 
 }

u8 Tx_buff[Tx_size]={0};
void UART_IO_TxProcess(vu8 *buff,vu8 size)//发送数据
{
	u8 i;
    for(i=0;i<size;i++)
    {
        WByte(buff[i]);
    }
}

定时器2中断函数,外部中断函数和定时器4中断函数

void TIMER2_INPT(void)//定时器2中断函数
{
    if(T2UIE && T2UIF)                	
	{
		T2UIF = 1;//写1清零标志位
		T2UIE=0;  //清零T2UIE,与WaitTF0()配合,等待延时。                      
    }
}


void IO_INPT(void)//外部中断函数 下降沿
{
	if(EPIF0 & 0x02)                	
	{
		EPIF0 |= 0x02;//写1清零标志位
        NOP();NOP();NOP();NOP();//进入中断后延时等待,滤除干扰 
        NOP();NOP();NOP();NOP();
		if(RXIO == 0)//等待一段时间后,管脚仍然低电平,则认为有下降沿产生
        {
			EPIE0   = 0B00000000;  	//禁止PA1下降沿中断
            RXFLAG = 1;
        } 
    }
}


void TIMER4_INPT(void)   定时器4中断,1ms中断一次
{
    static u16 TIM_500ms;
    if(T4UIE && T4UIF)    //中断标志                	
	{
		T4UIF = 1;                    	//写1清零标志位
        TIM_500ms++;
        
        if(Rx_Tim)//接收倒计时。
			Rx_Tim--;

        if(TIM_500ms>499)
        {
            TIM_500ms=0;
            B_uart_500ms=1;//500ms发送一次询问数据,并接收应答数据
        }
	} 
}

main.c

void main (void)
{
   Timer2_Init();
   Timer4_Init();
   Uart_IO_GPIO_Init();
   IO_INT_INITIAL();
   while(1)
   {
        if(B_uart_500ms)//每500ms发送一次询问数据,并等待接收
        {
            B_uart_500ms=0;
            UART_IO_TxProcess(Txbuff,Txsize);
        }
        UART_IO_RxProcess(Rxbuff,Rxsize);//必须在while(1)里面一直运行,等待接收

        if(B_Rx_decode)//接收到的数据正确
        {
            B_Rx_decode=0;
            //对数据进行处理
        }
    }
}

  • 6
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值