在单片机寄存器封装的时候,一般库都会选择用地址偏移的办法完成。
#define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00)
#define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04)
#define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08)
#define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C)
#define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10)
#define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14)
#define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18)
GPIOC_BASE是一个基地址,记录的是GPIOC寄存器的起始地址,是一个普通的常量,假设这个常量为C。
(unsigned int*)代表强制转换,即将后面的常量转变为一个地址指针,且类型为无符号整型。总的来说,C就是一个指向无符号整型的地址指针。如果没有强制转换,编译器会认为C就是一个普通的常量。
前面再加(*),就是指针的取内容,代表是取出在C地址的内容。所以GPIOC_CRL等内容并不是一个地址(指针),而是对应地址上面的内容。
typedef struct
{
uint32_t CRL; /*GPIO 端口配置低寄存器 地址偏移: 0x00 */
uint32_t CRH; /*GPIO 端口配置高寄存器 地址偏移: 0x04 */
uint32_t IDR; /*GPIO 数据输入寄存器 地址偏移: 0x08 */
uint32_t ODR; /*GPIO 数据输出寄存器 地址偏移: 0x0C */
uint32_t BSRR; /*GPIO 位设置/清除寄存器 地址偏移: 0x10 */
uint32_t BRR; /*GPIO 端口位清除寄存器 地址偏移: 0x14 */
uint16_t LCKR; /*GPIO 端口配置锁定寄存器 地址偏移: 0x18 */
}GPIO_TypeDef;
然后会封装一个结构体,命名为GPIO_TypeDef,存在7个成员。
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
之后GPIOC宏定义为GPIO_TypeDef类型的指针。GPIOC是一个指向GPIO_TypeDef类型,且初始位置为GPIOC_BASE的指针。这里和前面不同的地方在于,前面没有(*),也就是不需要取内容。
typedef struct // 定义一个结构体类型
{
char name[20]; // 结构体成员:name
int height; // 结构体成员:height
}STUDENT;
STUDENT data; // 声明一个结构体变量
data.height = 180; // 结构体变量通过点运算符( . )访问
以上是我们经常用的结构体定义和访问,可以看到我们并不是定义一个指针,而是定义了一个普通结构体变量,然后通过点运算符(.)去访问成员。那GPIOC作为一个指针,是如何访问结构体成员内部的结构。
- 点运算符的左操作数必须是一个结构或者一个联合,而右操作数必须是该类型(结构或联合)成员的名字。
- 运算符 -> 也可用于选择结构或联合的成员,但是箭头运算符的左操作数必须是一个指针,它指向一个结构或联合类型。右操作数是该结构或联合成员的名字。
所以一般寄存器的访问都是通过箭头运算符进行访问的。例如GPIOC->CRL= 0x00000001
,可对CRL寄存器进行赋值。
typedef struct // 定义一个结构体类型
{
char name[20]; // 结构体成员:name
int height; // 结构体成员:height
}STUDENT;
STUDENT data; // 结构体变量
STUDENT *pdata; //结构体指针变量
//点运算符( . )访问
data.height = 180;
//箭头运算符( -> )访问
pdata->height = 180;