打印
[开发工具]

深入浅出:APM32单片机中的位带操作艺术

[复制链接]
87|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
DKENNY|  楼主 | 2025-2-16 11:30 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 DKENNY 于 2025-2-16 11:28 编辑

#技术资源# #申请原创# @21小跑堂

前言

      在APM32单片机体系中,其SRAM存在两个区域,这两个区域支持位带(bit-band)操作。那么,位带究竟是什么概念?位带操作背后的原理又是如何呢?这正是本文要探讨的内容。

1. ARM Cortex-M3的存储器映射
      在深入探讨 ARM Crotext - M3 的位带操作之前,我们有必要先了解一下它的存储器映射。CM3 的地址空间总计为 4GB,在这个广阔的空间里,程序可以在代码区、内部 SRAM 区以及外部 RAM 区中执行。就拿 APM32 单片机来说,它的程序存储器、数据存储器、寄存器以及输入输出端口,都被整合在同一个 4GB 的线性地址空间内。并且,数据字节是以小端格式存放在存储器中的。


2. 位带区与位带别名区的概念
      这里要提到 CM3 用来表示位带存储相关地址的一些术语:
      - 位带区:简单来说,就是支持位带操作的地址区域。
      - 位带别名:当我们访问别名地址时,实际上最终会通过一个地址映射过程,转换为对真正位带区的访问。
      Cortex - M3 的存储器映像中包含两个位段(bit - band)区。这两个位段区有着神奇的功能,它们会将别名存储器区中的每个字映射到位段存储器区的一个位。这意味着,当我们在别名存储区写入一个字时,其效果等同于对位段区的目标位执行读 - 改 - 写操作。
      具体来看这两个位带区:
      - 第一个位带区位于 SRAM 区的最低 1MB 范围,地址范围是从 0x2000_0000 到 0x200F_FFFF。
      - 第二个位带区则是片内外设区的最低 1MB 范围,地址范围为 0x4000_0000 到 0x400F_FFFF。
      这两个位带区里的地址,除了能像普通的 RAM 一样正常使用外,还各自拥有对应的“位带别名区”。这个位带别名区很有意思,它把每个比特“膨胀”成一个 32 位的字。这样一来,当我们通过位带别名区访问这些字时,就能够实现访问原始比特的目的。


      为了让大家更清楚位带区与位带别名区的对应关系,这里详细说明一下它们的膨胀对应原理。在位带区中,每一个比特都被映射到别名地址区的一个字,而且这个字只有最低有效位(LSB)是有效的。当某个别名地址被访问时,系统会先将该地址转换为位带地址。
      在 APM32F10x 系列中,外设寄存器和 SRAM 都被映射到了位段区里,这就为我们执行单一的位段写和读操作提供了便利。

3. 位带区与位带别名区的映射原理
      下面给大家介绍一下别名区中的字是如何对应位带区的相应位的映射公式:
bit_word_addr=bit_band_base+(byte_offset×32)+(bit_number×4)

   这里面各个参数的含义如下:
      - bit_word_addr:它代表的是别名存储器区中字的地址,这个地址最终会映射到某个目标位。
      - bit_band_base:指的是别名区的起始地址。
      - byte_offset:是包含目标位的字节在位段里的序号。
      - bit_number:表示目标位所在的位置,取值范围是 0 到 31。
      为了让大家更好地理解这个公式的应用,我重新举个例子。假设我们要映射别名区中 SRAM 地址为 0x20000700 的字节中的位 7,按照公式计算就是:
0x2201C02C=0x22000000+(0x700×32)+(7×4)

  这就表明,对 0x2201C02C 地址的写操作,和对 SRAM 中地址 0x20000700 字节的位 7 执行读 - 改 - 写操作,效果是完全一样的。

4. 位带操作的优越性
      在没有位带操作的情况下,访问内存的操作流程相对复杂。对于读操作,我们需要先读取位带地址中的一个字,然后把需要的位右移到最低有效位(LSB),最后将 LSB 返回。而写操作则更麻烦,要先把需要写的位左移至对应的位序号处,然后执行一个原子的“读-改-写”操作。
      有了位带操作之后,情况就大不一样了。通过读/写位带别名区,就能轻松完成上述操作。
      位带操作的优势体现在很多方面。比如说,在实际应用中,我们常常需要通过 GPIO 的管脚来单独控制每盏 LED 的点亮与熄灭,位带操作就能让这个控制过程变得更加简便。另外,在操作串行接口器件时,位带操作也提供了极大的便利。

5. 位带操作在多任务环境中的应用
      对于硬件 I/O 密集型的底层程序而言,位带操作简直是如鱼得水。在这类程序中,频繁地对硬件进行操作是常态,位带操作能够提高操作效率,减少不必要的开销。
      而且,对于大范围使用位标志的系统程序来说,位带机制也是一大助力。它能够让程序的逻辑更加清晰,代码更加简洁。
位带操作还有一个很巧妙的应用,就是用来化简跳转的判断。以前,当跳转依据是某个位时,我们需要执行一系列繁琐的操作:先读取整个寄存器,然后掩蔽不需要的位,最后进行比较并跳转。而现在,有了位带操作,我们只需要从位带别名区读取状态位,然后直接进行比较并跳转就可以了,大大简化了操作流程。
      此外,位带操作在多任务环境中也有着重要的作用。在多任务系统里,共享资源必须满足一次只有一个任务能够访问它,这就是所谓的“原子操作”。以前的读-改-写操作需要 3 条指令才能完成,这中间就会留下两个空当,很容易被中断,从而可能导致数据访问的紊乱。比如说,在一个多任务的资源管理系统中,如果多个任务同时对一个共享资源进行读 - 改 - 写操作,就可能出现数据不一致的情况。


      但是,通过使用 CM3 的位带操作,就能很好地解决这个问题。CM3 把“读-改-写”操作设计成了一个硬件级别支持的原子操作,这个操作是不能被中断的,从而保证了共享资源的正确访问。

6. C语言中的位带操作实现
      在 C 编译器中,并没有直接支持位带操作的功能。这是因为 C 编译器并不知道同一块内存可以使用不同的地址来访问,也不清楚对位带别名区的访问只对最低有效位(LSB)有效。
      那么,我们要在 C 语言中使用位带操作该怎么办呢?最简单的方法就是通过 #define 来定义一个位带别名区的地址。例如:
#define GPIOA_REG ((volatile unsigned long *) (0x40010800))
#define GPIOA_PIN5_BIT ((volatile unsigned long *) (0x42042020))
#define GPIOA_PIN12_BIT ((volatile unsigned long *) (0x42044048))
     为了更方便地使用位带操作,我们还可以建立两个宏。一个宏用于把“位带地址+位序号”转换成别名地址,另一个宏用于把别名地址转换成指针类型。示例代码如下:
//把“位带地址+位序号”转换成别名地址的宏
#define BITBAND_ADDR(addr, bitnum) ((addr & 0xF0000000) + 0x2000000 + ((addr & 0xFFFFF) << 5) + (bitnum << 2))

//把该地址转换成一个指针
#define ACCESS_ADDR(addr) *((volatile unsigned long *) (addr))
     这里需要特别注意一点,当我们使用位带功能时,要访问的变量必须用 volatile 来定义。这是因为 C 编译器并不知道同一个比特可以有两个地址。如果不使用 volatile,编译器可能会出于优化的考虑,在中途使用寄存器来操作数据的复本,直到最后才把复本写回存储器。这样一来,就会导致我们按不同的方式访问同一个位时,得到不一致的结果。
      在实际的 APM32 单片机开发中,我们一般会使用标准库或者DAL 库。这些库已经帮我们完成了位带转换的工作,我们只需要直接使用库提供的操作 API 就能够完成相应的任务。
      不过,虽然库的使用很方便,但我们还是要深入掌握位带操作的原理。只有知其然、知其所以然,当我们在开发过程中遇到相关问题时,才能从原理层面进行分析,找到问题的根源,并最终解决问题。

7. 位带操作的实际应用场景
      进一步拓展来说,随着项目复杂度的增加,位带操作的优势会更加明显。例如在一些工业控制领域的项目中,需要对大量的传感器数据进行快速处理和精准控制。众多传感器会实时采集各种物理量数据,如温度、压力、流量等,并通过接口传输到单片机中。
      在这种情况下,使用位带操作可以高效地处理这些数据中的每一位信息。比如,某些传感器数据以特定的位模式表示不同的状态或测量值范围。通过位带操作,我们可以直接对这些位进行检测、设置或修改,无需进行复杂的数据转换和整体字节或字的操作。这不仅大大提高了数据处理的速度,还减少了内存占用,提升了系统的整体性能。
      再比如在电机控制方面,电机的运行状态(如正反转、速度调节等)通常由几个控制位来决定。利用位带操作,我们可以直接对这些控制位进行原子操作,确保电机控制的准确性和及时性。在多电机协同工作的场景下,这种优势尤为突出,能够避免因传统读 - 改 - 写操作可能带来的冲突和错误,保证各个电机按照预定的逻辑稳定运行。
      在通信协议处理中,位带操作也能发挥重要作用。例如在一些自定义的通信协议里,数据包的某些位用于表示特殊指令或校验信息。通过位带操作,我们可以快速提取和处理这些关键位,确保通信的可靠性和高效性。特别是在处理高速通信数据时,位带操作能够在不影响系统整体性能的前提下,满足实时性要求。
      另外,在一些对功耗要求严格的应用场景中,位带操作由于减少了不必要的指令执行和内存访问次数,有助于降低系统的功耗。因为每一次额外的指令执行和内存访问都可能消耗一定的电量,而位带操作通过简化操作流程,减少了这些不必要的功耗,从而延长了设备的续航时间,这对于依靠电池供电的物联网设备等应用来说至关重要。

总结
      总之,位带操作作为 ARM Crotext - M3 架构中的一项强大功能,为 APM32 单片机的应用开发提供了更多的可能性和优化空间。无论是在基础的控制任务还是复杂的大型项目中,深入理解和灵活运用位带操作,都能够帮助开发者打造出性能更优、效率更高、稳定性更强的系统 。
      希望通过以上内容,能让大家对位带操作有更清晰、更深入的理解。好了,今天的分享就到这里,祝愿大家在单片机开发的道路上不断进步,加油!

本文编辑参考文档:《Cortex-M3权威指南(中文).pdf》


使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

42

主题

78

帖子

7

粉丝