注意:本文中关于STM32的位带操作原理只适用于Cortex-M3和Cortex-M4(F)内核处理器,Cortex-M系列的其他内核处理器可能不支持位段操作(如Cortex-M0内核处理器就不支持位段操作),详情请参考相关内核处理器的指南或技术参考手册(TRM)。
1、简介
今天参考了好几篇博客,总算是对 bit-band操作有些了解了。首先,
- 什么是位带操作?
位带操作:可以实现对某一GPIO口寄存器(或SRAM内存中)的某一bit位直接赋值0或1,达到控制GPIO口输出(或改变SRAM中这一bit位的值)的目的;就如同51单片机控制GPIO口一样的方便。比如:
51:P1^0=1; //把P1口的第一个引脚设置为高电平
STM32:PAout(0)=1; //把PA口的第一个引脚设置成高电平
位带操作的对象可以是SRAM、I/O和外设空间。要实现对这些地方的某一位的操作。它是这样做的:在寻址空间(32位对应的地址空间为 4GB )的另一地方,取个别名区空间,从这个地址开始处,每一个字(32BIT)对应SRAM或I/O的一位。这样,1MB SRAM 就可以有 32MB 的对应别名区空间,就是1位膨胀到32位(1 BIT 变为1个字节)。我们对这个别名区空间内的某一字操作(置0或置1),就等于它映射的 SRAM 或 I/O 相应的某地址的某一位的操作。
那么,使用如下术语来表示位带存储的相关地址
- 使用位带操作的好处
简单来说,可以把代码缩小, 速度更快,效率更高,更安全。 一般操作要6条指令,而使用位带别名区只要4条指令。一般操作是 读-改-写 的方式, 而位带别名区是 写 操作。防止中断对 读-改-写 的方式的影响。
图1.1
2、进入正题,别名区中的字与 bit-band 区中对应位或目标位是如何关联在一起的呢
位带别名区就是把每个比特(BIT)膨胀成一个 32 位的字。 每个比特膨胀成一个32 位的字,就是把 1M 扩展为 32M 。在位带区中,每个比特都映射到别名地址区的一个字——这是只有 LSB (低位先行或最低有效)有效的字。当一个别最低名地址被访问时,会先把该地址变换成位带地址。于是,位于 RAM 地址 0X20000000 的一个字节扩展为8个32 位的字,扩展后每位相对应的的地址是:0X22000000,0X22000004,0X22000008,0X2200000C,0X22000010,0X22000014,0X22000018,0X2200001C。
由图1.1可知,
- SRAM的位带区地址范围:0x20000000-0x200FFFFF(1M 寻址空间的地址范围)
- SARM的位带别名区地址范围:0x22000000-0x23FFFFFF(32M 寻址空间的地址范围)
- 片上外设的位带区地址范围:0x40000000-0x400FFFFFF(1M)
- 片上外设的位带别名区地址范围:0x42000000-0x43FFFFFF(32M)
图 1.2
结合图5.3B和图1.2中的16进制序列表,可得别名区中的字与 bit-band 区中对应位或目标位的关联公式,映射公式如下:
对于 SRAM 位带区的某个比特,记它所在字节地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr = 0x22000000(位带别名区的起始地址)+ ((A - 0x20000000(位带区的起始地址))*8 + n)*4 = 0x22000000 + (A - 0x20000000)*32 + n*4
对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr = 0x42000000 + ((A-0x40000000)*8 + n)*4 = 0x42000000 + (A - 0x40000000)*32 + n*4
上式中,“*4” 表示一个字为 4 个字节,“*8” 表示一个字节中有 8 个比特。
也等效于
- 位带别名区的字地址 = 0x22000000 + (位带区目标位所在的字地址偏移 << 5) + (位带区目标位序号 << 2) ① (注:位带区目标位序号范围为 0-7)
- 位带别名区的字地址 = 0x42000000 + (位带区目标位所在的字地址偏移 << 5) + (位带区目标位序号 << 2) ② (注:位带区目标位序号范围为 0-7)
为了方便起见,所以将式①和式②合并成一个公式:
- 位带别名区的字地址 =(位带区目标位所在的字地址 & 0xF0000000)+ 0x02000000 + [(位带区目标位所在的字地址 & 0xFFFFF << 5 ] +(位带区目标位编号 << 2) ③(注:位带区目标位序号范围为 0-31)
下面解释一下该公式的含义:
这部分是膨胀公式,位带别名区就是把每个比特(BIT)膨胀成一个 32 位的字,乘以8是先把单元内的每一位上升到字节的高度上,这样,你想设置第二位,就直接在原来的基地址上+2就可以了,确定完是第几位,再乘4,就是把位再上升到字的高度上,也就是每一位对应一个32位的字,这样最终的地址转换就完成.字节和字区别什么意思?我想的是把1m的bitband区域的一个比特位膨胀为32位的stm32中地址。在stm32中无法直接对位直接访问,只能访问32位的地址(所谓的寄存器映射的地址),故将要控制的GPIO口的一个bit位,就要这个bit位膨胀为32位的字节0地址。
3、下面举例 SRAM bit-band 别名区和 SRAM bit-band 区之间的 bit-band 映射的例子:
运用位带访问GPIOB_BSRR的bit 5.
GPIOB_BSRR地址为0x4001 0C00 + 0x10,位带区的外设的基地址为0x4000 0000,所以GPIOB_BSRR与外设的基地址相差(0x4001 0C10 - 0x4000 0000)个字节(byte),每个字节8bit,额外加上5个bit,于是
=(0x4001 0C10-0x4000 0000)*8+5个bit
回到位带运算中,位带区一个32bit,即4byte代表一个bit,增加一个bit,位带别名区的地址增加4个字节(即增加32bit),那么最终位带别名区的地址是:
0x4200 0000+((0x4001 0C10-0x4000 0000)*8+5)*4 = 0x4221 8014
0x4200 0000+((0x4001 0C10-0x4000 0000)*32 + 5*4 = 0x4221 8014
在程序中可以这样定义:
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 0xFFFFF) << 5)+(bitnum << 2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
同理
- 地址 0x23FFFFE0 的别名字映射为 0x200FFFFF 的bit-band 字节的位 0:
0x23FFFFE0 = 0x22000000+((0x200FFFFF - 0x20000000)*32)+0*4 - 地址 0x23FFFFFC 的别名字映射为 0x200FFFFF 的bit-band 字节的位 7:
0x23FFFFFC = 0x22000000+(0xFFFFF*32)+7*4 - 地址 0x22000000 的别名字映射为 0x20000000 的bit-band 字节的位 0:
0x22000000 = 0x22000000+(0*32)+0*4 - 地址 0x220001C 的别名字映射为 0x20000000 的bit-band 字节的位 0:
0x2200001C = 0x22000000+(0*32)+7*4
以下是一个根据bit-band区域地址和目标位来计算bit-band alias region映射的对应字节的宏(来源于Atmel ASF的bit-banding Example):
1 #define BITBAND_ALIAS_ADDRESS(addr, bit) \
2 ((volatile uint32_t*)((((uint32_t)(addr) & 0xF0000000) + 0x02000000) \ 3 +((((uint32_t)(addr)&0xFFFFF)*32)\ 4 +( (uint32_t)(bit)*4))))
为简化位带操作,也可以定义一些宏。比如,我们可以建立一个把“位带地址+位序号”转换成别名地址的宏,再建立一个把别名地址转换成指针类型的宏:
//把“位带地址+位序号”转换成别名地址的宏
#define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x02000000 + ((addr & 0xFFFFF) << 5) + (bit<<2));
//把该地址转换成一个指针
#define MEM_ADDR(addr) *((volatile unsigned long *) (adr));
在此基础上,我们就可以如下改写代码:
MEM_ADDR(DEVICE REG0) = 0xAB; //使用正常地址访问寄存器,即把0xAB作为DEVICE REG0地址上的值
MEM_ADDR(DEVICE_REG0) = MEM_ADDR(DEVICE_REG0) | 0x2; //传统做法
MEM_ADDR(BITBAND(DEVICE_REG0, 1)) = 0x1; //使用位带别名地址
或者,
//将 “位带地址 + 位序号” 转换成位带别名地址的宏定义,并强制转换成指针
1 #define Bit_Band(addr,num) *((volatile unsigned long*)((addr&0xF0000000)+0x02000000+((addr&0xFFFFF)<<5)+(num<<2)))
以下是应用在32单片机中的具体的例子:
1 /////////////////////////////////////////////////////////////// 2 //位带操作,实现51类似的GPIO控制功能 3 //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页). 4 //IO口操作宏定义
5 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
6 #define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
7 #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
8 //IO口地址映射
9 #define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
10 #define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
11 #define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
12 #define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
13 #define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
14 #define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
15 #define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
16
17 #define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
18 #define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
19 #define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
20 #define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
21 #define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
22 #define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
23 #define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
24
25 //IO口操作,只对单一的IO口! 26 //确保n的值小于16!
27 #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
28 #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
29
30 #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
31 #define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
32
33 #define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
34 #define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
35
36 #define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
37 #define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
38
39 #define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
40 #define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
41
42 #define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
43 #define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
44
45 #define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
46 #define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
————————————————
声明:本文是参考多篇博客删减而来,在此非常感谢原作者!^-^