九、位带操作
1. 位带操作介绍
为了减少 “ 读-改-写
” 操作的次数,Cortex-M3处理器提供了一个可以执行单原子比特操作的位带功能。存储器映射包含了两个支持位带操作的区域。其中一个是SRAM区的最低1MB范围,第二个是片内外设区的最低1MB范围。这两个区域中的地址除了普通应用外,还有自己的 “ 位带别名区
” 。位带别名区把每个比特扩展成一个32位的字。当用户访问位带别名区时,就可以达到访问原始比特的目的。总结就是CPU不能直接对位带区中的单个数据位位寻址,只能通过对位带别名区的访问(或读/写)实现对位带区单个数据位的访问(或读/写),这种操作被称为位带操作。使用位带操作的目的是能够像51单片机那样直接给IO口拉高拉低,例如 PCout(13) = 1 这种操作。
可以查看CM3权威指南中的位带操作介绍篇
2. 位带操作内存地址
前面我们说了存储器映射支持两个位带操作的区域。
关于支持位带操作的两个内存区的范围是:
- 0x2000 0000 ‐ 0x200F FFFF(SRAM 区的最低1MB)
- 0x4000 0000 ‐ 0x400F FFFF(片上外设区的最低 1MB)
对应的位段别名区的起始地址分别为 0x2200 0000 和 0x4200 0000 ,这个在编程的时候需要使用。
3. 位带操作优势
- 更高效
- 读取更简单
- 访问速度快
- 相对安全。在带有操作系统的开发中,多任务并发运行的时候就有可能在任务切换的过程中发生不可预料的问题,而位带操作由于是属于硬件完成的不可被异常打断的操作(原子操作),相对于读-写-改的操作模式会更安全。
- 提高运行效率和节省代码空间。简单的程序直接使用库函数或者寄存器操作,对于比较复杂的程序建议尽量使用位带操作来实现。
4. 配置位带操作
上面介绍了位带操作的地址和优势,那怎么去查找目标比特对应的位带别名区的地址呢?关于位带操作的介绍在CM3权威指南的第847页,如图所示。
通过公式可以计算出对应的位带别名区的地址:
这里给大家介绍一下这个公式如何去使用,以PC13为例:
// 位带操作的宏定义,直接使用您提供的BIT_ADDR宏
#define BIT_ADDR(Addr, Bit_Num) *((volatile uint32_t *)(0x42000000 + ((Addr - 0x40000000) * 32) + (Bit_Num * 4)))
// 计算GPIOC寄存器的位带别名区地址
#define GPIOC_ODR_ADDR (GPIOC_BASE+12) //0x40000000 + 0x10000 + 0x1000 + 0x0C
#define GPIOC_IDR_ADDR (GPIOC_BASE+8) //0x40000000 + 0x10000 + 0x1000 + 0x08
// 定义PCout和PCin宏
#define PCout(Pin) BIT_ADDR(GPIOC_ODR_ADDR, Pin) // GPIOC输出
#define PCin(Pin) BIT_ADDR(GPIOC_IDR_ADDR, Pin) // GPIOC输入
2
3
4
5
6
7
8
9
10
宏定义解释
BIT_ADDR(Addr, Bit_Num)
:这个宏用于计算位带别名区的地址。通过给定的基地址 Addr(比如寄存器地址)和位号 Bit * Num,它会根据位带别名区的计算公式 0x42000000 + ((Addr - 0x40000000) * 32) + (Bit*Num * 4) 来计算出对应位的地址,并返回一个指向该地址的指针。GPIOC_ODR_ADDR
:定义了 GPIOC 的输出数据寄存器(ODR)的地址,即 GPIOC 寄存器基地址加上偏移量 12,对应于0x40000000 + 0x10000 + 0x1000 + 0x0C。GPIOC_IDR_ADDR
:定义了 GPIOC 的输入数据寄存器(IDR)的地址,即 GPIOC 寄存器基地址加上偏移量 8,对应于0x40000000 + 0x10000 + 0x1000 + 0x08。PCout(Pin)
:使用 BIT_ADDR 宏来定义,用于操作 GPIOC 的输出数据寄存器(ODR)。它会将指定引脚 Pin 对应的位设置为输出数据寄存器的值。PCin(Pin)
:使用 BIT_ADDR 宏来定义,用于操作 GPIOC 的输入数据寄存器(IDR)。它可以读取指定引脚 Pin 对应的输入数据寄存器的值。
需要注意的是当你使用位带功能时,要访问的变量必须用volatile
来定义。因为c编译器并不知道同一个比特可以有两个地址。通过volatile
使得编译器每次都如实地把新数值写入存储器,避免语句被优化。
5. 位带操作文件
完整代码:
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-03-26 LCKFB-LP first version
*/
#ifndef __SYS_H__
#define __SYS_H__
#include "stm32f10x.h"
#define BIT_ADDR(Addr, Bit_Num) *((volatile uint32_t *)(0x42000000 + ((Addr - 0x40000000) * 32) + (Bit_Num * 4)))
#define GPIOA_ODR_ADDR (GPIOA_BASE+12) //0x40000000 + 0x10000 + 0x0800 + 0x0C
#define GPIOB_ODR_ADDR (GPIOB_BASE+12) //0x40000000 + 0x10000 + 0x0C00 + 0x0C
#define GPIOC_ODR_ADDR (GPIOC_BASE+12) //0x40000000 + 0x10000 + 0x1000 + 0x0C
#define GPIOA_IDR_ADDR (GPIOA_BASE+8) //0x40000000 + 0x10000 + 0x0800 + 0x08
#define GPIOB_IDR_ADDR (GPIOB_BASE+8) //0x40000000 + 0x10000 + 0x0C00 + 0x08
#define GPIOC_IDR_ADDR (GPIOC_BASE+8) //0x40000000 + 0x10000 + 0x1000 + 0x08
#define PAout(Pin) BIT_ADDR(GPIOA_ODR_ADDR, Pin) // GPIOA输出
#define PBout(Pin) BIT_ADDR(GPIOB_ODR_ADDR, Pin) // GPIOB输出
#define PCout(Pin) BIT_ADDR(GPIOC_ODR_ADDR, Pin) // GPIOC输出
#define PAin(Pin) BIT_ADDR(GPIOA_IDR_ADDR, Pin) // GPIOA输入
#define PBin(Pin) BIT_ADDR(GPIOB_IDR_ADDR, Pin) // GPIOB输入
#define PCin(Pin) BIT_ADDR(GPIOC_IDR_ADDR, Pin) // GPIOC输入
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
6. 实验现象
我们使用位带操作文件控制 PC13 的LED灯闪烁。
关于LED灯闪烁实验代码百度网盘下载,在立创·STM32F103C8T6开发板资料(标准库)/第03章软件资料/代码例程/004位带操作。
烧写我们的代码之后,可以看到开发板LED将会0.1s亮0.1s灭。