学习单片机时经常听说学会操作寄存器很重要,情况也确实如此,比如某些功能库函数不能实现,或者库函数效率很低时,就只能直接操作寄存器实现。
简单可以理解为能够通过软件改写数值、控制硬件的一个32bit的数字。多个数字一起控制一个外设。
这个数字要被存起来,就一定有一个存储的地址,地址可以通过单片机的数据手册查到。
有了地址,可以设置一个指针指向此地址,然后写入数据。有库可以直接写寄存器名字进行操作。
寄存器是软件控制硬件的接口,几乎所有的硬件功能控制都是通过操作寄存器来实现的,库函数也只是将寄存器操作封装成函数,方便调用,一些复杂操作库函数可能无法实现,就只能直接操作寄存器。
一个寄存器32bit,按规则修改这个32bit数值就能控制硬件,通常一个外设需要多个寄存器控制。
因为ARM单片机是统一编址的,寄存器像内存一样,内存中每个数据都有一个存储的地址,寄存器也是,只是寄存器的地址是固定的,你只能修改指定地址存储的数字才能控制硬件。
如何查找寄存器:以控制GPIO的寄存器为例。
(1)首先要有《stm32中文参考手册》这个网上很多,随便一搜就有。
(2)在目录中可以看到各种外设,比如下面这些有RCC、GPIO、DMA、ADC等
(3)展开目录,基本每一个外设的章节都有一个寄存器描述
(4)查看寄存器详细描述这一小节。
这里有三个关键点,了解这三点就知道改寄存器的规则了,如下图的①②③所示。
①偏移地址
这个寄存器叫CRL(全称应该是control register low),但具体地址并没有直接给出,而是以具体地址=基地址+偏移地址的形式计算的。其中基地址可以在2.3存储器映像这一章找到,如图:
如果我们要控制GPIOA的CRL寄存器那么,CRL的地址就是0x40010800+0x00=0x40010800;在它后面的CRH寄存器偏移量为0x04,那么GPIOA的CRH地址为0x40010800+0x04=0x40010804;
手册里为什么要这样写?因为GPIO有多组,GPIOA、GPIOB。。。。每一组都有这个寄存器,且功能相同,只是位置不一样。
这样就比较方便在程序上的定义与使用,定义一个结构体,结构体数据形式和寄存器一样,只需要指定结构体基地址,就比较容易进行寄存器读写,而不用指定每个寄存器地址。
----------|----------|----------|----------|----------|
GPIOA | GPIOB | GPIOx | GPIOF | .... | |
----------|----------|----------|----------|----------| |
CRL | CRL | CRL | CRL | CRL | |
----------|----------|----------|----------|----------| |
CRH | CRH | CRH | CRH | CRH | |
----------|----------|----------|----------|----------| |
IDR | IDR | IDR | IDR | IDR | ↓
----------|----------|----------|----------|----------| 地址4字节递增
... | | | | |
typedef struct
{
vu32 CRL;
vu32 CRH;
vu32 IDR;
...
} GPIO_TypeDef;
②这两条方框是干嘛的
仔细看方框上有数字1-15和16-31这就是之前说的,每个寄存器是一个32bit数字。
1bit或几个bit组合在一起用于设置功能,比如③号框圈起来的30和31bit,这2bit一起决定一个功能。
③每bit数字具体功能
那么③里的2bit是什么功能,就要看下面的具体描述,这些描述功能的文字应该很熟悉了吧。
现在我们知道了这些寄存器的功能和地址,那么下一步就是修改它的数值了。做开发时我们主要还是使用固件库(只是掺杂一点直接修改寄存器操作),而库文件中定义了这些寄存器的地址,我们只需要通过他的名字访问他就行。
就比如上图中的GPIOx_CRL,这是一个通用名,比如要控制GPIOA的这个寄存器那么具体操作如下
GPIOA->CRL = 0x44444444;
其实操作寄存器并不难,难点是了解每个外设各个寄存器的作用。要完全了解一个外设的寄存器,可以通过寄存器例程和手册中的寄存器描述,结合实践加深对外设控制的理解。
1、stm32固件库库是如何实通过寄存器名字操作寄存器的?
要理解这个问题需要有良好的C语言基础,理解起来还是比较简单的。
每一组控制GPIO的几个寄存器地址是连续的,按4字节递增(每个寄存器占4字节)。
那么我们只需要定义一个结构体,成员变量都定义为32位无符号整形变量,成员的数量就是寄存器的数量。以GPIO为例
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;
GPIO_TypeDef *GPIOA;
GPIOA = GPIOA的基地址;
GPIOA->ODR = 0x11114444;
通过GPIOA这个指针访问对应的寄存器地址。
编译器会自动计算ODR相对GPIOA基地址的具体地址。
2、一些给寄存器赋值的小技巧。
通常我们只想修改32bit中的某一位,清零或者置1
GPIOA->CRL |= 1<<6; 将bit6设为1
GPIOA->CRL |= (1<<6+1<<7);GPIOA->CRL |= (0x3<<6); 将bit6和7设为1
GPIOA->CRL &= ~(1<<6); 将bit6清零
GPIOA->CRL &= ~(1<<6+1<<7);GPIOA->CRL &= ~(0x3<<6); 将bit6和7清零