2.正式開始
3.總結
在平時的學習和工作中,可能很少有人會實際去操作暫存器,但是去了解庫函式是如何去操作暫存器是很有必要的。不僅可以加深對stm32的理解還能學習借鑑它庫函式的封裝架構。
stm32是目前市面上最常見的微處理器之一。這得益於它價效比和低門檻。在使用stm32時,往往沒去注意它庫函式是怎麼將底層暫存器操作封裝起來的,為什麼stm32用庫函式開發的驅動具有很好的移植性?這裡我們將來談一談。
在stm32裡面,同型別的外設的暫存器位址對映是規律是相同的。
上圖中gpiox 代表 gpioa gpiob等等,從最左邊的offset可以看出,不同埠的gpio內部暫存器的對映方式是一樣的,也就是說在gpioa中,gpioa_crh在0x04這個偏移位址處,那麼gpiob的gpiob_crh也在0x04。這就意味著,在寫驅動時,我們可以用同乙個gpio驅動去控制gpioa gpiob ,只需要把gpio模組的偏移位址改變就可以了。
我們還要需要了解一下的就是在stm32f10x系列處理器上,gpioa是掛載在apb2匯流排上的,因此,訪問gpioa的流程就應該是:先找到apb2的位址,然後在上面找到gpioa的位址,然後再在gpioa中找到相應暫存器的位址。知道了這些我們就可以繼續說了。
大概是這樣乙個計算公式:
gpioa的crh暫存器位址=apb2位址+gpioa相對於apb2的偏移位址+crh相對於gpioa的偏移位址。
這裡我會以stm32f103c8t6微控制器最常見的gpio控制led燈為例,由頂向下來展開,也就是從我們呼叫gpio_init
開始,追根溯源,看控制字是怎麼一步一步寫入到暫存器的。
首先,庫函式裡宣告了乙個gpio_inittypedef
的結構體,它包含了gpio配置的成員。
包含了我們要配置哪個引腳,引腳需要多高驅動頻率,gpio的模式是什麼。
typedef
struct
gpio_inittypedef;
我們可以看到以上**的結構體中還包含了一些結構體,其實他們不是結構體,而是一些列舉變數,這樣我們在給上面結構體賦值時就可以用預先定義好的那些列舉變數了。
/**
* @brief output maximum frequency selection
*/typedef
enum
gpiospeed_typedef;
在下圖中我們就用到了gpio_initstructure.gpio_speed = gpio_speed_50mhz;
來給gpio_initstructure.gpio_speed
賦值。
然後我們需要使用gpio來操作led燈時,就會先定義乙個gpio_inittypedef
結構體,然後將對應的結構體成員賦值。最後呼叫gpio_init
來初始化gpio。
#include
"stm32f10x.h"
// device header
#include
"led.h"
void
led_init
(void
)
在使用gpio_init(gpioa, &gpio_initstructure);
初始化gpio時,gpio_initstructure
是我們自己定義的,那gpioa是**來的呢?
在stm32f10x.h
這個標頭檔案裡有這樣的一些巨集:
這裡的gpioa就是我們在gpio_init
時填寫的第乙個引數。這裡巨集定義是什麼意思呢?
對於gpioa來說,首先我們將gpioa_base強制轉換為乙個gpio_typedef *型別的指標,以後我們
訪問gpioa_base就可以使用gpioa啦!
恰好是下圖從使用者手冊上查詢到的值。
這裡的0x0800就是偏移位址。從資料手冊查得:
然後我們回到 **訪問gpioa_base就可以使用gpioa啦!**意思就是我們訪問gpioa這個結構體就可以訪問到具體的硬體了。現在讓我們來看一下這個結構體都包含什麼:
/**
* @brief general purpose i/o
*/typedef
struct
gpio_typedef;
上面的**中我在後面注釋了0x00,0x04都是什麼意思?沒錯這些都是對應暫存器相對於gpioa的偏移位址。
重點:有木有發現register map 裡,暫存器排列是 chl,chr,idr…,而在gpio_typedef結構體裡也是chl,chr,idr…。底層沒有明確定義各個暫存器的位址,而是通過將暫存器從低位址向高位址排列,定義的型別恰好是uint32_t,所以結構體成員相對於結構體首位址的偏移位址是以4個位元組為單位的,恰好微控制器也是32位的,每個暫存器是32位的,偏移單位也是4個位元組。通過這樣子,就將結構體成員和暫存器一一對應上了。而前面講了,將gpioa的基位址轉換成了gpio_typedef 的指標,所以以後我們通過訪問gpioa指向的結構體成員 就可以準確的訪問到對應的暫存器啦 。*
通過上面的操作: gpio_init(gpioa, &gpio_initstructure); 我們將要在gpio_init裡,把gpio_initstructure結構體裡的控制字寫入到gpioa對應的暫存器去了。進入gpio_init函式。
void
gpio_init
(gpio_typedef* gpiox, gpio_inittypedef* gpio_initstruct)
/* set the corresponding odr bit */
if(gpio_initstruct->gpio_mode == gpio_mode_ipu)}}
gpiox->crh = tmpreg;
}}
限於篇幅,這裡省略了很多**,並且這個函式的實現不是我們討論的重點,因此省略了大部分。但是我們依然可以看到,傳入引數後首先通過斷言assert_param
判斷引數正確性,方便除錯時定位,然後大部分操作都是在 與或非 移位等操作,也就是將gpio_initstructure裡的控制字分離出來,寫到對應的暫存器去。
比如:gpiox->crh = tmpreg;
比如這行**,就是將分離出來的值寫入gpiox的crh暫存器。這裡的gpiox也就是gpio_init(gpioa, &gpio_initstructure);
傳入的gpioa(結構體指標)。
這裡是以gpio的初始化為例,其實其他的外設也大同小異。
stm32大量使用了結構體和列舉來實現標準庫,並且將具有相同特點的外設模組分門別類,提高了**復用,也使程式設計大大簡化。
其實這裡面最關鍵的一點就是:
外設暫存器位址=匯流排基位址位址+外設偏移位址+暫存器偏移位址。
STM32(二 什麼是暫存器
stm32晶元架構簡圖 stm32f10xx系統框圖 儲存器本身不具有位址資訊,它的位址是由晶元廠商或使用者分配,給儲存器分配位址分配位址的過程就稱為儲存器對映。分配位址後,會使用指標去操作記憶體位址。有特定功能的記憶體單元,通常我們會給這個特殊的記憶體單元取乙個名字,這個給已經分配好位址的有特定功...
STM32蜂鳴器 暫存器
這次實驗犯了個笑話,竟然在巨集定義後面加分號.就像這樣 define 大家千萬不要學我,結果報錯expected expression,還苦惱半天,想為啥操作不了暫存器了?我真愚蠢!剛開始我也不會寫這些東西,其實摸清套路就好,rcc時鐘使能 gpio初始化 相關暫存器初始化 延時函式 串列埠等初始化...
STM32暫存器對映
1.對映即將記憶體的某段位址與某一暫存器對應,微控制器對函式的操作底層是操作暫存器,而暫存器最終是操作記憶體上對應的單元,2.各個暫存器對應的位址都是在st定義的起始位址上層層偏移得到 3.想要實現某功能可直接對此段記憶體寫進相應的值,即可賦予某一功能 4.stm32底層實際是先定義出外設基位址,然...