目录
1、封装总线和外设基地址
在编程上为了方便理解和记忆,我们把总线基地址和外设基地址都以相应的宏定义起来,总线或者外设都以他们的名字作为宏名;具体代码见代码清单:
/*外设基地址*/
#define PERIPH_BASE ((unsigned int)0x40000000)
/*总线基地址*/
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x00020000)
/*GPIO外设基地址*/
#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)
/*寄存器基地址,以GPIOB为例*/
#define GPIOB_CRL (GPIOB_BASE + 0x00)
#define GPIOB_CRH (GPIOB_BASE + 0x04)
#define GPIOB_IDR (GPIOB_BASE + 0x08)
#define GPIOB_ODR (GPIOB_BASE + 0x0C)
#define GPIOB_BSRR (GPIOB_BASE + 0x10)
#define GPIOB_BRR (GPIOB_BASE + 0x14)
#define GPIOB_LCKR (GPIOB_BASE + 0x18)
首先定义了”片上外设“基地址PERIPH_BASE,接着在PERIPH_BASE上加入各个总线的地址偏移,得到APB1、APB2总线的基地址APB1PERIPH_BASE、APB2PERIPH_BASE,在其之上加入外设地址的偏移,得到GPIOA-G的外设地址,最后在外设地址上加入各寄存器的地址偏移,得到的特定寄存器的地址。一旦有了具体地址,就可以用指针读写。
/*控制GPIOB引脚0输出低电平(BSRR寄存器的BR0置位1)*/
*(unsigned int*)GPIOB_BRR = (0x01<<(16+0));
/*控制GPIOB引脚0输出高电平(BSRR寄存器的BS0置位1)*/
*(unsigned int*)GPIOB_BSRR = (0x01<<0);
unsigned int temp;
/*读取GPIOB端口所有引脚的电平(读IDR寄存器)*/
temp = *(unsigned int*)GPIOB_IDR;
该代码使用(unsigned int*)把GPIOB_BSRR宏的数值强制转换成了地址,然后再用”*“号做取指针操作,对该地址的赋值,从而实现了写寄存器的功能。同样,读寄存器也是用取指针操作,把寄存器中的数据取到变量里,从而获取STM32外设的状态。
2、封装寄存器列表
用上面的方法去定义地址,还是稍显繁琐,例如GPIOA-GPIOE都各有一组功能相同的寄存器,如GPIOA_ODR/GPIOB_ODR/GPIOC_ODR等等,它们只是地址不一样,但却要为每个寄存器都定义它的地址。为了方便的访问寄存器,我们引入C语言中的结构体语法对寄存器进行疯传,具体见代码。
typedef unsigned int uint32_t /*无符号32位变量*/
typedef unsigned short uint16_t /*无符号16位变量*/
/*GPIO寄存器列表*/
typedef struct{
uint32_t CRL; /*GPIO端口配置低寄存器 地址偏移0x00*/
uint32_t CRH; /*GPIO端口配置高寄存器 地址偏移0x04*/
uint32_t IDR; /*GPIO数据输入寄存器 地址偏移0x08*/
uint32_t ODR; /*GPIO数据输出寄存器 地址偏移0x0C*/
uint32_t BSRR; /*GPIO位设置/清除寄存器 地址偏移0x10*/
uint32_t BRR; /*GPIO位清除寄存器 地址偏移0x14*/
uint32_t LCKR; /*GPIO端口配置锁定寄存器 地址偏移0x18*/
}GPIO_Typedef;
这段代码用typedef关键字声明了名为GPIO_Typedef的结构体类型,结构体有7个成员变量,变量名正好对应寄存器的名字。C语言的语法规定,结构体内变量的存储空间是连续的,其中32位的变量占用4个字节,16位的变量占用2个字节,具体见下图:
这样的地址偏移与STM32 GPIO外设定义的寄存器地址偏移一一对应,只要给结构体设置好首地址,就能把结构体成员的地址确定下来,然后就能以结构体形式访问寄存器,具体见代码:
GPIO_Typedef *GPIOx; //定义一个GPIO_Typedef型结构体指针GPIOx GPIOx = GPIOB_BASE; //把指针地址设置为宏GPIOB_BASE地址 GPIOx->IDR = 0xFFFF; GPIOx->ODR = 0xFFF; uint32_t temp; temp = GPIOx->IDR; //读取GPIOB_IDR寄存器的值到变量temp中
3、修改寄存器的位操作的方法
使用C语言对寄存器赋值时,我们常常要求只修改寄存器的某几位的值,且其他的寄存器位不变,这个时候我们就需要用到C语言的位操作方法了。
把变量的某位清零
此处我们以变量a代表寄存器,并假设寄存器中本来已有数值,此时我们需要把变量a的某一位清零,且其它位不变,方法见代码段:
代码:寄存器对某位清零 //定义一个变量a = 1001 1111b(二进制数) unsigned char a = 0x9f; //对bit2清零 a &= ~(1<<2); //括号中的1左移两位得二进制数:0000 0100b //按位取反。~(1<<2)得1111 1011b //假如a中原来得值为二进制数,a = 1001 1111b //所得得数与a作与运算,a = (1001 1111b)&(1111 1011b) //经过运算后,a = 1001 1011b //a的bit2位被清零,而其他位不变
把变量的某几个连续位清零
//若把 a 中的二进制位分成 2 个一组 2 //即 bit0、bit1 为第 0 组,bit2、bit3 为第 1 组, 3 // bit4、bit5 为第 2 组,bit6、bit7 为第 3 组 4 //要对第 1 组的 bit2、bit3 清零 a &= ~(3<<2*1); 78 //括号中的 3 左移两位,(3<<2*1) 得二进制数:0000 1100 b 9 //按位取反,~(3<<2*1) 得 1111 0011 b 10 //假如 a 中原来的值为二进制数: a = 1001 1111 b 11 //所得的数与 a 作”位与&”运算,a = (1001 1111 b)&(1111 0011 b), 12 //经过运算后,a 的值 a=1001 0011 b 13 // a 的第 1 组的 bit2、bit3 被清零,而其它位不变。 14 15 //上述 (~(3<<2*1)) 中的 (1) 即为组编号; 如清零第 3 组 bit6、bit7 此处应为 3 16 //括号中的 (2) 为每组的位数,每组有 2 个二进制位; 若分成 4 个一组,此处即为 4 17 //括号中的 (3) 是组内所有位都为 1 时的值; 若分成 4 个一组,此处即为二进制数“1111 b” 18 19 //例如对第 2 组 bit4、bit5 清零 20 a &= ~(3<<2*2);
对变量的某几位进行赋值。
//a = 1000 0011 b 2 //此时对清零后的第 2 组 bit4、bit5 设置成二进制数“01 b ” 34 a |= (1<<2*2); 5 //a = 1001 0011 b,成功设置了第 2 组的值,其它组不变
对变量的某位取反
//a = 1001 0011 b 2 //把 bit6 取反,其它位不变 34 a ^=(1<<6); 5 //a = 1101 0011 b