【STM32】存储器和位带映射(bit band mapping)

文章目录

    • 0 前言
    • 1 关于地址和存储器
    • 2 STM32内部存储器
    • 3 位带映射(bit band mapping)
    • 4 扩展:IAP

0 前言

  最近在研究stm32标准库,对使用宏定义实现位操作的函数非常感兴趣,简单的一句PAout(1) = 0;就能实现某个引脚电平的输出,非常有51时代的风格,有一种简洁美,于是在仔细阅读参考手册和数据手册的同时结合网上众说纷纭的文章,希望产出一篇正确且全面的文章。

  终于知道为什么谈到单片机一般就是存储器和外设,因为这是对芯片应用者来说最基本也是最重要的东西了。希望这篇文章能够让读者对STM32的存储器有一个全面且略深入的认识。

1 关于地址和存储器

  在开始之前,不妨先回想一下微机原理所学的内容。计算机的中央处理器(CPU)包含三大总线:数据总线,地址总线,控制总线。在和外界交互时,一般是先设置地址总线,选定某一块存储区域,然后将数据放到数据总线上,在控制总线的控制下,读写这块被选定的存储区块,实现CPU和存储器之间的数据交互
  单片机其实同理,所谓单片机,即单片微型计算机,它本质上就是将cpu,存储器和一些外围设备集成到一个芯片上。单片机内核大抵相当于cpu,内部的sram和flash大抵相当于存储器,因此,这些总线(一般在微机中称为内部总线)对开发者来说是不可见的,也无需关心如何连接,这些已经在芯片内部实现了。
  既然集成了存储器和外设,那就需要去读写和控制,怎么操作呢,很简单,就是给他们分配地址,然后读写地址即可,一个地址实际上对应了一个字节,而STM32具有32位地址总线,因此其最大可读写的存储器大小是2^32 Byte = 2^10 * 2^10 * 2^10 * 2^2 = 4 GByte(一个2的10次方等于10进制下的3次方,KB,MB,GB),是不是很惊讶?一个小小的芯片竟然可以控制4GB的内存?!显然,实际上并没有这么多,很大一部分是保留或者给外部扩展用的。

关于外部扩展RAM或者FLASH,一般来说想实现和内部存储器一样访问,需要使用FSMC这个外设,但这个外设只在大容量设备中有,所以对于小容量和中容量的芯片来说,不能扩展外部ram,实现像内部ram一样访问。关于FSMC可以参考这篇文章。

  那外设怎么控制呢?看手册我们会发现,所有外设的控制,都是通过读写寄存器实现的,那寄存器是怎么读写的?实际上也是通过上面提到的地址去访问。在芯片设计时,这些外设都是设定好的,即哪个寄存器的某一位被设置为1,对应外设会有什么反应。然后这些寄存器都分配了一个地址(这也就是为什么不可能4GB全部作为内存来用),开发者只需要去访问这些地址,然后读写该位置的内存即可读写寄存器。但是很显然,这样做非常麻烦,没有人会愿意记一堆地址,因此,芯片厂商一般会提供一个寄存器名称和地址相对应的文件,一般用宏定义来实现。这就是最早的固件库,都是一堆宏定义。早期的单片机编程全部是使用寄存器编程,虽然麻烦,但其实效率更高。

  总结来说,在STM32内部所有的存储器都被分配了一个地址,开发者在使用时需要访问对应地址的内存,这就是芯片开发的本质。

2 STM32内部存储器

  对于玩底层开发的来说,了解芯片的存储结构非常重要,尤其代码里面时不时需要操作寄存器。
  以STM32F103C8T6型号为例,这里截取官方数据手册第34页的Memory mapping:

在这里插入图片描述

从上图可以看出,STM32的存储(4GB)被分成8块,每块512MB(0.5G),其中灰色的部分是保留的,也就是未使用。
  先来看第一部分,也就是标0的那一大块,右侧是具体的分布结构。

在这里插入图片描述
这一部分是存储代码的区域,其中Flash Memory是存放用户编写下载的代码,其起始地址为0x0800 0000,所以在Keil中设置仿真器,起点要设置成这个。
在这里插入图片描述

虽然这里写的size是0x0002 0000,也就是2^17 B = 2^7 kB = 128 kB,但实际STM32F103C8T6数据手册上写的是64kB的Flash,但网上也有人研究如何使用后64k的Flash,有兴趣的可以自行搜索。

System Memory也就是系统主闪存,用于存放芯片的BootLoader程序,下载程序的时候执行。
  但实际上,芯片上电之后,单片机一般是从0地址开始运行的,从图中可以看出,0地址处其实是一个“跳转部分”,根据 BOOT 引脚转向闪存或系统存储器执行程序,也就是经常被讨论的STM32启动配置的问题,上电复位后根据BOOT引脚的电平进入不同的启动模式。

  第2部分,也就是标1的那一大块。这里主要是SRAM所在区域,一般不会直接访问。
  重点是第3部分,这里主要是存储各种外设对应的寄存器。这部分,在参考手册中有更加详细的描述,建议结合标准库代码对照着看。

  在标准库文件stm32f10x.h的1267行,有关于外设存储映射(Peripheral_memory_map)的宏定义:

在这里插入图片描述
这里定义了一些存储器区段基地址的宏,方便开发者使用,比如FLASH_BASE就是上面谈到的FLASH起始地址,就是0x0800 0000。
  值得一提的是,这些宏定义其实就是来自参考手册,包括“总线基地址”。如下图所示,是参考手册中外设寄存器及其对应的地址范围:

在这里插入图片描述

在这里插入图片描述

这是标准库中的代码部分:

在这里插入图片描述

注意区分“外设基地址”,“总线基地址”以及“TIM1(某个外设)基地址”,所谓基地址,其实就是地址范围的起点(一般从低地址到高地址,所以起点是低地址),而参考手册中所谓的偏移地址,所基于的地址就是这个外设地址范围的起点:

在这里插入图片描述

以上图为例,GPIOA_CRH = GPIOA_BASE + OFFSET(0x04),这样就可以得到寄存器的地址。因此,如果需要操作寄存器,可以通过这个地址来访问,但是很显然,这样使用相对(偏移)地址相比于使用绝对地址简单一些,但仍然要记住一大堆地址对应的偏移,非常不方便。
  所以标准库中给出了一种更加简单直观的方式,就是使用结构体,因为这些寄存器地址都是连续的,那么就可以使用一个结构体来依次包含这些寄存器,如下图所示。
在这里插入图片描述
然后再将基地址强制转换成这个结构体的指针:

在这里插入图片描述
这样在使用时,就可以直接以GPIOA->CRL = 0x0100 0030这样的形式,来访问某个寄存器了。
  如果只想改变其中的一位呢?可以使用位运算符,比如|=, &=, ^=,具体用法建议参考这篇文章。

3 位带映射(bit band mapping)

  既然位运算也可以实现位操作,那为什么还需要有位带呢?GPT这样回答:
在这里插入图片描述
emmmm, 听着挺有道理。

  所谓位带,也叫位段(一个是Cortex-M手册中的表达,一个是STM32参考手册中文翻译版中的表达,实际是一个意思,后文统称位带),类比51单片机,那就是位寻址区段,即可以直接位访问的区域。
  先来看看官方参考手册对位带的解释:

在这里插入图片描述

重点是将“别名存储器”区中的每个字映射到位段存储器区的一个位,所谓别名存储器区,实际上就是这块区域的每一个单位的存储器,都具有别名,而不再是单纯的地址,这块存储器区的地址范围也是在上面讨论的4GB地址范围内的(不是另外有一块存储区)。
  这里有一个关键点,那就是“字”,这可不是一个字节。所谓字,实际上取决于cpu的数据总线宽度,也就是所谓的字长,STM32中的32就是指数据总线的宽度(而不是地址总线的宽度,只是一般地址总线会和数据总线位宽保持一致,这样内存访问更加高效)。因此STM32中一个“字”是指4个字节。

一开始我以为映射一个位,一个字节足够,但是STM32“财大气粗”,用4个字节来映射,但我觉得实际取的时候还是取低地址字节,高地址的三个字节更像是用来隔离的

在网上找到一张位带示意图,结构表示得非常清晰明确:

在这里插入图片描述

图片来源

从图中可以看出,在STM32内部存储中,有两个位带区,一个是片上SRAM最低地址1MB范围内,一个是片上外设最低地址1MB,根据图中所示,它所映射到的区域大小是32MB,在高地址处,中间有31MB的空当。

  前面展示的标准库中的各种BASE地址,其实指的都是其位带中的地址,如果我们想实现位操作,还需要得到其对应的别名存储器区对应的地址,根据参考手册,计算公式如下

在这里插入图片描述
一般我们使用的是外设段,SRAM段使用较少。以外设段为例,其别名区的起始地址(bit_band_base)是0x4200 0000,因为是一个位映射到四个字节,所以一个字节映射到32个字节,比例关系是1:32,所以字节偏移量(byte_offset)是乘以32,而位偏移(bit_number)是乘以4。

  基于以上的学习,再来看正点原子提供的sys.h文件中给出的宏定义,就基本可以理解了,这里基于该代码作了一些简单的修改,起名io.h,可以添加到项目中:

io.h

#ifndef __IO_H
#define __IO_H

#include "stm32f10x.h"

//根据位带存储器区中的地址和位号,得到别名存储器区对应的地址
#define BIT_BAND_ALIAS_ADDR(bit_band_addr, bitnum) (PERIPH_BB_BASE + (bit_band_addr-PERIPH_BASE)<<5 + bitnum<<2)
//使用位运算代替乘法,更高效

//获取地址对应的内存
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr))
//获取地址和位号对应的别名区映射的内存
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BIT_BAND_ALIAS_ADDR(addr, bitnum))

//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+0x0c) //0x4001080C
#define GPIOB_ODR_Addr    (GPIOB_BASE+0x0c) //0x40010C0C
#define GPIOC_ODR_Addr    (GPIOC_BASE+0x0c) //0x4001100C
#define GPIOD_ODR_Addr    (GPIOD_BASE+0x0c) //0x4001140C
#define GPIOE_ODR_Addr    (GPIOE_BASE+0x0c) //0x4001180C
#define GPIOF_ODR_Addr    (GPIOF_BASE+0x0c) //0x40011A0C
#define GPIOG_ODR_Addr    (GPIOG_BASE+0x0c) //0x40011E0C

#define GPIOA_IDR_Addr    (GPIOA_BASE+0x08) //0x40010808
#define GPIOB_IDR_Addr    (GPIOB_BASE+0x08) //0x40010C08
#define GPIOC_IDR_Addr    (GPIOC_BASE+0x08) //0x40011008
#define GPIOD_IDR_Addr    (GPIOD_BASE+0x08) //0x40011408
#define GPIOE_IDR_Addr    (GPIOE_BASE+0x08) //0x40011808
#define GPIOF_IDR_Addr    (GPIOF_BASE+0x08) //0x40011A08
#define GPIOG_IDR_Addr    (GPIOG_BASE+0x08) //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)  //输入

#endif

另外,从上图可以看出,高地址的存储区域,主要存放的就是内核相关的东西,比如NVIC,TPIU等。这也就能理解为什么stm32标准库中内核相关的代码,比如中断,要放在misc文件中,和其他外设文件形成鲜明对比,可能就是因为本身地址差别比较大。

4 扩展:IAP

  IAP的原理与上面两种有较大区别,这种方式将主存储区又分成了两个区域(根据实际需要由开发者自行分配),0800 0000起始处的这部分,存储一个开发者自己设计的Bootloader程序,另一部分存储真正需要运行的APP程序。

单片机的Bootloader程序,其主要作用就是给单片机升级。在单片机启动时,首先从Bootloader程序启动,一般情况不需要升级,就会立即从Bootloader程序跳转到存储区另一部分的APP程序开始运行。

假如Bootloader程序时,需要进行升级(比如APP程序运行时,接收到升级指令,可以在flash中的特定位置设置一个标志,然后触发重启,重启后进入Bootloader程序,Bootloader程序根据标志位就能判断是否需要升级),则会通过某种方式(比如通过WIFI接收升级包,或借助另一块单片机接收升级包,Bootloader再通过串口或SPI等方式从另一块单片机获取升级包数据)先将接收到的程序写入存储区中存储APP程序的那个位置,写入完成后再跳转到该位置,即实现了程序的升级

在这里插入图片描述

参考链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/521208.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

lua学习笔记7(函数的学习)

print("*****************************函数的学习*******************************") print("*****************************无参数无返回值函数的学习*******************************") function f1()print("f1函数") end f1() f2function()--…

隐私计算实训营学习九:隐语多方安全计算在安全核对的行业实践

文章目录 一、业务背景&#xff1a;安全核对产生的土壤二、产品方案&#xff1a;从试点到规模化的路三、技术共建&#xff1a;与隐语的共同成长 一、业务背景&#xff1a;安全核对产生的土壤 业务背景&#xff1a;很多粗放使用数据的方式被新出台的法律法规所规范&#xff0c;…

【RISC-V 指令集】RISC-V 向量V扩展指令集介绍(七)- 向量算术指令格式

1. 引言 以下是《riscv-v-spec-1.0.pdf》文档的关键内容&#xff1a; 这是一份关于向量扩展的详细技术文档&#xff0c;内容覆盖了向量指令集的多个关键方面&#xff0c;如向量寄存器状态映射、向量指令格式、向量加载和存储操作、向量内存对齐约束、向量内存一致性模型、向量…

【unity】【C#】延时调用(协程)和场景管理

文章目录 什么是协程协程的应用 - IEnumerator如何控制协程的暂停协程的另一种写法 - Invoke场景管理 多看代码块中的注释 什么是协程 A coroutine alows vou to spreacwhere it left off on the following anc return control toolinencoeframe. 协程允许您将任务分布在多个帧…

拦截器抛出异常无法被全局异常处理器捕获问题

文章目录 基本说明问题描述问题原因解决方法前端执行的所有请求都通过Controller&#xff0c;而不是直接访问html定义一个/error路径的方法 总结 基本说明 我的前后端项目是放在一起的&#xff0c;前后端都是由springMVC进行控制&#xff0c;但是现在我在拦截器的preHandle方法…

HAL STM32主从定时器联级使用

HAL STM32主从定时器联级使用 具体介绍参考STM32参考手册 &#x1f33f;主从定时器联级&#xff1a;使用一个定时器作为另一个定时器的预分频器。 &#x1f341;时钟关系&#xff1a; &#x1f33f;TIM1 和TIM8 控制寄存器 2(TIMx_CR2)相关位&#xff1a; &#x1f516;主…

Redis性能管理及主从复制、哨兵的配置与部署

一、redis性能管理 1.1 查看Redis内存使用 1.2 内存碎片率 1.3 内存使用率 1.3.1 避免内存交换发生的方法 1.4 内回收key 1.4.1 配置文件中修改 maxmemory-policy 属性值 1.5 缓存穿透 1.5.1 原因 1.5.2 条件 1.5.3 解决方案 1.6 缓存击穿 1.6.1 原因 1.6.2 现象…

移位运算与乘法

描述 题目描述&#xff1a; 已知d为一个8位数&#xff0c;请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输入的d有效&#xff08;d给出的信号的上升沿表示写入有效&#xff09; 信号示意图&#xff1a; 波形示意图&#xff1a; 输入描述&#…

Leetcode 215. 数组中的第K个最大元素

心路历程&#xff1a; 这道题本质上是排序不完全的过程&#xff0c;而且这道题有bug&#xff0c;直接用python的排序算法其实就能AC。 可以按照快排排到找到k-1个large元素的思维去做&#xff0c;不过这道题需要考虑空间复杂度&#xff0c;所以需要用指针快排。 其实也可以考虑…

SUPS:一种用于自动驾驶的仿真地下泊车场景数据集

SUPS&#xff1a;一种用于自动驾驶的仿真地下泊车场景数据集 附赠自动驾驶学习资料和量产经验&#xff1a;链接 摘要 本文介绍了SUPS&#xff1a;一种用于自动驾驶的仿真地下泊车场景数据集。随着自动驾驶的范围扩大&#xff0c;自动地下泊车引起了人们极大的关注。自动驾驶汽…

【stm32】软件I2C读写MPU6050

软件I2C读写MPU6050(文章最后附上源码) 编码 概况 首先建立通信层的.c和.h模块 在通信层里写好I2C底层的GPIO初始化 以及6个时序基本单元 起始、终值、发送一个字节、接收一个字节、发送应答、接收应答 写好I2C通信层之后&#xff0c;再建立MPU6050的.c和.h模块 基于I2C通…

京东云幻兽帕鲁4核16G服务器优惠价格26元1个月、398元一年

京东云幻兽帕鲁4核16G服务器优惠价格26元1个月、658元1年、三年3098元&#xff0c;配置为&#xff1a;轻量云主机4C16G-100G SSD系统盘-5M带宽-1000G月流量 华北-北京&#xff0c;京东云优惠活动 yunfuwuqiba.com/go/jd 活动链接打开如下图&#xff1a; 幻兽帕鲁4核16G服务器优…

计算机网络-TCP重传、滑动窗口、流量控制、拥塞控制

重传机制 超时重传&#xff1a;超时重传时间&#xff08;RTO&#xff09;设定为略大于RTT&#xff08;动态&#xff09;。触发场景包括自己发送的数据包丢失和别人给自己的回应数据包丢失。启动重传机制后如果还没有收到数据包&#xff0c;则RTO设置为上次的两倍&#xff0c;直…

双连通分量算法

1. 连通图概念 连通图&#xff1a;无向图任意两点之间存在通路。 强连通&#xff1a;有向图&#xff08;前提&#xff09;中&#xff0c;任意两点都有至少一条通路&#xff0c;则此图为强连通图。 弱连通图&#xff1a;将有向图的有向边换成无向边得到的图是连通图&#xff0c…

Tomcat管理配置

Tomcat管理配置 1 host-manager项目2 manager项目 Tomcat 提供了Web版的管理控制台&#xff0c;位于webapps目录下。Tomcat 提供了用于管理Host的host-manager和用于管理Web应用的manager。 1 host-manager项目 Tomcat启动之后&#xff0c;可以通过 http://localhost:8080/ho…

Cortex-M7 外设(peripherals)总览

1 PPB内存映射总览 由Cortex-M7的内存映射模型可知&#xff0c;0xE000_0000~0xE00F_FFFF地址空间为私有外设总线 (Private peripheral bus&#xff0c;PPB)的内存区域&#xff0c;其具体的地址映射如表1所示。 表1 PPB寄存器内存映射 其中&#xff0c;注释后缀的相关含义如…

5.5.1MFC对话框——文件对话框

本文仅供学习交流&#xff0c;严禁用于商业用途&#xff0c;如本文涉及侵权请及时联系将于24小时内删除 目录 1.实验原理 2.示例说明 1.实验原理 CFileDialog类 用CFileDialog类提供的通用文件对话框&#xff0c;实现Windows标准的【打开】和【另存为】功能。 CFileD…

前端canvas项目实战——在线图文编辑器(八):复制、删除、锁定、层叠顺序

目录 前言一、效果展示二、实现步骤1. 复制2. 删除3. 锁定4. 层叠顺序 三、实现过程中发现的bug1. clone方法不复制自定义属性2. 复制「锁定」状态的对象&#xff0c;得到的新对象也是「锁定」状态 四、Show u the code后记 前言 上一篇博文中&#xff0c;我们细致的讲解了实现…

如何在没有备份的情况下从 iPad 恢复照片?

有很多操作都可能导致iPad照片丢失&#xff0c;包括误删除、出厂设置、iPad的iOS更新等。如果没有备份&#xff0c;似乎没有办法找回它们。然而&#xff0c;即使您将备份保留在 iCloud 或iTunes上&#xff0c;这些方式也需要您的 iPad 首先重置&#xff0c;从而用备份内容覆盖当…

Java-类型转换

Java数据类型转换的规则掌握后&#xff0c;将使我们对以后的学习事半功倍&#xff0c;下面是我列出的一些重点。 类型转换 由于Java是强类型语言&#xff0c;所以要进行有些运算的时候&#xff0c;需要用到类型转换。底到高依次是&#xff1a;byte,short,char->int->lo…