寄存器详解
什么是寄存器?
寄存器是 CPU 内部的高速存储单元,是处理器最接近执行单元的存储层次。在嵌入式开发中,我们常说的"寄存器操作"通常指的是 外设寄存器——通过内存映射方式访问的硬件控制接口。
寄存器层次结构
┌─────────────────────────────────────────────────────────────┐
│ 存储层次结构 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CPU 寄存器 │ │
│ │ R0-R15, CPSR, SPSR (ARM) │ │
│ │ 访问延迟: 0-1 周期 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Cache │ │
│ │ L1 / L2 缓存 │ │
│ │ 访问延迟: 1-10 周期 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 内存 (RAM) │ │
│ │ 访问延迟: 10-100 周期 │ │
│ └─────────────────────────────────────────────────────┘ │
│ ▲ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 外设寄存器 (Memory-Mapped) │ │
│ │ GPIO, UART, SPI, Timer... │ │
│ │ 访问延迟: 取决于总线 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
两类寄存器
| 类型 | 说明 | 示例 |
|---|---|---|
| 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 └────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
寄存器访问的本质
c
#define GPIOA_ODR (*(volatile uint32_t *)0x40020014)
GPIOA_ODR = 0x01;1
2
3
2
3
这段代码的底层操作:
┌─────────────────────────────────────────────────────────────┐
│ 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 引脚输出高电平 │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
volatile 关键字
为什么需要 volatile?
c
#define GPIO_ODR (*(uint32_t *)0x40020014)
void led_on(void)
{
GPIO_ODR = 0x01;
GPIO_ODR = 0x01;
}1
2
3
4
5
6
7
2
3
4
5
6
7
优化后的汇编:
asm
led_on:
LDR R0, =0x40020014
MOV R1, #1
STR R1, [R0] ; 只有一条 STR 指令
BX LR1
2
3
4
5
2
3
4
5
编译器认为两次写入相同值是冗余操作,直接优化掉了一次。但对于硬件寄存器,每次写入都有意义!
volatile 的作用
c
#define GPIO_ODR (*(volatile uint32_t *)0x40020014)
void led_on(void)
{
GPIO_ODR = 0x01;
GPIO_ODR = 0x01;
}1
2
3
4
5
6
7
2
3
4
5
6
7
优化后的汇编:
asm
led_on:
LDR R0, =0x40020014
MOV R1, #1
STR R1, [R0] ; 第一次写入
STR R1, [R0] ; 第二次写入(保留)
BX LR1
2
3
4
5
6
2
3
4
5
6
volatile 的三个特性
| 特性 | 说明 |
|---|---|
| 禁止缓存 | 每次访问都从内存读取,不使用寄存器缓存 |
| 禁止优化 | 不会因为"冗余"而被优化掉 |
| 保证顺序 | 在同一 volatile 变量访问点,编译器不会重排指令 |
volatile 的局限性
c
volatile uint32_t *reg = (volatile uint32_t *)0x40020014;
*reg = 0x01;
*reg = 0x02;1
2
3
4
2
3
4
volatile 保证编译器不优化这两次写入,但 不保证 CPU 乱序执行。如果需要严格顺序,需要使用 内存屏障:
c
#define DMB() __asm__ volatile ("dmb" ::: "memory")
*reg = 0x01;
DMB();
*reg = 0x02;1
2
3
4
5
2
3
4
5
位操作详解
常用位操作
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))1
2
3
4
5
6
7
2
3
4
5
6
7
位操作底层原理
设置第 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 01001
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
读取-修改-写入 问题
c
REG |= BIT(3);1
这条语句的底层操作:
asm
LDR R0, [REG_ADDR] ; 1. 读取
ORR R0, R0, #0x08 ; 2. 修改
STR R0, [REG_ADDR] ; 3. 写入1
2
3
2
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!) │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
解决方案:关闭中断
c
void safe_set_bit(uint8_t bit)
{
uint32_t primask = __get_PRIMASK();
__disable_irq();
REG |= BIT(bit);
__set_PRIMASK(primask);
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
位带操作
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)│
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
位带宏定义:
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;
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
寄存器结构体定义
传统定义方式
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))1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
结构体方式
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);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
结构体对齐问题
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;1
2
3
4
5
6
7
2
3
4
5
6
7
问题: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;1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
或者使用 __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;1
2
3
4
5
6
7
2
3
4
5
6
7
常见寄存器类型
数据寄存器
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;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
状态寄存器
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): 接收数据寄存器非空1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
控制寄存器
c
void usart_enable(USART_TypeDef *usart)
{
usart->CR1 |= (1 << 13);
usart->CR1 |= (1 << 3);
usart->CR1 |= (1 << 2);
}1
2
3
4
5
6
2
3
4
5
6
写 1 清零寄存器
某些状态位通过 写 1 清零,而不是写 0:
c
#define EXTI_PR (*(volatile uint32_t *)0x40013C14)
void exti_clear_pending(uint8_t line)
{
EXTI_PR = (1 << line);
}1
2
3
4
5
6
2
3
4
5
6
写 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 被清除 │
└─────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
寄存器访问优化
批量操作
c
void gpio_set_multiple(uint16_t pins)
{
GPIOA->BSRR = pins;
GPIOA->BSRR = pins << 16;
}1
2
3
4
5
2
3
4
5
BSRR 寄存器设计:
BSRR 寄存器布局:
┌─────────────────────────────────────────────────────────────┐
│ Bit 31-16 | Bit 15-0 │
│ BR (复位) | BS (置位) │
│ 写 1 复位 | 写 1 置位 │
└─────────────────────────────────────────────────────────────┘
写 0x00010002:
- BSRR[1] = 1 → PA1 置高
- BR[16] = 1 → PA0 置低1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
原子操作
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));
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
调试技巧
寄存器转储
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);
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
寄存器监控
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)1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
总结
| 概念 | 要点 |
|---|---|
| 内存映射 | 外设寄存器映射到内存地址空间 |
| 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