上星期试验课按例很水,首要是准备工作没做好,J-Link的驱动没装好,并且咱们机房电脑自身的问题很多机子无法正确装驱动,或许在进入keil后会弹出莫名过错、闪退等状况,方教师说得好,当咱们浪费时刻再做这些工作的时分(浪费时刻很大程度上是由于机房电脑形成的),好一点的校园早就在写程序了。这么多时刻现已浪费了,还有多少能剩余来看代码轿车了解它呢?
从新建一个工程开端学习,再加上上星期试验课的位带操作相关内容,有需求的同学可以看看,也期望纠正相关过错:)
1.新建工程
在keil中新建一个依据51的工程挺简略,不过新建一个STM32工程要杂乱一些,多了一些进程,需求树立更详细的工程目录,导入一些CMSIS(Cortex Microcontroller Software Interface Standard)文件、规范外设驱动文件、发动文件等等,并要进行一些参数设置。下面这篇博客现已阐明得挺好了(在SITP中我也是参阅的这篇博客),因而不再赘述。
http://www.cnblogs.com/emouse/archive/2012/03/01/2375146.html
至于相关的文件,可以在网上找到,我也在百度网盘晒干传了一个上学期总线课用到的无线模块通讯的工程,可以在晒干找到所需的文件。
2.位带操作
先摘录一些书上的内容,再结合代码剖析
图2.1 Cortex ‐ M3预界说的存储器映射
注:依据我的了解,右边那一大块,对应的地址 从0x0 0 到0xFFFF FFFF 是存储器映射地址,浅显一点说便是序号,每一个地址(序号)对应内存中的一个字节的区域
2.1
在Cortex-M3中,有两个区中完成了位带(Bit Band)操作,其一是内部SRAM区最低的1MB规模,其二是片表里设去的最低1MB规模,这两个区中的地址还有自己的位带别号区(Bit Band Alias Region)。位带别号区把每个比特胀大成一个32位的字,当经过位带别号区拜访这些字时,就可以到达拜访原始比特的意图。
图2.2位带区与位带别号区的胀大映射联系
注1:和图2.1相同,图上显现的是地址,也便是序号,就像是第几号房间,而每个房间晒干有8张床 (8个格子) (8bit 可以存东西的空间) 可以放0/1
注2:上半部分位带别号区,从开端地址开端,每4个序号(如0x2200 0~ 0x2200 3)对应下半部分位带区的一个序号中的一个位(如0x2 0. 0),这样就把位带区的1位扩展成了32位(4个序号 ,每个序号对应内存中的1字节=8位,4×8=32,更详细一点,便是0x2200 0.0 ~ 0x2200 3.7的空间),即1字。
/*
在位带区,每个比特都映射到别号地址区的一个字,该字只要最低位有用。当一个别号地址被拜访时,会先把该地址变换成位带地址。关于读操作,读取位带地址中的一个字,在把需求的位右移到需求的最低位并把最低位回来。关于写操作,把需求写的位左移至对应的位序号出,然后碑文一个原子的“读——改——写”进程。
*/
关于内部SRAM位带区的某个比特,记它地点的字节的地址为Addr,字节中位序号为n(0≤n≤7),则该比特在别号区的地址为:
AliasAddr = 0x2200 0 + ( ( Addr – 0x2 0 ) × 8 + n ) × 4
= 0x2200 0 + ( Addr – 0x2 0 )×32 + n × 4 (转化公式)
上式中 × 4 是由于 1字 = 4字节, × 8 一共 1字节 = 8比特。
2.2
2.2.1
下面放上代码再详细阐明
1 /* Private define */2 #define RAM_BASE 0x203 #define RAM_BB_BASE 0x224 5 /* Private macro -*/6 #define Var_ResetBit_BB(VarAddr, BitNumber) \7 (*(__IO uint32_t *) (RAM_BB_BASE ((VarAddr - RAM_BASE) << 5) ((BitNumber) << 2)) = 0)8 9 #define Var_SetBit_BB(VarAddr, BitNumber) \10 (*(__IO uint32_t *) (RAM_BB_BASE ((VarAddr - RAM_BASE) << 5) ((BitNumber) << 2)) = 1)11 12 #define Var_GetBit_BB(VarAddr, BitNumber) \13 (*(__IO uint32_t *) (RAM_BB_BASE ((VarAddr - RAM_BASE) << 5) ((BitNumber) << 2)))14 15 /* Private variables */16 17 __IO uint32_t Var, VarAddr = 0, VarBitValue = 0;18 19 Var = 0x05AA5;20 21 VarAddr = (uint32_t)&Var;
首要是界说基址,RAM_BASE是位带区的开端地址,RAM_BB_BASE是位带别号区的开端地址,这也可以从图2.1 看出。
然后是三个宏界说#define,可以看成是三个函数,其间\是续行符,一共下面一行是紧接着当前行的,一般用于将很长的代码查办分几段写,但要留意 \ 后边除了换行回车不能有任何字符,空格也不可。
接下来以Var_ResetBit_BB为例:
#define Var_ResetBit_BB(VarAddr, BitNumber) (*(__IO uint32_t *) (RAM_BB_BASE ((VarAddr - RAM_BASE) << 5) ((BitNumber) << 2)) = 0)
这一行榜首眼看去不明觉厉。。需求细心想想。把Var_ResetBit_BB 界说成这样一个函数,它的两个参数是(VarAddr, BitNumber)
//用这种方式一共
void Reset(VarAddr, BitNumber){
首要是 (RAM_BB_BASE ((VarAddr - RAM_BASE) << 5) ((BitNumber) << 2)) 假定得到的成果是A
然后是 (__IO uint32_t *) A 这是强制类型转化,即把A 的类型转化成__IO uint32_t的指针,假定成果是B,即B =(__IO uint32_t *) A
接下来 * B 即取 B的内容,假定C = *B
最终是 C = 0
}
接着再来详细剖析一下函数晒干的进程,也便是转化公式的完成部分( RAM_BB_BASE ( (VarAddr - RAM_BASE) << 5) ( (BitNumber) << 2) )
VarAddr对应于公式中的 Addr ,BitNumber 对应于 n
(1) 首要,二进制数左移n位,就相当于乘以2的n次方(D),因而
(VarAddr - RAM_BASE) << 5 就相当于(VarAddr - RAM_BASE) × 32 ,(BitNumber) << 2相当于(BitNumber) × 4
对照转化公式可以发现两者很像了,假如 按位或 和 公式中的 + 在这儿能得到相同的成果,那么这个函数就可以用了。下面来看看是不是这样。
(2)
RAM_BB_BASE=0x2200 0,转化成二进制便是
0010 0010 0 0 0 0 0 0
咱们低25位全都是0(加上一个数必定不会发生进位),因而 与一个数相加 和 与一个数按位或 的成果是相同的(可是 或 更快),当然这个数不能超越25位。
而位带区地址最多是0x2 0开端的1MB=2^20规模,也便是VarAddr-RAM_BASE涉及到的规模是在0x00 ~ 0xFFFFF,也即
0 0 0 0 0 ~ 1 1 1 1 1 (20位)
左移5位后也便是最多25位,因而契合上面的条件,按位或和加法等效,不存在进位问题,精妙的规划!
每一个序号(地址)对应的内存中有8位,也便是说BitNumber规模是0~7 (0 ~ 0),左移2位后是 00 ~ 00,不超越VarAddr-RAM_BASE左移5位 后多出来的0,因而也契合条件,按位或和加法等效。
因而( RAM_BB_BASE ( (VarAddr - RAM_BASE) << 5) ( (BitNumber) << 2) ) = 转化公式的右边
再回到(*(__IO uint32_t *) (RAM_BB_BASE ((VarAddr - RAM_BASE) << 5) ((BitNumber) << 2)) = 0) ,在方才假定的那个void Reset(VarAddr, BitNumber) 函数中,A(或许说是B)代表变量在位带别号区的对应地址(序号),C=*B,也便是取内容,即取这个序号的内容(其实是由此开端的4个序号,即 32位 )。
若要Reset,则C = 0,若要Set 则 C = 1(不必 =31,由于该字只要最低位有用)。由此完成了:若要对位带别号区的地址(序号)中的内容进行复位/置位(其实是写一个字进去),就可以改动位带区中对应的地址中的某一位的值(比方),而这个位带别号区的地址只需经过VarAddr = (uint32_t)&Var得到在位带区中的地址,并把要改动该地址中的哪一位(BitNumber)一同作为参数给 Reset /Set 函数即可 的功用。而GetBit则是到*B学校,即return位带别号区地址的内容(32位)。
可是现在有个问题:VarAddr = (uint32_t)&Var仅仅从Var的开端地址算起,Offset = VarAddr - BB_BASE现已固定了,无法转到下一个字(n=0~7),这要是Var这个变量过大,那不就够不着对应的位带别号区了。。?
2.2.2
结合代码中调用这几个“函数”的部分来看,会有进一步发现。
//Var = 0x05AA5;
//VarAddr = (uint32_t)&Var;
(1)
1 /* Modify Var variable bit 0 --*/2 Var_ResetBit_BB(VarAddr, 0); /* Var = 0x05AA4 = 0 0 0 0 0101 1010 1010 0100 */3 printf("VAR=0x%x\n",Var);4 5 Var_SetBit_BB(VarAddr, 0); /* Var = 0x05AA5 = 0 0 0 0 0101 1010 1010 0101 */6 printf("VAR=0x%x\n",Var);
位带区地址(序号) | …… | 0x2 3.(7 ~ 0) | 0x2 2.(7 ~ 0) | 0x2 1.(7 ~ 0) | 0x2 0.(7 ~ 0) |
位带区地址对应的内容.(7 ~ 0) | …… | 0 0 | 0 0 | 0101 1010 | 1010 0101 |
Reset(VarAddr, 0)之后 | 0 0 | 0 0 | 0101 1010 | 1010 0100 | |
再Set(VarAddr, 0)之后 | 0 0 | 0 0 | 0101 1010 | 1010 0101 |
BitNumber=0,Reset得到的成果是把榜首个地址的.0位(第1位)变成了0,Set后又回到了1。
(2)
1 /* Modify Var variable bit 11 --*/2 Var_ResetBit_BB(VarAddr, 11); /* Var = 0x052A5 = 0 0 0 0 0101 0010 1010 0101*/3 printf("VAR=0x%x",Var);4 5 /* Get Var variable bit 11 value */6 VarBitValue = Var_GetBit_BB(VarAddr, 11); /* VarBitValue = 0x00 */7 printf(" Bit11=%x\n",VarBitValue);8 9 //******************************************10 Var_SetBit_BB(VarAddr, 11); /* Var = 0x05AA5 = 0 0 0 0 0101 1010 1010 0101*/11 printf("VAR=0x%x",Var);12 13 /* Get Var variable bit 11 value */14 VarBitValue = Var_GetBit_BB(VarAddr, 11); /* VarBitValue = 0x01 */15 printf(" Bit11=%x\n",VarBitValue);
位带区地址(序号) | …… | 0x2 3.(7 ~ 0) | 0x2 2.(7 ~ 0) | 0x2 1.(7 ~ 0) | 0x2 0.(7 ~ 0) |
位带区地址对应的内容.(7 ~ 0) | …… | 0 0 | 0 0 | 0101 1010 | 1010 0101 |
Reset(VarAddr, 11)之后 | 0 0 | 0 0 | 01010010 | 1010 0101 | |
再Set(VarAddr, 11)之后 | 0 0 | 0 0 | 01011010 | 1010 0101 |
BitNumber=0,Reset得到的成果是把榜首个地址的.11位(第12位)变成了0,Set后又回到了1。从这儿这儿看到,当要改动下一个地址中的内容时,不是用VarAddr+1,而是用更大的BitNumber,即n不约束在0~7,而是0~31,或许更大。这样又呈现了一个问题:之条件到的
( RAM_BB_BASE ( (VarAddr - RAM_BASE) << 5) ( (BitNumber) << 2) ) = 转化公式的右边
条件之一是 n=0~7,即只与低位的0按位或,这样才和加法是等效的。
结合2.2.1最终说到的问题,其实这也是一种等效。当BitNumber > 7 时,可以看成是一次进位。
假定VarAddr = 0x2 0 ,当BitNumber = 7, 则指的是 0x2 0.7 即第1个地址的第8位
当BitNumber = 11 = 7 + 4 ,相当于VarAddr+1,指的是0x2 1.3 即第2个地址的第四位
因而BitNumber 和 VarAddr就像个位和十位的联系相同,不过是逢八进一。并且这样做有个优点,只需求改BitNumber就行,而不需求一起改BitNumber和VarAddr,比方在Var_ResetBit_BB(VarAddr, BitNumber)函数中不必Var_ResetBit_BB(VarAddr+1, BitNumber)了,而是直接依据需求,修正BitNumber就行。
别的,在Set今后也可以看到,VarBitValue变成了1(即只要一个字中的最低位变成了1)。
(3)
1 /* Modify Var variable bit 31 --*/2 Var_SetBit_BB(VarAddr, 31); /* Var = 0x85AA5 = 0x1 0 0 0 0101 1010 1010 0101 */3 printf("VAR=0x%x",Var);4 5 /* Get Var variable bit 31 value */6 VarBitValue = Var_GetBit_BB(VarAddr, 31); /* VarBitValue = 0x01 */7 printf(" Bit31=%x\n",VarBitValue);8 9 //******************************************10 Var_ResetBit_BB(VarAddr, 31); /* Var = 0x05AA5 = 0x0 0 0 0 0101 1010 1010 0101 */11 printf("VAR=0x%x",Var);12 13 /* Get Var variable bit 31 value */14 VarBitValue = Var_GetBit_BB(VarAddr, 31); /* VarBitValue = 0x00 */15 printf(" Bit31=%x",VarBitValue);
位带区地址(序号) | …… | 0x2 3.(7 ~ 0) | 0x2 2.(7 ~ 0) | 0x2 1.(7 ~ 0) | 0x2 0.(7 ~ 0) |
位带区地址对应的内容.(7 ~ 0) | …… | 0 0 | 0 0 | 0101 1010 | 1010 0101 |
Set(VarAddr, 31)之后 | 10 | 0 0 | 01011010 | 1010 0101 | |
再Reset(VarAddr, 31)之后 | 00 | 0 0 | 01011010 | 1010 0101 |
成果和(2)中得到的序幕相符。
2.2.3
到这儿基本上把位带操作及其完成的根底部分写完了,最终再来一发从位带别号区地址反推回位带区地址的进程,备忘。
假定 AliasAddr = 0x2200 002C = 0010 0010 0 0101 0 0 0010 1100
可以把AliasAddr分红3段,1[0010 001] 2[0 0 0101 0 0 001] 3[0 1100]
第1段,在后边补上25个0,可以看成是位带别号区的开端0x2200 0
第2段,便是offset=VarAddr - BB_Base ,也即相对位带区的偏移0 0 0101 0 0 001 =0 0010 1 0 1 = 0x02801
第3段,右移两位后便是BitNumber啦 右移后得到011 = 3
这样可以得到相应的位带区 地址.位 是 (0x2 0 + 0x0 2801) . 3= (0x2 2801) . 3
再加一点内容吧,关于内存对齐,来自C言语吧
假如你了解体系结构,就会知道,计算机内存寻址并不是一个字节一个字节读取的,而是一次读取多个,比方32bit数据线的计算机就可一次读取4字节,既一个int值.这时就呈现问题了,比方你在结构体中界说如下:
struct a{
char c;
int i;
}
那么计算机在内存中该怎样寄存呢?
比较笨的方法是c占榜首个字节,i占用2-5字节.那么假定你的程序正优点于寻址鸿沟,比方0x0这样的方位,那么计算机为了获取i,就必须先获取1-4字节,然后左移8位,再获取5-8字节,右移24位,然后再相加,才干得到i,无意这种方法是比较傻的.所以计算机在处理这种问题的时分,往往会将内存按4字节对齐,比方c占用榜首字节,i占用5-8字节.这样i就和c在4位上对齐了.相当于咱们写字一行不够了,爽性就不写在一行,直接重起一行.主要是便利寻址,进步功用.
】
没想到写这篇博客花了这么长时刻,关于位带操作及其完成的知道也是反反复复,写的时分再一考虑发现部分本来的了解是过错的。
假如还有其他过错,还期望读到这一篇文章的你可以协助纠正,也协助我学习 🙂
参阅资料:1http://www.cnblogs.com/emouse/archive/2012/03/01/2375146.html
2http://blog.chinaunix.net/uid-26285146-id-3071387.html
3http://www.amobbs.com/thread-5464765-1-1.html
4http://tieba.baidu.com/p/2138813
5 《嵌入式体系及其使用》_同济大学出版社 P37~P42
弥补一句:
其实一般来说,初学不需求把握函数内部的常识、进程,只需求知道怎样用就好了,《码农的原罪》晒干有一句“没必要就不必学,有必要的时分你天然就会了。”
刚入门时学会新建一个工程、导入文件、相关设置才是更重要的。
2014.5.20弥补
由同学指出,新发现一点疑问,便是这段话
在位带区,每个比特都映射到别号地址区的一个字,该字只要最低位有用。当一个别号地址被拜访时,会先把该地址变换成位带地址。关于读操作,读取位带地址中的一个字,在把需求的位右移到需求的最低位并把最低位回来。关于写操作,把需求写的位左移至对应的位序号出,然后碑文一个原子的“读——改——写”进程。
参阅下面两篇
STM32位带操作
STM32位带介绍
还没重看程序,我觉着咱们只需求改相应地址的位带别号区的内容(最低位)就好,而改完之后,就由ARM内核主动完成了“位带”功用,即在发现位带别号区改动之后,主动改动相应的位带区内容。
今后看有时刻能不能再细心研究一下