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模拟器和大多数开发板的默认配置。
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
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 |
|---|---|---|---|
| 115200 | 1 | 0x01 | 0x00 |
| 57600 | 2 | 0x02 | 0x00 |
| 38400 | 3 | 0x03 | 0x00 |
| 19200 | 6 | 0x06 | 0x00 |
| 9600 | 12 | 0x0C | 0x00 |
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()后切换到中断模式。理解这两种模式下寄存器配置的差异,是后续实现驱动模式切换的核心基础。