单片机
返回首页

STM32位带操作详细介绍

2021-09-30 来源:eefocus

前言

//在STM32中,如果我们要使PB0端口输出低电平,可以用如下语句

GPIOB->ODR |= 0<<0;

//在51单片机中我们是这样的

P0 = 0xff;   //总线操作

//还有一种方法

sbit LED1 = P0^0;

LED1 = 0;//位操作


在STM32中能不能实现位操作呢,当然是可以的,这种方法我们叫做位带操作。位操作就是可以单独的对一个比特位读和写。


1.位带简介

在STM32中,有两个地方实现了位带,一个是 SRAM 区的最低 1MB 空间,另一个是外设区最低 1MB 空间。这两个 1MB 的空间除了可以像正常的 RAM 一样操作外,他们还有自己的位带别名区,位带别名区把这 1MB 的空间的每一个位膨胀成一个 32 位的字,当访问位带别名区的这些字时,就可以达到访问位带区某个比特位的目的。


比如位带区:外设区,这其中的一个位,可以在位带别名区重新找到4个字节的地址,也就是32位,来重新命名,而这32位,只有最低位有效。所以空间利用不是很充足,但是提供了方便。


例子:

PB的ODR寄存器地址为0x40010C0C,假设我们就操作PB0,即操作ODR寄存器第0位,那么这个位新的地址Addr= =0x42000000+ (0x40010C0C-0x40000000)84 +0*4。


给这个地址命名为PBout(0);


2.计算公式

外设区:

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


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


SRAM区:

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


公式分析同上


统一公式

为了方便操作,我们可以把这两个公式合并成一个公式,把“位带地址+位序号”转

换成别名区地址统一成一个宏。


 // 把“位带地址+位序号”转换成别名地址的宏

2 #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 别名区的起始地址。

addr & 0x00FFFFFF 屏蔽了高三位,相当于减去 0X20000000 或者 0X40000000,但是为什么是屏蔽高三位?因为外设的最高地址是: 0X2010 0000, 跟起始地址 0X20000000 相减的时候,总是低 5 位才有效,所以干脆就把高三位屏蔽掉来达到减去起始地址的效果,具体屏蔽掉多少位跟最高地址有关。 SRAM 同理分析即可。 <<5 相当于84, <<2 相当于*4,这两个我们在上面分析过。


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


// 把一个地址转换成一个指针

#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))

// 把位带别名区地址转换成指针

#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))


3.GPIO位带实现

// 把“位带地址+位序号”转换成别名地址的宏

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x02000000+((addr & 0x00FFFFFF)<<5)+(bitnum<<2))


// 把一个地址转换成一个指针

#define MEM_ADDR(addr)           *((unsigned long *)(addr))

// 把位带别名区地址转换成指针

#define BIT_ADDR(addr, bitnum)   MEM_ADDR(BITBAND(addr, bitnum))


//GPIO ODR 和 IDR 寄存器地址映射

#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 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


//单独操作 GPIO 的某一个 IO 口, n(0,1,2...16),n 表示具体是哪一个 IO 口

#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) //输入


这样就可以使用PBout(0),相比之下还是很方便的。

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • CCD图像传感器在微光电视系统中的应用

  • 如何利用ESP8266制作一个简单的四轴飞行器

  • 非常简单的150W功放电路图

  • 基于IC555的可变PWM振荡器电路

  • 优化电路板布局的简单方法

  • 分享一个电网倾角计电路

    相关电子头条文章