05-GPIO原理
一、概述
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;
}
五、应用领域:如常见家里的电子门锁。