1、位带简介
位操作就是可以单独地对一个比特位进行读和写,在51单片机中通过sbit来实现位定义,STM32没有这样的关键字,而是通过访问位带别名区来实现。

在STM32中,有两个地方实现了位带,一个是SRAM区的最低1MB空间,另一个是外社区最低1MB空间。

这两个1MB的空间除了可以像正常的RAM一样操作外,它们还有自己的位带别名区,位带别名区把这1MB的空间的每一个位膨胀成一个32位的字访问位带别名区的这些字,就可以达到访问位带区某个位的目的。在这里插入图片描述2、外设位带区
外设位带区的地址为:0x40000000~0x40100000,大小为1MB,这1MB的大小在F103系列大、中、小容量型号的单片机中包含了片上外设的全部寄存器,这些寄存器的地址为0x40000000~0x40029FFF。
STM32的全部寄存器都可以通过访问位带别名区的方式来达到访问原始寄存器位的效果,这比51单片机强大很多。因为51单片机里面并不是所有的寄存器都可以进行位操作,有些寄存器还是得用字节操作,比如SBUF。

虽然说全部寄存器都可以实现位操作,但我们在实际项目中并不会这么做。有时候为了特定的项目需要,比如需要频繁地操作很多IO口,这个时候可以考虑把IO相关的寄存器实现位操作。

3、 SRAM位带区
SRAM位带区的地址为0x20000000~0x20100000,大小为1MB,经过膨胀后的位带别名区地址为0x22000000~0x23FFFFFF,大小为32MB。操作SRAM的位用得很少。

4、GPIO位带操作
外设的位带区覆盖了全部的片上外设的寄存器,可以通过宏为每个寄存器的位都定义一个位带别名区地址,从而实现位操作。但这个在实际项目中不是很现实,也很少人有会这么做,我们在这里仅仅演示GPIO中ODR和IDR这两个寄存器的位操作。
从手册中可以知道,ODR和IDR这两个寄存器对应GPIO基址的偏移是12和8,先实现这两个寄存器的地址映射,其中GPIOx_BASE在库函数里面有定义。

4.1.GPIO寄存器映射
代码清单3-1 GPIO ODR和IDR寄存器映射

    1 //GPIO ODR和IDR寄存器地址映射
    2 #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C
    3 #define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C
    4 #define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C
    5 #define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C
    6 #define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C
    7 #define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C
    8 #define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C
    9
   10 #define GPIOA_IDR_Addr    (GPIOA_BASE+8)  //0x40010808
   11 #define GPIOB_IDR_Addr    (GPIOB_BASE+8)  //0x40010C08
   12 #define GPIOC_IDR_Addr    (GPIOC_BASE+8)  //0x40011008
   13 #define GPIOD_IDR_Addr    (GPIOD_BASE+8)  //0x40011408
   14 #define GPIOE_IDR_Addr    (GPIOE_BASE+8)  //0x40011808
   15 #define GPIOF_IDR_Addr    (GPIOF_BASE+8)  //0x40011A08
   16 #define GPIOG_IDR_Addr    (GPIOG_BASE+8)  //0x40011E08

现在就可以用位操作的方法来控制GPIO的输入和输出了,其中宏参数n表示具体是哪一个IO口,n为0~16。这里面包含了端口A~G,并不是每个单片机型号都有这么多端口,使用这部分代码时,要查看单片机型号,如果是64pin的,则最多只能使用A~C端口。

4.2.GPIO位操作
代码清单1-2 GPIO输入输出位操作

   1 // 单独操作GPIO的某一个IO口,n(0~16),n表示具体是哪一个IO口
   2 #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出
   3 #define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入
   4
   5 #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出
   6 #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入
   7
   8 #define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出
   9 #define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入
  10
  11 #define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出
  12 #define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入
  13
  14 #define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出
  15 #define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入
  16
  17 #define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出
  18 #define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入
  19
  20 #define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出
  21 #define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入

5.main函数
该工程是直接从LED库函数操作移植过来的,有关LED GPIO初始化和软件延时等函数可直接用,修改的是:控制GPIO输出的部分改成了位操作。该实验中让IO口输出高低电平来控制LED的亮灭,负逻辑点亮。具体使用哪一个IO和点亮方式由硬件平台决定。
代码清单1-3 main函数

1 int main(void)
2 {
3     // 程序进入main函数之前,启动文件statup_stm32f10x_hd.s已经调用
4     // System Init()函数把系统时钟初始化成72MHz
5     // system Init()在system_stm32f10x.c中定义
6     // 如果用户想修改系统时钟,可自行编写程序修改
7
8     LED_GPIO_Config();
9
10   while ( 1 ) {
11          // PB0 = 0,点亮LED
12          PBout(0)= 0;
13          SOFT_Delay(0x0FFFFF);
14
15          // PB1 = 1,熄灭LED
16          PBout(0)= 1;
17          SOFT_Delay(0x0FFFFF);
18     }
19 }
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐