新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計應(yīng)用 > Keil創(chuàng)建新的STM32工程以及CortexM3的位帶操作

Keil創(chuàng)建新的STM32工程以及CortexM3的位帶操作

作者: 時間:2016-11-19 來源:網(wǎng)絡(luò) 收藏
  上周實驗課照例很水,首先是準(zhǔn)備工作沒做好,J-Link的驅(qū)動沒裝好,而且由于機房電腦本身的問題好多機子無法正確裝驅(qū)動,或者在進入keil后會彈出莫名錯誤、閃退等情況,方老師說得好,當(dāng)我們浪費時間再做這些事情的時候(浪費時間很大程度上是因為機房電腦造成的),好一點的學(xué)校早就在寫程序了。這么多時間已經(jīng)浪費了,還有多少能剩下來看代碼進而理解它呢?

  從新建一個工程開始學(xué)習(xí),再加上上周實驗課的位帶操作相關(guān)內(nèi)容,有需要的同學(xué)可以看看,也希望指正相關(guān)錯誤:)

本文引用地址:http://butianyuan.cn/article/201611/318090.htm

1.新建工程

  在keil中新建一個基于51的工程挺簡單,不過新建一個STM32工程要復(fù)雜一些,多了一些步驟,需要建立更詳細(xì)的工程目錄,導(dǎo)入一些CMSIS(Cortex Microcontroller Software Interface Standard)文件、標(biāo)準(zhǔn)外設(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)

  這一行第一眼看去不明覺厲。。需要仔細(xì)想想。把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 00 00101 10101010 0101
Reset(VarAddr, 0)之后0 00 00101 10101010 0100
再Set(VarAddr, 0)之后0 00 00101 10101010 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 00 00101 10101010 0101
Reset(VarAddr, 11)之后0 00 0010100101010 0101
再Set(VarAddr, 11)之后0 00 0010110101010 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 00 00101 10101010 0101
Set(VarAddr, 31)之后100 0010110101010 0101
再Reset(VarAddr, 31)之后000 0010110101010 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)的認(rè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)容。

以后看有時間能不能再仔細(xì)研究一下



評論


技術(shù)專區(qū)

關(guān)閉