STM32 的位带操作理解

2023-11-26 08:40
文章标签 操作 理解 stm32 位带

本文主要是介绍STM32 的位带操作理解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

位带操作

  • 为什么要用位带操作?

32位处理器一次能处理4个字节即32位,不能直接对位操作,而我们需要更方便更快捷更安全的设置或读取地址中的内容。

  • 什么是位带操作?

地址空间的有一个 512MB 范围由片上外设(的寄存器)使用。这个区通过系统总线来访问。在这个区的下部,有一个 1MB 的区间,被称为“位带区”。该位带区还有一个对应的、 32MB 的“位带别名(alias)区”,容纳了 8M 个“位变量”(对比 8051 的只有 128 个位变量)。位带区对应的是最低的 1MB 地址范围。这个区中也有一条 32MB 的位带别名,以便于快捷地访问外设寄存器。例如,可以方便地访问各种控制位和状态位。要注意的是,外设区内不允许执行指令。SRAM区同样有位带区。

  • 位带区: 支持位带操作的地址区
  • 位带别名: 对别名地址的访问最终会变换成对位带区的访问(注意:这中途有一个地址映射过程)

下图为位带区和位带别名区的对应关系:
在这里插入图片描述
可以发现,位带区首地址:0x200F FFFF - 0x2000 0000 = 0xF FFFF (1Mb个地址) ,而位带别名区: 0x23FF FFFC - 0x2200 0000 = 0x1FF FFFF (32Mb个地址),的确是 1:32。又有 0x2200 0000 - 0x2000 0000 = 0x200 0000 (32Mb),0x23FF FFF0 - 0x200F FFFF = 0x3EFFFF1 (62Mb),即位带别名区和位带区并非相邻的,而是相隔 32Mb 的内存空间(用作其他用途)。且位带区相互之间对应的位带别名区也不是连续的。

QUESTION:为什么位带别名区与位带区的地址相差32Mb?

  • 如何位带操作?

在位带区中,每个比特都映射到别名地址区的一个字——这是个只有 LSB 才有效的字。当一个别名地址被访问时,会先把该地址变换成位带地址。对于操作,读取位带地址中的一个字,再把需要的位右移到 LSB,并把 LSB 返回。对于操作,把需要写的位左移至对应的位序号处,然后执行一个原子的 “读-改-写” 过程。

  • 支持位带操作的两个内存区的范围是:

    0x2000_0000-0x200F_FFFF(SRAM 区中的最低 1MB)

    0x4000_0000-0x400F_FFFF(片上外设区中的最低 1MB)

对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n(0<=n<=7),则该比特在别名区的地址为:

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

上式中,“*4” 表示一个字为 4 个字节,“*8” 表示一个字节中有 8 个比特。

在STM32中,上式可理解为 
addr(GPIOx.y) = (addr(GPIO_BASE) + 0x200 0000) + (GPIOx-GPIO_BASE)*32 + y*4位带别名区的起始地址           外设的偏移地址 1:32  别名区以4字节为步进单位  (                   外设的别名区起始地址              )       

上式中,x = (A, B, C, …),y 为 bit 位。GPIO_BASE 为 IO 口的起始地址。

举例:欲设置地址 0x2000_0000 中的比特 2,则使用位带操作的设置过程如下图所示:
在这里插入图片描述

对应的汇编代码如下图:
在这里插入图片描述
位带读操作相对简单些:
在这里插入图片描述
对应的汇编代码如下图:
在这里插入图片描述
例子:

  1. 在地址 0x40000000 处写入 0x3355AACC。
  2. 读取地址 0x42000008。本次读访问将读取 0x20000000,并提取比特 2,值为 1。
  3. 往地址 0x42000008 处写 0。本次操作将被映射成对地址 0x40000000 的 “读-改-写” 操作
    (原子的),把比特 2 清 0。
  4. 现在再读取 0x40000000,将返回 0x3355AAC8(bit[2]已清零)。
    位带别名区的字只有 LSB 有意义。另外,在访问位带别名区时,不管使用哪一种长度的数据传
    送指令(字/半字/字节),都把地址对齐到字的边界上,否则会产生不可预料的结果。
  • 在 C 编译器中使用位带操作

C 编译器中并没有直接支持位带操作。比如, C 编译器并不知道同一块内存能够使用不同的地址来访问,也不知道对位带别名区的访问只对 LSB 有效。欲在 C 中使用位带操作,最简单的做法就是 #define 一个位带别名区的地址。例如:

#define DEVICE_REG0 ((volatile unsigned long *) (0x40000000))
#define DEVICE_REG0_BIT0 ((volatile unsigned long *) (0x42000000))
#define DEVICE_REG0_BIT1 ((volatile unsigned long *) (0x42000004))
...
*DEVICE_REG0 = 0xAB; //使用正常地址访问寄存器
...
*DEVICE_REG0 = *DEVICE_REG0 | 0x2; //使用传统方法设置 bit1
...
*DEVICE_REG0_BIT1 = 0x1; // 通过位带别名地址设置 bit1

为简化位带操作,也可以定义一些。比如,我们可以建立一个把 “位带地址+位序号” 转换成别名地址的宏,再建立一个把别名地址转换成指针类型的宏:

// 把 “位带地址+位序号” 转换成 别名地址 的宏    
//                                 外设的基地址                  外设地址区域<=1Mb
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr & 0xFFFFF)<<5) +(bitnum<<2))
// 把该地址转换成一个指针
#define MEM_ADDR(addr) *((volatile unsigned long *) (adr))
// 在此基础上,我们就可以如下改写代码:
MEM_ADDR(DEVICE_REG0) = 0xAB; //使用正常地址访问寄存器
MEM_ADDR(DEVICE_REG0)= MEM_ADDR(DEVICE_REG0) | 0x2; //传统做法
MEM_ADDR(BITBAND(DEVICE_REG0,1)) = 0x1; //使用位带别名地址

注意:当使用位带功能时,要访问的变量必须用 volatile 来定义。因为 C 编译器并不知道同一个比特可以有两个地址。所以就要通过 volatile,使得编译器每次都如实地把新数值写入存储器(内存),而不再会出于优化的考虑,在中途使用寄存器来操作数据的复本,直到最后才把复本写回——这会导致按不同的方式访问同一个位会得到不一致的结果。

  • STM32内的位带操作整理

CM3的预定义的存储器映射如下:
在这里插入图片描述

  • 其中 512M 的片上外设区和片上SRAM区都具有1M的位带区以及对应的32M未带别名区。

对应于 STM32F103RCT6 内存映射如下:
在这里插入图片描述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NDEvO5Y7-1575361121600)(assets/image-20191203144040531.png)]

  • 可知外设区位带区地址 0x4000 0000 - 0x400F FFFF 基本涵盖了所有的外设寄存器,从而使得读改外设的效率提高。

以 GPIO 的位带操作为例:

// IO口操作宏定义
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr & 0xFFFFF)<<5)+(bitnum<<2)) 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) // stm32f10x.h
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)

GPIOx_ODR 寄存器如下:
在这里插入图片描述
每个寄存器32位,占4个地址(4 x 8),在访问或修改某个寄存器时,是从首地址开始的,逻辑运算则是直接可涵盖到32bit,offset 为 0x0C。GPIOA 的地址在 stm32 的 datasheet 中给出为 0x40010800
在这里插入图片描述

// 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    // 类似的,GPIOx_IDR的offset为 0x08.
#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 // IO口操作,只对单一的IO口
// 确保n的值小于16:
// stm32的GPIOx只有16个bit,一个地址8bit,该16bit的地址是连续的,从而位带操作不受影响
#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)  

这篇关于STM32 的位带操作理解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

【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

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

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 时钟中断配置 一、裸机开发和操作系统开发介绍 裸机:前后台系