《PIC单片机软件异步串行口实现技巧》
简单的C语言参源程序如下:
#include
__CONFIG(XT | PROTECT | PWRTEN | WDTEN);//程序中设定配置信息
//===========================
//定义软件UART发送/接收引脚
//===========================
#defineRX_PINRB0//串行接收脚
#defineTX_PINRB1//串行发送脚
//===========================
//定义软件UART状态机控制字
//===========================
#defineRS_IDLE0 //空闲
#defineRS_DATA_BIT1 //数据位
#defineRS_STOP_BIT2 //停止位
#defineRS_STOP_END3 //停止位结束
//===========================
//定义软件UART采样频率
//===========================
#defineOSC_FREQ4000//单片机工作频率(单位:KHz)
#defineBAUDRATE1200 //通讯波特率
#defineTMR0PRE2 //TMR0预分频比1:2
#defineTMR0CONST117//256 - OSC_FREQ*1000/TMR0PRE/4/(BAUDRATE*3)
//===================================================================
//定义函数类型
void UART_Out(void);
void UART_In(void);
//===================================================================
//定义位变量
bit rsTxBusy; //串行发送忙标志
//定义串行发送的数据结构
struct {
unsigned char state;//发送状态机控制单元
unsigned char sliceCount; //波特率控制
unsigned char shiftBuff; //字节数据发送移位寄存器
unsigned char shiftCount; //字节数据发送移位计数器
} rsTx;
//定义串行接收的数据结构
struct {
unsigned char state;//接收状态机控制单元
unsigned char sliceCount; //波特率(采样点)控制
unsigned char shiftBuff; //字节数据接收移位寄存器
unsigned char shiftCount; //字节数据接收移位计数器
unsigned char dataBuff[8]; //接收数据FIFO缓冲队列
unsigned char putPtr, getPtr;//FIFO队列存放/读取指针
} rsRx;
//用于串行发送的变量定义
unsigned char outBuff[10]; //发送队列
unsigned char outPtr,//发送队列指针
outTotal,//发送的字节总数
chkSum;//发送的校验码
//=====================================================================
//主程序
//=====================================================================
void main(void)
{
PORTA= 0;
PORTB= 0;
TRISB=0b01; //输入输出定义
OPTION = 0b10000000;//TMR0选择内部指令周期计数
//TMR0预分频 1:2
rsRx.state= RS_IDLE; //初始化接收状态
rsTxBusy= 0;//发送空闲
INTCON= 0b00100000; //T0IE使能
GIE = 1;//打开中断
while(1) {//程序主循环
asm("clrwdt");//清看门狗
UART_In();//接收串行数据
UART_Out();//发送串行数据
}
}
//=====================================================================
//查询在接收FIFO队列中是否有新数据到
//然后解读数据
//=====================================================================
void UART_In(void)
{
unsigned char data1;
if (rsRx.putPtr==rsRx.getPtr)
return; //如果读取和存放的指针相同,则队列为空
data1 = rsRx.dataBuff[rsRx.getPtr]; //读取1个数据字节
rsRx.getPtr++;//调整读取指针到下一位置
rsRx.getPtr &= 0x07;//考虑环形队列回绕
//此处为数据解读分析,略
}
//=====================================================================
//软件UART发送数据
//数据在outBuff中,outTotal为总字节数
//=====================================================================
void UART_Out(void)
{
if (rsTxBusy==1)
return;//正处于移位发送忙
//可以发送新数据
if (outTotal) {//如果有字节要发送
rsTx.shiftBuff = outBuff[outPtr++]; //取字节到发送移位寄存器
rsTxBusy = 1;//置发送忙标志,启动发送
outTotal--;//字节计数器减1
}
}
//===================================================================
//中断服务程序
//===================================================================
void interrupt isr(void)
{
//利用TMR0 定时中断实现全双工软件UART
if (T0IE && T0IF) {
T0IF = 0;//清TMR0中断标志
//实现串行接收 RX 状态机控制
switch (rsRx.state) {//判当前接收状态
case RS_IDLE:
//当前状态为"空闲", 唯一要做的就是判"起始位"出现
if (RX_PIN==0) {//如果接收到低电平
rsRx.sliceCount = 4; //准备4*Ts时间间隔
rsRx.shiftCount = 8; //总共接收8位数据位
//改变此数值可以实现任意位数的数据接收
rsRx.state = RS_DATA_BIT; //切换到数据位接收状态
}
break;
case RS_DATA_BIT:
//当前状态为"数据接收"
if (--rsRx.sliceCount==0) {//等采样时间到
rsRx.shiftBuff >>= 1;//接收移位寄存器右移1位
if (RX_PIN) rsRx.shiftBuff|=0x80; //保存最新收到的数据位
rsRx.sliceCount = 3;//下次采样间隔为3*Ts
if (--rsRx.shiftCount==0) { //已经收到8位数据位?
//保存数据字节到FIFO缓冲队列
rsRx.dataBuff[rsRx.putPtr] = rsRx.shiftBuff;
//队列存放指针调整,最多8个字节缓冲
rsRx.putPtr = (rsRx.putPtr+1) & 0x07;
//转去下个状态,判停止位
rsRx.state = RS_STOP_BIT;
}
}
break;
case RS_STOP_BIT:
//当前状态为停止位判别(此程序没有判别)
if (--rsRx.sliceCount==0) { //等采样时间到
//此处可以判RX_PIN是否为1
rsRx.state = RS_IDLE; //复位接收过程
}
break;
default:
//异常处理
rsRx.state = RS_IDLE; //复位接收过程
}
//实现串行发送 TX 状态机控制
switch (rsTx.state) {//判当前发送状态
case RS_IDLE://发送起始位
if (rsTxBusy) {//如果发送启动
TX_PIN = 0;//发出起始位低电平
rsTx.sliceCount = 3; //持续时间3*Ts
rsTx.shiftCount = 8; //数据位数为8位
rsTx.state = RS_DATA_BIT; //转去下一状态
} else TX_PIN = 1;//如果没有数据发送则保证数据线为空闲
break;
case RS_DATA_BIT://发送8位数据位
if (--rsTx.sliceCount==0) { //码元宽度定时到
if (rsTx.shiftBuff & 0x01)//看数据位是0还是1
TX_PIN = 1;//发送1
else
TX_PIN = 0;//发送0
rsTx.shiftBuff >>= 1; //准备下次数据位发送
rsTx.sliceCount = 3; //数据位宽度为3*Ts
if (--rsTx.shiftCount==0) {
//8位数据位发送结束,转去发送停止位
rsTx.state = RS_STOP_BIT;
}
}
break;
case RS_STOP_BIT://发送1位停止位
if (--rsTx.sliceCount==0) { //等数据位发送结束
TX_PIN = 1;//发送停止位高电平
rsTx.sliceCount = 9; //持续宽度9*Ts
//额外考虑字节连续发送的时间间隔
rsTx.state = RS_STOP_END; //转停止位宽度延时
}
break;
case RS_STOP_END://等待停止位时间宽度结束
if (--rsTx.sliceCount==0) { //如果停止位结束时间到
rsTxBusy = 0;//一个字节发送过程结束,清发送忙标志
rsTx.state = RS_IDLE; //复位发送过程
}
break;
default:
//异常处理
rsTx.state = RS_IDLE; //复位发送过程
}
TMR0 += TMR0CONST;//重载TMR0,实现下次定时中断
}
}