四、位带操作:内存映射的神奇魔法

一、位带操作概念介绍

  • 位带操作是一种可以单独对一个字节(8位)中的某一位进行读写操作的机制。在GD32F450ZG单片机中,位带操作提供了一种高效的方式来访问和控制寄存器或内存中的单个位,而不需要读取整个字节,修改位,然后再写回整个字节。这对于控制单个引脚(如GPIO引脚)的状态(置位或清零)非常有用。

二、位带操作内存地址

  • GD32F450ZG单片机有两个位带区:位带别名区和位带区。
  • 位带区:是实际存储数据的区域,例如,对于SRAM区的位带区起始地址为0x2000_0000,对于片上外设区的位带区起始地址为0x4000_0000。这些位带区中的每个字节都可以通过位带别名区进行单独的位操作。
  • 位带别名区:它是用于实现位带操作的一个“映射”区域。位带别名区的地址计算有一定的公式。对于一个位带区中的位,其在位带别名区中的地址可以通过以下公式计算:
    • 别名区地址 = 位带别名区基地址+(字节偏移量×32)+(位偏移量×4)
    • 例如,对于片上外设区,位带别名区基地址是0x4200_0000。如果要操作外设寄存器中的某一位,首先需要找到该寄存器所在的字节偏移量和要操作的位在字节中的偏移量,然后按照上述公式计算出在位带别名区的地址,通过这个地址就可以直接操作对应的位。
      在这里插入图片描述

三、位带操作原理

1、位带区和位带别名区的映射关系

  • 在支持位带操作的单片机(如GD32F450ZG)中,存在位带区和位带别名区这两个概念。位带区是实际存储数据的物理区域,包括SRAM位带区和片上外设位带区。以片上外设为例,位带区存储着外设寄存器等信息。
  • 位带别名区是一个“虚拟”的映射区域,它的存在是为了方便对位带区中的单个位进行操作。位带别名区中的每个字(32位)都对应到位带区中的一个位。这种映射是通过特定的地址转换规则来实现的。
  • 具体来说,对于片上外设位带区中的每一位,在位带别名区中有一个唯一的32位字与之对应。通过访问位带别名区中的这个32位字,就相当于访问了位带区中的相应位。

2、地址计算原理

  • 位带别名区地址的计算是位带操作的核心原理之一。计算位带别名区地址的公式为:别名区地址 = 位带别名区基地址+(字节偏移量×32)+(位偏移量×4)。
  • 字节偏移量计算:首先要确定位带区中目标字节的偏移量。以访问一个外设寄存器中的某一位为例,需要先找到该寄存器相对于位带区基地址(如片上外设位带区基地址为0x40000000)的偏移量。将这个偏移量除以4(因为每个字是4字节),就得到了字节偏移量。
  • 位偏移量计算:位偏移量就是目标位在字节中的位置,范围是0 - 7(对于一个字节中的8位)。
  • 例如,对于一个位于地址为0x40020C14的外设寄存器中的某一位,先计算字节偏移量:(0x40020C14 - 0x40000000)/4 = 0x00000C14/4,然后根据目标位在字节中的位置确定位偏移量,最后按照公式计算出在位带别名区的地址。
    在这里插入图片描述

3、读写操作原理

  • 写操作原理:当向位带别名区中的一个32位字写入数据时,实际上只有最低位(LSB)会被用来更新位带区中对应的位。例如,如果向位带别名区中的一个字写入0x00000001,那么位带区中对应的位就会被置为1;如果写入0x00000000,对应的位就会被清零。这是因为位带别名区和位带区之间的映射是按照这种特殊的位更新规则设计的。
  • 读操作原理:当读取位带别名区中的一个32位字时,返回的值是位带区中对应位的值扩展到32位后的结果。也就是说,读取得到的32位字的最低位(LSB)就是位带区中目标位的值,其余高位的值通常是未定义的或者是由硬件实现定义的。这种设计使得可以通过简单地读取位带别名区中的字来获取位带区中单个位的状态。

通过这种位带操作的原理,可以高效、精确地对单片机内存或寄存器中的单个位进行读写操作,避免了传统方法中读取整个字节、修改位、再写回整个字节可能带来的不便和潜在的错误。

四、位带操作优势

  • 精准控制:可以对单个位进行精确的读写操作,这对于控制像GPIO引脚这样的设备非常方便。例如,只需要改变一个引脚对应的位状态,而不需要对整个GPIO端口寄存器进行读 - 改 - 写操作,从而避免了对其他引脚状态的意外干扰。
  • 高效性:在某些情况下,位带操作可以减少代码量和执行时间。因为不需要进行额外的位屏蔽和组合操作来修改单个位,直接访问位带别名区就可以实现位的设置和清除。
  1. 配置位带操作实现PD7口LED灯亮灭操作
    • 步骤一:初始化GPIO端口
      • 首先需要对包含PD7引脚的GPIOD端口进行初始化,使能GPIOD端口的时钟,设置引脚模式为输出模式等。
      #include "gd32f4xx.h"
      #include "gd32f4xx_gpio.h"
      #include "gd32f4xx_rcu.h"
      void GPIO_Init(void)
      {
          rcu_periph_clock_enable(RCU_GPIOD);
          gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_7);
          gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
      }
      
    • 步骤二:位带操作实现LED亮灭
      • 根据位带别名区地址计算公式,计算出PD7引脚对应的位带别名区地址。假设GPIOD_ODR(输出数据寄存器)的地址为0x40020C14PD7是该寄存器的第7位。
      • 首先计算字节偏移量:(0x40020C14 - 0x40000000)/4 = 0x00000C14/4(这里0x40000000是片上外设位带区基地址),得到字节偏移量。
      • 位偏移量为7。
      • 根据公式计算位带别名区地址:0x42000000+(字节偏移量×32)+(7×4)(这里0x42000000是片上外设位带别名区基地址)。
      • 以下是通过位带操作实现LED亮灭的函数:
      #define BITBAND(addr, bitnum) ((volatile unsigned long)((addr & 0xF0000000)+0x02000000+((addr & 0xFFFFF)<<5)+(bitnum << 2)))
      #define GPIO_D_ODR_Addr    (GPIOD_BASE+0x14)
      #define PD7_BITBAND_ADDR   BITBAND(GPIO_D_ODR_Addr, 7)
      void LED_Toggle(void)
      {
          // 读取当前位状态并取反,然后写回
          * (volatile unsigned long *) PD7_BITBAND_ADDR =!(* (volatile unsigned long *) PD7_BITBAND_ADDR);
      }
      
    • 步骤三:主函数调用
      • 在主函数中,先初始化GPIO端口,然后可以通过调用LED_Toggle函数来实现LED的亮灭操作。
      int main(void)
      {
          GPIO_Init();
          while (1)
          {
              LED_Toggle();
              // 可以添加适当的延时,使闪烁效果更明显
              for (int i = 0; i < 1000000; i++);
          }
      }
      

在实际应用中,要根据芯片的数据手册和具体的硬件连接来准确地进行位带操作,并且注意位带别名区地址计算的准确性以及操作的合法性,避免对其他寄存器位或内存单元造成错误的修改。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值