本文目标:掌握典型UART控制器的寄存器结构,能够编写底层驱动代码
目录
- [典型UART IP架构](#典型UART IP架构)
- [16550 UART寄存器详解](#16550 UART寄存器详解)
-
- [THR/RBR - 发送/接收缓冲寄存器](#THR/RBR - 发送/接收缓冲寄存器)
- [IER - 中断使能寄存器](#IER - 中断使能寄存器)
- [IIR - 中断标识寄存器](#IIR - 中断标识寄存器)
- [FCR - FIFO控制寄存器](#FCR - FIFO控制寄存器)
- [LCR - 线路控制寄存器](#LCR - 线路控制寄存器)
- [LSR - 线路状态寄存器](#LSR - 线路状态寄存器)
- [DLL/DLM - 分频器锁存寄存器](#DLL/DLM - 分频器锁存寄存器)
- [ARM PL011寄存器详解](#ARM PL011寄存器详解)
-
- 寄存器地址映射(简化版)
- [UARTDR - 数据寄存器](#UARTDR - 数据寄存器)
- [UARTFR - 标志寄存器](#UARTFR - 标志寄存器)
- [UARTLCR_H - 线路控制](#UARTLCR_H - 线路控制)
- FIFO配置与管理
- 中断控制与状态
- 波特率配置
- 初始化配置流程
-
- [16550 UART初始化](#16550 UART初始化)
- [ARM PL011初始化](#ARM PL011初始化)
- 寄存器访问代码示例
- 延伸阅读
典型UART IP架构
通用UART控制器框图
+---------------------------+
APB总线 <---------->| 寄存器接口 |
+---------------------------+
|
+---------------------+---------------------+
| | |
+----------+ +----------+ +----------+
| 发送器 | | 接收器 | | 波特率 |
| Transmitter | Receiver | | 发生器 |
+----------+ +----------+ +----------+
| | |
| | 时钟分频
TX FIFO RX FIFO |
| | |
V V V
TX引脚 RX引脚 采样时钟
主要功能模块
-
发送器(Transmitter)
- 并行数据 → 串行输出
- 添加起始位、停止位、校验位
- TX FIFO缓冲
-
接收器(Receiver)
- 串行输入 → 并行数据
- 过采样、边沿检测
- 错误检测(帧错误、校验错误、溢出)
- RX FIFO缓冲
-
波特率发生器(BRG)
- 时钟分频产生波特率
- 可编程分频系数
-
中断控制器
- 多种中断源
- 中断使能/屏蔽
- 中断优先级
16550 UART寄存器详解
寄存器地址映射
| 偏移 | DLAB=0 读 | DLAB=0 写 | DLAB=1 读/写 | 名称 |
|---|---|---|---|---|
| 0x00 | RBR | THR | DLL | 接收/发送缓冲 / 分频低字节 |
| 0x01 | IER | IER | DLM | 中断使能 / 分频高字节 |
| 0x02 | IIR | FCR | - | 中断标识 / FIFO控制 |
| 0x03 | LCR | LCR | LCR | 线路控制 |
| 0x04 | MCR | MCR | MCR | Modem控制 |
| 0x05 | LSR | - | - | 线路状态 |
| 0x06 | MSR | - | - | Modem状态 |
| 0x07 | SCR | SCR | SCR | 暂存寄存器 |
DLAB位:Line Control Register (LCR) 的第7位,用于切换寄存器0/1的功能
THR/RBR - 发送/接收缓冲寄存器
地址:Base + 0x00
发送缓冲寄存器(THR - Transmit Holding Register)
写操作,DLAB=0
Bit 7-0: 待发送数据(5-8位,取决于LCR配置)
使用:
*THR = 0x55; // 发送字节0x55
接收缓冲寄存器(RBR - Receive Buffer Register)
读操作,DLAB=0
Bit 7-0: 接收到的数据
使用:
uint8_t data = *RBR; // 读取接收数据
IER - 中断使能寄存器
地址:Base + 0x01,DLAB=0
Bit 7-4: 保留
Bit 3: Modem状态中断使能
Bit 2: 接收线路状态中断使能(帧错误、校验错误等)
Bit 1: 发送保持寄存器空中断使能(THRE)
Bit 0: 接收数据可用中断使能(RDA)
配置示例:
#define IER_RDA (1<<0) // 接收数据中断
#define IER_THRE (1<<1) // 发送空中断
#define IER_RLS (1<<2) // 线路状态中断
// 使能接收和发送中断
*IER = IER_RDA | IER_THRE;
IIR - 中断标识寄存器
地址:Base + 0x02,读操作
Bit 7-6: FIFO使能状态
00 = 无FIFO
11 = FIFO使能
Bit 5-4: 保留
Bit 3-1: 中断ID(优先级从高到低)
000 = Modem状态变化
001 = 发送保持寄存器空(THRE)
010 = 接收数据可用(RDA)
011 = 接收线路状态(错误)
110 = 字符超时(FIFO模式)
Bit 0: 中断挂起标志
0 = 有中断挂起
1 = 无中断
中断处理示例:
uint8_t iir = *IIR;
if (!(iir & 0x01)) { // 有中断挂起
switch ((iir >> 1) & 0x07) {
case 0b011: // 接收线路状态错误
handle_line_error();
break;
case 0b010: // 接收数据可用
handle_rx_data();
break;
case 0b110: // 字符超时
handle_rx_timeout();
break;
case 0b001: // 发送缓冲区空
handle_tx_empty();
break;
}
}
FCR - FIFO控制寄存器
地址:Base + 0x02,写操作
Bit 7-6: 接收FIFO触发级别
00 = 1字节
01 = 4字节
10 = 8字节
11 = 14字节
Bit 5-3: 保留
Bit 2: 清空发送FIFO(写1自清除)
Bit 1: 清空接收FIFO(写1自清除)
Bit 0: FIFO使能
0 = 禁用FIFO(16450模式)
1 = 使能FIFO
配置示例:
#define FCR_ENABLE (1<<0)
#define FCR_RX_RESET (1<<1)
#define FCR_TX_RESET (1<<2)
#define FCR_TRIGGER_14 (3<<6)
// 使能FIFO,14字节触发,清空FIFO
*FCR = FCR_ENABLE | FCR_RX_RESET | FCR_TX_RESET | FCR_TRIGGER_14;
LCR - 线路控制寄存器
地址:Base + 0x03
Bit 7: DLAB(分频器锁存访问位)
0 = 访问THR/RBR/IER
1 = 访问DLL/DLM
Bit 6: 设置Break信号
1 = 强制TX输出低电平
Bit 5: 强制校验位
Bit 4-3: 校验位选择
00 = 无校验
01 = 奇校验
11 = 偶校验
10 = Mark(校验位恒为1)
10 = Space(校验位恒为0)
Bit 2: 停止位数
0 = 1个停止位
1 = 1.5个停止位(5数据位)或2个停止位(6/7/8数据位)
Bit 1-0: 数据位长度
00 = 5位
01 = 6位
10 = 7位
11 = 8位
配置示例:8N1(8数据位,无校验,1停止位)
#define LCR_8N1 0x03 // 0b00000011
*LCR = LCR_8N1;
配置示例:8E1(8数据位,偶校验,1停止位)
#define LCR_8E1 0x1B // 0b00011011
*LCR = LCR_8E1;
LSR - 线路状态寄存器
地址:Base + 0x05,只读
Bit 7: FIFO错误(FIFO中至少有一个错误)
Bit 6: 发送器空且空闲(TEMT)
Bit 5: 发送保持寄存器空(THRE)
Bit 4: Break中断
Bit 3: 帧错误(FE)
Bit 2: 校验错误(PE)
Bit 1: 溢出错误(OE)
Bit 0: 数据就绪(DR)
使用示例:
#define LSR_DR (1<<0) // 数据就绪
#define LSR_OE (1<<1) // 溢出错误
#define LSR_PE (1<<2) // 校验错误
#define LSR_FE (1<<3) // 帧错误
#define LSR_THRE (1<<5) // 发送缓冲区空
#define LSR_TEMT (1<<6) // 发送器完全空
// 轮询发送
while (!(*LSR & LSR_THRE)); // 等待发送缓冲区空
*THR = data;
// 轮询接收
while (!(*LSR & LSR_DR)); // 等待数据就绪
data = *RBR;
// 检查错误
uint8_t lsr = *LSR;
if (lsr & (LSR_OE | LSR_PE | LSR_FE)) {
// 处理错误
}
DLL/DLM - 分频器锁存寄存器
地址:Base + 0x00 / 0x01,DLAB=1
DLL (Divisor Latch Low) - 分频系数低8位
DLM (Divisor Latch High) - 分频系数高8位
波特率计算:
Baud = Clock / (16 × Divisor)
Divisor = Clock / (16 × Baud)
示例:
时钟 = 1.8432 MHz
波特率 = 115200 bps
Divisor = 1843200 / (16 × 115200) = 1
配置代码:
*LCR |= (1<<7); // 设置DLAB=1
*DLL = divisor & 0xFF;
*DLM = (divisor >> 8) & 0xFF;
*LCR &= ~(1<<7); // 清除DLAB
ARM PL011寄存器详解
寄存器地址映射(简化版)
| 偏移 | 名称 | 说明 |
|---|---|---|
| 0x00 | UARTDR | 数据寄存器 |
| 0x04 | UARTRSR/ECR | 接收状态/错误清除 |
| 0x18 | UARTFR | 标志寄存器 |
| 0x24 | UARTIBRD | 整数波特率除数 |
| 0x28 | UARTFBRD | 小数波特率除数 |
| 0x2C | UARTLCR_H | 线路控制 |
| 0x30 | UARTCR | 控制寄存器 |
| 0x38 | UARTIMSC | 中断屏蔽 |
| 0x3C | UARTRIS | 原始中断状态 |
| 0x40 | UARTMIS | 屏蔽后中断状态 |
| 0x44 | UARTICR | 中断清除 |
UARTDR - 数据寄存器
写操作:发送数据
读操作:接收数据 + 错误标志
Bit 11: 溢出错误(OE)
Bit 10: Break错误(BE)
Bit 9: 校验错误(PE)
Bit 8: 帧错误(FE)
Bit 7-0: 数据
使用:
// 发送
UART0->DR = 0x55;
// 接收
uint32_t dr = UART0->DR;
if (dr & 0x0F00) { // 检查错误位
// 处理错误
}
uint8_t data = dr & 0xFF;
UARTFR - 标志寄存器
Bit 7: 发送FIFO空(TXFE)
Bit 6: 接收FIFO满(RXFF)
Bit 5: 发送FIFO满(TXFF)
Bit 4: 接收FIFO空(RXFE)
Bit 3: UART忙(BUSY)
Bit 0-2: 保留
轮询发送:
while (UART0->FR & (1<<5)); // 等待TX FIFO不满
UART0->DR = data;
轮询接收:
while (UART0->FR & (1<<4)); // 等待RX FIFO不空
data = UART0->DR;
UARTLCR_H - 线路控制
Bit 7: 粘滞校验使能(SPS)
Bit 6-5: 字长(WLEN)
00 = 5位
01 = 6位
10 = 7位
11 = 8位
Bit 4: 使能FIFO(FEN)
Bit 3: 两个停止位使能(STP2)
Bit 2: 偶校验选择(EPS)
0 = 奇校验
1 = 偶校验
Bit 1: 校验使能(PEN)
Bit 0: 发送Break
配置8N1 + FIFO:
UART0->LCRH = (3<<5) | (1<<4); // 8位 + FIFO使能
FIFO配置与管理
FIFO深度配置
16550 UART:
c
// 16字节FIFO,触发级别14字节
#define FIFO_TRIGGER_1 (0<<6)
#define FIFO_TRIGGER_4 (1<<6)
#define FIFO_TRIGGER_8 (2<<6)
#define FIFO_TRIGGER_14 (3<<6)
*FCR = (1<<0) | FIFO_TRIGGER_14; // 使能FIFO,14字节触发
选择原则:
- 低延迟应用:触发级别设置为1字节
- 高吞吐量应用:触发级别设置为接近满(14字节)
- 折中:触发级别设置为中等(4-8字节)
FIFO复位
c
// 清空RX和TX FIFO
*FCR |= (1<<1) | (1<<2); // RX_RESET | TX_RESET
注意:这些位是自清除位,写1后硬件自动清0
中断控制与状态
中断优先级(16550)
从高到低:
- 接收线路状态中断(帧错误、校验错误、溢出、Break)
- 接收数据可用中断(FIFO达到触发级别)
- 字符超时中断(FIFO有数据但未达到触发级别,超时)
- 发送保持寄存器空中断(THR空)
- Modem状态中断(CTS/DSR/RI/DCD变化)
中断处理流程
c
void UART_IRQHandler(void) {
uint8_t iir = UART->IIR;
if (iir & 0x01) {
return; // 无中断挂起
}
uint8_t int_id = (iir >> 1) & 0x07;
switch (int_id) {
case 0b011: // 接收线路状态
uint8_t lsr = UART->LSR; // 读取LSR清除中断
// 处理错误
break;
case 0b010: // 接收数据可用
case 0b110: // 字符超时
while (UART->LSR & 0x01) { // 数据就绪
uint8_t data = UART->RBR;
rx_buffer_put(data);
}
break;
case 0b001: // 发送缓冲区空
if (tx_buffer_has_data()) {
UART->THR = tx_buffer_get();
} else {
UART->IER &= ~(1<<1); // 禁用THRE中断
}
break;
}
}
波特率配置
16550波特率计算
c
// 公式:Baud = Clock / (16 × Divisor)
uint16_t calculate_divisor(uint32_t clock, uint32_t baud) {
return (clock / (16 * baud));
}
void set_baudrate(uint32_t baud) {
uint16_t divisor = calculate_divisor(UART_CLOCK, baud);
uint8_t lcr = UART->LCR;
UART->LCR = lcr | (1<<7); // 设置DLAB
UART->DLL = divisor & 0xFF;
UART->DLM = (divisor >> 8) & 0xFF;
UART->LCR = lcr; // 恢复DLAB
}
PL011波特率配置(分数分频)
c
// 公式:Baud = Clock / (16 × (IBRD + FBRD/64))
void pl011_set_baudrate(uint32_t clock, uint32_t baud) {
uint32_t divider = (clock * 4) / baud; // ×64 / 16 = ×4
uint32_t ibrd = divider / 64;
uint32_t fbrd = divider % 64;
UART0->IBRD = ibrd;
UART0->FBRD = fbrd;
}
// 示例:48MHz时钟,115200波特率
// divider = 48000000 * 4 / 115200 = 1666.67
// ibrd = 1666 / 64 = 26
// fbrd = 1666 % 64 = 2
初始化配置流程
16550 UART初始化
c
void uart_init(uint32_t baud) {
// 1. 禁用UART(如果需要)
// 2. 配置波特率
uint16_t divisor = UART_CLOCK / (16 * baud);
UART->LCR = (1<<7); // DLAB=1
UART->DLL = divisor & 0xFF;
UART->DLM = (divisor >> 8) & 0xFF;
// 3. 配置帧格式:8N1
UART->LCR = 0x03; // 8位,无校验,1停止位,DLAB=0
// 4. 使能并配置FIFO
UART->FCR = 0xC7; // 使能FIFO,清空FIFO,14字节触发
// 5. 使能中断
UART->IER = 0x01; // 使能接收数据中断
// 6. 配置Modem控制(如果需要)
UART->MCR = 0x00; // 不使用硬件流控
}
ARM PL011初始化
c
void pl011_init(uint32_t clock, uint32_t baud) {
// 1. 禁用UART
UART0->CR = 0;
// 2. 等待UART空闲
while (UART0->FR & (1<<3)); // BUSY
// 3. 清空FIFO
UART0->LCRH &= ~(1<<4); // 禁用FIFO
// 4. 配置波特率
uint32_t divider = (clock * 4) / baud;
UART0->IBRD = divider / 64;
UART0->FBRD = divider % 64;
// 5. 配置帧格式和FIFO
UART0->LCRH = (3<<5) | (1<<4); // 8位,FIFO使能
// 6. 使能UART
UART0->CR = (1<<0) | (1<<8) | (1<<9); // UARTEN | TXE | RXE
}
寄存器访问代码示例
寄存器结构体定义
c
typedef struct {
volatile uint32_t DR; // 0x00: 数据寄存器
volatile uint32_t RSR_ECR; // 0x04: 状态/错误清除
uint32_t RESERVED0[4];
volatile uint32_t FR; // 0x18: 标志寄存器
uint32_t RESERVED1[1];
volatile uint32_t ILPR; // 0x20: IrDA
volatile uint32_t IBRD; // 0x24: 整数波特率
volatile uint32_t FBRD; // 0x28: 小数波特率
volatile uint32_t LCR_H; // 0x2C: 线路控制
volatile uint32_t CR; // 0x30: 控制寄存器
volatile uint32_t IFLS; // 0x34: FIFO级别选择
volatile uint32_t IMSC; // 0x38: 中断屏蔽
volatile uint32_t RIS; // 0x3C: 原始中断状态
volatile uint32_t MIS; // 0x40: 屏蔽中断状态
volatile uint32_t ICR; // 0x44: 中断清除
volatile uint32_t DMACR; // 0x48: DMA控制
} UART_TypeDef;
#define UART0_BASE 0x40004000
#define UART0 ((UART_TypeDef *)UART0_BASE)
完整驱动示例
c
#define RX_BUFFER_SIZE 256
static uint8_t rx_buffer[RX_BUFFER_SIZE];
static volatile uint16_t rx_head = 0;
static volatile uint16_t rx_tail = 0;
void uart_putchar(char c) {
while (UART0->FR & (1<<5)); // 等待TX FIFO不满
UART0->DR = c;
}
void uart_puts(const char *str) {
while (*str) {
uart_putchar(*str++);
}
}
int uart_getchar(void) {
if (rx_head == rx_tail) {
return -1; // 缓冲区空
}
uint8_t c = rx_buffer[rx_tail];
rx_tail = (rx_tail + 1) % RX_BUFFER_SIZE;
return c;
}
void UART0_IRQHandler(void) {
uint32_t mis = UART0->MIS;
if (mis & (1<<4)) { // RX中断
while (!(UART0->FR & (1<<4))) { // RX FIFO不空
uint8_t c = UART0->DR & 0xFF;
uint16_t next_head = (rx_head + 1) % RX_BUFFER_SIZE;
if (next_head != rx_tail) {
rx_buffer[rx_head] = c;
rx_head = next_head;
}
}
UART0->ICR = (1<<4); // 清除RX中断
}
}
延伸阅读
- \[03-时钟与波特率] - 波特率计算详解
- \[08-中断驱动vs轮询vs DMA] - 不同传输模式的实现
- \[09-FIFO管理与流控] - FIFO深度选择策略
上一篇 :\[06-时序与同步]
下一篇:\[08-中断驱动vs轮询vs DMA]
✨ 感谢你的阅读 ✨
技术之路漫漫,分享是为了更好地交流。
如果本文的内容对你有启发,希望能得到你的支持:
👍 点赞 | ⭐ 收藏 | 👀 关注
如果你在调试过程中遇到了其他问题,
欢迎在 评论区 💬 留言,我们一起探讨。
关注我,一起交流底层开发的那些事儿!🚀