打印
[应用相关]

stm32位带操作介绍

[复制链接]
1126|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Vitality1|  楼主 | 2015-3-25 19:11 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在CM3中,有两个区中实现了位带。其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB 范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。
在位带区中,每个比特都映射到别名地址区的一个字(这是只有 LSB 有效的字)。当一个别名地址被访问时,会先把该地址变换成位带地址。对于读操作,读取位带地址中的一个字,再把需要的位右移到 LSB,并把 LSB 返回。对于写操作,把需要写的位左移至对应的位序号处,然后执行一个原子的“读-改-写”过程。
支持位带操作的两个内存区的范围是:
0x2000_0000‐0x200F_FFFF(SRAM 区中的最低1MB)
0x4000_0000‐0x400F_FFFF(片上外设区中的最低 1MB)

沙发
Vitality1|  楼主 | 2015-3-25 19:11 | 只看该作者
位带(Bit-band)操作是Cortex-M3提供的特殊操作:位带区的每个位都有位带别名区的一个字与之对应。Bit-band区域的存储器以32位方式进行访问,其中有效的仅仅是BIT0位,只有BIT0的值才对应到相应的普通区域的比特位上,其他位无效。STM32F系列芯片为所有外设寄存器和SRAM提供相对应的Bit-band区域,以简化对外设寄存器和SRAM的操作位带操作最重要的一环就是寻址,即为需要操作的“目标位”找到位带别名区相对应的地址:
位带别名区首地址+(操作字节的偏移量*32) +(操作位的偏移量*4)
内置SRAM区的位带别名区首地址 = 0x2200,0000
外设寄存器区的位带别名区首地址 = 0x4200,0000
例如:GPIOA的端口输出数据寄存器地址0x4001080c(stm32f1xx系列),对于PA.0来说控制其输出电平的比特位的位带操作地址为:
0x42000000+(0x1080c*32)+(0*4) = 0x42021018
以stm32f207为例,介绍stm32的位带操作。
在《ARM Cortex M3权威指南》92页介绍了如何在C语言中使用位带操作。个人在使用位带操作过程中将其大致总结为三种:

使用特权

评论回复
板凳
Vitality1|  楼主 | 2015-3-25 19:12 | 只看该作者
  查找需要定义的位所在的地址,将地址强制转换为指针,通过取指针内容的方式使用。
例如:
#definePD12   ((volatile unsigned long*)(0x424182b0))  
//0x424182b0为GPIOD_Pin12引脚对应的映射地址
*PD12=0x01;                    //PD12引脚置高

使用特权

评论回复
地板
Vitality1|  楼主 | 2015-3-25 19:12 | 只看该作者
  直接使用地址进行操作
  (*((u32*)0x424182b0))=0x01;    //PD12引脚置高
当然也可以通过如下定义使用
例如:
#definePD12   *((volatile unsigned long*)(0x424182b0))
//0x424182b0为GPIOD_Pin12引脚对应的映射地址
PD12=0x01;                      //PD12引脚置高

使用特权

评论回复
5
Vitality1|  楼主 | 2015-3-25 19:12 | 只看该作者
③在多个引脚需要定义时,显然上面两种方法都比较繁琐。为简化位带操作,也可以定义一些宏。比如,我们可以建立一个把“位带地址+位序号”换成别名地址的宏,再建立一个把别名地址转换成指针类型的宏。
例如
#defineGPIOD_ODR_Addr   ((uint32_t)(GPIOD_BASE+0x14))        // GPIOD_BASE已经定义过
#defineBITBAND(addr,bitnum) ((addr&0xF0000000)+0x2000000+((addr&0xFFFFF)<<5)+(bitnum<<2))
#defineMEM_ADDR(addr)  (*((volatile unsignedlong *)(addr)) )   
#define BIT_ADDR(addr,bitnum)   MEM_ADDR( BITBAND(addr, bitnum))
#define PD12 BIT_ADDR(GPIOD_ODR_Addr, 12)  //12为GPIOD对应的引脚号
#definePD12  BIT_ADDR(GPIOD_ODR_Addr, 13)  //13为GPIOD对应的引脚号
PD12=0x01;                            //PD12引脚置高
PD13=0x01;                            //PD13引脚置高

使用特权

评论回复
6
Vitality1|  楼主 | 2015-3-25 19:13 | 只看该作者
说明:GPIOD_BASE已经在库文件stm32f2xx.h中定义过,直接使用即可,对应地址是GPIOD的基地址,0x14是ODR的偏移地址。第二条语句是把“位带地址+序号”转换为对应的位带别名区地址。第三条语句MEM_ADDR(addr)代表的是(addr)地址中的内容。volatile必须使用,原因如下。
volatile是易变的、不稳定的意思。用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
先看看下面的例子:
inti=10;
intj = i;//(1)语句
intk = i;//(2)语句
这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候编译器认为 i 的值没有发生改变,所以在(1)语句时从内存中取出 i 的值赋给 j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。编译器不会生成出汇编代码重新从内存里取 i 的值,这样提高了效率。但要注意:(1)、(2)语句之间 i 没有被用作左值才行。
再看另一个例子:
volatile   int i=10;
intj = i;//(3)语句
intk = i;//(4)语句
volatile关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出 i的值,因而编译器生成的汇编代码会重新从 i 的地址处读取数据放在 k 中。这样看来,如果 i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。

使用特权

评论回复
7
搞IT的| | 2015-3-26 13:11 | 只看该作者
楼主辛苦!感谢分享!留着日后用!

使用特权

评论回复
8
一颗心的思考| | 2015-5-27 23:18 | 只看该作者
有个问题:
内置SRAM区的位带别名区首地址 = 0x2200,0000
外设寄存器区的位带别名区首地址 = 0x4200,0000
为什么地址是:0x2000,0000+0x0200,0000=0x2200,0000
                     0x4000,0000+0x0200,0000=0x4200,0000
这个0x200,0000是怎么计算出来的呢?

使用特权

评论回复
9
huzi2099| | 2015-5-27 23:59 | 只看该作者
一颗心的思考 发表于 2015-5-27 23:18
有个问题:
内置SRAM区的位带别名区首地址 = 0x2200,0000
外设寄存器区的位带别名区首地址 = 0x4200,0000

是固定的,内部映射到这个地址.

使用特权

评论回复
10
一颗心的思考| | 2015-5-28 08:53 | 只看该作者
huzi2099 发表于 2015-5-27 23:59
是固定的,内部映射到这个地址.

我以为我以为是计算出位带区,使位带区和这个在地址上相连

使用特权

评论回复
11
huzi2099| | 2015-6-4 18:34 | 只看该作者
一颗心的思考 发表于 2015-5-28 08:53
我以为我以为是计算出位带区,使位带区和这个在地址上相连

准确的说是这部分单元内容重新映射到另外的地址.

使用特权

评论回复
12
一颗心的思考| | 2015-6-4 18:46 | 只看该作者
huzi2099 发表于 2015-6-4 18:34
准确的说是这部分单元内容重新映射到另外的地址.

恩。这个一开始就明白。当时唯一不理解的就是
   0x2000,0000+0x0200,0000=0x2200,0000
   0x4000,0000+0x0200,0000=0x4200,0000
这个0x200,0000我以为是计算出来的。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

81

主题

421

帖子

9

粉丝