GPIO的使用--存储系统与位带操作理解
目录
存储系统与位带操作
1、对GPIO的操作函数
2、计算机对地址的管理
3、板子地址
4、什么是位带操作
5、位带地址好处
存储系统与位带操作
1、对GPIO的操作函数
//方案一
GPIO_WriteBit(GPIOF,GPIO_Pin_9,0);
//方案二
GPIO_Write(GPIOF,0x0000);
//方案三
GPIOF->ODR = 0x0000;//寄存器操作
//方案四
*((volatile uint32_t*)(0X40021414))=0x0000;
//方案五
*((volatile uint32_t*)(0x42000000 + 0x21414*32 + 9*4)) =1;
//方案六
PFout(9)=0;
其中
GPIO_Write(GPIOF,0x0600);
GPIO_Write()本质上是对寄存器的操控:即
GPIOF ->ODR = 0x0600;
而寄存器本质上是一块存贮空间,可以理解上面代码是对于一块地址写入 0x0600.
*((volatile uint32_t*)(0X40021414)) = 0x0600;
但是对于STM32f407系列的板子,还有一种写法:
*((volatile uint32_t*)(0x42000000 + 0x21414*32 + 9*4)) = 1;
*((volatile uint32_t*)(0x42000000 + 0x21414*32 + 10*4)) = 1;
上面这个地址能实现对于LED的控制,是硬件工程师做了特殊处理,该解决方案 叫做“位带操作”。要想理解这个中方案,需要对计算机地址访问有着深刻理解。
2、计算机对地址的管理
从C语言角度理解内存:拿到地址,找到地址中的数据并返回
从计算组成理解的存储结构示意图
假设cpu中有3根地址线,那么它进过译码器后组合出来(扩展)的结果表示8根线(再多的都表示不了),进而控制8个内存单元。每一个内存单元即就是一个字符型(char)。
内存申请和释放的最小单元, 实际申请的是地址资源,而一个地址刚好对应一个字符存储空间。因此字符空间 (char)是内存分配的最小单元。【bool型实际不存在,它是变相的char】
内存不可以无限扩展,受制于机器位数,管理不了那么多的内存单元。32位机器 最多管理2^32个地址单元。64位机器可以管理2^64个地址单元。
3、板子地址
为什么 ((volatile uint32_t)(0X40021414)) = 0x0600,是可以控制LED灯?
首先我们机器是32位的地址线。这些地址线提供了2的32次方的地址。这些地址 我们机器没有用完,有剩余的。我们机器上的用在了这些地方:程序存储器, 数据存储器,IO端口和寄存器。
存储器:内存。
IO端口:端口地址。网络通信里面要选端口。USB插口。CPU使用IO来通信。
寄存器:这个和内存使用起来很像,但是具体物理实现和计算机所处位置不同。
stm32给与GPIOF组的寄存器分配的地址如下
从地址:0X4002 1400 - 0X4002 17FF.总共有0x3FF+1的空间给与了GPIOF组使用。我们之前所有的操纵函数本质上都是操作了F组的寄存器的地址。给与这个地址写入0或者1。而寄存器的数据决定了引脚上的电压为高/低。
操作函数GPIO_WriteBit();也是在操作寄存器,给寄存器写入0或1,完成灯的控制。
GPIOF组的地址如下
地址跳转步骤
则GPIOF组地址为:GPIO_BASE = 0x4000 0000 + 0x0002 0000 + 0x1400 = 0x4002 1400
输出寄存器:ODR寄存器,偏移地址 = 0x14。
GPIOF->ODR = GPIOF_BASE +0x14 = 0x40021414 ,给与这根地址线-->0x40021414,写数据。相当于配置ODR寄存器,相当于调用 GPIO的操控函数。
这几个位对应的是:
PF15,PF14,PF13,PF12,PF11,PF10,PF9,PF8,PF7,PF6,PF5,PF4,PF3,PF2,PF1,PF0,每一个位对应F组16个引脚的每一个,故此数据是 0XFFFF--0X0000。
4、什么是位带操作
了解更多点击这里-->位带(位段)操作<--
我的通俗理解:一块内存可以用两个地址来访问,一个是内存地址,一个是硬件工程师在寄存器内接的,该地址与原来PF9的输出寄存器的地址(GPIOF->ODR = 0x40021414)地位平等。
上图:红色线路是原有的地址,属于这个寄存器的,因此可以访问整个寄存器。 绿色线路是位带区域,硬件接上去的,这个地址仅仅属于这个寄存器的一个特定的位。
那么这块区域的操作有两种路线:
红色路线1:GPIOF->ODR = 0XFFFF FFFF。我们计算好第9的数据位的数据,赋 值给ODR寄存器。
绿色路线2:获取单独分给ODR的第9根的数据位的地址,并给与这根地址赋值 1/0.
位带操作可以让我们更好地理解底层存储,但也知识理解一下就好了,现有一个简化位带操作的封装函数,BitBand.h,可以简化书写
#ifndef __BITBAND_H__
#define __BITBAND_H__
#define BITBAND(addr,bitnum) 0x42000000+ (addr & 0xFFFFF)*32 + bitnum *4
#define MEM_ADDR(addr) *((volatile uint32_t*)(addr))
#define BIT_ADDR(addr,bitnum) MEM_ADDR(BITBAND(addr,bitnum))
#define GPIOA_IDR_ADDR (GPIOA_BASE + 0x10)
#define GPIOA_ODR_ADDR (GPIOA_BASE + 0x14)
#define GPIOB_IDR_ADDR (GPIOB_BASE + 0x10)
#define GPIOB_ODR_ADDR (GPIOB_BASE + 0x14)
#define GPIOC_IDR_ADDR (GPIOC_BASE + 0x10)
#define GPIOC_ODR_ADDR (GPIOC_BASE + 0x14)
#define GPIOD_IDR_ADDR (GPIOD_BASE + 0x10)
#define GPIOD_ODR_ADDR (GPIOD_BASE + 0x14)
#define GPIOE_IDR_ADDR (GPIOE_BASE + 0x10)
#define GPIOE_ODR_ADDR (GPIOE_BASE + 0x14)
#define GPIOF_IDR_ADDR (GPIOF_BASE + 0x10)
#define GPIOF_ODR_ADDR (GPIOF_BASE + 0x14)
#define GPIOG_IDR_ADDR (GPIOG_BASE + 0x10)
#define GPIOG_ODR_ADDR (GPIOG_BASE + 0x14)
#define GPIOH_IDR_ADDR (GPIOH_BASE + 0x10)
#define GPIOH_ODR_ADDR (GPIOH_BASE + 0x14)
#define GPIOI_IDR_ADDR (GPIOI_BASE + 0x10)
#define GPIOI_ODR_ADDR (GPIOI_BASE + 0x14)
#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)
#define PHout(n) BIT_ADDR(GPIOH_ODR_ADDR,n)
#define PHin(n) BIT_ADDR(GPIOH_IDR_ADDR,n)
#define PIout(n) BIT_ADDR(GPIOI_ODR_ADDR,n)
#define PIin(n) BIT_ADDR(GPIOI_IDR_ADDR,n)
#endif
封装前
封装后
5、位带地址好处
1.提升效率:比特位的读写,操作效率提升。
2.代码简洁:仅仅对于某一个位进行控制,无需考虑取值判断。(0x0200) 硬件把GPIO寄存器映射为位带区域,可以提升IO操控和通信的速度。