了解下库函数寄存器的定义过程,对我们理解底层很有帮助。以后开发其他软件可以效仿此类定义方法。
1. 寄存器定义
这里以STM32F1xx系列的 GPIO)为例,库函数找到的相关代码如下
#define PERIPH_BASE ((u32)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
首先前3行,不用解释太多,就是数据的宏定义。这个数据最终计算出来GPIOB的基地址
GPIOB_BASE= 0x4001 0C00
对照手册,正确无误。
第四行可能有小伙伴就得思考下了,其实也非常简单,(GPIO_TypeDef *)就是一个强制类型转换。 因为前面编译器就认为GPIOB_BASE就是一个数据。经过强转后,就可以把GPIOB_BASE转换成一个结构体指针。
然后经过宏定义#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
则GPIOB 就代表指向的是 RCC_TypeDef定义的结构体指针了。
GPIO_TypeDef的定义是这样做的:
typedef struct
{
vu32 CRL;
vu32 CRH;
vu32 IDR;
vu32 ODR;
vu32 BSRR;
vu32 BRR;
vu32 LCKR;
} GPIO_TypeDef;
结构体里面的GPIO的各寄存器需要按顺序定义,因为结构体成员的地址是按首地址连续往下排列的。所以各成员需要根据偏移地址的高低来确定,如果寄存器的偏移地址不连续,结构体要空出相应的位置。参考手册截图如下,方便对应。
2. 应用
结构体指针对成员的引用用“->”符号即可。
比如上面GPIOB的使用
GPIOB->CRL&=0XFF0FFFFF;
GPIOB->CRL|=0X00300000;//PB5 推挽输出
GPIOB->ODR|=1<<5; //PB5 输出高
3. 补充(了解即可,有些时候自己用裸机定义一些寄存器可以参考)
上面是STM32官方的定义方式。当然有一些程序员自己开发裸机的时候用下列方式,如上面端口B输出寄存器GPIOB_ODR 如下定义:
#define GPIOB_ODR (*((volatile uint32_t *)0x40010C0C))
这里0x40010C0C,对应GPIOB的输出寄存器地址(基地址0x4001 0C00+偏移地址0x0C),但是编译器同时不知道是个地址,所以用强制类型转换(volatile uint32_t *)将数据强制转换成一个指针,指针指向0x40010C0C地址的值,然后外面一个取数据的符号,*((volatile uint32_t *)0x40010C0C)代表取GPIOB_ODR里面的值,通过宏定义,改变GPIO的输出只需要修改GPIOB_ODR值即可,如下
GPIOB_ODR=1<<5; //PB5 输出高
如果采用官方的库的定义,要实现同样功能是这样写:
GPIOB->ODR|=1<<5; //PB5 输出高