1、操作外设实际上就是操作寄存器
使用STM32进行编程,我们一般是用官方提供的库函数(HAL库)来操作各种外设。本质上,每个外设都有自己的一组外设寄存器,操作外设就是操作各种外设寄存器。HAL库的各个库函数就是对他们的寄存器操作的高度抽象后的封装。打开stm32f10x的数据手册的地址映射表,实际上图中的外设区就是里边就是映射的每个外设的各个寄存器的地址。
在STM32中,每个外设所拥有的一组寄存器的地址是连续的(注意,这点十分重要,下边会讲),外设的起始地址称为基地址,后续的每个寄存器都可以用这个基地址+偏移地址的方式来描述。
STM32,顾名思义,是32位的系统,每个寄存器都是32位的。因此,每个寄存器可以操作的东西其实是很多的,基本上每个位都可以操作一个东西。也正因此,对寄存器的操作,其实就是位操作,位操作在嵌入式底层开发中是十分重要的。
注:此图来自stm32f10x的数据手册。
2、C语言指针操作寄存器
我们知道,汇编语言是直接操作硬件的语言,也就是直接操作寄存器。那么,如何用C语言操作寄存器呢?实际上,C语言操作寄存器是通过指针的方式。具体的方法是,找到想要操作的寄存器的地址,将寄存器的地址强制转换为指针(volatile unsigned long *),然后再用#define给它定义个别名,就可以把这个寄存器的别名当作指针进行操作了。
例如,操作Cortex-M3内核的寄存器NVIC_CCR,查阅相关手册知道其地址为0xE000ED14,那么就可以用以下所示的方法操作该寄存器:
#define NVIC_CCR ((volatile unsigned long *)(0xE000ED14))
*NVIC_CCR = *NVIC_CCR | 0x200; //设置STKALIGN位
其中,unsigned long表示无符号长整型,在stm32中(32位系统),也就是4字节的无符号整型数。volatile是c语言关键字,用于防止对编译器对其进行优化。volatile unsigned long在stm32的库函数中有时候会被替换为__IO uint32_t,__IO和uint32_t分别是volatile和unsigned long的宏定义。
(volatile unsigned long *)(0xE000ED14)表示指向地址0xE000ED14的指针。许多初学者看到这个语句可能会有点疑惑,不太理解为什么可以这么做。实际上,比如定义个指针int* ptr,ptr就表示这个指针所指向内存的首地址,而0xE000ED14本身就是个地址,所以并没有什么不妥。或者说,本质上,指针就是地址,这也是c语言可以用指针操作寄存器的本质原因。
随后我们再用#define定义哥别名NVIC_CCR来表示这个指针,于是,我们就可以使用*NVIC_CCR直接操作这个寄存器了。