本篇总结
介绍 W55MH32 寄存器:存储器映射、地址访问与结构体封装、位操作技巧。
🔑 KEIL MDK
芯片架构与总线
W55MH32 芯片由内核和片上外设组成,通过总线连接。驱动单元(CPU 侧)4 个,被动单元(外设侧)4 个:
| 分类 | 单元 | 功能 |
| 驱动单元 | ICode 总线 | 取指令(从 FLASH 读取程序指令) |
| DCode 总线 | 取数据(常量在 FLASH,变量在 SRAM) |
| 系统总线 | 访问外设寄存器(寄存器编程的主要通道) |
| DMA 总线 | 数据传输(外设/SRAM/FLASH 之间) |
| 被动单元 | FLASH | 程序存储器,通过 ICode 总线访问 |
| SRAM | 变量/堆栈存储,通过 DCode 总线访问 |
| FSMC | 灵活静态存储器控制器,扩展外部 SRAM/NAND/NOR |
| AHB→APB 桥 | APB1/APB2 总线,挂载 GPIO/USART/I2C/SPI 等外设 |
存储器映射
存储器本身不具有地址信息,给存储器分配地址的过程称为存储器映射。W55MH32 的程序存储器、数据存储器、寄存器和 I/O 端口被组织在 4GB 线性地址空间内,ARM 将其分为 8 个块,每块 512MB:
| 块 | 用途 | 地址范围 |
| Block 0 | Code(FLASH) | 0x00000000 ~ 0x1FFFFFFF |
| Block 1 | SRAM | 0x20000000 ~ 0x3FFFFFFF |
| Block 2 | 片上外设(寄存器) | 0x40000000 ~ 0x5FFFFFFF |
| Block 3-5 | FSMC bank1~4 + 寄存器 | 0x60000000 ~ 0xCFFFFFFF |
| Block 6 | 未使用 | 0xD0000000 ~ 0xDFFFFFFF |
| Block 7 | Cortex-M3 内部外设 | 0xE0000000 ~ 0xFFFFFFFF |
三个关键 Block
Block 0 = 内部 FLASH(程序存储,1024KB);Block 1 = 内部 SRAM(变量/堆栈);Block 2 = 片上外设寄存器(GPIO/USART/I2C 等)。Block 2 又按速度分为 APB1(低速)、APB2 和 AHB(高速)。
寄存器映射
在 Block 2 片上外设区,每个外设以 4 字节(32bit)为一个单元,每个单元对应不同功能。给已分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。以 GPIOB 的 ODR(输出数据寄存器)为例,地址为 0x40010C0C:
/* 方式1:通过绝对地址访问(不好记忆,容易出错) */
*(unsigned int*)(0x40010C0C) = 0xFFFF; // GPIOB 全部输出高电平
/* 方式2:通过宏定义别名访问 */
#define GPIOB_ODR *(unsigned int*)(GPIOB_BASE + 0x0C)
GPIOB_ODR = 0xFFFF; // 同上,但更清晰
/* 方式3:使用指针操作 BSRR 寄存器 */
/* 控制GPIOB 引脚0输出低电平(BR0置1) */
*(unsigned int *)GPIOB_BSRR = (0x01 << (16+0));
/* 控制GPIOB 引脚0输出高电平(BS0置1) */
*(unsigned int *)GPIOB_BSRR = 0x01 << 0;
/* 读取GPIOB 端口所有引脚电平(读IDR) */
unsigned int temp = *(unsigned int *)GPIOB_IDR;
GPIOB 寄存器地址列表
| 寄存器 | 地址 | 偏移 | 功能 |
| CRL | 0x40010C00 | 0x00 | 端口配置低寄存器 |
| CRH | 0x40010C04 | 0x04 | 端口配置高寄存器 |
| IDR | 0x40010C08 | 0x08 | 数据输入寄存器 |
| ODR | 0x40010C0C | 0x0C | 数据输出寄存器 |
| BSRR | 0x40010C10 | 0x10 | 位设置/清除寄存器 |
| BRR | 0x40010C14 | 0x14 | 端口位清除寄存器 |
| LCKR | 0x40010C18 | 0x18 | 配置锁定寄存器 |
C 语言对寄存器的封装
固件库通过宏定义和结构体对寄存器进行层层封装,使代码更清晰易读:
/* 1. 外设基地址宏定义 */
#define PERIPH_BASE ((unsigned int)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
/* 2. 结构体封装寄存器组(成员顺序 = 地址偏移顺序) */
typedef struct {
uint32_t CRL; /* 地址偏移: 0x00 */
uint32_t CRH; /* 地址偏移: 0x04 */
uint32_t IDR; /* 地址偏移: 0x08 */
uint32_t ODR; /* 地址偏移: 0x0C */
uint32_t BSRR; /* 地址偏移: 0x10 */
uint32_t BRR; /* 地址偏移: 0x14 */
uint16_t LCKR; /* 地址偏移: 0x18 */
} GPIO_TypeDef;
/* 3. 宏定义指针,将地址强制转换为结构体指针 */
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
/* 4. 直接使用宏访问寄存器 */
GPIOB->BSRR = 0xFFFF; // 通过指针访问 BSRR 寄存器
GPIOB->ODR = 0xFFFF; // 修改 ODR 寄存器
uint32_t temp = GPIOB->IDR; // 读取 IDR 寄存器
结构体封装的巧妙之处
C 语言结构体成员存储空间连续,32 位变量占 4 字节。GPIO_TypeDef 结构体成员的地址偏移恰好与硬件寄存器偏移一一对应。将外设基地址强制转换为结构体指针后,GPIOB->ODR 自动指向 0x40010C0C,无需手动计算偏移。
修改寄存器的位操作方法
使用 C 语言对寄存器赋值时,常常只修改某几位而保持其他位不变。以下是四种常用位操作:
unsigned char a = 0x9F; // a = 1001 1111 b
/* 1. 对某位清零:&= ~(1<<n) */
a &= ~(1<<2); // a = 1001 1011 b,bit2 被清零
/* 2. 对连续几位清零:&= ~(mask<<shift) */
a &= ~(3<<2*1); // 对第1组(bit2,bit3)清零
// 3 = 二进制11,左移2位 = 0000 1100 b
// 取反 = 1111 0011 b,与运算后 bit2/bit3 清零
/* 3. 对某几位赋值:|= (value<<shift) */
a |= (1<<2*2); // 第2组(bit4,bit5)设为 01 b
/* 4. 对某位取反:^= (1<<n) */
a ^= (1<<6); // bit6 取反:0变1,1变0
| 操作 | 公式 | 用途 |
| 某位清零 | a &= ~(1<<n) | 清除第 n 位,其余不变 |
| 连续位清零 | a &= ~(mask<<shift) | 清除连续多位,如 ~(3<<2) |
| 某几位赋值 | a |= (value<<shift) | 先清零后写入新值 |
| 某位取反 | a ^= (1<<n) | 翻转第 n 位 |
常见问题
Q: 寄存器到底是什么
寄存器是给已分配好地址、有特定功能的内存单元取的别名。本质上就是 Block 2 片上外设区中 4 字节为一个单元的内存,通过读写这些地址来控制外设工作。
Q: 如何从参考手册找到寄存器地址
三步法:① 查外设基地址(如 GPIOB = 0x40010C00);② 查寄存器偏移地址(如 BSRR 偏移 0x10);③ 基地址 + 偏移 = 寄存器地址(0x40010C00 + 0x10 = 0x40010C10)。
Q: BSRR 和 ODR 有什么区别
ODR 直接写整个端口输出值,会影响所有 16 个引脚;BSRR 通过置位/复位位操作,只影响指定引脚,不会干扰其他引脚。需要原子操作(不被中断打断)时用 BSRR。
Q: 结构体封装为什么能自动对齐地址
C 语言结构体成员在内存中连续存放,32 位成员占 4 字节。GPIO_TypeDef 的成员顺序(CRL/CRH/IDR/ODR/BSRR/BRR/LCKR)恰好与硬件寄存器偏移(0x00/0x04/0x08/0x0C/0x10/0x14/0x18)一一对应。
Q: 只读寄存器和只写寄存器
只读(r)寄存器由硬件自动更新,程序通过读取来判断外设状态(如 IDR 读取引脚电平);只写(w)寄存器写入后无法读回真实值(如 BSRR)。读写(rw)寄存器既可设置也可读取。
总结
- 寄存器 = 有功能的内存别名:Block 2 片上外设区每 4 字节一个单元,取别名即寄存器映射
- 三层地址:外设基地址 → 总线偏移 → 寄存器偏移,三者相加得绝对地址
- 封装三步:宏定义基地址 → 结构体封装寄存器组 → 强制转换为结构体指针,
GPIOB->ODR 自动定位
- 位操作四式:清零
&= ~(1<<n)、连续清零 &= ~(mask<<shift)、赋值 |= (val<<shift)、取反 ^= (1<<n)
- 固件库:以上封装工作已由标准库完成,理解原理有助于深入使用库函数