对位带操作的理解和应用

嵌入式_对位带操作的理解和应用

最近返回去研究stm32,看到STM32部分空间支持了位带操作,就是可以使用普通的加载/存储指令来对单一的比特进行读写。所以写篇日志记录以下对于位带操作的理解,本文参考了《Cortex M3权威指南》。



前言

M3内核中,有两个区中实现了位带。其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB 范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。


一、怎么理解位带操作

根据官方给的解释就是说,在静态RAM区有一个1M大小的存储空间A,把这块A空间上的每个字节的每一位都映射到另外一块32M大小的存储空间B上去,A被称之为位带区,B称之为位带别名区;我们可以通过对B空间进行操作间接性对A空间的每一个位进行精确读写操作。
在这里插入图片描述

有人说把这个看作为一种指针变量比较好理解,他的意思是把位带区的某个位X看作仅有1bit大小的存储空间,这个位X对应的位带别名区P理解为这个储存这个位的地址的指针变量。那么0x22000000就是位带区中位X的固定地址,只不过这个地址比较特殊,一般地址都是指向一个字节,这个地址是指向特殊区域的某一个位。
我一开始也是这样理解的,但是后来觉得这种理解方式有问题;
如果P是指针变量,那么它里面存放的是x位的地址,如果对x赋值,也因该是对地址0x22000000取值得到X的地址再进行读写操作:
但现在我们是直接对地址0x22000000进行读写操作,所以看成指针变量说不通,所以指针变量与映射还是有区别的;

二、注意事项

因为它的空间大小只有一个位,所以它的值只能是0或1。根据官方手册上所说,赋值是LSB有效,意思就是假如写了个0x8B(10001011)给这个位,那也只是最低位1有效,就给这个位写了个1 。

根据《Cortex M3权威指南》给出的具体读写过程是:当一个别地址被访问时,会先把该地址变换成位带地址。
读操作:读取位带地址中的一个字,再把需要的位右移到 LSB,并把 LSB 返回
写操作:把需要写的位左移至对应的位序号处,然后执行一个原子的“读-改-写”过程。
当然按照我的个人理解也是没什么问题的。

M3中支持位带操作的两个内存区的范围有两个,其分别是:
0x2000_0000‐0x200F_FFFF( SRAM 区中的最低 1MB)
0x4000_0000‐0x400F_FFFF(片上外设区中的最低 1MB)

对于SRAM位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr= 0x42000000+((A‐0x40000000)*8+n)*4 =0x42000000+ (A‐0x40000000)*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 个比特。

例如:
1 事先在地址 0x20000000 处写入四字节数据 0x3355AACC
2. 读取地址 0x22000008。本次读访问将读0x20000000,并提取比特 2,值为 1
3. 往地址 0x22000008 处写 0。本次操作将被映射成对地址 0x20000000 的“读-改-写操作(原子的),把比特 2 清 0。
4. 现在再读取 0x20000000,将返回 0x3355AAC8( bit[2]已清零)

二、优势与运用

1.对于硬件 I/O 密集型的底层程序最有用处,最简单就是直接操作GPIO 的管脚高低电平。
2.简化对于位的读或写。
3.在多任务务中,用于实现共享资源在任务间的“互锁”访问。多任务的共享资源必须满足一次只有一个任务访问它——亦即所谓的“原子操作”。以前的读-改-写需要 3 条指令,导致这中间留有两个能被中断的空当。是用位操作即是原子操作,不会被打断。

在C语言中并不支持直接位带操作,但是可以通过宏将位带区与位带别名区的地址定义出来进行访问。

STM32F407标准库代码如下(示例):

//把“位带地址+位序号”转换成别名地址的宏
#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                                                 
                                                                                                       
#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010                                                 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410                                                 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810                                                 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10                                                 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010                                                 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410                                                 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810                                                 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10                                                 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010                                                 
                                                                                                       
//IO口操作,只对单一的IO口!                                                                             
//确保n的值小于16,每组只有十六个GPIO!                                                                                     
#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出                                                  
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入                                                  
                                                                                                       
#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出                                                  
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入                                                  
                                                                                                       
#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出                                                  
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入                                                  
                                                                                                       
#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出                                                  
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入                                                  
                                                                                                       
#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出                                                  
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入                                                  
                                                                                                       
#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出                                                  
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入                                                  
                                                                                                       
#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出                                                  
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入                                                  
                                                                                                       
#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出                                                  
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入                                                  
                                                                                                       
#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)  //输出                                                  
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  //输入                                                  

在STM32F407使用标准库的位带操作对GPIO 输出0或1的代码如下:

运用代码如下(示例):

#define LEDx   PBout(14)


LEDx   = 0//关灯
LEDx   = 1//亮灯

注意:
当你使用位带功能时,要访问的变量必须用 volatile 来定义。因为 C 编译器并不知道同一个比特可以有两个地址。所以就要通过 volatile,使得编译器每次都如实地把新数值写入存储器,而不再会出于优化的考虑 , 在中途使用寄存器来操作数据的复本,直到最后把复本写回(这和 cache 的原理是一样的)


总结

在嵌入式操作中,支持位带操作还是比较方便与实用的
如有错误,欢迎指正,原创不易,转载留名!

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值