【串口技术系列文档 07】UART控制器寄存器详解

本文目标:掌握典型UART控制器的寄存器结构,能够编写底层驱动代码


目录


典型UART IP架构

通用UART控制器框图

复制代码
                    +---------------------------+
APB总线 <---------->| 寄存器接口                 |
                    +---------------------------+
                              |
        +---------------------+---------------------+
        |                     |                     |
   +----------+          +----------+          +----------+
   | 发送器   |          | 接收器   |          | 波特率   |
   | Transmitter        | Receiver |          | 发生器   |
   +----------+          +----------+          +----------+
        |                     |                     |
        |                     |                 时钟分频
   TX FIFO              RX FIFO                    |
        |                     |                     |
        V                     V                     V
      TX引脚              RX引脚              采样时钟

主要功能模块

  1. 发送器(Transmitter)

    • 并行数据 → 串行输出
    • 添加起始位、停止位、校验位
    • TX FIFO缓冲
  2. 接收器(Receiver)

    • 串行输入 → 并行数据
    • 过采样、边沿检测
    • 错误检测(帧错误、校验错误、溢出)
    • RX FIFO缓冲
  3. 波特率发生器(BRG)

    • 时钟分频产生波特率
    • 可编程分频系数
  4. 中断控制器

    • 多种中断源
    • 中断使能/屏蔽
    • 中断优先级

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)

从高到低:

  1. 接收线路状态中断(帧错误、校验错误、溢出、Break)
  2. 接收数据可用中断(FIFO达到触发级别)
  3. 字符超时中断(FIFO有数据但未达到触发级别,超时)
  4. 发送保持寄存器空中断(THR空)
  5. 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]


✨ 感谢你的阅读 ✨

技术之路漫漫,分享是为了更好地交流。

如果本文的内容对你有启发,希望能得到你的支持:

👍 点赞 | ⭐ 收藏 | 👀 关注

如果你在调试过程中遇到了其他问题,

欢迎在 评论区 💬 留言,我们一起探讨。

关注我,一起交流底层开发的那些事儿!🚀


相关推荐
甜甜的大香瓜2 小时前
【MT32F006】MT32F006之TM1621C驱动LCD
单片机
硅基手札2 小时前
【串口技术系列文档 09】FIFO管理与流控
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
硅基手札2 小时前
【串口技术系列文档 08】中断驱动vs轮询vs DMA
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
尼喃2 小时前
OVP芯片完整方案:前级瞬态钳位、持续过压隔离与后端保护
嵌入式硬件
不会代码的小猴2 小时前
认识HAL库
单片机·嵌入式硬件
莫小墨2 小时前
数字地、模拟地、功率地
嵌入式硬件
Zw-awa2 小时前
把 AO 接回来:黑线和白底之间到底差了多少
stm32
BSD_HY2 小时前
薄膜开关矩阵按键的GPIO扩展芯片选型与中断响应延迟
单片机·线性代数·矩阵
xunyan62342 小时前
51 单片机LED闪烁(SDCC + Notepad++ + STC-ISP/普中ISP)
单片机·notepad++·接口隔离原则