stm32位带操作及内存地址

请添加图片描述

注:以下均属个人片面之理解,有误之处请留言,我很愿意将这篇文章完善的更好

STM32地址

基地址、总线地址、寄存器地址是什么

基地址以及偏移地址

这是stm32的系统结构图,只需要看一下红线框出来的地方即可
图一从下图可以看到,总线分为 4 大块,每块都有一个起始地址,这个起始地址就是基地址,然后到下一块起始地址的时候就会和前一块地址出现偏差,这个差值就是偏移量,即相对基地址的偏移量。
在这里插入图片描述
请添加图片描述

寄存器地址

在这里插入图片描述
拿GPIOC口控制寄存器举例子,由上面的图可知GPIOC的基地址是0x4001 1000
我们IO口的7个控制寄存器的地址如下图所示
在这里插入图片描述

他们之间有什么样的联系(重点)

在这里插入图片描述
每个寄存器是32位相对的偏移为0x04,这是因为stm32是字节编址
即对于内存的最小操作也是字节操作
即对于内存的最小操作也是字节操作
即对于内存的最小操作也是字节操作

STM32位带操作

What 位带操作是什么

如果你用过51单片机你就会知道在51单片机中我们可以直接操作单个IO口来控制外设
比如我们可以通过改变固定的IO口的电平来控制单个led的亮灭
这里我先附一张led的原理图以便后面理解
在这里插入图片描述
这个是51单片机led模块的原理图,可以看到模块对应的IO口是P2口,那么我们可以通过整个P2口进行操作来进行控制led的亮灭(通过控制P2口对应的寄存器)

P2=0x01; //0000 0001

我们这么写对应的就是P2^1口对应的led点亮
但是我们还有另外一种改变P2^1口电平的方法,那就是位待操作

sbit led=P2^1;	   //将对应的IO口定义为一个变量
led=1;             //对这个变量进行操作就是对P2^1口进行操作

看到这里我相信大多数同学应该懂了什么是位带操作,其实简而言之就是单独的对一个IO口进行操作

Why 为什么要使用位带操作

如果想知道IO口具体的寄存器控制请看链接: link.
我们在编程时会遇到一些IO口相互影响的问题
例如链接: link.
虽然有些时候可以通过或与操作来解决,但是也难免会有一些解决不了的时候,所以我们这个时候就可以通过位待操作来操作IO口对应的寄存器的每个位。

How 位带操作是如何实现的

看完上面的地址联系应该可以知道为什么stm32不能直接通过地址来进行位待操作
那我们要怎么实现位带操作呢
首先并不是所有的内核都支持位带操作的,在M3的内核中,有两个区支持位带。
第一个是 SRAM 区的最低 1MB 范围
SRAM区地址为 0x2000 0000 ~ 0x200F FFFF 映射地址
(位带区地址)在SRAM地址的基地址下加0x0200 0000
第二个则是片内外设区的最低 1MB范围
0x4000 0000 ~ 0x400F FFFF
(位带区地址)在片内外设区地址的基地址下加0x0200 0000
位待操作可以理解成将地址中的每个字节(内存区)都映射(膨胀)成一个32位的字(映射区)
那么我们通过改变映射区的32位的字即可改变内存区中这个字节里面的位
这么说大家可能不太理解,没关系直接上图,这里拿SRAM区举个例子
请添加图片描述

拿0x2000 0000这个字节来举例子
从0x2000 0000到0x2000 0001是一个字节8个比特
我们映射(膨胀)到位待区是
从0x2200 0000到0x2200 0100这里是32个字节 32*8个比特
显而易见内存大小扩大了32倍
即内存地址的每个位都对应映射区的4个字节
从0x2000 0000开始 后面的两个字节转化为二进制

位数位待区地址后两位转换成二进制
第一个位0x2200 00000000 0000
第二个位0x2200 00040000 0100
第三个位0x2200 00080000 1000
第四个位0x2200 000C0000 1100
第五个位0x2200 00100001 0000
第六个位0x2200 00140001 0100
第七个位0x2200 00180001 1000
第八个位0x2200 001C0001 1100

可以看出每次变化为4个字节 4*8 == 32个字节
所以我们只需操作位待区的32位内存地址即可改变原内存中的位

代码

代码解读

这里直接拿GPIOB.5 LED0 进行举例

#define PERIPH_BASE           0x40000000UL 
//宏定义基地址为 0x4000 0000
#define APB2PERIPH_BASE   
 (PERIPH_BASE + 0x00010000UL)
 //宏定义APB2PERIPH_BASE (APB2的基地址)为总线基地址加偏移地址 
 #define GPIOB_BASE            (APB2PERIPH_BASE + 0x00000C00UL)
 //宏定义GPIOB的基地址为APB2总线的基地址加相对APB2总线的偏移地址
#define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C
//宏定义GPIOB->ODR寄存器的基地址为GPIOB的基地址加ODR寄存器相对GPIOB基地址的偏移地址

至此为止是用来找到GPIOB->ODR寄存器的基地址
如果有不明白的可以看上面地址关系

基地址映射

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
//将地址映射到位待区

此处addr为寄存器地址
(addr & 0xF0000000) //保留基地址
0x4001 0C0C& 0xF000 0000 = 0xF000 0000
+0x200 0000 = 0xF200 0000 (基地址的映射完成)

偏移地址映射

+((addr &0xFFFFF)<<5)
0x4001 0C0C& 0xF FFFF= 0x0001 0C0C //将偏移地址放大32倍变成了位待区中的偏移地址
再加上基地址的映射地址就是
0xF200 0000 + 0x0001 0C0C = 0xF201 0C0C(寄存器首地址对应的位待区地址)
这时候我们可以展开后面两位
0C ---- 0000 1100

寄存器的位数16进制二进制
00C0000 1100
1100001 0000
2140001 0100
3180001 1000
41C0001 1100
5200010 0000

(1)第一种理解(二进制)
通过二进制我们可以发现

寄存器的位数位待区中对应的地址(二进制)
0寄存器首地址对应的位待区地址
1寄存器首地址对应的位待区地址+0100
2寄存器首地址对应的位待区地址+1000
3寄存器首地址对应的位待区地址+1100
4寄存器首地址对应的位待区地址+0001 0000
5寄存器首地址对应的位待区地址+0001 0100

可能到这里你还没有发现什么
可以把上面加的地址右移两位试一下
0000 0001 -->1
0000 0010 -->2
0000 0011 -->3
0000 0100 -->4
0000 0101 -->5
所以我们只需要将需要操作的IO口对应的数字左移两位然后再加上寄存器地址对应的位待区地址即可
即**(bitnum<<2)**
(2)第二种理解(十六进制)
(bitnum<<2) 将IO对应的数字左移两位即放大2^2=4倍
所以 0xF201 0C0C(寄存器地址对应的位待区地址)+ bitnum<<2

寄存器的位数移位后对应的位待区地址
000xF201 0C0C
10x040xF201 0C0C + 0x04 = 0xF201 0C10
20x080xF201 0C0C +0x08 =0xF201 0C14
30x0C0xF201 0C0C +0x0C =0xF201 0C18
40x100xF201 0C0C + 0x10 =0xF201 0C1C
50x140xF201 0C0C + 0x14 =0xF201 0C20

例程

#ifndef __BIT_H
#define __BIT_H

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


#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 


#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
  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老尚嵌入式

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值