【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程序的那个位置,写入完成后再跳转到该位置,即实现了程序的升级

在这里插入图片描述

参考链接

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于单片机光伏太阳能跟踪系统设计

**单片机设计介绍&#xff0c;基于单片机光伏太阳能跟踪系统设计 文章目录 一 概要二、功能设计三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机光伏太阳能跟踪系统的设计&#xff0c;旨在通过单片机技术实现对光伏太阳能设备的自动跟踪&#xff0c;以提高太阳…

InnoDB 数据页结构

1.行格式 1.1 Compact行格式 1.1.1 示意图 1.1.2 准备一下 1&#xff09;建表 mysql> CREATE TABLE record_format_demo (-> c1 VARCHAR(10),-> c2 VARCHAR(10) NOT NULL,-> c3 CHAR(10),-> c4 VARCHAR(10)-> ) CHARSETascii ROW_FORMATCOM…

ARM、X86、RISC-V三分天下

引入&#xff1a; 简单的介绍一下X86、ARM、RISC-V三种cpu架构的区别和应用场景。 目录 简单概念讲解 1. X86架构 2. ARM架构 3. RISC-V架构 应用场景 X86、ARM和RISC-V是三种不同的CPU架构&#xff0c;它们在设计理念、指令集和应用场景上有一些区别。 简单概念讲解 1. X…

力扣Lc29---- 541. 反转字符串 II(java版)-2024年4月06日

1.题目描述 2.知识点 &#xff08;1&#xff09;执行步骤如下&#xff1a; 初始化 s “abcdefg” 和 k 2 将字符串分割成长度为 2k 4 的块。 对每个块中的前 k 2 个字符进行反转。 执行过程 1&#xff09;第一次循环&#xff08;i 0&#xff09; start 0 end Math.min(0…

MPT - 初识账户状态树(World State)

往期回顾 ETH网络中的账户ETH网络中的区块链 通过以上文章&#xff0c;我们了解到ETH网络中的World State是节点根据交易维护的&#xff0c;节点在维护Wrold State时为了方便操作会将用户状态构建成一颗树&#xff0c;称为账户状态树&#xff0c;采用一种叫做MPT的数据结构 MP…

如何申请到免费SSL证书

免费SSL证书主要通过权威CA机构如Lets Encrypt、JoySSL等签发&#xff0c;具备基础的服务器身份验证功能&#xff0c;能有效防止中间人攻击&#xff0c;确保信息从用户的浏览器到服务器间的全程加密传输&#xff0c;保护用户隐私数据不被窃取或篡改。 尽管免费SSL证书可能存在有…

腾讯云添加域名后不生效

问题原因 添加域名后不生效可能是因为没有加CDN域名解析 解决步骤

ISWH卧式不锈钢管道离心泵

ISWH卧式不锈钢管道离心泵是一种设计精良的泵类产品&#xff0c;它采用先进的水力模型&#xff0c;结合高质量的不锈钢材料&#xff0c;确保了泵在输送流体时的高效率和长久耐用性。这种泵通常用于输送清水或其他类似于水的液体&#xff0c;特别是在需要避免腐蚀或污染的场合&a…

跨境金融区块链服务平台

跨境金融服务是因企业及个人跨境经营、交易、投资、往来等活动而产生的资金使用、调拨、配置等需求&#xff0c;而提供的金融服务。近年来&#xff0c;随着我国经济的快速稳步增长和全球化经济一体化的不断深入发展&#xff0c;跨境金融业务增长迅速&#xff0c;监管也开始转化…

2024年【安全员-C证】最新解析及安全员-C证实操考试视频

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-C证最新解析是安全生产模拟考试一点通生成的&#xff0c;安全员-C证证模拟考试题库是根据安全员-C证最新版教材汇编出安全员-C证仿真模拟考试。2024年【安全员-C证】最新解析及安全员-C证实操考试视频 1、【多…

各主流电商数据采集机器人|电商数据采集API接口(淘宝/京东/1688)抓取效率提升100%

业务痛点 某电商代运营公司帮助新手电商在京东平台进行开店、创业&#xff0c;需要获取平台上大量的商品信息&#xff0c;以作为帮助客户分析选品趋势&#xff0c;爆款打造的依据。 商家或客服人员通常需要整理、分析的商品数据量巨大。客服每天需要频繁点击、下载大量商品信…

tomcat 结构目录

bin 启动&#xff0c;关闭和其他脚本。这些 .sh文件&#xff08;对于Unix系统&#xff09;是这些.bat文件的功能副本&#xff08;对于Windows系统&#xff09;。由于Win32命令行缺少某些功能&#xff0c;因此此处包含一些其他文件。比如说&#xff1a;windows下启动tomcat用的是…

文档管理系统解决方案(word原件)

1.系统概述 1.1.需求描述 1.2.需求分析 1.3.重难点分析 1.4.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 数据备份技术 3.系统功能设计 3.1.功能清单列表 3.2.基础数据管理 3.3.位置管理 3.4.文档使用 3.5.文档管理 软件全套资料包获取方式①&#xff1a;软件项…

[C#]winform使用OpenCvSharp实现透视变换功能支持自定义选位置和删除位置

【透视变换基本原理】 OpenCvSharp 是一个.NET环境下对OpenCV原生库的封装&#xff0c;它提供了大量的计算机视觉和图像处理的功能。要使用OpenCvSharp实现透视变换&#xff08;Perspective Transformation&#xff09;&#xff0c;你首先需要理解透视变换的原理和它在图像处理…

院内感染的相关因素分析(Boruta联合SHAP分析2)R

院内感染的相关因素分析&#xff08;Boruta联合SHAP分析2&#xff09;R 和鲸社区一键运行代码 院内感染是指住院患者在医疗机构内发生的感染&#xff0c;是医院管理中常见且严重的问题。院内感染不仅会延长患者住院时间&#xff0c;增加医疗费用&#xff0c;还会严重威胁患者生…

大语言模型落地的关键技术:RAG

1、什么是RAG&#xff1f; RAG 是检索增强生成&#xff08;Retrieval-Augmented Generation&#xff09;的简称&#xff0c;是当前最火热的大语言模型应用落地的关键技术&#xff0c;主要用于提高语言模型的效果和准确性。它结合了两种主要的NLP方法&#xff1a;检索&#xff…

如何使用CSS构建一个瀑布流布局

如何使用CSS构建一个瀑布流布局 瀑布流布局是一种常见的网页布局方式&#xff0c;其中元素以不同的大小排列&#xff0c;且行与列之间没有不均匀的间隙。在瀑布流布局中&#xff0c;即使某一行或列中的元素较短&#xff0c;下一个元素也会占据空间。 如何实现瀑布流布局 实现…

【Frida】【Android】 工具篇:查壳工具大赏

&#x1f6eb; 系列文章导航 【Frida】【Android】01_手把手教你环境搭建 https://blog.csdn.net/kinghzking/article/details/136986950【Frida】【Android】02_JAVA层HOOK https://blog.csdn.net/kinghzking/article/details/137008446【Frida】【Android】03_RPC https://bl…

深入浅出 -- 系统架构之负载均衡Nginx反向代理

一、Nginx反向代理-负载均衡 首先通过SpringBootFreemarker快速搭建一个WEB项目&#xff1a;springboot-web-nginx&#xff0c;然后在该项目中&#xff0c;创建一个IndexNginxController.java文件&#xff0c;逻辑如下&#xff1a; Controller public class IndexNginxControl…

【React】React知识要点记录

描述UI 万物皆组件 为什么多个 JSX 标签需要被一个父元素包裹&#xff1f; 切勿将数字放在 && 左侧 React 中为什么需要 key&#xff1f; React 为何侧重于纯函数? 渲染树 模块依赖树 添加交互 React如何传递事件处理函数&#xff1f; React 如何知道返回哪个 sta…