第16章.GPIO位带操作

目录

0. 《STM32单片机自学教程》专栏

16.1 位带基本概念 

16.2 STM32位带区 

 16.2.1  外设位带区

16.2.2  SRAM 位带区

16.2.3  位带区和位带别名区地址转换

16.2.3.1 外设位带别名区地址

16.2.3.2 SRAM 位带别名区地址: 

16.2.3.3 位带区计算宏定义

16.3 位带操作示例


0. 《STM32单片机自学教程》专栏

        本文作为专栏《STM32单片机自学教程》专栏其中的一部分,返回专栏总纲,阅读所有文章,点击Link:  

STM32单片机自学教程-[目录总纲]_stm32 学习-CSDN博客     

16.1 位带基本概念 

        在STM32中,位带(Bit-Banding)是一种特殊的内存映射技术,它允许将特定的位(Bit)与特定的内存地址绑定,从而实现对单个位的原子级操作。这种技术主要用于对GPIO端口、寄存器以及其他外设的单个位进行高效的读写操作,极大地提高了代码的可读性和执行效率。 

位带操作的基本概念

  1. 位带区(Bit-Band Region)
    • 位带区是指内存中的一块区域,用于存储位带操作的目标数据。在STM32中,支持位带操作的区域主要包括SRAM区的最低1MB范围和片内外设区的最低1MB范围。
    • 位带区的一个位在位带别名区会被“膨胀”成四个字节(即32位),这意味着每个位都被映射到了一个32位的地址空间上。
  2. 位带别名区(Bit-Band Alias Region)
    • 位带别名区是与位带区相对应的另一块内存区域,用于通过别名方式访问位带区的数据。在STM32中,SRAM区里的0x22000000-0x23FFFFFF地址段和外设区里0x42000000-0x43FFFFFF地址段都是位带别名区。
    • 通过操作位带别名区的地址,可以间接实现对位带区中单个位的读写操作。

        位操作就是可以单独的对一个比特位读和写,这个在51单片机中非常常见。51单片机中通过关键字sbit来实现位定义,STM32没有这样的关键字,而是通过访问位带别名区来实现。在STM32中,有两个地方实现了位带,一个是SRAM区的最低1MB空间,另一个是外设区最低1MB空间。这两个1MB的空间除了可以像正常的RAM一样操作外,他们还有自己的位带别名区,位带别名区把这1MB的空间的每一个位膨胀成一个32位的字,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。如图16.1-1所示。

图16.1-1 STM32位带示意图 

位带操作的优势

  1. 原子性:位带操作可以确保对单个位的读写是原子性的,即在任何时候只有一个CPU周期可以访问这个位,从而避免了多线程并发访问时可能出现的竞态条件和数据不一致性问题。
  2. 高效性:通过位带操作,可以直接对单个位进行读写,而不需要对整个字节或更大的数据单元进行操作,这大大提高了操作的效率。
  3. 可读性:使用位带操作可以使得代码更加简洁明了,提高了代码的可读性和可维护性。

16.2 STM32位带区 

 16.2.1  外设位带区

        外设外带区的地址为:0X40000000~0X40100000,大小为1MB,这1MB的大小在F103系列大/中/小容量型号的单片机中包含了片上外设的全部寄存器,这些寄存器的地址为:0X40000000~0X400233FF。外设位带区经过膨胀后的位带别名区地址为:0X42000000~0X43FFFFFF,这个地址仍然在Cortex-M3片上外设的地址空间中。在F103系列大/中小容量型号的单片机里面,0X40030000~0X4FFFFFFF属于保留地址,膨胀后的32MB位带别名区刚好就落到这个地址范围内,不会跟片上外设的其他寄存器地址重合。

        STM32的全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器比特位的效果,这比51单片机强大很多。因为51单片机里面并不是所有的寄存器都是可以比特位操作,有些寄存器还是得字节操作,比如SBUF。虽然说全部寄存器都可以实现比特操作,但我们在实际项目中并不会这么做,甚至不会这么做。有时候为了特定的项目需要,比如需要频繁的操作很多IO口,这个时候我们可以考虑把IO相关的寄存器实现比特操作。

16.2.2  SRAM 位带区

        SRAM 的位带区的地址为:0x2000 0000~0x2000 FFFF,经过膨胀后的位带别名区地址为:0X2200 0000~0X23FF FFFF,大小为32MB。操作SRAM 的比特位这个用得很少。 

16.2.3  位带区和位带别名区地址转换

        位带区的一个比特位经过膨胀之后,虽然变大到4个字节,但是还是LSB才有效。有人会问这不是浪费空间吗,要知道STM32的系统总线是32位的,按照4个字节访问的时候是最快的,所以膨胀成4个字节来访问是最高效的。

        我们可以通过指针的形式访问位带别名区地址从而达到操作位带区比特位的效果。下面我们简单介绍一下这两个地址如何转换。

16.2.3.1 外设位带别名区地址

        这个要结合前面图16.1-1来看更容易理解。对于片上外设位带区的某个比特,记它所在字节的地址为A,位序号为n(0<=n<=31)(n的范围根据具体寄存器能控制的位决定),则该比特在别名区的地址为:

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

        0X42000000是外设位带别名区的起始地址,0x40000000是外设位带区的起始地址,(A-0x40000000)表示该比特前面有多少个字节,一个字节有8位,所以*8,一个位膨胀后是4个字节,所以*4,n表示该比特在A地址的序号,因为一个位经过膨胀后是四个字节,所以也*4。

16.2.3.2 SRAM 位带别名区地址: 

        对于SRAM位带区的某个比特,记它所在字节的地址为A,位序号为n(0<=n<=31)(n的范围根据具体寄存器能控制的位决定),则该比特在别名区的地址为:

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

        这个计算思路和外设位带别名区一样,区别只是基地址的不同。 

16.2.3.3 位带区计算宏定义

        在STM32中使用位带操作通常需要通过宏定义来实现。这些宏定义会将位带别名区的地址与位带区中的位进行映射,并提供方便的读写接口。代码及解释如下:

// 把“位带地址+位序号”转换成别名地址的宏
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2)) 

         代码解释:

        前面2个公式可能还比较好理解,换成位操作宏定义后,可能很多人就看的很蒙圈。解释如下,可以结合下面的图16.2-1,可能就比较好理解了。单片机更擅长做位移操作。

图16.2-1 位带计算示意图 

        我们以SRAM为例addr&0xF0000000,取地址的高4位,看看是2还是4,用于区分SRAM和外设地址,如果是2,+0x02000000则=0X22000000,即是SRAM,如果是4,+0x02000000则=0x42000000,即是外设;

        addr&0x000FFFFF,屏蔽掉高三位,相当于减去0X20000000 或者0X40000000,为什么是屏蔽高三位?因为外设的最高地址是:0X20100000,跟起始地址0X20000000 ,真正有用的地址就是后五位,相减的时候,总是低5 位才有效,所以就把高三位屏蔽掉来达到减去起始地址的效果,具体屏蔽掉多少位跟最高地址有关。<<5 相当于*8*4,<<2 相当于*4,虽然没有直接*32或者*4形象,但单片机更擅长做位移操作。

        最后我们就可以通过指针的形式操作这些位带别名区地址,最终实现位带区的比特位操作。

// 把一个地址转换成一个指针
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 

// 把位带别名区地址转换成指针
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))   

        addr是位带区的一个寄存器地址,bitnum是这个寄存器里的某一个bit位。BITBAND(addr, bitnum)这个宏定义计算得到了这个位在位带别名区的地址,MEM_ADDR(BITBAND(addr, bitnum))宏定义就是取位带别名区对应地址里映射的值。

16.3 位带操作示例

        外设的位带区,覆盖了全部的片上外设的寄存器,我们可以通过宏为每个寄存器的位
都定义一个位带别名地址,实现位操作。 我们就以最简单的一个案例看一下,LED接在PB1上,每隔1秒亮灭一次。我们只是为了将下用法,所以就全部写在main函数中了。我们从前面讲解知识知道,我们直接操作ODR寄存器即可。从《参考手册》中我们可以知道 ODR 寄存器对应 GPIO 基址的偏移是12。delay函数直接用上一章示例中的即可。main函数如下:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

// 把“位带地址+位序号”转换成别名地址的宏 
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 0x000FFFFF)<<5)+(bitnum<<2))
// 把一个地址转换成一个指针 
#define MEM_ADDR(addr)  *((volatile unsigned long  *)(addr)) 
// 把位带别名区地址转换成指针 
#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum)) 
// GPIO ODR  寄存器地址映射
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
// 单独操作 GPIO 的某一个 IO 口,n(0,1,2...16),n 表示具体是哪一个 IO 口 
#define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)

//LED 初始化函数
void LED_Init(void);
int main(void)
{	
	/* LED端口初始化 */
	LED_Init();
	
	/* LED每隔一秒亮灭 */
	while(1)                            
	{	
		//点亮
		PBout(1)=0;
		Delay_ms(1000);
		//熄灭
		PBout(1)=1;
		Delay_ms(1000);
	}
}

//LED端口初始化函数
void LED_Init(void)
{		
	/*定义一个GPIO_InitTypeDef类型的结构体*/
	GPIO_InitTypeDef GPIO_InitStructure;
	/*开启LED相关的GPIO外设时钟*/

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	/*选择要控制的GPIO引脚*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;	
	/*设置引脚模式为通用推挽输出*/

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;  

	/*设置引脚速率为50MHz */   
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 

	/*调用库函数,初始化GPIO*/
	GPIO_Init(GPIOB, &GPIO_InitStructure);		

}

资源已经上传:

​​​​​​​https://download.csdn.net/download/weixin_42109443/89623715?spm=1001.2014.3001.5503

参考资料:
        【1】哔站江协科技STM32入门教程

        【2】《STM32单片机原理与项目实战》刘龙、高照玲、田华著

        【3】《ARM Cortex-M3嵌入式原理及应用》黄可亚著

        【4】《STM32嵌入式微控制器快速上手》陈志旺著

        【5】《STM32单片机应用与全案例实践》沈红卫等著

        【6】《野火STM32开发指南》

        【7】《正点原子STM32开发指南》   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值