4.3 串口通信

4.3.1 通信的概念

通信一词按照传统的理解就是信息的传输与交换。

对于单片机来说,通信则与传感器、存储芯片、外围控制芯片等技术紧密结合,成为整个单片机系统的“神经中枢”;没有通信,单片机所实现的功能仅仅局限于单片机本身,就无法通过其它设备获得有用信息,也无法将自己产生的信息告诉其它设备。如果单片机通信没处理好的话,它和外围器件的合作程度就受到限制,最终整个系统也无法完成强大的功能,由此可见单片机通信技术的重要性。

 UART(Universal Asynchronous Receiver/Transmitter,即通用异步收发器)串行通信是单片机最常用的一种通信技术,通常用于单片机和电脑之间以及单片机和单片机之间的通信。

4.3.2 串口通信介绍

串口通信是按照位(bit)发送和接收,串口可以在使用一根线发送数据的同时用另一根线接收数据;这种通信方式使用的数据线少,在远距离通信中可以节约通信成本,但其传输速度比并行传输低。

串口是计算机上一种非常通用的设备通信协议,大多数计算机(不包括笔记本电脑,主要是台式主机)包含两个基于RS-232的串口。串口同时也是仪器仪表设备通用的通信协议。

51单片机开发之串口通信_java

图4-3-1 标准RS232串口

 

上面图中的串行接口叫做 RS232 接口,由于现在笔记本电脑都不带这种 9 针串口了,所以和单片机通信越来越趋向于使用USB协议虚拟的串口(就是使用USB转串口协议芯片,实现串口与USB协议互转,比如:CH340)。 标准的RS232接口里,2号引脚是接收数据口RXD,3号引脚是发送数据TXD,对于 RS232 标准来说,它的TXD 和 RXD 的电压, -3V~-15V 电压代表是 1, +3~+15V 电压代表是 0。因此电脑的 9 针 RS232串口是不能和单片机直接连接的,需要用一个电平转换芯片 MAX232 来完成,单片机上的电压是TTL标准,TTL电平信号规定,+5V等价于逻辑“1”,0V等价于逻辑“0”。

STC90C51RC/RD+系列单片机串口通信对应的专用管脚是P3.0/RxDP3.1/TxD,由它们组成的通信接口就叫做串行接口,简称串口

51单片机开发之串口通信_java_02

图4-3-2 两个单片机之间串口通信示意图

 

图中, GND 表示单片机系统电源的参考地, TXD 是串行发送引脚, RXD 是串行接收引脚。两个单片机之间要通信,首先电源基准得一样,所以要把两个单片机的 GND 相互连接起来,然后单片机1的TXD引脚接到单片机2 的 RXD 引脚上,即此路为单片机 1 发送而单片机 2 接收的通道,单片机 1 的 RXD 引脚接到单片机 2 的 TXD 引脚上,即此路为单片机 2 发送而单片机 1 接收的通道。这个示意图就体现了两个单片机相互收发信息的过程。

当单片机 1 想给单片机 2 发送数据时,比如发送一个 0xE4 这个数据,用二进制形式表示就是 0b11100100,在 UART 通信过程中,是低位先发,高位后发的原则,那么就让 TXD首先拉低电平,持续一段时间,发送一位 0,然后继续拉低,再持续一段时间,又发送了一位 0,然后拉高电平,持续一段时间,发了一位 1……一直到把 8 位二进制数字 0b11100100全部发送完毕。

这里就涉及到了一个问题,就是持续的这“一段时间”到底是多久?由此便引入了通信中的一个重要概念——波特率,也叫做比特率。

波特率就是发送二进制数据位的速率,习惯上用 baud 表示,即发送一位二进制数据的持续时间=1/baud。在通信之前,单片机 1 和单片机 2 首先都要明确的约定好它们之间的通信波特率,必须保持一致,收发双方才能正常实现通信。

约定好速度后,还要考虑第二个问题,数据什么时候是起始,什么时候是结束?不管是提前接收还是延迟接收,数据都会接收错误。在 UART 通信的时候,一个字节是 8 位,规定当没有通信信号发生时,通信线路保持高电平,当要发送数据之前,先发一位 0 表示起始位,然后发送 8 位数据位,数据位是先低后高的顺序,数据位发完后再发一位 1 表示停止位。这样本来要发送一个字节的 8 位数据,而实际上一共发送了 10 位,多出来的两位其中一位起始位,一位停止位。而接收方,原本一直保持的高电平,一旦检测到了一位低平,那就知道了要开始准备接收数据了,接收到 8 位数据位后,然后检测到停止位,再准备下一个数据的接收。

下面图片展示了一个完整的串口数据发送接收过程:

51单片机开发之串口通信_java_03

图4-3-3 串口数据发送示意图

 

4.3.3 51单片机的串口寄存器介绍

STC90C51RC/RD+系列单片机内部集成有一个功能很强的全双工串行通信口(P3.0/RxDP3.1/TxD),与传统8051单片机的串口完全兼容。设有2个互相独立的接收、发送缓冲器,可以同时发送和接收数据。

串行通信设有4种工作方式,其中两种方式的波特率是可变的,另两种是固定的,波特率由内部定时器/计数器产生。

4种工作模式,可通过软件编程对SCON中的SM0、 SM1的设置进行选择。其中模式1、模式2和模式3为异步通信,每个发送和收的字符都带有1个起始位和1个停止

发送缓冲器只能写入而不能读出,接收缓冲器只能读出而不能写入,因而两个缓冲器可以共用一个地址码(99H)。两个缓冲器统称串行通信特殊功能寄存器SBUF

串口相关的配置寄存器如下:

51单片机开发之串口通信_java_04

图4-3-4

完成基本串口通信主要使用的寄存器有4个:

SCON: 串行控制寄存器 (可位寻址)

PCON: 电源控制寄存器 (不可位寻址)

IE : 中断允许寄存器 (可位寻址)

SBUF: 发送/接收缓冲区(双向的)

4.3.4 串行控制寄存器SCON

串行控制寄存器SCON用于选择串行通信的工作方式和某些控制功能,其格式如下:

51单片机开发之串口通信_java_05

图4-3-5

 

TI: 数据发送完成中断请求标志位,由内部硬件自动置位(TI=1),必须用软件复位(TI=0)。

RI: 数据接收完成中断请求志位,由内部硬件置位,即RI=1,必须由软件复位,即RI=0。

SCON的所有位在复位之后全部为"0"。

REN允许/禁止串行接收控制位。 由软件置位REN,即REN=1为允许串行接收状态,可启动串行接收器RxD,开始接收信息。软件复位REN,即REN=0,则禁止接收。

SM0/FE:PCON寄存器中的SMOD0/PCON.6位为0时,该位和SM1一起指定串行通信的工作方式,如下表所示。

51单片机开发之串口通信_java_06

图4-3-6

 

SCON寄存器主要配置串口的工作方式,启动串口的接收功能,常用的工作模式是方式1(8位UART)。

串口工作在方式1时,波特率是可变的,可变的波特由定时器/计数器1或独立波特率发生器产生

示例:

 

  •  
SCON=0x50;          //设置为工作方式1,允许串口接收

4.3.5 电源控制寄存器PCON

电源控制寄存器PCON格式如下:

51单片机开发之串口通信_java_07

图4-3-7

 

SMOD: 波特率选择位。当SMOD=1,则使串行通信方式1、 2、 3的波特率加倍;复位时SMOD=0。

SMOD0: 帧错误检测有效控制位,当SMOD0=0时,与SCON寄存器中的SM0/FE位一起指定串行口的工作方式。复位时SMOD0=0。

配置示例:

PCON=0x80;          //波特率加倍

 

4.3.6 串行口数据缓冲寄存器SBUF

STC90C51RC/RD+系列单片机的串行口缓冲寄存器(SBUF)的地址是99H,实际是2个缓冲器,写SBUF的操作完成待发送数据的加载,读SBUF的操作可获得已接收到的数据。两个操作分别对应两个不同的寄存器,1个是只写寄存器,1个是只读寄存器。

示例:

 

  •  
  •  
  •  
u8 Rx_Byte;Rx_Byte = SBUF;  //接收到的数据保存到变量中SBUF = Rx_Byte; //将变量保存的数据发送出去

4.3.7 波特率设置

51单片机开发之串口通信_java_08

图4-3-8

51单片机开发之串口通信_java_09

图4-3-9

51单片机开发之串口通信_java_10

图4-3-10 常见的波特率设置

 

4.3.8 配置串口实现数据收发示例(波特率不加倍)

下面代码配置串口的波特率为9600,波特率不加倍,当前运行代码的单片机晶振是: 11.059200MHZ。

单片机工作在12T模式下(在12T架构下一个机器周期是12个时钟周期,也就是 12/11059200 秒)

主函数里1秒钟向串口发送一个字符串,串口开启了接收中断,如果收到数据就原样将数据再发送出去。

波特率的配置方法,在STC芯片参考手册的串口章节有示例代码(P199),可以参考修改。

示例代码:

(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)

 

#include <reg51.h>/*串口初始化函数*/void UART_Init(void){    PCON=0x00;    //波特率不加倍    SCON=0x50;    //配置串口工作在模式1(8位数据模式)    EA=1;         //打开总中断    ES=1;         //打开接收中断    TMOD&=0x0F;   //清零T1的控制位    TMOD|=0x20;   //配置T1为模式2 (8位自动重装载)    TL1=TH1=256-(11059200/12/32/9600); //计算 T1 重载值 28800    //TH1= TL1=256-(FOSC/12/32/BAUD);  //计算公式  FOSC表示晶振频率  BAUD表示波特率    TR1=1;        //启动 T1}/*串口接收中断*/void UART_IRQHandler(void) interrupt 4{    u8 Rx_Byte;    if(RI)  //接收到字节    {        RI=0;//手动清零接收中断标志位        Rx_Byte=SBUF;  //接收到的数据保存到变量中        UART_SendOneByte(Rx_Byte); //再发回给电脑端    }}/*发送一个字符*/void UART_SendOneByte(u8 c){    SBUF = c;    while(TI==0){}    TI = 0;}/*发送字符串*/void UART_SendString(u8 *p){    while(*p++!='\0')    {        UART_SendOneByte(*p);    }}int main(){    UART_Init();    while(1)    {        UART_SendString("12345欢迎学习51单片机开发.\r\n");        DelayMs(1000);    }}

4.3.9 配置51单片机的串口支持printf函数

下面代码中重写了putchar函数支持了标准的printf函数,因为printf函数底层会调用putchar函数进行字节发送。Keil软件上不需要做任何其他设置。putchar函数原型在stdio.h文件中有原型声明。

如果要支持scanf函数,重写getchar函数即可。

示例代码:

(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)

 

 

#include <reg51.h>/*串口初始化函数*/void UART_Init(void){    PCON=0x00;    //波特率不加倍    SCON=0x50;    //配置串口工作在模式1(8位数据模式)    //EA=1;       //打开总中断    //ES=1;       //打开接收中断    TMOD&=0x0F;   //清零T1的控制位    TMOD|=0x20;   //配置T1为模式2 (8位自动重装载)    TL1=TH1=256-(11059200/12/32/9600); //计算 T1 重载值  11956000    //TH1= TL1=256-(FOSC/12/32/BAUD);  //计算公式  FOSC表示晶振频率  BAUD表示波特率    TR1=1;        //启动 T1}/*发送一个字符*/void UART_SendOneByte(u8 c){    SBUF = c;    while(TI==0){}    TI = 0;}/*重写putchar函数为了支持printf函数*/char putchar(char c){    UART_SendOneByte(c);    return c;}int main(){    u8 str[]="我是字符串";    u32 data1=123456;    float data2=123.456;    int data3=0x12345;    UART_Init();    while(1)    {        printf("字符串:%s\r\n",str);        printf("data1:%ld\r\n",data1); //这里的u32是typedef unsigned long u32;        printf("data2:%f\r\n",data2);        printf("十六进制:%#x\r\n",(int)data3);        DelayMs(1000);    }}

4.3.10 配置串口实现数据收发(12M晶振、波特率加倍)

下面代码配置串口的波特率为4800,单片机晶振的频率为12MHZ。

示例代码:

(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)

 

 

#include <reg51.h>int main(){    u8 key;    UART_Init();    while(1)    {        key=Array_Scan();        if(key)        {            UART_SendString("12345欢迎学习51单片机开发.\r\n");        }    }}/*串口初始化函数单片机采用了12M的晶振*/void UART_Init(void){    PCON=0x80;    //波特率加倍    SCON=0x50;    //配置串口工作在模式1(8位数据模式)    EA=1;         //打开总中断    ES=1;         //打开接收中断    TMOD&=0x0F;   //清零T1的控制位    TMOD|=0x20;   //配置T1为模式2 (8位自动重装载)    TL1=TH1=0xF3;    //TH1=TL1=256-(FOSC/12/32/BAUD);  //计算公式  FOSC表示晶振频率  BAUD表示波特率    TR1=1;        //启动 T1} /*串口接收中断*/void UART_IRQHandler(void) interrupt 4{    u8 Rx_Byte;    if(RI)  //接收到字节    {        RI=0;//手动清零接收中断标志位        Rx_Byte=SBUF;  //接收到的数据保存到变量中        UART_SendOneByte(Rx_Byte); //再发回给电脑端    }} /*发送一个字符*/void UART_SendOneByte(u8 c){    SBUF = c;    while(TI==0){}    TI = 0;}/*发送字符串*/void UART_SendString(u8 *p){    while(*p++!='\0')    {        UART_SendOneByte(*p);    }} /*重写putchar函数为了支持printf函数*/char putchar(char c){    UART_SendOneByte(c);    return c;}