Keil創(chuàng)建新的STM32工程以及CortexM3的位帶操作
從新建一個工程開始學(xué)習(xí),再加上上周實驗課的位帶操作相關(guān)內(nèi)容,有需要的同學(xué)可以看看,也希望指正相關(guān)錯誤:)
本文引用地址:http://m.butianyuan.cn/article/201611/318090.htm1.新建工程
在keil中新建一個基于51的工程挺簡單,不過新建一個STM32工程要復(fù)雜一些,多了一些步驟,需要建立更詳細的工程目錄,導(dǎo)入一些CMSIS(Cortex Microcontroller Software Interface Standard)文件、標準外設(shè)驅(qū)動文件、啟動文件等等,并要進行一些參數(shù)設(shè)置。下面這篇博客已經(jīng)說明得挺好了(在SITP中我也是參考的這篇博客),因此不再贅述。
http://www.cnblogs.com/emouse/archive/2012/03/01/2375146.html
至于相關(guān)的文件,可以在網(wǎng)上找到,我也在百度網(wǎng)盤里面?zhèn)髁艘粋€上學(xué)期總線課用到的無線模塊通信的工程,可以在里面找到所需的文件。
2.位帶操作
先摘抄一些書上的內(nèi)容,再結(jié)合代碼分析
圖2.1 Cortex ‐ M3預(yù)定義的存儲器映射
注:根據(jù)我的理解,右邊那一大塊,對應(yīng)的地址 從0x0 0 到0xFFFF FFFF 是存儲器映射地址,通俗一點說就是序號,每一個地址(序號)對應(yīng)內(nèi)存中的一個字節(jié)的區(qū)域
2.1
在Cortex-M3中,有兩個區(qū)中實現(xiàn)了位帶(Bit Band)操作,其一是內(nèi)部SRAM區(qū)最低的1MB范圍,其二是片內(nèi)外設(shè)去的最低1MB范圍,這兩個區(qū)中的地址還有自己的位帶別名區(qū)(Bit Band Alias Region)。位帶別名區(qū)把每個比特膨脹成一個32位的字,當(dāng)通過位帶別名區(qū)訪問這些字時,就可以達到訪問原始比特的目的。
圖2.2位帶區(qū)與位帶別名區(qū)的膨脹映射關(guān)系
注1:和圖2.1一樣,圖上顯示的是地址,也就是序號,就像是第幾號房間,而每個房間里面有8張床 (8個格子) (8bit 可以存東西的空間) 可以放0/1
注2:上半部分位帶別名區(qū),從起始地址開始,每4個序號(如0x2200 0~ 0x2200 3)對應(yīng)下半部分位帶區(qū)的一個序號中的一個位(如0x2 0. 0),這樣就把位帶區(qū)的1位擴展成了32位(4個序號 ,每個序號對應(yīng)內(nèi)存中的1字節(jié)=8位,4×8=32,更具體一點,就是0x2200 0.0 ~ 0x2200 3.7的空間),即1字。
/*
在位帶區(qū),每個比特都映射到別名地址區(qū)的一個字,該字只有最低位有效。當(dāng)一個別名地址被訪問時,會先把該地址變換成位帶地址。對于讀操作,讀取位帶地址中的一個字,在把需要的位右移到需要的最低位并把最低位返回。對于寫操作,把需要寫的位左移至對應(yīng)的位序號出,然后執(zhí)行一個原子的“讀——改——寫”過程。
*/
對于內(nèi)部SRAM位帶區(qū)的某個比特,記它所在的字節(jié)的地址為Addr,字節(jié)中位序號為n(0≤n≤7),則該比特在別名區(qū)的地址為:
AliasAddr = 0x2200 0 + ( ( Addr – 0x2 0 ) × 8 + n ) × 4
= 0x2200 0 + ( Addr - 0x2 0 )×32 + n × 4 (轉(zhuǎn)換公式)
上式中 × 4 是因為 1字 = 4字節(jié), × 8 表示 1字節(jié) = 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是位帶區(qū)的起始地址,RAM_BB_BASE是位帶別名區(qū)的起始地址,這也可以從圖2.1 看出。
然后是三個宏定義#define,可以看成是三個函數(shù),其中是續(xù)行符,表示下面一行是緊接著當(dāng)前行的,一般用于將很長的代碼語句分幾段寫,但要注意 后面除了換行回車不能有任何字符,空格也不行。
接下來以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 定義成這樣一個函數(shù),它的兩個參數(shù)是(VarAddr, BitNumber)
//用這種形式表示
void Reset(VarAddr, BitNumber){
首先是 (RAM_BB_BASE ((VarAddr - RAM_BASE) << 5) ((BitNumber) << 2)) 假設(shè)得到的結(jié)果是A
然后是 (__IO uint32_t *) A 這是強制類型轉(zhuǎn)換,即把A 的類型轉(zhuǎn)換成__IO uint32_t的指針,假設(shè)結(jié)果是B,即B =(__IO uint32_t *) A
接下來 * B 即取 B的內(nèi)容,假設(shè)C = *B
最后是 C = 0
}
接著再來具體分析一下函數(shù)里面的過程,也就是轉(zhuǎn)換公式的實現(xiàn)部分( RAM_BB_BASE ( (VarAddr - RAM_BASE) << 5) ( (BitNumber) << 2) )
VarAddr對應(yīng)于公式中的 Addr ,BitNumber 對應(yīng)于 n
(1) 首先,二進制數(shù)左移n位,就相當(dāng)于乘以2的n次方(D),因此
(VarAddr - RAM_BASE) << 5 就相當(dāng)于(VarAddr - RAM_BASE) × 32 ,(BitNumber) << 2相當(dāng)于(BitNumber) × 4
對照轉(zhuǎn)換公式可以發(fā)現(xiàn)兩者很像了,如果 按位或 和 公式中的 + 在這里能得到相同的結(jié)果,那么這個函數(shù)就可以用了。下面來看看是不是這樣。
(2)
RAM_BB_BASE=0x2200 0,轉(zhuǎn)換成二進制就是
0010 0010 0 0 0 0 0 0
由于低25位全都是0(加上一個數(shù)肯定不會產(chǎn)生進位),因此 與一個數(shù)相加 和 與一個數(shù)按位或 的結(jié)果是一樣的(但是 或 更快),當(dāng)然這個數(shù)不能超過25位。
而位帶區(qū)地址最多是0x2 0開始的1MB=2^20范圍,也就是VarAddr-RAM_BASE涉及到的范圍是在0x00 ~ 0xFFFFF,也即
0 0 0 0 0 ~ 1 1 1 1 1 (20位)
左移5位后也就是最多25位,因此符合上面的條件,按位或和加法等效,不存在進位問題,精妙的設(shè)計!
每一個序號(地址)對應(yīng)的內(nèi)存中有8位,也就是說BitNumber范圍是0~7 (0 ~ 0),左移2位后是 00 ~ 00,不超過VarAddr-RAM_BASE左移5位 后多出來的0,因此也符合條件,按位或和加法等效。
因此( RAM_BB_BASE ( (VarAddr - RAM_BASE) << 5) ( (BitNumber) << 2) ) = 轉(zhuǎn)換公式的右邊
再回到(*(__IO uint32_t *) (RAM_BB_BASE ((VarAddr - RAM_BASE) << 5) ((BitNumber) << 2)) = 0) ,在剛才假設(shè)的那個void Reset(VarAddr, BitNumber) 函數(shù)中,A(或者說是B)代表變量在位帶別名區(qū)的對應(yīng)地址(序號),C=*B,也就是取內(nèi)容,即取這個序號的內(nèi)容(其實是由此開始的4個序號,即 32位 )。
若要Reset,則C = 0,若要Set 則 C = 1(不用 =31,因為該字只有最低位有效)。由此實現(xiàn)了:若要對位帶別名區(qū)的地址(序號)中的內(nèi)容進行復(fù)位/置位(其實是寫一個字進去),就可以改變位帶區(qū)中對應(yīng)的地址中的某一位的值(比如),而這個位帶別名區(qū)的地址只需通過VarAddr = (uint32_t)&Var得到在位帶區(qū)中的地址,并把要改動該地址中的哪一位(BitNumber)一起作為參數(shù)給 Reset /Set 函數(shù)即可 的功能。而GetBit則是到*B為止,即return位帶別名區(qū)地址的內(nèi)容(32位)。
但是現(xiàn)在有個問題:VarAddr = (uint32_t)&Var只是從Var的開始地址算起,Offset = VarAddr - BB_BASE已經(jīng)固定了,無法轉(zhuǎn)到下一個字(n=0~7),這要是Var這個變量過大,那不就夠不著對應(yīng)的位帶別名區(qū)了。。?
2.2.2
結(jié)合代碼中調(diào)用這幾個“函數(shù)”的部分來看,會有進一步發(fā)現(xiàn)。
//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%xn",Var);4 5 Var_SetBit_BB(VarAddr, 0); /* Var = 0x05AA5 = 0 0 0 0 0101 1010 1010 0101 */6 printf("VAR=0x%xn",Var);
位帶區(qū)地址(序號) | …… | 0x2 3.(7 ~ 0) | 0x2 2.(7 ~ 0) | 0x2 1.(7 ~ 0) | 0x2 0.(7 ~ 0) |
位帶區(qū)地址對應(yīng)的內(nèi)容.(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得到的結(jié)果是把第一個地址的.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=%xn",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=%xn",VarBitValue);
位帶區(qū)地址(序號) | …… | 0x2 3.(7 ~ 0) | 0x2 2.(7 ~ 0) | 0x2 1.(7 ~ 0) | 0x2 0.(7 ~ 0) |
位帶區(qū)地址對應(yīng)的內(nèi)容.(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得到的結(jié)果是把第一個地址的.11位(第12位)變成了0,Set后又回到了1。從這里這里看到,當(dāng)要改變下一個地址中的內(nèi)容時,不是用VarAddr+1,而是用更大的BitNumber,即n不限制在0~7,而是0~31,或者更大。這樣又出現(xiàn)了一個問題:之前說到的
( RAM_BB_BASE ( (VarAddr - RAM_BASE) << 5) ( (BitNumber) << 2) ) = 轉(zhuǎn)換公式的右邊
前提之一是 n=0~7,即只與低位的0按位或,這樣才和加法是等效的。
結(jié)合2.2.1最后提到的問題,其實這也是一種等效。當(dāng)BitNumber > 7 時,可以看成是一次進位。
假設(shè)VarAddr = 0x2 0 ,當(dāng)BitNumber = 7, 則指的是 0x2 0.7 即第1個地址的第8位
當(dāng)BitNumber = 11 = 7 + 4 ,相當(dāng)于VarAddr+1,指的是0x2 1.3 即第2個地址的第四位
因此BitNumber 和 VarAddr就像個位和十位的關(guān)系一樣,不過是逢八進一。而且這樣做有個好處,只需要改BitNumber就行,而不需要同時改BitNumber和VarAddr,比如在Var_ResetBit_BB(VarAddr, BitNumber)函數(shù)中不用Var_ResetBit_BB(VarAddr+1, BitNumber)了,而是直接根據(jù)需要,修改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=%xn",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);
位帶區(qū)地址(序號) | …… | 0x2 3.(7 ~ 0) | 0x2 2.(7 ~ 0) | 0x2 1.(7 ~ 0) | 0x2 0.(7 ~ 0) |
位帶區(qū)地址對應(yīng)的內(nèi)容.(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 |
結(jié)果和(2)中得到的結(jié)論相符。
2.2.3
到這兒基本上把位帶操作及其實現(xiàn)的基礎(chǔ)部分寫完了,最后再來一發(fā)從位帶別名區(qū)地址反推回位帶區(qū)地址的過程,備忘。
假設(shè) 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,可以看成是位帶別名區(qū)的起始0x2200 0
第2段,就是offset=VarAddr - BB_Base ,也即相對位帶區(qū)的偏移0 0 0101 0 0 001 =0 0010 1 0 1 = 0x02801
第3段,右移兩位后就是BitNumber啦 右移后得到011 = 3
這樣可以得到相應(yīng)的位帶區(qū) 地址.位 是 (0x2 0 + 0x0 2801) . 3= (0x2 2801) . 3
再加一點內(nèi)容吧,關(guān)于內(nèi)存對齊,來自C語言吧
如果你了解體系結(jié)構(gòu),就會知道,計算機內(nèi)存尋址并不是一個字節(jié)一個字節(jié)讀取的,而是一次讀取多個,比如32bit數(shù)據(jù)線的計算機就可一次讀取4字節(jié),既一個int值.這時就出現(xiàn)問題了,比如你在結(jié)構(gòu)體中定義如下:
struct a{
char c;
int i;
}
那么計算機在內(nèi)存中該如何存放呢?
比較笨的辦法是c占第一個字節(jié),i占用2-5字節(jié).那么假設(shè)你的程序正好處于尋址邊界,比如0x0這樣的位置,那么計算機為了獲取i,就必須先獲取1-4字節(jié),然后左移8位,再獲取5-8字節(jié),右移24位,然后再相加,才能得到i,無意這種辦法是比較傻的.所以計算機在處理這種問題的時候,往往會將內(nèi)存按4字節(jié)對齊,比如c占用第一字節(jié),i占用5-8字節(jié).這樣i就和c在4位上對齊了.相當(dāng)于我們寫字一行不夠了,干脆就不寫在一行,直接重起一行.主要是方便尋址,提高性能.
】
沒想到寫這篇博客花了這么長時間,對于位帶操作及其實現(xiàn)的認識也是反反復(fù)復(fù),寫的時候再一思考發(fā)現(xiàn)部分原來的理解是錯誤的。
如果還有其他錯誤,還希望讀到這一篇文章的你能夠幫忙指正,也幫助我學(xué)習(xí) :)
參考資料: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 《嵌入式系統(tǒng)及其應(yīng)用》_同濟大學(xué)出版社 P37~P42
補充一句:
其實一般來說,初學(xué)不需要掌握函數(shù)內(nèi)部的知識、過程,只需要知道怎么用就好了,《碼農(nóng)的原罪》里面有一句“沒必要就不用學(xué),有必要的時候你自然就會了。”
剛?cè)腴T時學(xué)會新建一個工程、導(dǎo)入文件、相關(guān)設(shè)置才是更重要的。
2014.5.20補充
由同學(xué)指出,新發(fā)現(xiàn)一點疑問,就是這段話
在位帶區(qū),每個比特都映射到別名地址區(qū)的一個字,該字只有最低位有效。當(dāng)一個別名地址被訪問時,會先把該地址變換成位帶地址。對于讀操作,讀取位帶地址中的一個字,在把需要的位右移到需要的最低位并把最低位返回。對于寫操作,把需要寫的位左移至對應(yīng)的位序號出,然后執(zhí)行一個原子的“讀——改——寫”過程。
參考下面兩篇
STM32位帶操作
S?T?M?3?2?位?帶?介?紹
還沒重看程序,我覺著我們只需要改相應(yīng)地址的位帶別名區(qū)的內(nèi)容(最低位)就好,而改完之后,就由ARM內(nèi)核自動完成了“位帶”功能,即在發(fā)現(xiàn)位帶別名區(qū)改變之后,自動改變相應(yīng)的位帶區(qū)內(nèi)容。
以后看有時間能不能再仔細研究一下
評論