自從CubeMX等圖像配置軟件的出現(xiàn),同學(xué)們往往點(diǎn)幾下鼠標(biāo)就解決了單片機(jī)的配置問題。對(duì)于追求開發(fā)速度的業(yè)務(wù)場景下,使用快速配置軟件是合理的,高效的,但對(duì)于學(xué)生的學(xué)習(xí)場景下,更為重要的是知其然并知其所以然。
以下是學(xué)習(xí)(包括但不限于)嵌入式的三個(gè)重要內(nèi)容,
1、學(xué)會(huì)如何參考官方的手冊(cè)和官方的代碼來獨(dú)立寫自己的程序。
2、積累常用代碼段,知道哪里的問題需要哪些代碼處理。
3、跟隨大佬步伐,一步一個(gè)腳印。
首先:我們都知道編程時(shí)一般查的是 《參考手冊(cè)》 ,而進(jìn)行芯片選型或需要芯片數(shù)據(jù)時(shí),查閱的是**《數(shù)據(jù)手冊(cè)》**。此外市面上所有關(guān)于STM32的書籍都是立足于前二者(+ Cortex內(nèi)核手冊(cè) )進(jìn)行編著。
其次:要分清什么是內(nèi)核外設(shè)與內(nèi)核之外的外設(shè),為了便于區(qū)分,按照網(wǎng)上的一種說法,將“內(nèi)核之外的外設(shè)”以“處理器外設(shè)”代替。再者:如今很少使用標(biāo)準(zhǔn)庫了,都是HAL庫,但作為高校目前教學(xué)方式,
我們將以STM32f10xxx為例對(duì)標(biāo)準(zhǔn)庫開發(fā)進(jìn)行概覽。
一、STM32 系統(tǒng)結(jié)構(gòu)
STM32f10xxx 系統(tǒng)結(jié)構(gòu)
內(nèi)核IP
從結(jié)構(gòu)框圖上看,Cortex-M3 內(nèi)部有若 干個(gè)總線接口,以使 CM3 能同時(shí)取址和訪內(nèi)(訪問內(nèi)存),它們是: 指令存儲(chǔ)區(qū)總線(兩條)、系統(tǒng)總線、私有外設(shè)總線。 有兩條代碼存儲(chǔ)區(qū)總線負(fù)責(zé)對(duì)代碼存儲(chǔ)區(qū)(即 FLASH 外設(shè))的訪問,分別是** I-Code 總線**和 D-Code 總線 。
I-Code 用于取指,D-Code 用于查表等操作,它們按最佳執(zhí)行速度進(jìn)行優(yōu)化。
**系統(tǒng)總線(System)**用于訪問內(nèi)存和外設(shè),覆蓋的區(qū)域包括 SRAM,片上外設(shè),片外 RAM,片外擴(kuò)展設(shè)備,以及系統(tǒng)級(jí)存儲(chǔ)區(qū)的部分空間。
私有外設(shè)總線負(fù)責(zé)一部分私有外設(shè)的訪問,主要就是訪問調(diào)試組件。它們也在系統(tǒng)級(jí)存儲(chǔ)區(qū)。
還有一個(gè) DMA 總線,從字面上看,DMA 是 data memory access 的意思,是一種連接內(nèi)核和外設(shè)的橋梁,它可以訪問外設(shè)、內(nèi)存,傳輸不受 CPU 的控制,并且是雙向通信。簡而言之,這個(gè)家伙就是一個(gè)速度很快的且不受老大控制的數(shù)據(jù)搬運(yùn)工。
處理器外設(shè)(內(nèi)核之外的外設(shè))
從結(jié)構(gòu)框圖上看,STM32 的外設(shè)有 串口、定時(shí)器、IO 口、FSMC、SDIO、SPI、I2C 等,這些外設(shè)按 照速度的不同,分別掛載到 AHB、APB2、APB1 這三條總線上。
二、寄存器
什么是寄存器?寄存器是內(nèi)置于各個(gè) IP 外設(shè)中,是一種用于配置外設(shè)功能的存儲(chǔ)器,并且有想對(duì)應(yīng)的地址。一切庫的封裝始于 映射 。
是不是“又臭又長”,如果進(jìn)行寄存器開發(fā),就需要懟地址以及對(duì)寄存器進(jìn)行字節(jié)賦值,不僅效率低而且容易出錯(cuò)。
來,開個(gè)玩笑。
你也許聽說過“國際 C 語言亂碼大賽(IOCCC)”下面這個(gè)例子就是網(wǎng)上廣為流傳的 一個(gè)經(jīng)典作品:
#include < stdio.h >
main(t,_,a)char *a;{return!0< t?t< 3?main(-79,-13,a+main(-87,1-_,
main(-86,0,a+1)+a)):1,t< _?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_< 13?
main(2,_+1,"%s %d %d
"):9:16:t 0?t< -72?main(_,t,
"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#
;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l
q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;#
){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw'
iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c
;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/")
:t -50?_==*a?putchar(31[a]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)
:0< t?main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,
"!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:
uwloca-O;m.vpbks,fxntdCeghiry"),a+1);}
庫的存在就是為了解決這類問題,將代碼語義化。語義化思想不僅僅是嵌入式有的,前端代碼也在追求語義特性。
三、萬物始于點(diǎn)燈
(1)內(nèi)核庫文件分析
cor_cm3.h
這個(gè)頭文件實(shí)現(xiàn)了:
1、內(nèi)核結(jié)構(gòu)體寄存器定義。
2、內(nèi)核寄存器內(nèi)存映射。
3、內(nèi)存寄存 器位定義。跟處理器相關(guān)的頭文件 stm32f10x.h 實(shí)現(xiàn)的功能一樣,一個(gè)是針對(duì)內(nèi)核的寄存器,一個(gè)是針對(duì)內(nèi)核之外,即處理器的寄存器。
misc.h
內(nèi)核應(yīng)用函數(shù)庫頭文件,對(duì)應(yīng) stm32f10x_xxx.h。
misc.c
內(nèi)核應(yīng)用函數(shù)庫文件,對(duì)應(yīng) stm32f10x_xxx.c。在 CM3 這個(gè)內(nèi)核里面還有一些功能組 件,如 NVIC、SCB、ITM、MPU、CoreDebug ,CM3 帶有非常豐富的功能組件,但是芯片廠商在設(shè)計(jì) MCU 的時(shí)候有一些并不是非要不可的,是可裁剪的,比如 MPU、ITM 等在 STM32 里面就沒有。
其中 NVIC 在每一個(gè) CM3 內(nèi)核的單片機(jī)中都會(huì)有,但都會(huì)被裁剪,只能是 CM3 NVIC 的一個(gè)子集。在 NVIC 里面還有一個(gè) SysTick,是一個(gè)系統(tǒng)定時(shí)器,可以提供時(shí)基,一般為操作系統(tǒng)定時(shí)器所用。misc.h 和 mics.c 這兩個(gè)文件提供了操作這些組件的函數(shù),并可以在 CM3 內(nèi)核單片機(jī)直接移植。
(2)處理器外設(shè)庫文件分析
startup_stm32f10x_hd.s
這個(gè)是由匯編編寫的啟動(dòng)文件,是 STM32 上電啟動(dòng)的第一個(gè)程序,啟動(dòng)文件主要實(shí)現(xiàn)了
- 初始化堆棧指針 SP;
- 設(shè)置 PC 指針=Reset_Handler ;
- 設(shè)置向量表的地址,并 初始化向量表,向量表里面放的是 STM32 所有中斷函數(shù)的入口地址
- 調(diào)用庫函數(shù) SystemInit,把系統(tǒng)時(shí)鐘配置成 72M,SystemInit 在庫文件 stytem_stm32f10x.c 中定義;
- 跳轉(zhuǎn)到標(biāo)號(hào)_main,最終去到 C 的世界。
system_stm32f10x.c
這個(gè)文件的作用是里面實(shí)現(xiàn)了各種常用的系統(tǒng)時(shí)鐘設(shè)置函數(shù),有 72M,56M,48, 36,24,8M,我們使用的是是把系統(tǒng)時(shí)鐘設(shè)置成 72M。
Stm32f10x.h
這個(gè)頭文件非常重要,這個(gè)頭文件實(shí)現(xiàn)了:
1、處理器外設(shè)寄存器 的結(jié)構(gòu)體定義。
2、處理器外設(shè)的內(nèi)存映射。
3、處理器外設(shè)寄存器的位定義。
關(guān)于 1 和 2 我們?cè)谟眉拇嫫鼽c(diǎn)亮 LED 的時(shí)候有講解。
其中 3:處理器外設(shè)寄存器的位定義,這個(gè)非常重要,具體是什么意思?
我們知道一個(gè)寄存器有很多個(gè)位,每個(gè)位寫 1 或 者寫 0 的功能都是不一樣的,處理器外設(shè)寄存器的位定義就是把外設(shè)的每個(gè)寄存器的每一 個(gè)位寫 1 的 16 進(jìn)制數(shù)定義成一個(gè)宏,宏名即用該位的名稱表示,如果我們操作寄存器要開啟某一個(gè)功能的話,就不用自己親自去算這個(gè)值是多少,可以直接到這個(gè)頭文件里面找。
我們以片上外設(shè) ADC 為例,假設(shè)我們要啟動(dòng) ADC 開始轉(zhuǎn)換,根據(jù)手冊(cè)我們知道是要控制 ADC_CR2 寄存器的位 0:ADON,即往位 0 寫 1,即:
ADC- >CR2=0x00000001;
這是 一般的操作方法?,F(xiàn)在這個(gè)頭文件里面有關(guān)于 ADON 位的位定義:
#define ADC_CR2_ADON ((uint32_t)0x00000001)
有了這個(gè)位定義,我們剛剛的 代碼就變成了:
ADC- >CR2=ADC_CR2_ADON
stm32f10x_xxx.h
外設(shè) xxx 應(yīng)用函數(shù)庫頭文件,這里面主要定義了實(shí)現(xiàn)外設(shè)某一功能 的結(jié)構(gòu)體,比如通用定時(shí)器有很多功能,有定時(shí)功能,有輸出比較功能,有輸入捕捉功 能,而通用定時(shí)器有非常多的寄存器要實(shí)現(xiàn)某一個(gè)功能。
比如定時(shí)功能,我們根本不知道 具體要操作哪些寄存器,這個(gè)頭文件就為我們打包好了要實(shí)現(xiàn)某一個(gè)功能的寄存器,是以機(jī)構(gòu)體的形式定義的,比如通用定時(shí)器要實(shí)現(xiàn)一個(gè)定時(shí)的功能,我們只需要初始化 TIM_TimeBaseInitTypeDef 這個(gè)結(jié)構(gòu)體里面的成員即可,里面的成員就是定時(shí)所需要 操作的寄存器。
有了這個(gè)頭文件,我們就知道要實(shí)現(xiàn)某個(gè)功能需要操作哪些寄存器,然后 再回手冊(cè)中精度這些寄存器的說明即可。
stm32f10x_xxx.c
stm32f10x_xxx.c:外設(shè) xxx 應(yīng)用函數(shù)庫,這里面寫好了操作 xxx 外設(shè)的所有常用的函 數(shù),我們使用庫編程的時(shí)候,使用的最多的就是這里的函數(shù)。
(3)SystemInit
工程中新建main.c 。
在此文件中編寫main函數(shù)后直接編譯會(huì)報(bào)錯(cuò):
?Undefined symbol SystemInit (referred from startup_stm32f10x_hd.o).
?
錯(cuò)誤提示說SystemInit 沒有定義。從分析啟動(dòng)文件startup_stm32f10x_hd.s時(shí)我們知道,
;Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
;IMPORT SystemInit
;LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
?匯編中;分號(hào)是注釋的意思
?
第五行第六行代碼Reset_Handler 調(diào)用了SystemInit該函數(shù)用來初始化系統(tǒng)時(shí)鐘,而該函數(shù)是在庫文件system_stm32f10x.c 中實(shí)現(xiàn)的。我們重新寫一個(gè)這樣的函數(shù)也可以,把功能完整實(shí)現(xiàn)一遍,但是為了簡單起見,我們?cè)趍ain 文件里面定義一個(gè)SystemInit 空函數(shù),為的是騙過編譯器,把這個(gè)錯(cuò)誤去掉。
關(guān)于配置系統(tǒng)時(shí)鐘之后會(huì)出文章RCC 時(shí)鐘樹詳細(xì)介紹,主要配置時(shí)鐘控制寄存器(RCC_CR)和時(shí)鐘配置寄存器(RCC_CFGR)這兩個(gè)寄存器,但最好是直接使用CubeMX直接生成,因?yàn)樗呐渲眠^程有些冗長。
如果我們用的是庫,那么有個(gè)庫函數(shù)SystemInit,會(huì)幫我們把系統(tǒng)時(shí)鐘設(shè)置成72M。
現(xiàn)在我們沒有使用庫,那現(xiàn)在時(shí)鐘是多少?答案是8M,當(dāng)外部HSE 沒有開啟或者出現(xiàn)故障的時(shí)候,系統(tǒng)時(shí)鐘由內(nèi)部低速時(shí)鐘LSI 提供,現(xiàn)在我們是沒有開啟HSE,所以系統(tǒng)默認(rèn)的時(shí)鐘是LSI=8M。
(4)庫封裝層級(jí)
如圖,達(dá)到第四層級(jí)便是我們所熟知的固件庫或HAL庫的效果。當(dāng)然庫的編寫還需要考慮許多問題,不止于這些內(nèi)容。我們需要的是了解庫封裝的大概過程。
將庫封裝等級(jí)分為四級(jí)來介紹是為了有層次感,就像打怪升級(jí)一樣,進(jìn)行認(rèn)知理解的升級(jí)。
我們都知道,操作GPIO輸出分三大步:
時(shí)鐘控制:
STM32 外設(shè)很多,為了降低功耗,每個(gè)外設(shè)都對(duì)應(yīng)著一個(gè)時(shí)鐘,在系統(tǒng)復(fù)位的時(shí)候這些時(shí)鐘都是被關(guān)閉的,如果想要外設(shè)工作,必須把相應(yīng)的時(shí)鐘打開。
STM32 的所有外設(shè)的時(shí)鐘由一個(gè)專門的外設(shè)來管理,叫 RCC(reset and clockcontrol) ,RCC 在STM32 參考手冊(cè)的第六章。
STM32 的外設(shè)因?yàn)樗俾实牟煌?,分別掛載到三條總系上:AHB、APB2、APB1,AHB為高速總線,APB2 次之,APB1 再次之。所以的IO 口都掛載到APB2 總線上,屬于高速外設(shè)。
模式配置:
這個(gè)由端口配置寄存器來控制。端口配置寄存器分為高低兩個(gè),每4bit 控制一個(gè)IO 口,所以端口配置低寄存器:CRL 控制這IO 口的低8 位,端口配置高寄存器:CRH控制這IO 口的高8bit。
在4 位一組的控制位中,CNFy[1:0] 用來控制端口的輸入輸出,MODEy[1:0]用來控制輸出模式的速率,又稱驅(qū)動(dòng)電路的響應(yīng)速度,注意此處速率與程序無關(guān),具體內(nèi)容見文章:【嵌入式】GPIO引腳速度、翻轉(zhuǎn)速度、輸出速度區(qū)別輸入有4種模式,輸出有4種模式,我們?cè)诳刂芁ED 的時(shí)候選擇通用推挽輸出。
輸出速率有三種模式:2M、10M、50M,這里我們選擇2M。
電平控制:
STM32 的IO 口比較復(fù)雜,如果要輸出1 和0,則要通過控制:端口輸出數(shù)據(jù)寄存器ODR 來實(shí)現(xiàn),ODR 是:Output data register 的簡寫,在STM32 里面,其寄存器的命名名稱都是英文的簡寫,很容易記住。
從手冊(cè)上我們知道ODR 是一個(gè)32 位的寄存器,低16位有效,高16 位保留。低16 位對(duì)應(yīng)著IO0~IO16,只要往相應(yīng)的位置寫入0 或者1 就可以輸出低或者高電平。
第一層級(jí):基地址宏定義
時(shí)鐘控制:
在STM32 中,每個(gè)外設(shè)都有一個(gè)起始地址,叫做 外設(shè)基地址 ,外設(shè)的寄存器就以這個(gè)基地址為標(biāo)準(zhǔn)按照順序排列,且每個(gè)寄存器32位,(后面作為結(jié)構(gòu)體里面的成員正好內(nèi)存對(duì)齊)。
查表看到時(shí)鐘由APB2 外設(shè)時(shí)鐘使能寄存器(RCC_APB2ENR)來控制,其中PB 端口的時(shí)鐘由該寄存器的位3 寫1 使能。我們可以通過 基地址+偏移量0x18 ,算出RCC_APB2ENR 的地址為:0x40021018。那么使能PB 口的時(shí)鐘代碼則如下所示:
#define RCC_APB2ENR *(volatile unsigned long *)0x40021018
// 開啟端口B 時(shí)鐘
RCC_APB2ENR |= 1< < 3;
模式配置:
同RCC_APB2ENR 一樣,GPIOB 的起始地址是:0X4001 0C00,我們也可以算出GPIO_CRL 的地址為:0x40010C00。那么設(shè)置PB0 為通用推挽輸出,輸出速率為2M 的代碼則如下所示:
同上,從手冊(cè)中我們看到ODR 寄存器的地址偏移是:0CH,可以算出GPIOB_ODR 寄存器的地址是:0X4001 0C00 + 0X0C = 0X4001 0C0C?,F(xiàn)在我們就可以定義GPIOB_ODR 這個(gè)寄存器了,代碼如下:
#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
//PB0 輸出低電平
GPIOB_ODR = 0< < 0;
第一層級(jí):基地址宏定義完成用STM32 控制一個(gè)LED 的完整代碼:
#define RCC_APB2ENR *(volatile unsigned long *)0x40021018
#define GPIOB_CRL *(volatile unsigned long *)0x40010C00
#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
int main(void)
{
// 開啟端口B 的時(shí)鐘
RCC_APB2ENR |= 1< < 3;
// 配置PB0 為通用推挽輸出模式,速率為2M
GPIOB_CRL = (2< < 0) | (0< < 2);
// PB0 輸出低電平,點(diǎn)亮LED
GPIOB_ODR = 0< < 0;
}
void SystemInit(void)
{
}
第二層級(jí):基地址宏定義+結(jié)構(gòu)體封裝
外設(shè)寄存器結(jié)構(gòu)體封裝
上面我們?cè)诓僮骷拇嫫鞯臅r(shí)候,操作的是寄存器的絕對(duì)地址,如果每個(gè)寄存器都這樣操作,那將非常麻煩。我們考慮到外設(shè)寄存器的地址都是基于外設(shè)基地址的偏移地址,都是在外設(shè)基地址上逐個(gè)連續(xù)遞增的,每個(gè)寄存器占 32 個(gè)或者 16 個(gè)字節(jié),這種方式跟結(jié)構(gòu)體里面的成員類似。
所以我們可以定義一種外設(shè)結(jié)構(gòu)體, 結(jié)構(gòu)體的地址等于外設(shè)的基地址,結(jié)構(gòu)體的成員等于寄存器 ,成員的排列順序跟寄存器的順序一樣。這樣我們操作寄存器的時(shí)候就不用每次都找到絕對(duì)地址,只要知道外設(shè)的基地址就可以操作外設(shè)的全部寄存器,即操作結(jié)構(gòu)體的成員即可。
下面我們先定義一個(gè) GPIO 寄存器結(jié)構(gòu)體,結(jié)構(gòu)體里面的成員是 GPIO 的寄存器,成員的順序按照寄存器的偏移地址從低到高排列,成員類型跟寄存器類型一樣。(struct用法參考【C語言】(2):關(guān)鍵字的詳細(xì)介紹)
typedef struct
{
volatile uint32_t CRL;
volatile uint32_t CRH;
volatile uint32_t IDR;
volatile uint32_t ODR;
volatile uint32_t BSRR;
volatile uint32_t BRR;
volatile uint32_t LCKR;
} GPIO_TypeDef;
在 《STM32 中文參考手冊(cè)》 8.2 寄存器描述章節(jié),我們可以找到結(jié)構(gòu)體里面的7 個(gè)寄存器描述。在點(diǎn)亮LED 的時(shí)候我們只用了CRL 和ODR 這兩個(gè)寄存器,至于其他寄存器的功能大家可以自行看手冊(cè)了解。
在GPIO 結(jié)構(gòu)體里面我們用了兩個(gè)數(shù)據(jù)類型,一個(gè)是uint32_t,表示無符號(hào)的32 位整型,因?yàn)镚PIO 的寄存器都是32 位的。這個(gè)類型聲明在標(biāo)準(zhǔn)頭文件stdint.h 里面使用typedef對(duì)unsigned int重命名,我們?cè)诔绦蛏现灰@個(gè)頭文件即可。
另外一個(gè)是 volatile (volatile用法參考【C語言】(2):關(guān)鍵字的詳細(xì)介紹),作用就是告訴編譯器這里的變量會(huì)變化不因優(yōu)化而省略此指令,必須每次都直接讀寫其值,這樣就能確保每次讀或者寫寄存器都真正執(zhí)行到位。
外設(shè)封裝
STM32F1 系列的GPIO 端口分A~G,即GPIOA、GPIOB。。。。。。GPIOG。每個(gè)端口都含有GPIO_TypeDef 結(jié)構(gòu)體里面的寄存器,我們可以根據(jù)手冊(cè)各個(gè)端口的基地址把GPIO 的各個(gè)端口定義成一個(gè)GPIO_TypeDef 類型指針,然后我們就可以根據(jù)端口名(實(shí)際上現(xiàn)在是結(jié)構(gòu)體指針了)來操作各個(gè)端口的寄存器,代碼實(shí)現(xiàn)如下:
#define GPIOA ((GPIO_TypeDef *) 0X4001 0800)
#define GPIOB ((GPIO_TypeDef *) 0X4001 0C00)
#define GPIOC ((GPIO_TypeDef *) 0X4001 1000)
#define GPIOD ((GPIO_TypeDef *) 0X4001 1400)
#define GPIOE ((GPIO_TypeDef *) 0X4001 1800)
#define GPIOF ((GPIO_TypeDef *) 0X4001 1C00)
#define GPIOG ((GPIO_TypeDef *) 0X4001 2000)
外設(shè)內(nèi)存映射
講到基地址的時(shí)候我們?cè)僖艘粋€(gè)知識(shí)點(diǎn):Cortex-M3 存儲(chǔ)器系統(tǒng),這個(gè)知識(shí)點(diǎn)在**《Cortex-M3 權(quán)威指南》**第5 章里面講到。CM3 的地址空間是4GB,如下圖所示:
我們這里要講的是片上外設(shè),就是我們所說的寄存器的根據(jù)地,其大小總共有512MB,512MB 是其極限空間,并不是每個(gè)單片機(jī)都用得完,實(shí)際上各個(gè)MCU 廠商都只是用了一部分而已。STM32F1 系列用到了:0x4000 0000 ~0x5003 FFFF。現(xiàn)在我們說的STM32 的寄存器就是位于這個(gè)區(qū)域
APB1、APB2、AHB 總線基地址
現(xiàn)在我們說的STM32 的寄存器就是位于這個(gè)區(qū)域,這里面ST 設(shè)計(jì)了三條總線:AHB、APB2 和APB1,其中AHB 和APB2 是高速總線,APB1 是低速總線。不同的外設(shè)根據(jù)速度不同分別掛載到這三條總線上。
從下往上依次是:APB1、APB2、AHB,每個(gè)總線對(duì)應(yīng)的地址分別是:APB1:0x40000000,APB2:0x4001 0000,AHB:0x4001 8000。
這三條總線的基地址我們是從 《STM32 中文參考手冊(cè)》 2.3 小節(jié)—存儲(chǔ)器映像得到的:APB1 的基地址是TIM2 定時(shí)器的起始地址,APB2 的基地址是AFIO 的起始地址,AHB 的基地址是SDIO 的起始地址。其中APB1 地址又叫做外設(shè)基地址,是所有外設(shè)的基地址,叫做PERIPH_BASE。
現(xiàn)在我們把這三條總線地址用宏定義出來,以后我們?cè)诙x其他外設(shè)基地址的時(shí)候,只需要在這三條總線的基址上加上偏移地址即可,代碼如下:
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
GPIO 端口基地址
因?yàn)镚PIO 掛載到APB2 總線上,那么現(xiàn)在我們就可以根據(jù)APB2 的基址算出各個(gè)GPIO 端口的基地址,用宏定義實(shí)現(xiàn)代碼如下:
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
第二層級(jí):基地址宏定義+結(jié)構(gòu)體封裝完成用STM32 控制一個(gè)LED 的完整代碼:
#include < stdint.h >
#define __IO volatile
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
typedef struct
{
__IO uint32_t CR;
__IO uint32_t CFGR;
__IO uint32_t CIR;
__IO uint32_t APB2RSTR;
__IO uint32_t APB1RSTR;
__IO uint32_t AHBENR;
__IO uint32_t APB2ENR;
__IO uint32_t APB1ENR;
__IO uint32_t BDCR;
__IO uint32_t CSR;
} RCC_TypeDef;
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB1PERIPH_BASE PERIPH_BASE
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x1400)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x1800)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *) GPIOG_BASE)
#define RCC ((RCC_TypeDef *) RCC_BASE)
#define RCC_APB2ENR *(volatile unsigned long *)0x40021018
#define GPIOB_CRL *(volatile unsigned long *)0x40010C00
#define GPIOB_ODR *(volatile unsigned long *)0x40010C0C
int main(void)
{
// 開啟端口B 的時(shí)鐘
RCC- >APB2ENR |= 1< < 3;
// 配置PB0 為通用推挽輸出模式,速率為2M
GPIOB- >CRL = (2< < 0) | (0< < 2);
// PB0 輸出低電平,點(diǎn)亮LED
GPIOB- >ODR = 0< < 0;
}
void SystemInit(void)
{
}