内存位带操作

本节主要介绍正点原子教程 [STM32F1开发指南-寄存器版本_V3.3.pdf]中5.2.1节:IO 口的位操作实现,该节主要讲解sys.h文件中IO代码段(如下所示)的宏定义方法。该方法主要为了用位带操作,在STM32F1上实现51类似的GPIO控制功能。废话不多说,看代码

//位带操作,实现51类似的GPIO控制功能
//具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
//IO口操作宏定义
#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+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#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)  //输入
/////////////////////////////////////////////////////////////////

添置过渡的话

位带区和位带别名区

位带操作 通过位带别名区(32MB)映射到位带区(1MB),实现位带区的原子操作。换句话说就是将位带区的每个位(bit) 映射到一个地址上,通过更细化的地址,实现位操作。

Cortex M3 预定义存储器映射
图1 Cortex M3 预定义存储器映射

支持位带操作的两个内存区范围是:

位带别名区地址
图2 位带别名区地址
位带区内存范围(Bit Band Region) 位带别名区(Bit Band Alias)
0x20000000-0x200FFFFF (SRAM区中的最低1MB) 0x22000000- 0x23FFFFFC(32MB)
0x40000000-0x400FFFFF (片上外设区中的最低1MB) 0x42000000- 0x43FFFFFC(32MB)
位带别名区地址和位带区地址换算关系
1. 位带区字节地址:A 位
2. 序号:n (0<=n<=7)
SRAM位带区比特在别名区的地址
AliasAddr = 0x22000000 + ((A-0x20000000)*8+n)*4 = 0x22000000+(A-0x20000000)*32+n*4
片上外设区比特在别名区的地址
AliasAddr = 0x42000000 + ((A-0x40000000)*8+n)*4 = 0x42000000+(A-0x40000000)*32+n*4
这里还需要一个通用的地址换算方法,同时适用于SRAM和片上外设区,也会被应用在[C语言实现](#C 语言中实现)
AliasAddr = (A & 0xF0000000) +0x2000000 + ((A & 0xFFFFF)<<5) + (n<<2)

关于 AliasAddr = (A & 0xF0000000) +0x2000000 + ((A & 0xFFFFF)<<5) + (n<<2),作如下解释:

1. `(A & 0xF0000000)` :与运算保留最高位,可能是2,也可能是4;
2.  `(A & 0xF0000000) +0x2000000`:注意0x2000000为6个0,这样组合成 0x22000000 或 0x42000000
3. `((A & 0xFFFFF)<<5)`:[位带区只有1M字节内存,从0x00000-0xFFFFF](https://jingyan.baidu.com/article/1709ad807b28ac4635c4f072.html),A & 0xFFFFF 保留下1M字节空间的地址名称,即后5位地址,等价于A-0x20000000 或 A-0x40000000。<< 5 相当于 *32
4. `(n<<2)`:<< 5 相当于 *4

读写操作

读操作

不使用位带操作对应汇编指令

LDR R0, =0x20000000 ; setup address
LDR R1, [R0]		; read
UBFX.W R1,R1, #2, #1	; read bit2

使用位带操作对应汇编指令

LDR R0, =0x22000008 ; setup address
LDR R1, [R0]		; read 

写操作

欲设置地址 0x2000_0000 中的比特 2

不使用位带操作对应汇编指令

LDR R0, =0x20000000 ; setup address
LDR R1, [R0]		; read
ORR.W R1, #0x4		; modify bit
STR R1, [R0]		; write back result

使用位带操作对应汇编指令

LDR R0, =0x22000008 ; setup address,
MOV R1, #1			; setup data
STR R1, [R0]		; write back result

优势

  1. 位带操作对于硬件 I/O 密集型底层程序最有用处了
  2. 可以简化跳转指令(可以直接读取状态位,取代读整个寄存器,再做掩蔽的做法)
  3. 在多任务中,用于实现共享资源在多任务间的“互锁”访问(CM3 把这个“读-改-写” 做成一个硬件级别支持的原子操作,不能被中断,不会造成多任务同时共享资源时,出现紊乱,丢失数据)

C 语言中实现

在 C 编译器中并没有直接支持位带操作。比如, C 编译器并不知道同一块内存能够使用不同的地址来访问,也不知道对位带别名区的访问只对 LSB 有效。

方法1:使用#define定义位带别名区地址

#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_BIT0 = 0x1;//使用位带别名区设置 bit1

方法2:定义宏

把“位带地址+位序号”换成别名地址的宏,再建立一个把别名地址转换成指针类型的宏:

#define DEVICE_REG0 ((volatile unsigned long *) (0x40000000))
# define BITBAND(addr,bitnum) ((addr & 0xF0000000) +0x2000000 + ((addr & 0xFFFFF)<<5) + (bitnum<<2))
# define MEM_ARRE(addr) *((volatile unsigned long *)(adr))

MEM_ADDR(DEVICE_REG0) = 0xAB;
MEM_ADDR(DEVICE_REG0) = MEM_ADDR(DEVICE_REG0) | 0x2; //使用传统方法设置 bit1
MEM_ADDR(BITBAND(DEVICE_REG0,1)) = 0x1; //使用位带别名区设置 bit1
posted @ 2022-04-06 17:41  Oddpage  阅读(255)  评论(0编辑  收藏  举报