一、位带操作的概念
1. 概念
STM32微控制器中的位带操作(Bit-Banding)是一种特殊的内存映射技术,它允许将特定的位(Bit)与特定的内存地址绑定,从而实现对单个位的原子级操作。
位带操作在STM32中通常用于对GPIO端口、寄存器以及其他外设的单个位进行读写操作,提高了代码的可读性和效率。
2. 位带操作的一些特点
(1)原子级操作
位带操作可以确保对单个位的读写是原子性的,即在任何时候只有一个CPU周期可以访问这个位,从而避免了多线程并发访问时可能出现的竞态条件和数据不一致性问题。
(2)内存映射
位带操作利用了STM32微控制器中的特殊内存区域,将每个位与一个特定的内存地址绑定。通过读写这个内存地址,可以实现对位的操作。
(3)语法简单
位带操作的语法非常简单明了,通常使用类似于“(uint32_t)BITBAND_ADDRESS = VALUE;”的形式进行位的设置或清除。
(4)应用广泛
位带操作可以用于对GPIO端口的单个引脚进行操作,也可以用于对外设寄存器的单个位进行设置或清除,因此在嵌入式开发中应用非常广泛。
(5)性能优化
由于位带操作是在硬件层面实现的,因此具有非常高的执行效率,适用于对性能要求较高的应用场景。
3. 位带操作的缺点
- 在多线程或者中断环境下,需要特别注意原子性操作的问题,以避免可能的竞态条件和数据不一致性问题。
- 位带操作也可能会使代码的可移植性降低,因为不同的微控制器架构可能对位带操作的支持程度不同。
二、位带操作的步骤
1. 确定位带区域
首先需要确定哪些地址是位带区域,只有位带区域才支持位带操作。
在 STM32 中,支持位带操作的区域有:
- SRAM 区的最低 1M 范围(APB1、APB2、AHB外设)
- 片内外设区的最低 1M 范围。
在 《Cortex权威指南中(中文)》中可以查到具体说明:
具体地址范围:
- 0x2000 0000-0x200F FFFF (SRAM区中的最低1MB)
- 0x4000 0000-0x400F FFFF (片上外设区中的最低1MB)
对应别名区的范围为:
- 0x2200 0000-0x23FF FFFF(32MB)
- 0x4200 0000-0x43FF FFFF (32MB)
下面介绍使用的都片内外设区位带操作。
2. 计算位带地址
对于要进行位带操作的位,需要计算其对应的位带地址。位带地址的计算通常使用公式 :
BITBAND_ALIAS_BASE + (BYTE_OFFSET * 32) + (BIT_NUMBER * 4)
,
其中:
-
BITBAND_ALIAS_BASE
是位带区域的基地址, -
BYTE_OFFSET
是位所在字节相对于位带区域起始地址的偏移量, -
BIT_NUMBER
是位在字节中的偏移量,乘以 4 是因为每个位的地址是 4 个字节对齐的。
当使用片内外设别位带区时,起始地址是 BITBAND_ALIAS_BASE = 0x42000000
。
当使用SRAM位带区时,BITBAND_ALIAS_BASE = 0x20000000
。
为方便操作,用一个公式来计算位带地址:
KaTeX parse error: Expected 'EOF', got '&' at position 5: ((A &̲ 0xF0000000) + …
其中:
- A 是要操作的寄存器地址
- n 是位号。
用宏来表示:
// 计算地址值
#define BIT_BAND_ADDR(A, n) (((A & 0xF0000000) + 0x02000000) + ((A & 0x000FFFFF) << 5) + ((n) << 2))
// 计算指针
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
// 位带操作
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
3. 读取位操作
定义宏:
#define PAin(n) BIT_ADDR(GPIOA->IDR, n)
#define PBin(n) BIT_ADDR(GPIOB->IDR, n)
#define PCin(n) BIT_ADDR(GPIOC->IDR, n)
#define PDin(n) BIT_ADDR(GPIOD->IDR, n)
#define PEin(n) BIT_ADDR(GPIOE->IDR, n)
4. 写入位操作
#define PAout(n) BIT_ADDR(GPIOA->ODR, n)
#define PBout(n) BIT_ADDR(GPIOB->ODR, n)
#define PCout(n) BIT_ADDR(GPIOC->ODR, n)
#define PDout(n) BIT_ADDR(GPIOD->ODR, n)
#define PEout(n) BIT_ADDR(GPIOE->ODR, n)
5. 完成操作
完成位带操作后,可以继续进行其他操作,或者根据需要进行错误处理或其他逻辑。
三、位带操作GPIOC LED示例
1. 项目目录结构
2. utils.h
#ifndef __utilsH__
#define __utilsH__
#include "stm32f10x.h"
#define GPIOA_ODR_A (GPIOA_BASE+0x0c)
#define GPIOA_IDR_A (GPIOA_BASE+0x08)
#define GPIOB_ODR_A (GPIOB_BASE+0x0c)
#define GPIOB_IDR_A (GPIOB_BASE+0x08)
#define GPIOC_ODR_A (GPIOC_BASE+0x0c)
#define GPIOC_IDR_A (GPIOC_BASE+0x08)
#define GPIOD_ODR_A (GPIOD_BASE+0x0c)
#define GPIOD_IDR_A (GPIOD_BASE+0x08)
#define GPIOE_ODR_A (GPIOE_BASE+0x0c)
#define GPIOE_IDR_A (GPIOE_BASE+0x08)
#define BitBind(Addr,BitNum) *((volatile unsigned long *)((Addr&0xF0000000)+0x2000000+((Addr&0xfffff)<<5)+(BitNum<<2)))
#define PAout(n) BitBind(GPIOA_ODR_A,n) // 输出
#define PAin(n) BitBind(GPIOA_IDR_A,n) // 输入
#define PBout(n) BitBind(GPIOB_ODR_A,n)
#define PBin(n) BitBind(GPIOB_IDR_A,n)
#define PCout(n) BitBind(GPIOC_ODR_A,n)
#define PCin(n) BitBind(GPIOC_IDR_A,n)
#define PDout(n) BitBind(GPIOD_ODR_A,n)
#define PDin(n) BitBind(GPIOD_IDR_A,n)
#define PEout(n) BitBind(GPIOE_ODR_A,n)
#define PEin(n) BitBind(GPIOE_IDR_A,n)
void GPIO_Configuration(void);
void delay(uint32_t i);
void allOff(void);
#endif
3. utils.c
#include "utils.h"
// GPIO初始化
void GPIO_Configuration(void)
{
// 打开时钟
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
// 推挽输出
GPIOC->CRL = 0x33333333;
GPIOC->CRH = 0x33333333;
}
// 延时
void delay(uint32_t i)
{
while (i--)
;
}
void allOff(void){
for (int j = 0; j < 8; j++)
{
PCout(j) = 1;
}
}
4. main.c
#include "stdint.h"
#include "utils.h"
void SystemInit(){
}
int main(void)
{
GPIO_Configuration();
int j;
while (1)
{
allOff();
delay(0xfffff);
for (j = 0; j < 8; j++)
{
PCout(j) = 0;
delay(0xfffff);
}
}
}