OpenEdv-开源电子网

 找回密码
 立即注册

扫一扫,访问微社区

正点原子全套STM32开发资料,上千讲STM32视频教程,RT1052教程免费下载啦...

查看: 849|回复: 66

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

[复制链接]

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
发表于 2018-7-3 20:59:38 | 显示全部楼层 |阅读模式
正点原子公众号
本帖最后由 warship 于 2018-7-16 19:56 编辑

在原子例程的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))



************************************************************************************************
注:本文后文所探索的寄存器位段操作宏定义包含在另文所附范例(外部中断试验的工程包)中,并随时更新。
有需要研究探讨的网友,可移步下载http://www.openedv.com/forum.php ... d=274724&extra=


回复

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-3 21:00:18 | 显示全部楼层
本帖最后由 warship 于 2018-7-3 22:34 编辑

这三句是一环套一环的,
首先第一句:
#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宏去访问这个地址
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-3 21:01:19 | 显示全部楼层
简单吧,这就是所有的位操作的奥秘了!!
有了这三句,你就可以完成所有的位操作,让我们举一个实例,比方说要置位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;就行了。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-3 21:01:53 | 显示全部楼层
本帖最后由 warship 于 2018-7-3 22:41 编辑

怎么是这样的呢?因为有前面这些宏定义为基础,
反正闲着没事儿,我就当一回编译器,把这句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;
是完全等效的。

而这,就是宏定义的效能和魅力!


回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-3 21:02:53 | 显示全部楼层
正点原子公众号
本帖最后由 warship 于 2018-7-3 22:49 编辑

还有:因为定义了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




回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-3 21:45:36 | 显示全部楼层
除了可以操作STM32所有片上外设寄存器的每一比特位外,
我们还可以操作SRAM区的每一比特位。
因为,片上SRAM的全部,无一例外地都落在位带区。
我们正常程序中声明的所有变量,因此也都被分配在这一个位带区。
只要我们知道了变量的地址,也就可以通过其相应的别名区地址按比特访问。
比如:我们可以将程序中用到的标志位集中定义到一个变量(8-32位均可)
给这个变量分配一个固定地址的单元,
然后在程序中按位来访问这些标志,
这样可以提高软件的效率。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-3 22:13:12 | 显示全部楼层
另外还要指出的是:
虽然位段区的每一位都被映射到别名区膨胀到了32位,
但这32位只是个名头而已,实际只有最低位有效。

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

使用道具 举报

  离线 

10

主题

123

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
388
金钱
388
注册时间
2018-4-13
在线时间
56 小时
发表于 2018-7-4 13:22:27 | 显示全部楼层
谢谢分享,很受启示。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-4 19:47:44 | 显示全部楼层
xiatianyun 发表于 2018-7-4 13:22
谢谢分享,很受启示。

谢谢指导。
前面的分析我们是以寄存器区的位段操作为例来剖析的。
SRAM区的位段操作也是一样的机制,
我前面提到,
我们所定义的变量,都被分配在SRAM区,
只不过我们一般不关心这些变量的具体地址,
而现在我们必须知道地址,才能进行位段操作。
下面我和大家分享一下SRAM位段操作的具体方法:
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-4 19:54:06 | 显示全部楼层
首先是,如何才能事先得到变量的地址呢?
这就需要用到C语言的一项功能,声明变量时可以指定地址:
方法是使用attribute,格式是__attribute__((at(addr)))
注意要求是双下划线,再加双括号,
addr就是我们可以指定的地址。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-4 19:57:37 | 显示全部楼层
网上有一种方法是用结构体来实现SRAM的位操作:
具体实现代码是:
#define __BITBAND__         __attribute__((bitband))
#define __BITBAND__ADDR(addr)         __attribute__((at(addr)))

//定义位带结构类型
typedef struct
{
        u32 a1:1;   //宽度为1位
        u32 a2:1;   //宽度为1位
}_STATE_FLAG __BITBAND__;

//声明一个结构类型变量,并指定存储地址
volatile _STATE_FLAG StateFlag __BITBAND__ADDR(0x2000A000);

//在程序中使用方法示例
//  StateFlag.a1=0;  
// if(!StateFlag.a1) StateFlag.a1=1;

具体原理我不详述了,反正我用不习惯。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-4 20:04:54 | 显示全部楼层
本帖最后由 warship 于 2018-7-8 09:58 编辑

这里我建议大家使用下面的方法:
这个方法沿袭了前面寄存器位带操作的成果
便于加深理解,并且感觉比上面的结构体少占内存。
代码是:
#define MyFlag_Addr    0x2000A100   //选择SRAM区的一个固定地址
//定义一个全局变量作为集中的标志位寄存器, 分配于上述固定地址
volatile u32 MyFlag __attribute__((at(MyFlag_Addr)));

#define Myflg(n) BIT_ADDR(MyFlag_Addr,n)  //可以通过改变n访问不同的标志位

//也可以通过下列宏定义访问单一的标志位, 每一个标志位可以自定义一个有明确意义的名称
#define Myflg0   BIT_ADDR(MyFlag_Addr,0)  //标志位0
#define Myflg1   BIT_ADDR(MyFlag_Addr,1)  //标志位1

//访问时用例:  Myflg(n)=1; 或者: Myflg0=1;
完整的测试代码,详见21楼
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-4 20:08:12 | 显示全部楼层
本帖最后由 warship 于 2018-7-4 20:13 编辑

另外,还有一种方法也一样,那就是: 直接定义一个常数指针变量指向该标志位寄存器的别名区基址
volatile u32* const pMyFlag=(void *)(0x22000000+((MyFlag_Addr &0xFFFFF)<<5));
//用const通知编译器:该指针不能再被修改而指向其它地址

//访问时用例:  *(pMyFlag+n)=1; 或者数组形式: pMyFlag[n]=1;

以上两种方法和所贴出来的代码,我今天已经完全验证过了,可以放心使用。

回复 支持 反对

使用道具 举报

  在线 

21

主题

190

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1667
金钱
1667
注册时间
2012-8-25
在线时间
426 小时
发表于 2018-7-5 07:57:49 | 显示全部楼层
赞赞赞赞
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-5 20:08:09 | 显示全部楼层

多谢支持。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-6 18:58:57 | 显示全部楼层
补充说明一下:
定义一个固定的地址的问题,如12楼的如下宏定义语句:
#define MyFlag_Addr    0x2000A100   //选择SRAM区的一个固定地址
这个地址如何确定呢?
我们在KEIL中编译完程序后,编译结果的后两项就是与SRAM相关的,即RW_data和ZI_data
系统对SRAM的需求就是这两项之和,从地址2000 0000开始。
我们定义的固定地址在这之后就可以了。即地址要大于2000 0000+RW_data+ZI_data
为了保险起见,再多加点余量,尽量往后一点定义就行了。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-6 19:08:29 | 显示全部楼层
但注意不要超出你器件的SRAM地址范围,
如F103C8T6,SRAM的SRAM为20K,
即地址范围为:2000 0000至2000 5000
则定义地址时,不能超出2000 5000
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-7 08:28:33 | 显示全部楼层
本帖最后由 warship 于 2018-7-8 10:53 编辑

STM32内部的位段操作机理的实质是:把对别名区的地址的读写--〉映射成对相应的位的读写
而不是反过来(注意理解二者的差别)。
这是STM32设计的一种内部机制,它无须CPU的寄存器(Rn)参与运算,直接在CACHE中完成原子操作,效率较高。
我们有些人经常反过来说:“比特位被映射到别名区”,其实是对这种内部机制规则的反向推算。
那个著名的别名地址计算公式实质是由比特位反推出对应的别名区地址,其目的就是为了
算得别名区地址后,运用STM32的内部机制,通过读写别名区实现对其相对应的比特位的读写。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-7 08:29:16 | 显示全部楼层
本帖最后由 warship 于 2018-7-7 12:40 编辑

具体效果是:
对2200 0000的读写,被映射成对2000 0000所存字节中BIT0的读写;
对2200 0004的读写,被映射成对2000 0000所存字节中BIT1的读写;
对2200 0008的读写,被映射成对2000 0000所存字节中BIT2的读写;
......
注意:别名区的地址是以字(32位,占用连续的4个字节)对齐的,
每个字只有LSB有意义。在访问别名区时,要把地址对齐到字的边界上,
否则会产生不可预料的结果。什么意思?就是说:只能对别名区中的LSB所在的地址进行读写。
如上述22000000、22000004、22000008、......等地址是有效的,这些地址都是4的整数倍。
必须避免对其它的地址如:22000001、20000002......20000007等地址的读写!!!
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-7 13:01:35 | 显示全部楼层
本帖最后由 warship 于 2018-7-9 17:08 编辑

没有人回帖讨论,有点孤独,但我还是将专题研究进行到底。
回到在SRAM区实现位操作的话题。

个人认为,12楼所介绍的方法是最理想的方法,
手工分配一个固定地址存放标志位综合寄存器就好了。
有些人可能要问,如果不用手工分配固定地址的方法行不行?
我今天也进行了一些探索,是完全可行的。

那就是在程序中查询地址。
闲话少说,直接上代码:
u32 MyFlag;   //定义一个我们用于存放各类标志位的全局变量
volatile u32*  pMyFlag;   //再定义一个指向此变量对应的别名区地址的指针

//   在系统初始化代码中,查询并计算出这个别名指针的具体值
        u32 *pwMyFlag;  //临时定义一个指向MyFlag变量本身的指针
        pwMyFlag=&MyFlag;    //查询该变量的地址赋值给pwMyFlag
        pMyFlag=(void *)(0x22000000+(((u32)pwMyFlag & 0xFFFFF)<<5));  //计算出对应的别名区地址

//这样,所有的准备工作就完成了,以后可在程序的任何地方使用位操作
//访问时用例-置位某标志:  *(pMyFlag+n)=1; 或者数组形式: pMyFlag[n]=1;
//条件语句用例: if(pMyFlag[3])    pMyFlag[3]=0;
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-7 13:11:30 | 显示全部楼层
本帖最后由 warship 于 2018-7-8 10:58 编辑

下面给出手工分配固定地址(即12楼本人推荐方法)的完整测试代码:
#define MyFlag_Addr    0x20001800   //选择SRAM区的一个固定地址
//定义一个全局变量作为集中的标志位寄存器, 分配于上述固定地址
volatile u32 MyFlag __attribute__((at(MyFlag_Addr)));

#define Myflg(n) BIT_ADDR(MyFlag_Addr,n)  //可以通过改变n访问不同的标志位
//也可以通过下列宏定义访问单一的标志位, 每一个标志位可以自定义一个有明确意义的名称
#define Myflg0   BIT_ADDR(MyFlag_Addr,0)  //标志位0
#define Myflg1   BIT_ADDR(MyFlag_Addr,1)  //标志位1
#define Myflg2   BIT_ADDR(MyFlag_Addr,2)  //标志位2
#define Myflg3   BIT_ADDR(MyFlag_Addr,3)  //标志位3

//测试例程:可以在main()中调用,也可直接作为main()的主体
void BITBAND_TEST(void)
{

        MyFlag=2;  //赋初值为2,即最低四位(下同,共32位这里只关注低四位的变化)为0010
        
        if(Myflg1) Myflg1=0;  //如果BIT1为1,则清为0,此时MyFlag变为0000
               
        Myflg3=1;   //置位BIT3  
        printf(temp, "Flag=%d ",MyFlag);        //显示MyFlag值,此时为1000值应为8;        
        
        Myflg0=1;                 //置位BIT0   
        printf("Flag=%d ",MyFlag);//显示MyFlag值,此时为1001值应为9;
        
        while(1);   //测试完毕,进入死循环。如要做其它测试,可对上述代码进行相应修改。
   
}

//在下面的21楼、22楼对本测试代码的反汇编目标码进行了深入剖析

回复 支持 反对

使用道具 举报

  离线 

2

主题

118

帖子

0

精华

高级会员

Rank: 4

积分
867
金钱
867
注册时间
2017-5-17
在线时间
157 小时
发表于 2018-7-7 13:38:15 | 显示全部楼层
厉害厉害
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-7 19:36:18 | 显示全部楼层

多谢支持。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-8 09:16:38 | 显示全部楼层
本帖最后由 warship 于 2018-7-8 09:23 编辑

继续研究,对21楼的测试例程进行反汇编如下:
   143:         MyFlag=2;  //赋值为2,即最低四位(下同,共32位这里只关注低四位的变化)为0010
0x0800019A 2002      MOVS     r0,#0x02
0x0800019C 49FE      LDR      r1,[pc,#1016]  ; @0x08000598处所存的内容(MyFlag分配的地址值)赋值给R1
0x0800019E 6008      STR      r0,[r1,#0x00]        ;寻址这一MyFlag地址,将R0的值存入
   144:         if(Myflg1) Myflg1=0;  //如果BIT1为1,则清为0,此时MyFlag变为0000
   145:                  
0x080001A0 48FE      LDR      r0,[pc,#1016]  ; @0x0800059C,标志位Myflg0的地址(2203 0000),指向别名区
                                             
0x080001A2 6840      LDR      r0,[r0,#0x04]  ; 加#4后为标志位Myflg1的地址,指向别名区
0x080001A4 B110      CBZ      r0,0x080001AC
0x080001A6 2000      MOVS     r0,#0x00
0x080001A8 49FC      LDR      r1,[pc,#1008]  ; @0x0800059C,(此处存地址值2203 0000)
0x080001AA 6048      STR      r0,[r1,#0x04]
   146:         Myflg3=1;   //置位BIT3   
0x080001AC 2001      MOVS     r0,#0x01      
0x080001AE 49FB      LDR      r1,[pc,#1004]  ; @0x0800059C(此处存地址值2203 0000)
0x080001B0 60C8      STR      r0,[r1,#0x0C]  ; 加#12后为标志位Myflg3的地址,指向别名区
......
......

0x08000598 1800      DCW      0x1800       ; 这里存放了我们手工指定的地址值:2000 1800
0x0800059A 2000      DCW      0x2000
0x0800059C 0000      DCW      0x0000      ; 这里存放了计算出来的别名区基址:2203 0000
0x0800059E 2203      DCW      0x2203
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-8 09:38:00 | 显示全部楼层
本帖最后由 warship 于 2018-7-8 09:47 编辑

由上楼可以清楚的看到:
编译出来的最终目标代码,为32位的变量MyFlag按我们手工指定,分配了固定地址:2000 1800
编译器根据我们的宏定义,算好了别名区基址:2203 0000
当我们访问Myflg0时,目标代码执行的是访问地址2203 0000
当我们访问Myflg1时,目标代码执行的是访问地址2203 0000+4
当我们访问Myflg2时,目标代码执行的是访问地址2203 0000+8
当我们访问Myflg3时,目标代码执行的是访问地址2203 0000+12
......
即访问Myflg[n]时,目标代码执行的是访问地址2203 0000+4*n;
KEILC编译器是把这些地址当成一般的正常地址来对待的,
而实际执行时,由STM32的内部机制,把对这些实际并不存在的地址的访问,
映射并执行成了对相应比特位的访问。
回复 支持 反对

使用道具 举报

  离线 

10

主题

123

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
388
金钱
388
注册时间
2018-4-13
在线时间
56 小时
发表于 2018-7-8 09:41:18 | 显示全部楼层
谢谢分享。SRAM区的位带操作用12楼的做法和sys.h中定义的方法我觉得是一样的啊。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-8 09:46:38 | 显示全部楼层
xiatianyun 发表于 2018-7-8 09:41
谢谢分享。SRAM区的位带操作用12楼的做法和sys.h中定义的方法我觉得是一样的啊。

就是借用了GPIO位访问的代码
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-8 11:07:20 | 显示全部楼层
占用空间情况:
FLASH:两个字,分别用于存放变量MyFlag的地址和别名基址;
SRAM:一个字,存放MyFlag变量本身。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 09:20:14 | 显示全部楼层
本帖最后由 warship 于 2018-7-9 09:51 编辑

深入剖析了位带操作的机理和优势之后,
有一个疑问,其实外设寄存器都可以通过位带操作,
尤其是寄存器比特位的操作和判别,用位带操作的高效尤其明显。
但是看STM官方的库,却基本不用位带操作:
举个例子,SystemInit()中,把PLL作为系统时钟源,代码如下:
   RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    //系统时钟选择PLL(BIT1位)置1
这种对整个32位进行读出--逻辑运算--再写回的效率是很低的。
假如用位带操作,则只须 bRCC_CFGR_SW_PLL=1; 就行了
前面要做的准备工作只是定义一个位别名地址宏: #define  bRCC_CFGR_SW_PLL  0x42420040就可以了。
有人可能会想,这是否增加了宏定义的工作量?    我反问:RCC_CFGR_SW_PLL难道就不需要定义吗?
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 10:36:44 | 显示全部楼层
本帖最后由 warship 于 2018-7-9 12:58 编辑

拿前不久我使用的一个从STOP模式中快速恢复时钟的函数为例,
库函数版本如下:
/**
  * 本函数用于从STOP模式唤醒后重新配置系统时钟:使能HSE,PLL并选择PLL作为系统时钟源
  *         
  */
static void SYSCLKConfig_STOP(void)
{  
  /* 从STOP模式唤醒后重新配置系统时钟 */
  /* 使能 HSE */
  RCC_HSEConfig(RCC_HSE_ON);
  
  /* 等待HSE时钟就绪 */
  while (RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET)
  {}  
  
  /* 使能PLL */
  RCC_PLLCmd(ENABLE);
  
  /* 等待PLL就绪 */
  while (RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
  {}
  
  /* 选择PLL作为系统时钟源 */
  RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
  
  /* 等待时钟源配置就绪 */
  while (RCC_GetSYSCLKSource() != 0x08)
  {}
}
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 10:37:59 | 显示全部楼层
如果改写成STM官方惯用的寄存器版本,则如下:
static void SYSCLKConfig_STOP(void)
{  
  /* 从STOP模式唤醒后重新配置系统时钟 */
  /* 使能 HSE */   
  RCC->CR |= ((uint32_t)RCC_CR_HSEON);
  
  /* 等待HSE时钟就绪 */
  while ((RCC->CR & RCC_CR_HSERDY) == 0)
  {}
  
  /* 使能 PLL */
  RCC->CR |= RCC_CR_PLLON;
  
  /* 等待直到 PLL 就绪 */
  while((RCC->CR & RCC_CR_PLLRDY) == 0)
   {
    }
  
  /* 把 PLL 作为系统时钟源 */     
    RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //系统时钟选择位(BIT1:0)复位(00为HSI 01为HSE 10为PLL)
    RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    //系统时钟选择PLL(BIT1位)置1

  /* 等待,直到 PLL 被用作系统时钟源 */
    while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) //BIT3为1指示PLL已为钟源
    {
    }
}
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 10:43:17 | 显示全部楼层
本帖最后由 warship 于 2018-7-9 11:07 编辑

用到的寄存器位:
RCC寄存器基址  4002 1000
RCC->CR   +0
RCC_CR_HSEON    位16
RCC_CR_HSERDY   位17
RCC_CR_PLLON     位24
RCC_CR_PLLRDY   位25

RCC->CFGR +4
RCC_CFGR_SW0      位0
RCC_CFGR_SW_PLL  位1
RCC_CFGR_SWS_PLL  位3
现在定义一下位别名地址:
// #define RCC_BASE   0x40021000    //这个官方已有定义
#define bRCC_CR_HSEON   BIT_ADDR(RCC_BASE, 16)  
#define bRCC_CR_HSERDY   BIT_ADDR(RCC_BASE, 17)
#define bRCC_CR_PLLON   BIT_ADDR(RCC_BASE, 24)
#define bRCC_CR_PLLRDY    BIT_ADDR(RCC_BASE, 25)

#define bRCC_CFGR_SW0       BIT_ADDR(RCC_BASE+4, 0)
#define bRCC_CFGR_SW_PLL   BIT_ADDR(RCC_BASE+4, 1)
#define bRCC_CFGR_SWS_PLL   BIT_ADDR(RCC_BASE+4, 3)

为了与官方定义区别,位地址宏均加上前缀b
话说这种基础性的工作,一个上午就可以全部搞定,做成一个位别名地址宏的头文件。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 11:04:25 | 显示全部楼层
本帖最后由 warship 于 2018-7-9 16:53 编辑

//改写成位操作版本,如下:
static void SYSCLKConfig_STOP(void)
{  
  /* 从STOP模式唤醒后重新配置系统时钟 */
  /* 使能 HSE */   
  bRCC_CR_HSEON=1;
  
  /* 等待HSE时钟就绪 */
  while (!bRCC_CR_HSERDY){}
  
  /* 使能 PLL */
  bRCC_CR_PLLON=1;
  
  /* 等待直到 PLL 就绪 */
  while(!bRCC_CR_PLLRDY){}
  
  /* 把 PLL 作为系统时钟源 */     
  bRCC_CFGR_SW0=0;       //系统时钟选择SW0位复位   
  bRCC_CFGR_SW_PLL=1;    //系统时钟选择PLL(BIT1位)置1

  /* 等待,直到 PLL 被用作系统时钟源 */
  while (!bRCC_CFGR_SWS_PLL) //等待BIT3为1指示PLL已为钟源
   {}
}




//怎么样? 非常简洁!!!
//不仅程序简洁,方便书写,而且可读性也非常强。 如果再定义#define YES 1 或(和) #define ENABLE  1  的话,全函数如下:
static void SYSCLKConfig_STOP(void)
{  
  bRCC_CR_HSEON=YES;         
  while (! bRCC_CR_HSERDY);    bRCC_CR_PLLON=YES;         
  while(! bRCC_CR_PLLRDY);     
  bRCC_CFGR_SW_PLL=YES;         
  while (! bRCC_CFGR_SWS_PLL);     
}
//看起来就像自然描述语言一样,但却非常底层,远远超越了寄存器版本的高效。



回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 11:15:21 | 显示全部楼层
本帖最后由 warship 于 2018-7-9 11:24 编辑

再比较一下,编译出来的目标代码,先看位操作版本:
   506:   bRCC_CR_HSEON=1;
   507:   
   508:   /* 等待HSE时钟就绪 */
0x08000D58 2001      MOVS     r0,#0x01
0x08000D5A 490F      LDR      r1,[pc,#60]  ; @0x08000D98
0x08000D5C 6408      STR      r0,[r1,#0x40]
   509:   while (!bRCC_CR_HSERDY){}
   510:   
   511:   /* 使能 PLL */
0x08000D5E BF00      NOP      
0x08000D60 480D      LDR      r0,[pc,#52]  ; @0x08000D98
0x08000D62 6C40      LDR      r0,[r0,#0x44]
0x08000D64 2800      CMP      r0,#0x00
0x08000D66 D0FB      BEQ      0x08000D60
   512:   bRCC_CR_PLLON=1;
   513:   
   514:   /* 等待直到 PLL 就绪 */
0x08000D68 2001      MOVS     r0,#0x01
0x08000D6A 490B      LDR      r1,[pc,#44]  ; @0x08000D98
0x08000D6C 6608      STR      r0,[r1,#0x60]
   515:   while(!bRCC_CR_PLLRDY){}
   516:   
   517:   /* 把 PLL 作为系统时钟源 */      
0x08000D6E BF00      NOP      
0x08000D70 4809      LDR      r0,[pc,#36]  ; @0x08000D98
0x08000D72 6E40      LDR      r0,[r0,#0x64]
0x08000D74 2800      CMP      r0,#0x00
0x08000D76 D0FB      BEQ      0x08000D70
   518:   bRCC_CFGR_SW0=0;       //系统时钟选择SW0位复位   
0x08000D78 2000      MOVS     r0,#0x00
0x08000D7A 4907      LDR      r1,[pc,#28]  ; @0x08000D98
0x08000D7C 3180      ADDS     r1,r1,#0x80
0x08000D7E 6008      STR      r0,[r1,#0x00]
   519:   bRCC_CFGR_SW_PLL=1;    //系统时钟选择PLL(BIT1位)置1
   520:  
   521:   /* 等待,直到 PLL 被用作系统时钟源 */
0x08000D80 2001      MOVS     r0,#0x01
0x08000D82 4905      LDR      r1,[pc,#20]  ; @0x08000D98
0x08000D84 F8C10084  STR      r0,[r1,#0x84]
   522:   while (!bRCC_CFGR_SWS_PLL) //BIT3为1指示PLL已为钟源
   523:    {} 0x08000D88 BF00      NOP      
0x08000D8A 4803      LDR      r0,[pc,#12]  ; @0x08000D98
0x08000D8C 308C      ADDS     r0,r0,#0x8C
0x08000D8E 6800      LDR      r0,[r0,#0x00]
0x08000D90 2800      CMP      r0,#0x00
0x08000D92 D0FA      BEQ      0x08000D8A
   524: }

回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 11:23:03 | 显示全部楼层
本帖最后由 warship 于 2018-7-9 11:26 编辑

再看一下31楼的STM官方惯用的寄存器版本,编译结果为:        469:   RCC->CR |= ((uint32_t)RCC_CR_HSEON);
   470:   
   471:   /* 等待HSE时钟就绪 */
0x08000D58 4816      LDR      r0,[pc,#88]  ; @0x08000DB4
0x08000D5A 6800      LDR      r0,[r0,#0x00]
0x08000D5C F4403080  ORR      r0,r0,#0x10000
0x08000D60 4914      LDR      r1,[pc,#80]  ; @0x08000DB4
0x08000D62 6008      STR      r0,[r1,#0x00]
   472:   while ((RCC->CR & RCC_CR_HSERDY) == 0)
   473:    {}
   474:   
   475:   /* 使能 PLL */
0x08000D64 BF00      NOP      
0x08000D66 4813      LDR      r0,[pc,#76]  ; @0x08000DB4
0x08000D68 6800      LDR      r0,[r0,#0x00]
0x08000D6A F4003000  AND      r0,r0,#0x20000
0x08000D6E 2800      CMP      r0,#0x00
0x08000D70 D0F9      BEQ      0x08000D66
   476:   RCC->CR |= RCC_CR_PLLON;
   477:   
   478:   /* 等待直到 PLL 就绪 */
0x08000D72 4810      LDR      r0,[pc,#64]  ; @0x08000DB4
0x08000D74 6800      LDR      r0,[r0,#0x00]
0x08000D76 F0407080  ORR      r0,r0,#0x1000000
0x08000D7A 490E      LDR      r1,[pc,#56]  ; @0x08000DB4
0x08000D7C 6008      STR      r0,[r1,#0x00]
   479:   while((RCC->CR & RCC_CR_PLLRDY) == 0)
   480:    {}
   481:   
   482:   /* 把 PLL 作为系统时钟源 */      
0x08000D7E BF00      NOP      
0x08000D80 480C      LDR      r0,[pc,#48]  ; @0x08000DB4
0x08000D82 6800      LDR      r0,[r0,#0x00]
0x08000D84 F0007000  AND      r0,r0,#0x2000000
0x08000D88 2800      CMP      r0,#0x00
0x08000D8A D0F9      BEQ      0x08000D80
   483:     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //系统时钟选择位(BIT1:0)复位(00为HSI 01为HSE 10为PLL)
0x08000D8C 4809      LDR      r0,[pc,#36]  ; @0x08000DB4
0x08000D8E 6840      LDR      r0,[r0,#0x04]
0x08000D90 F0200003  BIC      r0,r0,#0x03
0x08000D94 4907      LDR      r1,[pc,#28]  ; @0x08000DB4
0x08000D96 6048      STR      r0,[r1,#0x04]
   484:     RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    //系统时钟选择PLL(BIT1位)置1
   485:  
   486:   /* 等待,直到 PLL 被用作系统时钟源 */
0x08000D98 4608      MOV      r0,r1
0x08000D9A 6840      LDR      r0,[r0,#0x04]
0x08000D9C F0400002  ORR      r0,r0,#0x02
0x08000DA0 6048      STR      r0,[r1,#0x04]
   487:   while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) //BIT3为1指示PLL已为钟源
   488:    {}
0x08000DA2 BF00      NOP      
0x08000DA4 4803      LDR      r0,[pc,#12]  ; @0x08000DB4
0x08000DA6 6840      LDR      r0,[r0,#0x04]
0x08000DA8 F000000C  AND      r0,r0,#0x0C
0x08000DAC 2808      CMP      r0,#0x08
0x08000DAE D1F9      BNE      0x08000DA4
   489: }

回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 11:38:09 | 显示全部楼层
本帖最后由 warship 于 2018-7-9 11:41 编辑

库函数版本的编译结果我就不贴了,
调用库函数,不停地压栈出栈,那效率就可想而知了。
只比较上述两种版本:
位操作版本:0x08000D58--0x08000D92
寄存器版本:0x08000D58--0x08000DAE
不到10行的源代码,编译出来的目标代码差了0xAE-0x92=0x1C
即寄存器版本多出来28个字节,
而且从目标代码来看,位操作直接精准地操作目标位,
而寄存器版本则,取出整个32位,进行AND/ORR逻辑运算,然后再写回结果,明显效率差了不少。
回复 支持 反对

使用道具 举报

  离线 

18

主题

509

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1138
金钱
1138
注册时间
2016-4-29
在线时间
174 小时
发表于 2018-7-9 12:37:03 | 显示全部楼层
以前的RAM都是省着用,标志位全部是定义成bit,不像现在的单片机资源那么丰富,只要能快速的把项目做出来,谁还去花时间去研究代码效率啊
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 12:57:43 | 显示全部楼层
d1z1y2 发表于 2018-7-9 12:37
以前的RAM都是省着用,标志位全部是定义成bit,不像现在的单片机资源那么丰富,只要能快速的把项目做出来, ...

有道理,一般人可以不用研究,
但官方的库还是要讲究效率的,个人觉得。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 16:27:40 | 显示全部楼层
意外的收获是发现使用位带操作寄存器可以使代码可阅读性更强,
有时候可以省略掉注释。
比如:原子的时钟初始化代码中有
while(!(RCC->CR>>17));                   //等待外部时钟就绪         //这种必须加注释,否则很难懂,时间长了自己也看不懂
用我上面介绍的位带操作则书写为: while(!bRCC_CR_HSERDY); 不加注释也可以明白

回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 16:32:49 | 显示全部楼层
不过,位带操作的局限性是如果同时给多个位赋值,就得分开操作各个位,这时候就不如直接操作寄存器本身了。
但是没有关系,可以采取操作单一比特时用位带,两个以上时仍读写整个寄存器的混合编程方式。
我试着对原子寄存器版本中的系统时钟初始化函数局部使用位带操作进行改写。
原版是:
void Stm32_Clock_Init(u8 PLL)
{
        unsigned char temp=0;   
        MYRCC_DeInit();                  //复位并配置向量表
        RCC->CR|=0x00010000;  //外部高速时钟使能HSEON
        while(!(RCC->CR>>17));//等待外部时钟就绪
        RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
        PLL-=2;                                  //抵消2个单位(因为是从2开始的,设置0就是2)
        RCC->CFGR|=PLL<<18;   //设置PLL值 2~16
        RCC->CFGR|=1<<16;          //PLLSRC ON
        FLASH->ACR|=0x32;          //FLASH 2个延时周期
        RCC->CR|=0x01000000;  //PLLON
        while(!(RCC->CR>>25));//等待PLL锁定
        RCC->CFGR|=0x00000002;//PLL作为系统时钟         
        while(temp!=0x02)     //等待PLL作为系统时钟设置成功
        {   
                temp=RCC->CFGR>>2;
                temp&=0x03;
        }   
}
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 16:33:30 | 显示全部楼层
本帖最后由 warship 于 2018-7-9 16:35 编辑

改写后变成(已经测试通过):
void Stm32_Clock_Init(u8 PLL)
{
        MYRCC_DeInit();          //复位并配置向量表
        bRCC_CR_HSEON=1;   
        while(!bRCC_CR_HSERDY);
        RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
        PLL-=2;                          //抵消2个单位(因为是从2开始的,设置0就是2)
        RCC->CFGR|=PLL<<18;   //设置PLL的倍频数 2~16
        bRCC_CFGR_PLLSRC=1;      
        FLASH->ACR|=0x32;           //FLASH 2个延时周期
        bRCC_CR_PLLON=1;           
        while(!bRCC_CR_PLLRDY);  
        bRCC_CFGR_SW_PLL=1;    //PLL作为系统时钟
        while(!bRCC_CFGR_SWS_PLL); //等待PLL作为系统时钟设置成功
}        
回复 支持 反对

使用道具 举报

  在线 

21

主题

190

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1667
金钱
1667
注册时间
2012-8-25
在线时间
426 小时
发表于 2018-7-9 16:58:25 | 显示全部楼层
本帖最后由 gotofly21 于 2018-7-9 17:06 编辑

说道寄存器位寻找,好像有一回我用位寻址设置和清除 TIMER的CEN,结果老是出错(大部分时候也对)。(关键有时候要单脉冲,有时候不需要单脉冲,就是想着需要单脉冲只要把单脉冲位赋值一次,后面就不管这个位,只要赋值cen就启动)。后来还是用CR1整体赋值就不出错了。也没时间查查为什么。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 2018-7-9 17:06:10 | 显示全部楼层
gotofly21 发表于 2018-7-9 16:58
说道寄存器位寻找,好像有一回我用位寻址设置和清除 TIMER的CEN,结果老是出错。后来还是用CR1整体赋值就不 ...

那可能是别名地址没有计算正确,
对照寄存器基址、偏移量和比特位,
编出一份别名地址宏定义的头文件后,
就是想出错也不容易的,哈哈。
回复 支持 反对

使用道具 举报

  在线 

21

主题

190

帖子

0

精华

金牌会员

Rank: 6Rank: 6

积分
1667
金钱
1667
注册时间
2012-8-25
在线时间
426 小时
发表于 2018-7-9 17:09:07 | 显示全部楼层
warship 发表于 2018-7-9 17:06
那可能是别名地址没有计算正确,
对照寄存器基址、偏移量和比特位,
编出一份别名地址宏定义的头文件后 ...

大部分时间是对的
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 7 天前 | 显示全部楼层
***************************************************************
在stm32f10x.h中,从第1267行开始,是外设存储器映射的宏定义
#define SRAM_BASE             ((uint32_t)0x20000000) /*!< SRAM base address in the alias region */
#define PERIPH_BASE           ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */

#define SRAM_BB_BASE          ((uint32_t)0x22000000) /*!< SRAM base address in the bit-band region */
#define PERIPH_BB_BASE        ((uint32_t)0x42000000) /*!< Peripheral base address in the bit-band region */
分别是SRAM区和片上外设起始地址和它们对应的别名区起始地址,不过看英文注释好像正好搞反了。

接下来从第1281行开始,是专门的片上外设基址宏定义,
它根据总线区的不同,分成了三段,分别是APB1段、APB2段、AHB段
#define APB1PERIPH_BASE       PERIPH_BASE
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
然后是各个外设的具体基址,具体定义值是上述各段基址加段内偏移
#define TIM2_BASE             (APB1PERIPH_BASE + 0x0000)
#define TIM3_BASE             (APB1PERIPH_BASE + 0x0400)
#define TIM4_BASE             (APB1PERIPH_BASE + 0x0800)
......
#define AFIO_BASE             (APB2PERIPH_BASE + 0x0000)
#define EXTI_BASE             (APB2PERIPH_BASE + 0x0400)
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
......
#define DMA1_BASE             (AHBPERIPH_BASE + 0x0000)
#define DMA1_Channel1_BASE    (AHBPERIPH_BASE + 0x0008)
#define DMA1_Channel2_BASE    (AHBPERIPH_BASE + 0x001C)
#define DMA1_Channel3_BASE    (AHBPERIPH_BASE + 0x0030)
......
不管怎么定义,
反正可以通过XXXX_BASE最后得到各外设的基址
回复 支持 反对

使用道具 举报

  离线 

499

主题

9万

帖子

31

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
129612
金钱
129612
注册时间
2010-12-1
在线时间
1163 小时
发表于 6 天前 | 显示全部楼层
不错。。。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 6 天前 | 显示全部楼层

多谢夸奖。
给个酷呗
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 6 天前 | 显示全部楼层
从第1463行开始就是各个外设寄存器的比特定义了:
对于具独立意义的比特定义采取了32位字中其它无关比特为0,自身所在比特为1的方式,如:
#define  RCC_CSR_LSIRDY                      ((uint32_t)0x00000002)
说明RCC外设的CSR寄存器中的比特位是第1位,而这样的定义正说明了STM官方的工程师们在编制库函数时,
完全放弃了位段操作的思想精髓,而捡起了用“与”、“或”操作来清位/置位的传统的、效率较低的方法。
唯一我可以为他们找到的理由是:因为有许多同时多比特操作的场合,所以二者同时兼顾起来有困难。
不管他了,我们要想定义位别名,就可以借用这个名称定义,并加一个前缀b,用来表示位别名。
即#define  bRCC_CSR_LSIRDY     BIT_ADDR(RCC_BASE+n, 1)  
括号中的1为比特位号,表示该比特在寄存器CSR中的第1比特,
RCC_BASE就是RCC的基址,直接借用就可以了。
还缺少一个数字,就是n, 这个n是某个特定寄存器在其外设基址基础之上的偏移量,
我没有在文件中找到,但偏移量以及比特位都可以在参考手册上查到。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 6 天前 | 显示全部楼层
还有一个需要统一约定的问题,那就是有些同类的外设可能有多个,但它们的寄存器名称及位定义是一致的。
如多个GPIO、多个TIM、多个DMA通道、多个SPI等等,则需要在宏定义名称中增加我们习惯的数字或字母以示区分,
如bGPIOE_ODR_ODR0、bSPI1_SR_BSY、bTIM2_SMCR_ECE等,它们各自的区别是基址不同,如TIM2_BASE、TIM3_BASE等等。
回复 支持 反对

使用道具 举报

  离线 

16

主题

502

帖子

1

精华

高级会员

Rank: 4

积分
861
金钱
861
注册时间
2018-5-11
在线时间
153 小时
 楼主| 发表于 6 天前 | 显示全部楼层
实际应用时,可以选择那些比较重要,操作比较频繁的位来优先进行定义和使用,
为了便于理解和记忆,也可以完全自己编制宏名称,如:bRCC_CR_HSIRDY,直接定义成bHSI_Ready
不过,为了查找来龙去脉,可以通过宏定义加壳的方式二次定义,#define  bHSI_Ready  bRCC_CSR_LSIRDY
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则




关闭

"原子哥”推荐上一条 /1 下一条

QQ|联系我们|手机版|官方淘宝店|新浪微博|微信公众平台|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2018-7-17 08:24

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表
/* */