上一章里说到了BSRR(位设置/清除寄存器)、BRR(位清除寄存器)、ODR(数据输出寄存器),这里再讲一下位带操作。
- 点亮LED顺序
- 开启时钟
- 设置GPIO口推挽输出
- 输出高低电平控制LED亮灭
注意:本人使用开发板LED为PB5和PE5。
一、位操作
首先看一段代码:
//这里我们开启GPIOB和GPIOE的时钟
RCC_APB2ENR=1<<3; //GPIOB
RCC_APB2ENR=1<<6; //GPIOE
仔细看一下,确定我们真的都打开了两个时钟吗?
并没有,我们只开启了GPIOE的时钟,什么原因,我们分析一下:
RCC_APB2ENR=1<<3; //GPIOB
//1<<3,得到1000 这句代码结束,这个寄存器里存放的值是:
//0000 0000 0000 0000 0000 0000 0000 1000
RCC_APB2ENR=1<<6; //GPIOE
//1<<6,得到1000000 这句代码结束,这个寄存器里存放的值是:
//0000 0000 0000 0000 0000 0000 0100 0000
可以看到,因为赋值运算符“=”,之前的赋值被刷新,所以只开启了GPIOE的时钟。
所以,我们用到了C语言中的按位与和按位或:
- 按位与:& 有0则0
- 按位或: | 有1则1
可以看到,如果想给某位置0,而不影响其他位,则按位与;想给某位置1,而不影响其他位,则按位或。
我们重新按位与赋值,得到以下正确代码:
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<6; //APB2-GPIOE外设时钟使能
二、位设置/清除寄存器实现流水灯
//--------------APB2使能时钟寄存器------------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_BSRR *((unsigned volatile int*)0x40010C10)
#define GPIOB_BRR *((unsigned volatile int*)0x40010C14)
//----------------GPIOE配置寄存器 ------------------------
#define GPIOE_CRL *((unsigned volatile int*)0x40011800)
#define GPIOE_BSRR *((unsigned volatile int*)0x40011810)
#define GPIOE_BRR *((unsigned volatile int*)0x40011814)
//-------------------简单的延时函数-----------------------
void delay(unsigned long xx)
{
while(xx--); //用自减的方式粗略延时
}
//------------------------主函数--------------------------
int main()
{
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<6; //APB2-GPIOE外设时钟使能
//这两行代码可以合为 RCC_APB2ENR|=1<<3|1<<6;
GPIOB_CRL&=0xFF0FFFFF; //设置位 清零
GPIOB_CRL|=0x00200000; //PB5推挽输出
GPIOB_BSRR|=1<<5; //设置初始灯为灭
GPIOE_CRL&=0xFF0FFFFF; //设置位 清零
GPIOE_CRL|=0x00200000; //PE5推挽输出
GPIOE_BSRR|=1<<5; //设置初始灯为灭
while(1)
{
GPIOB_BRR|=1<<5; //PB5低电平
delay(1000000);
GPIOB_BSRR|=1<<5; //PB5高电平
delay(1000000);
GPIOE_BRR|=1<<5; //PE5低电平
delay(1000000);
GPIOE_BSRR|=1<<5; //PE5高电平
delay(1000000);
}
}
三、数据输出寄存器实现流水灯
//--------------APB2使能时钟寄存器------------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
//----------------GPIOE配置寄存器 ------------------------
#define GPIOE_CRL *((unsigned volatile int*)0x40011800)
#define GPIOE_ODR *((unsigned volatile int*)0x4001180C)
//-------------------简单的延时函数-----------------------
void delay(unsigned long xx)
{
while(xx--); //用自减的方式粗略延时
}
//------------------------主函数--------------------------
int main()
{
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<6; //APB2-GPIOE外设时钟使能
//这两行代码可以合为 RCC_APB2ENR|=1<<3|1<<6;
GPIOB_CRL&=0xFF0FFFFF; //设置位 清零
GPIOB_CRL|=0x00200000; //PB5推挽输出
GPIOB_ODR|=1<<5; //设置初始灯为灭
GPIOE_CRL&=0xFF0FFFFF; //设置位 清零
GPIOE_CRL|=0x00200000; //PE5推挽输出
GPIOE_ODR|=1<<5; //设置初始灯为灭
while(1)
{
GPIOB_ODR&=~(1<<5); //PB5低电平,因为是置0,所以用按位与
delay(1000000);
GPIOB_ODR|=1<<5; //PB5高电平
delay(1000000);
GPIOE_ODR&=~(1<<5); //PE5低电平
delay(1000000);
GPIOE_ODR|=1<<5; //PE5高电平
delay(1000000);
}
}
四、位带操作实现流水灯
先说说什么是位带操作
我们知道,STM32中寄存器有32位,而把每一位(bit)膨胀成一个32位的字,通过访问这些32位字,达到操作寄存器中的一个位,这就是位带操作。
通俗点来讲,就是给寄存器每一个位,设置了一个地址,而通过这个地址就可以直接操作这个位。
位带地址计算方式:
SRAM 位带区的某个比特,记它所在别名区的地址为A,位序号为n:
AliasAddr=0x22000000+ (A‐0x20000000)x32 + nx4
片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n,则该比特
AliasAddr=0x42000000+ (A‐0x40000000)x32 + nx4
- 注意:相乘的时候要把32和n*4转化成16进制再做乘法。
//--------------APB2使能时钟寄存器------------------------
#define RCC_APB2ENR *((unsigned volatile int*)0x40021018)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRL *((unsigned volatile int*)0x40010C00)
#define GPIOB_ODR *((unsigned volatile int*)0x40010C0C)
//----------------GPIOE配置寄存器 ------------------------
#define GPIOE_CRL *((unsigned volatile int*)0x40011800)
#define GPIOE_ODR *((unsigned volatile int*)0x4001180C)
//----------------------位带定义--------------------------
#define PB5 *((unsigned volatile int*)0x42218194)
#define PE5 *((unsigned volatile int*)0x42230194)
//-------------------简单的延时函数-----------------------
void delay(unsigned long xx)
{
while(xx--); //用自减的方式粗略延时
}
//------------------------主函数--------------------------
int main()
{
RCC_APB2ENR|=1<<3; //APB2-GPIOB外设时钟使能
RCC_APB2ENR|=1<<6; //APB2-GPIOE外设时钟使能
GPIOB_CRL&=0xFF0FFFFF; //设置位 清零
GPIOB_CRL|=0x00200000; //PB5推挽输出
GPIOB_ODR|=1<<5; //设置初始灯为灭
GPIOE_CRL&=0xFF0FFFFF; //设置位 清零
GPIOE_CRL|=0x00200000; //PE5推挽输出
GPIOE_ODR|=1<<5; //设置初始灯为灭
while(1)
{
PB5=0; //PB5低电平
delay(1000000);
PB5=1; //PB5高电平
delay(1000000);
PE5=0; //PE5低电平
delay(1000000);
PE5=1; //PE5高电平
delay(1000000);
}
}
我们知道,数据输出寄存器(ODR)低位既可以清除,也可以设置,所以在进行位带操作点亮流水灯时,计算位带别名地址:
AliasAddr=0x42000000+ (A‐0x40000000)x32 + nx4
这里的 A 就是ODR寄存器的地址。