第11章 STM32位带操作

2024/11/27 STM32
第11章 STM32位带操作

一、位带操作介绍

  1. 用教科书的话来说,位带操作(Bit Banding),位操作就是可以单独的对一个比特位读和写,这个在 51 单片机中非常常见。51 单片机中通过关键字 sbit 来实现位定义,STM32 没有这样的关键字,而是通过访问位带别名区来实现。
  2. 在 STM32 中,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,令一个是外设区最低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的位带别名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。它通过将内存的每一位映射到一个独立的地址空间,从而实现对单个位的读写操作。
  3. 具体来说,位带操作是通过将一个32位的内存地址映射到另外一个32位的地址空间来实现的。例如,假设我们要对一个变量的第0位进行操作,那么就可以将该变量的地址通过位带映射到一个新的地址空间,该地址空间的第0位对应原地址变量的第0位,这样就可以直接对该位进行读写操作,而不需要对整个变量进行读写。

pAhXONF.png

  1. 用的最多的是外设别名区GPIO的操作
  2. 通俗的来说,就是STM32如果要具体对比如PA的1号引脚进行比特位级别的操作时,是无法直接实现的,那为了单独地对这个比特位进行操作赋值,就要使用位带操作。把这个bit位映射到一个长度为32bit,即4个字节的另外的一个内存空间里去,也就是说给这个1bit位起了一个别名,但是你可以直接操作这个别名,效果等同映射给本名。将原来PA1的地址扩展成一个32位的字地址,对32位的地址进行操作。
  3. 更通俗的说,我想吃西瓜、苹果、梨这三种水果的某一个,但是有明确的限制,不允许吃有具体名称的水果,那我给西瓜起个别名叫地上长的水果,苹果叫树上长的水果,那么,我可以,并且吃的是地上长的水果,实际上吃的就是西瓜,绕开了这个限制,同时吃到了想要吃的具体水果。

二、位带区与位带别名区地址转换

  1. 外设位带别名区地址 AliasAddr=0x42000000+ (A-0x40000000)*8*4 +n*4 理解:A是位带区的一个地址,A-0x40000000是地址偏移量,偏移量中的一个字节是8位,一个位要被膨胀成32位,即4个字节,然后要操作A字节的第n位,每一位上有4个字节,第n位的位置就是n * 4,再加上前面的就得到了这个公式
  2. SRAM位带别名区地址 AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4
//&运算:都转化为二进制按位与
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))

// 地址比较大,所以定义的是无符号的长整型
#define MEM_ADDR(addr) 		*((volatile unsigned long *)(addr))
// 对位带的封装
#define BIT_ADDR(addr, bitnum) 	MEM_ADDR(BITBAND(addr, bitnum))

  1. volatile关键字: volatile用于声明一个变量,告诉编译器该变量值容易发生改变,在编译、读取、存储该变量的时候都不要做任何优化,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取存储数据,不做优化,在做嵌入式开发的时候,因为有时变量地址有可能是系统的一个外设地址,他的值的变化并不在程序控制范围内,随时有可能变化,因此需要对他进行声明,每次读取或者存储直接对地址进行操作。
  2. &运算: 有0即0,都1为1

三、位带操作的优点

  1. 控制GPIO口输入输出非常简单。
  2. 操作串行接口芯片非常方便(DS1302(SPI通信)、74HC595等)。
  3. 代码简洁,阅读方便。

四、位带操作代码

#ifndef __SYSTEM_H
#define __SYSTEM_H

#include "stm32f10x.h" // 包含 STM32F10x 标准外设库头文件

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))

#define MEM_ADDR(addr) 		*((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) 	MEM_ADDR(BITBAND(addr, bitnum))


#define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
#define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
#define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
#define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
#define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
#define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C

#define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
#define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
#define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
#define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
#define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
#define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
#define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08


#define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
#define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 

#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
#define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 

#define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
#define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 

#define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
#define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 

#define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
#define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入

#define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
#define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入

#define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
#define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入


#endif

  • out是输出,in是输入

四、软件设计

  1. 在工程目录文件夹下,创建一个Public文件夹

pA4R1jH.png 该文件夹用来存放一些公共的文件,如:位带定义、串口、延时等

system.h

pA4IkjO.png

system.c

pA4InUA.png

LED.h

pA4I1v8.png

LED.c

pAW5HI0.png

main.c

pA4IU5n.png

文档信息

搜索

    Table of Contents