13、单片机串口通信
项目目标
了解计算机串行通信基础知识,掌握51单片机的串口编程。
知识要点
1、串口的4种工作方式。
2、双机串行通信的软件编程。
1、项目分析
单片机的串口通信工作方式有4种,需要设置更多的寄存器。前面学习过的定时器与中断是单片机通信的基础。本章的任务依然是流水灯,但需要通过通信方式,将甲机的控制信号发送到乙机实现。
2、技术准备
1、计算机通信的概念
通信是指通过某种媒体将信息从一地传送到另一地。古代飞鸽传书和今天电话、手机,都是人与人之间的通信。计算机通信是将计算机技术和通信技术的相结合,完成计算机与外部设备或计算机与计算机之间的信息交换。上面所说的计算机与计算机之间的通信分下面3种情况:
PC机与PC机通信;
PC机与单片机通信(本讲只讲这一种);
单片机与单片机通信。
注意:本讲所提及的计算机包括单片计算机。
2、计算机通信的意义
计算机通信的出现,大大拓展了计算机的应用范围。 PC机与单片机通信,可以实现:
1、实现远程测控。
2、组成计算机网络。

通信有并行通信和串行通信两种方式。在多微机系统以及现代测控系统中信息的交换多采用串行通信方式。
3、并行通信和串行通信
计算机通信是将计算机技术和通信技术的相结合,完成计算机与外部设备或计算机与计算机之间的信息交换。可以分为两大类:并行通信与串行通信。
并行通信通常是将数据字节的各位用多条数据线同时进行传送。
串行通信是将数据字节分成一位一位的形式在一条传输线上逐个地传送。
并行通信:数据多位同时传送

控制简单,传输速度快,传输线较多
串行通信:数据字节一位一位在一条传输线上逐个传送。

传输线少,可利用电话网,但传送控制复杂
小结
并行通信的特点:控制简单、传输速度快;由于传输线较多,长距离传送时成本高且接收方的各位同时接收存在困难。
串行通信的特点:传输线少,长距离传送时成本低,且可以利用电话网等现成的设备,但数据的传送控制比并行通信复杂。
4、异步通信与同步通信
(1) 异步通信:收、发设备使用各自时钟

以字符(构成的帧)为单位
字符间是异步的
字符内各位是同步的
(2) 同步通信:发送方时钟与接收方时钟同步。既保持位同步,也保持字符同步。

面向字符的同步格式:

同步字符SYN(16H)
序始字符SOH (01H),表示标题的开始
标题:源地址、目标地址和路由指示等信息
文始字符STX (02H)
数据块是传送的正文内容,由多个字符组成
组终字符ETB(17H)或文终字符ETX(03H)
校验码
示例:IBM的二进制同步规程BSC
面向位的同步格式:

用序列01111110作为开始和结束标志。
发送方在其发送的数据流中每出现5个连续的1就插入一个附加的0;接收方则每检测到5个连续的1且其后有一个0时,就删除该0。
例:ISO的高级数据链路控制规程HDLC和IBM的同步数据链路控制规程SDLC。
传输效率较高,但硬件设备复杂。
异步通信汇总,单片机与外设必须有 两项规定
通信双方采用怎样的数据格式(指串行通信中怎样标记一组数据的开头与结束,这组数据有多少个有效数据位,以及是否带有校验码等)
通信双方在通信过程中每发送一个数位需要多长时间(即:波特率,每秒钟传送的二进制数位,对微处理与外界通信尤为重要)
5、串口通信
1 数据格式
说明
通信双方采取什么样的数据格式(串行通信中怎样标记一组数据的开头和结束,这组数据多有少个有效数据位,以及是否带有校验码等)
UART串行通信的数据格式:

异步通信的特点:不要求收发双方时钟的严格一致,实现容易,设备开销较小,但每个字符要附加2~3位用于起止位,各帧之间还有间隔,因此传输效率不高。
2 传输方向
单工:单工是指数据传输仅能沿一个方向,不能实现反向传输。
半双工:半双工是指数据传输可以沿两个方向,但需要分时进行。
全双工:全双工是指数据可以同时进行双向传输。

信号的调试与解调
调制器:把数字信号转换成模拟信号,然后送到通信线路上去
解调器:把从通信线路上收到的模拟信号转换成数字信号。

3 传输速率
说明
双方通信过程汇总发送一个数据需要多长的时间。
传输速率
比特率:每秒钟传输二进制代码的位数
波特率:每秒钟调制信号变化的次数,即每秒钟发送的位数,单位是:波特(Baud)。
波特率和比特率不总是相同的,但对于基带传输,比特率和波特率是相同的。
传输距离与传输速率的关系
传输距离随传输速率的增加而减小。
4 奇偶校验
1、奇偶校验
在发送数据时,数据位尾随的1位为奇偶校验位(1或0)。奇校验时,数据中“1”的个数与校验位“1”的个数之和应为奇数;偶校验时,数据中“1”的个数与校验位“1”的个数之和应为偶数。接收字符时,对“1”的个数进行校验,若发现不一致,则说明传输数据过程中出现了差错。
2、代码和校验
代码和校验是发送方将所发数据块求和(或各字节异或),产生一个字节的校验字符(校验和)附加到数据块末尾。接收方接收数据同时对数据块(除校验字节外)求和(或各字节异或),将所得的结果与发送方的“校验和”进行比较,相符则无差错,否则即认为传送过程中出现了差错。
3、循环冗余校验
这种校验是通过某种数学运算实现有效信息与校验位之间的循环校验,常用于对磁盘信息的传输、存储区的完整性校验等。这种校验方法纠错能力强,广泛应用于同步通信中。
5 串口结构
51单片机内部有一个 全双工 串行通信接口,具有UART功能。

有两个物理上独立的接收、发送缓冲器SBUF,它们占用同一地址99H,但二者是有做隔离(系统会自动区分);
6 SCON
介绍
串行口控制寄存器(SCON,Serial Control Register):用于控制串行通信的方式选择、接收和发送,指示串口的状态。
寄存器
| 位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|---|---|---|---|---|---|---|---|---|
| 功能 | SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI |
RI:接收中断标志位
接收结束时,会由硬件置1,向CPU发出中断请求(单片会从SBUF中读取数据,要由软件复位)
TI:发送中断标志位
发送结束时,会由硬件置1,向CPU发出中断请求(要由软件复位)
RB8:用来存放接收的第9位。
TB8:用来存放发送的第9位。
REN:是串行接收允许位。0时:允许串行接收,1时:禁止串行接收
SM2:多级通信控制位。
因为多级通信是在工作方式2和工作方式3下进行的,因此SM2主要用在工作方式2和工作方式3。
当SM2=0时:不论接收的第9位是0还是1,都接收数据,产生中断。
当SM2=1时:只有在接收到的第9位为1时,才接收数据,并产生中断;而如果接收到的第9位为0时,则将接收到的数据丢弃,不产生中断。
串口通信的工作方式

方式0、2的波特率固定。
方式1、3的波特率可变,有定时器T1来决定。
方式0
串行口的SBUF做为同步移位寄存器使用,发送SBUF相当于一个并入传出的移位寄存器,接收SBUF相当于一个传入并出的移位寄存器。
方式1(最常用)
串行口在方式1下工作于异步通信方式,一帧数据有10位,包括1位起始位、8位数据位和1位
停止位。
发送过程:单片机执行一条写入SBUF的指令就启动发送,数据从TXD引脚输出,发送完一帧数据后,硬件置位TI标志。
接收过程:当REN=1时,接收器对RXD引脚进行采样,采样脉冲频率是所选波特率的16倍。当采样到RXD引脚上出现从高电平“1”到低电平“0”的负跳变时,就启动接收器接收数据接收完数据后,将有效的8位数据送入接收SBUF中,停止位送入RB8中,并置位RI
方式2和方式3
方式2或方式3异步通讯的数据帧都是由11位组成,包括1位起始位、8位数据位、1位可编程位(第9位)、1位停止位。发送数据时,第9位送入SCON中的TB8;接收数据时,第9位送入SCON中的RB8。(可用于多机通信)
发送过程:先把第9位数据装入SCON中的TB8中,再把要发送的数据送入发送SBUF。发送器便立即启动发送数据,发送完一数据后,硬件置位TI.
接收过程:当REN=1时,串行口可以接收数据。接收到的有效8位数据送入接收SBUF,第9位数据装入RB8,然后根据SM2的设置判定是否置位RI
7 PCON
介绍
功率控制寄存器(Power Control Register),主要用于控制电源管理模式以及实现串行通信时波特率的加倍。
寄存器
| 位 | D7 | D6 | D5 | D4 | D3 | D2 | D1 | D0 |
|---|---|---|---|---|---|---|---|---|
| 功能 | SMOD | – | – | – | – | – | – | – |
SMOD:是波特率是否加倍的选择位。
0时:波特率不加倍。
1时:波特率加倍。
8 波特率计算
波特率的定义是:串行口每秒钟发送的位数称为波特率。
比如说2400的波特率就是每秒钟发送2400个位数。
串行口的波特率是用定时器T1作为波特率发生器的,定时器软件设置在工作方式2(可自动重装初值)。

常用波特率及其相关设置,编程时可以通过查表的方式获得定时器T1初值。另外,在计算波特率时,若晶振频率为11.0592MHz,则可以得到精确的T1初值(不会产生小数值),因此频率为11.0592MHz的晶振在单片机系统中经常被采用。

示例
如需精确的通信波特率 4800bps,设晶振频率为 fosc=11.0592MHzf_{osc} = 11.0592MHzfosc=11.0592MHz,定时器T1工作在方式2下,如何实现?
方法1:SMOD = 0
计数初值=256−晶振频率∗2SMOD波特率∗32∗12=256−11.0592MHz∗204800∗32∗12=250=FAH 计数初值 = 256 – frac{晶振频率 * 2^{SMOD}}{波特率 * 32 * 12} = 256 – frac{11.0592MHz * 2^0}{4800 * 32 * 12} = 250 = FAH \ 计数初值=256−波特率∗32∗12晶振频率∗2SMOD=256−4800∗32∗1211.0592MHz∗20=250=FAH
在实际工作中,T1从定时计数初值FAH开始计数工作,则实际的波特率为:
$$
波特率 = frac{晶振频率 * 2^{SMOD}}{(256 – 计数初值)* 32 * 12} = frac{11.0592MHz * 2^0}{(256 – 250) * 32 * 12} = 4800
波特率误差 = (4800 – 4800) / 4800 * 100% = 0
$$
方法2:SMOD = 1
计数初值=256−晶振频率∗2SMOD波特率∗32∗12=256−11.0592MHz∗214800∗32∗12=244=F4H 计数初值 = 256 – frac{晶振频率 * 2^{SMOD}}{波特率 * 32 * 12} = 256 – frac{11.0592MHz * 2^1}{4800 * 32 * 12} = 244 = F4H \ 计数初值=256−波特率∗32∗12晶振频率∗2SMOD=256−4800∗32∗1211.0592MHz∗21=244=F4H
在实际工作中,T1从定时计数初值F4H开始计数工作,则实际的波特率为:

由上可知,SMOD的选择对计数初值和波特率有直接影响,一般波特率误差不大于2.5%,所以选择SMOD的值时先计算一下,选择使波特率误差小的SMOD值
6 操作步骤
设置波特率
设置定时器T1为工作方式2(设置TMOD寄存器)
计算定时器T1的初值(工作方式2会自动重装TH1、TL1)
启动定时器T1(设置TCON寄存器的TR1位)
设置串口工作方式:
设置SCON寄存器(如果允许)
如果使用中断方式,那么打开相应的中断和总中断。
ES-打开串口中断
CPU对中断系统所有中断以及某个中断源的开放和屏蔽是由中断允许寄存器IE控制的。

EX0(IE.0),外部中断0允许位;
ET0(IE.1),定时/计数器T0中断允许位;
EX1(IE.2),外部中断0允许位;
ET1(IE.3),定时/计数器T1中断允许位;
ES(IE.4),串行口中断允许位;
EA (IE.7),CPU中断允许(总允许)位。
EA-总中断允许与否,EA=1开启单片机总中断(只有总中断开启后才可响应各个中断)。
TR1-定时器运行与否,TR1=1运行起来(才会产生波特率)。
串口中断函数(也称为串口收发回调函数):
void uart() interrupt 4
{
unsigned char data;
data = SBUF; // 取出接收到的字节RI=0;
RI=0; // 清除硬件接收中断标志位
SBUF = data; // 将接收的数据放回发送缓冲区
while (!TI); // 等待发送数据完毕
TI = 0; // 清除发送中断标志位
}
波特率计算:在实际项目中,一般采用工具计算生成初始化代码

3、项目实验
项目1、发送字符A
分析:实现单片机通过串行口发送字符 “A”,利用PC机接收该字符,接收完毕后LED亮,假设通信波特率为 9600bps,8N1通信格式(8个数据位,无奇偶校验,1个停止位)。
![图片[1] - 51单片机教程(十三)- 单片机串口通信 - 宋马](https://pic.songma.com/blogimg/20250715/61746ae233314692b9fc02bf7875e9a5.png)
选择 SMOD=0,计数初值=232 = E8H,其波特率误差只有0,远小于小于2.5%,符合传输要求。
PCON寄存器(地址为87H)中的第7位是SMOD位,因为不能对该寄存器进行位寻址,所以只能对PCON寄存器进行整体操作。
– 项目实现
/*
实验:发送一个字符"A"
说明:单片机 --> PC(串口助手)
过程:
1 T1的工作方式2
1 设置工作模式
TMOD = 0x20;
2 计算初值
TH1 = 0xe8; // 设置波特率为:1200
TL1 = 0xe8;
3 开启开关
EA = 1;
TR1 = 1; // 开启T1
2 串口设置
SCON = 0x50; // 工作在方式1
PCON = 0x00; // 波特率不加倍
3 发送数据
3.1 将字符‘A’ 给 SBUF
SBUF = 'A';
3.2 循环等待数据发送完毕
while(!TI);
3.3 软件方式对TI清零
TI = 0;
*/
#include <reg52.h>
void main1()
{
// T1的设置
TMOD = 0x20;
TH1 = 0xe8;
TL1 = 0xe8;
EA = 1;
TR1 = 1;
// UART的设置
SCON = 0x50; // 工作在方式1
PCON = 0x00; // 波特率不加倍
while (1) {
SBUF = 'A';
while (!TI);
TI = 0;
}
}
项目2、发送多个字符
/*
实验:发送多个字符
说明:单片机 --> PC(串口助手)
过程:
1 T1的工作方式2
1 设置工作模式
TMOD = 0x20;
2 计算初值
TH1 = 0xe8; // 设置波特率为:1200
TL1 = 0xe8;
3 开启开关
EA = 1;
TR1 = 1; // 开启T1
2 串口设置
SCON = 0x50; // 工作在方式1
PCON = 0x00; // 波特率不加倍
3 发送数据
3.0 多个字符 - 数组
unsigned char SendData[] = {'H', 'e', 'l', 'l', 'o', 'B', 'o', 'y'};
3.1 循环遍历数组
for(i=0; i<8; i++)
{
3.1 将字符给 SBUF
SBUF = SendData[i];
3.2 循环等待数据发送完毕
while(!TI);
3.3 软件方式对TI清零
TI = 0;
}
*/
#include <reg52.h>
unsigned char SendData[] = {
'H', 'e', 'l', 'l', 'o', 'B', 'o', 'y'};
int i;
// 串口初始化
void uart_init2() //9600bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xFA; //设置定时初始值
TH1 = 0xFA; //设置定时重载值
EA = 1;
TR1 = 1; //定时器1开始计时
}
void main2()
{
uart_init();
while (1) {
for (i = 0; i < 8; i++) {
SBUF = SendData[i];
while(!TI);
TI = 0;
}
}
}
项目3、串口的发送数据
/*
实验:串口的数据收发
说明:单片机 <--> PC(串口助手)
要求:
单片机 接收 串口助手发送的数据,回复:'OK'
实现:
1、串口初始化 - 波特率:9600
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xFA; //设置定时初始值
TH1 = 0xFA; //设置定时重载值
EA = 1;
TR1 = 1; //定时器1开始计时
2、数据发送
2.1 将发送的数据赋值SBUF
2.2 等待发送完毕
2.3 对TI清零
3、接收数据
3.1 定义接收数据的数组
3.2 将接收SBUF赋值给数组
3.3 对RI清零
*/
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
// 定义发送的数据:"OK"
uchar sData[] = {
'o', 'k'};
// 定义发送数据的索引
uint idx;
// 串口初始化
static void uart_init() //9600bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xFA; //设置定时初始值
TH1 = 0xFA; //设置定时重载值
EA = 1;
TR1 = 1; //定时器1开始计时
ES = 1; // 开启串口中断允许位 (不知道串口助手什么时候会发送数据)
}
void main()
{
uart_init();
while(1);
}
// 串口中断函数
void uart_IRQ() interrupt 4
{
// 接收数据
uchar rData;
rData = SBUF;
RI = 0;
// 发送数据
for (idx = 0; idx < 2; idx++) {
SBUF = sData[idx];
while(!TI);
TI = 0;
}
}
项目4、串口接收数据显示在数据管
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
// 定义位选、段选
#define pos P2
#define par P0
// 定义位选数组:第1~4位
uint posValue[] = {
0x18, 0x28, 0x48, 0x88};
// 定义段选数组:存储0-9/A/b/c/d/E/F
uint parValue[] = {
0x3f, 0x06, 0x5b, 0x4f, 0x66,
0x6d, 0x7d, 0x07, 0x7f, 0x6f};
// 定义接收存储数据数组
uint DisplayData[4] = 0;
// 定义是否接受到数据标志
uint flags = 0;
// 定义接收收到的数据位数索引
uint num=0;
void Uart_Init();
void displayNumber();
static void delay(uint num);
void main()
{
Uart_Init();
while(1)
{
displayNumber();
num = 0;
}
}
// 串口初始化
void Uart_Init()
{
// 定时器T1设置
TMOD = 0x20; // 选择工作方式2
TH1 = 0xfa; // 设置波特率为9600
TL1 = 0xfa;
TR1 = 1; // 开启定时器T1
// 串口配置
SCON = 0x50; // 设置串口工作在方式1,T1位清0
PCON = 0x80; // PCON 7位是SMOD位,SMOD=1
// 中断定义
EA = 1; // 开启总中断
ES = 1; // 开启串口中断
}
// 串口中断服务函数
void Uart_IRQ() interrupt 4
{
if(RI == 1) // 判断接收到数据
{
RI = 0; // 清除接收数据标志位
DisplayData[num] = SBUF - 0x30; // 数字字符转换为对应的整数值,0x30('0')到0x39('9')
num++;
}
// 清除发送数据标志位
if (TI == 1) TI = 0;
}
// 数码显示
void displayNumber()
{
uint i;
for(i=0; i<4; i++)
{
pos = posValue[i];
par = parValue[DisplayData[i]];
delay(300);
par = 0x00; // 消影
}
}
// 延迟函数
static void delay(uint num)
{
while(num--);
}


















暂无评论内容