您现在的位置是:首页 >技术杂谈 >STM32F4_位带操作网站首页技术杂谈

STM32F4_位带操作

light_2025 2024-07-03 06:01:02
简介STM32F4_位带操作

目录

1. 位带简介

2. 别名区地址的计算

2.1 合并计算

3. 位带操作访问ODR和IDR寄存器

4. GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2 / GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2


        位带操作在写单片机程序时,或者在宏定义某个变量时,是非常有用且方便的。

1. 位带简介

位带操作其实就是库函数操作下最底层的操作逻辑。

//在STM32中如果要使PA0输出低电平,可以是
GPIOA->ODR|=0<<0;


//在51单片机中,位带操作是通过sbit来定义的。

//P0=0xFF; //总线操作

sbit LED1=P^0   //位操作

LED1=0;

在51单片机中,通过 sbit 定义的LED1,在函数中调用 LED1 就等同于调用了 P0 引脚,给 LED1 低电平,LED 灯就会亮。

拿一个寄存器来说明位带操作的必要性:

        一个32位的寄存器,整个寄存器是有自己特定的地址的,当访问寄存器的地址时,会操作整个寄存器。但有些时候,我们只需要访问寄存器的某一位,将该位设为低电平0/高电平1就可以实现特定功能,寄存器的每一位都是有特定地址的,访问寄存器中特定位的操作,就称为位带操作

支持了位带操作以后,可以使用普通的加载/存储指令来对单一的比特进行读写。SRAM静态存储器的最低1MB范围内可以实现位带,也可以在片内最低1MB范围内实现位带操作。这两个区的地址除了可以像普通的ARM一样使用外,还都有自己的 位带别名区 ,位带别名区把每个比特膨胀成一个32位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。

比方说:PB引脚的ODR寄存器的地址是0x40010C0C,此时我们操作PB0,其最底层的操作逻辑就是操作ODR寄存器的第0位,那么这个位新的地址就是0x42000000+(0x40010C0C-0x40000000)32+0*4;通过宏定义给这个地址命名为PBout(0)。

#define PBout(0)  0x42000000+(0x40010C0C-0x40000000)84+0*4

2. 别名区地址的计算

支持位带操作的两个内存区的范围是

0x2000_0000-0x200F_FFFF(SRAM区中的最低1MB)

0x4000_0000-0x400F_FFFF(片上外设区中的最低1MB)

对于SRAM位带区的某个比特位,记它所在字节地址为A,位序号为n(0<=n<=7),则该比特位在别名区的地址为:

AliasAddr=0x22000000+((A-0x20000000)*8+n)*4=0x22000000+(A-0x20000000)*32+n*4

对于片上外设的某个比特位,记它所在字节的地址为A,位序号为n(0<=n<=7),则该比特位在别名区的地址为:

AliasAddr=0x42000000+((A-0x40000000)*8+n)*4=0x42000000+(A-0x40000000)*32+n*4

其中,4表示一个字为4个字节,8表示一个字节中有8个比特位 

2.1 合并计算

外设与SRAM位带区与位带别名区的地址统一用一个公式表示

((addr&0xF000000)+0x02000000+((addr&0x00FFFFFF)<<5)+(bitnum<<2))

其中:addr:要操作的位所在的寄存器地址

           bitnum:位号,也就是在寄存器中的第几位 

公式中:

        addr&0xF0000000的意思是拿出要操作位所在的寄存器的最高位,如果是外设位带区,由上图,则拿出的最高位就是4,0x40000000+0x02000000=0x42000000,也就是外设位带别名区地址;如果是SRAM位带区,由上图,则拿出来的最高位就是2,0x20000000+0x02000000=0x22000000,也就是SRAM位带别名区地址;整个过程本质就是区分是外设位带操作还是SRAM位带操作

        addr&0x00FFFFFF的意思是屏蔽掉高两位,由上图可知,对于没有合并之前的公式来说,A-0x40000000就是最高位地址减去起始地址。对于外设位带区来说,最高地址就是0x40100000,SRAM位带区的最高地址就是0x20100000,不管是0x40100000-0x40000000还是0x20100000-0x20000000,其最终相减的地址都是低六位有效,最高两位是相同的,在做减法的过程中没有实际作用,所以&0x00FFFFFF就是把高2位屏蔽掉

        (addr&0x00FFFFFF)<<5左移5位的意思表示乘以2^{5},也就是乘以32,正好是对应没有合并之前的公式,左移5位表示放大了32倍。因为在C语言的位操作中,左移表示向左移位,右侧补0。

        bitnum<<2左移2位表示乘以2^{2},也就是乘以4,正好也是对应了没有合并公式之前的*4,表示 bitnum 放大了4倍。

3. 位带操作访问ODR和IDR寄存器

在学习GPIO口时,我们已经学习过了GPIO的ODR寄存器IDR寄存器,分别是GPIO输出数据寄存器GPIO输入数据寄存器

//IO口操作宏定义
#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)) 
//IO口地址映射
#define GPIOA_ODR_Addr    (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr    (GPIOB_BASE+20) //0x40020414 
#define GPIOC_ODR_Addr    (GPIOC_BASE+20) //0x40020814 
#define GPIOD_ODR_Addr    (GPIOD_BASE+20) //0x40020C14 
#define GPIOE_ODR_Addr    (GPIOE_BASE+20) //0x40021014 
#define GPIOF_ODR_Addr    (GPIOF_BASE+20) //0x40021414    
#define GPIOG_ODR_Addr    (GPIOG_BASE+20) //0x40021814   
#define GPIOH_ODR_Addr    (GPIOH_BASE+20) //0x40021C14    
#define GPIOI_ODR_Addr    (GPIOI_BASE+20) //0x40022014     

#define GPIOA_IDR_Addr    (GPIOA_BASE+16) //0x40020010 
#define GPIOB_IDR_Addr    (GPIOB_BASE+16) //0x40020410 
#define GPIOC_IDR_Addr    (GPIOC_BASE+16) //0x40020810 
#define GPIOD_IDR_Addr    (GPIOD_BASE+16) //0x40020C10 
#define GPIOE_IDR_Addr    (GPIOE_BASE+16) //0x40021010 
#define GPIOF_IDR_Addr    (GPIOF_BASE+16) //0x40021410 
#define GPIOG_IDR_Addr    (GPIOG_BASE+16) //0x40021810 
#define GPIOH_IDR_Addr    (GPIOH_BASE+16) //0x40021C10 
#define GPIOI_IDR_Addr    (GPIOI_BASE+16) //0x40022010 
 
//IO口操作,只对单一的IO口!
//确保n的值小于16!
#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)  //输入

#define PHout(n)   BIT_ADDR(GPIOH_ODR_Addr,n)  //输出 
#define PHin(n)    BIT_ADDR(GPIOH_IDR_Addr,n)  //输入

#define PIout(n)   BIT_ADDR(GPIOI_ODR_Addr,n)  //输出 
#define PIin(n)    BIT_ADDR(GPIOI_IDR_Addr,n)  //输入

4. GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2 / GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2

在上一节IIC的代码程序中,其IIC.h头文件中使用位操作进行了串行数据总线SDA输入输出的设置。这里我们具体来讲解一下代码是如何定义的。


#define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;}  //PB9输入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;}  //PB9输出模式

        在GPIO口的学习中,我们知道访问某一IO口时,需要初始化GPIO结构体,结构体包括MODE、OTYPE、PUPD、PIN,其中MODE是通过GPIOx_MODER(GPIO端口模式寄存器)来控制的。

该寄存器为32位寄存器,每两位控制一个引脚,则分别控制GPIOx的Px0-15引脚(x可取A、B、C、D、E、F、G)。

操作该寄存器的位带操作的主要思想是首先将想要操作的那一确定位清0,即使用与&操作来清零;然后将想要的功能通过软件来写入,即用或|操作来赋值。功能主要也就是

  •         00:输入
  •         01:通用输出
  •         10:复用功能
  •         11:模拟功能

明白了这些,我们现在再来看代码:


#define SDA_IN() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;}  //PB9输入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;}  //PB9输出模式


//GPIOB->MODER表示通过指针调用GPIOB模式寄存器
//该寄存器是每两个位控制一个引脚,9*2,也就是操作PB9引脚,这里也就默认我们想要操作的引脚就是PB9引脚
//3<<(9*2)的意思是:在32位寄存器下,
//3=0000 0000 0000 0000 0000 0000 0000 0011,左移9*2,那么最低2位11就会被移动到17 18位的地址上
//  0000 0000 0000 0011 0000 0000 0000 0000,然后取反就是
//  1111 1111 1111 1100 1111 1111 1111 1111,然后和GPIOB->MODER进行与&操作后就是
//  0000 0000 0000 0000 0000 0000 0000 0000,表示将17 18位地址清零,

//到这里完成了位带操作的第一步清0,然后第二部进行赋值

//0<<9*2表示 0000 0000 0000 0000 0000 0000 0000 0000,GPIOB->MODER|=0<<9*2就是把17 18位地址置为00,对应寄存器也就是PB9输入模式

//同理PB9输出模式也是这样,首先将17 18位清零,然后将这两位写入01

//1<<9*2表示  0000 0000 0000 0000 0000 0000 0000 0001
              0000 0000 0000 0001 0000 0000 0000 0000 此时根据寄存器,也就表示PB9输出模式

//寄存器定义
//00:输入
//01:通用输出
//10:复用功能
//11:模拟功能
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。