《匠人手记》推荐网上购书渠道:
互动出版网(china-pub)购书入口   >>>
当当网(dangdang)购书入口   >>>
卓越亚马逊网 购书入口   >>>
淘宝网(taobao)购书入口   >>>
更多购书渠道……   >>> 

设为首页加入收藏联系匠人管理入口21IC首页21IC博客21IC社区侃单片机回复的贴参与的贴

天气预报
百宝日历
载入中...

百宝专栏

载入中...
最新货色

载入中...

粉丝评论

载入中...

载入中...



百宝信息

载入中...

百宝流量

(2006-07-01开始)


匠人手记

 匠人观点: 好记性不如烂笔头  
 黑色幽默:三鹿门——后世畅想

PIC 单片机软件异步串行口实现技巧
程序匠人 发表于 2007-6-29 13:16:00  阅读全文 | 回复(1) | 引用通告 | 编辑

在用单片机开发各种嵌入式应用系统时,异步串行通信是经常要用到的一种通信模式,很多应用中还要求实现多路异步串行通信。大家平时熟悉的各种厂家的单片机,绝大部分片上只提供一个硬件UART模块,利用它可以方便实现一路串行通讯。PIC系列单片机也不例外,在其丰富的产品家族成员中,除高端系列(PIC17/18)一些型号片上带有两路硬件UART模块外,其它大部分型号片上只有一路UART,一些低端廉价的PIC单片机甚至还不带硬件 UART。为了提高系统的性能价格比,就要求设计工程师用软件增加实现一路或多路异步串行通信。很多工程师对用软件实现的UART在可靠性和效率方面持怀疑态度,其实关键问题是看软件采用何种方式来实现可靠的UART功能。

  在讨论具体实现方式前,我们先来简单回顾一下异步串行通信的格式定义。发送一个完整的字节信息,必须有“起始位”、“若干数据位”、“奇偶校验位”和“停止位”;必须定义每位信息的时间宽度——每秒发送的信息位个数,即为“波特率”。单片机系统中常用的波特率从300~19 200 b/s。当波特率为1200b/s时,每个信息位的时间宽度为 1/1200≈833μs;无数据通信时,数据线空闲状态应该是高电平,“起始位”为低电平,数据位低位先发且后跟奇偶校验位(若有),“停止位”为高电平,如图1所示。

 
图1

  按图1最基本的异步串行通信时序,软件实现UART在不同架构的单片机上有多种方法。其中数据接收是关键,因异步通信没有可参照的时钟信号,发送方随时都可能发送数据,任何时刻串行数据到来时,系统都应该及时准确地接收。比较而言,本机发送串行数据相对容易,只要对发送出去的电平做持续时间的定时即可。按不同的接收技巧并针对PIC单片机的特点,这里介绍两种常用且十分可靠的方法。

1  三倍速采样法

  三倍速采样法顾名思义就是以三倍于波特率的频率对接收引脚Rx进行采样,保证检测到“起始位”,又可以调整采样的时间间隔;将有效数据位的采样点控制在码元的中间1/3处,最大限度地减少误码,提高接收的准确性。我们把图1的起始位和部分数据位放大,如图2所示,把每个信息位分成三等份,每等份的时间宽度设为ts,以方便分析。

 
图2

  以三倍频对信息位进行采样时,每个信息位都将可能被采样到三次。当处于空闲状态并检测起始位时,最早检测到起始位低电平的时刻必将落在S0阴影区,虽然每次具体的采样点会在此S0阴影区随机变化。检测到起始位低电平后,间隔4×ts时间,正好是第一位数据位的中间1/3处(图2中Ds阴影区)。此后的数据位、校验位和停止位的采样间隔都是3×ts,所有采样点均落在码元的中间1/3处,采样数据最可靠。

  PIC单片机采用此法实现软件UART时,硬件上只要任意定义两个I/O引脚,分别初始化成输入(串行数据接收)和输出(串行数据发送)即可;软件上只要实现定时采样,定时时间间隔在中档以上有中断机制的单片机上可以用不同的定时器(TMR0、TMR1、TMR2等)通过定时中断实现,在低档无中断的PIC单片机上可以控制每次主循环所耗的时间来实现。对于1200 b/s波特率,码元宽度为833μs,采样时间间隔即为278μs。整个串行接收或发送是一个过程控制问题,用状态机方式实现最为高效简易。图3给出了串行接收的参考状态机转移过程。

 
图3

  本刊网络补充版中,介绍了简单的C语言参考源程序。此段程序实现1200b/s全双工串行通信,1位起始位,8位数据位,无校验位,1位停止位,没有帧错误等判别。编译环境为HITECH-PICC编译器V8.00PL4或更高版。

  在网络补充版的程序中,关键部分是TMR0的中断服务。TMR0每隔278μs左右中断一次,TMR0的中断响应即为软件UART接收和发送全双工通信过程的实现。通过Hitech-PICC高效的代码编译后,约有150条单字指令代码,整个中断服务平均用约35个指令周期,即实现一路软件 UART在4 MHz工作频率下占用MCU约12%的运行带宽。理论上,只要保证MCU留有足够的运行带宽给其它任务,在此中断服务程序内把接收和发送的代码再复制一份或多份(数据结构独立),即可实现多路软件UART。当然,如果每路的波特率不同,采样频率必须是最高波特率的三倍。不同波特率的采样点间隔独立调整。

  此法最大的好处是软硬件配置极其灵活:接收发送的引脚可以任意定义;采样定时可以用不同的定时器实现;利用同一个定时采样可以方便地实现多路软件UART等。缺点是:不管有无数据通信,始终占用MCU运行带宽;串行通信的波特率不能太高,4 MHz工作的PIC单片机一般能实现2400bps的全双工通信。当然,可以通过提高MCU的振荡频率来实现高波特率通信,当PIC单片机工作在20 MHz时,实现9600b/s绰绰有余。

2  起始位中断捕捉、定时采样法

  实现此法的硬件条件是PIC单片机有外部脉冲下降沿中断触发功能,在中档以上PIC单片机中有RB0/INT外部中断脚,CCP1/CCP2脉冲沿捕捉脚,PORTB的第4/5/6/7电平变化中断脚等都可以满足。另外需配备一个定时器,以定时中断方式对接收码元正确采样,或发送串行数据流。其关键的异步接收工作原理简介如图4所示。

 
图4

  设串行数据位宽度为td。起始位到来时刻(图4 A点)的下降沿触发一个中断并立即响应该中断。在此中断服务中立即关闭本中断使能位(后续的数据流变化无需触发中断),开启定时器,使其在 1.5td后产生定时中断,用于采样第一个数据位(确保S0采样点落在数据位的中心位置处);在处理下降沿中断服务的最后,再检测接收端是否还是0电平,以区分窄脉冲干扰。在S0点采样到第一个数据位后的所有采样间隔都是1td,直到收到停止位后,关闭定时器中断,重新开放下降沿捕捉中断,准备接收下一个字节。

  异步数据接收和发送的状态机控制流程,除了起始位判断和定时时间参数设置与前述方式不同外,其它几乎一样,此处不再重复。

  此法的好处是可以实现较高的通信波特率。对于通信不是很频繁的系统,此软件UART几乎不耗MCU运行带宽,9600b/s接收或发送在4 MHz运行的PIC单片机上即可轻松实现;另外,由于下降沿中断可以唤醒处于睡眠的单片机,故极易实现通信唤醒的功能。缺点是不能全双工通信(除非另外单独用一个定时器实现发送定时),异步接收的引脚必须有下降沿触发中断的能力。

  上面介绍的两种方法在实际产品设计中都得到了很好的验证,最典型的是红外线自动抄表系统。该系统要求收发均为38 kHz红外调制,串行数据1 200bps半双工通讯。用软件实现此UART,并充分利用PIC单片机CCP模块的脉宽调制PWM输出38 kHz载波时,在单片机外除了一个一体化红外接收头和一个红外发射二极管,无需其它任何外围器件,即可完成所有设计要求,最大程度地减化了硬件设计,降低了成本,提高了系统的可靠性和性能价格比。

  以上的侧重点是基本原理的介绍,希望对大家有所帮助。在接收数据的可靠性处理方面没有太多涉及。有兴趣者可以在采样时刻到来时对数据做多次采样,以消除干扰误码;或有其它处理技巧,欢迎和笔者作进一步交流。

简单的C语言参源程序如下:

#i nclude <pic.h>   //PIC单片机通用头文件,实际型号为16F84

__CONFIG(XT | PROTECT | PWRTEN | WDTEN);//程序中设定配置信息

//===========================
//定义软件UART发送/接收引脚
//===========================
#define      RX_PIN  RB0  //串行接收脚
#define      TX_PIN  RB1  //串行发送脚

//===========================
//定义软件UART状态机控制字
//===========================
#define   RS_IDLE        0 //空闲
#define   RS_DATA_BIT    1 //数据位
#define   RS_STOP_BIT    2 //停止位
#define   RS_STOP_END    3 //停止位结束

//===========================
//定义软件UART采样频率
//===========================
#define   OSC_FREQ       4000   //单片机工作频率(单位:KHz)
#define   BAUDRATE       1200 //通讯波特率
#define   TMR0PRE        2 //TMR0预分频比1:2
#define   TMR0CONST      117    //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,实现下次定时中断
  }
}

看《匠人手记》,与匠人同行!北航出版,正在热卖!

  • 标签:PIC 串口 
  • Re:PIC 单片机软件异步串行口实现技巧
    深圳电子技术网(游客)发表评论于2007-6-29 22:53:00  个人主页 | 引用 | 返回 | 删除 | 回复

    深圳电子技术网(游客)

    看《匠人手记》,与匠人同行!北航出版,正在热卖!

    发表评论:
    载入中...

    芯片专题

    器件专题

    软件专题

    硬件专题

    综合专题

    项目专题

    原创专题

    器件检测
    LCD LED
    按键 触摸键
    E2PROM
    电池 电机
    电阻 电容 电感

    指令系统
    软件算法
    编程规范
    滤波算法
    串行通讯

    PCB设计
    I2C PWM
    红外遥控
    充电技术
    中断 ADC 

    匠人手记
    匠人夜话
    网络心路
    一周热点串烧
    从零开始玩PIC
    DIY旋转时钟

    广告5号位 [投放]


    学习板、开发板、编程器、下载器、仿真器(查看详情……)

    广告3号位 [投放]

    站内搜索


    站外搜索


    百度  google
    mp3  歌词 
    图片  FLASH 
    知道  文档
    新闻  词典 
    地图  mp3 
    软件  天网 
    雅虎  爱问 
    搜狗  讯雷 
    网讯  华军 
    天空 

    21IC器件搜索
    百宝箱分站
  • 《匠人的百宝箱》21IC站
  • 《匠人的百宝箱》21IC笔记团队
  • 《匠人手记》21IC书友会
  • 《匠人的百宝箱》MCUBLOG站
  • 《匠人的百宝箱》MCUBLOG笔记团队
  • 《匠人的百宝箱》EDN站
  • 《匠人手记》EDN书友会
  • 《匠人的百宝箱》与非网站
  • 《匠人的百宝箱》新浪站
  • 《匠人的百宝箱》百度站
  • 《匠人的百宝箱》网易126站
  • 《匠人的百宝箱》网易163站
  • 《匠人的百宝箱》互动出版网站
  • 广告4号位 [投放]

     
     

    匠人原创

    往日酷贴

     
     
     

    大千八卦

    友情连接

    新浪新闻:
    新浪财经:
    AK58新闻:
    新浪股票:
    新浪股票:
    证券之星:

     [更多酷站连接]

     

     

    [欢迎交换连接]

    [百宝箱之与非门分舵]

    [电脑圈圈的家当]

    [IC921的博客]

    [柔月阁]

    [八楼的呼吸]

    [hotpower 的水潭]

    [xwj的文君阁]

    [所长的BLOG]

    [阿摆手记]

    [电子伙伴]

    [unaided的笔记]

    [小飞的笔记]

    [单片机开发联盟]

    [网址之家]

    [好东西网址大全]

    [美萍中文精选]

    [数字电视之家]

    [SMARTCODE电子书斋]

    [软件开发之窗]

    [Armoric]

    [我爱研发网]

    [infernal的笔记]

    [雄鹰的空中加油站]

    [SunK]

    [逍遥电子]

    [ningpanda的博客]

    [C-Design]

    [一网见天下]

    [海边淘沙]

    [嵌入式365]

    [水牛的仓库]

    [股剩是怎样炼成的]

    [PIC论坛]

    [ICC AVR开发网]

    [中国高校自动化网]

     

     

     

    MCU博客-中国电子工程师博客网 

    大学生电子网 

     

     

     

     

     

    !!! 《匠人的百宝箱》 !!!