[CW32F030系列] 位带操作分析

[复制链接]
 楼主| belindagraham 发表于 2025-7-11 18:06 | 显示全部楼层 |阅读模式
8051单片机可以直接对某一位IO进行读写操作,而Cortex-M3的位带操作是8051位寻址区的加强版。使用位带操作后,可以使用普通的加载/存储指令对单一的比特进行读写操作。
一、相关概念。
位带区:支持位带操作的地址区。
位带别名区:对别名地址的访问最终作用到位带区的访问上。位带别名区对位带区的访问有个地址映射过程。

二、位带操作的原理
位带操作的最终目的是想对位带区的比特位进行独立的读写操作。但它是通过对位带别名区的操作来实现的。
具体过程如下:
对位带别名区进行读写访问,位带别名区通过地址映射关系映射到相应的位带区,对位带区进行原始比特的读写操作。



三、地址映射
上面对位带操作进行了简单的介绍,那么哪些地址支持位带操作?它们对应的位带别名区地址又是多少?两者间的地址映射关系又是怎么样的?
1、支持位带操作的地址
Cortex-M3有两个区支持位带操作。这两个区除了像普通的RAM一样使用外,还可以通过位带别名区进行操作。这两个区(位带区)分别是:
SRAM区中的最低1MB:0x2000 0000 - 0x200F FFFF
片上外设去中的最低1MB:0x4000 0000 - 0x400F FFFF
这两个位带区对应的位带别名区是:
SRAM区中的最低1MB的位带区对应的位带别名区:



片上外设去中的最低1MB的位带区对应的位带别名区:



2、位带区与位带别名区的地址映射关系
位带别名区把位带区的每一个比特位膨胀成32位的字,即位带区的每一个比特位对应位带别名区中一个4个字节大小的地址。
下图示例SRAM区中的最低1MB的位带区与位带别名区的膨胀对应关系:



计算公式:
对于SRAM位带区的某个比特,记它所在字节地址为A,比特位序号为n(0<=n<=7),则该比特位对应的位带别名区的地址为:



对于片上外设位带区的某个比特,记它所在字节地址为A,比特位序号为n(0<=n<=7),则该比特位对应的位带别名区的地址为:



说明:“*4”表示一个字为4个字节,“*8”表示一个字节中有8个比特。

四、读写操作的机制
在位带区中,虽然每个比特位都映射到别名区的一个字,但别名区的字只有LSB(最低位有效)有效,所以读写操作是对别名区字的LSB进行读写,LSB的数值是0或1.
1、读流程:



举例子:
读取SRAM地址0x2000 0000 的第二位的值:



2、写流程:读、改、写



举例子:
将SRAM地址0x2000 0000 的第二位的值置1:




五、位带操作编程实现
在C编译器中并没有直接支持位带操作,比如,C编译器并不知道同一块内存可以用不同的地址来访问,也不知道对位带别名区的访问只对LSB有效。欲在C中使用位带操作,最简单的做法时#define一个位带别名区的地址。
1、位带操作的宏定义
为了简化位带操作,我们可以建议一个把”位带地址+位序号“转换为别名地址的宏,再建立一个把别名地址转换为指针类型的宏:

  1. #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))                         /* 把”位带地址+位序号“转换为别名地址的宏  */
  2. #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))                 /* 把别名地址转换为指针类型的宏  */
  3. #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))         /* 某地址某一位的位带操作的宏  */
复制代码



2、举个例子
下面以 GPIOA->ODR寄存器(地址为0x40020014)为例,通过位带操作进行读写,并与传统方式读写比较,并通过串口将信息发送到控制台显示。代码如下:
头文件定义:

  1. #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
  2. #define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
  3. #define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))
  4. //IO口地址映射
  5. #define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
  6. #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  
  7. main函数:
  8. int main(void)
  9. {
  10.         u8 Temp;
  11.         u16 Data;
  12.         Data = 0;
  13.        
  14.          Clock_Init(336,8,2,7);//初始化时钟为 168Mhz
  15.         delay_init(168);  //初始化延时函数
  16.         uart_init(84,115200);  //串口初始化为 115200

  17.         printf("init finished\r\n");
  18.        
  19.         GPIOA->ODR = 0xffff;                        /* 传统方式写:将GPIOA->ODR各个位写1 */
  20.         printf("Tratidional operate: GPIOA->ODR[0x%x]:0x%x\r\n", &GPIOA->ODR, GPIOA->ODR);               
  21.         GPIOA->ODR = 0x0;                        /* 将GPIOA->ODR清零 */
  22.         printf("Clear GPIOA->ODR: GPIOA->ODR[0x%x]:0x%x\r\n", &GPIOA->ODR, GPIOA->ODR);

  23.         for(Temp = 0; Temp < 16; Temp++)
  24.         {
  25.                 PAout(Temp) = 1;                /* 位带操作写:通过位带别名区写GPIOA->ODR每一位为1 */
  26.         }               
  27.         printf("Bit-band write: GPIOA->ODR[0x%x]:0x%x\r\n", &GPIOA->ODR, GPIOA->ODR);        /* 可以看到位带操作写后,GPIOA->ODR各位的值 */

  28.         for(Temp = 0; Temp < 16; Temp++)
  29.         {
  30.                 Data |= (PAout(Temp) << Temp);                /* 位带操作读:通过位带别名区读GPIOA->ODR每一位的值,并存在Data中 */
  31.         }               
  32.         printf("Bit-band read: GPIOA->ODR[0x%x]:0x%x\r\n", &GPIOA->ODR, Data);                /* 可以看到位带操作读后,GPIOA->ODR各个比特位对应的别名区的值 */
  33.        
  34.         while(1)
  35.         {
  36.                
  37.         }
  38.        
  39.         return 1;
  40. }
复制代码




串口打印的信息:



AdaMaYun 发表于 2025-7-16 21:10 | 显示全部楼层
位带操作非常实用
OKAKAKO 发表于 2025-7-16 22:37 | 显示全部楼层
位带别名区把位带区的每一个比特位膨胀成32位的字,即位带区的每一个比特位对应位带别名区中一个4个字节大小的地址。
中国龙芯CDX 发表于 2025-7-17 22:21 | 显示全部楼层
位代操作非常重要的操作
星辰大海不退缩 发表于 2025-7-21 13:03 | 显示全部楼层
Cortex-M3的位带操作非常实用
我是一颗胖蘑菇 发表于 2025-7-23 13:26 | 显示全部楼层
位带操作确实可以提高访问效率,特别是在需要频繁访问单个位的情况下。这种方式可以减少对总线的占用,提高效率。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

30

主题

1700

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部