STM32F103ZET6位带操作

目录

1.位带操作

2.位带区与位带别名区地址转换

2.1外设位带别名区地址

2.2SRAM 位带别名区地址

2.3宏定义外设位带和 SRAM 位带

3.位带操作的优点

4.GPIO 位带操作

5.实验设计-LED闪烁

6.实验现象


1.位带操作

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

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

2.位带区与位带别名区地址转换

2.1外设位带别名区地址

        对于片上外设位带区的某个比特,记它所在字节的地址为 A,位序号为 n,n 值的范围是 0-7,则该比特在别名区的地址为:

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

2.2SRAM 位带别名区地址

        对于 SRAM 位带区的某个比特,记它所在字节的地址为 A,位序号为 n,n 值 的范围是 0-7,则该比特在别名区的地址为:

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

2.3宏定义外设位带和 SRAM 位带

        上面我们已经把外设位带别名区地址和 SRAM 位带别名区地址使用公式表示出来,为了操作方便,我们将这两个公式进行合并,通过一个宏来定义,并把位带地址和位序号作为这个宏定义的参数。

公式如下

#define BITBAND(addr, bitnum)

((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))

(1)addr & 0xF0000000

为了区分我们操作的是 SRAM 还是外设,实际上就是 获取最高位的值是 4 还是 2。如果操作的是外设,那么 addr & 0xF0000000 结果 就是 0x40000000,后面+0x2000000 就等于 0X42000000,0X42000000 是外设别 名区的起始地址。如果操作的是 SRAM,那么 addr & 0xF0000000 结果就是 0x20000000,后面+0x2000000 就等于 0X22000000,0X22000000 是 SRAM 别名区 的起始地址。

(2)addr & 0x000FFFFF

屏 蔽 了 高 三 位 , 相 当 于 减 去 0X20000000 或 者 0X40000000,屏蔽高三位是因为 SRAM 和外设的位带区最高地址是 0X200F FFFF 和 0X400F FFFF,SRAM 或者外设位带区上任意地址减去其对应的起始地址,总是 低 5 位 有 效 , 所 以 这 里 屏 蔽 高 3 位 就 相 当 于 减 去 了 0X20000000 或 者 0X40000000。

(3)<<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.位带操作的优点

(1)对于控制 GPIO 的输入和输出非常简单。

(2)操作串行接口芯片非常方便(DS1302、74HC595 等),如果采用库函数的话,那么这个时序编写就非常不方便。

(3)代码简洁,阅读方便

4.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。其他端口调用方法类似。

5.实验设计-LED闪烁

system.h代码如下

#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

main.c代码如下

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

6.实验现象

LED模块中的D1闪烁

 已经学会了如何进行位带操作,那么试一下如何实现LED流水灯效果吧!

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值