八、位带操作
1.1.位带操作介绍
为了减少“读-改-写”操作的次数,Cortex-M4 处理器提供了一个可以执行单原子比特操作的位带功能。存储器映射包含了两个支持位带操作的区域。其中一个是 SRAM 区的最低 1MB 范围,第二个是片内外设区的最低 1MB 范围。这两个区域中的地址除了普通应用外,还有自己的“位带别名区”。位带别名区把每个比特扩展成一个 32 位的字。当用户访问位带别名区时,就可以达到访问原始比特的目的。总结就是 CPU 不能直接对位带区中的单个数据位位寻址,只能通过对位带别名区的访问(或读/写)实现对位带区单个数据位的访问(或读/写),这种操作被称为位带操作。使用位带操作的目的是能够像 51 单片机那样直接给 IO 口拉高拉低,例如 P60 = 1 这种操作。
1.2.位带操作内存地址
前面我们说了存储器映射支持两个位带操作的区域。关于支持位带操作的两个内存区的范围是: 0x2000_0000‐0x200F_FFFF(SRAM 区的最低 1MB) 0x4000_0000‐0x400F_FFFF(片上外设区的最低 1MB)
对应的位段别名区的起始地址分别为 0x2200 0000 和 0x4200 0000,这个在编程的时候需要使用。
1.3.位带操作优势
- 更高效
- 读取更简单
- 访问速度快
- 相对安全。在带有操作系统的开发中,多任务并发运行的时候就有可能在任务切换的过程中发生不可预料的问题,而位带操作由于是属于硬件完成的不可被异常打断的操作(原子操作),相对于读-写-改的操作模式会更安全。
- 提高运行效率和节省代码空间。简单的程序直接使用库函数或者寄存器操作,对于比较复杂的程序建议尽量使用位带操作来实现。
1.4.配置位带操作
上面介绍了位带操作的地址和优势,那怎么去查找目标比特对应的位带别名区的地址呢?关于位带操作的介绍在用户手册的第 41 页,如图 4-1-1 所示。
从图 4-1-1 可以看到,通过 bit_word_addr = bit_band_base +(byte_offset×32)+(bit_number×4)这个公式可以计算出对应的位带别名区的地址。
这里给大家介绍一下这个公式如何去使用,以 PD7 为例。
先分析一下这个公式,要计算位带操作别名区地址,首先要知道位带别名区的起始地址、位带区目标比特所在的字节的字节地址偏移量和目标比特在对应字节中的位置。位带别名区的地址在 1.2 节可以了解到是 0x42000000。我们需要配置 PD7 引脚的输出功能,那么对应的寄存器为端口输出控制寄存器(GPIOx_OCTL),我们要操作的是这一个字节地址的第 7 位,那对应的字节地址偏移量就是 GPIOx_OCTL 的偏移量 0x14 + GPIOD 的地址然后还要减去地址 0x40000000(片上外设的起始地址为 0x4000_0000),对应目标比特在对应字节中的位置就是 7。如果是配置为输入功能的话,要操作的寄存器为端口输入状态寄存器(GPIOx_ISTAT),对应的偏移量为 0x10 + GPIOD 的地址然后还要起始地址 0x40000000,对应目标比特在对应字节中的位置也是 7。
bit_word_addr =bit_band_base +(byte_offset×32)+(bit_number×4)转化为代码就是
#define BIT_ADDR(byte_offset,bitnum)
(volatile unsigned long*)(0x42000000 + (byte_offset * 32) + (bitnum * 4))
PD7输出对应的字节偏移量为#define GPIOD_OCTL_OFFSET ((GPIOD + 0x14) - 0x40000000)
PD7输入对应的字节偏移量为#define GPIOD_ISTAT_OFFSET ((GPIOD + 0x10) - 0x40000000)
PD7输出最终可配置为#define PDout(n) *(BIT_ADDR(GPIOD_OCTL_OFFSET,n)) // 输出
PD7输入最终可配置为#define PDin(n) *(BIT_ADDR(GPIOD_ISTAT_OFFSET,n)) // 输入
这里的n就是对应的第几位(n取值范围为0-15)。
2
3
4
5
6
7
8
9
10
11
12
13
14
我们要配置 PD7 输出高电平就是 PDout(7) = 1;配置输出低电平就是 PDout(7) = 0; 。其它的也是类似的,完全可以举一反三,大家有兴趣可以自己尝试一下。
需要注意的是当你使用位带功能时,要访问的变量必须用 volatile 来定义。因为 c 编译器并不知道同一个比特可以有两个地址。通过 volatile 使得编译器每次都如实地把新数值写入存储器,而不会再出于优化的考虑。
1.5.LED 灯闪烁实验
前面章节我们学习了如何去使用库函数和滴答定时器实现 LED 灯闪烁的效果。这一章就在原来的基础上把用库函数输出高低电平的函数替换为我们的位带操作输出高低电平。只需要
将main.c文件下的gpio_bit_set(BSP_LED2_PORT,BSP_LED2_PIN); 函数替换为PDout(7) = 1;
将gpio_bit_reset(BSP_LED2_PORT,BSP_LED2_PIN); 函数替换为PDout(7) = 0;
2
然后编译运行即可。
关于 LED 灯闪烁实验代码,在资源包/04 软件资料/代码例程/里面的 004 位带操作。
烧写我们的代码之后,可以看到开发板的 LED2 将会 1s 亮 1s 灭。