找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 17704|回复: 5
打印 上一主题 下一主题
收起左侧

STC单片机官方的模拟串口程序解析(详细注释+图解)

  [复制链接]
跳转到指定楼层
楼主
ID:91442 发表于 2015-10-29 12:51 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
STC官方的模拟串口例程,看了很久,做了一点改动。但是核心还是他家的。
/*
  STC MCU STC15F204EA 模拟串口的示例程序
  原始代码来自 STCMCU的官方例程。
  
  ****************************************************************************
  *** 重要说明:本程序不适用于硬件串口的产品,仅为无硬件UART的产品使用。******
  ****************************************************************************
  
  本例程同时Timer0中断,同步完成接受和发送的工作,属于双工首发。
  示例中,由串口将收到的信息在下一个发送周期转发出去。
  
  示例中的收发波特率可变,其它设置为8位数据位,无奇偶校验位,1位停止位。
*/

//以下参数设置信息来自 STCMCU 官方例程。
//特别说明, MCU工作频率FOSC设置为11.0592MHz。如果使用12.000MHz,24.000时,会有频差。

#include "reg51.h"
#include "intrins.h"
//波特率设定值计算公式:BAUD = 65536 - FOSC/3/BAUDRATE/M (1T:M=1; 12T:M=12)
//为了确保通信质量,(FOSC/3/BAUDRATE) 必须大于 98,推荐大于110。
//这里每3次中断,做一次输出检查或输出。一般要求做连续16次采样,取其开始,中间,
//结束3个数据,如果稳定,则可以认为数据有效。
//3*16*2(?)=96,加上定时信号的不稳定性,在98以上合理。   ???
//因此,中断发生的频率是波特率信号的3倍以上。

//按照主频为11059200计算,如果选择信号比大于110,则最大波特率为33512.
//如果选择98,则为37616。选择38400的通讯速率都不能保证接收的正确性。
//因此,最可靠的数据接收波特率应该是19200。

//当时钟频率提升到33.1174MHz时,最大波特率为112848,依然小于115200. 使用115200时,
//波特率误差可能高达2%,出现数据差错。因此最大通讯速率选择 57600.

//#define BAUD  0xF400                  // 1200bps @ 11.0592MHz
//#define BAUD  0xFA00                  // 2400bps @ 11.0592MHz
//#define BAUD  0xFD00                  // 4800bps @ 11.0592MHz
//#define BAUD  0xFE80                  // 9600bps @ 11.0592MHz
//#define BAUD  0xFF40                  //19200bps @ 11.0592MHz
//#define BAUD  0xFFA0                  //38400bps @ 11.0592MHz

//#define BAUD  0xEC00                  // 1200bps @ 18.432MHz
//#define BAUD  0xF600                  // 2400bps @ 18.432MHz
//#define BAUD  0xFB00                  // 4800bps @ 18.432MHz
//#define BAUD  0xFD80                  // 9600bps @ 18.432MHz
//#define BAUD  0xFEC0                  //19200bps @ 18.432MHz
//#define BAUD  0xFF60                  //38400bps @ 18.432MHz

//#define BAUD  0xE800                  // 1200bps @ 22.1184MHz
//#define BAUD  0xF400                  // 2400bps @ 22.1184MHz
//#define BAUD  0xFA00                  // 4800bps @ 22.1184MHz
//#define BAUD  0xFD00                  // 9600bps @ 22.1184MHz
//#define BAUD  0xFE80                  //19200bps @ 22.1184MHz
//#define BAUD  0xFF40                  //38400bps @ 22.1184MHz
#define   BAUD  0xFF80                  //57600bps @ 22.1184MHz
//后续程序段中,对于不同的设置,可能有如下的条件编译指令
//在不同的通讯参数配置下,可能需要重新编译,以降低代码量。

#define DATALEN 8           //8位数据位
#define NOPARITY            //无校验位
//#define EVENPARITY       //偶校验
//#define ODDPARITY         //奇校验
#define STOP1BIT            //1位停止位
//#define STOP1P5BITS      //停止位1.5位
//#define STOP2BITS         //停止位2位
#ifdef EVENPARITY           //偶校验
#endif
#ifdef ODDPARITY            //奇校验
#endif
#ifdef NOPARITY             //无校验
#endif
#ifdef STOP1BIT             //停止位1位
#endif
#ifdef STOP1P5BITS          //停止位1.5位
#endif
#ifdef STOP2BITS            //停止位2位
#endif
#define YES 1
#define NO  0
sfr AUXR = 0x8E;
sbit RXB = P3^0;                          //UART RX 管脚
sbit TXB = P3^1;                          //UART TX 管脚
//数据发送相关的中间数据
unsigned char nDataToSend;                //要发送的字符
unsigned char nTmpTxData;                //发送过程的临时数据
unsigned char nTransmitCount;             //输出定时计数器。每三次输出一个数据。
unsigned char nTxBitCount;                //发送剩余位计数器
unsigned char pTx;                        //发送数据指针
bit tData;                                        //***新增加:准备发送的位数据(使用CY作为中间数据位不可靠,可能会被修改)
bit bIsTransmitting;
bit bTransmitCompleted;
//数据接收相关的中间数据
unsigned char nReceiveCount;              //输入电平多次采样计数器
//说明:这里默认的发送和接收过程都是在3次中断发生后采样输入信号或发送数据。
unsigned char nReceivedData;              //最终接收的数据
unsigned char nTmpRcvData;                //接收过程的临时数据(未完成的数据)
unsigned char nRxBitCount;                //接收剩余位计数器
unsigned char pRx;                        //接收数据指针
bit bIsReceiving;
bit bReceiveCompleted;
//数据接收缓冲区的定义
#define BUFFLEN 0x10            //字符缓冲区长度
#define CYCLEMASK 0x0F          //缓冲区循环掩模字码,=BUFFLEN-1。如长度为0x10,则掩模为0x0F,如果长度为0x20,则掩模为0x1F。
unsigned char buf[BUFFLEN];
void UartInit(void);
//基本输入输出函数,其它类型数据的处理,可以通过标准库的转换实现,如sprintf,atoi等。
void SendChar(unsigned char c);   //发送一个字符
void SendStr(char *str);          //发送一个字符串(长度不能超过BUFFLEN)
unsigned char uGetChar(void);     //获取一个字符
char *uGetStr(void);              //获取字符串,结果指向默认的通用缓冲区buf[BUFFLEN].

void SendChar(unsigned char c)
{
   while (!bTransmitCompleted);     //如果发送未完成,则等待
   nDataToSend = c;                 //准备要发送的字符
   bTransmitCompleted = 0;
   bIsTransmitting = 1;             //标识正在发送标志
}
void SendStr(char *str)
{
   while (*str) SendChar(*str++);
}
unsigned char uGetChar(void)
{
   while (!bReceiveCompleted);      //如果数据接收未完成,则等待
   bReceiveCompleted = 0;           //允许开始接收下一个字符
   return nReceivedData;            //返回接收到的字符
}
char *uGetStr(void)
{
    /*
        字符串接收停止的标志:\r\n(0xD 0xA),或者 '\0'?
    */
    unsigned char p;                //用数字标号
    unsigned char c;
   
    p = 0;                        //指针指向缓冲区开头
    c = uGetChar();
    while (c != 0x0) {
        buf[p] = c;
        p++;
        p &= CYCLEMASK;           //防止指针溢出,自动从头到尾循环,但是会丢失前面的字符。
        //if (p = CYCLEMASK) break;  //第二策略:如果溢出,则自动停止接收。未测试。
        c = uGetChar();
    }
    buf[p] = 0;                   //字符串结尾
    return buf;
}

void main(void)
{
    /*
      程序的初始状态准备。
    */
    bIsTransmitting = 0;       //尚未开始发送
    bIsReceiving = 0;           //尚未开始接受
    bTransmitCompleted = 1;     //缓冲区数据已发送完成(没数据可以发送)
    bReceiveCompleted = 0;      //尚未接收到数据
    nTransmitCount = 0;         //发送过程的T0中断次数计数器为0
    nReceiveCount = 0;          //接收过程的T0中断次数计数器为0
    pRx = 0;                    //接收指针指向起始位置
    pTx = 0;                   //发送指针指向起始位置
   
    UartInit();                 //设置中断条件(工作模式、计数值等,并打开中断)
    while (1)
    {                                   
        //SendChar(uGetChar());   //测试1:边收边发。具体波形见下图1.
        
        //测试2:按字符串进行收发,具体波形见下图2.
        uGetStr();
        SendStr(buf);
       /*
            以下程序段是 STC的官方例程。
            完成接收一个字符后,由Tx再次转发出去的动作。
            当没有数据输入时,也不会有数据输出。
            数据的输出最少比输入要晚一个字节。
        */
       /*
        if (bReceiveCompleted)          //如果数据接收完成
        {
            buf[pRx] = nReceivedData;   //将已接收的字符存入缓冲区中。
            pRx++;                      //接收指针指向下一个字符位置。
            pRx &= CYCLEMASK;           //修订为按缓冲区长度循环的指针。
            bReceiveCompleted = 0;      //可以再次接收下一个字符了。
        }
        if (bTransmitCompleted)         //如果数据发送完成     
        {
            if (pTx != pRx)             //只有在接收指针的位置和发送指针的位置不同时,才开始发送。
            {
                nDataToSend = buf[pTx];
                pTx++;
                pTx &= CYCLEMASK;
                bTransmitCompleted = 0;
                bIsTransmitting = 1;
            }
        }
        */
    }
}
//定时器0中断,用于接收和发送字符。
void Timer0ISR(void) interrupt 1 using 1
{
    //数据接收过程
    if (bIsReceiving)                             //如果正在接收过程中,则完成以下动作。
    {
        nReceiveCount--;                          //接收中断计数器值减1
        if (nReceiveCount == 0)                   //如果接收中断计数器值为0,则开始判定输入数据。
        {
            nReceiveCount = 3;                    //重置接收中断计数器的值为3。每发生3次中断,接收1位数据。
            nRxBitCount--;                        //当前要接受的数据剩余位计数器减1。说明接收完次数据后,还需要接收多少位。
            if (nRxBitCount == 0)                 //如果剩余位计数器为0,说明当前数据已经接收完成。
            {
                nReceivedData = nTmpRcvData;      //保存所接收的数据到nReceivedData中。
                bIsReceiving = 0;                 //停止继续接收。(下一个数据要重新开始对齐起始位)
                bReceiveCompleted = 1;            //数据接收完成标志置1。
            }
            else                                  //如果还有数据要接收
            {
                nTmpRcvData >>= 1;                //当前接收的临时数据右移一位,最高位自动补0。
                if (RXB) nTmpRcvData |= 0x80;    //如果当前输入位是1(RXB=1),则将临时数据的最高位修改为1。
            }
        }
    }
    else if (!RXB)                               //如果不是当前数据的接收过程,但是出现 RXB=0,则表明是数据的起始位,新的输入开始。
    {
        bIsReceiving = 1;                         //设置标志,表明开始新的数据接收过程
        nReceiveCount = 4;                        //因为停止位已经做了响应,后续的判断点设置在3次判定点的中间,
                                                  //因此在接受到起始位以后,需要4次中断才开始下一个数据判定。

        nRxBitCount = 9;                         //剩余未接收的数据位数(8个数据位 + 0个校验位 + 1个停止位,共9位)
                                                                                //****注意:没有编写2个停止位的程序。
    }
    //数据的发送过程:每次中断均可发生。因此数据的发送和接收几乎是同时进行。
    //在main()例程中,数据的发送是等待缓冲区中至少一个数据接收完成后,才开始发送动作,因此延迟一个字符的时间。
    //如果不考虑判定一个完整字符,可以同步进行接收和发送的动作(直接转发,不解释,也不判定),延迟时间是以上指令的执行时间。
   
    nTransmitCount--;                             //发送中断计数器减1
    if (nTransmitCount == 0)                      //如果发送中断计数器为0,则开始发送动作。
    {
        nTransmitCount = 3;                      //重设发送中断计数器的初值,每3次做一个发送动作。
        if (bIsTransmitting)                      //如果当前字符尚未发送完成,正在发送过程中
        {
            if (nTxBitCount == 0)                 //如果尚未发送的位计数值是0,说明当前数据还未开始发送(此变量的含义跟接收过程的剩余位计数器意义不同)。
            {
                TXB = 0;                          //发送起始位
                nTmpTxData = nDataToSend;         //加在要发送的数据(把 nDataToSend 存放到 nTmpTxData 中,实际发送的是 nTmpTxData。)
#ifdef STOP1BIT               
                nTxBitCount = 9;                  //初始化剩余位计数器(这里是8个数据位 + 0个奇偶校验位 + 1 个停止位)
#endif
#ifdef STOP2BITS               
                nTxBitCount = 10;                //初始化剩余位计数器(这里是8个数据位 + 0个奇偶校验位 + 2 个停止位)
#endif
               
            }
            else                                  //如果剩余位计数值不等于0,说明已经有数据在发送中
            {
                tData = (nTmpTxData & 0x1);        //****注意:此处做了修改,防止CY被后续程序改变而出错。获取要发送数据的最低位
                nTmpTxData >>= 1;                  //数据右移到 CY 中,最高位自动补 0。
                                                   //这里要用到特殊位CY,最好查看汇编后的程序,否则可能出错。增加位变量 tData后则无此影响。
                nTxBitCount--;                     //剩余位计数器值减1。
#ifdef STOP1BIT
                if (nTxBitCount == 0)             //如果已经发送完成
                {
                    TXB = 1;                       //发送1位停止位。如果是2位停止位,还需要一次。如果是1.5位停止位,如何做?  
                    bIsTransmitting = 0;           //表示当前数据的所有位发送完成
                    bTransmitCompleted = 1;        //表示整个字符的传送完成,设置标志为1。
                }
#endif
#ifdef STOP2BITS
                if (nTxBitCount < 2 )               //这个操作是否会影响到 CY的值?
                {
                    TXB = 1;                       //发送停止位。如果是2位停止位,还需要一次。如果是1.5位停止位,如何做?  
                   if (nTxBitCount == 0)          //如果已经发送完成
                    {
                        bIsTransmitting = 0;       //表示当前数据的所有位发送完成
                        bTransmitCompleted = 1;    //表示整个字符的传送完成,设置标志为1。
                    }
                }
#endif                   
                else                               //剩余位计数器不为0,还需要继续发送数据。
                {
                    //TXB = CY;                    //TX的数据来自 CY 寄存器,是循环右移指令的结果。
                    TXB = tData;                   //改用标准数据
                }
            }
        }
    }
}
//定时器0初始化,用于虚拟串口初始化
void UartInit(void)
{
    TMOD = 0x00;                        //Timer0设置为16位自动重载模式。
    AUXR = 0x80;                        //Timer0工作在1T模式
    TL0 = BAUD;                         //Timer0重载值的低8位
    TH0 = BAUD>>8;                      //Timer0重载值得高8位
    TR0 = 1;                            //tiemr0启动
    ET0 = 1;                            //允许Timer0中断
    PT0 = 1;                            //Timer0中断优先级为高
    EA = 1;                             //总中断开关开启
}
以上程序使用 Keil C51 V9.52 编译通过。其中 STC15F204EA的主频设置为22.1184MHz,通讯波特率为 57600,8位数据位,使用1或2位停止位,无校验的方式,可以顺利通讯。

使用串口调试助手,从PC向单片机发送数据(下图中通道1),由单片机在接收后进行发送(下图中通道0)。对两种工作方式进行测试:
1. 使用边收边发时的波形如下(SendChar(uGetChar());):

图1. 接收一个字符后就进行转发的例子
2. 使用整个字符串(以字符'\0'结尾)接收完成后再进行发送的波形如下(uGetStr(); SendStr(buf);):

图2. 接收完一个字符串后再转发的例子(去掉了末尾的'\0')
根据实际情况,可以选择不同的接收方式。

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏13 分享淘帖 顶1 踩
回复

使用道具 举报

沙发
ID:343681 发表于 2018-6-2 19:59 | 只看该作者
感谢楼主  很有帮助
回复

使用道具 举报

板凳
ID:386669 发表于 2018-8-18 17:08 | 只看该作者
SPI和IIC都好弄,只有Uart我一直嫌麻烦,只用芯片自带的资源,刚看到这个帖子,觉得太有用处了,留名以后看。
回复

使用道具 举报

地板
ID:184868 发表于 2020-1-9 14:36 | 只看该作者
感谢楼主  很有帮助
回复

使用道具 举报

5#
ID:67274 发表于 2020-3-20 22:57 | 只看该作者
STC范例程序就是这个,理解起来好绕脑。
回复

使用道具 举报

6#
ID:584195 发表于 2022-4-12 13:02 | 只看该作者
谢谢楼主分享,非常感谢,我也是因为串口不够用,所以想用iO摸拟。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表