架构图
带位操作原理
以往我们在使用暂存器时,都是在操作该暂存器32bits(4bytes)的储存地址,要对其中单一bit进行操作,可以仰赖bit operation来完成。而bit-banding(位带操作)的目的就是实现直接操作单一比特位
为了达成这个目的首先我们有必要理解STM32的特殊存储区段位带区与位带别名区。
下图中的红色方框代表位带区的範围,可以看到SRAM中位带区佔有1MB,外设区段部分,1MB的储存範围刚好涵盖了AHB, APB1, APB2,可以说我们使用的大部分外设,例如GPIO, UART, SPI等等都在位带区当中
外设的位带区地址範围: 0x40000000~0x400F0000
SRAM的位带区地址範围: 0x20000000~0x200FFFFF
位带区不仅可以用来储存暂存器数据外还有一个膨胀32倍位带别名区,这个位带别名区就是操作单一比特位的关键。首先我们来看看为何位带别名区是位带区的32倍
如图所示,每个比特位都可以用一个完整的32bits地址来代替,有就是说一个32bits的暂存器在位带别名区会被扩展成32*32=1024bits,因此位带别名区的储存空间为1MB的位带区空间乘上32等于32MB。由此可知,我们可以直接操作位带别名区地址来达到操作位带区单一比特位的目的
需要注意膨胀过后的位带别名区只有LSB(最低位)比特位有效
地址转换
不过要操作位带别名区之前须要先知道位带区与位带别名区之间的关係,幸好地址之间的转换是有迹可循的:
外设地址转换公式:0x42000000 + (A-0x40000000)*32+n*4
SRAM地址转换公式: 0x22000000 + (A-0x20000000)*32+n*4
上述公式可以拆解成: 位带别名区地址 = 位带别名区首地址 + (目标位带区地址 - 位带区首地址)*32 + 第n个比特位*4
该转换公式简单来说就是地址膨胀后的位置运算,各个位带别名区的起始位置为:
外设位带别名区首地址:0x42000000
SRAM位带别名区首地址: 0x22000000
为了在运算当中不用在程式中编写两个不同的运算式,我们试着将两个公式用一条语句来完成:
(bit_band_addr & 0xf0000000) + 0x02000000 + ((bit_band_addr & 0x000fffff << 5) + n << 2)
程式案例
位带操作的实作非常简单,主要就下列4个步骤,其中最重要的就是地址转换以及转换成指标类型
寻找指定位带区地址转换成位带别名地址转换成指标类型进行赋值因为我们计算出来的"地址"充其量也就只是一个整数值而已,必须经过类型转换告诉编译器它是一"地址",引此我们使用macro来处理运算出来的位带别名区数值
下列程式码我们只要使用macroBIT_GPIOH(暂存器地址, 比特位)
就可以成功转换成位带别名区地址:
#define BITBAND(addr, bitnum) ((addr&0xf0000000) + 0x2000000 + ((addr&0x000fffff)<<5) + (bitnum<<2))#define MEM_ADDR(addr) *(volatile uint32_t*)(addr)#define BIT_GPIOH(addr, n) MEM_ADDR(BITBAND(addr, n))
举个简单的LED操作来说,透过位带别名区也可以控制GPIO的输出,例如以下範例程式码,只要知道别名区地址后,操作方式其实跟普通的暂存器操作无二致:
#include "bsp_led.h"void GPIO_Initial_API(uint16_t LED){ BIT_GPIOH(BIT_MODER, (LED*2)) = SET;BIT_GPIOH(BIT_MODER, ((LED*2)+1)) = RESET;BIT_GPIOH(BIT_TYPER, LED) = GPIO_OType_PP;BIT_GPIOH(BIT_SPEEDER, (LED*2)) = SET;BIT_GPIOH(BIT_SPEEDER, ((LED*2)+1)) = SET;BIT_GPIOH(BIT_PuPr, (LED*2)) = SET;BIT_GPIOH(BIT_PuPr, ((LED*2)+1)) = RESET;BIT_GPIOH(BIT_ODR, LED) = SET;}void LED_Init(void){/*Enable peripheral cloack*/RCC_AHB1PeriphClockCmd(LED_RCC, ENABLE);/*Initialize pin to set*/GPIO_Initial_API(LED1);GPIO_Initial_API(LED2);GPIO_Initial_API(LED3);}void LED_button_toggle(uint16_t LED, uint8_t key){if(key){BIT_GPIOH(BIT_ODR, LED) = BIT_GPIOH(BIT_IDR, LED) ^ 0x01;}}
附上header对照
#ifndef __BSP_LED_H_#define __BSP_LED_H_#include "stm32f4xx.h"#include "stm32f4xx_gpio.h"#include <stdio.h>#include <stdint.h>#include "time.h"/*Increase portability of bsp_led*/#define LED_Red_PIN GPIO_Pin_10#define LED_Green_PIN GPIO_Pin_11#define LED_Blue_PIN GPIO_Pin_12#define LED_PORT GPIOH#define LED_RCC RCC_AHB1Periph_GPIOH#define LED1 10#define LED2 11#define LED3 12#define SET 1#define RESET 0#define BIT_MODER (GPIOH_BASE)#define BIT_TYPER (GPIOH_BASE + 0x04)#define BIT_SPEEDER (GPIOH_BASE + 0x08)#define BIT_PuPr (GPIOH_BASE + 0x0c)#define BIT_IDR (GPIOH_BASE + 0x10)#define BIT_ODR (GPIOH_BASE + 0x14)#define BITBAND(addr, bitnum) ((addr&0xf0000000) + 0x2000000 + ((addr&0x000fffff)<<5) + (bitnum<<2))#define MEM_ADDR(addr) *(volatile uint32_t*)(addr)#define BIT_GPIOH(addr, n) MEM_ADDR(BITBAND(addr, n))extern void LED_Init(void);extern void LED_button_toggle(uint16_t PIN, uint8_t key);#endif /*__BSP_LED_H_*/