一. Cortex-M3存储器映射
存储区最开始的1GB空间分别为code(代码)区和SRAM区,Code区使用经过针对优化的I-Code总线来连接,同理,SRAM区使用D-Code总线连接,虽然SRAM也可以用来转载和执行代码,但这样做会使CPU不得不通过系统总线来取指令,产生额外CPU等待周期,因此在SRAM中运行代码会比代码区的片上Flash中运行要缓慢。
接下来的0.5GB存储空间是片上外设区,微控制器的所有用户设备的基地址都落在这个区域内,片上外设区和SRAM区的起始1MB区域可以用来使用位带技术实现位寻址,由于STM32所有SRAM和外设都位于这个区域,因此STM32所有存储区都可以用“字(Word)”或“位(bit)”为最小单位实现数据操作
早期的ARM7和ARM9处理器使用“&”,“|”指令来想、实现SRAM区域或者外设存储区进行位操作,这是一个“读”-> “修改” -> “写”的过程,因此为了实现单个位操作讲会耗费数个时钟周期,并增加了代码量
二.位带区域的意义
如何能像MCS51中位操作,sbit P1.0 = P1^0; P1.1 = 1;于是STM的位段和位带别名区实现这个功能。可以操作SRM,外设I/O进行位操作,
STM32支持了位带操作(bit_band),有两个区中实现了位带。其中一个是SRAM 区的最低1MB 范围,第二个则是片内外设 区的最低1MB 范围。这两个区中的地址除了可以像普通的RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区 把每个比特扩展成一个32 位的字。 每个位扩展成一个32 位的字,就是把 1M 扩展为 32M ,
于是:
RAM地址 0X200000000(一个字节)扩展到8个32 位的字,它们是:(STM32中的SRAM依然是8位的,所以RAM中任一地址对应一个字节内容)
0X220000000 ,0X220000004,0X220000008,0X22000000C,0X220000010,0X220000014, 0X220000018,0X22000001C
在 CM3 支持的位段中,有两个区中实现了位段。
其中一个是 SRAM 区的最低 1MB 范围, 0x20000000 ‐ 0x200FFFFF(SRAM 区中的最低 1MB);
第二个则是片内外设区的最低 1MB范围, 0x40000000 ‐ 0x400FFFFF(片上外设区中的最低 1MB)。
三.位带操作实现
Cortex?-M3存储器映像包括两个位段(bit-band)区。这两个位段区将别名存储器区中的每个字映射到位段存储器区的一个位,在别名存储区写入一个字具有对位段区的目标位执行读-改-写操作的相同效果。
在STM32F10xxx里,外设寄存器和SRAM都被映射到一个位段区里,这允许执行单一的位段的写和读操作。
下面的映射公式给出了别名区中的每个字是如何对应位带区的相应位的:
bit_word_addr = bit_band_base + (byte_offset x 32) + (bit_number × 4)
其中:
bit_word_addr是别名存储器区中字的地址,它映射到某个目标位。
bit_band_base是别名区的起始地址。
byte_offset是包含目标位的字节在位段里的序号
bit_number是目标位所在位置(0-31)
位带别名区地址 = 位带别名区基地址 + 字偏移地址
字偏移地址 = (字节相对位带区的偏移 << 5 ) + (位数目 << 2)
例子一:
计算GPIOB的第8位在位带别名区的地址:
寄存器地址 = 0x40010c0c
设备位带区基地址 = 0x40000000
设备位带别名区 = 0x42000000
位带区的字节偏移量 = 0x400010c0c - 0x40000000 = 10c0c
字偏移地址 = (0x10c0c << 5) + (8 << 2)
位带别名区地址 = 0x42000000 + 0x2181A0 = 0x422181A0
例子二:
下面的例子说明如何映射别名区中SRAM地址为0x20000300的字节中位2
0x22006008 = 0x22000000 + (0x300*32) + (2*4);
C代码实现:
把“位带地址+位序号”转换别名地址宏
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
把该地址转换成一个指针
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
MEM_ADDR(BITBAND( (u32)&CRCValue,1)) = 0x1;
注意:当你使用位段功能时,要访问的变量必须用 volatile 来定义。因为 C 编译器并不知道同一个比特可以有两个地址。所以就要通过 volatile,使得编译器每次都如实地把新数值写入存储器,而不再会出于优化的考虑 ,在中途使用寄存器来操作数据的复本,直到最后才把复本写回。
方法一:
#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))
然后定义:#define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
最后操作:#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出 ODR保存要输出的数据;IDR保存读入的数据
测试代码:
/*
* 爱雨测试位带操作区 实验
* 硬件接口: GPIOC9 ---- LED
* 实验现象: 低电平亮
*/
#define MeM_Addr(addr) *((volatile unsigned long *)(addr))
#define BITBAND(addr,bitnum) ((addr & 0xF0000000) + 0x2000000 + ((addr & 0xfffff) << 5 )+ (bitnum << 2))
void main()
{
RCC_Configuration(); //System clocks configuration
NVIC_Configuration(); //NVIC configuration
TIM2_Configuration();
LED_Init();
LED_Off();
while (1)
{
//LED_Prompt();
MeM_Addr(BITBAND(0x4001100C,9)) = 0; // LED点亮
}
}
方法二
#define __BITBAND__ __attribute__((bitband))
#define __BITBAND__ADDR(addr) __attribute__((at(addr)))
typedef struct
{
u32 a1 :1;
u32 a2 : 1;
} _STATE_FLAG __BITBAND__;
volatile _STATE_FLAG StateFlag __BITBAND__ADDR(0x20000000);
StateFlag.a1 = 1;
汇编代码比较:
方法三直接在 C/C++编译选项中加入“--bitband”,那么凡是结构体中的位变量都会使
用位带操作。
如果用这方法, C 文件中不用像方法二中介绍的这么麻烦。 可以直接按照普通的结构体进行
变量说明。
volatile struct
{
u32 a1 : 1;
u32 a2 : 1;
} StateFlag;
四. 位带区域局限性
要想使编译器支持位带操作,对代码是有要求的。只能使用在纯粹的结构体中,不能和 uinon、enum 混合使用。对结构体中的某个位有效,当结构体中的位是 2 位或以上,编译后还是按照“读-改-写”的顺序进行。
本文根据借鉴一下资料:
1> CortexM3技术参考手册.pdf
2> STM32中文参考手册_V10.pdf
3> STM32自学笔记 2012.pdf
4> MDK下Cortex-M3的位带操作.pdf
位操作文章:
http://blog.csdn.net/cy757/article/details/5816929