嵌入式層層遞進(jìn),了解庫開發(fā)

      網(wǎng)友投稿 984 2022-05-29

      自從CubeMX等圖像配置軟件的出現(xiàn),同學(xué)們往往點(diǎn)幾下鼠標(biāo)就解決了單片機(jī)的配置問題。對(duì)于追求開發(fā)速度的業(yè)務(wù)場(chǎng)景下,使用快速配置軟件是合理的,高效的,但對(duì)于學(xué)生的學(xué)習(xí)場(chǎng)景下,更為重要的是知其然并知其所以然。

      以下是學(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è) MDA 總線,從字面上看,DMA 是 data memory access 的意思,是一種連接內(nèi)核和外設(shè)的橋梁,它可以訪問外設(shè)、內(nèi)存,傳輸不受 CPU 的控制,并且是雙向通信。簡(jiǎn)而言之,這個(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)的地址。一切庫的封裝始于映射。

      是不是“又臭又長(zhǎng)”,如果進(jìn)行寄存器開發(fā),就需要懟地址以及對(duì)寄存器進(jìn)行字節(jié)賦值,不僅效率低而且容易出錯(cuò)。

      來,開個(gè)玩笑。

      你也許聽說過“國(guó)際 C 語言亂碼大賽(IOCCC)”下面這個(gè)例子就是網(wǎng)上廣為流傳的 一個(gè)經(jīng)典作品:

      #include main(t,_,a)char *a;{return!0

      庫的存在就是為了解決這類問題,將代碼語義化。語義化思想不僅僅是嵌入式有的,前端代碼也在追求語義特性。

      三、萬物始于點(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) 了:1、初始化堆棧指針 SP;2、設(shè)置 PC 指針=Reset_Handler ;3、設(shè)置向量表的地址,并 初始化向量表,向量表里面放的是 STM32 所有中斷函數(shù)的入口地址 4、調(diào)用庫函數(shù) SystemInit,把系統(tǒng)時(shí)鐘配置成 72M,SystemInit 在庫文件 stytem_stm32f10x.c 中定義;5、 跳轉(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;

      這是 一般的操作方法。現(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í)我們知道,

      1 ;Reset handler 2 Reset_Handler PROC 3 EXPORT Reset_Handler [WEAK] 4 IMPORT __main 5 ;IMPORT SystemInit 6 ;LDR R0, =SystemInit 7 BLX R0 8 LDR R0, =__main 9 BX R0 10 ENDP

      匯編中;分號(hào)是注釋的意思

      第五行第六行代碼Reset_Handler 調(diào)用了SystemInit該函數(shù)用來初始化系統(tǒng)時(shí)鐘,而該函數(shù)是在庫文件system_stm32f10x.c 中實(shí)現(xiàn)的。我們重新寫一個(gè)這樣的函數(shù)也可以,把功能完整實(shí)現(xiàn)一遍,但是為了簡(jiǎ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)樗呐渲眠^程有些冗長(zhǎng)。

      如果我們用的是庫,那么有個(gè)庫函數(shù)SystemInit,會(huì)幫我們把系統(tǒng)時(shí)鐘設(shè)置成72M。

      現(xiàn)在我們沒有使用庫,那現(xiàn)在時(shí)鐘是多少?答案是8M,當(dāng)外部HSE 沒有開啟或者出現(xiàn)故

      【嵌入式】層層遞進(jìn),了解庫開發(fā)

      障的時(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)樗俾实牟煌謩e掛載到三條總系上: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 的簡(jiǎn)寫,在STM32 里面,其寄存器的命名

      名稱都是英文的簡(jiǎn)寫,很容易記住。從手冊(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。現(xiàn)在我們就可以定義GPIOB_ODR 這個(gè)寄存器了,代碼如下:

      #define GPIOB_ODR *(volatile unsigned long *)0x40010C0C //PB0 輸出低電平 GPIOB_ODR = 0<<0;

      第一層級(jí):基地址宏定義完成用STM32 控制一個(gè)LED 的完整代碼:

      1 #define RCC_APB2ENR *(volatile unsigned long *)0x40021018 2 #define GPIOB_CRL *(volatile unsigned long *)0x40010C00 3 #define GPIOB_ODR *(volatile unsigned long *)0x40010C0C 45 int main(void) 6 { 7 // 開啟端口B 的時(shí)鐘 8 RCC_APB2ENR |= 1<<3; 9 10 // 配置PB0 為通用推挽輸出模式,速率為2M 11 GPIOB_CRL = (2<<0) | (0<<2); 12 13 // PB0 輸出低電平,點(diǎn)亮LED 14 GPIOB_ODR = 0<<0; 15 } 16 17 void SystemInit(void) 18 { 19 }

      第二層級(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ì)介紹)

      1 typedef struct { 2 volatile uint32_t CRL; 3 volatile uint32_t CRH; 4 volatile uint32_t IDR; 5 volatile uint32_t ODR; 6 volatile uint32_t BSRR; 7 volatile uint32_t BRR; 8 volatile uint32_t LCKR; 9 } 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)

      如下:

      1 #define GPIOA ((GPIO_TypeDef *) 0X4001 0800) 2 #define GPIOB ((GPIO_TypeDef *) 0X4001 0C00) 3 #define GPIOC ((GPIO_TypeDef *) 0X4001 1000) 4 #define GPIOD ((GPIO_TypeDef *) 0X4001 1400) 5 #define GPIOE ((GPIO_TypeDef *) 0X4001 1800) 6 #define GPIOF ((GPIO_TypeDef *) 0X4001 1C00) 7 #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í)候,

      只需要在這三條總線的基址上加上偏移地址即可,代碼如下:

      1 #define PERIPH_BASE ((uint32_t)0x40000000) 2 #define APB1PERIPH_BASE PERIPH_BASE 3 #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) 4 #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)

      GPIO 端口基地址

      因?yàn)镚PIO 掛載到APB2 總線上,那么現(xiàn)在我們就可以根據(jù)APB2 的基址算出各個(gè)

      GPIO 端口的基地址,用宏定義實(shí)現(xiàn)代碼如下:

      1 #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) 2 #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00) 3 #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000) 4 #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400) 5 #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800) 6 #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00) 7 #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000)

      第二層級(jí):基地址宏定義+結(jié)構(gòu)體封裝完成用STM32 控制一個(gè)LED 的完整代碼:

      1 #include 2 #define __IO volatile 3 4typedef struct { 5 __IO uint32_t CRL; 6 __IO uint32_t CRH; 7 __IO uint32_t IDR; 8 __IO uint32_t ODR; 9 __IO uint32_t BSRR; 10 __IO uint32_t BRR; 11 __IO uint32_t LCKR; 12 } GPIO_TypeDef; 13 14 typedef struct { 15 __IO uint32_t CR; 16 __IO uint32_t CFGR; 17 __IO uint32_t CIR; 18 __IO uint32_t APB2RSTR; 19 __IO uint32_t APB1RSTR; 20 __IO uint32_t AHBENR; 21 __IO uint32_t APB2ENR; 22 __IO uint32_t APB1ENR; 23 __IO uint32_t BDCR; 24 __IO uint32_t CSR; 25 } RCC_TypeDef; 26 27 #define PERIPH_BASE ((uint32_t)0x40000000) 28 29 #define APB1PERIPH_BASE PERIPH_BASE 30 #define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) 31 #define AHBPERIPH_BASE (PERIPH_BASE + 0x20000) 32 33 #define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) 34 #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00) 35 #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000) 36 #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400) 37 #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800) 38 #define GPIOF_BASE (APB2PERIPH_BASE + 0x1C00) 39 #define GPIOG_BASE (APB2PERIPH_BASE + 0x2000) 40 #define RCC_BASE (AHBPERIPH_BASE + 0x1000) 41 42 #define GPIOA ((GPIO_TypeDef *) GPIOA_BASE) 43 #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE) 44 #define GPIOC ((GPIO_TypeDef *) GPIOC_BASE) 45 #define GPIOD ((GPIO_TypeDef *) GPIOD_BASE) 46 #define GPIOE ((GPIO_TypeDef *) GPIOE_BASE) 47 #define GPIOF ((GPIO_TypeDef *) GPIOF_BASE) 48 #define GPIOG ((GPIO_TypeDef *) GPIOG_BASE) 49 #define RCC ((RCC_TypeDef *) RCC_BASE) 50 51 52 #define RCC_APB2ENR *(volatile unsigned long *)0x40021018 53 #define GPIOB_CRL *(volatile unsigned long *)0x40010C00 54 #define GPIOB_ODR *(volatile unsigned long *)0x40010C0C 55 56 int main(void) 57 { 58 // 開啟端口B 的時(shí)鐘 59 RCC->APB2ENR |= 1<<3; 60 61 // 配置PB0 為通用推挽輸出模式,速率為2M 62 GPIOB->CRL = (2<<0) | (0<<2); 63 64 // PB0 輸出低電平,點(diǎn)亮LED 65 GPIOB->ODR = 0<<0; 66 67 } 68 69 void SystemInit(void) 70 { 71 }

      第二層級(jí)變化:

      ①、定義一個(gè)外設(shè)(GPIO)寄存器結(jié)構(gòu)體,結(jié)構(gòu)體的成員包含該外設(shè)的所有寄存器,

      成員的排列順序跟寄存器偏移地址一樣,成員的數(shù)據(jù)類型跟寄存器的一樣。

      ②外設(shè)內(nèi)存映射,即把地址跟外設(shè)建立起一一對(duì)應(yīng)的關(guān)系。

      ③外設(shè)聲明,即把外設(shè)的名字定義成一個(gè)外設(shè)寄存器結(jié)構(gòu)體類型的指針。

      ④通過結(jié)構(gòu)體操作寄存器,實(shí)現(xiàn)點(diǎn)亮LED。

      第三層級(jí):基地址宏定義+結(jié)構(gòu)體封裝+“位封裝”(每一位的對(duì)應(yīng)字節(jié)封裝)

      上面我們?cè)诳刂艷PIO 輸出內(nèi)容的時(shí)候控制的是ODR(Output data register)寄存器,

      ODR 是一個(gè)16 位的寄存器,必須以字的形式控制

      其實(shí)我們還可以控制BSRR 和BRR 這兩個(gè)寄存器來控制IO 的電平,下面我們簡(jiǎn)單介

      紹下BRR 寄存器的功能,BSRR 自行看手冊(cè)研究。

      位清除寄存器BRR 只能實(shí)現(xiàn)位清0 操作,是一個(gè)32 位寄存器,低16 位有效,寫0 沒

      影響,寫1 清0。

      現(xiàn)在我們要使PB0 輸出低電平,點(diǎn)亮LED,則只要往BRR 的BR0 位寫1 即可,其他

      位為0,代碼如下:

      1 GPIOB->BRR = 0X0001;

      這時(shí)PB0 就輸出了低電平,LED 就被點(diǎn)亮了。

      如果要PB2 輸出低電平,則是:

      1 GPIOB->BRR = 0X0004;

      如果要PB3/4/5/6。。。。。。這些IO 輸出低電平呢?道理是一樣的,只要往BRR 的

      相應(yīng)位置賦不同的值即可。因?yàn)锽RR 是一個(gè)16 位的寄存器,位數(shù)比較多,賦值的時(shí)候容

      易出錯(cuò),而且從賦值的16 進(jìn)制數(shù)字我們很難清楚的知道控制的是哪個(gè)IO。這時(shí),我們是

      否可以把BRR 的每個(gè)位置1 都用宏定義來實(shí)現(xiàn),如GPIO_Pin_0 就表示0X0001,

      GPIO_Pin_2 就表示0X0004。只要我們定義一次,以后都可以使用,而且還見名知意。

      “位封裝”(每一位的對(duì)應(yīng)字節(jié)封裝) 代碼如下:

      1 #define GPIO_Pin_0 ((uint16_t)0x0001) /*!< Pin 0 selected */ 2 #define GPIO_Pin_1 ((uint16_t)0x0002) /*!< Pin 1 selected */ 3 #define GPIO_Pin_2 ((uint16_t)0x0004) /*!< Pin 2 selected */ 4 #define GPIO_Pin_3 ((uint16_t)0x0008) /*!< Pin 3 selected */ 5 #define GPIO_Pin_4 ((uint16_t)0x0010) /*!< Pin 4 selected */ 6 #define GPIO_Pin_5 ((uint16_t)0x0020) /*!< Pin 5 selected */ 7 #define GPIO_Pin_6 ((uint16_t)0x0040) /*!< Pin 6 selected */ 8 #define GPIO_Pin_7 ((uint16_t)0x0080) /*!< Pin 7 selected */ 9 #define GPIO_Pin_8 ((uint16_t)0x0100) /*!< Pin 8 selected */ 10 #define GPIO_Pin_9 ((uint16_t)0x0200) /*!< Pin 9 selected */ 11 #define GPIO_Pin_10 ((uint16_t)0x0400) /*!< Pin 10 selected */ 12 #define GPIO_Pin_11 ((uint16_t)0x0800) /*!< Pin 11 selected */ 13 #define GPIO_Pin_12 ((uint16_t)0x1000) /*!< Pin 12 selected */ 14 #define GPIO_Pin_13 ((uint16_t)0x2000) /*!< Pin 13 selected */ 15 #define GPIO_Pin_14 ((uint16_t)0x4000) /*!< Pin 14 selected */ 16 #define GPIO_Pin_15 ((uint16_t)0x8000) /*!< Pin 15 selected */ 17 #define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< All pins selected */

      1 GPIOB->BRR = GPIO_Pin_0;

      (如果同時(shí)讓PB0/PB15輸出低電平,用或運(yùn)算,代碼:

      1 GPIOB->BRR = GPIO_Pin_0|GPIO_Pin_15;

      為了不使main 函數(shù)看起來冗余,上述庫封裝 的代碼不應(yīng)該放在main 里面,因

      為其是跟GPIO 相關(guān)的,我們可以把這些宏放在一個(gè)單獨(dú)的頭文件里面。

      在工程目錄下新建stm32f10x_gpio.h,把封裝代碼放里面,然后把這個(gè)

      文件添加到工程里面。這時(shí)我們只需要在main.c 里面包含這個(gè)頭文件即可。

      第四層級(jí):基地址宏定義+結(jié)構(gòu)體封裝+“位封裝”+函數(shù)封裝

      我們點(diǎn)亮LED 的時(shí)候,控制的是PB0 這個(gè)IO,如果LED 接到的是其他IO,我們就

      需要把GPIOB 修改成其他的端口,其實(shí)這樣修改起來也很快很方便。但是為了提高程序的

      可讀性和可移植性,我們是否可以編寫一個(gè)專門的函數(shù)用來復(fù)位GPIO 的某個(gè)位,這個(gè)函

      數(shù)有兩個(gè)形參,一個(gè)是GPIOX(X=A...G),另外一個(gè)是GPIO_Pin(0...15),函數(shù)的主體

      則是根據(jù)形參GPIOX 和GPIO_Pin 來控制BRR 寄存器,代碼如下:

      1 void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) 2 { 3 GPIOx->BRR = GPIO_Pin; 4 }

      這時(shí),PB0 輸出低電平,點(diǎn)亮LED 的代碼就變成了:

      1 GPIO_ResetBits(GPIOB,GPIO_Pin_0);

      同理, 我們可以控制BSRR 這個(gè)寄存器來實(shí)現(xiàn)關(guān)閉LED,代碼如下:

      1 // GPIO 端口置位函數(shù) 2 void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) 3 { 4 GPIOx->BSRR = GPIO_Pin; 5 }

      這時(shí),PB0 輸出高電平,關(guān)閉LED 的代碼就變成了:

      1 GPIO_SetBits(GPIOB,GPIO_Pin_0);

      同樣,因?yàn)檫@個(gè)函數(shù)是控制GPIO 的函數(shù),我們可以新建一個(gè)專門的文件來放跟gpio

      有關(guān)的函數(shù)。

      在工程目錄下新建stm32f10x_gpio.c,把GPIO 相關(guān)的函數(shù)放里面。

      這時(shí)我們是否發(fā)現(xiàn)剛剛新建了一個(gè)頭文件stm32f10x_gpio.h,這兩個(gè)文件存放的都是

      跟外設(shè)GPIO 相關(guān)的。C 文件里面的函數(shù)會(huì)用到h 頭文件里面的定義,這兩個(gè)文件是相輔

      相成的,故我們?cè)趕tm32f10x_gpio.c 文件中也包含stm32f10x_gpio.h 這個(gè)頭文件。別忘了把

      stm32f10x.h 這個(gè)頭文件也包含進(jìn)去,因?yàn)橛嘘P(guān)寄存器的所有定義都在這個(gè)頭文件里面。

      如果我們寫其他外設(shè)的函數(shù),我們也應(yīng)該跟GPIO 一樣,新建兩個(gè)文件專門來存函

      數(shù),比如RCC 這個(gè)外設(shè)我們可以新建stm32f10x_rcc.c 和stm32f10x_rcc.h。其他外依葫蘆

      畫瓢即可。

      (5)實(shí)例編寫

      以上,是對(duì)庫封住過程的概述,下面我們正在地使用庫函數(shù)編寫LED程序

      ①管理庫的頭文件

      當(dāng)我們開始調(diào)用庫函數(shù)寫代碼的時(shí)候,有些

      庫我們不需要,在編譯的時(shí)候可以不編譯,可以通過一個(gè)總的頭文件stm32f10x_conf.h 來

      控制,該頭文件主要代碼如下:

      1 //#include "stm32f10x_adc.h" 2 //#include "stm32f10x_bkp.h" 3 //#include "stm32f10x_can.h" 4 //#include "stm32f10x_cec.h" 5 //#include "stm32f10x_crc.h" 6 //#include "stm32f10x_dac.h" 7 //#include "stm32f10x_dbgmcu.h" 8 //#include "stm32f10x_dma.h" 9 //#include "stm32f10x_exti.h" 10 //#include "stm32f10x_flash.h" 11 //#include "stm32f10x_fsmc.h" 12 #include "stm32f10x_gpio.h" 13 //#include "stm32f10x_i2c.h" 14 //#include "stm32f10x_iwdg.h" 15 //#include "stm32f10x_pwr.h" 16 #include "stm32f10x_rcc.h" 17 //#include "stm32f10x_rtc.h" 18 //#include "stm32f10x_sdio.h" 19 //#include "stm32f10x_spi.h" 20 //#include "stm32f10x_tim.h" 21 //#include "stm32f10x_usart.h" 22 //#include "stm32f10x_wwdg.h" 23 //#include "misc.h"

      這里面包含了全部外設(shè)的頭文件,點(diǎn)亮一個(gè)LED 我們只需要RCC 和GPIO 這兩個(gè)外

      設(shè)的庫函數(shù)即可,其中RCC 控制的是時(shí)鐘,GPIO 控制的具體的IO 口。所以其他外設(shè)庫函

      數(shù)的頭文件我們注釋掉,當(dāng)我們需要的時(shí)候就把相應(yīng)頭文件的注釋去掉即可。

      stm32f10x_conf.h 這個(gè)頭文件在stm32f10x.h 這個(gè)頭文件的最后面被包含,在第8296

      行:

      1 #ifdef USE_STDPERIPH_DRIVER 2 #include "stm32f10x_conf.h" 3 #endif

      代碼的意思是,如果定義了USE_STDPERIPH_DRIVER 這個(gè)宏的話,就包含

      stm32f10x_conf.h 這個(gè)頭文件。我們?cè)谛陆üこ痰臅r(shí)候,在魔術(shù)棒選項(xiàng)卡C/C++中,我們定

      義了USE_STDPERIPH_DRIVER 這個(gè)宏,所以stm32f10x_conf.h 這個(gè)頭文件就被

      stm32f10x.h 包含了,我們?cè)趯懗绦虻臅r(shí)候只需要調(diào)用一個(gè)頭文件:stm32f10x.h 即可。(預(yù)

      處理指令詳細(xì)內(nèi)容會(huì)在【C語言】的文章中提到)

      ②編寫LED 初始化函數(shù)

      經(jīng)過寄存器點(diǎn)亮LED 的操作,我們知道操作一個(gè)GPIO 輸出的編程要點(diǎn)大概如下:

      1、開啟GPIO 的端口時(shí)鐘

      2、選擇要具體控制的IO 口,即pin

      3、選擇IO 口輸出的速率,即speed

      4、選擇IO 口輸出的模式,即mode

      5、輸出高/低電平

      STM32 的時(shí)鐘功能非常豐富,配置靈活,為了降低功耗,每個(gè)外設(shè)的時(shí)鐘都可以獨(dú)自

      的關(guān)閉和開啟。STM32 中跟時(shí)鐘有關(guān)的功能都由RCC 這個(gè)外設(shè)控制,RCC 中有三個(gè)寄存

      器控制著所以外設(shè)時(shí)鐘的開啟和關(guān)閉:RCC_APHENR、RCC_APB2ENR 和

      RCC_APB1ENR,AHB、APB2 和APB1 代表著三條總線,所有的外設(shè)都是掛載到這三條

      總線上,GPIO 屬于高速的外設(shè),掛載到APB2 總線上,所以其時(shí)鐘有RCC_APB2ENR 控

      制。

      GPIO 時(shí)鐘控制

      固件庫函數(shù):RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE)函數(shù)的

      原型為:

      1 void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState) 2 { 3 /* Check the parameters */ 4 assert_param(IS_RCC_APB2_PERIPH(RCC_APB2Periph)); 5 assert_param(IS_FUNCTIONAL_STATE(NewState)); 6 if (NewState != DISABLE) { 7 RCC->APB2ENR |= RCC_APB2Periph; 8 } else { 9 RCC->APB2ENR &= ~RCC_APB2Periph; 10 } 11 }

      當(dāng)程序編譯一次之后,把光標(biāo)定位到函數(shù)/變量/宏定義處,按鍵盤的F12 或鼠標(biāo)右鍵

      的Go to definition of,就可以找到原型。固件庫的底層操作的就是RCC 外設(shè)的APB2ENR

      這個(gè)寄存器,宏RCC_APB2Periph_GPIOB 的原型是:0x00000008,即(1<<3),還原成

      存器操作就是:RCC->APB2ENR |= 1<<<3。相比固件庫操作,寄存器操作的代碼可讀性

      就很差,只有才查閱寄存器配置才知道具體代碼的功能,而固件庫操作恰好相反,見名知

      意。

      GPIO 端口配置

      GPIO 的pin,速度,模式,都由GPIO 的端口配置寄存器來控制,其中IO0~IO7 由端

      口配置低寄存器CRL 控制,IO8~IO15 由端口配置高寄存器CRH 配置。

      固件庫把端口配置的pin,速度和模式封裝成一個(gè)結(jié)構(gòu)體:

      1 typedef struct { 2 uint16_t GPIO_Pin; 3 GPIOSpeed_TypeDef GPIO_Speed; 4 GPIOMode_TypeDef GPIO_Mode; 5 } GPIO_InitTypeDef;

      pin 可以是GPIO_Pin_0~GPIO_Pin_15 或者是GPIO_Pin_All,這些都是庫預(yù)先定義好的

      宏。

      speed 也被封裝成一個(gè)結(jié)構(gòu)體:

      1 typedef enum { 2 GPIO_Speed_10MHz = 1, 3 GPIO_Speed_2MHz, 4 GPIO_Speed_50MHz 5 } GPIOSpeed_TypeDef;

      速度可以是10M,2M 或者50M,這個(gè)由端口配置寄存器的MODE 位控制,速度是針

      對(duì)IO 口輸出的時(shí)候而言,在輸入的時(shí)候可以不用設(shè)置。

      mode 也被封裝成一個(gè)結(jié)構(gòu)體:

      1 typedef enum { 2 GPIO_Mode_AIN = 0x0, // 模擬輸入 3 GPIO_Mode_IN_FLOATING = 0x04, // 浮空輸入(復(fù)位后的狀態(tài)) 4 GPIO_Mode_IPD = 0x28, // 下拉輸入 5 GPIO_Mode_IPU = 0x48, // 上拉輸入 6 GPIO_Mode_Out_OD = 0x14, // 通用開漏輸出 7 GPIO_Mode_Out_PP = 0x10, // 通用推挽輸出 8 GPIO_Mode_AF_OD = 0x1C, // 復(fù)用開漏輸出 9 GPIO_Mode_AF_PP = 0x18 // 復(fù)用推挽輸出 10 } GPIOMode_TypeDef;

      IO 口的模式有8 種,輸入輸出各4 種,由端口配置寄存器的CNF 配置。平時(shí)用的最多

      的就是通用推挽輸出,可以輸出高低電平,驅(qū)動(dòng)能力大,一般用于接數(shù)字器件。至于剩下

      的七種模式的用法和電路原理,我們?cè)诤竺娴腉PIO 章節(jié)再詳細(xì)講解。

      最終用固件庫實(shí)現(xiàn)就變成這樣:

      1 // 定義一個(gè)GPIO_InitTypeDef 類型的結(jié)構(gòu)體 2 GPIO_InitTypeDef GPIO_InitStructure; 3 4// 選擇要控制的IO 口 5 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; 6 7// 設(shè)置引腳為推挽輸出 8 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 9 10 // 設(shè)置引腳速率為50MHz 11 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; 12 13 /*調(diào)用庫函數(shù),初始化GPIOB0*/ 14 GPIO_Init(GPIOB, &GPIO_InitStructure);

      倘若同一端口下不同引腳有不同的模式配置,每次對(duì)每個(gè)引腳配置完成后都要調(diào)用GPIO初始化函數(shù),代碼如下:

      GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉輸入 GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_Init(GPIOB, &GPIO_InitStructure);

      GPIO 輸出控制

      GPIO 輸出控制,可以通過端口數(shù)據(jù)輸出寄存器ODR、端口位設(shè)置/清除寄存器BSRR

      和端口位清除寄存器BRR 這三個(gè)來控制。

      端口輸出寄存器ODR 是一個(gè)32 位的寄存器,低16 位有效,對(duì)應(yīng)著IO0~IO15,只能

      以字的形式操作,一般使用寄存器操作。

      // PB0 輸出高電平,點(diǎn)亮LED GPIOB->ODR = 1<<0;

      端口位清除寄存器BRR 是一個(gè)32 位的寄存器,低十六位有效,對(duì)應(yīng)著IO0~IO15,只

      能以字的形式操作,可以單獨(dú)對(duì)某一個(gè)位操作,寫1 清0。

      // PB0 輸出低電平,點(diǎn)亮LED GPIO_ResetBits(GPIOB, GPIO_Pin_0);

      BSRR 是一個(gè)32 位的寄存器,低16 位用于置位,寫1 有效,高16 位用于復(fù)位,寫1

      有效,相當(dāng)于BRR 寄存器。高16 位我們一般不用,而是操作BRR 這個(gè)寄存器,所以

      BSRR 這個(gè)寄存器一般用來置位操作。

      // PB0 輸出高電平,熄滅LED GPIO_SetBits(GPIOB, GPIO_Pin_0);

      綜上:固件庫LED GPIO 初始化函數(shù)

      1 void LED_GPIO_Config(void) 2 { 3 // 定義一個(gè)GPIO_InitTypeDef 類型的結(jié)構(gòu)體 4 GPIO_InitTypeDef GPIO_InitStructure; 5 6// 開啟GPIOB 的時(shí)鐘 7 RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); 8 9// 選擇要控制的IO 口 10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; 11 12 // 設(shè)置引腳為推挽輸出 13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 14 15 // 設(shè)置引腳速率為50MHz 16 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 17 18 /*調(diào)用庫函數(shù),初始化GPIOB0*/ 19 GPIO_Init(GPIOB, &GPIO_InitStructure); 20 21 // 關(guān)閉LED 22 GPIO_SetBits(GPIOB, GPIO_Pin_0); 23 }

      主函數(shù)

      1 #include "stm32f10x.h" 2 3 void SOFT_Delay(__IO uint32_t nCount); 4 void LED_GPIO_Config(void); 5 6int main(void) 7 { 8 // 程序來到main 函數(shù)之前,啟動(dòng)文件:statup_stm32f10x_hd.s 已經(jīng)調(diào)用 9 // SystemInit()函數(shù)把系統(tǒng)時(shí)鐘初始化成72MHZ 10 // SystemInit()在system_stm32f10x.c 中定義 11 // 如果用戶想修改系統(tǒng)時(shí)鐘,可自行編寫程序修改 12 13 LED_GPIO_Config(); 14 15 while ( 1 ) { 16 // 點(diǎn)亮LED 17 GPIO_ResetBits(GPIOB, GPIO_Pin_0); 18 Time_Delay(0x0FFFFF); 19 20 // 熄滅LED 21 GPIO_SetBits(GPIOB, GPIO_Pin_0); 22 Time_Delay(0x0FFFFF); 23 } 24 } 25// 簡(jiǎn)陋的軟件延時(shí)函數(shù) 26 void Time_Delay(volatile uint32_t Count) 27 { 28 for (; Count != 0; Count--); 29 }

      注意void Time_Delay(volatile uint32_t Count)只是一個(gè)簡(jiǎn)陋的軟件延時(shí)函數(shù),如果小伙伴們有興趣可以看一看MultiTimer,它是一個(gè)軟件定時(shí)器擴(kuò)展模塊,可無限擴(kuò)展所需的定時(shí)器任務(wù),取代傳統(tǒng)的標(biāo)志位判斷方式, 更優(yōu)雅更便捷地管理程序的時(shí)間觸發(fā)時(shí)序。

      C 語言 單片機(jī) 嵌入式

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:Windows 消息機(jī)制淺析
      下一篇:Linux 內(nèi)核代碼風(fēng)格
      相關(guān)文章
      久久久久亚洲AV无码去区首| 亚洲图片中文字幕| 在线观看亚洲AV日韩AV| 亚洲三级中文字幕| 91亚洲自偷在线观看国产馆| 亚洲人成影院在线高清| 亚洲国产成人精品青青草原| 亚洲日产2021三区| 亚洲人成网站在线观看播放青青| 亚洲欧洲日产v特级毛片| 亚洲国产精品综合福利专区| 亚洲国产成人久久三区| 天堂亚洲国产中文在线| 亚洲午夜福利在线视频| 亚洲爆乳无码专区www| 国产精品无码亚洲一区二区三区| 亚洲国产成人手机在线观看 | 朝桐光亚洲专区在线中文字幕| 日本亚洲欧美色视频在线播放| 久久精品亚洲日本波多野结衣| 一本久久综合亚洲鲁鲁五月天| 亚洲А∨精品天堂在线| 亚洲一区二区三区无码影院| 亚洲综合av永久无码精品一区二区| 在线观看亚洲成人| 国产成人精品日本亚洲网站| 久久久综合亚洲色一区二区三区| 亚洲成AV人片在线观看无码| 亚洲韩国—中文字幕| 亚洲精品天天影视综合网| 亚洲视频在线观看地址| 亚洲a级在线观看| 亚洲日韩精品无码专区| yy6080亚洲一级理论| 国产亚洲精品久久久久秋霞 | 亚洲国产视频久久| 久久亚洲色WWW成人欧美| 亚洲一级Av无码毛片久久精品| 亚洲国产精品国自产拍AV| 亚洲综合久久久久久中文字幕| 亚洲国产日韩综合久久精品|