Bit-banding 简称位带、位段
支持位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写
对于硬件 I/O 密集型的底层程序最有用处
优点- 使代码更简洁
- 在多任务中,用于实现共享资源在任务间的“互锁”访问
多任务的共享资源必须满足一次只有一个任务访问它,即“原子操作”,位带操作可以满足这个条件
以前的”读-改-写“需要 3 条指令,导致这中间留有两个能被中断的空当,可能会出现紊乱危象
紊乱危象主程序是一个任务,ISR 是另一个任务,这两个任务并发执行
ISR 所作的改动已丢失:指 ISR 对输出端口所做的修改 0x3,被后面的主程序的修改 0x0 覆盖了

位带操作把这个“读-改-写”做成一个硬件级别支持的原子操作(具体如何实现我还不知道),避免了紊乱危象
位带操作并不只限于以字为单位的传送,也可以按半字和字节为单位传送。例如,可以使用 LDRB/STRB 来以字节为长度单位去访问位带别名区,同理可用于 LDRH/STRH。但是不管用哪一个,都必须保证目标地址对齐到字的边界上
产生由来在 STM32 中不能直接操作寄存器的某一个 Bit 位,比如 PA 端口 port input data register 中的 ODR,只能以 32bit 去操作

而 port output data register 这种 ODR 可以单独操作某个 bit 位

为了能直接操作 ODR 的某个 Bit 位,在内核中开辟了一块地址区域(位带别名):可将 ODR这类 Bit 位(位带区)映射到位带别名区域对应的地址,只需操作映射后的地址,就可实现操作 ODR1 位
原理- 位带区: 支持位带操作的地址区
- 位带别名: 对别名地址的访问最终作 CM3 将用到位带区的访问上(中途有一个地址映射过程)
位带区大小 1MB,由于每个 bit 都要映射到位带别名区,每个 bit 需要膨胀为一个 字(只有 LSB 有效的字)。对于一个 32 位的 MCU 来说,1MB 需要 1MB * 32bit = 32MB(这里计算式不严谨)的位带别名区空间
当一个别名地址被访问时,会先把该地址变换成位带地址:
对于读操作,读取位带地址中的一个字,再把需要的位右移到 LSB,并把 LSB 返回
对于写操作,把需要写的位左移至对应的位序号处,然后执行一个原子的“读-改-写”过程
示例- 在地址 0x20000000 处写入一个字的数据 0x3355AACC
- 读取地址 0x22000008,本次读访问将读取 0x20000000,并提取 bit2,值为 1
- 往地址 0x22000008 处写 0,本次操作将被映射成对地址 0x20000000 的“读-改-写”操作(原子的),把 bit2 清 0
- 现在再读取 0x20000000,将返回 0x3355AAC8(bit2 已清零)
位带别名区的字只有 LSB 有意义。在访问位带别名区时,不管使用哪一种长度的数据传送指令(字/半字/字节),都把地址对齐到字的边界上,否则会产生不可预料的结果

CM3在 Cortex-M3 中有两个区实现了位带操作
SRAM 区的最低 1MB 范围
片内外设区的最低 1MB 范围



- // SRAM 位带区: 0X2000 0000~0X2010 0000
- // SRAM 位带别名区:0X2200 0000~0X23FF FFFF
- // 外设 位带区: 0X4000 0000~0X4010 0000
- // 外设 位带别名区:0X4200 0000~0X43FF FFFF
- // 把“位带地址+位序号”转换成别名地址的宏
- #define BITBAND(addr, bitnum) ((addr & 0xF0000000) + 0x02000000 + ((addr & 0x00FFFFFF) << 5) + (bitnum << 2))
- /*
- * addr & 0xF0000000,取地址的高 4 位,提取 SRAM 或 外设地址 的高地址
- * 如 2,+ 0x02000000 则 =0X2200 0000,即是 SRAM
- * 如 4,+ 0x02000000 则 =0X4200 0000,即是 外设
- *
- * addr & 0x00FFFFFF,屏蔽高 8 位,偏移位带区多少个字节
- * <<5 等于 *8*4,位带区一个地址表示一个字节,一个 bit 膨胀成一个字(32位MCU),即 4byte
- * <<2 等于 *4,一个 bit 膨胀成一个字(32位MCU),即 4byte
- *
- * SRAM位带别名地址
- * AliasAddr= 0x22000000+((A-0x20000000)*8+n)*4 =0x22000000+ (A-0x20000000)*8*4 +n*4
- * 外设位带别名地址
- * AliasAddr= 0x22000000+((A-0x20000000)*8+n)*4 =0x22000000+ (A-0x20000000)*8*4 +n*4
- */
- // 把一个地址转换成一个指针
- #define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
- // 把位带别名区地址转换成指针
- #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
- #define DEVICE_REG0 ((volatile unsigned long *) (0x40000000))
- #define DEVICE_REG0_BIT0 ((volatile unsigned long *) (0x42000000))
- #define DEVICE_REG0_BIT1 ((volatile unsigned long *) (0x42000004))
- *DEVICE_REG0 = 0xAB; // 使用正常地址访问寄存器
- *DEVICE_REG0 = *DEVICE_REG0 | 0x2; // 使用传统方法设置 bit1
- *DEVICE_REG0_BIT1 = 0x1; // 通过位带别名地址设置 bit1
- CM4
- #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))
- // IO口地址映射
- #define GPIOA_ODR_Addr (GPIOA_BASE + 20) // 0x40020014
- #define GPIOB_ODR_Addr (GPIOB_BASE + 20) // 0x40020414
- #define GPIOC_ODR_Addr (GPIOC_BASE + 20) // 0x40020814
- #define GPIOD_ODR_Addr (GPIOD_BASE + 20) // 0x40020C14
- #define GPIOE_ODR_Addr (GPIOE_BASE + 20) // 0x40021014
- #define GPIOF_ODR_Addr (GPIOF_BASE + 20) // 0x40021414
- #define GPIOG_ODR_Addr (GPIOG_BASE + 20) // 0x40021814
- #define GPIOH_ODR_Addr (GPIOH_BASE + 20) // 0x40021C14
- #define GPIOI_ODR_Addr (GPIOI_BASE + 20) // 0x40022014
|