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