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

2024-04-06 23:52

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

在这里插入图片描述

参考链接

这篇关于【STM32】存储器和位带映射(bit band mapping)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/881164

相关文章

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

STM32(十一):ADC数模转换器实验

AD单通道: 1.RCC开启GPIO和ADC时钟。配置ADCCLK分频器。 2.配置GPIO,把GPIO配置成模拟输入的模式。 3.配置多路开关,把左面通道接入到右面规则组列表里。 4.配置ADC转换器, 包括AD转换器和AD数据寄存器。单次转换,连续转换;扫描、非扫描;有几个通道,触发源是什么,数据对齐是左对齐还是右对齐。 5.ADC_CMD 开启ADC。 void RCC_AD

STM32内部闪存FLASH(内部ROM)、IAP

1 FLASH简介  1 利用程序存储器的剩余空间来保存掉电不丢失的用户数据 2 通过在程序中编程(IAP)实现程序的自我更新 (OTA) 3在线编程(ICP把整个程序都更新掉) 1 系统的Bootloader写死了,只能用串口下载到指定的位置,启动方式也不方便需要配置BOOT引脚触发启动  4 IAP(自己写的Bootloader,实现程序升级) 1 比如蓝牙转串口,

FreeRTOS-基本介绍和移植STM32

FreeRTOS-基本介绍和STM32移植 一、裸机开发和操作系统开发介绍二、任务调度和任务状态介绍2.1 任务调度2.1.1 抢占式调度2.1.2 时间片调度 2.2 任务状态 三、FreeRTOS源码和移植STM323.1 FreeRTOS源码3.2 FreeRTOS移植STM323.2.1 代码移植3.2.2 时钟中断配置 一、裸机开发和操作系统开发介绍 裸机:前后台系

寻迹模块TCRT5000的应用原理和功能实现(基于STM32)

目录 概述 1 认识TCRT5000 1.1 模块介绍 1.2 电气特性 2 系统应用 2.1 系统架构 2.2 STM32Cube创建工程 3 功能实现 3.1 代码实现 3.2 源代码文件 4 功能测试 4.1 检测黑线状态 4.2 未检测黑线状态 概述 本文主要介绍TCRT5000模块的使用原理,包括该模块的硬件实现方式,电路实现原理,还使用STM32类

STM32 ADC+DMA导致写FLASH失败

最近用STM32G070系列的ADC+DMA采样时,遇到了一些小坑记录一下; 一、ADC+DMA采样时进入死循环; 解决方法:ADC-dma死循环问题_stm32 adc dma死机-CSDN博客 将ADC的DMA中断调整为最高,且增大ADCHAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_Buffer_Size); 的ADC_Bu

[论文笔记]LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale

引言 今天带来第一篇量化论文LLM.int8(): 8-bit Matrix Multiplication for Transformers at Scale笔记。 为了简单,下文中以翻译的口吻记录,比如替换"作者"为"我们"。 大语言模型已被广泛采用,但推理时需要大量的GPU内存。我们开发了一种Int8矩阵乘法的过程,用于Transformer中的前馈和注意力投影层,这可以将推理所需

基于stm32的河流检测系统-单片机毕业设计

文章目录 前言资料获取设计介绍功能介绍具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W+,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设计 主要对象是咱们电子相关专业的大学生,希望您们都共创辉煌!✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 单片机设计精品

STM32的使用方法一

注:我采用的是STM32F103RC芯片、相应的电路图和STM32CubeIDE软件这是在STM32CubeIDE软件定义芯片后,所给的必要的代码逻辑,加上了注释 #include "main.h"/* Private variables ---------------------------------------------------------*//* Private function

STM32 HAL CAN通讯 实操

1、简介 相比于串口通讯,对于刚接触CAN通讯的小白来说,CAN通讯相对复杂,看各种视频、帖子理论,总是一知半解。本次通过傻瓜式操作,先实现CAN通讯的交互,以提高小白的信心,也便于自己复习观看。本次以STM32CubeMX进行初始化配置,通过Keil 5软件进行软件设计,通过CAN盒实现进行数据的交互。该流程实际以STM32F0、F1、F3、F4、F7实测好用(理论上都适用),这三种型号单片机