串口相关寄存器
047.6.3 串口相关寄存器
一、串口相关寄存器
STC89C52的串口不是由程序逐位翻转引脚来完成发送和接收,而是由芯片内部UART硬件自动完成串并转换、起始位与停止位生成、接收采样和状态标记。程序主要负责配置寄存器、向SBUF写入待发送数据,以及在TI或RI置位后处理结果。
本节涉及的寄存器及作用如下:
| 寄存器 | 地址 | 主要作用 | 是否可位寻址 |
|---|---|---|---|
SCON | 0x98 | 选择串口工作方式,控制接收,保存收发状态标志 | 是 |
PCON | 0x87 | 通过SMOD控制方式1、2、3的波特率倍率 | 否 |
TMOD | 0x89 | 设置定时器1的计数来源和工作方式 | 否 |
TCON | 0x88 | 用TR1启动或停止定时器1 | 是 |
TH1、TL1 | 0x8D、0x8B | 保存定时器1的重装值和当前计数值 | 否 |
SBUF | 0x99 | 写入时进入发送通路,读取时取得接收数据 | 否 |
IE | 0xA8 | 控制总中断和各中断源是否允许 | 是 |
XICON | 0xC0 | 控制外部中断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,支持位寻址。位从高到低排列如下:
| 位 | 符号 | 含义 |
|---|---|---|
| B7 | SM0/FE | SMOD0=0时作为SM0,与SM1共同选择串口方式;SMOD0=1时作为帧错误标志FE |
| B6 | SM1 | 串口工作方式选择位 |
| B5 | SM2 | 多机通信控制位;方式1下还会影响接收停止位不为1时是否置RI |
| B4 | REN | 接收允许位,1允许接收,0禁止接收 |
| B3 | TB8 | 方式2、3中要发送的第9位数据 |
| B2 | RB8 | 方式2、3中接收到的第9位;方式1中保存接收到的停止位 |
| B1 | TI | 发送中断请求标志,由硬件置1,必须由软件清0 |
| B0 | RI | 接收中断请求标志,由硬件置1,必须由软件清0 |
在SMOD0=0时,SM0和SM1共同决定工作方式:
SM0 | SM1 | 方式 | 帧及波特率特点 |
|---|---|---|---|
| 0 | 0 | 方式0 | 8位同步移位寄存器方式,波特率固定 |
| 0 | 1 | 方式1 | 8位UART,1位起始位、8位数据位、1位停止位,波特率可变 |
| 1 | 0 | 方式2 | 9位UART,波特率由系统时钟和SMOD决定 |
| 1 | 1 | 方式3 | 9位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,不能位寻址。它不只包含串口波特率控制位,还包含电源管理和通用标志位:
| 位 | 符号 | 含义 |
|---|---|---|
| B7 | SMOD | 串口方式1、2、3的波特率倍率选择位 |
| B6 | SMOD0 | 1时把SCON.7用作帧错误标志FE;0时把它用作方式选择位SM0 |
| B5 | - | 保留位,不应依赖其读出值 |
| B4 | POF | 上电标志,可供程序辅助判断上电状态 |
| B3 | GF1 | 用户通用标志位1 |
| B2 | GF0 | 用户通用标志位0 |
| B1 | PD | 掉电模式控制位 |
| B0 | IDL | 空闲模式控制位 |
对方式1而言,SMOD的正确关系是:
SMOD | 波特率倍率 | 含义 |
|---|---|---|
| 0 | 1倍 | 不加倍 |
| 1 | 2倍 | 在相同定时器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符号 |
|---|---|---|---|
| B7 | GATE1 | B3 | GATE0 |
| B6 | C/T1 | B2 | C/T0 |
| B5 | M1_1 | B1 | M1_0 |
| B4 | M0_1 | B0 | M0_0 |
各类控制位的意义如下:
| 位 | 取值 | 作用 |
|---|---|---|
GATE1 | 0 | 只要TR1=1,定时器1就运行 |
GATE1 | 1 | 还要求INT1引脚为高电平,定时器1才运行 |
C/T1 | 0 | 定时模式,计数脉冲来自芯片内部系统时钟分频 |
C/T1 | 1 | 计数模式,计数脉冲来自外部T1/P3.5引脚 |
M1_1 M0_1 | 00 | 模式0,13位定时器/计数器 |
M1_1 M0_1 | 01 | 模式1,16位定时器/计数器 |
M1_1 M0_1 | 10 | 模式2,8位自动重装载 |
M1_1 M0_1 | 11 | 定时器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'; | 0x31 | ASCII字符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,支持位寻址:
| 位 | 符号 | 控制的中断源 |
|---|---|---|
| B7 | EA | CPU总中断允许位 |
| B6 | - | 保留位 |
| B5 | ET2 | 定时器2中断允许位 |
| B4 | ES | 串行口中断允许位 |
| B3 | ET1 | 定时器1中断允许位 |
| B2 | EX1 | 外部中断1允许位 |
| B1 | ET0 | 定时器0中断允许位 |
| B0 | EX0 | 外部中断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等各中断源的独立开关。 */
EA | ES | TI或RI | 能否进入串口中断 |
|---|---|---|---|
| 0 | 任意值 | 1 | 不能,总开关关闭 |
| 1 | 0 | 1 | 不能,串口分开关关闭 |
| 1 | 1 | 0 | 不能,没有串口请求 |
| 1 | 1 | 1 | 可以进入 |
串口中断号为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,也支持位寻址。它的各位如下:
| 位 | 符号 | 含义 |
|---|---|---|
| B7 | PX3 | 外部中断3优先级低位 |
| B6 | EX3 | 外部中断3允许位 |
| B5 | IE3 | 外部中断3请求标志 |
| B4 | IT3 | 外部中断3触发方式选择 |
| B3 | PX2 | 外部中断2优先级低位 |
| B2 | EX2 | 外部中断2允许位 |
| B1 | IE2 | 外部中断2请求标志 |
| B0 | IT2 | 外部中断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读写方向”的顺序检查。这样能把“完全不收发、乱码、只发送不接收、反复进中断”等现象分别定位到对应寄存器,而不是只反复修改一个数值。