嵌入式微系统
上QQ阅读APP看书,第一时间看更新

1.3.6 串口通信

串口是MCU51最常用的一种通信方式接口,常用于下载程序、追踪调试信息、扩展HMI串口屏、与PC等其他设备通信。TXD为发送,RXD为接收。Keil自带的软件仿真器提供了一个Serial#1的串口对话窗口(图1-19),基于这个窗口可以输入输出串口信息。

需要注意的是,该仿真串口界面基于字符,不是基于字节,比如说在里面输入1,实际是输入字符'1',对应的编码为0x31,字符与编码有一个映射表,叫ASCII码,常用ASCII码包括128个字符,PC键盘常用的按键都包括在这128个ASCII码中,包括了英文中常用的数字、字母、符号,比如'0'是0x30,空格是0x20。字母'A'是0x41,'a'就是0x61。字符串也是由ASCII码字符组成的。此外,Serial支持中文显示,Keil编译的时候支持国标GB-2312码,它是一种双字节编码,双字节16 bit,理论可以记录65536个汉字,已足够用了,比如“我们”,GB-2312码就是“CE D2 C3 C7”。Serial#1显示也是GB-2312码,不过Serial#1对中文支持不好,新显示的是乱码,需要刷新一下才能正确显示。关于ASCII和一些编码,后续详细介绍。

图1-19 Keil仿真串口界面

Keil-C51的stdio.h库文件提供了printf函数,这是一个很出名的字符串函数,可以实现字符串、变量等输出,通过串口传送到PC上。一些不带屏幕显示的最小系统板子,就是利用串口作为输入输出对象,因为MCU51串口可以跟PC的超级终端或者串口工具连接起来,在PC上显示作为界面。

printf函数是一个标准函数,考虑到MCU51底层硬件和实现有所不同,该函数提供了一个跟硬件相关的回调函数putchar,我们需要把跟硬件相关的发送信息代码填入其中,完善putchar函数。printf等于把要打印的字符串或数据,根据参数转换成具体的字符,存放在RAM中,之后再由putchar函数通过串口发送出去。putchar函数代码如下。

代码清单1-20:putchar代码

char putchar (char c)
{
    if (c == '\n')                          // 检测换行符'\n',若是,需插入回车符'\r'
    {
        ES = 0;                             // 关闭串口中断
        SBUF = '\r';                        // 发送回车符
        while(!TI); TI = 0;                 // 发送较慢,等待发送完,清除发送标记
        ES = 1;                             // 开启串口中断,以响应串口接收中断
    }
    ES = 0;
    SBUF = c;                               // 发送字符
    while(!TI); TI = 0;
    ES = 1;
    return(true);                           // 必须要返回true
}

程序中'\n','\r'为换行和回车字符,因为这两个字符没有对应的显示字母,所以一般用转意符'\'加字母表示,都属于ASCII符,'\r'编码为0x0D,'\n'编码为0x0A,十六进制。一般printf打印字符串时,往往以'\n'结束,而因为Windows系统对于文本都是以'\r'回车结束,再'\n'换行,所以要插入一个'\r',这是一个习惯问题,需要注意的是Linux是没有'\r',直接以'\n'结束并且换行,所以Windows下的文本在Linux下打开,经常看到有一个小黑块,因为无法显示。

因为串口通信中的每个字节是以比特流的形式串行输出,一个字符串再以多个字符一个个的发送,所以速度比较慢,占用时间长。而MCU51的RAM不多,并且实际MCU51项目功能比较单一,所以采用了最常规的等待发送方式。在容量大的RAM系统中,可以采用先把数据打印到RAM中,之后利用串口发送中断或者是系统节拍一个个的发送走,这样不占用系统等待资源。MS采用的就是这种做法。

需要注意的是,printf函数很占ROM和RAM的空间,程序加入这个函数,代码起码增加1K。其次还要注意,printf打印数据时,是按16 bit数据类型打印的,不是8 bit数据类型,碰到8 bit数据类型,要强制转换成16 bit,如printf(“MS=%d”,(short)charData),否则容易引起未知错误。同时不建议打印太长内容,否则容易导致RAM溢出。总之,库不是理想的,需要了解它的各种特性,所以用库要谨慎。

printf虽然功能比较多,但有些自己定义的串口通信协议,是基于字节流传输,而不是基于字符串传输的,所以需要增加一个支持字节流传输的串口函数PutString,而支持字节流传输的函数,一般也适合字符串传输,这样可以取代部分printf函数功能,在一些串口应用不复杂,资源又比较紧张的场合,可以直接用PutString函数取代printf。PutString函数代码如下。

代码清单1-21:PutString代码

void PutString(string string, Byte sum) 
{
    if (sum)                                // 判断是否按字符串发送,0为字符串
    {
        while (sum--)                       // 按字节流发送
            PutByte(*string++);
    }
    else                                    // 按字符串发送,直到检测到0x00结束
    {
        while (*string) 
        {
            if (*string == '\n') 
                PutByte('\r');
            PutByte(*string++);
        }
    }
}

参数sum判断是按字符串发送方式,还是按字节流发送方式。当sum=0时,一直发送数据,直到碰到字符串结束符’\0’=0x00,这个效果等价于printf打印字符串功能。当sum>0时,按字节流发送,sum是多少,就连续发送多少个字节。当串口应用于串口通信的时候,需要注意几点:

1)串口速度不要设置得太高,用之前一定要清空接收寄存器,防止乱码。

2)字节与字节之间发送等待间隔时间,防止错帧,有条件可等待一个字节的发送时间,这样让串口接收复位,降低出现错帧概率。

3)因为串口是一个字节流,往往需要标志一个帧的开始与结束,一种常用的做法是用ASCII码作为通信手段,STX(0x02)作为开始,ETX(0x03)作为结束,中间内容都用ASCII,比如发送数字0x30,则要发送'3'和'0',即0x33和0x30,此外内容中要避开STX和ETX这两个字符。另外一种常用超时来作为开始与结束,比如超过某一个设定的时间没有接收到串口数据,就认为结束,内容可以发送任意的字节码。基于串口的Modbus协议中有两种通信方式,就是上述的这两种,前者叫ASC方式,后者叫RTU方式。