前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >stm32位带操作

stm32位带操作

作者头像
飞哥
发布2020-07-10 10:20:42
7410
发布2020-07-10 10:20:42
举报

在51单片机里,如果要对某一位进行操作,可以直接使用关键字sbit,但是在stm32里面没有这样的关键字,而是通过访问位带别名区来实现。

在stm32里,有两个地方实现了位带,一个是SRAM区的最低1MB空间,地址范围为0x20000000~0x20100000(在block1的起始位置处),另一个是外设区最低1MB空间,地址范围为0x40000000~0x40100000(在block2的起始位置处)。这些位带区除了像正常的RAM一样操作外,它们还有自己的位带别名区,位带别名区把这1MB空间的每一位膨胀成32位,访问位带别名区的这些字,就可以达到访问位带区某个位的目的。

将位带区的每个位膨胀成32位(即4个字节)之后,那么每个位都与位带别名区的一个地址相映射。至于为什么访问位带别名区就可以操作位,这是由内核里的硬件设计决定的,位带操作会增加硬件成本,比如在更便宜的stm32的F0系列里就没有位带操作。

位带操作的好处有:

①对于控制GPIO的输入输出非常简单;

②操作串行接口芯片非常方便,如果采用库函数的话,时序的编写将非常不方便;

③代码简洁,阅读方便。

另外,位带区的一个位经过膨胀之后,虽然变大到4个字节,但是还是最低位有效,有人会问,这不是浪费空间吗?要知道stm32的系统总线是32位的,按照4字节访问的时候是最快的,所以膨胀成4个字节来访问是最高效的。并且32MB的位带别名区刚好是位于原来保留的地址范围,不会与其他寄存器地址重合。

接下来对这两个位带区进行介绍。

位带区介绍

1、外设位带区

外设位带区的地址为0x40000000~0x40100000,大小为1MB,这1MB的大小包含了片上外设的全部寄存器,膨胀后的位带别名区地址为0x42000000~0x43FFFFFF。虽然说全部寄存器都可以实现位操作,但在实际中不会这么做,在需要频繁地操作IO口时,可以考虑把IO相关的寄存器实现位操作。

2、SRAM位带区

SRAM位带区的地址为0x20000000~0x20100000,大小为1MB,经过膨胀之后的位带别名区地址为0x22000000~0x23FFFFFF,操作SRAM的位用的比较少。

二、地址转换

地址转换主要是把握“位带区的一个位膨胀为32位”。我们得把位带区的每个位映射一个地址。所以也就是从位带区的“位”→别名区的字节(地址)。

对于片上外设位带区的某个位,记它所在字节的地址为A,位序号为n(0~7),则该位在别名区的地址为:

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

同理,对于SRAM位带区的某个位,类似为

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

这也很好理解,就是将位带区的位*4即得到别名区的地址(因为32位就是4个字节)。而位带区的位包含两部分,一部分是地址(字节)*8,另一部分就是n,也就是

(字节数*8+位数n)*4=别名区地址;

其中字节数就是某寄存器的地址减去起始地址。

为了将上述两个公式统一,可以采用下面的操作

#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))

addr & 0xF0000000取出最高位是4还是2,用来区分是外设还是SRAM

addr &0xFFFFF把高三位屏蔽掉了,保留低五位,相当于是减去了起始地址。因为不管是外设区还是SRAM区,减去起始地址之后,都在只剩下低五位了。左移5位即*32,左移两位即*4。这样就将两个公式统一起来了。这一部分可以用具体的例子来验证一下。

三、GPIO位带操作

接下来具体说一下如何用位带操作来操作GPIO中的ODR寄存器。我们知道在每个GPIO端口都有一个ODR寄存器,它的低16位控制了对应管脚的输出。比如GPIOC的ODR寄存器的第0位控制了PC0的输出。那么该如何操作具体的某一位呢?

首先,定义好寄存器的地址。

#define GPIOC_ODR_Addr (GPIOC_BASE+12)//偏移量为12

然后①要把位带区的地址映射到别名区,②并且将地址转化为指针,为书写方便把上述两个步骤统一起来,实现代码如下

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

所以,如果要使PC0输出,可以这样写

#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n)

有了上面的宏定义,可以非常方便的控制每个引脚的输出,当然,如果要操作其他寄存器,只要用类似的方法进行封装就好了。

例:用位带操作实现流水灯闪烁

int main()

{

int k;

LED_Init();

while(1)

{

for (k=0;k<8;k++)

{

if(k==0)

{

PCout(k)=0;

PCout(k+7)=1;

delay(6000000);

}

else

{

PCout(k)=0;

PCout(k-1)=1;

delay(6000000);

}

}

}

}

需要注意的是点亮每个灯时要把前一个灯熄灭。

总结:位带操作就是把位带区的每个位膨胀为32位,因为stm32不能对位进行读写,只能对字或者半字操作,并且执行32位效率会更高。并不是所有的单片机都能进行位带操作,这是由内核设计决定的。

要操作哪个寄存器的某个位,只要定义好它的地址,再经过地址转化,就可以用指针的方式访问了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子技术研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档