1、位操作:
就是可以单独的对一个比特位读和写,这个在 51 单片机中非常常见。51 单片机
中通过关键字 sbit 来实现位定义,STM32 没有这样的关键字,而是通过访问位带别名区来
实现(通俗一点就是STM32的位带操作可以像51单片机一样可以对某个GPIO进行单独操作输入或输出)。
2、有两个地方实现了位带:一个是 SRAM 区的最低 1MB 空间,令一个是
外设区最低 1MB 空间。这两个 1MB的空间除了可以像正常的 RAM 一样操作外,他们还有
自己的位带别名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字,当访
问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。(个人理解就STM32有两个位带区,这个区域范围里面的地址可以膨胀成位带别名区,进行位带操作)
3、位带别名区的地址:
- 外设外带区的地址为:
- 0X40000000~0X40100000,大小为 1MB,这 1MB 的大小在 103 系列大/中/小容量型号的单片机中包含了片上外设的全部寄存器,这些寄存器的地址为:
- 0X40000000~0X40029FFF 。 外 设 位 带 区 经 过 膨 胀 后 的 位 带 别 名 区 地 址 为 :
- 0X42000000~0X43FFFFFF,这个地址仍然在 CM3 片上外设的地址空间中。在 103 系列大/ 中小容量型号的单片机里面,0X40030000~0X4FFFFFFF 属于保留地址,膨胀后的 32MB 位 带别名区刚好就落到这个地址范围内,不会跟片上外设的其他寄存器地址重合。
- SRAM 的位带区的地址为:
- 0X2000 0000~X2010 0000,大小为 1MB,经过膨胀后的位
- 带别名区地址为:0X2200 0000~0X23FF FFFF,大小为 32MB。操作 SRAM 的比特位这个
- 用得很少。
4、想要使用位带别名区的步骤:
- 先找到片上外设的基地址:如GPIOB的起始地址为APB2的基地址+GPIOB的偏移0x0C,在固件库函数表示 (GPIOB_BASE+0x0C)
- 接下来引入计算位带别名区的公式:
- 外设位带区:AliasAddr= =0x42000000+ (A-0x40000000)*8*4 +n*4
- SRAM位带区:AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4
- 解释含义:
- 0X42000000 是外设位带别名区的起始地址,0x40000000 是外设位带区的起始地址
- (A-0x40000000)表示该比特前面有多少个字节,一个字节有 8 位,所以*8,一个位膨胀后是 4 个字节,所以*4,n 表示该比特在 A 地址的序号,因为一个位经过膨胀后是四个字 节,所以也*4。(后面SRAM位带区一样含义,起始地址换成0x20000000)
- 在代码中体现(二合一)
((GPIOB_ODR_Addr&0xF0000000)+0x02000000+((GPIOB_ODR_Addr&0x00FFFFFF)<<5)+(n<<2))
- addr & 0xF0000000 是为了区别 SRAM 还是外设,实际效果就是取出 4 或者 2,如果是 外设,则取出的是 4,+0X02000000 之后就等于 0X42000000,0X42000000 是外设别名区的 起始地址。如果是 SRAM,则取出的是 2,+0X02000000 之后就等于 0X22000000, 0X22000000 是 SRAM 别名区的起始地址。
- addr & 0x00FFFFFF 屏蔽了高三位,相当于减去 0X20000000 或者 0X40000000,但是为 什么是屏蔽高三位?因为外设的最高地址是:0X2010 0000,跟起始地址 0X20000000 相减 的时候,总是低 5 位才有效,所以干脆就把高三位屏蔽掉来达到减去起始地址的效果,具 体屏蔽掉多少位跟最高地址有关。
- SRAM同理分析即可。<<5相当于*8*4,<<2相当于*4, 这两个我们在上面分析过。
- 最后我们就可以通过指针的形式操作这些位带别名区地址,最终实现位带区的比特位 操作
5、代码操作:宏定义 main.c
#define GPIOB_ODR_Addr (GPIOB_BASE+0x0C) // 定义GPIOB的基地址
#define GPIOA_IDR_Addr (GPIOA_BASE+0x08) // 定义GPIOA寄存器的基地址
#define GPIOC_IDR_Addr (GPIOC_BASE+0x08) // 定义GPIOC寄存器的基地址
// PBout为odr寄存器控制GPIOB端口x位输出,转换成位带别名区
#define PBout(n) *(unsigned int *)((GPIOB_ODR_Addr&0xF0000000)+0x02000000+((GPIOB_ODR_Addr&0x00FFFFFF)<<5)+(n<<2))
// PAout为idr寄存器控制GPIOA端口x位输入,转换成位带别名区
#define PAinp(n) *(unsigned int *)((GPIOA_IDR_Addr&0xF0000000)+0x02000000+((GPIOA_IDR_Addr&0x00FFFFFF)<<5)+(n<<2))
// PCout为idr寄存器控制GPIOC端口x位输入,转换成位带别名区
#define PCinp(n) *(unsigned int *)((GPIOC_IDR_Addr&0xF0000000)+0x02000000+((GPIOC_IDR_Addr&0x00FFFFFF)<<5)+(n<<2))
#define KEY_1 PAinp(0) // GPIOA_PIN_0 口
#define KEY_2 PCinp(13) // GPIOC_PIN_13口
#define LED_1 PBout(0) // GPIOB_PIN_0 口
#define LED_2 PBout(1)
#define LED_3 PBout(5)
完整代码操作:
通过位带操作控制按键按下时点亮LED,蜂鸣器伴随响。
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_key.h"
#include "beep.h"
#define GPIOB_ODR_Addr (GPIOB_BASE+0x0C)
#define GPIOA_IDR_Addr (GPIOA_BASE+0x08)
#define GPIOC_IDR_Addr (GPIOC_BASE+0x08)
#define PBout(n) *(unsigned int *)((GPIOB_ODR_Addr&0xF0000000)+0x02000000+((GPIOB_ODR_Addr&0x00FFFFFF)<<5)+(n<<2))
#define PAinp(n) *(unsigned int *)((GPIOA_IDR_Addr&0xF0000000)+0x02000000+((GPIOA_IDR_Addr&0x00FFFFFF)<<5)+(n<<2))
#define PCinp(n) *(unsigned int *)((GPIOC_IDR_Addr&0xF0000000)+0x02000000+((GPIOC_IDR_Addr&0x00FFFFFF)<<5)+(n<<2))
#define KEY_1 PAinp(0)
#define KEY_2 PCinp(13)
#define LED_1 PBout(0)
#define LED_2 PBout(1)
#define LED_3 PBout(5)
int main(void)
{
LED_GPIO_Config();
KEY_GPIO_Config();
BEEP_GPIO_Config();
LED_R(OFF);
LED_G(OFF);
while(1)
{
if(KEY_1 == KEY_ON)
{
while(KEY_1 == KEY_ON)
LED_1 = 0;
Beep_Res(); // 蜂鸣器响
}
else LED_1 = 1;
if(KEY_2 == KEY_ON)
{
while(KEY_2 == KEY_ON)
LED_2 = 0;
Beep_Res();
}
else LED_2 = 1;
}
}
总结:多练习,举一反三,比如说视频教程教你配置GPIO1,你可以去配置2,3甚至更多,多看看源码函数的实现和注释