来看STM32的寄存器是如何用C封装的。首先学习的是GPIO,所以只能看看GPIO的寄存器如何封装的。其他设备的寄存器举一反三。
精英版的STM32F103ZET的GPIO有7组,每组16Pin。也就是GPIOA /GPIOB...GPIOG。
GPIO是片上外设,挂在APB2总线上,也就是高速总线,一个IO外设为什么是高速的呢?我也不清楚,不过IO不要以为只是简单的按键输入和继电器输出,还有可以用到编码器,这都需要高速的。
先来看地址的对应关系:APB2总线的地址从0x4001,0000开始到0x4001,3FFF结束,而GPIOA作为挂在APB2上的设备,地址从0x4001,0800开始,GPIOA一共有7个寄存器,也就是说每组都有7个寄存器,STM32的寄存器都是32位的,2个word4个字节,并且有一点值得关注,就是这7个寄存器都是一个接一个的,地址连续,这让封装有了极好的机会。GPIOA占了4*7=28个字节,那GPIOB应该从0x4001,0800+28=0x4001,081C开始了吧?不是,每组地址之间有很大的空隙,GPIOB地址从0x4001,0C00开始,中间差不多有996个字节用不到,不知道做什么用,不会持家呀。只能一种解释,就是方便书写或者是为了扩展预留。不过从以前的一些零星知识猜测是因为挂在总线上的设备地址受限于硬件设计不可能连续,总有地址间隔,比如说使能位就能够空出很大空间。
这样,GPIOB从0x4001,0C00开始,GPIOC从0x4001,1000开始,GPIOD从0x4001,1400开始...直至GPIOG从0x4001,2000开始。
这些地址对于一块芯片来说总是固定的,不可能改变。
而GPIO的寄存器呢?GPIOA的寄存器说了是连续的,也就是说哪个地址是哪个寄存器是固定的。这样7组GPIO的每组7个寄存器的排列一样。
数字地址总是难以记忆难以理解实际作用的,这样就需要一个别名。这对于高级语言来说不是事。
可以把所有的总线地址、寄存器地址都用别名来表示,来看看ST库里面是如何实现的。
为了简单起见,也为了编写减少出错几率,采用了偏移地址的方法,也就是固定一个基址,其他地址都是相对基址的偏移,这样一环环编写可以减少很多不必要的麻烦也减少了书写错误。
首先定义的是总线地址APB2,APB2是外设地址上的一部分,所以需要定义外设地址:[mw_shl_code=c,true]#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */[/mw_shl_code]
其实也就是block2的基地址。
APB2这样定义:
- [mw_shl_code=c,true]#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)[/mw_shl_code]
复制代码
就是在外设基地址上加偏移0x10000,这样APB2的地址其实是:0x4001,0000。
明白了这个其他的好理解了。
GPIOA~GPIOG的定义是:
- [mw_shl_code=c,true]#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
- #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
- #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
- #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
- #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
- #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
- #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)[/mw_shl_code]
复制代码
--------------------------------
GPIO上的寄存器呢?
这个需要一点点技巧。因为7组的寄存器排列一样且连续,刚好对应C的结构体特性,那就用结构先定义7个寄存器。
- [mw_shl_code=c,true]typedef struct
- {
- __IO uint32_t CRL;
- __IO uint32_t CRH;
- __IO uint32_t IDR;
- __IO uint32_t ODR;
- __IO uint32_t BSRR;
- __IO uint32_t BRR;
- __IO uint32_t LCKR;
- } GPIO_TypeDef;[/mw_shl_code]
复制代码
结构体里面的寄存器顺序很重要,不能排错,因为这是利用了C的结构体语法特性来定义寄存器的。
定义好寄存器结构后如果使用GPIOA的BSRR寄存器(举例)就这样用:GPIOA_BASE->BSRR。
不过这样做似乎还不行,因为GPIOA_BASE怎么说也只是个数字不是地址,所以要先强制转换为指针。
[mw_shl_code=c,true]#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)[/mw_shl_code]
GPIO_TypeDef就是刚说的寄存器结构,这样GPIOA就是寄存器结构体指针了,可以直接使用GPIOA->BSRR了。
--------------------------
这些定义位于stm32f10x.h头文件中。
还有,寄存器的读写需要32位读写,不能16位读写两次,这个我没有试验,因为不像普通语言说试就试,还有很多东西需要了解。
|