zoukankan      html  css  js  c++  java
  • 我对STM32所用位带操作宏的超详细剖析、优势分析及应用推广探索研究(持续更新,欢迎讨论交流)(转)

    原文:http://www.openedv.com/forum.php?mod=viewthread&tid=274196&extra=page%3D6&page=1

    我对原文的内容进行了二次整理

    1.问题的抛出

     在原子例程的sys.h中,使用宏定义建立了位带操作的基础,
    使得操作IO端口可以像51一样实现位操作。
    其实深入了解了位带操作的原理,几乎就可以实现对STM32所有外设寄存器的访问,
    极端情况下,什么库函数版本,什么寄存器版本都可以不用,直接精准地操控所有寄存器的每一位的读写!!!

    知道了STM32将所有外设寄存器的每一位都建立了位带别名区,
    你只要再花一点点时间,彻底搞明白下面的三句宏定义,位带操作就都不在话下了:
    #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))

     2.分析问题

    这三句是一环套一环的,
    首先第一句:
    #define    BITBAND(addr, bitnum)          ((addr & 0xF000 0000)+0x200 0000+((addr &0xF FFFF)<<5)+(bitnum<<2)) 
    这一句定义了位带存储地址的计算方法,
    知道了寄存器的地址,以及我们关心的寄存器的某一比特位,就可以根据此计算方法算出其对应的别名区地址
    这个计算公式不仅对外设寄存器对应的别名区计算有用,对用户SRAM对应的别名区一样适用。
    addr & 0xF000 0000 只取绝对地址的最高4位,实际上是用来区分段的,是寄存器段还是SRAM段。
    +0x200 0000(值为32M)是别名区相对位段区的地址偏移量,别名区在相应位段上方的32M处;
    (addr &0xF FFFF)<<5) 位段地址膨胀32倍,左移5位即可;
    (bitnum<<2)由于每1比特膨胀为32位,32位占用4个字节的存储位置,所以计算地址时要乘以4,左移2位即是;

    然后是第二句
    #define   MEM_ADDR(addr)            *((volatile unsigned long  *)(addr)) 
    上一句计算出来的地址只是一个数值,要将它强制转化成一个地址(并且声明这个地址存储的是一个32位的long型变量)
    用(unsigned long  *)(addr) 即可,这样就成了一个真正的有血有肉的地址了。
    前面再加一个*号,就可以访问这个地址得到其中的变量值了。
    在C语言中,unsigned char *p; 定义p为一个指向unsigned char的地址指针;而 *p=1;就是向这个指针指向的地址所存储的变量赋值为1了。
    至于中间加一个volatile关键字,则指示编译器不要自作主张对此进行优化,必须每次老老实实地去直接访问这个地址!!!

    第三句呢?毫无难度,就是以前两句宏为基础的结合
    #define BIT_ADDR(addr, bitnum)       MEM_ADDR(BITBAND(addr, bitnum))
    给定寄存器的绝对地址addr,以及我们关心的比特位号bitnum,
    先用BITBAND宏算出别名区对应的地址值
    再用MEM_ADDR宏去访问这个地址

    3.简单总结

    简单吧,这就是所有的位操作的奥秘了!!
    有了这三句,你就可以完成所有的位操作,让我们举一个实例,比方说要置位GPIO A口的第9位,即让PA9输出高电平。
    我们只须知道控制GPIO A的寄存器ODR的地址就行了,这个去查一下用户手册就行了,
    一般手册上会给出两项,一是外设寄存器的基址,GPIOA的基址是0x4001 0800, 再找ODR,手册上一般给出其偏移量0C,
    也就是说,GPIOA的ODR寄存器是0x4001 0800+0C=0x4001 080C
    什么?你不知道寄存器的地址怎么查? 哈哈,早有人替你查好了,并且为你查好,定义了下列宏:
    #define GPIOA_ODR_Addr    (0x4001 0800+0C) //0x4001080C
    并且一切为你着想,好事做到底,还定义了宏:
    #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)
    简单到你想置位GPIO A口的第9位,只须使用语句:PAout(9)=1;就行了。

    4.再次深入

    怎么是这样的呢?因为有前面这些宏定义为基础,反正闲着没事儿,我就当一回编译器,把这句PAout(9)=1一步步地编译出来,宏的展开就是一个替换的过程:

    PAout(9)=1;因为定义了PAout(n) 要替换成 BIT_ADDR(GPIOA_ODR_Addr,n),所以展开成:BIT_ADDR(GPIOA_ODR_Addr,9)=1;因为定义了BIT_ADDR(addr, bitnum) 要替换成 MEM_ADDR(BITBAND(addr, bitnum)),所以展开成:
    MEM_ADDR(BITBAND(GPIOA_ODR_Addr, 9))=1;因为定义了BIT_ADDR(addr, bitnum) 要替换成 MEM_ADDR(BITBAND(addr, bitnum)),所以展开成:
    MEM_ADDR((GPIOA_ODR_Addr & 0xF0000000)+0x2000000+((GPIOA_ODR_Addr &0xFFFFF)<<5)+(9<<2))=1;
    最后一步,因为定义了MEM_ADDR(addr)要替换成 *((volatile unsigned long  *)(addr))
    所以展开成为如下的语句,不要晕倒哦,*((volatile unsigned long  *)((GPIOA_ODR_Addr & 0xF0000000)+0x2000000+((GPIOA_ODR_Addr &0xFFFFF)<<5)+(9<<2)))=1;

    神奇吧?一句  PAout(9)=1;与     *((volatile unsigned long  *)((GPIOA_ODR_Addr & 0xF0000000)+0x2000000+((GPIOA_ODR_Addr &0xFFFFF)<<5)+(9<<2)))=1;

    是完全等效的。而这,就是宏定义的效能和魅力!

    还有:因为定义了GPIOA_ODR_Addr就是(0x4001 0800+0C),哦,等一下,我先算出数值来吧,GPIOA_ODR_Addr就是0x4001 080C,得到:   
    *((volatile unsigned long  *)((0x4001 080C & 0xF0000000)+0x2000000+((0x4001 080C &0xFFFFF)<<5)+(9<<2)))=1;

    看着很长,其实有了具体数值,算出结果就短了:
    解&运算符:得到*((volatile unsigned long  *)(0x4000 0000 +0x200 0000+(0x0001 080C<<5)+(9<<2)))=1;
    即:*((volatile unsigned long  *)(0x4200 0000 +(0x0001 080C<<5)+(9<<2)))=1;
    解移位运算符:9=1001 经过<<2得到 100100 即0x24;
    1 080C=0001 0000 1000 0000 1100经过<<5得到0010 0001 0000 0001 1000 0000 即0x21 0180
    所以语句变成:*((volatile unsigned long  *)(0x4200 0000 + 0x21 0180 + 0x24)=1;
    最后结果就是如下语句(以上这些过程都只是预编译器干的话,实际交付编译器的也就是下面这一句):
    *((volatile unsigned long  *)(0x4221 01A4)=1;
    说成大白话,就是给0x4221 01A4这个地址中所存储的变量赋值为1.  
       
    (注意:这个变量是一个long型的,32位,占用从0x4221 01A4开始的连续4个存储单元) 
    但是ARM的设计师们并没有在物理上设计这些存储单元(也永远不允许这些存储单元实际存在!!!),取而代之的是设计了位映射机制:
    凡是访问别名区域地址的操作,都被转换为访问其所映射对应的比特位
    *(0x4221 01A4)=1的执行结果就是:GPIOA的ODR寄存器第9位=1

    除了可以操作STM32所有片上外设寄存器的每一比特位外,我们还可以操作SRAM区的每一比特位。因为,片上SRAM的全部,无一例外地都落在位带区。我们正常程序中声明的所有变量,因此也都被分配在这一个位带区。只要我们知道了变量的地址,也就可以通过其相应的别名区地址按比特访问。比如:我们可以将程序中用到的标志位集中定义到一个变量(8-32位均可)给这个变量分配一个固定地址的单元,然后在程序中按位来访问这些标志,这样可以提高软件的效率。
    另外还要指出的是:虽然位段区的每一位都被映射到别名区膨胀到了32位,但这32位只是个名头而已,实际只有最低位有效。

    对别名区的访问,是双向的:
    对别名区的读:结果非0即1,反应的是对应位段的某一比特位的值。
    对别名区的写:只有最低位有效,效果是将对应位段区的某一比特位置1或清0.    写入0和写入FE效果是完全一样的。

  • 相关阅读:
    TypeScript Handbook 2——接口1(翻译)
    TypeScript Handbook 1——基本类型(翻译)
    [转]AJAX 跨源 HTTP 请求
    [转] Delphi Socket Architecture
    handsontable的一种js继承方式
    test11
    test
    javascript代码风格
    maven创建web项目
    Oracle 11g R2 for Win10(64位)的安装注意点
  • 原文地址:https://www.cnblogs.com/TonyJia/p/12859872.html
Copyright © 2011-2022 走看看