要點亮LED,需要完成LED的驅動, 在工程模板上新建一個led.c和led.h文件,將其存放在led文件夾內。這兩個文件需要我們自己編寫。
通常xxx.c文件用於存放編寫的驅動程序,xxx.h文件用於存放xxx.c內的stm32頭文件、管腳定義、全局變量聲明、函數聲明等內容。
因此在led.c文件內編寫如下代碼:
#include "led.h"
/*******************************************************************************
* 函 數 名 : LED_Init
* 函數功能 : LED 初始化函數
* 輸 入 : 無
* 輸 出 : 無
*******************************************************************************/
void LED_Init()
{
GPIO_InitTypeDef GPIO_InitStructure; //定義結構體變量
RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);
GPIO_InitStructure.GPIO_Pin=LED_PIN; //選擇你要設置的 IO 口
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; //設置推挽輸出模式
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //設置傳輸速率
GPIO_Init(LED_PORT,&GPIO_InitStructure); /* 初始化 GPIO */
GPIO_SetBits(LED_PORT,LED_PIN); //將 LED 端口拉高,熄滅所有 LED
}
函數中的LED_PORT_RCC、LED_PIN和LED_PORT是我們定義的宏,其存放在led.h頭文件內 。LED_PORT_RCC定義的是LED端口時鍾(如RCC_APB2Periph_GPIOC),LED_PIN定義的是LED的引腳(如 GPIO_Pin_0),LED_PORT定義的是LED的端口(如GPIOC)。這樣定義宏的好處是有效提高了程序的移植性,即使后續需要換其他端口,只需簡單修改這幾個宏就可以完成對LED的控制。
在 led.h 文件內編寫如下代碼:
#ifndef _led_H
#define _led_H
#include "stm32f10x.h"
/* LED 時鍾端口、引腳定義 */
#define LED_PORT GPIOC
#define LED_PIN
(GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_6|GPIO_Pin_7)
#define LED_PORT_RCC RCC_APB2Periph_GPIOC
void LED_Init(void);
#endif
LED_Init()函數就是對LED所接端口的初始化,是按照GPIO初始化步驟完成,這些內容在“寄存器點亮一個LED”章節中有介紹。下面我們主要看庫函數是如何實現GPIO初始化的。
在庫函數中實現 GPIO 的初始化函數是:
void GPIO_Init(GPIO_TypeDef*GPIOx,GPIO_InitTypeDef*GPIO_InitStruct);
這個函數具體有什么功能以及函數形參的意義,我們可以通過庫函數幫助文檔來查閱。GPIO_Init函數內有兩個形參,第一個形參是GPIO_TypeDef類型的指針變量,而GPIO_TypeDef是一個結構體類型,封裝了GPIO外設的所有寄存器,所以給它傳送GPIO外設基地址即可通過指針操作寄存器內容,第一個參數值可以為GPIOA、GPIOB、...GPIOG等,其實這些就是封裝好的GPIO外設基地址,在stm32f10x.h文件中可以找到。第二個形參是GPIO_InitTypeDef類型的指針變量,而GPIO_InitTypeDef也是一個結構體類型,里面封裝了GPIO外設的寄存器配置成員。我們初始化GPIO,其實就是對這個結構體配置。
如果想快速查看代碼或參數可以用鼠標點擊要查找的函數或者參數,然后右鍵鼠標選擇“Go To Definition Of ...”即可進入所要查找的函數或參數內。
查找函數內變量類型也是同樣的方法,但是如果發現此方法查找不出內容,那可能就是你所查找的東西在 KEIL5 軟件認為是不正確的。
在 LED 初始化函數中最開始調用的一個函數是:
RCC_APB2PeriphClockCmd(LED_PORT_RCC,ENABLE);
此函數功能是使能GPIOC外設時鍾, 在STM32中要操作外設必須將其外設時鍾使能,否則即使其他的內容都配置好,也是徒勞無功。因為GPIO外設是掛接在APB2總線上,所以是對APB2總線時鍾進行使能,函數內有兩個參數,一個是用來選擇外設時鍾,一個是用來選擇使能還是失能,使能:ENABLE,失能:DSIABLE。
在LED初始化函數內最后還調用了GPIO_SetBits(LED_PORT,LED_PIN)函數,此函數功能是讓GPIOC端口的第0-7個引腳輸出高電平,讓LED處於熄滅狀態,如果要對同一端口的多個引腳輸出高電平,可以使用“|”運算符,相應的在對結構體初始化配置時管腳設置那里也要使用“|”將管腳添加進去,即在led.h文件內對LED引腳的定義。(前提條件是:要操作的多個引腳必須是配置同一種工作模式)例如:
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1;//管腳設置
GPIO_SetBits(GPIOC,GPIO_Pin_0|GPIO_Pin_1);
其實從函數名我們大致就可以知道函數的功能。函數內有兩個參數,一個是端口的選擇,一個是端口管腳的選擇。如果要輸出低電平的話可以使用如下庫函數:
GPIO_ResetBits(GPIOC,GPIO_Pin_0);
這個函數功能和GPIO_SetBits是相反的,一個輸出低電平,一個輸出高電平,里面參數功能是一樣的。
GPIO輸出函數還有好幾個,例如:
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin,BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
功能:設置端口管腳輸出電平,這兩個函數很少使用。
從 GPIO 內部結構可知,STM32 的 GPIO 還可以讀取輸入或輸出引腳電平狀態。其函數如下:
-
讀取輸入引腳
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_tGPIO_Pin);
功能:讀取端口中的某個管腳輸入電平。底層是通過讀取 IDR 寄存器。
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
功能:讀取某組端口的輸入電平。底層是通過讀取 IDR 寄存器。
-
讀取輸出引腳
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_tGPIO_Pin);
功能:讀取端口中的某個管腳輸出電平。底層是通過讀取 ODR 寄存器。
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
功能:讀取某組端口的輸出電平。底層是通過讀取 ODR 寄存器。
在 led.h 文件中可以看到使用了一個定義頭文件的結構,代碼如下:
#ifndef _led_H
#define _led_H
//此處省略頭文件定義的內容
#endif
它的功能是防止頭文件被重復包含,避免引起編譯錯誤。在頭文件的開頭,使用“#ifndef”關鍵字,判斷標號“ _led_H”是否被定義,若沒有被定義,則從“#ifndef”至“ #endif”關鍵字之間的內容都有效,也就是說,這個頭文件若被其它文件“ #include”,它就會被包含到其該文件中,且頭文件中緊接着使用“#define”關鍵字定義上面判斷的標號“_led_H”。當這個頭文件被同一個文件第二次“#include”包含的時候,由於有了第一次包含中的“ #define _led_H”定義,這時再判斷“#ifndef _led_H”,判斷的結果就是假了,從“#ifndef”至“#endif”之間的內容都無效,從而防止了同一個頭文件被包含多次,編譯時就不會出現“redefine(重復定義)”的錯誤了。
一般來說,我們不會直接在C的源文件寫兩個“#include”來包含同一個頭文件,但可能因為頭文件內部的包含導致重復,這種代碼主要是避免這樣的問題。如“led.h”文件中調用了#include “stm32f10x.h”頭文件,可能我們寫主程序的時候會在 main 文件開始處調用#include“stm32f10x.h”和“led.h”,這個時候“stm32f10x.h”文件就被包含兩次了,如果在頭文件中沒有這種機制,編譯器就會報錯。