stm32位带操作中对内存的浅显理解

最近学习stm32位的位带操作,有很多地方不理解,才发现自己对内存的理解不够,搜集资料后,得出以下浅显理解。

基础知识

进制

  计算机以二进制代码储存信息,每个二进制数表示一位 (bit),每8个二进制数表示一个字节 (Byte) , 而再往上的KB,就是210倍的字节,总结有以下进制关系。

1 Byte = 8 bit
1 KB = 1024Byte(210=1024)
1 MB = 1024KB
1 GB = 1024MB
1 GB = 230Byte

内存地址

  内存地址使用16进制数表示,内存地址只是一个编号表示,一个内存空间,计算机以字节存储数据,所以一个内存地址对应的应该是一个字节(8 bit)的大小,这个之后会详细解释。
这里用32位机的内存做一个图例。32位机的内存地址用8位16进制数表示。

0x00000000----->[8bit];
0x00000001----->[8bit];
0x00000002----->[8bit];
0x00000003----->[8bit];

32位机

  32位表示CPU一次可以处理32位数据or一次可以寻址32位,就是4个字节的数据。也就是CPU一次可以读取的数据可以有 232种不同的数,从0000 00001111 1111,这一共232个不同的数。这 232个数就是232个地址,每个地址对应8个实际的二进制位,也就是一个地址管理一个字节的数据。
  这也就是CPU寻址能力的来由,32位机的最大寻址能力就是 232Byte = 4GB;
  对于32位机,高于4GB的内存将无法一次性寻得,所以32位机理论最大内存是 4GB。

内存的理解

下面的图将说明计算机的内存地址,实际存储样式和人所看到的表现。
现在我们向内存中写入“A” “B”"C"看看实际的存储是怎样的关系。
在这里插入图片描述注:这里的数据是编造的。

第一行的ABC是用户输入的数据,第二行是计算机实际储存的数据
而第三行就是内存地址,可见一个内存地址对应一个字节,而计算机就靠这个地址来找到储存的信息。

对STM32的理解

寄存器地址

首先我们看一下STM32的参考手册
在这里插入图片描述
可以看到,这里的内存地址使用8位16进制储存的。一个地址对应一个字节的二进制数。
再以GPIO的寄存器为例
在这里插入图片描述每个寄存器首地址以4个字节递增,每个寄存器占4个字节,对于BSRR寄存器可以画出这样的图
在这里插入图片描述由此我们可以理解:
 每个GPIO端口有16个IO口,

 而每个 GPIO端口 有7个寄存器对这16个 IO 口进行各种设置,如:输入,输出,模式,速度等等,

 每个寄存器占4个字节(Byte),32位(bit)。所以用这个32位就可以对16个 IO 口进行设置。一般这32位有以下集中分配方式:

·每两位对应一个 IO 口,如 CRL,CRH
在这里插入图片描述
·每一位对应一个IO,但只使用低16位进行设置,高16位保留,如 IDR, ODR, BRR, LCKR
在这里插入图片描述
·每一位对应一个IO,低16位对应一种操作,高十六位对应另一种操作,如 BSRR

在这里插入图片描述

位带操作

 位带操作是对STM32寄存器的位进行直接操作,有如51单片机中的“sbit”关键字。且只有GPIO 和 SRAW 中的低 1Mb 空间可以被位带操作。
 其中寄存器的每一位数据在别名膨胀成4个字节,但只有最后一位有效。如此膨胀的原因是stm32为32位机,一次处理32位数据效率最高。
由以上的分析,位带别名区的转化公式也就很好理解了。

计算公式

如GPIO的计算公式:

AliasAddr = 0x42000000 + (A-0x40000000)*8*4+n*4

  首先要明白,这个公式是计算字节的也就是内存地址

  A为寄存器地址, 0x40000000 外设基地址是,A-0x40000000 就可以算出寄存器相对外设基地址的偏移了多少个字节(地址),又一个字节有8个位,每个位又膨胀为4个字节,所以先乘8再乘4。

  算完寄存器的偏移就要计算位的偏移了,前面算出的其实是每个寄存器0位的偏移量,而 n 表示位号,每位膨胀为4个字节,所以乘4。

  最后加上GPIO对应别名区的首地址 0x42000000 ,就可以算出寄存器某位对应别名区的地址了。
同理,SRAM的计算公式只是把对应别名区首地址修改

AliasAddr = 0x22000000 + (A-0x40000000)*8*4+n*4

统一公式

( (addr & 0xF0000000) +0x02000000+ ( (addr & 0x00FFFFFF)<<5 ) + bitnum<<2 )

addr 是要操作的为所在寄存器的地址
bitnum 位号,在寄存器的第几位。

公式解释:
addr & 0xF0000000 留下最高位的数字,再加上0x02000000 就可以得到二者分别公式中的第一项。

addr & 0x00FFFFFF 去除最高的两位,算出寄存器地址和基地址的偏移, <<5 左移5位即为乘32。

bitnum<<2 左移2位即为乘4。

开始编程

使用位带操作,完成按下按键LED点亮和熄灭的操作。电路图如下。

  首先,检测按键,要用到GPIO的输入功能,先配置端口输入模式,再读取寄存器 IDR 就可以知道IO端口电平变化。
  点亮led灯必须先配置IO输出模式,再对ODR或BRR,BSRR操作即可置位和清零。

第一,制作一个计算器,可以帮对上面位带操作的公式计算

#define SetBit(addr,n)     *(unsigned int*)\
		((addr & 0xF0000000) +0x02000000+ \
		( (addr & 0x00FFFFFF)<<5 ) + (n<<2))

  使用一个带参宏就可以完成这个工作。传入 addr 为寄存器的地址, n 为位在寄存器中的位置。

  为了检测代码可行性,先尝试对IDR寄存器的读取,以便判断按键是否按下,和对 ODR 的置位,以便点亮LED灯。而对GPIO输入输出的配置沿用之前固件库编程的代码。

一下为检测按键按下的代码

#define GPIOA_IDR           GPIOA_BASE + 0x08
#define SetBit(addr,n)     *(unsigned int*)((addr & 0xF0000000) +0x02000000+ ( (addr & 0x00FFFFFF)<<5 ) + (n<<2))
int main(void)
{
	while(1)
	{
		if(SetBit(GPIOA_IDR,0) == 1)
		{
			while(SetBit(GPIOA_IDR,0) == 1);
			LED_G_TOGGLE;
		}
	}
}

  GPIOA_BASE 是固件库中GPIOA 的基地址,0x08 是参考手册中的地址偏移,二者相加就是 GPIOAIDR 寄存器的基地址。

  if语句检测按下,while语句检测松手。要注意的是,要读取的位是 0 位,这点要注意。最后用一个宏定义 LED_G_TOGGLE 完成led灯的反转。

LED_G_TOGGLE的宏定义如下:

#define GPIOB_ODR           GPIOB_BASE + 0x0C
#define SetBit(addr,n)     *(unsigned int*)((addr & 0xF0000000) +0x02000000+ ( (addr & 0x00FFFFFF)<<5 ) + (n<<2))

#define LED_G_TOGGLE  {SetBit(GPIOB_ODR,0) ^= 1;}

使用异或运算完成对这一位的反转。

完整的代码如下:

#define GPIOA_IDR           GPIOA_BASE + 0x08
#define GPIOB_ODR           GPIOB_BASE + 0x0C
#define SetBit(addr,n)     *(unsigned int*)((addr & 0xF0000000) +0x02000000+ ( (addr & 0x00FFFFFF)<<5 ) + (n<<2))

#define LED_G_TOGGLE       {SetBit(GPIOB_ODR,0) ^= 1;}

int main(void)
{
	KEY_GPIO_Config();//配置输入端口为浮空,50M,Pin0
	LED_GPIO_Config();//配置输出端口为推挽,50M,Pin0
	
	while(1)
	{
		if(SetBit(GPIOA_IDR,0) == KEY_ON)
		{
			while(SetBit(GPIOA_IDR,0) ==KEY_ON);
			LED_G_TOGGLE;
		}
	}
}

其中使用固件库配置端口输入输出模式的
KEY_GPIO_Config()LED_GPIO_Config() 代码这里不再展示。

遗留问题

  上面的程序只是使用位带操完成了对 IDR 寄存器的读取和对 ODR 寄存器的写入。而对GPIO的初始化是使用固件库函数完成的。对应的位带操作要配置 CRL 寄存器,但在配置过程中出现了一系列问题。
  第一,删去两句固件库函数写成的初始化函数 KEY_GPIO_Config()LED_GPIO_Config() 后。位带操作不再起效果。通过调试可以看见,使用 SetBit(addr,n) 并未完成对应位的置位。
  第二,留下两句初始化函数。在初始化函数之前的位带操作语句没有效果,而初始化函数后面的位带操作有效果。
  一句话总结,不用固件库初始化,位带操作就没效果。
  百思不得其解。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值