STC MCU STC15F204EA 模拟串口的示例程序:

例程使用 Timer0 中断,同步完成接受和发送的工作,属于双工首发。

串口将收到的信息在下一个发送周期转发出去;收发波特率可变,其它设置为 8 位数据位,无奇偶校验位,1 位停止位。

MCU 工作频率 FOSC 设置为 11.0592MHz,如果使用 12.000MHz,24.000MHz 时,会有频差。

1、头文件

#include "reg51.h"
#include "intrins.h"

2、如何选择设置波特率

波特率设定值计算公式:

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

3、串口选项设置

#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

4、变量与函数定义

#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].

5、详细实现

5.1、发送一个字节数据

void SendChar(unsigned char c)
{
   while (!bTransmitCompleted);     //如果发送未完成,则等待
   nDataToSend = c;                 //准备要发送的字符
   bTransmitCompleted = 0;
   bIsTransmitting = 1;             //标识正在发送标志
}

5.2、发送字符串数据

void SendStr(char *str)
{
   while (*str) SendChar(*str++); 
}

5.3、接收数据

unsigned char uGetChar(void)
{
   while (!bReceiveCompleted);      //如果数据接收未完成,则等待
   bReceiveCompleted = 0;           //允许开始接收下一个字符
   return nReceivedData;            //返回接收到的字符
}

5.4、接收字符串

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;
}

5.5、主业务逻辑

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:边收边发。
        
        //测试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;                             //总中断开关开启
}

其中 STC15F204EA的主频设置为 22.1184MHz,通讯波特率为 57600,8 位数据位,使用 1 或 2 位停止位,无校验的方式,可以顺利通讯。