STM32入门:从位到位带操作
位(bit)-> 字节(byte)->字(word)
1、位(bit)
位来自英文bit(比特),常用‘b’表示
是计算的内部数据存储的最小单位,其状态有1与0两种。
2、字节(byte)
字节来自英文byte(拜特),常用’B’表示
字节是计算机中数据的处理基本单位,一个字节’B’ = 8位’b’(bit)
3、字(word)
计算机进行数据处理时,一次存取、加工和传送的数据长度称为字(word)
一个字由若干个字节组成,不同的计算机系统的字长是不同的,常见的有8位(1字节)、16位(2字节)、32位(4字节)、64位等(8字节),字长越长,计算机一次处理的信息位就越多,精度就越高,字长是计算机性能的一个重要指标。目前主流微机都是32位机。
STM32寄存器
在STM32中,每个外设寄存器含有多个对应的内部寄存器。且外设寄存器的所占字节个数是各个内部寄存器所占字节个数的总和。
举个栗子啊~
在stm32各个端口的起始地址与结束地址之间包含多个内部细分的寄存器。
其中如GPIO端口A的起始地址是:0x40010800 ,那么要设置GPIO端口A的第1个引脚的输出模式为推挽输出、输出速率为50MHZ时,就要用到GPIO寄存器中的端口配置低寄存器(GPIOA_CRL)
如上
可知端口配置低寄存器(GPIOA_CRL)的偏移地址为:0x00
可得端口配置低寄存器(GPIOA_CRL)的起始地址为:(0x40010800+0x00)
对应设置GPIO端口A的第1个引脚**的输出模式为推挽输出、输出速率为50MHZ
对应代码:
#define GPIOA (int)0x40010800
*((int *)(GPIOA+0X00)) = 0x44444443;
由于端口配置低寄存器(GPIOA_CRL)的复位值为0x4444 4444,因此设置时在此状态上进行设置。
细心的小伙伴会发现,我们用8位十六进制的数来赋值这个32位的寄存器。
并且端口配置低寄存器(GPIOA_CRL)到端口配置高寄存器(GPIOx_CRH)的偏移地址偏动了4个字节(0x00->0x04)
因此可得:
一个寄存器中由4个字节组成(及32位),其中2位十六进制数(8位)表示一个字节,且一个字节由8对应4个框。
可参考上方复位值进行理解
在端口配置高寄存器(GPIOx_CRH) (x=A…E)中,其仅能设置前8个引脚(0 -> 7引脚),因此一个十六进制位(4个bit位)控制一个引脚。端口配置高寄存器(GPIOx_CRH)同理
位带操作
搞清楚了寄存器正常情况下的位的关系后,我们开搞位带!
一个寄存器中由4个字节组成(及32位),其中2位十六进制数(8位)表示一个字节,且一个字节由8对应4个框。
在开搞前,我们要先搞清楚他为什么要搞位带操作呢?
学过51单片机的同学可能都知道,我们在设置引脚时是直接可以对引脚进行0/1设置的,例如P0_1引脚置高。
P0^1=1
很是方便,但STM32不一样,它要通过寄存器来设置引脚,我们拿端口输出数据寄存器(GPIOx_ODR) (x=A…E)进行讨论。
端口输出数据寄存器(GPIOx_ODR) (x=A…E)的复位值为0x0000 0000
之前我们说过,16进制的中的一位代表的4个(bit)位。
在端口输出数据寄存器(GPIOx_ODR) 中一位及代表一个引脚。那么要想设值其中一个引脚,就必须修改对应的16进制位。一个十六进制位相当于直接将四个引脚封装到一块,修改时也要一起调用。使用起来就很麻烦。
那么有没有办法让它一对一的设置输出呢?
那就是将每一位对应设置一个地址!
位带操作就是对位一个位对应设置相应的地址,方便对位进行操作。
那么如何来设置这个地址呢!要按怎样的格式来设置呢?
不知道有没有小伙伴看过《三体》,里面出现过一个词,叫量级,比如我们说的个、十、百、千、万就是数量级的差别。而量级,则是比数量级差距更大的概念。量级之间是跳跃式的差距,且不是连续的。简单的讲,量级就是蚂蚁、苹果、西瓜、大象、大山、地球、太阳、银河系这样大的差别。
位带操作就相当于提升了(bit)位的量级,将其提升到字节。将原地址变为位带别名地址从而得到该位相应的位带别名地址!
现在位提升了一个量级变成字节,那么字节肯定也要提升一个量级啊。字节变成字。 没有位带操作之前地址码:2位十六进制数表示一个字节(一个16进制位对应4个二进制位),且一个字节表示8位。
通过位带操作之后:将原位地址上的一个16进制位变为一个字,一个字下含有4个字节,一个字节为8位。一个字代表32位
那如何从数值上表示呢?
计算方法如下:
A原地址的起始量级为字节,在对地址进行修改都是从(bit)位开始操作。因此处理完原地址A后要×8转换为比特位,加上n对应引脚的引脚号(0->F)。因为又多了一个字到字节的量级跳变(一个字下含有4个字节),因此最后整体要×4。处理后就可得出相应的位带别名地址。
位带操作程序为:
#include <stm32f10x.h>
//λ´ø²Ù×÷,ʵÏÖ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) //输出
/
void NVIC_Configuration(void);
void Delay(u16 x);