开始学习嵌入式的第二天,发现昨天学习的东西有些遗漏,今天要做个补充😎。那么就从寄存器那一块开始补学吧,昨天没有那么仔细地去学习这个知识。
(一)学会看丝印:
-
如果有小圆点在芯片上,则从这个小圆点开始逆时针,就是引脚口从1到最大引脚口。
-
如果没有小圆点在芯片上,则正看芯片,引脚口1在左上角开始,再逆时针。
(二)芯片和外设之间通过各种总线连接,其中主控线(也就是内核控制的部分)有8条,被控总线有7条,主控总线通过一个总线矩阵来连接被控总线,总线矩阵用于主控总线之间的访问仲裁管理,仲裁采用 循环调度算法。总线之间交叉的时候如果有个圆圈则表示可以通信,没有圆圈则表示不可以通信。
(三)STM32有三种启动方式:
-
从Flash启动(包含系统存储器)
-
从内部SRAM启动
-
从外部RAM启动
这也可以说明,启动总线与三根被控总线有交叉,也就是再总线接口图中有圆圈,这三种存储器就刚好对应三条总线。
(四)总线基地址
片上的外设分为四条总线,根据外设速度不同,不同总线挂载着不同的外设,APB挂载低速外设,AHB挂载高速外设(GPIO为高速外设)。相应总线的最低地址我们陈为总线的基地址。
-
如图,0x4002 0000为AHB1总线的基地址。
相对外设基地址的偏移:即该总线地址与“片上外设”基地址 0x4000 0000 的差值。方便对外设进行封装使用。
(五)外设基地址
总线上挂载着各种外设,这些外设也有自己的地址范围,特定外设的首个地址称为 “XX 外设基地址”,也叫 XX 外设的边界地址。
对于我的理解就是,在确定总线基地址之后,我们在对总线中的外设进行分配地址,而相对地址的偏移,就是相对于总线地址的偏移。
(六)外设寄存器
在 XX 外设的地址范围内,分布着的就是该外设的寄存器。以 GPIO 外设为例,GPIO 是通用输入输出端口的简称,简单来说就是 STM32 可控制的引脚,基本功能是控制引脚输出高电平或者低电平。最简单的应用就是把 GPIO 的引脚连接到 LED 灯的阴极,LED 灯的 阳极接电源,然后通过 STM32 控制该引脚的电平,从而实现控制 LED 灯的亮灭。
GPIO 有很多个寄存器,每一个都有特定的功能。每个寄存器为 32bit,占四个字节, 在该外设的基地址上按照顺序排列,寄存器的位置都以相对该外设基地址的偏移地址来描述。
按照以上几层基地址的层次嵌套,我们使用C语言封装后,可以看到以下:
1 /* 外设基地址 */
2 #define PERIPH_BASE ((unsigned int)0x40000000)
4 /* 总线基地址 */
5 #define APB1PERIPH_BASE PERIPH_BASE
6 #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
7 #define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
8 #define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000)
10 /* GPIO外设基地址 */
11 #define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)
12 #define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400)
13 #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800)
14 #define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00)
15 #define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000)
16 #define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400)
17 #define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800)
18 #define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)
20 /* 寄存器基地址,以GPIOH为例 */
21 #define GPIOH_MODER (GPIOH_BASE+0x00)
22 #define GPIOH_OTYPER (GPIOH_BASE+0x04)
23 #define GPIOH_OSPEEDR (GPIOH_BASE+0x08)
24 #define GPIOH_PUPDR (GPIOH_BASE+0x0C)
25 #define GPIOH_IDR (GPIOH_BASE+0x10)
26 #define GPIOH_ODR (GPIOH_BASE+0x14)
27 #define GPIOH_BSRR (GPIOH_BASE+0x18)
28 #define GPIOH_LCKR (GPIOH_BASE+0x1C)
29 #define GPIOH_AFRL (GPIOH_BASE+0x20)
30 #define GPIOH_AFRH (GPIOH_BASE+0x24)
所以偏移地址的好处就显而易见了,在封装时,在基地址上加入偏移地址就可以了!😎
(七)举个栗子
如果要使PH10实现输出低/高电平,要怎么实现?
step1:确定总线地址
#define PERIPH_BASE ((unsigned int)0x40000000)
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)
#define GPIOH_ODR *(unsignedint*)(GPIOH_BASE+0x14)
step 2 :
//输出低电平
GPIOH_ODR &= ~(1<<10);
以上代码是使1左移10位,后再取反,再与原来的值相与,保存原来的电平状态。如图所示:
step 3:
//输出高电平
GPIOH_ODR |= (1<<10);
(八)由于每个寄存器的偏移地址都是4位,类似于结构体的成员,成员地址都是递增的,所以我们使用结构体来封装寄存器列表,也就是上一篇文的所有GPIO寄存器,那就再提及一遍吧:
typedef unsigned int uint32_t; /*无符号32位变量*/
typedef unsigned short int uint16_t; /*无符号16位变量*/
/* GPIO寄存器列表 */
typedef struct {
uint32_t MODER; /*GPIO模式寄存器 地址偏移: 0x00 */
uint32_t OTYPER; /*GPIO输出类型寄存器 地址偏移: 0x04 */
uint32_t OSPEEDR; /*GPIO输出速度寄存器 地址偏移: 0x08 */
uint32_t PUPDR; /*GPIO上拉/下拉寄存器 地址偏移: 0x0C */
uint32_t IDR; /*GPIO输入数据寄存器 地址偏移: 0x10 */
uint32_t ODR; /*GPIO输出数据寄存器 地址偏移: 0x14 */
uint16_t BSRRL; /*GPIO置位/复位寄存器低16位部分 地址偏移: 0x18 */
uint16_t BSRRH; /*GPIO置位/复位寄存器高16位部分 地址偏移: 0x1A */
uint32_t LCKR; /*GPIO配置锁定寄存器 地址偏移: 0x1C */
uint32_t AFR[2]; /*GPIO复用功能配置寄存器 地址偏移: 0x20-0x24 */
} GPIO_TypeDef;
所以我们就可以使用结构体来访问寄存器:
GPIO_TypeDef *GPIOx; //定义一个GPIO_TypeDef类型的结构体指针GPIOx
GPIOx = GPIOH_BASE; //把指针地址设置为宏GPIOH_BASE地址
GPIOx->BSRRL = 0xFFFF; //通过指针访问并修改GPIOH_BSRRL寄存器
GPIOx->MODER = 0xFFFFFFFF; //修改GPIOH_MODER寄存器
GPIOx->OTYPER =0xFFFFFFFF; //修改GPIOH_OTYPER寄存器
uint32_t temp;
temp = GPIOx->IDR; //读取GPIOH_IDR寄存器的值到变量temp中
还有一种更简单的方法,就是定义GPIO端口基地址指针,这种方法比较常见:
/*使用GPIO_TypeDef把地址强制转换成指针*/
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH ((GPIO_TypeDef *) GPIOH_BASE)
定义以上后,我们可以直接”调用寄存器“:
GPIOA->ODR = 0xFF;
GPIOB->ODR = 0xFF;
... ...
那最后做个总结吧,其实封装就是,从内存映射到寄存器(define),之后用结构体封装,最后再用结构体指针指向从而可以直接使用。(个人理解)
以上只是以GPIO为例子来理解这个东西,其实其他外设也是这么封装的,那么今天的学习就到这吧,希望明天课多的我还可以挤出时间来学习!而且学习不敢像第一天这么敷衍了,漏了好多知识点T T。