一、什么是位带操作
位带操作简单讲就是将一个对二进制位的操作映射到一个32位的地址上,通过给这个地址置0或1来给这个二进制位置0或1。
二、CM3的位带操作
在CM3支持的位段中,有两个区中实现了位段。
其中一个是 SRAM 区的最低 1MB 范围,0x20000000‐0x200FFFFF(SRAM 区中的最低 1MB);
第二个则是片内外设区的最低 1MB范围,0x40000000‐0x400FFFFF(片上外设区中的最低 1MB)。
从上图可以看出,SRAM中地址为0x20000000单元的32位Bit可以分别被映射到0x22000000-0x2200008F的128字节的地址范围中,即0x20000000-0x20000003的4个字节的32位Bit被分别映射到地址为0x22000000, 0x22000004, 0x22000008, 0x2200000C, 0x22000010, …, 0x22000080, 0x22000084,0x22000088, 0x2200008C的32个地址上。
同样,对片内外设寄存器的操作也是类似的,只不过地址的范围不同。片内外设中地址为0x40000000单元的32位Bit可以分别被映射到0x42000000-0x2200008F的128字节的地址范围中,即0x40000000-0x40000003的4个字节的32位Bit被分别映射到地址为0x42000000, 0x42000004, 0x42000008, 0x4200000C, 0x42000010, …, 0x42000080, 0x42000084,0x42000088, 0x4200008C的32个地址上。
三、编程中如何进行位带地址转换
对SRAM位带区的某个比特,记它所在字节地址为A,位序号为n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr=0x22000000+((A-0x20000000)*8+n)*4=0x22000000+(A-0x20000000)*32+n*4
对于片上外设位带区的某个比特,记它所在字节的地址为A,位序号为n(0<=n<=7),则该比特在别名区的地址为:
AliasAddr=0x42000000+((A-0x40000000)*8+n)*4=0x42000000+(A-0x40000000)*32+n*4
对于SRAM中的某个地址肯定是在0x20000000~0x200FFFFF之间,而在片内外设的某个地址肯定在0x40000000~0x400FFFFF之间,假设地址为A,可以通过:
A &0xF0000000 + 0x02000000
得到地址为A的数据对应的位带区的首地址,后面的(A-0x20000000)*32+n*4和(A-0x40000000)*32+n*4可以统一为(A-A&0xF0000000)*32+n*4,则两个区间的位带地址的表达式可以统一为:
AliasAddr= (A& 0xF0000000 + 0x02000000)+ (A-A&0xF0000000)*32+n*4
将乘法运算换成左移位运算,则表示为:
AliasAddr= (A& 0xF0000000 + 0x02000000)+ ((A-A&0xF0000000)<<5)+(n<<2)
而(A-A&0xF0000000)实际上就是地址A相对于0x20000000或者0x40000000的偏移地址,只需要保留低20位,高12位取0即可,所以(A-A&0xF0000000)等效于:A&0xFFFFF
在C编程中定义如下宏进行地址转换:
#defineBITBAND(addr,n) ((addr&0xF0000000) + 0x02000000 +((addr&0xFFFFF)<<5) + (n<<2))
下面的宏将地址转换成变量形式:
#defineMEM_ADDR(addr) *((volatile unsigned long *)(addr))
下面的宏将某一具体地址及第n位映射到位带
#defineBIT_ADDR(addr,n) MEM_ADDR(BITBAND(addr,n))
有了这些宏定义,就可以对位映射到位带区了。例如对GPIOA的数据输出寄存器的第0为操作,GPIOA的基地址为0x40010800,而端口输出寄存器GPIOx_ODR的地址偏移为12,则GPIOA_ODR寄存器的地址为0x4001080C,可以定义如下的宏:
#definePAOut(n) BIT_ADDR(0x4001080C , n)
这样,如果在程序中想在GPIOA_0输出低电平,只需要如下赋值语句:
PAOut(0) = 0;
四、应用实例
1、硬件连接:现在将STM32F103ZET6的GPIOC的0-7引脚连接到共阳数码管的a-h端。
2、采用STM32F10X外设库函数,这里使用到stm32f10x_rcc.h, stm32f10x_rcc.c, stm32f10x_gpio.h, stmf10x_gpio.c,项目的创建过程请参看:https://blog.csdn.net/fanxp66/article/details/80215090
3、新建led.h头文件,输入以下内容:
/* LED头文件,GPIO初始化 GPIO位带操作 */ #ifndef __LED__H #define __LED__H #include "stm32f10x_gpio.h" #include "stm32f10x_rcc.h" //定义位带地址宏 #define BITBAND(addr,bitnum) ((addr&0xF0000000) + 0x02000000 + ((addr&0x000FFFFF)<<5) + (bitnum<<2)) #define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) #define BIT_ADDR(addr,bitnum) MEM_ADDR(BITBAND(addr,bitnum)) //IO口地址映射 //数据输出寄存器地址 #define GPIOC_ODR_Addr (GPIOC_BASE + 12) //定义GPIOC的位地址变量宏,位输出宏 #define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n)
#define LED_PORT GPIOC #define LED_PIN (GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7) #define LED_PORT_RCC RCC_APB2Periph_GPIOC void LED_Init(void); #endif |
4、新建led.c程序文件,内容如下:
#include "led.h" void LED_Init() { GPIO_InitTypeDef GPIOC_0_mode; RCC_APB2PeriphClockCmd( LED_PORT_RCC, ENABLE ); //使能GPIOC时钟 GPIOC_0_mode.GPIO_Pin = LED_PIN; GPIOC_0_mode.GPIO_Speed = GPIO_Speed_50MHz; GPIOC_0_mode.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOC, &GPIOC_0_mode); } |
5、将led.c加入到项目的"User"组中,在项目配置中,配置C/C++的包含路径有能找到led.h的路径。
6、修改main.c程序,内容为:
#include "stm32f10x.h" #include "stm32f10x_rcc.h" #include "led.h" void delay(int t) { int i; for( ;t>0; t--) for(i=0;i<1000;i++); } int main() { u32 i,j; //共阳数码管’0’-‘9’的显示码 u32 LED[10] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90}; LED_Init(); while(1) { for(i=0;i<10;i++) { //根据 LED[n]数组的值决定数码管各个段位的显示 for(j=0; j<8; j++) if( ~(LED[i]) & 0x1<<j ) PCout(j) = 0; else PCout(j) = 1; delay(10000); } } } |