前言
位操作就是可以单独的对一个比特位读和写,这个在 51 单片机中非常常见。51 单片机中通过关键字 sbit 来实现位定义,STM32 没有这样的关键字,而是通过访问位带别名区来实现。
提示:以下是本篇文章正文内容
一、示意图
在 STM32 中,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,令一个是外设区最低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的位带别名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。
二、位带区
1.外设位带区
外设外带区的地址为:0X40000000~0X40100000,大小为 1MB,这 1MB 的大小在 103系列大/中/小容量型号的单片机中包含了片上外设的全部寄存器,这些寄存器的地址为:0X40000000~0X40029FFF 。
外设位带区地址为: AliasAddr= =0x42000000+ (A-0x40000000)*8*4 +n*4
0X42000000 是外设位带别名区的起始地址,0x40000000 是外设位带区的起始地址,(A-0x40000000)表示该比特前面有多少个字节,一个字节有 8 位,所以8,一个位膨胀后是 4 个字节,所以4,n 表示该比特在 A 地址的序号,因为一个位经过膨胀后是四个字节,所以也*4。
2.SRAM 位带区
SRAM 的位带区的地址为:0X2000 0000~X2010 0000,大小为 1MB,经过膨胀后的位带别名区地址为:0X2200 0000~0X23FF FFFF,大小为 32MB。
SRAM 位带区地址为: AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4,分析同上。
3.统一公式
为了方便操作,我们可以把这两个公式合并成一个公式,把“位带地址+位序号”转换成别名区地址统一成一个宏。
// 把“位带地址+位序号”转换成别名地址的宏
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 0x00FFFFFF)<<5)+(bitnum<<2))
addr & 0xF0000000 是为了区别 SRAM 还是外设,实际效果就是取出 4 或者 2,如果是外设,则取出的是 4,+0X02000000 之后就等于 0X42000000,0X42000000 是外设别名区的起始地址。如果是 SRAM,则取出的是 2,+0X02000000 之后就等于 0X22000000,0X22000000 是 SRAM 别名区的起始地址。
实际应用:
1 // 把一个地址转换成一个指针
2 #define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
3
4 // 把位带别名区地址转换成指针
5 #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
三、GPIO 位带操作
1.GPIO 寄存器映射
1 // GPIO ODR 和 IDR 寄存器地址映射
2 #define GPIOA_ODR_Addr (GPIOA_BASE+12) //0x4001080C
3 #define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
4 #define GPIOC_ODR_Addr (GPIOC_BASE+12) //0x4001100C
5 #define GPIOD_ODR_Addr (GPIOD_BASE+12) //0x4001140C
6 #define GPIOE_ODR_Addr (GPIOE_BASE+12) //0x4001180C
7 #define GPIOF_ODR_Addr (GPIOF_BASE+12) //0x40011A0C
8 #define GPIOG_ODR_Addr (GPIOG_BASE+12) //0x40011E0C
9
10 #define GPIOA_IDR_Addr (GPIOA_BASE+8) //0x40010808
11 #define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
12 #define GPIOC_IDR_Addr (GPIOC_BASE+8) //0x40011008
13 #define GPIOD_IDR_Addr (GPIOD_BASE+8) //0x40011408
14 #define GPIOE_IDR_Addr (GPIOE_BASE+8) //0x40011808
15 #define GPIOF_IDR_Addr (GPIOF_BASE+8) //0x40011A08
16 #define GPIOG_IDR_Addr (GPIOG_BASE+8) //0x40011E08
现在我们就可以用位操作的方法来控制 GPIO 的输入和输出了,其中宏参数 n 表示具体是哪一个 IO 口。
2. GPIO 位操作
1 // 单独操作 GPIO 的某一个 IO 口,n(0,1,2...16),n 表示具体是哪一个 IO 口
2 #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
3 #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
4
5 #define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
6 #define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
7
8 #define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) //输出
9 #define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) //输入
10
11 #define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) //输出
12 #define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) //输入
13
14 #define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) //输出
15 #define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) //输入
16
17 #define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n) //输出
18 #define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n) //输入
19
20 #define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n) //输出
21 #define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n) //输入
3. 主函数
该工程我们直接从 LED-库函数 操作移植过来,有关 LED GPIO 初始化和软件延时等函数我们直接用,修改的是控制 GPIO 输出的部分改成了位操作。
main 函数
1 int main(void)
2 {
3 // 程序来到 main 函数之前,启动文件:statup_stm32f10x_hd.s 已经调用
4 // SystemInit()函数把系统时钟初始化成 72MHZ
5 // SystemInit()在 system_stm32f10x.c 中定义
6 // 如果用户想修改系统时钟,可自行编写程序修改
7
8 LED_GPIO_Config();
9
10 while ( 1 ) {
11 // PB0 = 0,点亮 LED
12 PBout(0)= 0;
13 SOFT_Delay(0x0FFFFF);
14
15 // PB1 = 1,熄灭 LED
16 PBout(0)= 1;
17 SOFT_Delay(0x0FFFFF);
18 }
19 }
该实验我们让 IO 口输出高低电平来控制 LED 的亮灭,负逻辑点亮。
四、下载验证
把编译好的程序下载到开发板并复位,按下按键可以控制 LED 灯亮、灭状态。
五、收获
1.初学者写一部分代码,编译一部分, 不要等到写完一起编译。
2.位带操作可以实现操作寄存器的某一位不改变其他位。
上一篇:STM32启动文件执行步骤分析
下一篇:STM32固件库实现按键控制
推荐阅读最新更新时间:2024-11-12 13:05