3. UART硬件基础:通用异步收发传输器的工作原理,寄存器映射(LSR、THR、RBR、IER)

3.1 通用异步收发传输器(UART)工作原理

UART(Universal Asynchronous Receiver/Transmitter)是串行通信的核心硬件,负责将CPU的并行数据转换为串行比特流,并通过单根数据线发送;接收端则将串行比特流还原为并行数据。xv6使用的NS16550A UART芯片是PC平台的标准串口控制器,其工作原理遵循以下关键约定:

  • 异步特性:收发双方无需共享时钟信号,依靠预先约定的波特率(Baud Rate)和帧格式实现同步。
  • 帧结构:每个数据帧包含1个起始位(逻辑0)、5~8个数据位(LSB优先)、1个可选的奇偶校验位、1个或多个停止位(逻辑1)。
  • 全双工:发送(TX)和接收(RX)使用独立的数据线,可同时进行。
关键概念:xv6默认使用8N1帧格式(8数据位、无校验、1停止位),波特率为115200 bps。这是QEMU模拟器和大多数开发板的默认配置。
UART 8N1 帧结构(发送方向) 起始位 0 数据位0 LSB 数据位1 数据位2 数据位3 数据位4 数据位5 数据位6 数据位7 MSB 停止位 1 比特流传输方向(LSB → MSB) 帧总长度 = 1起始 + 8数据 + 1停止 = 10比特;无奇偶校验位

3.2 NS16550A寄存器映射与基地址

在x86平台,NS16550A UART的寄存器通过I/O端口(Port-mapped I/O)访问,基地址为0x3F8(对应COM1)。每个寄存器偏移量相对于基地址计算。由于寄存器宽度为8位,使用inb/outb指令读写。

偏移量 DLAB=0 读 DLAB=0 写 DLAB=1 读/写
+0 RBR(接收缓冲寄存器) THR(发送保持寄存器) DLL(除数锁存器低字节)
+1 IER(中断使能寄存器) IER(中断使能寄存器) DLM(除数锁存器高字节)
+2 IIR(中断标识寄存器) FCR(FIFO控制寄存器) —
+3 LCR(线路控制寄存器) LCR(线路控制寄存器) —
+5 LSR(线路状态寄存器) — —
寄存器复用机制:RBR/THR与DLL共享同一I/O地址(+0),通过LCR寄存器的DLAB位(Divisor Latch Access Bit,LCR[7])区分。当DLAB=1时,访问+0和+1得到波特率除数;DLAB=0时,+0读为RBR、写为THR。

3.3 核心寄存器详解

3.3.1 LSR – 线路状态寄存器(偏移+5,只读)

LSR是驱动判断UART状态的核心寄存器,轮询模式和中断模式都依赖其状态位。xv6在uart.c中通过读取LSR决定是否发送下一个字符或接收数据。

位 名称 含义 xv6用途
bit 0 DR(Data Ready) 接收FIFO中有数据可读 轮询接收时检查
bit 1 OE(Overrun Error) 接收数据溢出丢失 错误处理
bit 5 THRE(Transmitter Holding Register Empty) THR已空,可写入下一个字符 轮询发送时检查
bit 6 TEMT(Transmitter Empty) 发送移位寄存器也空,线路空闲 判断发送完成
// xv6 uart.c 中读取LSR的典型用法
// 轮询发送:等待THRE位置位
while((inb(COM1+5) & 0x20) == 0)  // LSR[5] = THRE
  ;
outb(COM1+0, c);  // 写入THR

// 轮询接收:检查DR位
if(inb(COM1+5) & 0x01)  // LSR[0] = DR
  c = inb(COM1+0);      // 读取RBR

3.3.2 THR – 发送保持寄存器(偏移+0,只写)

CPU将待发送的字符写入THR。UART内部自动将THR内容移入发送移位寄存器(TSR),然后逐位从TX引脚发出。当THR内容被取走后,LSR[5](THRE)自动置1,表示可以写入下一个字符。

注意:THR是一个8位寄存器,写入时只使用低8位。如果UART FIFO已启用(FCR[0]=1),写入THR的数据会先进入16字节发送FIFO,再由硬件自动移入TSR。xv6默认启用FIFO。

3.3.3 RBR – 接收缓冲寄存器(偏移+0,只读)

当UART从RX引脚完整接收到一个帧后,将数据存入RBR(或接收FIFO),同时LSR[0](DR)置1。CPU读取RBR后,DR位自动清零。如果FIFO启用,RBR读取的是FIFO头部数据。

3.3.4 IER – 中断使能寄存器(偏移+1,读/写)

IER控制UART在哪些事件发生时向CPU发送中断请求(IRQ)。xv6在中断初始化时配置IER,轮询模式下则清零IER以禁用中断。

位 中断类型 触发条件
bit 0 接收数据可用中断 RBR/FIFO中有数据(DR=1)
bit 1 发送保持寄存器空中断 THR空(THRE=1)
bit 2 接收线路状态中断 LSR中OE/PE/FE/BE错误位变化
bit 3 Modem状态中断 Modem控制信号变化(xv6未使用)
// xv6 中断模式初始化:使能接收中断
outb(COM1+1, 0x01);  // IER[0]=1,仅使能接收中断

// xv6 轮询模式:禁用所有中断
outb(COM1+1, 0x00);  // IER = 0x00
NS16550A UART 寄存器架构与数据流 CPU / xv6 inb/outb 指令 THR (写) RBR (读) IER (R/W) LSR (只读) 发送移位寄存器 (TSR) 接收移位寄存器 (RSR) TX RX 写数据 读数据 LSR状态位反馈 轮询模式 vs 中断模式的关键差异 轮询:CPU循环读取LSR,检查DR/THRE位 → 软件开销大,但实现简单 中断:IER使能对应中断 → UART硬件触发IRQ → CPU响应中断处理

3.4 波特率发生器与除数锁存器

UART使用一个可编程的波特率发生器从系统时钟分频产生发送/接收时钟。分频系数由16位除数(Divisor)决定,存储在DLL(+0)和DLM(+1)中,访问前需设置LCR[7]=1。

// 波特率计算公式
// 输入时钟频率 = 1.8432 MHz(标准值)
// 目标波特率 = 115200
// 除数 = 输入时钟频率 / (16 × 目标波特率)
//      = 1,843,200 / (16 × 115,200)
//      = 1,843,200 / 1,843,200
//      = 1

// xv6 初始化波特率设置
// LCR[7] = 1 使能除数锁存器访问
outb(COM1+3, 0x80);      // LCR = 0x80, DLAB=1
outb(COM1+0, 0x01);      // DLL = 0x01 (低字节)
outb(COM1+1, 0x00);      // DLM = 0x00 (高字节)
outb(COM1+3, 0x03);      // LCR = 0x03, DLAB=0, 8N1模式
常见波特率除数表(输入时钟1.8432 MHz)
波特率 除数(十进制) DLL DLM
11520010x010x00
5760020x020x00
3840030x030x00
1920060x060x00
9600120x0C0x00

3.5 轮询模式与中断模式的寄存器配置对比

配置项 轮询模式 中断模式
IER 0x00(全部禁用) 0x01(使能接收中断)
FCR 0x07(启用FIFO,清零) 0x07(启用FIFO,清零)
LCR 0x03(8N1,DLAB=0) 0x03(8N1,DLAB=0)
发送流程 循环等待LSR[5]→写THR 写THR后立即返回;THRE中断触发时继续发送
接收流程 循环检查LSR[0]→读RBR 中断处理函数中读RBR
CPU占用 高(忙等) 低(事件驱动)
设计启示:xv6在系统启动初期使用轮询模式(此时中断系统尚未初始化),在main.c调用uartinit()后切换到中断模式。理解这两种模式下寄存器配置的差异,是后续实现驱动模式切换的核心基础。