Keil創建新的STM32工程以及CortexM3的位帶操作
從新建一個工程開始學習,再加上上周實驗課的位帶操作相關內容,有需要的同學可以看看,也希望指正相關錯誤:)
本文引用地址:http://cqxgywz.com/article/201611/318090.htm1.新建工程
在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%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);| 位帶區地址(序號) | …… | 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=%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);| 位帶區地址(序號) | …… | 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=%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);| 位帶區地址(序號) | …… | 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位帶操作
S?T?M?3?2?位?帶?介?紹
還沒重看程序,我覺著我們只需要改相應地址的位帶別名區的內容(最低位)就好,而改完之后,就由ARM內核自動完成了“位帶”功能,即在發現位帶別名區改變之后,自動改變相應的位帶區內容。
以后看有時間能不能再仔細研究一下


評論