内存位带操作
本节主要介绍正点原子教程 [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) 映射到一个地址上,通过更细化的地址,实现位操作。
支持位带操作的两个内存区范围是:
位带区内存范围(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
优势
- 位带操作对于
硬件 I/O 密集型
的底层程序
最有用处了 - 可以简化跳转指令(可以直接读取状态位,取代读整个寄存器,再做掩蔽的做法)
- 在多任务中,用于实现共享资源在多任务间的“互锁”访问(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