2.1 变量定义
- Cortex 微控制器编程中常使用特定类型的变量定义,以增强代码的可读性和可移植性。
Cortex 变量类型与标准 C 中变量类型的对应关系:
Cortex类型 | 标准C类型 |
uint8_t | unsigned char |
uint16_t | unsigned short int |
uint32_t | unsigned int |
int8_t | signed char |
int16_t | signed short int |
int32_t | signed int |
- 这些类型如
uint8_t
通常在标准库的stdint.h
文件中通过typedef
重定义。
2.2 #include
语句
#include
语句#include
指令常用来包含某个头文件。
- 头文件分类:
- 系统头文件:以
< >
为标识,例如#include <stdio.h>
。编译器在编译系统安装目录的库文件夹中搜寻。 - 自定义头文件:以
" "
为标识,例如#include "config.h"
。编译器首先在用户的文件目录中搜寻,找不到则去系统库文件目录搜寻。自定义头文件一般需要在编译器中设置包含目录。
2.3 预处理语句
- 预处理语句以
#
开头,是给编译器传达指示。
- 常见用途:
- 预处理宏定义 (可用于定义全局变量的别名或常量):
#define GPIOH_MODER *(unsigned int*)(GPIOH_BASE + 0x00)
- 取消宏定义 (
#undef
):用于解除一个已有的宏定义,以避免命名冲突。#undef MAX_NUM
2.4 条件编译语句
- 结构:
- 作用:如果“标识符”已经被定义,则编译程序段1;否则编译程序段2。
- 防止重复包含:在头文件中常使用
#ifndef
、#define
、#endif
结构,以防止同一个头文件被多次包含。

2.5 extern
外部声明
extern
外部声明- 在 C 语言中,
extern
关键字表明变量或函数是定义在其他文件中的。
- 用于在一个文件中声明已在其他文件中定义的全局变量或函数。
2.6 位运算操作
- C 语言提供以下6个位运算操作符:
&
(按位与),|
(按位或),^
(按位异或),~
(按位非),<<
(左移),>>
(右移)。
- 示例:
GPIOH_MODER &= ~(0x03 << 20);
// 将GPIOH_MODER的第20、21位清零GPIOH_MODER |= (0x01 << 20);
// 将GPIOH_MODER的第20位设置为1
2.7 __IO
关键字
__IO
关键字__IO
(或_IO
) 关键字通常被定义为volatile
。
volatile
变量的含义:允许程序之外的硬件或软件修改其内容,确保当前读取到的值是最新的数据值,防止编译器优化。
- 示例:
__IO uint32_t tmp;tmp = GPIOx->LCKR;
// 确保从实际地址读取
2.8 结构体与外设定义❗❗
结构体变量的地址对应关系
在 Cortex-M 微控制器编程中,尤其是当我们需要直接访问外设寄存器时,通常会使用 C 语言的结构体来组织这些寄存器。这种做法的优点在于:
- 相关的设置参数放置在一个结构体中:
将一个外设的所有相关寄存器(例如一个 GPIO 端口的所有配置寄存器)集中到一个结构体里,使得代码更具可读性和组织性
- 对外设操作时,将相关寄存器地址映射到结构体的成员:
通过将结构体实例(或指向结构体的指针)的地址设置为外设寄存器的起始地址,我们可以通过访问结构体成员的方式来间接访问这些寄存器,而不是使用裸的内存地址。
实际的寄存器的地址也是连续排列的,我们定义的结构体在被赋予了正确的首地址之后就可以正确的访问这一组寄存器了。

- 结构体变量地址对应这个结构体的首地址:
这是 C 语言的一个基本特性。结构体在内存中是连续存放的,其首地址就是第一个成员的地址。
详细讲解
让我们以上图中的
GPIO_TypeDef
结构体为例进行说明:以及对应的 GPIO 寄存器映射表:
偏移 | 寄存器 |
0x00 | GPIOA_MODER |
0x04 | GPIOX_OTYPER |
0x08 | GPIOX_OSPEEDER |
0x0C | GPIOA_PUPDR |
... | ... |
【地址对应关系的核心】
编译器会按照结构体成员的定义顺序,在内存中为它们分配连续的存储空间。每个成员的地址是相对于结构体首地址的偏移量加上结构体首地址。
_IO uint32_t MODER;
(偏移 0x00):MODER
是结构体的第一个成员。uint32_t
表示它占用 4 个字节 (32 位)。- 因此,
MODER
成员的地址偏移量是 0x00。它对应着外设寄存器GPIOx_MODER
的基地址(例如,对于 GPIOA,就是0x40020000 + 0x00
)。
_IO uint32_t OTYPER;
(偏移 0x04):OTYPER
是结构体的第二个成员。- 紧跟在
MODER
之后。由于MODER
占用了 4 个字节 (从 0x00 到 0x03),所以OTYPER
的地址偏移量就是 0x04。 - 它对应着外设寄存器
GPIOx_OTYPER
的地址(例如,对于 GPIOA,就是0x40020000 + 0x04
)。
_IO uint32_t OSPEEDR;
(偏移 0x08):- 类似地,
OSPEEDR
的地址偏移量是 0x08,对应GPIOx_OSPEEDER
。
_IO uint32_t PUPDR;
(偏移 0x0C):PUPDR
的地址偏移量是 0x0C,对应GPIOx_PUPDR
。
【如何映射到实际外设寄存器】
为了让程序能够通过结构体来访问实际的硬件寄存器,我们需要定义一个指向该结构体类型的指针,并将其赋值为外设寄存器的基地址。
例如,对于 GPIOB 端口,其基地址是
0x400204001
(真实地址)我们可以这样定义并使用:
通过这种方式,
GPIOB->MODER
实际上访问的就是地址 0x40020400 + 0x00
处的 32 位寄存器,GPIOB->OTYPER
访问的就是地址 0x40020400 + 0x04
处的 32 位寄存器,以此类推。这大大提高了代码的可读性和可维护性,使得操作寄存器就像操作普通变量一样直观。