004 - STM32学习笔记 - 位带操作
目前掌握的对GPIO引脚的输入输出操作只能使用BSRRL/H、I/ODR寄存器,记得以前学51的时候,对于引脚的输入输出可以采用关键字sbit实现位定义,例如
sbit LED1 = P1^3;
在STM32中没有类似于sbit一样的关键字,但是提供了位带操作来实现类似于51的为才做。
图中可以看到,STM32在两个地方实现了位带。一个是SRAM区,另外一个是Peripherals(外设)区,以外设区为例,可以看到外设区位带实际占1MB空间,前面学习的寄存器操作都是在这个空间内操作的,对应的将外设的空间膨胀32倍,膨胀后的空间就是外设的位带别名区。
外设的寄存器位带区空间为0x400000000 ~ 0x400F0000,这1MB的空间包含了APB1/2和AHB1上所有外设的寄存器,AHB2/3总线上的寄存器没有包含在内。经过膨胀后,外设位带别名区的地址范围为0x42000000 ~ 0x43FFFFFF。
SRAM的不在这里说明,具体可以参照上图结合外设位带别名区的办法进行分析。
位带别名区地址的计算
片上外设的位带区某个寄存器的地址为A,位序号为n(0 ≤ n ≤ 7),那么这个寄存器的位带别名区地址应该为:
AliasAddr = 0x42000000+(A-0x40000000) * 8 * 4 + n * 4
例如F429开发板上的PH10的ODR寄存器 A = GPIOH_BASE+0x14,n = 10,那么其位带别名区的地址是:
Alias_Ph10_ODR= 0x42000000 + ( GPIOH_BASE + 0x14 -0x40000000) * 8 * 4 + 10 * 4
另外,这里给出SRAM的位带别名区的计算公式:
AliasAddr = 0x22000000+(A-0x20000000) * 8 * 4 + n * 4
整合一下,外设和SRAM可以用一个统一公式来计算位带别名区的地址:
/*addr为外设寄存器地址,bitnum为位号(0 ≤ n ≤ 7)*/
AliasAddr = (addr & 0xF0000000) + 0x02000000 + ((addr & 0x000FFFFF) << 5) + (bitnum << 2)
公式分析:
①、addr & 0xF0000000
是为了区别当前计算的是外设区还是SRAM区,比如地址为0x40006789
,那么与0xF0000000
与运算后,得结果为0x40000000,再加上0x02000000就等于0x42000000,那么就得出来这个寄存器为外设区寄存器,如果是0x22000000,那么就是SRAM区寄存器等同于原始公式中的0x42000000
或者0x22000000
;
②、addr & 0x000FFFFF
则是屏蔽高三位,取出低五位的地址,0x40006789
计算后,取出值为0x06789
,等同于原始公式中的A-0x40000000
或者A-0x20000000
;
③、<< 5
和<< 2
则是通过位运算分别提到了* 8 * 4
和* 4
;
用以上公式转出的位带别名区地址还不能立即使用,此时转换出来的地址是个立即数,并不能当作地址去使用,需要进行转换,这里用两个宏进行最终的操作:
//计算位带别名区地址,并转换为地址指针
#define BITBAND(addr,bitnum) (*(__IO unsigned long *)((addr & 0xF0000000) + 0x2000000+((addr & 0xFFFFF) << 5) + (bitnum << 2)))
//最终操作的寄存器
#define BIT_ACTION(addr,bitnum) BITBAND(addr,bitnum)
位带操作的内容基本就这些,不太明白的话可以在看一下完整的程序。
bitband.h
#ifndef __BSP_BIT_BAND_H__
#define __BSP_BIT_BAND_H__
#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
//计算位带别名区地址
#define BITBAND(addr,bitnum) (*(__IO unsigned long *)((addr & 0xF0000000) + 0x2000000+((addr & 0xFFFFF) << 5) + (bitnum << 2)))
#define BIT_ACTION(addr,bitnum) BITBAND(addr,bitnum)
/*A-H端口ODR地址*/
#define GPIOA_ODR_ADDR (GPIOA_BASE+0x14)
#define GPIOB_ODR_ADDR (GPIOB_BASE+0x14)
#define GPIOC_ODR_ADDR (GPIOC_BASE+0x14)
#define GPIOD_ODR_ADDR (GPIOD_BASE+0x14)
#define GPIOE_ODR_ADDR (GPIOE_BASE+0x14)
#define GPIOF_ODR_ADDR (GPIOR_BASE+0x14)
#define GPIOG_ODR_ADDR (GPIOG_BASE+0x14)
#define GPIOH_ODR_ADDR (GPIOH_BASE+0x14)
/*A-H端口IDR地址*/
#define GPIOA_IDR_ADDR (GPIOA_BASE+0x10)
#define GPIOB_IDR_ADDR (GPIOB_BASE+0x10)
#define GPIOC_IDR_ADDR (GPIOC_BASE+0x10)
#define GPIOD_IDR_ADDR (GPIOD_BASE+0x10)
#define GPIOE_IDR_ADDR (GPIOE_BASE+0x10)
#define GPIOF_IDR_ADDR (GPIOR_BASE+0x10)
#define GPIOG_IDR_ADDR (GPIOG_BASE+0x10)
#define GPIOH_IDR_ADDR (GPIOH_BASE+0x10)
/*A-H端口输出宏*/
#define PAout(n) BIT_ACTION(GPIOA_ODR_ADDR,n)
#define PBout(n) BIT_ACTION(GPIOB_ODR_ADDR,n)
#define PCout(n) BIT_ACTION(GPIOC_ODR_ADDR,n)
#define PDout(n) BIT_ACTION(GPIOD_ODR_ADDR,n)
#define PEout(n) BIT_ACTION(GPIOE_ODR_ADDR,n)
#define PFout(n) BIT_ACTION(GPIOF_ODR_ADDR,n)
#define PGout(n) BIT_ACTION(GPIOG_ODR_ADDR,n)
#define PHout(n) BIT_ACTION(GPIOH_ODR_ADDR,n)
/*A-H端口输入宏*/
#define PAin(n) BIT_ACTION(GPIOA_IDR_ADDR,n)
#define PBin(n) BIT_ACTION(GPIOB_IDR_ADDR,n)
#define PCin(n) BIT_ACTION(GPIOC_IDR_ADDR,n)
#define PDin(n) BIT_ACTION(GPIOD_IDR_ADDR,n)
#define PEin(n) BIT_ACTION(GPIOE_IDR_ADDR,n)
#define PFin(n) BIT_ACTION(GPIOF_IDR_ADDR,n)
#define PGin(n) BIT_ACTION(GPIOG_IDR_ADDR,n)
#define PHin(n) BIT_ACTION(GPIOH_IDR_ADDR,n)
#endif /* __BSP_BIT_BAND_H__ */
bsp_key.c中增加按键检测扫描函数Key1_Scan()和Key2_Scan()
uint8_t Key1_Scan(void)
{
if(PAin(0) == KEY_ON) //判断按键是否按下
{
while(PAin(0) == KEY_ON); //消抖,等待按键释放
return KEY_ON;
}
else
return KEY_OFF;
}
uint8_t Key2_Scan(void)
{
if(PCin(13) == KEY_ON) //判断按键是否按下
{
while(PCin(13) == KEY_ON); //消抖,等待按键释放
return KEY_ON;
}
else
return KEY_OFF;
}
main.c
#include "stm32f4xx.h"
#include ".\LED\bsp_led.h"
#include ".\KEY\bsp_key.h"
#include ".\BIT_BAND\bsp_bit_band.h"
int main(void)
{
LED_Config();
Key_Config();
while(1)
{
if(Key1_Scan() == KEY_ON) //K1按键检测
{
PHout(10) = 0; //红灯亮
}
if(Key2_Scan() == KEY_ON) //K2按键检测
{
PHout(10) = 1; //红灯灭
}
}
}