STM32-寄存器

寄存器

芯片内部

  • STM32芯片架构

    5d20417c7aaf334860

  • 芯片与外设总线连接,主控总线8条,被控总线7条

    5d20416b2127011370

  • STM32三种启动方式,FLASH、内部SRAM、外部RAM

存储器映射

连接被控总线的是FLASH、RAM、片上外设

5d2041bf8c94482558

5d2041d1c689662634

Boock0 用来设计成内部 FLASH, Block1 用来设计成内部 RAM, Block2 用来设计成片上的外设

5d2041f76132483848

5d2042095b1ab63850

5d2042258baeb33393
AHB3 包含了 Block3/4/5/6,这四个 Block 用于扩展外部存储器,如 SDRAM,NORFLASH 和 NANDFLASH 等

寄存器映射

给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

通过 C 语言指针的操作方式,让 GPIOH 的 16 个 IO 都输出高电平

代码 5-1 通过绝对地址访问内存单元

// GPIOH 端口全部输出 高电平 
*(unsigned int*)(0x4002 1C14) = 0xFFFF;

0x4002 1C14 在我们看来是 GPIOH 端口 ODR 的地址,但是在编译器看来,这只是一个普通的变量,是一个立即数,要想让编译器也认为是指针,我们得进行强制类型转换,把它转换成指针,即(unsigned int *)0x4002 1C14,然后再对这个指针进行 * 操作。

通过寄存器的方式来操作

代码 5-2 通过寄存器别名方式访问内存单元

// GPIOH 端口全部输出 高电平

#define GPIOH_ODR (unsigned int*)(GPIOH_BASE+0x14)

*GPIOH_ODR = 0xFF;

把指针操作“*”也定义到寄存器别名里面

代码 5-3 通过寄存器别名访问内存单元

//GPIOH 端口全部输出 高电平
#define GPIOH_ODR *(unsigned int*)(GPIOH_BASE+0x14)
GPIOH_ODR = 0xFF;

外设地址映射

片上外设四条总线,外设速度不同,挂在不同总线

总线的最低地址称为总线的基地址

5d304870425db84210

特定外设的首地址称为XX外设基地址

5d30487defff585717

外设寄存器

5d3048946792256436

5d3048a61f03327822

①名称

寄存器说明中首先列出了该寄存器中的名称,“(GPIOx_BSRR)(x=A…I)”这段的意思是该寄存器名为“GPIOx_BSRR”其中的“x”可以为 A-I,也就是说这个寄存器说明适用于 GPIOA、 GPIOB 至 GPIOI,这些 GPIO 端口都有这样的一个寄存器。

②偏移地址

偏移地址是指本寄存器相对于这个外设的基地址的偏移。本寄存器的偏移地址是 0x18,从参考手册中我们可以查到 GPIOA 外设的基地址为 0x4002 0000 ,我们就可以算出GPIOA 的这个 GPIOA_BSRR 寄存器的地址为: 0x4002 0000+0x18 ;同理,由于 GPIOB 的外设基地址为 0x4002 0400,可算出 GPIOB_BSRR 寄存器的地址为: 0x4002 0400+0x18 。其他 GPIO 端口以此类推即可。

③寄存器位表

紧接着的是本寄存器的位表,表中列出它的 0-31 位的名称及权限。表上方的数字为位编号,中间为位名称,最下方为读写权限,其中 w 表示只写, r 表示只读, rw 表示可读写。本寄存器中的位权限都是 w,所以只能写,如果读本寄存器,是无法保证读取到它真正内容的。而有的寄存器位只读,一般是用于表示 STM32 外设的某种工作状态的,由 STM32硬件自动更改,程序通过读取那些寄存器位来判断外设的工作状态。

④位功能说明

位功能是寄存器说明中最重要的部分,它详细介绍了寄存器每一个位的功能。例如本寄存器中有两种寄存器位,分别为 BRy 及 BSy,其中的 y 数值可以是 0-15,这里的 0-15表示端口的引脚号,如BR0、 BS0 用于控制 GPIOx 的第 0 个引脚,若 x 表示 GPIOA,那就是控制 GPIOA 的第 0 引脚,而 BR1、 BS1 就是控制 GPIOA 第 1 个引脚。

C语言对寄存器的封装

封装总线和外设基地址

代码 5-4 总线和外设基址宏定义

/* 外设基地址 */ 
#define PERIPH_BASE ((unsigned int)0x40000000) 
/* 总线基地址 */ 
#define APB1PERIPH_BASE PERIPH_BASE 
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) 
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000) 
#define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000) 
/* GPIO 外设基地址 */ 
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000) 
#define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400) 
#define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800) 
#define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00) 
#define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000) 
#define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400) 
#define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800) 
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00) 
/* 寄存器基地址,以 GPIOH 为例 */ 
#define GPIOH_MODER (GPIOH_BASE+0x00) 
#define GPIOH_OTYPER (GPIOH_BASE+0x04) 
#define GPIOH_OSPEEDR (GPIOH_BASE+0x08) 
#define GPIOH_PUPDR (GPIOH_BASE+0x0C) 
#define GPIOH_IDR (GPIOH_BASE+0x10) 
#define GPIOH_ODR (GPIOH_BASE+0x14) 
#define GPIOH_BSRR (GPIOH_BASE+0x18) 
#define GPIOH_LCKR (GPIOH_BASE+0x1C) 
#define GPIOH_AFRL (GPIOH_BASE+0x20) 
#define GPIOH_AFRH (GPIOH_BASE+0x24)

代码 5-5 使用指针控制 BSRR 寄存器

/* 控制 GPIOH 引脚 10 输出低电平(BSRR 寄存器的 BR10 置 1) */
*(unsigned int *)GPIOH_BSRR = (0x01<<(16+10));

/* 控制 GPIOH 引脚 10 输出高电平(BSRR 寄存器的 BS10 置 1) */ 
*(unsigned int *)GPIOH_BSRR = 0x01<<10; 

unsigned int temp; 
/* 控制 GPIOH 端口所有引脚的电平(读 IDR 寄存器) */ 
temp = *(unsigned int *)GPIOH_IDR;

封装寄存器列表

代码 5-6 使用结构体对 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;

这段代码用 typedef 关键字声明了名为 GPIO_TypeDef 的结构体类型,结构体内有 8 个成员变量,变量名正好对应寄存器的名字。 C 语言的语法规定,结构体内变量的存储空间是连续的,其中 32 位的变量占用 4 个字节, 16 位的变量占用 2 个字节

5d30499b4c36b56979

也就是说,我们定义的这个 GPIO_TypeDef , 假如这个结构体的首地址为 0x40021C00(这也是第一个成员变量 MODER 的地址) , 那么结构体中第二个成员变量OTYPER 的地址即为 0x4002 1C00 +0x04 , 加上的这个 0x04 ,正是代表 MODER 所占用的4 个字节地址的偏移量,其它成员变量相对于结构体首地址的偏移,在上述代码右侧注释已给出,其中的 BSRR 寄存器分成了低 16 位 BSRRL 和高 16 位 BSRRH, BSRRL 置 1 引脚输出高电平, BSRRH 置 1 引脚输出低电平,这里分开只是为了方便操作。

这样的地址偏移与 STM32 GPIO 外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体内成员的地址确定下来,然后就能以结构体的形式访问寄存器了

代码 5-7 通过结构体指针访问寄存器

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_TypeDef 类型的指针,而且指针指向各个 GPIO 端口的首地址,使用时我们直接用该宏访问寄存器即可

代码 5-8 定义好 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) 

/*使用定义好的宏直接访问*/ 
/*访问 GPIOH 端口的寄存器*/ 
GPIOH->BSRRL = 0xFFFF; //通过指针访问并修改 
GPIOH_BSRRL 寄存器 GPIOH->MODER = 0xFFFFFFF; //修改 GPIOH_MODER 寄存器 
GPIOH->OTYPER =0xFFFFFFF; //修改 GPIOH_OTYPER 寄存器 

uint32_t temp; temp = GPIOH->IDR; //读取 GPIOH_IDR 寄存器的值到变量 temp 中 

/*访问 GPIOA 端口的寄存器*/ 
GPIOA->BSRRL = 0xFFFF; //通过指针访问并修改 GPIOA_BSRRL 寄存器 
GPIOA->MODER = 0xFFFFFFF; //修改 GPIOA_MODER 寄存器 
GPIOA->OTYPER =0xFFFFFFF; //修改 GPIOA_OTYPER 寄存器 

uint32_t temp; 
temp = GPIOA->IDR; //读取 GPIOA_IDR 寄存器的值到变量 temp 中

修改寄存器的位操作方法

把变量某位清零

代码清单 5-1 对某位清零

//定义一个变量 a = 1001 1111 b (二进制数) 
unsigned char a = 0x9f; 

//对 bit2 清零 
a &= ~(1<<2); 

//括号中的 1 左移两位, (1<<2)得二进制数: 0000 0100 b 
//按位取反, ~(1<<2)得 1111 1011 b 
//假如 a 中原来的值为二进制数: a = 1001 1111 b 
//所得的数与 a 作”位与&”运算, a = (1001 1111 b)&(1111 1011 b), 
//经过运算后, a 的值 a=1001 1011 b 
// a 的 bit2 位被被零,而其它位不变。

某几个连续位清零

//若把 a 中的二进制位分成 2 个一组 
//即 bit0、 bit1 为第 0 组, bit2、 bit3 为第 1 组,
// bit4、 bit5 为第 2 组, bit6、 bit7 为第 3 组 
//要对第 1 组的 bit2、 bit3 清零 

a &= ~(3<<2*1); 

//括号中的 3 左移两位, (3<<2*1)得二进制数: 0000 1100 b 
//按位取反, ~(3<<2*1)得 1111 0011 b 
//假如 a 中原来的值为二进制数: a = 1001 1111 b 
//所得的数与 a 作”位与&”运算, a = (1001 1111 b)&(1111 0011 b), 
//经过运算后, a 的值 a=1001 0011 b 
// a 的第 1 组的 bit2、 bit3 被清零,而其它位不变。 
//上述(~(3<<2*1))中的(1)即为组编号;如清零第 3 组 bit6、 bit7 此处应为 3 
//括号中的(2)为每组的位数,每组有 2 个二进制位;若分成 4 个一组,此处即为 4 
//括号中的(3)是组内所有位都为 1 时的值;若分成 4 个一组,此处即为二进制数“1111 b” 
//例如对第 2 组 bit4、 bit5 清零 
a &= ~(3<<2*2);

对变量的某几位进行赋值

代码清单 5-3 对某几位进行赋值

//a = 1000 0011 b 
//此时对清零后的第 2 组 bit4、 bit5 设置成二进制数“01 b ” 
a |= (1<<2*2); 
//a = 1001 0011 b,成功设置了第 2 组的值,其它组不变

对变量的某位取反

代码清单 5-4 对某位进行取反操作

//a = 1001 0011 b 
//把 bit6 取反,其它位不变 
a ^=(1<<6); 
//a = 1101 0011 b

参考引用:

  1. 野火---《零死角玩转STM32-F429挑战者》
  2. 《STM32F4xx中文参考手册》
  3. 《Cortex-M4内核编程手册》
posted @ 2019-07-06 14:44  LOXO  阅读(1931)  评论(0编辑  收藏  举报