起因
双单片机通信,MCU资源受限,无法使用片上UART通道,只可用普通I/O
成果
使用2个普通I/O端口,通过软件实现UART协议的数据全双工收发,8bits数据位、1600波特率,可移植其它单片机
资源占用
·1个定时器
·2个普通I/O端口
IDE
keil V4
硬件
51最小系统板
一、UART协议
二、思路
1>数据的发送其实就是控制发送引脚Ptxd的电平随着固定时序变化,那么固定的、周期性的时序需要一个定时器来产生。为了保证定时器周期高精度稳定可控,选用自动重装定时器模式 。为了尽可能的减少资源占用,发送和接收共用一个定时器资源。
2>程序的设计采用状态机设计模式,这样可以避免独占CPU,并且利于移植到各种状态机系统。状态迁移程序在ISR中实现。
3>定时器的中断频率为波特率的3倍[之所以这样做后面介绍接收时详述]。
4>开启发送之后,ISR中发送相关程序得到执行,每3次中断处理1bit.根据时序,先产生bit0[起始位],然后根据8bit型数据依次在时序线上控制Ptxd的电平高低。数据位发送完毕后,若设置了校验位,则对8bits数据中bit1的数量进行判断,根据奇偶校验对bit1的数量通过控制Prxd的电平进行奇偶补全[发送数据位时对bit1进行计数]。然后根据设置发送对应长度的bit1[停止位]。最后,判断发送数据是否达到发送长度,若发送长度到达,则结束发送程序执行,否则继续循环发送。
5>开启接收之后,ISR中接收相关程序得到执行,并与发送程序互不干涉。首先每次中断都判断Prxd的电平是否置低[检测起始位],当检测到bit0之后,四个中断后进行数据位最低位的接收。这里之所以选择四个中断的延时,是因为当检测到bit0[起始位]的时刻,不管此时处于实际起始位的哪个时间点位置,四个中断后即下一次判断的位置总会是最接近一位数据中心那个点[一个bit中有三次中断点,有一个最接近中心,越接近中心越能免受发送与接收波特率不同步的影响,具体细节画图可知]。之后的每一位改为每三个中断进行一次判断处理[与波特率同步]。处理到校验位时,如果设置了奇偶校验,则对校验结果输出至设置好的校验标志位中,外围程序可根据此标准位判断是否校验出错。然后判断接收数据的长度是否达到设置长度,若没有,则等待至Prxd变为高电平后继续循环接收。
三、代码[关键部分]
static void Xuart_isr(void) interrupt 3
{ Pts = ~Pts;
if(++CNTisr == 3)
{
CNTisr = 0;
/***************************SEND***************************/
if(Fsend == SET)
{
switch(SEQsend)
{
// 起始位0:1bit
case 0:
Ptxd = LOW;
SEQsend = 1;
break;
// 数据位SEND
case 1:
if((*ptr_send) & (1 << CNTsend_i))
{
CNTbit1++;
Ptxd = HIGH;
}
else
{
Ptxd = LOW;
}
if(CNTsend_i >= 7)
{
CNTsend_i = 0;
switch(Xuart_config.bit_parity)
{
case NONE: // 无校验
SEQsend = 4;
break;
case ODD: // 奇校验
SEQsend = 2;
break;
case EVEN: // 偶校验
SEQsend = 3;
break;
default:
SEQsend = 4;
break;
}
}
else{
CNTsend_i++;
}
break;
// 奇校验
case 2:
if((CNTbit1 % 2) == 0) // 偶数个1
{
Ptxd = HIGH;
}
else
{
Ptxd = LOW;
}
CNTbit1 = 0;
SEQsend = 4;
break;
// 偶校验
case 3:
if((CNTbit1 % 2) == 0) // 偶数个1
{
Ptxd = LOW;
}
else
{
Ptxd = HIGH;
}
SEQsend = 4;
break;
// 停止位
case 4:
CNTbit1 = 0;
switch(Xuart_config.bit_stop)
{
case S_1BIT:
SEQsend = 5;
break;
case S_2BIT:
if(++CNTstbit == 2)
{
CNTstbit = 0;
SEQsend = 5;
}
break;
default:
//SEQsend = 5;
break;
}
Ptxd = HIGH;
break;
// 下一帧or结束
case 5:
if(CNTsend_j >= len_send - 1) // 发送完毕处理
{
CNTsend_j = 0;
Fsend_end = SET;
Fsend = CLR;
//TR1 = 0;
}
else
{
CNTsend_j++;
ptr_send++;
}
SEQsend = 0;
break;
default:
SEQsend = 0;
CNTsend_i = 0;
CNTsend_j = 0;
CNTbit1 = 0;
Fsend_end = SET;
Fsend = CLR;
TR1 = 0;
}
}
}
/**************************RECEIVE*************************/
if(Freceive == SET)
{
switch(SEQreceive)
{
// 起始位bit0
case 0:
if(Prxd == LOW) // 收到起始位bit0
{
SEQreceive = 1;
CNTrec = 0;
break;
}
break;
// 8bits_bit1数据
case 1:
if(++CNTrec == 4) // 第一次进来时为4个周期,提升容错率
{ // 其它时机进来为3个周期,保持同步性
CNTrec = 1;
if(Prxd == HIGH)
{
CNTbit1_++;
*ptr_receive |= (U8)(1 << CNTreceive_i);
}
else
{
*ptr_receive &= (U8)(~(1 << CNTreceive_i));
}
if(CNTreceive_i >= 7) // 1帧接收完毕
{
CNTreceive_i = 0;
CNTrec = 0;
switch(Xuart_config.bit_parity)
{
case NONE: // 无校验
SEQreceive = 4;
break;
case ODD: // 奇校验
SEQreceive = 2;
break;
case EVEN: // 偶校验
SEQreceive = 3;
break;
default:
SEQreceive = 4;
break;
}
}
else
{
CNTreceive_i++;
}
}
break;
// 奇校验
case 2:
if(++CNTrec == 3) // 3个周期进入
{
CNTrec = 0;
if((CNTbit1_ % 2) & (!Prxd)) // 校验正确
{
Freceive_parity = RIGHT;
}
else // 校验错误
{
Freceive_parity = ERROR;
}
CNTbit1_ = 0;
SEQreceive = 4;
break;
}
break;
// 偶校验
case 3:
if(++CNTrec == 3) // 3个周期进入
{
CNTrec = 0;
if((CNTbit1_ % 2) & (Prxd)) // 校验正确
{
Freceive_parity = RIGHT;
}
else // 校验错误
{
Freceive_parity = ERROR;
}
SEQreceive = 4;
break;
}
break;
// 帧结束
case 4:
if(Prxd == HIGH)
{
if(CNTreceive_j >= (len_receive - 1))
{
CNTreceive_j = 0;
Freceive = CLR;
Freceive_end = SET;
}
else
{
CNTreceive_j++;
ptr_receive++;
}
SEQreceive = 0;
break;
}
break;
default:
break;
}
}
}
1.示波器
2.串口助手回环测试