串口相关寄存器

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-10-08 01:26:37 Views: 1 Likes: 0 Comments: 0

047.6.3 串口相关寄存器

一、串口相关寄存器

STC89C52的串口不是由程序逐位翻转引脚来完成发送和接收,而是由芯片内部UART硬件自动完成串并转换、起始位与停止位生成、接收采样和状态标记。程序主要负责配置寄存器、向SBUF写入待发送数据,以及在TI或RI置位后处理结果。

本节涉及的寄存器及作用如下:

寄存器地址主要作用是否可位寻址
SCON0x98选择串口工作方式,控制接收,保存收发状态标志是
PCON0x87通过SMOD控制方式1、2、3的波特率倍率否
TMOD0x89设置定时器1的计数来源和工作方式否
TCON0x88用TR1启动或停止定时器1是
TH1、TL10x8D、0x8B保存定时器1的重装值和当前计数值否
SBUF0x99写入时进入发送通路,读取时取得接收数据否
IE0xA8控制总中断和各中断源是否允许是
XICON0xC0控制外部中断2、3,与串口中断无直接关系是

这些寄存器在UART内部的关系可以概括为:

                           ┌──────── 波特率发生通路 ────────┐
系统时钟 ─→ 定时器1模式2 ─→ 溢出脉冲 ─→ SMOD倍率及16分频 ─┬→ TX时钟
                                                          └→ RX采样时钟

CPU写SBUF ─→ 发送缓冲器 ─→ 发送移位寄存器 ─→ P3.1/TXD
                                      │
                                      └→ 一帧发送到规定时刻,TI=1

P3.0/RXD ─→ 起始位检测 ─→ 16倍采样 ─→ 接收移位寄存器 ─→ 接收SBUF ─→ CPU读取
                                                       │
                                                       └→ 一帧有效接收后,RI=1

                         ┌─ TI ─┐
                         └─ RI ─┴→ 串口中断请求 ─→ ES门控 ─→ EA总门控 ─→ CPU

这是一张功能逻辑图,不是开发板外部接线图。图中的“缓冲器、移位寄存器、分频器、检测器”都在单片机内部;外部真正使用的串口信号引脚是P3.1/TXD和P3.0/RXD。

1.1 串行控制寄存器SCON

SCON是串行口控制寄存器,地址为0x98,支持位寻址。位从高到低排列如下:

位符号含义
B7SM0/FESMOD0=0时作为SM0,与SM1共同选择串口方式;SMOD0=1时作为帧错误标志FE
B6SM1串口工作方式选择位
B5SM2多机通信控制位;方式1下还会影响接收停止位不为1时是否置RI
B4REN接收允许位,1允许接收,0禁止接收
B3TB8方式2、3中要发送的第9位数据
B2RB8方式2、3中接收到的第9位;方式1中保存接收到的停止位
B1TI发送中断请求标志,由硬件置1,必须由软件清0
B0RI接收中断请求标志,由硬件置1,必须由软件清0

在SMOD0=0时,SM0和SM1共同决定工作方式:

SM0SM1方式帧及波特率特点
00方式08位同步移位寄存器方式,波特率固定
01方式18位UART,1位起始位、8位数据位、1位停止位,波特率可变
10方式29位UART,波特率由系统时钟和SMOD决定
11方式39位UART,波特率可变

实验采用方式1并允许接收,常见配置为:

/*
 * 片段目的:一次写入串行控制寄存器,选择本课程使用的UART方式1并打开接收通路。
 * 位级依据:0x50 = 0101 0000B,对应SM0=0、SM1=1、SM2=0、REN=1、TI=0、RI=0。
 * 使用前提:reg52.h已声明SCON;本句只配置帧方式和接收控制,不能单独产生正确波特率。
 * 课程上下文:后续还要配置PCON、定时器1并启动TR1,采用中断时还要设置ES和EA。
 */
SCON = 0x50;  /* 选择8位UART、波特率可变的方式1,允许接收,并把TI、RI初始化为0。 */

0x50的逐位拆解如下:

位号: B7   B6   B5   B4   B3   B2   B1   B0
符号: SM0  SM1  SM2  REN  TB8  RB8  TI   RI
数值:  0    1    0    1    0    0    0    0
作用: └方式1┘   普通接收  允许接收       清空收发标志

REN只控制接收通路

REN=1会使接收器监视RXD并允许接收一帧;REN=0会禁止接收。发送通路由“写SBUF”启动,与REN无关。因此,REN=1不能解释成“同时允许发送和接收”,单片机即使在REN=0时仍然可以发送数据。

SM2在方式1中的作用

普通8N1通信通常设置SM2=0。这样接收到一帧后,只要其他接收条件成立,就可以把RI置1。若方式1中设置SM2=1,只有接收到的停止位为1时才允许置RI,可用于附加的接收筛选。方式2、3下,SM2主要用于多机通信。

TI表示何时可以处理下一次发送

CPU写SBUF后,UART硬件自动发送起始位、8个数据位和停止位。在方式1中,发送停止位开始时,硬件把TI置1,表示本帧的主要移位过程已经完成,软件可以进行下一步发送管理。

写SBUF
   │
   ▼
START → D0 → D1 → D2 → D3 → D4 → D5 → D6 → D7 → STOP
                                                    ▲
                                            STOP开始时TI=1

硬件只负责把TI置1,不会自动把它清0。查询发送时应等待TI=1,随后由程序执行TI=0;使用中断发送时,则应在串口中断服务程序中判断并清除TI。

RI表示一帧已经有效接收

接收器先检测RXD上的1→0跳变,再按16倍采样时钟在每一位中部附近判定电平。方式1下,一帧数据接收完毕且满足RI=0、停止位条件等要求后,数据进入接收SBUF,硬件把RI置1。RI同样必须由软件清0,否则下一帧可能无法正常形成新的接收事件。

RXD:空闲1 → START0 → D0…D7 → STOP1
          │                 │
          └检测起始跳变     └数据装入接收SBUF,RI=1

TI和RI共用一个串口中断入口

51单片机没有独立的“发送中断函数”和“接收中断函数”。TI或RI任意一个为1,都可能形成串口中断请求:

$ \text{串口中断进入条件}=(TI\lor RI)\land ES\land EA $

因此进入串口中断服务程序后,应分别判断RI和TI,再清除实际触发的标志。不能只看到进入了串口中断,就默认一定收到了数据。

1.2 电源控制寄存器PCON

PCON地址为0x87,不能位寻址。它不只包含串口波特率控制位,还包含电源管理和通用标志位:

位符号含义
B7SMOD串口方式1、2、3的波特率倍率选择位
B6SMOD01时把SCON.7用作帧错误标志FE;0时把它用作方式选择位SM0
B5-保留位,不应依赖其读出值
B4POF上电标志,可供程序辅助判断上电状态
B3GF1用户通用标志位1
B2GF0用户通用标志位0
B1PD掉电模式控制位
B0IDL空闲模式控制位

对方式1而言,SMOD的正确关系是:

SMOD波特率倍率含义
01倍不加倍
12倍在相同定时器1溢出率下,波特率加倍

也就是说,设置SMOD=1才会使方式1波特率变为原来的2倍;SMOD=0是不加倍。其关系式为:

$ \text{方式1波特率}=\frac{2^{SMOD}}{32}\times\text{定时器1溢出率} $

实验需要SMOD=0时,可使用掩码只清除B7:

/*
 * 片段目的:关闭串口波特率加倍,同时避免破坏PCON中的其他控制位和状态位。
 * 运算思路:0x7F的B7为0、其余位为1;按位与后只有B7/SMOD被强制清0。
 * 寄存器前提:PCON不可位寻址,所以不能像操作TI、RI那样直接按位赋值。
 */
PCON &= 0x7F;  /* 将SMOD清0,使方式1波特率采用1倍系数;PCON其余7位保持原值。 */

由于PCON不可位寻址,不能像EA=1那样直接写SMOD=0。使用按位与掩码还可以避免误改SMOD0、POF、GF1/GF0、PD和IDL。在不了解其他位当前用途时,不宜为清除SMOD而直接执行PCON=0x00。

1.3 定时器计数器工作模式寄存器TMOD

方式1的波特率可变,本实验使用定时器1模式2产生稳定的溢出节拍。TMOD地址为0x89,不能位寻址;高4位控制定时器1,低4位控制定时器0:

位定时器1符号位定时器0符号
B7GATE1B3GATE0
B6C/T1B2C/T0
B5M1_1B1M1_0
B4M0_1B0M0_0

各类控制位的意义如下:

位取值作用
GATE10只要TR1=1,定时器1就运行
GATE11还要求INT1引脚为高电平,定时器1才运行
C/T10定时模式,计数脉冲来自芯片内部系统时钟分频
C/T11计数模式,计数脉冲来自外部T1/P3.5引脚
M1_1 M0_100模式0,13位定时器/计数器
M1_1 M0_101模式1,16位定时器/计数器
M1_1 M0_110模式2,8位自动重装载
M1_1 M0_111定时器1停止计数

作为串口波特率发生器时,需要定时器1、内部定时、模式2,因此高4位应为:

GATE1  C/T1  M1_1  M0_1
  0      0     1     0     = 0010B = 0x2

为了只改定时器1而保留定时器0的配置,可写成:

/*
 * 片段目的:把定时器1设为内部定时、模式2自动重装,同时保留定时器0原有配置。
 * 运算顺序:先用0x0F保留TMOD低4位并清除高4位,再用0x20写入定时器1的0010B配置。
 * 串口关系:定时器1的周期性溢出将作为方式1波特率发生器的基础时钟。
 */
TMOD = (TMOD & 0x0F) | 0x20;  /* GATE1=0、C/T1=0、M1_1=1、M0_1=0,即定时器1模式2。 */

先用TMOD & 0x0F清空高4位并保留低4位,再用| 0x20把定时器1设置为模式2。直接执行TMOD=0x20虽然也能得到所需模式,但会同时把定时器0的配置清零。

模式2为什么适合产生波特率

模式2把TL1作为8位计数器,把TH1作为自动重装值。TL1从初值向上计数,达到0xFF后再加1发生溢出,硬件立即把TH1中的值重新装入TL1,然后开始下一轮。因此每轮计数长度始终相同,不需要程序在每次溢出后手工重装。

                  ┌──────────────────────────┐
                  │                          │ 自动重装
系统时钟 ─→ 分频 ─→ TL1递增 ─→ 0xFF后溢出 ──┴← TH1保存重装值
                                  │
                                  └→ UART波特率分频电路

TH1=0xFD时,每次溢出需要3个计数脉冲:

TL1:0xFD → 0xFE → 0xFF → 溢出并重新装入0xFD
                         共经过 256 - 0xFD = 3 个计数

在11.0592MHz晶振、定时器1采用12T计数、串口方式1、SMOD=0时:

$ \text{定时器1溢出率}=\frac{f_{osc}}{12(256-TH1)} $

$ \text{波特率}=\frac{2^{SMOD}}{32}\times\frac{f_{osc}}{12(256-TH1)} $

代入fosc=11 059 200Hz、SMOD=0、TH1=0xFD=253:

$ \text{波特率}=\frac{1}{32}\times\frac{11,059,200}{12\times(256-253)} =\frac{1}{32}\times307,200 =9,600\text{ bit/s} $

初始化时通常同时写入TH1和TL1,再由TR1启动定时器1:

/*
 * 片段目的:装入定时器1模式2的初值并启动计数,为UART方式1提供9600波特的时基。
 * 时钟前提:计算以11.0592MHz晶振、定时器1工作在12T模式、PCON.SMOD=0为条件。
 * 模式2分工:TL1负责递增计数,溢出后由硬件自动复制TH1的重装值。
 */
TH1 = 0xFD;  /* 把自动重装值设为253,使TL1每经过256-253=3个计数脉冲溢出一次。 */
TL1 = 0xFD;  /* 给当前计数器写入同一初值,使第一次溢出的周期也与后续自动重装周期一致。 */
TR1 = 1;     /* 将TCON中的定时器1运行控制位置1,开始产生UART所需的连续溢出节拍。 */

如果STC芯片把定时器1改成6T模式,计数频率会变为12T模式的2倍,以上重装值和计算结果也会变化。因此计算前必须确认定时器的1T/6T/12T设置以及实际晶振频率。

定时器1不会要求程序每次溢出发送一位

真正的数据移位由UART硬件完成。定时器1溢出只向串口内部提供时基,经过SMOD选择和16分频后成为发送时钟、接收采样时钟:

定时器1溢出脉冲
       │
       ├─ SMOD=0:先÷2 ─┐
       └─ SMOD=1:不÷2 ─┴→ ÷16 ─┬→ TX移位时钟
                                └→ RX的16倍采样节拍

程序只需启动定时器1,无须开启定时器1中断,也无须在定时器中断中逐位发送。换句话说,TR1=1是启动波特率时基;ET1=1只是允许CPU响应定时器1溢出中断,通常不应为串口波特率发生器而打开ET1。

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

SBUF的特殊功能寄存器地址为0x99。从程序角度看只有一个名字,但芯片内部实际有两个不同的缓冲器:

                         同一个SFR地址0x99

CPU执行 SBUF = data ─→ 发送SBUF ─→ 发送移位寄存器 ─→ P3.1/TXD

CPU执行 data = SBUF ←─ 接收SBUF ←─ 接收移位寄存器 ←─ P3.0/RXD

地址译码电路会同时判断读写方向:写0x99时选择发送缓冲器,读0x99时选择接收缓冲器。因此“写入后又读取同一个SBUF”并不表示能原样读回刚写入的发送字节;读操作得到的是接收通路保存的数据。

写SBUF启动发送

/*
 * 片段目的:演示向发送SBUF写入一个原始数值字节,写操作会启动一帧UART发送。
 * 数据含义:这里发送0x01而不是可显示字符'1';串口助手应切换到HEX显示来直观看到01。
 * 时序前提:串口和波特率发生器已初始化;连续发送时还必须等待并清除TI。
 */
SBUF = 1;  /* 把数值1(0x01)交给UART发送通路,由硬件输出起始位、8位数据和停止位。 */

这条语句写入的是数值0x01,UART随后自动发送一帧。它不是ASCII字符'1';若希望串口助手按文本显示字符1,应写:

/*
 * 片段目的:演示发送能够在文本模式串口助手中显示为数字字符1的ASCII字节。
 * 语法区别:单引号表示字符常量,编译器会把'1'转换为其ASCII编码0x31。
 * 时序前提:与发送原始数值相同,若后面还有字节,软件需通过TI协调发送节奏。
 */
SBUF = '1';  /* 将ASCII码0x31写入发送SBUF,而不是发送数值0x01。 */

两者在线路上发送的8个数据位不同:

表达式字节值含义
SBUF = 1;0x01数值1,串口助手用HEX显示时通常看到01
SBUF = '1';0x31ASCII字符1,文本显示时看到1

CPU写入发送SBUF后,发送数据进入移位寄存器,UART依次生成START、D0~D7、STOP。如果连续发送多个字节,应使用TI确认前一帧已经推进到允许后续处理的时刻,不能无间隔地反复覆盖发送缓冲。

读SBUF取得接收数据

/*
 * 片段目的:采用查询方式检查接收完成标志,并把新收到的一个字节保存到普通变量。
 * 硬件依据:UART接收完有效帧后把RI置1,并把8位数据放入接收SBUF;RI必须由软件清0。
 * 使用前提:SCON.REN已置1,波特率已正确配置;实际程序应及时处理数据以免后续帧丢失。
 */
unsigned char a;         /* 定义8位无符号变量,用来转存接收SBUF中的一个完整数据字节。 */

if (RI == 1)             /* 查询接收标志;只有硬件置位RI后,SBUF里才有本次新收到的数据。 */
{                        /* 新数据处理分支开始,RI为0时跳过这里,不把旧SBUF内容误当成新数据。 */
    RI = 0;              /* 软件清除接收完成标志,为UART接收并标记下一帧做好准备。 */
    a = SBUF;            /* 读取地址0x99会选中内部接收缓冲器,把收到的8位数据复制给a。 */
}                        /* 本次单字节接收处理结束;变量a可在后续逻辑中被判断或使用。 */

只有RI=1时,才表示接收SBUF中已经装入一帧有效数据。尚未收到新数据时读取SBUF,可能得到上一次保留的内容,不能把它当成新字节。

经典8051串口通常没有多字节接收FIFO。如果上一字节进入接收SBUF后,程序长时间没有清除RI,随后到达的帧就无法装入SBUF而被丢弃。实际程序应及时响应RI,把SBUF内容转存到变量或环形缓冲区,再处理耗时任务。

1.5 中断允许寄存器IE和XICON

IE是中断允许寄存器,地址为0xA8,支持位寻址:

位符号控制的中断源
B7EACPU总中断允许位
B6-保留位
B5ET2定时器2中断允许位
B4ES串行口中断允许位
B3ET1定时器1中断允许位
B2EX1外部中断1允许位
B1ET0定时器0中断允许位
B0EX0外部中断0允许位

EA是所有可屏蔽中断前面的总开关,但EA=1不等于“所有中断都会自动打开”。每个中断源还要经过自己的分开关。串口中断需要:

TI或RI产生请求 ─→ ES=1允许串口中断 ─→ EA=1允许CPU总中断 ─→ 进入中断号4

所以,使用串口中断通常需要:

/*
 * 片段目的:依次打开串口中断的分开关和CPU可屏蔽中断总开关。
 * 门控关系:只有(TI或RI)=1、ES=1且EA=1同时满足,CPU才会响应串口中断号4。
 * 使用前提:中断服务程序必须正确判断并清除TI、RI,否则请求可能持续存在。
 */
ES = 1;  /* 将IE.4置1,允许串口的发送或接收请求通过本中断源的分开关。 */
EA = 1;  /* 将IE.7置1,打开CPU总中断门控;它不会替代ES等各中断源的独立开关。 */
EAESTI或RI能否进入串口中断
0任意值1不能,总开关关闭
101不能,串口分开关关闭
110不能,没有串口请求
111可以进入

串口中断号为4,中断向量地址为0x0023。Keil C51中通常使用interrupt 4声明串口中断服务程序。由于发送和接收共用入口,处理逻辑应类似:

/*
 * 函数目的:给出串口中断服务程序的通用骨架,在共用入口中分别处理接收和发送事件。
 * 设计原因:经典8051的TI与RI共用中断号4,进入函数本身不能说明究竟是哪一个标志触发。
 * 处理原则:检查到哪个标志就由软件清除哪个标志,并在对应分支完成最短、必要的工作。
 * 代码性质:这是说明寄存器协作关系的片段;真正工程还需定义接收变量或发送缓冲管理逻辑。
 */
void UART_Routine(void) interrupt 4  /* 声明串口中断服务程序;interrupt 4让Keil使用串口中断向量0x0023。 */
{                                    /* 串口中断处理开始;此时TI、RI可能有一个为1,也可能同时为1。 */
    if (RI == 1)                     /* 独立检查接收完成标志,不能因进入串口中断就默认发生了接收。 */
    {                                /* 接收事件处理分支开始,应尽快转存SBUF中的数据。 */
        RI = 0;                      /* 软件清除RI;UART硬件不会在中断响应后自动替程序清除此位。 */
        /* 在这里读取SBUF并把字节转存到变量或接收缓冲区,避免后续数据覆盖或丢失。 */
    }                                /* 接收分支结束;继续检查TI,以兼顾两个标志同时置位的情况。 */

    if (TI == 1)                     /* 再独立检查发送完成标志,判断本次中断是否也包含发送事件。 */
    {                                /* 发送事件处理分支开始,可在此安排下一个待发送字节。 */
        TI = 0;                      /* 软件清除TI,撤销本次发送中断请求并为下一帧重新置位做准备。 */
        /* 在这里按发送队列状态写入下一字节;若没有待发数据,则直接退出本分支。 */
    }                                /* 发送分支结束;两个if而非if/else可确保同时置位时均被处理。 */
}                                    /* 中断服务程序结束,执行中断返回并恢复先前被打断的程序。 */

如果发送采用查询TI、接收采用中断RI,仍要记住TI也连接到同一个串口中断请求逻辑。开启ES和EA后,不能长期让TI保持为1,否则CPU可能重复进入串口中断。

XICON地址为0xC0,也支持位寻址。它的各位如下:

位符号含义
B7PX3外部中断3优先级低位
B6EX3外部中断3允许位
B5IE3外部中断3请求标志
B4IT3外部中断3触发方式选择
B3PX2外部中断2优先级低位
B2EX2外部中断2允许位
B1IE2外部中断2请求标志
B0IT2外部中断2触发方式选择

XICON管理的是INT2/P4.3和INT3/P4.2两路扩展外部中断,不控制UART。串口中断使用IE.ES,而不是XICON.EX2或XICON.EX3。把XICON与IE并列学习,是因为两者都属于中断控制寄存器;配置普通串口收发时通常无须改动XICON。

最后把串口方式1、9600波特、中断接收所需条件串联起来:

SCON=0x50       → 方式1,允许接收,TI/RI初始为0
PCON.SMOD=0     → 波特率不加倍
TMOD高4位=0010 → 定时器1采用模式2自动重装
TH1=TL1=0xFD   → 11.0592MHz、12T条件下得到9600波特
TR1=1           → 启动定时器1,为UART提供时基
ES=1 且 EA=1   → 允许TI/RI触发串口中断
写SBUF          → 启动一帧发送
RI=1后读SBUF    → 取得一帧接收数据,并由软件清RI

排查串口故障时,可以按“时钟与波特率→工作方式→收发标志→中断门控→SBUF读写方向”的顺序检查。这样能把“完全不收发、乱码、只发送不接收、反复进中断”等现象分别定位到对应寄存器,而不是只反复修改一个数值。