今天被同学问到 STM32 的位带操作,因为不太会所以仔细研究了一波,这里记录一下。
位带操作的原理和推算过程写在后面。这里提供 STM32F103 GPIOA 映射代码,如果不想看原理的话,直接用就好了。
欢迎关注我的公众号:烦躁的铭轩
我会不定时分享一些有意思的内容
/**
* @brief F1芯片GPIO地址映射
* @param (addr & 0xF0000000) 这一步是为了区分要操作的地址是SRAM还是外设
* @param 0x2000000 地址偏移
* @param (addr &0xFFFFF)<<5 将要操作的地址放大32倍
* @param bitnum 要操控的引脚
*/
#define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x2000000 + ((addr & 0xFFFFF) << 5) + (bitnum << 2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE + 0x0C)
#define GPIOA_IDR_Addr (GPIOA_BASE + 0x0C)
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr, n)//输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr, n)//输入
零、什么是位带操作
位带操作 ,指的就是单独对一个bit位进行读和写。
学过 51 单片机的朋友都知道,我们可以通过直接赋值的方式控制某个引脚的高低电平,这就是位带操作。
P1_0 = 1; //控制 51 单片机 1.0 脚输出高电平
但在 STM32 中,我们不能直接控制某一位的值。
GPIOx->BSRR = (uint32_t)GPIO_Pin << 16U;//F4芯片控制GPIO引脚输出
由于 32 位处理器存储空间足够多,Cortex-M就利用了额外的空间实现了称为位带(Bit-Banding)操作的硬件属性,该技术使用地址空间的两个不同区域来指向同一物理地址。
一、32 位处理器
在此问一个看起来与题无关的问题:32 位 CPU 最多支持多少 G 内存?
我们都知道 32 位处理器的地址线有 32 位,因此有
2 32 2^{32} 232 bit = 2 29 2^{29} 229 byte = 2 19 2^{19} 219 KB = 2 9 2^9 29 MB = 2 − 1 2^{-1} 2−1 GB = 0.5 GB
(1 byte = 8 bit 、1 MB = 1024 KB、1 GB = 1024 MB)
看起来这个问题的答案是 0.5 GB,但去百度搜的话,会发现答案是 4 GB。
那么 32 位 CPU 最多支持 4G 内存是怎么算出来的?
打开 STM32 的参考手册,我们发现从 CRH 到 IDR 地址偏移了 0x04,但却能控制多达 16 位寄存器。
原来 32 位处理器的“位”与我们平时说的“位(bit)”不是一个东西。或者说,32 位处理器指的是有 2 32 2^{32} 232 的位置。每个位置的容量是 8 bit。
因此不难算出,真正的内存应该是 0.5 GB * 8 = 4 GB。
二、STM32的位带操作
由于这种操作不方便(主要是速度慢),以及 32 位处理器存储空间足够多,Cortex-M就利用了多余的空间实现了称为位带(Bit-Banding)操作的硬件属性,该技术使用地址空间的两个不同区域来指向同一物理地址。
在STM32中,将外设和 SRAM 的前 1MB 内存放大 32 倍,映射到其它地址上。
而控制外设的寄存器正好位于前 1 MB 内存上,因此我们可以实现操作相应地址的方式实现操作某个位。
三、代码
根据之前的理论,我们将代码写出
/**
* @brief F1芯片GPIO地址映射
* @param (addr & 0xF0000000) 这一步是为了区分要操作的地址是SRAM还是外设
* @param 0x2000000 地址偏移
* @param (addr &0xFFFFF)<<5 将要操作的地址放大32倍
* @param bitnum 要操控的引脚
*/
#define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x2000000 + ((addr & 0xFFFFF) << 5) + (bitnum << 2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE + 0x0C)
#define GPIOA_IDR_Addr (GPIOA_BASE + 0x0C)
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr, n)//输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr, n)//输入
注意
- F1和F4的寄存器不一样,不要搞错了!
- 位带操作其实很容易出现问题,比如上述代码 n 不在 0 ~ 15 之间会溢出出现BUG,使用需谨慎。