一、概述
1、GPIO,即通用I/O(輸入/輸出)端口,是STM32可控制的引腳。STM32芯片的GPIO引腳與外部設備連接起來,可實現與外部通訊、控制外部硬件或者采集外部硬件數據的功能。
?2、GPIO的復用:引腳復用是指將單個引腳配置為多個功能的能力。在 STM32 中,每個引腳都可以配置為多個不同的功能,如GPIO(輸入/輸出數據)、定時器、UART、SPI等。這樣一來,通過配置引腳復用功能,可以實現多種硬件功能的連接和實現,提高了芯片的靈活性和可擴展性。引腳默認為IO口。
二、GPIO的工作模式
?1、八大模式和四種輸出速度
4種輸入模式
(1)浮空輸入(即不連接內部上下拉電阻)
(2)上拉輸入(連接上拉電阻)
(3)下拉輸入(連接下拉電阻)
(4)模擬輸入(用于檢測模擬信號的輸入)
4種輸出模式?
(5)開漏輸出(帶上拉或者下拉)
(6)復用開漏輸出(帶上拉或者下拉)
(7)推挽輸出(帶上拉或者下拉):程序員可以輸出高電平與低電平
(8)復用推挽輸出(帶上拉或者下拉):程序員可以低電平,想要輸出電平,芯片外部需要有上拉電阻。
4種最大輸出速度
(1)2MHZ? (低速)
(2)25MHZ??(中速)
(3)50MHZ??(快速)
(4)100MHZ??(高速)
2、上下拉電阻
上拉電阻:
- 將一個不確定的信號,通過一個電阻與電源VCC相連,固定在高電平;
- 上拉是對器件注入電流;灌電流;
- 當一個接有上拉電阻的IO端口設置為輸入狀態時,它的常態為高電平;
下拉電阻:
- 將一個不確定的信號,通過一個電阻與地GND相連,固定在低電平;
- 下拉是從器件輸出電流;拉電流;
- 當一個接有下拉電阻的IO端口設置為輸入狀態時,它的常態為低電平;
?三、寄存器編程
1、基本概念
寄存器是微處理器或微控制器內部的小型存儲單元,用于臨時存儲數據、指令或控制信號。寄存器通常由硬件實現,并且可以直接通過CPU訪問。寄存器的設計目的是為了提高處理器的性能,因為它們比內存訪問更快。在微控制器如STM32F407中,寄存器用于配置和控制各種外設的功能。
2、寄存器分類
- 通用寄存器:用于存儲數據或作為臨時存儲空間。
- 狀態寄存器:存儲有關處理器狀態的信息,如標志位。
- 控制寄存器:用于控制處理器的行為。
- 外設寄存器:用于配置和控制微控制器中的外設。
?3、常見寄存器及用途
GPIO寄存器:- GPIOx_MODER:配置GPIO引腳的工作模式(輸入、輸出、復用功能等)。
- GPIOx_OTYPER:配置GPIO引腳的輸出類型(推挽或開漏)。
- GPIOx_OSPEEDR:配置GPIO引腳的輸出速度。
- GPIOx_PUPDR:配置GPIO引腳的上拉/下拉電阻。
- GPIOx_IDR:讀取GPIO引腳的輸入值。
- GPIOx_ODR:設置GPIO引腳的輸出值。
- GPIOx_BSRR:設置或清除GPIO引腳的輸出值。
- GPIOx_LCKR:鎖定GPIO引腳的配置。時鐘控制寄存器:
- RCC_CR:控制時鐘源的選擇和啟動。
- RCC_PLLCFGR:配置PLL(Phase-Locked Loop)時鐘。
- RCC_CFGR:配置時鐘樹,包括AHB/APB總線時鐘。
- RCC_CIR:時鐘中斷寄存器。中斷寄存器:
- NVIC_ISER:中斷使能寄存器。
- NVIC_ICER:中斷清除使能寄存器。
- NVIC_ISPR:中斷掛起寄存器。
- NVIC_ICPR:中斷清除掛起寄存器。
- NVIC_IPR:中斷優先級寄存器。
4、寄存器地址 = 外設的基地址+偏移地址
下面的邊界地址即為外設的基地址
例如
RCC_AHB1ENR寄存器地址 = RCC基地址+偏移地址
RCC_AHB1ENR寄存器地址 = 0x40023800+0x30
寄存器分析;
5、寄存器點燈例子:
(1)理解LED電路原理圖
LED0連接在PF9
PF9輸出低電平,燈亮;輸出高電平,燈滅
led.c文件代碼:
#include "led.h"/************************************
引腳說明:
LED0連接在PF9
PF9輸出低電平,燈亮;輸出高電平,燈滅************************************/void Led_Init(void)
{ //將第5位置1,你使能GPIOF組時鐘RCC_AHB1ENR |= (0x01<<5);//配置PF9輸出模式GPIOF_MODER &= ~(0x01<<19); //19位清0GPIOF_MODER |= (0x01<<18); //18位置1//配置PF9推挽輸出GPIOF_OTYPER &= ~(0x01<<9); //9位清0//配置PF9中速 25MHZGPIOF_OSPEEDR &= ~(0x01<<19); //19位清0GPIOF_OSPEEDR |= (0x01<<18); //18位置1 //配置PF9中速無上下拉GPIOF_PUPDR &= ~(0x01<<19); //19位清0GPIOF_PUPDR &= ~(0x01<<18); //18位清0
}
main.c文件:
#include <stdio.h>
#include "led.h"//粗延時(就是延時不一定準確的意思)void delay(int n)
{int i, j;for(i=0; i<n; i++)for(j=0; j<10000; j++);
}int main(void)
{//初始化后GPIOF_ODR默認為低電平,所以燈亮Led_Init();while(1){//燈亮GPIOF_ODR &= ~(0x01<<9); //9位清0delay(1000);//燈滅GPIOF_ODR |= (0x01<<9); //9位置1delay(1000); }return 0;
}
led.h文件:
#ifndef __LED_H
#define __LED_H//注意:0x40023800這個地址是根據不同的寄存器來決定的,編寫多個寄存器的時候要非常注意有沒有改
#define RCC_AHB1ENR *((volatile unsigned int *)(0x40023800 + 0x30)) //值強制轉換為地址 通過解引用訪問地址空間
#define GPIOF_MODER *((volatile unsigned int *)(0x40021400 + 0x00))
#define GPIOF_OTYPER *((volatile unsigned int *)(0x40021400 + 0x04))
#define GPIOF_OSPEEDR *((volatile unsigned int *)(0x40021400 + 0x08))
#define GPIOF_PUPDR *((volatile unsigned int *)(0x40021400 + 0x0C))
#define GPIOF_ODR *((volatile unsigned int *)(0x40021400 + 0x14))
void Led_Init(void);#endif
四、使用庫函數編程來點燈的例子
1、背景
🔸優點:
- 性能優化: 直接通過寄存器進行操作可以減少中間層的調用,提高程序執行效率。
- 靈活性高: 寄存器編程允許開發者直接控制硬件,提供最大的靈活性來定制特定的功能。
- 代碼緊湊: 通常情況下,寄存器級別的編程會生成更緊湊的代碼,有助于節省內存空間。
🔸缺點:
- 易出錯: 寄存器編程需要對硬件有深入的理解,容易因為配置錯誤而導致問題。
- 移植性差: 不同的微控制器可能有不同的寄存器布局和功能,這使得代碼難以在不同的硬件之間移植。
- 調試困難: 錯誤往往不易被發現,調試過程可能會比較復雜且耗時。
2、庫函數的使用步驟
1、需要在keil5中添加RCC和GPIO庫
2、使能端口F(PF9屬于GPIOF組)的硬件時鐘(芯片所有外設都是關閉時鐘,原因為了降低功耗)
//使能GPIOF組時鐘
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
3、(3)初始化引腳功能(推挽輸出 速度 上/下拉)
GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; //引腳9GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;//輸出GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;//推挽模式GPIO_InitStruct.GPIO_Speed = GPIO_Speed_25MHz;//25MHZ速度GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; //無上下拉GPIO_Init(GPIOF, &GPIO_InitStruct);
4、設置引腳電平
GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) //設置引腳為高電平
GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)//設置引腳為低電平
3、庫函數開發例子2(按鍵)
(1)理解KEY電路原理圖
KEY0連接PA0
KEY0按下,PA0為低電平
KEY0未按下,PA0為高電平
2、使能GPIO時鐘
//使能GPIOA組時鐘RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
3、配置PA0引腳
引腳輸入時,不需要配置GPIO 端口輸出類型及GPIO 端口輸出速度
//結構體變量GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; //引腳0GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; //輸入模式GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //根據外面電路來設置即可
4、讀引腳電平
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
具體代碼:四個按鍵控制四個燈
key.h文件:
#ifndef _KEY_H
#define _KEY_H#include "stm32f4xx.h"void key_init(void);
void led_init(void);enum
{key0 = 1,key1,key2,key3,
};u8 Key_Scan(u8 mode);#endif
key.c文件:
#include "key.h"//key2,key3,key4的引腳為PE組的2,3,4void key_init(void)
{// 使能GPIOA組時鐘RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN; // 入GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; // 上拉GPIO_Init(GPIOA, &GPIO_InitStruct);// 使能GPIOE組時鐘RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);GPIO_InitTypeDef GPIO_InitStruct1;GPIO_InitStruct1.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;GPIO_InitStruct1.GPIO_Mode = GPIO_Mode_IN; // 入GPIO_InitStruct1.GPIO_PuPd = GPIO_PuPd_UP; // 上拉GPIO_Init(GPIOE, &GPIO_InitStruct1);
}void led_init(void)
{// 使能GPIOF組時鐘RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; // 引腳9GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; // 輸出GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // 推挽模式GPIO_InitStruct.GPIO_Speed = GPIO_Speed_25MHz; // 25MHZ速度GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // 無上下拉GPIO_Init(GPIOF, &GPIO_InitStruct);// 使能GPIOE組時鐘RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);GPIO_InitTypeDef GPIO_InitStruct1;GPIO_InitStruct1.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14; // 引腳13GPIO_InitStruct1.GPIO_Mode = GPIO_Mode_OUT; // 輸出GPIO_InitStruct1.GPIO_OType = GPIO_OType_PP; // 推挽模式GPIO_InitStruct1.GPIO_Speed = GPIO_Speed_25MHz; // 25MHZ速度GPIO_InitStruct1.GPIO_PuPd = GPIO_PuPd_NOPULL; // 無上下拉GPIO_Init(GPIOE, &GPIO_InitStruct1);// 關閉所有LED(假設低電平點亮LED)GPIO_SetBits(GPIOF, GPIO_Pin_9 | GPIO_Pin_10);GPIO_SetBits(GPIOE, GPIO_Pin_13 | GPIO_Pin_14);
}void delays(int n)
{int i, j;for (i = 0; i < n; i++)for (j = 0; j < 10000; j++);
}// u8 == unsigned char
/************************************
函數功能:按鍵掃描
返回值:
成功:按下返回按鍵標志位,如:KEY0標志位1,KEY1標志位2,KEY2標志位3,KEY3標志位4
失敗:0u8 mode:是否支持連按
0:不支持連按
1:支持連按*************************************/
u8 Key_Scan(u8 mode)
{// key_up保存上一次的值static u8 key_up = 1; // 按鍵標志,表示未按下if (mode == 1) // 支持連按key_up = 1;if (key_up && (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0) | (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_2) == 0) | (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3) == 0) | (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4) == 0)){delays(15);key_up = 0; // 表示按下(防止重復觸發)if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 0){return key0; // KEY0標志值為1}if (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_2) == 0){return key1; // KEY0標志值為1}if (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3) == 0){return key2; // KEY0標志值為1}if (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4) == 0){return key3; // KEY0標志值為1}}else if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0) == 1 | GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_2) == 1 | GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3) == 1 | GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4) == 1) // 未按下--也可以理解為松開{key_up = 1; // 按鍵松開key_up == 1}// 未操作按鍵(按鍵松開)return 0;
}
main.c文件:
#include <stdio.h>
#include "key.h"//粗延時(就是延時不一定準確的意思)void delay(int n)
{int i, j;for(i=0; i<n; i++)for(j=0; j<10000; j++);
}int main()
{//初始化后GPIOF_ODR默認為低電平,所以燈亮key_init();led_init();u8 value = 0;u8 key = 0;while(1){ //支持連按key = Key_Scan(1);if(key == key2){GPIO_ToggleBits(GPIOE, GPIO_Pin_13);}//不支持連按value = Key_Scan(0);if(value == key0){GPIO_ToggleBits(GPIOF, GPIO_Pin_9);}if(value == key1){GPIO_ToggleBits(GPIOF, GPIO_Pin_10);}
// if(value == 3)
// {
// GPIO_ToggleBits(GPIOE, GPIO_Pin_13);
// }if(value == key3){GPIO_ToggleBits(GPIOE, GPIO_Pin_14);}}return 0;
}
五、應用領域:如常見家里的電子門鎖。