crh寄存器_STM32 学习笔记(寄存器)---2
故事很多,我打算用一輩子來跟你講,你準備好了嗎?(狗頭
沒必要一次看完,多看幾遍消化消化。
所有操作,最終目的都是操作寄存器
stm32編程實質上是修改寄存器的32位的具體的值
單片機
sfr P0=0x80; //關鍵字sfr 聲明地址和名稱的映射
P0=0x00; //將0x00賦值給P0口的8位(51單片機一組IO為8位)
STM32
GPIOA->ODR=0x00000000 //為GPIOA的ODR寄存器地址賦值0x00000000
什么是寄存器?
寄存器是中央處理器內的組成部分,它可以從其他寄存器,或者內存中獲取指令,地址,數據然后給CPU用,有時候 由于內存太慢,內存和寄存器之間要加一個叫Cache的東西,為了彌補內存的不足,內存-》Cache-》寄存器-》CPU。
指令、地址寄存器與數據寄存器都類似,里邊存放的都是0和1,畢竟單片機也只認識機器碼,機器碼都是0或1,只是特別的規定下,數據寄存器里面存放的0和1表示數據,指令寄存器里存放的表示指令。
既然有不同的寄存器,那CPU怎么去區分這些寄存器?
---通過地址,STM32給不同的寄存器分配了不同的地址。
怎么找到某個寄存器的地址?---查看數據手冊
補充:32位cpu代表了什么,stm32也是32位
代表的是數據總線32位,也就是說cpu一次可以處理32位數據,而上面我們說了,cpu要從寄存器取數據,所以它寄存器也是32位的,所以32位代表什么?代表它的寄存器是32位的。
敲黑板:stm32的寄存器是32位的,都是存儲8位16進制的數或者32位2進制的01數字。
什么是地址映射?
所謂地址映射,就是將芯片上的寄存器 甚至I/O等資源與地址建立一一對應的關系。
如果某地址對應著某寄存器,我們就可以運用C語言的指針來尋址并修改這個地址上的內容,從而實現修改該寄存器的內容。
所以我們能操作IO資源(如引腳)的根本原因是,這些引腳由于地址映射與地址建立了對應關系,而 部分地址 又和 寄存器建立了對應關系,所以,我們說操作某些資源就是操作某些寄存器應該不難理解了
IO資源---地址---寄存器
正是因為頭文件中有了對于各種 寄存器 和 I/O端口 的地址映射,我們才可以在51單片機程序中方便地使用P2^0 =0xFF; TMOD =0xFF等賦值句子對寄存器進行配置,從而控制單片機。
我們可以看出地址才最重要的紐帶,那么我們怎么去訪問地址,能訪問多少?怎么抽象出地址這一概念的?是不是得有一些東西支撐?這個東西就是地址總線。cpu通過地址總線訪問地址,我們操作地址當然也是通過地址總線啦,因為我們操作著CPU嘛(滑稽),假如一個芯片有32根地址總線,每根線有兩種狀態(0/1)所以32根能表示2^32個狀態,一個狀態代表一個地址,所以這一共有4G的地址能表示(訪問)。
2^10 = 1024 = 1Kb
2^30 = 1024x1024x1024= 1024*(1024*1kb) = 1024Mb = 1G
2^32 = 1G x 4= 4G
APB2總線就是指特定的一段地址,如:0x4001 0000—XXXX
然后官方書上有段話,大致意思:你能理解這句話的深刻含義嗎?
程序存儲器、數據存儲器、寄存器和輸入輸出端口 被 組織在同一個4GB的線性地址空間內。
說到映射大家可能就會想到函數映射,腦海里會有一個畫面:左邊一個集合中的某個元素“射”出一條帶箭頭的直線指向右邊的集合的某個元素。如果你高數或離散數學再好一點兒的話會想到單射、雙射、滿射、恒等映射(你要是這個都想到了,那你牛B!)……其實外圍設備的內存映射原理是一樣的,只不過左邊的集體變成了CPU,右邊的集合變成了外圍設備,那條帶箭頭的線就是連接CPU和外設地址引腳的地址總線。
學習stm32最困難的地方,就是理解功能對應的寄存器的邏輯關系,甚至一個簡單的功能往往是多個寄存器的調用的結果,理解寄存器之間的關系,學會查看寄存器的值。
以GPIOA為例說明STM32寄存器和名稱的映射
GPIOA下的某個寄存器,掛載在GPIOA下,地址為GPIOA基地址+偏移量
GPIOA掛載在APB2總線,地址為APB2總線基地址+GPIOA偏移量
ABP2掛載加外設基地址,地址為外設基地址+ABP2偏移量
源碼中可以找到:
//外部總線基地址 8位16進行不剛好是32位2進制嗎?---地址總線的作用別忘了
#define PERIPH_BASE ((uint32_t)0x40000000)
//APB2基地址=外部總線基地址+偏移量
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
//GPIOA基地址=APB2基地址+偏移量
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
//GPIOA將地址順序分配給7個32位寄存器(結構體分配)
#define GPIOA ((GPIO_TypeDef*)GPIOA_BASE)
/將寄存器地址映射到7個32位寄存器,分別控制
typedef struct
{
IO unit32_t CRL;
IO unit32_t CRH;
IO unit32_t ODR;
IO unit32_t IDR;
IO unit32_t BSRR;
IO unit32_t BRR;
IO unit32_t LCKR;
}GPIO_TypeDef;
關于寄存器的都說了,怎么查寄存器的地址呢?
上面說了利用參考手冊找,參考手冊網盤鏈接在文末
假如你想讀取PB3引腳的電平
第一步 找到GPIOB的基地址:(以后找就來這)
第二步找到端口輸入寄存器的地址偏移(結論是0x4001 0C00+8 = 0x4001 0C08),地址偏移上面寫著 8,PB3的數據位于從右往左數第4個 即IDR3
這張圖片包含了很多信息,首先這兩行表示的是一個寄存器的內部結構,上面一行告訴我們保留,就是放著不用,始終讀為0,而下面就是0~15個引腳位置,最下面的r的意思是只讀,不能寫
總結:PB3的輸入數據位于0x4001 0C08這個地址上,這個地址上所存放數據的右起第4個位就是PB3引腳 對應 的高低電平
我們可以簡單粗暴地直接訪問這個地址:
unsigned int *pGPIOB_IDR = (unsigned int *)0x40010C08;
unsigned char PB3 = *pGPIOB_IDR & 0x8;//取出從右往左數的第4位
直接訪問的操作并不好用,每操作一個寄存器就必須去查看數據手冊,然后找找這個寄存器的地址。
意法半導體公司為了方便大家使用,就把這些寄存器都起了一目了然的名字,把寄存器與地址映射關系放在他們提供的頭文件里。這個文件就是stm32f10x.h。(我們上面地址映射那段講到的知識又用到了,現在是讀這段話是不是覺得理所當然,是不是知道自己再做什么?而不是無腦比著其他代碼寫)
直接操作寄存器來點亮LED。
我的板子對應的LED是PB8。首先要配置時鐘使能。
為什么配置時鐘?為了省電,默認的時鐘都是關閉的。配置STM32的任何資源前,都必須首先使能時鐘。配置哪個時鐘?
時鐘的信息在參考手冊里邊,參考手冊十分巨大,不用通讀,就像一個字典,需要什么查什么。
時鐘控制名字叫做RCC,屬于AHB總線。GPIOB屬于APB2。
下圖系統結構可以看到時鐘的從屬關系,此圖位于手冊P25頁,十分重要。可以看出AHB總線包含RCC時鐘控制,GPIO是屬于APB2的。
我們已經知道,GPIO端口B的地址從0x4001 0C00開始。接下來只尋找時鐘使能寄存器的地址:
復位和時鐘控制RCC的地址從0x4002 1000開始。
可以在6.3.7小節找到APB2外設時鐘使能寄存器(RCC_APB2ENR),偏移地址是0x18,所以APB2的地址就是0x4002 1018。
看手冊RCC_APB2ENR,位3是IOPBEN---IO端口B時鐘使能,就是我們想要的。把RCC_APB2ENR的位3賦值為1,就是開啟GPIOB時鐘。配置為通用輸出
既然叫做IO,那么肯定就是可以輸入,可以輸出,到底是輸入還是輸出呢?
控制LED需要輸出高電平或是低電平,所以需要配置為輸出。
一個STM32的IO需要4個位來配置,所以一個32位的寄存器最大只能配置8個IO。STM32中,用端口配置低寄存器(GPIOx_CRL)來配置引腳Px0-Px7, 用端口配置高寄存器(GPIOx_CRH)來配置引腳Px8-Px15。
配置引腳PB8,使用的寄存器是GPIOB_CRH。下面我們來尋找這個寄存器的地址。
因為是PB8所以要配置對應的:CNF8和MODE8
關于此寄存器的說明位于8.2.2小節。先看標題GPIOx,表示PA,PB,PC直到PE都能用。
偏移地址是0x04,意思是在基地址的基礎上再加0x04,所以,對于GPIOB來說就是0x4001 0c04。如果配置PB0-PB7,那么需要的寄存器是低位的寄存器GPIOB_CRL,它的地址是0x4001 0c00。我們需要配置的寄存器是GPIOB_CRH。
找到需要操作的寄存器后,把它配置為通用輸出。
復位值是0x4444 4444,并不是0x0000 0000。所謂的復位值,就是指如果沒有操作這個寄存器時,寄存器存放的默認值。復位值按位拆分0x4 = 0b0100,0x表示16進制,0b表示二進制,也就是默認CNF 01,MODE 00,是浮空輸入。
我們需要的是輸出高低電平,所以要設置為輸出。輸出模式又有好幾種輸出:
推挽輸出:可以輸出高,低電平,連接數字器件;推挽結構一般是指兩個三極管分別受兩互補信號的控制,總是在一個三極管導通的時候另一個截止。
開漏輸出:輸出端相當于三極管的集電極,要得到高電平狀態需要上拉電阻才行,適合于做電流型的驅動,其吸收電流的能力相對強(一般20ma以內)。
開漏是需要外接上拉電阻才可以輸出高電平的,這里并不適合。所以需要設置為推挽輸出。
功能是否是復用呢?復用的意思是有別的功能在這個腳上,比如USB,CAN,串口等,所以這些個腳就可能有多個功能。暫時講多了反而會迷惑,等用到了這些功能再講解,我直接告訴大家,PB8沒有復用。
所以配置為輸出模式,通用推挽輸出。速度暫時不關注,隨便填寫一個50MHz吧,其它速度當然也可以。所以設置GPIOB_CRH的MODE8與CNF8為0b0011,即0x3。此寄存器中其它的位暫時不做修改,使用默認值,也就是GPIOB_CRH設置為:0x4444 4443。點亮LED需要輸出低電平
在單片機的編程中,要想做某件事,必須尋找相應的寄存器。
在8.2.4小節,可以找到端口輸出數據寄存器(GPIOx_ODR),就是我們需要的。我們需要輸出0。但是中文手冊有一個小小的BUG,0x0C寫成了0Ch,可以參考英文原版。得知地址的偏移是0x0C,所以這個數據寄存器的地址就是0x4001 0C0C,把第8位寫為0就行。默認就是0,但是也得學一下怎么寫,萬一是高電平點亮呢。
使用直接賦值的方式寫寄存器的地址
在搞清楚我們要用的幾個寄存器的地址,以及寄存器中需要裝填的數值以后,現在用一個簡單粗暴的方法來操作這些寄存器——直接操作。(注意,這段代碼不是實用的代碼,只是為了寫出一個最簡單的LED,有些部分是不可取的。)將main函數修改為:
int main(void)
{
unsigned int *pRCC_APB2ENR = (unsigned int *)0x40021018;
unsigned int *pGPIOB_CRH = (unsigned int *)0x40010c04;
unsigned int *pGPIOB_ODR = (unsigned int *)0x40010c0c;
*pRCC_APB2ENR = 0x00000008;
*pGPIOB_CRH = 0x44444443;
*pGPIOB_ODR = 0x00000000;
return 0;
}
C語言總是從main函數開始執行。
定義幾個指針,指向剛剛看到的地址。對于編譯器來說,它并不知道0x40021018代表的是數據還是指針,所以用(unsigned int *)作強制的類型轉換,告訴編譯器0x40021018是個指針。指針可以理解為地址。操作指針,把這些地址存放的值修改。
我們寫了一段另類的代碼,直接操作寄存器的地址,就是想得到這么一個結論:不論代碼怎么寫,不論是寄存器,庫函數,還是其他的操作系統,要在STM32F103這個單片機點亮LED燈,肯定需要把時鐘和GPIO這幾個相關的特殊地址,進行賦值或修改數值的操作。有點像打籃球,不論進攻時有怎樣花哨的運球與傳切配合,最后都要完成把球放入籃筐的動作,才能得分。
參考手冊第10版:
總結
以上是生活随笔為你收集整理的crh寄存器_STM32 学习笔记(寄存器)---2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 聚宽源码59
- 下一篇: 编程基础知识(变简单的进制转换)