學習本章時,配合《STM32F4xx 中文參考手冊》 “通用 I/O(GPIO)” 章節一起閱讀,效果會更佳,特别是涉及到寄存器說明的部分。 關于建立工程時使用 KEIL5 的基本操作,請參考前面的章節。
本講建議看火哥視訊,很重要。
連結:https://pan.baidu.com/s/1Dusgd-K1pPJpILpcp7xawg
提取碼:w6nx
5.1 GPIO 簡介:
GPIO 是通用輸入輸出端口的簡稱,簡單來說就是 STM32 可控制的引腳, STM32 晶片的 GPIO 引腳與外部裝置連接配接起來,進而實作與外部通訊、控制以及資料采集的功能。STM32 晶片的 GPIO 被分成很多組,每組有 16 個引腳,如型号為 STM32F4IGT6 型号的晶片有 GPIOA、 GPIOB、 GPIOC 至 GPIOI 共 9 組 GPIO,晶片一共 176 個引腳,其中 GPIO就占了一大部分,所有的 GPIO 引腳都有基本的輸入輸出功能。
最基本的輸出功能是由 STM32 控制引腳輸出高、低電平,實作開關控制,如把 GPIO引腳接入到 LED 燈,那就可以控制 LED 燈的亮滅,引腳接入到繼電器或三極管,那就可以通過繼電器或三極管控制外部大功率電路的通斷。
最基本的輸入功能是檢測外部輸入電平,如把 GPIO 引腳連接配接到按鍵,通過電平高低區分按鍵是否被按下。
5.2 GPIO 框圖剖析:

通過 GPIO 硬體結構框圖,就可以從整體上深入了解 GPIO 外設及它的各種應用模式。該圖從最右端看起,最右端就是代表 STM32 晶片引出的 GPIO 引腳,其餘部件都位于晶片内部。
5.2.1 基本結構分析:
下面我們按圖中的編号對 GPIO 端口的結構部件進行說明。
1. 保護二極管及上、下拉電阻:
引腳的兩個保護二級管可以防止引腳外部過高或過低的電壓輸入,當引腳電壓高于VDD_FT 時, 上方的二極管導通,當引腳電壓低于 VSS 時,下方的二極管導通,防止不正常電壓引入晶片導緻晶片燒毀。盡管有這樣的保護,并不意味着 STM32 的引腳能直接外接大功率驅動器件,如直接驅動電機,強制驅動要麼電機不轉,要麼導緻晶片燒壞,必須要加大功率及隔離電路驅動。具體電壓、電流範圍可查閱《STM32F4xx 規格書》。
上拉、下拉電阻, 從它的結構我們可以看出, 通過上、下拉對應的開關配置,我們可以控制引腳預設狀态的電壓,開啟上拉的時候引腳電壓為高電平,開啟下拉的時候引腳電壓為低電平,這樣可以消除引腳不定狀态的影響。如引腳外部沒有外接器件,或者外部的器件不幹擾該引腳電壓時, STM32 的引腳都會有這個預設狀态。
也可以設定“既不上拉也不下拉模式”,我們也把這種狀态稱為浮空模式,配置成這個模式時,直接用電壓表測量其引腳電壓為 1 點幾伏,這是個不确定值。是以一般來說我們都會選擇給引腳設定“上拉模式”或“下拉模式”使它有預設狀态。
STM32 的内部上拉是“弱上拉”,即通過此上拉輸出的電流是很弱的,如要求大電流還是需要外部上拉。
通過“上拉/下拉寄存器 GPIOx_PUPDR”控制引腳的上、下拉以及浮空模式。
2. P-MOS 管和 N-MOS 管:
GPIO 引腳線路經過上、下拉電阻結構後,向上流向“輸入模式”結構,向下流向“輸出模式”結構。先看輸出模式部分,線路經過一個由 P-MOS 和 N-MOS 管組成的單元電路。這個結構使 GPIO 具有了“推挽輸出”和“開漏輸出”兩種模式。
所謂的推挽輸出模式,是根據這兩個 MOS 管的工作方式來命名的。在該結構中輸入高電平時,上方的 P-MOS 導通,下方的 N-MOS 關閉,對外輸出高電平;而在該結構中輸入低電平時, N-MOS 管導通, P-MOS 關閉,對外輸出低電平。當引腳高低電平切換時,兩個管子輪流導通,一個負責灌電流,一個負責拉電流,使其負載能力和開關速度都比普通的方式有很大的提高。推挽輸出的低電平為 0 伏,高電平為 3.3 伏,參考圖 5-2 左側,它是推挽輸出模式時的等效電路。
而在開漏輸出模式時,上方的 P-MOS 管完全不工作。如果我們控制輸出為 0,低電平,則 P-MOS 管關閉, N-MOS 管導通,使輸出接地,若控制輸出為 1 (它無法直接輸出高電平)時,則 P-MOS 管和 N-MOS 管都關閉,是以引腳既不輸出高電平,也不輸出低電平,為高阻态。為正常使用時必須接上拉電阻(可用 STM32 的内部上拉,但建議在 STM32 外部再接一個上拉電阻),參考圖 5-2 中的右側等效電路。它具“線與”特性,也就是說,若有很多個開漏模式引腳連接配接到一起時,隻有當所有引腳都輸出高阻态,才由上拉電阻提供高電平,
此高電平的電壓為外部上拉電阻所接的電源的電壓。若其中一個引腳為低電平,那線路就相當于短路接地,使得整條線路都為低電平, 0 伏。
推挽輸出模式一般應用在輸出電平為 0 和 3.3 伏而且需要高速切換開關狀态的場合。在 STM32 的應用中,除了必須用開漏模式的場合,我們都習慣使用推挽輸出模式。
開漏輸出一般應用在 I2C、 SMBUS 通訊等需要“線與”功能的總線電路中。除此之外,還用在電平不比對的場合,如需要輸出 5 伏的高電平,就可以在外部接一個上拉電阻, 上拉電源為 5 伏, 并且把 GPIO 設定為開漏模式,當輸出高阻态時,由上拉電阻和電源向外輸出 5 伏的電平。
通過 “輸出類型寄存器 GPIOx_OTYPER”可以控制 GPIO 端口是推挽模式還是開漏模式。
3. 輸出資料寄存器:
前面提到的雙 MOS 管結構電路的輸入信号,是由 GPIO“輸出資料寄存器GPIOx_ODR”提供的,是以我們通過修改輸出資料寄存器的值就可以修改 GPIO 引腳的輸出電平。而“置位/複位寄存器 GPIOx_BSRR”可以通過修改輸出資料寄存器的值進而影響電路的輸出。
4. 複用功能輸出:
“複用功能輸出”中的“複用”是指 STM32 的其它片上外設對 GPIO 引腳進行控制,此時 GPIO 引腳用作該外設功能的一部分,算是第二用途。從其它外設引出來的“複用功能輸出信号”與 GPIO 本身的資料據寄存器都連接配接到雙 MOS 管結構的輸入中,通過圖中的梯形結構作為開關切換選擇。
例如我們使用 USART 序列槽通訊時,需要用到某個 GPIO 引腳作為通訊發送引腳,這個時候就可以把該 GPIO 引腳配置成 USART 序列槽複用功能,由序列槽外設控制該引腳,發送資料。
5. 輸入資料寄存器:
看 GPIO 結構框圖的上半部分,它是 GPIO 引腳經過上、下拉電阻後引入的,它連接配接到施密特觸發器,信号經過觸發器後,模拟信号轉化為 0、 1 的數字信号,然後存儲在“輸入資料寄存器 GPIOx_IDR”中,通過讀取該寄存器就可以了解 GPIO 引腳的電平狀态。
6. 複用功能輸入:
與“複用功能輸出”模式類似,在“複用功能輸出模式”時, GPIO 引腳的信号傳輸到STM32 其它片上外設,由該外設讀取引腳狀态。
同樣,如我們使用 USART 序列槽通訊時,需要用到某個 GPIO 引腳作為通訊接收引腳,這個時候就可以把該 GPIO 引腳配置成 USART 序列槽複用功能,使 USART 可以通過該通訊引腳的接收遠端資料。
7. 模拟輸入輸出:
當 GPIO 引腳用于 ADC 采集電壓的輸入通道時,用作“模拟輸入”功能,此時信号是不經過施密特觸發器的,因為經過施密特觸發器後信号隻有 0、 1 兩種狀态,是以 ADC 外設要采集到原始的模拟信号,信号源輸入必須在施密特觸發器之前。類似地,當 GPIO 引腳用于 DAC 作為模拟電壓輸出通道時,此時作為“模拟輸出”功能, DAC 的模拟信号輸出就不經過雙 MOS 管結構了,在 GPIO 結構框圖的右下角處,模拟信号直接輸出到引腳。同時,當 GPIO 用于模拟功能時(包括輸入輸出),引腳的上、下拉電阻是不起作用的,這個時候即使在寄存器配置了上拉或下拉模式,也不會影響到模拟信号的輸入輸出。
5.2.2 GPIO 工作模式:
總結一下,由 GPIO 的結構決定了 GPIO 可以配置成以下模式:
1. 輸入模式(上拉/下拉/浮空):
在輸入模式時, 施密特觸發器打開, 輸出被禁止。 資料寄存器每隔 1 個 AHB1 時鐘周期更新一次,可通過輸入資料寄存器GPIOx_IDR 讀取 I/O 狀态。 其中 AHB1 的時鐘如按預設配置一般為 180MHz。用于輸入模式時,可設定為上拉、下拉或浮空模式。
2. 輸出模式(推挽/開漏,上拉/下拉):
在輸出模式中, 輸出使能,推挽模式時雙 MOS 管以方式工作,輸出資料寄存器GPIOx_ODR 可控制 I/O 輸出高低電平。開漏模式時,隻有 N-MOS 管工作,輸出資料寄存器可控制 I/O 輸出高阻态或低電平。 輸出速度可配置,有 2MHz\25MHz\50MHz\100MHz 的選項。
此處的輸出速度即 I/O 支援的高低電平狀态最高切換頻率,支援的頻率越高,功耗越大,如果功耗要求不嚴格,把速度設定成最大即可。此時施密特觸發器是打開的,即輸入可用,通過輸入資料寄存器 GPIOx_IDR 可讀取I/O 的實際狀态。
用于輸出模式時,可使用上拉、 下拉模式或浮空模式。但此時由于輸出模式時引腳電平會受到 ODR 寄存器影響,而 ODR 寄存器對應引腳的位為 0,即引腳初始化後預設輸出低電平,是以在這種情況下,上拉隻起到小幅提高輸出電流能力,但不會影響引腳的預設狀态。
3. 複用功能(推挽/開漏,上拉/下拉):
複用功能模式中,輸出使能,輸出速度可配置,可工作在開漏及推挽模式,但是輸出信号源于其它外設,輸出資料寄存器 GPIOx_ODR 無效;輸入可用,通過輸入資料寄存器可擷取 I/O 實際狀态,但一般直接用外設的寄存器來擷取該資料信号。
用于複用功能時,可使用上拉、 下拉模式或浮空模式。同輸出模式,在這種情況下,初始化後引腳預設輸出低電平,上拉隻起到小幅提高輸出電流能力,但不會影響引腳的預設狀态。
4. 模拟輸入輸出:
模拟輸入輸出模式中,雙 MOS 管結構被關閉,施密特觸發器停用,上/下拉也被禁止。其它外設通過模拟通道進行輸入輸出。
通過對 GPIO 寄存器寫入不同的參數,就可以改變 GPIO 的應用模式,再強調一下,要了解具體寄存器時一定要查閱《STM32F4xx 參考手冊》中對應外設的寄存器說明。在GPIO 外設中,通過設定“模式寄存器 GPIOx_MODER”可配置 GPIO 的輸入/輸出/複用/模拟模式,“輸出類型寄存器 GPIOx_OTYPER”配置推挽/開漏模式,配置“輸出速度寄存器 GPIOx_OSPEEDR”可選 2/25/50/100MHz 輸出速度,“上/下拉寄存器 GPIOx_PUPDR”可配置上拉/下拉/浮空模式,各寄存器的具體參數值見表 5-1。
5.3 實驗:使用寄存器點亮 LED 燈:
本小節中,我們以執行個體講解如何通過控制寄存器來點亮 LED 燈。此處側重于講解原理,請您直接用 KEIL5 軟體打開我們提供的實驗例程配合閱讀,先了解原理,學習完本小節後,再嘗試自己建立一個同樣的工程。本節配套例程名稱為“GPIO 輸出—寄存器點亮 LED燈”,在工程目錄下找到字尾為“.uvprojx”的檔案,用 KEIL5 打開即可。
自己嘗試建立工程時,請對照查閱《用 KEIL5 建立工程模版 寄存器版本》章節。
若沒有安裝 KEIL5 軟體,請參考《如何安裝 KEIL5》章節。
打開該工程,可看到一共有三個檔案,分别 startup_stm32f429_439xx.s 、stm32f4xx.h 以及 main.c,下面我們對這三個工程進行講解。
5.3.1 硬體連接配接:
在本教程中 STM32 晶片與 LED 燈的連接配接見下圖。
圖中從 3 個 LED 燈的陽極引出連接配接到 3.3V 電源,陰極各經過 1 個電阻引入至 STM32的 3 個 GPIO 引腳 PH10、 PH11、 PH12 中,是以我們隻要控制這三個引腳輸出高低電平,即可控制其所連接配接 LED 燈的亮滅。如果您的實驗闆 STM32 連接配接到 LED 燈的引腳或極性不一樣,隻需要修改程式到對應的 GPIO 引腳即可,工作原理都是一樣的。
我們的目标是把 GPIO 的引腳設定成推挽輸出模式并且預設下拉,輸出低電平,這樣就能讓 LED 燈亮起來了。
5.3.2 啟動檔案:
名為“startup_stm32f429_439xx.s”的檔案,它裡邊使用彙編語言寫好了基本程式,當STM32 晶片上電啟動的時候,首先會執行這裡的彙程式設計式,進而建立起 C 語言的運作環境,是以我們把這個檔案稱為啟動檔案。該檔案使用的彙編指令是 Cortex-M4 核心支援的指令,可從《Cortex-M4 Technical Reference Manual》查到,也可參考《Cortex-M3 權威指南中文》, M3 跟 M4 大部分彙編指令相同。
startup_stm32f429_439xx.s 檔案是由官方提供的,一般有需要也是在官方的基礎上修改,不會自己完全重寫。該檔案可以從 KEIL5 安裝目錄找到,也可以從 ST 庫裡面找到,找到該檔案後把啟動檔案添加到工程裡面即可。不同型号的晶片以及不同編譯環境下使用的彙編檔案是不一樣的,但功能相同。
對于啟動檔案這部分我們主要總結它的功能,不詳解講解裡面的代碼,其功能如下:
初始化堆棧指針 SP;
初始化程式計數器指針 PC;
設定堆、棧的大小;
設定中斷向量表的入口位址;
配置外部 SRAM 作為資料存儲器(這個由使用者配置,一般的開發闆可沒有外部SRAM) ;
調用 SystemIni() 函數配置 STM32 的系統時鐘。
設定 C 庫的分支入口“__main”(最終用來調用 main 函數) ;
先去除繁枝細節,挑重點的講,主要了解最後兩點,在啟動檔案中有一段複位後立即執行的程式,代碼見代碼清單5-1。在實際工程中閱讀時,可使用編輯器的搜尋(Ctrl+F)功能查找這段代碼在檔案中的位置。
代碼清單 5-1 複位後執行的程式
;Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
開頭的是程式注釋,在彙編裡面注釋用的是“;”,相當于 C 語言的“//”注釋符
第二行是定義了一個子程式: Reset_Handler。 PROC 是子程式定義僞指令。這裡就相當于 C 語言裡定義了一個函數,函數名為 Reset_Handler。
第三行 EXPORT 表示 Reset_Handler 這個子程式可供其他子產品調用。 相當于 C 語言的函數聲明。關鍵字[WEAK] 表示弱定義,如果編譯器發現在别處定義了同名的函數,則在連結時用别處的位址進行連結,如果其它地方沒有定義,編譯器也不報錯,以此處位址進行連結,如果不了解 WEAK,那就忽略它好了。
第四行和第五行 IMPORT 說明 SystemInit 和__main 這兩個标号在其他檔案,在連結的時候需要到其他檔案去尋找。相當于 C 語言中,從其它檔案引入函數聲明。以便下面對外部函數進行調用。
SystemInit 需要由我們自己實作,即我們要編寫一個具有該名稱的函數,用來初始化STM32 晶片的時鐘,一般包括初始化 AHB、 APB 等各總線的時鐘,需要經過一系列的配置 STM32 才能達到穩定運作的狀态。
__main 其實不是我們定義的(不要與 C 語言中的 main 函數混淆),當編譯器編譯時,隻要遇到這個标号就會定義這個函數,該函數的主要功能是:負責初始化棧、堆,配置系統環境,準備好 C 語言并在最後跳轉到使用者自定義的 main 函數,從此來到 C 的世界。
第六行把 SystemInit 的位址加載到寄存器 R0。
第七行程式跳轉到 R0 中的位址執行程式,即執行 SystemInit 函數的内容。
第八行把__main 的位址加載到寄存器 R0。
第九行程式跳轉到 R0 中的位址執行程式,即執行__main 函數,執行完畢之後就去到我們熟知的 C 世界,進入 main 函數。
第十行表示子程式的結束。
總之,看完這段代碼後,了解到如下内容即可:我們需要在外部定義一個 SystemInit函數設定 STM32 的時鐘; STM32 上電後,會執行 SystemInit 函數,最後執行我們 C 語言中的 main 函數。
5.3.3 stm32f4xx.h 檔案:
看完啟動檔案,那我們立即寫 SystemInit 和 main 函數吧?别着急,定義好了SystemInit 函數和 main 我們又能寫什麼内容?連接配接 LED 燈的 GPIO 引腳,是要通過讀寫寄存器來控制的,就這樣空着手,如何控制寄存器呢。在上一章,我們知道寄存器就是特殊的記憶體空間,可以通過指針操作通路寄存器。是以此處我們根據 STM32 的存儲配置設定先定義好各個寄存器的位址,把這些位址定義都統一寫在 stm32f4xx.h 檔案中,見代碼清單 5-2。
代碼清單 5-2 外設位址定義
/*片上外設基位址 */
#define PERIPH_BASE ((unsigned int)0x40000000)
/*總線基位址 */
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
/*GPIO 外設基位址*/
#define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00)
/* GPIOH 寄存器位址,強制轉換成指針 */
#define GPIOH_MODER *(unsigned int*)(GPIOH_BASE+0x00)
#define GPIOH_OTYPER *(unsigned int*)(GPIOH_BASE+0x04)
#define GPIOH_OSPEEDR *(unsigned int*)(GPIOH_BASE+0x08)
#define GPIOH_PUPDR *(unsigned int*)(GPIOH_BASE+0x0C)
#define GPIOH_IDR *(unsigned int*)(GPIOH_BASE+0x10)
#define GPIOH_ODR *(unsigned int*)(GPIOH_BASE+0x14)
#define GPIOH_BSRR *(unsigned int*)(GPIOH_BASE+0x18)
#define GPIOH_LCKR *(unsigned int*)(GPIOH_BASE+0x1C)
#define GPIOH_AFRL *(unsigned int*)(GPIOH_BASE+0x20)
#define GPIOH_AFRH *(unsigned int*)(GPIOH_BASE+0x24)
/*RCC 外設基位址*/
#define RCC_BASE (AHB1PERIPH_BASE + 0x3800)
/*RCC 的 AHB1 時鐘使能寄存器位址,強制轉換成指針*/
#define RCC_AHB1ENR *(unsigned int*)(RCC_BASE+0x30)
GPIO 外設的位址跟上一章講解的相同,不過此處把寄存器的位址值都直接強制轉換成了指針,友善使用。代碼的最後兩段是 RCC 外設寄存器的位址定義, RCC 外設是用來設定時鐘的,以後我們會詳細分析,本實驗中隻要了解到使用 GPIO 外設必須開啟它的時鐘即可。
5.3.4 main 檔案:
現在就可以開始編寫程式了,在 main 檔案中先編寫一個 main 函數,裡面什麼都沒有,暫時為空.
int main (void)
{
}
此時直接編譯的話,會出現如下錯誤:
“Error: L6218E: Undefined symbol SystemInit (referred from startup_stm32f429_439xx.o)”
錯誤提示 SystemInit 沒有定義。從分析啟動檔案時我們知道, Reset_Handler 調用了該函數用來初始化 SMT32 系統時鐘,為了簡單起見,我們在 main 檔案裡面定義一個SystemInit 空函數,什麼也不做,為的是騙過編譯器,把這個錯誤去掉。關于配置系統時鐘我們在後面再寫。當我們不配置系統時鐘時, STM32 晶片會自動按系統内部的預設時鐘運作,程式還是能跑的。我們在 main 中添加如下函數:
// 函數為空,目的是為了騙過編譯器不報錯
void SystemInit(void)
{
}
這時再編譯就沒有錯了,完美解決。還有一個方法就是在啟動檔案中把有關SystemInit 的代碼注釋掉也可以,見代碼清單 7-3。
代碼清單 5-3 注釋掉啟動檔案中調用 SystemInit 的代碼
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
;IMPORT SystemInit
IMPORT __main
;LDR R0, =SystemInit
;BLX R0
LDR R0, =__main
BX R0
ENDP
接下來在 main 函數中添加代碼,對寄存器進行控制,寄存器的控制參數可參考表5-1(點選可跳轉)或《STM32F4xx 參考手冊》。
1. GPIO 模式:
首先我們把連接配接到 LED 燈的 PH10 引腳配置成輸出模式,即配置 GPIO 的 MODER 寄存器,見圖 5-5。 MODER 中包含 0-15 号引腳,每個引腳占用 2 個寄存器位。這兩個寄存器位設定成“01”時即為 GPIO 的輸出模式,見代碼清單 5-4。
代碼清單 5-4 配置輸出模式
/*GPIOH MODER10 清空*/
GPIOH_MODER &= ~( 0x03<< (2*10));
/*PH10 MODER10 = 01b 輸出模式*/
GPIOH_MODER |= (1<<2*10);
圖 5-5 MODER 寄存器說明(摘自《STM32F4xx 參考手冊》 )
在代碼中,我們先把 GPIOH MODER 寄存器的 MODER10 對應位清 0,然後再向它指派“01”,進而使 GPIOH10 引腳設定成輸出模式。
代碼中使用了“&=~”、“|=”這種複雜位操作方法是為了避免影響到寄存器中的其它位,因為寄存器不能按位讀寫,假如我們直接給 MODER 寄存器指派:
GPIOH_MODER = 0x00100000;
這時 MODER10 的兩個位被設定成“01”輸出模式,但其它 GPIO 引腳就有意見了,因為其它引腳的 MODER 位都已被設定成輸入模式。
如果對此處“&=”“|=”這樣的位操作方法還不了解,請閱讀前面的《規範的位操作方法》 小節。熟悉這種方法之後,會發現這樣按位操作其實比直接指派還要直覺。
2. 輸出類型:
GPIO 輸出有推挽和開漏兩種類型,我們了解到開漏類型不能直接輸出高電平,要輸出高電平還要在晶片外部接上拉電阻,不符合我們的硬體設計,是以我們直接使用推挽模式。配置 OTYPER 寄存中的 OTYPER10 寄存器位,該位設定為 0 時 PH10 引腳即為推挽模式,見代碼清單 5-5。
代碼清單 5-5 設定為推挽模式
/*GPIOH OTYPER10 清空*/
GPIOH_OTYPER &= ~(1<<1*10);
/*PH10 OTYPER10 = 0b 推挽模式*/
GPIOH_OTYPER |= (0<<1*10);
3. 輸出速度:
GPIO 引腳的輸出速度是引腳支援高低電平切換的最高頻率,本實驗可以随便設定。此處我們配置 OSPEEDR 寄存器中的寄存器位 OSPEEDR10 即可控制 PH10 的輸出速度,見代碼清單 5-6。
代碼清單 5-6 設定輸出速度為 2MHz
/*GPIOH OSPEEDR10 清空*/
GPIOH_OSPEEDR &= ~(0x03<<2*10);
/*PH10 OSPEEDR10 = 0b 速率 2MHz*/
GPIOH_OSPEEDR |= (0<<2*10);
4. 上/下拉模式:
當 GPIO 引腳用于輸入時,引腳的上/下拉模式可以控制引腳的預設狀态。但現在我們的 GPIO 引腳用于輸出,引腳受 ODR 寄存器影響, ODR 寄存器對應引腳位初始初始化後預設值為 0,引腳輸出低電平,是以這時我們配置上/下拉模式都不會影響引腳電平狀态。但是以處上拉能小幅提高電流輸出能力,我們配置它為上拉模式,即配置 PUPDR 寄存器的 PUPDR10 位,設定為二進制值“01”,見代碼清單 5-7。
代碼清單 5-7 設定為下拉模式
/*GPIOH PUPDR10 清空*/
GPIOH_PUPDR &= ~(0x03<<2*10);
/*PH10 PUPDR10 = 01b 下拉模式*/
GPIOH_PUPDR |= (1<<2*10);
5. 控制引腳輸出電平:
在輸出模式時,對 BSRR 寄存器和 ODR 寄存器寫入參數即可控制引腳的電平狀态。簡單起見,此處我們使用 BSRR 寄存器控制,對相應的 BR10 位設定為 1 時 PH10 即為低電平,點亮 LED 燈,對它的 BS10 位設定為 1 時 PH10 即為高電平,關閉 LED 燈,見代碼清單 5-8。
代碼清單5-8 控制引腳輸出電平
/*PH10 BSRR 寄存器的 BR10 置 1,使引腳輸出低電平*/
GPIOH_BSRR |= (1<<16<<10);
/*PH10 BSRR 寄存器的 BS10 置 1,使引腳輸出高電平*/
GPIOH_BSRR |= (1<<10);
6. 開啟外設時鐘:
設定完 GPIO 的引腳,控制電平輸出,以為現在總算可以點亮 LED 了吧,其實還差最後一步。
在《STM32 晶片架構》的外設章節中提到 STM32 外設很多,為了降低功耗,每個外設都對應着一個時鐘,在晶片剛上電的時候這些時鐘都是被關閉的,如果想要外設工作,必須把相應的時鐘打開。
STM32 的所有外設的時鐘由一個專門的外設來管理,叫 RCC(reset and clockcontrol),RCC 在《STM32 中文參考手冊》的第六章。
所有的 GPIO 都挂載到 AHB1 總線上,是以它們的時鐘由 AHB1 外設時鐘使能寄存器(RCC_AHB1ENR)來控制,其中 GPIOH 端口的時鐘由該寄存器的位 7 寫 1 使能,開啟GPIOH 端口時鐘。以後我們還會詳細解釋 STM32 的時鐘系統,此處我們了解到在通路GPIO 的寄存器之前,要先使能它的時鐘即可,使用代碼清單 5-9 中的代碼可以開啟GPIOH 時鐘。
代碼清單 5-9 開啟端口時鐘
/*開啟 GPIOH 時鐘,使用外設時都要先開啟它的時鐘*/
RCC_AHB1ENR |= (1<<7);
7. 水到渠成:
開啟時鐘,配置引腳模式,控制電平,經過這三步,我們總算可以控制一個 LED 了。現在我們完整組織下用 STM32 控制一個 LED 的代碼,見代碼清單 5-10。
代碼清單 5-10 main 檔案中控制 LED 燈的代碼
/*
使用寄存器的方法點亮 LED 燈
*/
#include "stm32f4xx.h"
/**
* 主函數
*/
int main(void)
{
/*開啟 GPIOH 時鐘,使用外設時都要先開啟它的時鐘*/
RCC_AHB1ENR |= (1<<7);
/* LED 端口初始化 */
/*GPIOH MODER10 清空*/
GPIOH_MODER &= ~( 0x03<< (2*10));
/*PH10 MODER10 = 01b 輸出模式*/
GPIOH_MODER |= (1<<2*10);
/*GPIOH OTYPER10 清空*/
GPIOH_OTYPER &= ~(1<<1*10);
/*PH10 OTYPER10 = 0b 推挽模式*/
GPIOH_OTYPER |= (0<<1*10);
/*GPIOH OSPEEDR10 清空*/
GPIOH_OSPEEDR &= ~(0x03<<2*10);
/*PH10 OSPEEDR10 = 0b 速率 2MHz*/
GPIOH_OSPEEDR |= (0<<2*10);
/*GPIOH PUPDR10 清空*/
GPIOH_PUPDR &= ~(0x03<<2*10);
/*PH10 PUPDR10 = 01b 上拉模式*/
GPIOH_PUPDR |= (1<<2*10);
/*PH10 BSRR 寄存器的 BR10 置 1,使引腳輸出低電平*/
GPIOH_BSRR |= (1<<16<<10);
/*PH10 BSRR 寄存器的 BS10 置 1,使引腳輸出高電平*/
//GPIOH_BSRR |= (1<<10);
while (1);
}
// 函數為空,目的是為了騙過編譯器不報錯
void SystemInit(void)
{
}
在本章節中,要求完全了解 stm32f4xx.h 檔案及 main 檔案的内容(RCC 相關的除外)。
5.3.5 下載下傳驗證:
把編譯好的程式下載下傳到開發闆并複位,可看到闆子上的 LED 燈被點亮。