要撰寫 STM32 有兩種方式,一種是使用寄存器的方式,這種比較複雜困難,另一種是使用函式庫的方式,對於初學者而言,這種會比較容易上手。上一篇:STM32筆記(14):在Keil 5 建立 STM32F1xx 函數庫程式開發範本,說明如何準備 STM32F1xx 的編譯環境,接著實做一個控制 LED 紅綠燈,來瞭解一下如何透過標準函式庫 SPL(Standard Peripheral Libraries) 撰寫一個程式。這個程式,主要控制 GPIO 來讓 LED 燈點亮或熄滅,以下就先從瞭解 GPIO 的基本概念開始。
要控制 STM32 的 GPIO ,主要還是操作相關的暫存器,所謂的標準函式庫或是 HAL 函式庫,都是對操作暫存器(Register)的過程進行封裝,目的是要減輕編寫程式的負擔。STM32F1xx 每組 GPIO Port都有如下 7 個 32-bit 暫存器,每個暫存器佔用 4 個字元(byte):
STM32 的標準函式庫要操作這些暫存器,都以簡短易懂的名稱當成函數或常數,取代複雜難背的記憶體位置,以下就如何使用標準函式庫控制 GPIO 進行說明。
STM32 的每一個外設都對應了一個時鐘,想要使用某一個外設,必須先開啟它的時鐘。STM32 有一個專門管理時鐘和復位的外設 RCC,有關 RCC 時鐘如何控制暫存器,可參考相關的手冊,在後面的實做中,再找一篇文章說明如何控制 RCC 暫存器。
所有的 GPIO 都掛載在 APB2 總線上,如要啟用 APB2 外設時鐘,只要將 RCC_APB2ENR 暫存器 Enable 即可。撰寫程式時,可以使用標準函式庫裡提供的函式 RCC_APB2PeriphClockCmd(),快速 Enable GPIO 時鐘。如以下寫法:
藉由調整不同的開關或電子零件,可產生 4 種輸入及 4 種輸出模式。說明如下:
這個函式主要用來初始化 GPIO 引腳,其中的參數有兩個:
執行 GPIO_Init() 這個函式前,需先定義 GPIO_InitStruct 這個結構,使用以下指令來定義:
✈ 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 常用的函式,不只上述幾個,之後的實做用到時,再來說明其內容及用法。
1、配置時鐘
2、配置引腳輸出
3、輸出低電平
main.c 程式如下:
delay.c 程式如下:並和 delay.h 放置在 System 目錄內。
delay.h 程式如下:
完整的程式請參考 Github:1. LED Traffic light
[GPIO概念]
GPIO(general purpose input output)是通用輸入輸出埠的簡稱,可以通過軟體來控制其輸入和輸出。GPIO 的引腳與外部硬體設備連接,可實現與控制外部硬體、外部通訊或接收來自外部設備的數據等功能。要控制 STM32 的 GPIO ,主要還是操作相關的暫存器,所謂的標準函式庫或是 HAL 函式庫,都是對操作暫存器(Register)的過程進行封裝,目的是要減輕編寫程式的負擔。STM32F1xx 每組 GPIO Port都有如下 7 個 32-bit 暫存器,每個暫存器佔用 4 個字元(byte):
GPIO暫存器 | 位元 | 功能描述 | 偏移地址 |
---|---|---|---|
GPIOx_CRL | 32 | 端口配置低寄存器 | 0x0000 |
GPIOx_CRH | 32 | 端口配置高寄存器 | 0x0004 |
GPIOx_IDR | 32 | 輸入數據寄存器 | 0x0008 |
GPIOx_ODR | 32 | 輸出數據寄存器 | 0x000C |
GPIOx_BSRR | 32 | 端口位設置/清除寄存器 | 0x0010 |
GPIOx_BRR | 16 | 端口位清除寄存器 | 0x0014 |
GPIOx_LCKR | 32 | 端口配置鎖定暫存器 | 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_FLOATING | GPIO 引腳設為輸入浮空時,沒有接上拉電阻或下拉電阻,經由觸發器輸入, 由於其輸入阻抗較大,一般這種模式用於標準通信協議如I2C,USART接收端。 |
上拉輸入 Input pull-up | GPIO_Mode_IPU | GPIO 引腳設為上拉輸入模式時,透過一個電阻與電源相連, 當 GPIO 引腳無輸入時,讀取的數據為 1,即高電位。 |
下拉輸入 Input-pull-down | GPIO_Mode_IPD | GPIO 引腳設為下拉輸入模式時,透過一個電阻與接地相連, 當 GPIO 引腳無輸入時,讀取的數據為 0,即低電位。 |
模擬輸入 | Analog In / GPIO_Mode_AIN | GPIO 引腳設為模擬輸入時,關閉TTL Schmitt trigger, 不接上拉或下拉電阻,經由另一線路把電壓傳送到片上外設模組。 例如傳送至 ADC 模組時,必須設為模擬輸入模式。 |
開漏輸出 Output open-drain | GPIO_Mode_Out_OD | GPIO 設為開漏輸出時,若要輸出低電位,則 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_PP | GPIO 設為推挽輸出時,若要輸出高電位時,上方 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
張貼留言