stm32位带操作

控制/MCU

1815人已加入

描述

  STM32系列基于专为要求高性能、低成本、低功耗的嵌入式应用专门设计的ARM Cortex-M3内核(ST‘s product portfolio contains a comprehensive range of microcontrollers, from robust, low-cost 8-bit MCUs up to 32-bit ARM-based Cortex®-M0 and M0+, Cortex®-M3, Cortex®-M4 Flash microcontrollers with a great choice of peripherals. ST has also extended this range to include an ultra-low-power MCU platform)[1] 。按内核架构分为不同产品:

  其中STM32F系列有:

  STM32F103“增强型”系列

  STM32F101“基本型”系列

  STM32F105、STM32F107“互联型”系列

  增强型系列时钟频率达到72MHz,是同类产品中性能最高的产品;基本型时钟频率为36MHz,以16位产品的价格得到比16位产品大幅提升的性能,是32位产品用户的最佳选择。两个系列都内置32K到128K的闪存,不同的是SRAM的最大容量和外设接口的组合。时钟频率72MHz时,从闪存执行代码,STM32功耗36mA,相当于0.5mA/MHz。

  

  在CM3中,有两个区中实现了位带。其中一个是 SRAM 区的最低 1MB 范围,第二个则是片内外设区的最低 1MB 范围。这两个区中的地址除了可以像普通的 RAM 一样使用外,它们还都有自己的“位带别名区”,位带别名区把每个比特膨胀成一个 32 位的字。当你通过位带别名区访问这些字时,就可以达到访问原始比特的目的。

  在位带区中,每个比特都映射到别名地址区的一个字(这是只有 LSB 有效的字)。当一个别名地址被访问时,会先把该地址变换成位带地址。对于读操作,读取位带地址中的一个字,再把需要的位右移到 LSB,并把 LSB 返回。对于写操作,把需要写的位左移至对应的位序号处,然后执行一个原子的“读-改-写”过程。

  支持位带操作的两个内存区的范围是:

  0x2000_0000‐0x200F_FFFF(SRAM 区中的最低1MB) 0x4000_0000‐0x400F_FFFF(片上外设区中的最低 1MB) 位带(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语言中使用位带操作。个人在使用位带操作过程中将其大致总结为三种:

  ① 查找需要定义的位所在的地址,将地址强制转换为指针,通过取指针内容的方式使用。

  例如:

  #define PD12 ((volatile unsigned long *)(0x424182b0))

  //0x424182b0为GPIOD_Pin12引脚对应的映射地址

  *PD12=0x01; //PD12引脚置高

  ② 直接使用地址进行操作

  (*((u32*)0x424182b0))=0x01; //PD12引脚置高 当然也可以通过如下定义使用 例如:

  #define PD12 *((volatile unsigned long *)(0x424182b0))

  //0x424182b0为GPIOD_Pin12引脚对应的映射地址

  PD12=0x01; //PD12引脚置高

  ③在多个引脚需要定义时,显然上面两种方法都比较繁琐。为简化位带操作,也可以定义一些宏。比如,我们可以建立一个把“位带地址+位序号”换成别名地址的宏,再建立一个把别名地址转换成指针类型的宏。

  例如

  #define GPIOD_ODR_Addr ((uint32_t)(GPIOD_BASE+0x14)) // GPIOD_BASE已经定义过 #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 PD12 BIT_ADDR(GPIOD_ODR_Addr, 12) //12为GPIOD对应的引脚号 #define PD12 BIT_ADDR(GPIOD_ODR_Addr, 13) //13为GPIOD对应的引脚号 PD12=0x01; //PD12引脚置高 PD13=0x01; //PD13引脚置高

  

  说明:GPIOD_BASE已经在库文件stm32f2xx.h中定义过,直接使用即可,对应地址是GPIOD的基地址,0x14是ODR的偏移地址。第二条语句是把“位带地址+序号”转换为对应的位带别名区地址。第三条语句MEM_ADDR(addr)代表的是(addr)地址中的内容。volatile必须使用,原因如下。

  volatile 是易变的、不稳定的意思。用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

  先看看下面的例子: int i=10; int j = i;//(1)语句 int k = i;//(2)语句

  这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候编译器认为 i 的值没有发生改变,所以在(1)语句时从内存中取出 i 的值赋给 j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。编译器不会生成出汇编代码重新从内存里取 i 的值,这样提高了效率。但要注意:(1)、(2)语句之间 i 没有被用作左值才行。

  再看另一个例子: volatile int i=10; int j = i;//(3)语句 int k = i;//(4)语句

  volatile 关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出 i的值,因而编译器生成的汇编代码会重新从 i 的地址处读取数据放在 k 中。这样看来,如果 i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分