Skip to content
约 0 字 · 预计阅读 0 分钟

寄存器详解

什么是寄存器?

寄存器是 CPU 内部的高速存储单元,是处理器最接近执行单元的存储层次。在嵌入式开发中,我们常说的"寄存器操作"通常指的是 外设寄存器——通过内存映射方式访问的硬件控制接口。

寄存器层次结构

┌─────────────────────────────────────────────────────────────┐
│                    存储层次结构                              │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    CPU 寄存器                        │   │
│  │         R0-R15, CPSR, SPSR (ARM)                   │   │
│  │              访问延迟: 0-1 周期                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                          ▲                                  │
│                          │                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    Cache                            │   │
│  │              L1 / L2 缓存                           │   │
│  │              访问延迟: 1-10 周期                     │   │
│  └─────────────────────────────────────────────────────┘   │
│                          ▲                                  │
│                          │                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    内存 (RAM)                        │   │
│  │              访问延迟: 10-100 周期                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                          ▲                                  │
│                          │                                  │
│  ┌─────────────────────────────────────────────────────┐   │
│  │              外设寄存器 (Memory-Mapped)              │   │
│  │         GPIO, UART, SPI, Timer...                   │   │
│  │              访问延迟: 取决于总线                    │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

两类寄存器

类型说明示例
CPU 寄存器处理器内部,汇编直接访问R0-R15, PC, SP, LR
外设寄存器内存映射,C 语言指针访问GPIO_ODR, USART_DR

内存映射原理

统一编址

ARM 采用 统一编址 方式,外设寄存器映射到内存地址空间:

┌─────────────────────────────────────────────────────────────┐
│                  ARM Cortex-M 地址映射                       │
│                                                             │
│  0xFFFF_FFFF ┌────────────────────────────────────────┐     │
│              │         系统区域                        │     │
│  0xE000_0000 ├────────────────────────────────────────┤     │
│              │         外设区域          │     │
│              │         GPIO, UART, SPI, Timer...      │     │
│  0x4000_0000 ├────────────────────────────────────────┤     │
│              │         SRAM 区域                       │     │
│              │         运行时数据                      │     │
│  0x2000_0000 ├────────────────────────────────────────┤     │
│              │         Flash/ROM 区域                  │     │
│              │         代码 + 常量                     │     │
│  0x0000_0000 └────────────────────────────────────────┘     │
└─────────────────────────────────────────────────────────────┘

寄存器访问的本质

c
#define GPIOA_ODR   (*(volatile uint32_t *)0x40020014)

GPIOA_ODR = 0x01;

这段代码的底层操作:

┌─────────────────────────────────────────────────────────────┐
│                    C 代码执行过程                            │
│                                                             │
│  1. 编译器解析                                               │
│     GPIOA_ODR = 0x01                                        │
│           │                                                 │
│           ▼                                                 │
│  2. 地址计算                                                 │
│     目标地址 = 0x40020014                                    │
│           │                                                 │
│           ▼                                                 │
│  3. 生成汇编指令 (ARM)                                       │
│     LDR  R0, =0x40020014    ; 加载地址到 R0                  │
│     MOV  R1, #1             ; 准备写入的值                   │
│     STR  R1, [R0]           ; 存储到内存                     │
│           │                                                 │
│           ▼                                                 │
│  4. CPU 执行                                                 │
│     - 发送地址 0x40020014 到地址总线                          │
│     - 发送数据 0x01 到数据总线                                │
│     - 发送写信号到控制总线                                    │
│           │                                                 │
│           ▼                                                 │
│  5. 总线矩阵路由                                              │
│     - 识别地址属于 AHB1 总线                                  │
│     - 路由到 GPIOA 控制器                                    │
│           │                                                 │
│           ▼                                                 │
│  6. GPIOA 硬件响应                                           │
│     - ODR 寄存器被写入 0x01                                  │
│     - PA0 引脚输出高电平                                     │
└─────────────────────────────────────────────────────────────┘

volatile 关键字

为什么需要 volatile?

c
#define GPIO_ODR  (*(uint32_t *)0x40020014)

void led_on(void)
{
    GPIO_ODR = 0x01;
    GPIO_ODR = 0x01;  
}

优化后的汇编:

asm
led_on:
    LDR  R0, =0x40020014
    MOV  R1, #1
    STR  R1, [R0]      ; 只有一条 STR 指令
    BX   LR

编译器认为两次写入相同值是冗余操作,直接优化掉了一次。但对于硬件寄存器,每次写入都有意义!

volatile 的作用

c
#define GPIO_ODR  (*(volatile uint32_t *)0x40020014)

void led_on(void)
{
    GPIO_ODR = 0x01;
    GPIO_ODR = 0x01;  
}

优化后的汇编:

asm
led_on:
    LDR  R0, =0x40020014
    MOV  R1, #1
    STR  R1, [R0]      ; 第一次写入
    STR  R1, [R0]      ; 第二次写入(保留)
    BX   LR

volatile 的三个特性

特性说明
禁止缓存每次访问都从内存读取,不使用寄存器缓存
禁止优化不会因为"冗余"而被优化掉
保证顺序在同一 volatile 变量访问点,编译器不会重排指令

volatile 的局限性

c
volatile uint32_t *reg = (volatile uint32_t *)0x40020014;

*reg = 0x01;  
*reg = 0x02;

volatile 保证编译器不优化这两次写入,但 不保证 CPU 乱序执行。如果需要严格顺序,需要使用 内存屏障

c
#define DMB()  __asm__ volatile ("dmb" ::: "memory")

*reg = 0x01;
DMB();       
*reg = 0x02;

位操作详解

常用位操作

c
#define REG  (*(volatile uint32_t *)0x40020000)

#define BIT(n)      (1U << (n))
#define SET_BIT(n)  (REG |= BIT(n))
#define CLR_BIT(n)  (REG &= ~BIT(n))
#define GET_BIT(n)  ((REG >> (n)) & 1)
#define TOG_BIT(n)  (REG ^= BIT(n))

位操作底层原理

设置第 3 位 (REG |= BIT(3)):

  REG:     1010 0100
  BIT(3):  0000 1000
           ───────── OR
  结果:     1010 1100

清除第 3 位 (REG &= ~BIT(3)):

  REG:     1010 1100
  ~BIT(3): 1111 0111
           ───────── AND
  结果:     1010 0100

翻转第 3 位 (REG ^= BIT(3)):

  REG:     1010 1100
  BIT(3):  0000 1000
           ───────── XOR
  结果:     1010 0100

读取-修改-写入 问题

c
REG |= BIT(3);

这条语句的底层操作:

asm
LDR  R0, [REG_ADDR]    ; 1. 读取
ORR  R0, R0, #0x08     ; 2. 修改
STR  R0, [REG_ADDR]    ; 3. 写入

问题:如果在读取和写入之间发生中断,中断中也修改了同一个寄存器,会导致数据丢失!

┌─────────────────────────────────────────────────────────────┐
│                    竞态条件示意                              │
│                                                             │
│  主程序                    中断                              │
│  ──────                    ────                              │
│  LDR R0, [REG]     ; R0=0x00                                 │
│       │                                                     │
│       │              ────►  LDR R1, [REG]    ; R1=0x00      │
│       │                     ORR R1, R1, #0x10               │
│       │                     STR R1, [REG]    ; REG=0x10     │
│       │                                                     │
│  ORR R0, R0, #0x08  ; R0=0x08                               │
│  STR R0, [REG]      ; REG=0x08  (丢失了中断写入的 0x10!)     │
└─────────────────────────────────────────────────────────────┘

解决方案:关闭中断

c
void safe_set_bit(uint8_t bit)
{
    uint32_t primask = __get_PRIMASK();
    __disable_irq();
    
    REG |= BIT(bit);
    
    __set_PRIMASK(primask);
}

位带操作

ARM Cortex-M 支持 位带,允许对单个位进行原子操作:

┌─────────────────────────────────────────────────────────────┐
│                    位带映射原理                              │
│                                                             │
│  别名区          位带区                                      │
│  (Alias)         (Bit-band)                                 │
│                                                             │
│  0x4200_0000 ──► 0x4000_0000 的 bit 0                       │
│  0x4200_0004 ──► 0x4000_0000 的 bit 1                       │
│  0x4200_0008 ──► 0x4000_0000 的 bit 2                       │
│  ...                                                        │
│  0x43FF_FFFC ──► 0x400F_FFFF 的 bit 31                      │
│                                                             │
│  映射公式:                                                   │
│  alias_addr = 0x42000000 + (byte_offset * 32) + (bit_num * 4)│
└─────────────────────────────────────────────────────────────┘

位带宏定义:

c
#define BITBAND(addr, bit)  \
    ((volatile uint32_t *)(0x42000000 + ((uint32_t)(addr) - 0x40000000) * 32 + (bit) * 4))

#define GPIOA_ODR   (*(volatile uint32_t *)0x40020014)
#define PA0_OUT     (*BITBAND(&GPIOA_ODR, 0))

void led_toggle(void)
{
    PA0_OUT = !PA0_OUT;  
}

寄存器结构体定义

传统定义方式

c
#define GPIOA_BASE      0x40020000
#define GPIOA_MODER     (*(volatile uint32_t *)(GPIOA_BASE + 0x00))
#define GPIOA_OTYPER    (*(volatile uint32_t *)(GPIOA_BASE + 0x04))
#define GPIOA_OSPEEDR   (*(volatile uint32_t *)(GPIOA_BASE + 0x08))
#define GPIOA_PUPDR     (*(volatile uint32_t *)(GPIOA_BASE + 0x0C))
#define GPIOA_IDR       (*(volatile uint32_t *)(GPIOA_BASE + 0x10))
#define GPIOA_ODR       (*(volatile uint32_t *)(GPIOA_BASE + 0x14))
#define GPIOA_BSRR      (*(volatile uint32_t *)(GPIOA_BASE + 0x18))

结构体方式

c
typedef struct {
    volatile uint32_t MODER;     
    volatile uint32_t OTYPER;    
    volatile uint32_t OSPEEDR;   
    volatile uint32_t PUPDR;     
    volatile uint32_t IDR;       
    volatile uint32_t ODR;       
    volatile uint32_t BSRR;      
    volatile uint32_t LCKR;      
    volatile uint32_t AFR[2];    
} GPIO_TypeDef;

#define GPIOA   ((GPIO_TypeDef *)0x40020000)

void gpio_init(void)
{
    GPIOA->MODER |= (1 << 0);    
    GPIOA->ODR |= (1 << 0);      
}

结构体对齐问题

c
typedef struct {
    volatile uint32_t CR;        
    volatile uint32_t CFGR;      
    volatile uint32_t CIR;       
    volatile uint8_t  AHBRSTR[4]; 
    volatile uint32_t APB2RSTR;  
} RCC_TypeDef;

问题:AHBRSTR 是 4 个字节,但每个元素是 uint8_t,会导致后续偏移错误!

正确做法:

c
typedef struct {
    volatile uint32_t CR;        
    volatile uint32_t CFGR;      
    volatile uint32_t CIR;       
    volatile uint32_t RESERVED0; 
    volatile uint32_t AHBRSTR;   
    volatile uint32_t APB2RSTR;  
} RCC_TypeDef;

或者使用 __packed

c
typedef __packed struct {
    volatile uint32_t CR;
    volatile uint32_t CFGR;
    volatile uint32_t CIR;
    volatile uint8_t  AHBRSTR[4];
    volatile uint32_t APB2RSTR;
} RCC_TypeDef;

常见寄存器类型

数据寄存器

c
typedef struct {
    volatile uint32_t SR;    
    volatile uint32_t DR;    
    volatile uint32_t BRR;   
    volatile uint32_t CR1;   
} USART_TypeDef;

void usart_send_byte(USART_TypeDef *usart, uint8_t data)
{
    while (!(usart->SR & (1 << 7)));  
    usart->DR = data;
}

uint8_t usart_recv_byte(USART_TypeDef *usart)
{
    while (!(usart->SR & (1 << 5)));  
    return (uint8_t)usart->DR;
}

状态寄存器

USART SR 寄存器布局:
┌─────────────────────────────────────────────────────────────┐
│ Bit 31-10 | 9  | 8  | 7  | 6  | 5  | 4  | 3  | 2  | 1  | 0  │
│  Reserved │CTS │LBD │TXE │TC  |RXNE|IDLE|ORE |NE  |FE  |PE  │
└─────────────────────────────────────────────────────────────┘

TXE (Bit 7): 发送数据寄存器空
TC  (Bit 6): 发送完成
RXNE(Bit 5): 接收数据寄存器非空

控制寄存器

c
void usart_enable(USART_TypeDef *usart)
{
    usart->CR1 |= (1 << 13);  
    usart->CR1 |= (1 << 3);   
    usart->CR1 |= (1 << 2);   
}

写 1 清零寄存器

某些状态位通过 写 1 清零,而不是写 0:

c
#define EXTI_PR   (*(volatile uint32_t *)0x40013C14)

void exti_clear_pending(uint8_t line)
{
    EXTI_PR = (1 << line);  
}
写 1 清零原理:
┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  PR 寄存器:     0000 1010  (bit 1 和 bit 3 挂起)            │
│                                                             │
│  写入值:        0000 1010                                   │
│                                                             │
│  内部逻辑:      PR_new = PR_old & ~write_value              │
│                 = 0000 1010 & 1111 0101                     │
│                 = 0000 0000                                 │
│                                                             │
│  结果:          bit 1 和 bit 3 被清除                       │
└─────────────────────────────────────────────────────────────┘

寄存器访问优化

批量操作

c
void gpio_set_multiple(uint16_t pins)
{
    GPIOA->BSRR = pins;          
    GPIOA->BSRR = pins << 16;    
}

BSRR 寄存器设计:

BSRR 寄存器布局:
┌─────────────────────────────────────────────────────────────┐
│ Bit 31-16 | Bit 15-0                                        │
│ BR (复位) | BS (置位)                                        │
│ 写 1 复位 | 写 1 置位                                        │
└─────────────────────────────────────────────────────────────┘

写 0x00010002:
- BSRR[1] = 1  → PA1 置高
- BR[16] = 1   → PA0 置低

原子操作

c
void gpio_toggle_atomic(uint8_t pin)
{
    GPIOA->ODR ^= (1 << pin);  
}

void gpio_set_atomic(uint8_t pin)
{
    GPIOA->BSRR = (1 << pin);  
}

void gpio_clr_atomic(uint8_t pin)
{
    GPIOA->BSRR = (1 << (pin + 16));  
}

调试技巧

寄存器转储

c
void dump_gpio_reg(GPIO_TypeDef *gpio)
{
    printf("MODER:   0x%08X\n", gpio->MODER);
    printf("OTYPER:  0x%08X\n", gpio->OTYPER);
    printf("OSPEEDR: 0x%08X\n", gpio->OSPEEDR);
    printf("PUPDR:   0x%08X\n", gpio->PUPDR);
    printf("IDR:     0x%08X\n", gpio->IDR);
    printf("ODR:     0x%08X\n", gpio->ODR);
}

寄存器监控

c
#define REG_MONITOR(reg, expected) \
    do { \
        uint32_t val = (reg); \
        if (val != (expected)) { \
            printf("REG mismatch: expected 0x%08X, got 0x%08X\n", \
                   (expected), val); \
        } \
    } while(0)

总结

概念要点
内存映射外设寄存器映射到内存地址空间
volatile禁止优化,保证每次访问都从内存读写
位操作设置、清除、翻转、读取
位带单个位的原子操作
结构体封装寄存器组,注意对齐
原子性注意 RMW 问题,使用关中断或原子寄存器

参考资料

[1] ARM Cortex-M4 Technical Reference Manual

[2] STM32F4 Reference Manual

[3] The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors

相关主题

基于 VitePress 构建