STM32筆記(15):使用 GPIO 控制紅綠燈 LED

要撰寫 STM32 有兩種方式,一種是使用寄存器的方式,這種比較複雜困難,另一種是使用函式庫的方式,對於初學者而言,這種會比較容易上手。上一篇:STM32筆記(14):在Keil 5 建立 STM32F1xx 函數庫程式開發範本,說明如何準備 STM32F1xx 的編譯環境,接著實做一個控制 LED 紅綠燈,來瞭解一下如何透過標準函式庫 SPL(Standard Peripheral Libraries) 撰寫一個程式。這個程式,主要控制 GPIO 來讓 LED 燈點亮或熄滅,以下就先從瞭解 GPIO 的基本概念開始。

[GPIO概念]

GPIO(general purpose input output)是通用輸入輸出埠的簡稱,可以通過軟體來控制其輸入和輸出。GPIO 的引腳與外部硬體設備連接,可實現與控制外部硬體、外部通訊或接收來自外部設備的數據等功能。

要控制 STM32 的 GPIO ,主要還是操作相關的暫存器,所謂的標準函式庫或是 HAL 函式庫,都是對操作暫存器(Register)的過程進行封裝,目的是要減輕編寫程式的負擔。STM32F1xx 每組 GPIO Port都有如下 7 個 32-bit 暫存器,每個暫存器佔用 4 個字元(byte):
GPIO暫存器位元功能描述偏移地址
GPIOx_CRL32端口配置低寄存器0x0000
GPIOx_CRH32端口配置高寄存器0x0004
GPIOx_IDR32輸入數據寄存器0x0008
GPIOx_ODR32輸出數據寄存器0x000C
GPIOx_BSRR32端口位設置/清除寄存器0x0010
GPIOx_BRR16端口位清除寄存器0x0014
GPIOx_LCKR32端口配置鎖定暫存器0x0018

STM32 的標準函式庫要操作這些暫存器,都以簡短易懂的名稱當成函數或常數,取代複雜難背的記憶體位置,以下就如何使用標準函式庫控制 GPIO 進行說明。

STM32 的每一個外設都對應了一個時鐘,想要使用某一個外設,必須先開啟它的時鐘。STM32 有一個專門管理時鐘和復位的外設 RCC,有關 RCC 時鐘如何控制暫存器,可參考相關的手冊,在後面的實做中,再找一篇文章說明如何控制 RCC 暫存器。

所有的 GPIO 都掛載在 APB2 總線上,如要啟用 APB2 外設時鐘,只要將 RCC_APB2ENR 暫存器 Enable 即可。撰寫程式時,可以使用標準函式庫裡提供的函式 RCC_APB2PeriphClockCmd(),快速 Enable GPIO 時鐘。如以下寫法:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
啟用時鐘後,接著使用標準函式庫中的 GPIO_Init() 函式來進行 GPIO 初始化操作。在使用函式控制前,先瞭解一下 GPIO 的電路。每個 GPIO 內部都有一個如下圖的電路結構,藉由控制圖中的 on/off,可輸出上拉、下拉電壓,P-MOS 管和 N-MOS 管組成的單元電路,讓 GPIO 具有「推輓輸出」和「開漏輸出」的模式。
藉由調整不同的開關或電子零件,可產生 4 種輸入及 4 種輸出模式。說明如下:
GPIO工作模式 / 常數名 定義描述
浮空輸入
Input floating
GPIO_Mode_IN_FLOATINGGPIO 引腳設為輸入浮空時,沒有接上拉電阻或下拉電阻,經由觸發器輸入,
由於其輸入阻抗較大,一般這種模式用於標準通信協議如I2C,USART接收端。
上拉輸入
Input pull-up
GPIO_Mode_IPUGPIO 引腳設為上拉輸入模式時,透過一個電阻與電源相連,
當 GPIO 引腳無輸入時,讀取的數據為 1,即高電位。
下拉輸入
Input-pull-down
GPIO_Mode_IPDGPIO 引腳設為下拉輸入模式時,透過一個電阻與接地相連,
當 GPIO 引腳無輸入時,讀取的數據為 0,即低電位。
模擬輸入Analog In /
GPIO_Mode_AIN
GPIO 引腳設為模擬輸入時,關閉TTL Schmitt trigger,
不接上拉或下拉電阻,經由另一線路把電壓傳送到片上外設模組。
例如傳送至 ADC 模組時,必須設為模擬輸入模式。
開漏輸出
Output open-drain
GPIO_Mode_Out_ODGPIO 設為開漏輸出時,若要輸出低電位,則 N-MOS 管導通,
輸出接地,此時為低電位;若要輸出高電位時,N-MOS 管關閉,
這時既不輸出高電位及低電平,為高阻態。這時需要外接上拉電阻,
讓上拉電阻提供高電位的驅動能力。
復用開漏輸出
Alternate function
open-drain
GPIO_Mode_AF_OD開漏復用輸出和開漏輸出的區別在於:0/1信號的來源不同。
常用在片內外設功能(TX1、MOSI、MISO.SCK.SS)。
推挽輸出
Output push-pull
GPIO_Mode_Out_PPGPIO 設為推挽輸出時,若要輸出高電位時,上方 P-MOS 管導通,
下方 N-MOS 管關閉。而若要輸出低電位時,下方的N-MOS管導通,
上方的P-MOS管關閉。當引腳高低電位切換時,兩個 MOS 管輪流導通,
一個負責灌電流,另一個負責拉電流,使得負載能力和開關速度都有很大的提高。
復用推挽輸出
Alternate function
push-pull
GPIO_Mode_AF_PP推挽復用輸出和推挽輸出的區別在於:0/1信號的來源不同。
常用在片內外設功能(I2C的SCL、SDA)。


[常用的GPIO函式]

GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_InitStruct)
這個函式主要用來初始化 GPIO 引腳,其中的參數有兩個:
引數名稱說明
GPIOx範圍在 GPIO[A..G],根據不同的外設來配置 GPIOA~GPIOG,例如:GPIOB。
GPIO_InitStruct是一個 C 語言的指標,設定為 GPIO_InitTypeDef 的結構(Structure),所以傳參數時要加上取地址符號“&”,
這個結構體包含指定 GPIO 外圍裝置的設定資訊,包括模式、速度等。用法如以下說明:

執行 GPIO_Init() 這個函式前,需先定義 GPIO_InitStruct 這個結構,使用以下指令來定義:
GPIO_InitTypeDef GPIO_InitStruct;    // 定義一個GPIO_InitTypeDef類型的結構體
這個結構,有傳遞三個變數給 GPIO_InitStruct,說明如下:
引數名稱說明
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_x設定引腳,其 x 可以是0 ~ 15 以及 ALL,例如:GPIO_Pin_3。
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_xxx xxx 表示輸入或輸出模式,可以是以下值     * GPIO_Mode_AIN 模擬輸入
    * GPIO_Mode_IN_FLOATING 浮空輸入
    * GPIO_Mode_IPD 下拉輸入
    * GPIO_Mode_IPU上拉輸入
    * GPIO_Mode_Out_OD
    * GPIO_Mode_Out_PP
    * GPIO_Mode_AF_OD
    * GPIO_Mode_AF_PP
各種輸出入模式內容,請參考上一小節說明。
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_xxx xxx 表示三種速度,10MHz、2MHz 及 50MHz,例如:GPIO_Speed_50MHz。

GPIO_DeInit(GPIO_TypeDef *GPIOx)
取消 GPIO 初始化,即關閉 GPIO。
- 引數 GPIOx 是對應的 GPIO,範圍是GPIOA~GPIOG;
通常不要使用 GPIO_DeInit() 這個函式,因為它會關閉整個 GPIOx,如果 GPIOx 的 Pin_1~Pin_15 還有正在使用中的,也會一併被關閉。

GPIO_SetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
設定 GPIO_Pin 引腳為高電位。如連接的 LED 在對應引腳是低電位時處於點亮狀態,使用此函數,讓 LED 在初始狀態下熄滅。
- 引數 GPIOx:對應的 GPIO,範圍是GPIOA~GPIOG;
- 引數 GPIO_Pin:引腳編號,可以是GPIO_Pin_0 ~ GPIO_Pin_15 及 GPIO_Pin_All。

GPIO_ResetBits(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
設定 GPIO_Pin 引腳為低電位。如連接的 LED,在對應引腳是低電位時處於點亮狀態,使用此函數,讓 LED 在初始狀態下點亮。
- 引數 GPIOx:對應的 GPIO,範圍是GPIOA~GPIOG;
- 引數 GPIO_Pin:引腳編號,可以是GPIO_Pin_0 ~ GPIO_Pin_15 及 GPIO_Pin_All。

GPIO_Write(GPIO_TypeDef *GPIOx, uint16_t PortVal)
對多個 GPIO 引腳設定為 0 或 1。
- 引數 GPIOx:GPIOx 是對應的 GPIO,範圍是GPIOA~GPIOG。
- 引數 PortVal:為 16 進位制數,對應 IO Port 0 ~ 15 的狀態值,要注意如果同時有多個 IO Port 正在使用中,這個指令會用 PortVal 的 16 Bits 值,同時改變這 16 個 Port 的值。通常會使用以下 GPIO_WriteBit() 這個函式來改變各 Port 的值。

GPIO_WriteBit(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, BitAction BitVal)
對 GPIOx 的某個 Port 設定為高電位和低電位。
- 引數 GPIOx:GPIOx 是對應的 GPIO,範圍是GPIOA~GPIOG。
- 引數 GPIO_Pin:引腳編號,可以是GPIO_Pin_0 ~ GPIO_Pin_15 及 GPIO_Pin_All。
- 引數 BitVal: 0 或 !0,0 表示設定該 IO Port為低電位,!0 為高電位。

有關 GPIO 常用的函式,不只上述幾個,之後的實做用到時,再來說明其內容及用法。


[材料]

  • STM32F103C8T6 主板 x 1
  • 麵包板 x1
  • LED 紅、黃、綠 各 1 個
  • STLINK V2 模擬下載器 x 1
  • 連接線 x 1 條

[接線與電路圖]

LED 正極(長腳)都接到 3.3v,另一腳,分別接在 STM32F103 的 PA0、PA1 及 PA2。如下圖:

[程式]

要使用 GPIO 函數控制 IO Port 的高低電位,有三個重要的設定步驟:
1、配置時鐘
2、配置引腳輸出
3、輸出低電平

main.c 程式如下:
/**********************************************************
 * @file main.c
 * @date 2022/3/22  
 * @brief traffic light 
 **********************************************************/

#include "stm32f10x.h"
#include "stm32f10x_rcc.h"
#include "stm32f10x_gpio.h"
#include "delay.h"

GPIO_InitTypeDef GPIO_InitStruct;    // 定義一個GPIO_InitTypeDef類型的結構體

int main (void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //開啟GPIOA的外設時鐘
	 
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;  //選擇要控制的GPIOA引腳
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;   //設置引腳速率為2MHz
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;   //設置引腳為通用推輓輸出模式
	GPIO_Init(GPIOA, &GPIO_InitStruct);  			//調用庫函數,初始化GPIOA5引腳

	// 將引腳設為高電位,此時三個燈熄滅
	GPIO_SetBits(GPIOA, GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2);	
	
	while (1)
	{
		GPIO_ResetBits(GPIOA, GPIO_Pin_0);		// 點亮紅燈	
		delay_s(3);								// 停 3 秒

		GPIO_SetBits(GPIOA, GPIO_Pin_0);		// 熄滅紅燈
		GPIO_ResetBits(GPIOA, GPIO_Pin_1);		// 點亮黃燈	
		delay_s(1);

		GPIO_SetBits(GPIOA, GPIO_Pin_1);		// 熄滅黃燈
		GPIO_ResetBits(GPIOA, GPIO_Pin_2);		// 點亮綠燈	
		delay_s(3);
		
		GPIO_SetBits(GPIOA, GPIO_Pin_2);		// 熄滅綠燈
	}
}


delay.c 程式如下:並和 delay.h 放置在 System 目錄內。
#include "delay.h"

#define AHB_INPUT  72   	//RCC中設定的AHB時鐘頻率 MHz

void delay_us(u32 uS)		//uS微秒級延時程式(參考值即是延時數,72MHz時最大值233015)
{ 	
	u32 temp;
	SysTick->LOAD=AHB_INPUT*uS;   //重裝計數初值(當主頻是72MHz,72次為1微秒)
	SysTick->VAL=0x00;        	  //清空定時器的計數器
	SysTick->CTRL=0x00000005;	  //內部時鐘FCLK,打開定時器
	do
	{
		temp=SysTick->CTRL;
	}
	while(temp&0x01&&!(temp&(1<<16)));	//等待時間到達  
	SysTick->CTRL=0x00000004;		    //關閉定時器
	SysTick->VAL=0x00;        		    //清空定時器的計數器
}

void delay_ms(u16 ms)		//mS毫秒級延時程式(參考值即是延時數,最大值65535)
{ 	 		  	  
	while( ms-- != 0)
	{
		delay_us(1000);		//呼叫1000微秒的延時
	}
}
 
void delay_s(u16 s)			//S秒級延時程式(參考值即是延時數,最大值65535)
{ 	 		  	  
	while( s-- != 0)
	{
		delay_ms(1000);		//呼叫1000毫秒的延時
	}
}


delay.h 程式如下:
#ifndef __DELAY_H
#define __DELAY_H
 
#include "stm32f10x.h"
 
void delay_s(u16 s);
void delay_ms(u16 ms);
void delay_us(u32 us);

#endif


完整的程式請參考 Github:1. LED Traffic light

[結果]


[參考資料]


Post a Comment

較新的 較舊