08_stm32位带操作

一:位带操作介绍

1.   位带操作

  在学习 51 单片机的时候就使用过位操作, 通过关键字 sbit 对单片机 IO 口进行位定义。 但是 STM32 没有这样的关键字, 而是通过访问位带别名区来实现。即将每个比特位膨胀成一个 32 位字, 当访问这些字的时候就达到了访问比特的目的。 比方说 BSRR 寄存器有 32 个位, 那么可以映射到 32 个地址上, 当我们去访问这 32 个地址就达到访问 32 个比特的目的。
  STM32F1 中有两个区域支持位带操作, 一个是 SRAM 区的最低 1MB 范围, 一个是片内外设区的最低 1MB 范围(APB1、 APB2、 AHB 外设) 。 如图

  

  SRAM 的最低 1MB 区域, 地址范围是 0X2000 0000-0X200FFFFF。 片内外设最低 1MB 区域, 地址范围是 0X4000 0000-0X400F FFFF, 在这个地址范围内包括了 APB1、 APB2、 AHB 总线上所有的外设寄存器。
  在 SRAM 区中还有 32MB 空间, 其地址范围是 0X2200 0000-0X23FF FFFF, 它是 SRAM 的 1MB 位带区膨胀后的位带别名区, 前面已经说过位带操作, 要实现位操作即将每一位膨胀成一个 32 位的字, 因此 SRAM 的 1MB 位带区就膨胀为 32MB的位带别名区, 通过访问位带别名区就可以实现访问位带中每一位的目的。片内外设区的 32MB 的空间也是一样的原理。 片内外设区的 32MB 地址范围是0X4200 0000-0X43FF FFFF。
  通常我们使用位带操作都是在外设区, 在外设区中应用比较多的也就是GPIO 外设, SRAM 区内很少使用位操作。

2.   位带区与位带别名区的转换

  前面已经说过, 位带操作就是将位带区中的每一位膨胀成位带别名区中的一个 32 位的字, 通过访问位带别名区中的字就实现了访问位带区中位的目的。 因此我们就可以使用指针来访问位带别名区的地址, 从而实现访问位带区内位的目的。 那么位带别名区与位带区地址是如何转换的, 我们下面就来介绍下。
(1) 外设位带别名区地址
  对于片上外设位带区的某个比特, 记它所在字节的地址为 A,位序号为 n, n值的范围是 0-7, 则该比特在别名区的地址为:
    AliasAddr=0x42000000+ (A-0x40000000)*8*4 +n*4
  0x42000000 是外设位带别名区的起始地址, 0x40000000 是外设位带区的起始地址, (A-0x40000000) 表示该比特前面有多少个字节, 一个字节有 8 位, 所以*8, 一个位膨胀后是 4 个字节, 所以*4, n 表示该比特在 A 地址的序号, 因为一个位经过膨胀后是四个字节, 所以也*4。
(2) SRAM 位带别名区地址
  对于 SRAM 位带区的某个比特, 记它所在字节的地址为 A,位序号为 n, n 值的范围是 0-7, 则该比特在别名区的地址为:
    AliasAddr= =0x22000000+ (A-0x20000000)*8*4 +n*4
  0x22000000 是 SRAM 位带别名区的起始地址, 0x20000000 是 SRAM 位带区的起始地址, (A-0x20000000) 表示该比特前面有多少个字节, 一个字节有 8 位,所以*8, 一个位膨胀后是 4 个字节, 所以*4, n 表示该比特在 A 地址的序号,因为一个位经过膨胀后是四个字节, 所以也*4。

  上面我们已经把外设位带别名区地址和 SRAM 位带别名区地址使用公式表示出来, 为了操作方便, 我们将这两个公式进行合并, 通过一个宏来定义, 并把位带地址和位序号作为这个宏定义的参数。 公式如下:
    #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))
  addr & 0xF0000000 是为了区分我们操作的是 SRAM 还是外设, 实际上就是获取最高位的值是 4 还是 2。 如果操作的是外设, 那么 addr & 0xF0000000 结果就是 0x40000000, 后面+0x2000000 就等于 0X42000000, 0X42000000 是外设别名区的起始地址。 如果操作的是 SRAM, 那么 addr & 0xF0000000 结果就是0x20000000, 后面+0x2000000 就等于 0X22000000, 0X22000000 是 SRAM 别名区的起始地址。

 addr & 0x000FFFFF 屏 蔽 了 高 三 位 , 相 当 于 减 去 0X20000000 或 者0X40000000, 屏蔽高三位是因为 SRAM 和外设的位带区最高地址是 0X200F FFFF和 0X400F FFFF, SRAM 或者外设位带区上任意地址减去其对应的起始地址, 总是低 5 位 有 效 , 所 以 这 里 屏 蔽 高 3 位 就 相 当 于 减 去 了 0X20000000 或 者0X40000000。 <<5 相当于*8*4, <<2 相当于*4, 其作用在前面已经分析过。最后就可以通过指针形式来操作这些位带别名区地址, 实现位带区对应位的操作。 代码如下:

  最后就可以通过指针形式来操作这些位带别名区地址, 实现位带区对应位的操作。 代码如下:
  //把 addr 地址强制转换为 unsigned long 类型的指针
    #define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
  //把位带别名区内地址转换为指针 , 获取地址内的数据
    #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
  这里说明下 volatile 关键字, volatile 提醒编译器它后面所定义的变量随时都有可能改变, 因此编译后的程序每次需要存储或读取这个变量的时候, 都会直接从变量地址中读取数据。 如果没有 volatile 关键字, 则编译器可能优化读取和存储, 可能暂时使用寄存器中的值, 如果这个变量由别的程序更新了的话,将出现不一致的现象。 更详细的内容大家可以百度查找。

3. 位带操作优点

  在 STM32 应用程序开发中虽然可以使用库函数操作外设, 但如果加上位操作就如虎添翼。 想想 51 单片机内位操作的方便, 就可以理解为什么要对 STM32 使用位操作。 STM32 位操作优点非常多, 我们这里就列举几个突出的:
  (1) 对于控制 GPIO 的输入和输出非常简单
  (2) 操作串行接口芯片非常方便(DS1302、 74HC595 等) , 如果采用库函数的话, 那么这个时序编写就非常不方便。
  (3) 代码简洁, 阅读方便

二:GPIO位带操作

  我们已经知道 STM32F1 支持的位带操作区有两个, 其中应用最多的还是外设位带区, 在外设位带区中包含了 APB1、 APB2 还有 AHB 总线上的所有外设寄存器,使用位带操作应用最多的外设还属 GPIO, 通过位带操作控制 STM32 引脚输入与输出, 因此我们就以 GPIO 中 IDR 和 ODR 这两个寄存器的位操作进行讲解。根据《STM32F10x 中文参考手册》 对应的 GPIO 寄存器章节中可以知道, IDR和 ODR 寄存器相对于 GPIO 基地址的偏移量是 8 和 12。 所以可以通过宏定义实现这两个寄存器的地址映射, 具体代码如下:

  //IO 口地址映射
#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
  从上述代码中可以看到有 GPIOx_BASE, 这个也是一个宏, 里面封装的是相应 GPIO 端口的基地址, 在库函数中有定义。获取寄存器的地址以后, 就可以采用位操作的方法来操作 GPIO 的输入和输出, 代码如下:

//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) //输入
  上述代码中我们已经将 STM32F1 芯片的所有端口都进行了位定义封装, 假如要使用 PC0 管脚进行输出, 那么就可以调用 PCout(n)宏, n 值即为 0。 假如使用的是 PC0 管脚作为输入, 那么就可以调用 PCin(n)宏, n 值即为 0。 其他端口调用方法类似。

三:软件设计

  前面我们已经将 GPIO 的 ODR 和 IDR 寄存器进行了位操作, 下面我们把它应用到我们程序中去, 通过位操作实现 D1 指示灯闪烁, D1 指示灯的硬件电路前面已经介绍过, 这里就不重复。
  我们复制上一次的工程文件夹, 重新命名为“ LED 闪烁(使用位带操作) ”,在其目录下新建一个 Public 文件夹, 用于存放 STM32F1 的公共应用程序文件,比如本章所要实现的位操作功能, 后面等到我们讲解 Systick 定时器和串口的时候也会把它们的驱动程序文件放入在此文件夹内, 所以如果要对 STM32F1 系列芯片进行程序开发, 可以复制我们这个文件夹到你的工程中, 实现位操作、 Systick精确延时和串口功能。 打开工程程序, 新建 system.c 和 system.h 文件, 将其存放在 Public 文件夹内, 并在 KEIL5 内添加其头文件路径system.c 内未写任何代码, 只是将其头文件调用进来, 方便工程中其他源文件调用, system.h 内把 GPIO 的 IDR 和 ODR 寄存器位操作进行了封装, 具体代码如下:
  #ifndef _system_H
  #define _system_H
  #include "stm32f10x.h"
//位带操作,实现 51 类似的 GPIO 控制功能
//具体实现思想,参考<<CM3 权威指南>>第五章(87 页~92 页).
//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+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
//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) //输入
  #endif
  从 system.h 内的代码可以看到, 内部还调用了 stm32f10x.h 文件, 因此在main.c 文件中可以直接调用 system.h, 既可包含 stm32f10x.h 内容也能包含位操作功能。要使用位操作来控制 LED, 需要在 led.h 文件中调用 system.h, 由于我们开发板上有 8 个 LED, 为了清楚操作的是哪个 LED, 我们根据其 IO 管脚进行宏定义,
代码如下:
    #define led1 PCout(0) //D1 指示灯连接的是 PC0 管脚
    #define led2 PCout(1) //D2 指示灯连接的是 PC1 管脚
    #define led3 PCout(2) //D3 指示灯连接的是 PC2 管脚
  其他的我们就不逐个定义, 如果要控制 PC0 管脚输出一个高电平, 直接就可以使用 led1=1 来表示, 这就和 51 单片机管脚操作方法一样。最后看下主函数, 主函数实现的功能比较简单, 首先对 LED 端口时钟及管脚模式配置进行初始化, 然后通过位操作不断取反 PC0 管脚电平, 实现 LED 闪烁效果, 里面还用到了一个 delay 延时, 这个延时函数只是一个大约的延时, 要实现精确延时, 在后面章节中我们会讲解到。  

int main()
{
  LED_Init();
  while(1)
  {
    led1=!led1; //D1 状态取反
    delay(6000000);
  }
}

原文地址:https://www.cnblogs.com/eokey/p/11989233.html

时间: 2024-09-30 13:29:44

08_stm32位带操作的相关文章

STM32位带操作总结---浅显易懂

正在准备做毕业设计,配置LED_Config()的时候,又看到了位带操作的宏定义,我又嘀咕了,什么是位带操作,一年前在使用位带操作的时候,就查阅过好多资料,Core-M3也看过,但是对于博主这种"低能儿"来说,你不把它说的白一点,就是感觉理解的不够透彻,于是今天又一次,查阅了各种手册,也算是基本弄懂了,鉴于博主的个人特点,所以本人的介绍也会十分浅显易懂,希望能帮到各位! 首先,抛砖引玉,来两个问题: 1)为什么STM32里面会有位带操作? 2)STM32里面的位带操作是什么意思? 我也

STM32F030系列实现仿位带操作

1.闲言 最近开发的时候,用到了STM32F030F4P6型号的单片机,它只有20个引脚,价格非常便宜,但是功能齐全:定时器.外部中断.串口.IIC.SPI.DMA和WWDG等等,应用尽有,非常适合用来做小设备.可是有个问题是,它是Cortex-M0内核的,不像M3,M4内核一样,可以支持位带操作(就是一位一位地操作,像80C51单片机一样),这就给程序移植或者开发带来了一点点小麻烦,因此我就利用C语言结构的位段操作,实现了个访位带操作,只是在效率可能会稍逊于真正的位带操作,但是代码上可以兼容,

STM32之GPIO端口位带操作

#ifndef __SYS_H #define __SYS_H #include "stm32f10x.h" //位带操作 //把“位带地址+位序号”转换别名地址宏 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) //把该地址转换成一个指针 #define MEM_ADDR(addr) *((volatil

Duanxx的STM32学习:GPIO的位带操作

支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写.在 CM3中,有两个区中实现了位带.其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB 范围.这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的"位带别名区",位带别名区把每个比特膨胀成一个 32 位的字.当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的. 关于位带操作的博客说明有很多,这里主要将代码贴出来,并做详细的注释 /** ************

位带操作

1.位带区: 支持位带操作的地址区 2.位带别名:对别名地址的访问最终作用在位带区的访问(中途有地址映射) 3. 4. 5. 对于片上外设,映射关系参照上图关系修改即可. 6.举例: 建立一个把“位带地址+位序号”换成别名地址的宏,再建立一个把别名地址转换成指针类型的宏. 使用位带功能时,要访问的变量必须用volatile 来定义.(指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.)

第13章 GPIO—位带操作

第13章     GPIO-位带操作 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege 本章参考资料:<STM32F4xx 中文参考手册>存储器和总线构架章节.GPIO章节,<Cortex?-M4内核编程手册>2.2.5 Bit-banding.学习本章时,配套这些参考资料学习效果会更佳. 13.1 位带简介 位操作就是可以单独的对一个比特位读和写,这个在51单片机

玩转X-CTR100 | 位带操作-GPIO

更多塔克创新资讯欢迎登陆[塔克社区www.xtark.cn ][塔克博客www.cnblogs.com/xtark/ ] STM32F4位带概念,及位带的GPIO操作实践应用. 原理介绍 51单片机相信各位都用过,假设P1.1的IO口上挂了一个LED,那么你单独对LED的操作就是P1.1 = 0或P1.1 = 1,注意,是你可以单独的对P1端的第一个IO口进行操作,然而STM32是不允许这样做的,那么为了像51单片机一样能够单独的对某个端的某一个IO单独操作,就引入了位带操作这样的概念,简单说就

STM32中的位带(bit-band)操作

支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写.在 CM3 中,有两个区中实现了位带.其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB范围.这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的"位带别名区",位带别名区把每个比特膨胀成一个 32 位的字.当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的.       位带操作的概念其实 30 年前就有了,那还是8051 单片机开创的先河,如今,CM3 将

STM32中的位带(bit-band)操作(转)

源:STM32中的位带(bit-band)操作 支持了位带操作后,可以使用普通的加载/存储指令来对单一的比特进行读写.在 CM3 中,有两个区中实现了位带.其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB范围.这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字.当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的.       位带操作的概念其实 30 年前就有了,那还是805