ESP32 GPIO General course

来自Waveshare Wiki
跳转至: 导航搜索

预备知识

GPIO 简介

GPIO(General-Purpose Input/Output Port,通用输入/输出接口)是一种可编程接口,广泛应用于各种电子设备中。GPIO 在设置为输入模式时,能够感知外界信号,用于接收传感器或其他外部模块的输入;而在设置为输出模式时,则能够控制外部设备,如点亮LED灯、驱动蜂鸣器或控制继电器等。

在开发过程中,GPIO通常用于连接外部的简单模块,如LED、按键、蜂鸣器等。通过设置GPIO的输入或输出状态,我们可以轻松读取外部信号或控制外部设备。特别地,当GPIO管脚用于作为内部外设的信号引脚时,这被称为引脚复用,常见于某些协议的实现,例如UART、I2C等通信接口。

通过灵活配置GPIO,可以实现多种功能,且由于其简单易用,GPIO是嵌入式系统中不可或缺的基础硬件资源之一。

GPIO 工作模式

模式名称 说明
INPUT 普通输入模式
OUTPUT 输出模式,可以读取引脚状态
PULLUP 启用内部上拉电阻
INPUT_PULLUP 输入模式,启用内部上拉电阻
PULLDOWN 启用内部下拉电阻
INPUT_PULLDOWN 输入模式,启用内部下拉电阻
OPEN_DRAIN 开漏输出模式,需要外部上拉电阻
OUTPUT_OPEN_DRAIN 开漏输出模式
ANALOG 模拟输入/输出模式

GPIO 中断

ESP32-S3 具有 99 个外部中断源,但 CPU0 和 CPU1 各只能处理 32 个中断。因此,ESP32-S3 采用 中断矩阵 来映射外部中断到 CPU,以便在外设产生中断信号后,及时通知相应的 CPU 处理。

  • CPU 中断分配
    • 每个 CPU 共有 32 个中断号 (0~31)
      • 26 个外部中断(外设引发)
      • 6 个内部中断(CPU 自身触发)
  • 外部中断类型
    • 电平触发中断:高电平触发,电平需保持至 CPU 响应
    • 边沿触发中断:上升沿触发,一旦产生,CPU 即可响应
    • NMI(不可屏蔽中断):系统发生致命错误时触发
  • 内部中断类型
    • 定时器中断:由内部定时器触发,可用于周期性任务
    • 软件中断:软件写入特殊寄存器时触发
    • 解析中断:用于性能监测与分析
  • ESP32-S3 外部中断特点
    • 所有 GPIO 均可配置为外部中断引脚
    • 与 Arduino 控制器不同,ESP32-S3 几乎所有引脚都支持外部中断
    • Arduino 设备上,只有部分引脚支持外部中断,需先查找引脚编号再配置
  • GPIO 中断触发模式
模式名称 说明
DISABLED 禁用中断,表示此引脚不响应任何中断
RISING 上升沿中断,当GPIO引脚的电平从低变高时触发中断
FALLING 下降沿中断,当GPIO引脚的电平从高变低时触发中断
CHANGE 电平变化中断,当GPIO引脚的电平发生变化时触发中断
ONLOW 低电平触发中断,通常在GPIO引脚的电平为低时触发中断
ONHIGH 高电平触发中断,通常在GPIO引脚的电平为高时触发中断
ONLOW_WE 低电平触发中断并伴随外部事件(WE),表示某种外部条件触发了低电平中断
ONHIGH_WE 高电平触发中断并伴随外部事件(WE),表示某种外部条件触发了高电平中断

GPIO 函数

/******************************************************************************
 * 函数名称:pinMode
 * 描    述:设置引脚的工作模式
 * 输    入:①.uint8_t pin:需配置的引脚编号 
 *           ②.uint8_t mode:为GPIO指定工作模式
 * 输    出:无
 * 返 回 值:无
 *****************************************************************************/

 void pinMode(uint8_t pin, uint8_t mode);
/******************************************************************************
 * 函数名称:digitalWrite
 * 描    述:设置指定引脚的输出电平。根据传入的电平值,将指定引脚设置为高电平或低电平
 * 输    入:①.uint8_t pin:需配置的引脚编号 
 *           ②.uint8_t value:设置的电平值,常见值包括: 0:低电平  1:高电平                                                
 * 输    出:无
 * 返 回 值:无
 *****************************************************************************/

 void digitalWrite(uint8_t pin, uint8_t value);
/******************************************************************************
 * 函数名称:digitalRead
 * 描    述:读取指定引脚的电平状态。根据引脚的输入状态,返回引脚的电平(高或低)
 * 输    入:uint8_t pin:要读取状态的引脚编号
 * 输    出:返回引脚的电平状态(int类型)
 * 返 回 值:int:引脚的电平状态,0:低电平  1:高电平 
 *****************************************************************************/

 int digitalRead(uint8_t pin);
/******************************************************************************
 * 函数名称:attachInterrupt
 * 描    述:为指定引脚设置中断服务程序(ISR)。当引脚的状态变化时,触发指定的回调函数
 * 输    入:①.uint8_t pin:要设置中断的引脚编号 
 *           ②.voidFuncPtr handler:当中断触发时调用的回调函数 
 *           ③.int mode:中断触发模式,取值可以为:LOW(低电平触发)、CHANGE(电平变化触发)、RISING(上升沿触发)、FALLING(下降沿触发)
 * 输    出:无
 * 返 回 值:无
 *****************************************************************************/

 void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode);;
/******************************************************************************
 * 函数名称:attachInterruptArg
 * 描    述:为指定引脚设置中断服务程序(ISR)。当引脚的状态变化时,触发指定的回调函数。
 *           允许向回调函数传递一个附加的参数,`arg` 是一个指向中断参数的指针。
 * 输    入:①.uint8_t pin:要设置中断的引脚编号
 *           ②.voidFuncPtrArg handler:当中断触发时调用的回调函数,回调函数需要接受一个指针参数
 *           ③.void* arg:指向中断参数的指针,该参数会传递给回调函数
 *           ④.int mode:中断触发模式,取值可以为:LOW(低电平触发)、CHANGE(电平变化触发)、RISING(上升沿触发)、FALLING(下降沿触发)
 * 输    出:无
 * 返 回 值:无
 * 备    注:与 'attachInterrupt' 相比,'attachInterruptArg'可以传递一个附加的参数 'arg',该参数会被传递到回调函数中。
 *           'handler' 回调函数的定义需要能够接收一个 'void*' 类型的参数。
 *            voidFuncPtrArg 是一个带有参数的回调函数类型:
 *            void myInterruptHandler(void* arg) {
 *             // 处理中断时使用 arg 参数
 *           }
 *****************************************************************************/

attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void* arg, int mode);
/******************************************************************************
 * 函数名称:detachInterrupt
 * 描    述:取消指定引脚的中断服务程序(ISR)。停止引脚的中断触发
 * 输    入:uint8_t pin:要取消中断的引脚编号
 * 输    出:无
 * 返 回 值:无
 *****************************************************************************/

 void detachInterrupt(uint8_t pin);
  • 所用函数所在文件夹路径,以用户名waveshare为例
C:\Users\wavehsare\AppData\Local\Arduino15\Packages\esp32\hardware\esp32\3.1.1\cores\esp32\esp32-hal-gpio.c

示例程序

01_GPIO

程序说明

  • 示例程序:01_GPIO
  • 本示例实现了LED以1000毫秒的频率闪烁,并将LED的高/低状态读取并打印到串行监视器

硬件连接

使用 ESP32-S3-Touch-LCD-7 开发板

  • 无需修改程序,配置参数后烧录
  • 将板子接入电脑,将Sensor AD接口与LED灯连接
ESP32-S3 LED
3V3 VCC
GND GND
AD LED
  • ESP32-S3-Touch-LCD-7 原理图部分

Esp32-4.3b-gpio-02.png
Esp32-4.3b-gpio-03.png

使用其他开发板

  • 可修改引脚为led对应引脚以观察现象

代码分析

  • 这行代码定义了常量LED,将数字引脚6指定为LED的控制引脚。使用常量使得代码更清晰,后续修改引脚编号时只需修改这一行
#define LED 6
  • setup()函数是初始化部分,只会在程序开始时运行一次。这里设置了LED引脚为输出模式(pinMode(LED, OUTPUT)),并初始化串口通信,用于后续打印信息
void setup()
  • loop()是主程序部分,它会无限循环运行。在这里,我们控制LED的开关和每次LED状态变化时通过串口监视器输出状态信息
void loop()
  • 控制LED闪烁
digitalWrite(LED, HIGH):将LED引脚设置为高电平,LED点亮
delay(1000):等待1秒(1000毫秒)
digitalWrite(LED, LOW):将LED引脚设置为低电平,LED熄灭
  • 读取LED状态并输出
使用digitalRead(LED)读取LED引脚的电平状态,并根据状态值打印不同的消息到串口监视器

运行效果

  • LED灯闪烁(演示视频使用的LED灯珠,3V3---LED+;AD---LED-;GND悬空)

  • 串口监视器打印信息

Esp32-4.3b-gpio-04.png

02_KEY

程序说明

  • 示例程序:02_KEY
  • 本示例实现了当按钮被按下时,LED 的状态会在打开和关闭之间切换

硬件连接

使用 ESP32-S3-Touch-LCD-7 开发板

  • 无需修改程序,配置参数后烧录
  • 将板子接入电脑,将Sensor AD接口与LED灯连接
ESP32-S3 LED
3V3 VCC
GND GND
AD LED
  • ESP32-S3-Touch-LCD-7 原理图部分

Esp32-4.3b-gpio-02.png
Esp32-4.3b-gpio-03.png

使用其他开发板

  • 修改 LED 引脚为对应所使用的 LED 引脚
  • 修改 BUTTON 引脚为对应所使用的按钮引脚

代码分析

  • 常量定义与变量声明,后续修改引脚编号时只需修改宏定义这两行
#define LED    6                           // 定义 LED 的引脚为 6
#define BUTTON 0                           // 定义按钮的引脚为 0

uint8_t stateLED = 0;                      // LED 初始状态为 0(关闭)
unsigned long lastButtonPress = 0;         // 记录按钮最后一次按下的时间,用于防抖
unsigned long debounceDelay = 50;          // 防抖延时,单位为毫秒,设置为 50 毫秒。按钮在被按下时,由于物理特性可能会产生抖动,通过这个延时来防止抖动导致的多次触发
  • setup()函数
void setup() {
  pinMode(LED, OUTPUT);                    // 将 LED 引脚配置为输出模式,这样可以通过 digitalWrite() 控制其高低电平,进而控制 LED 的亮灭
  pinMode(BUTTON, INPUT_PULLUP);           // 将 BUTTON 引脚配置为输入模式,并启用内部上拉电阻。按钮按下时,电平变为 LOW,松开时为 HIGH。这意味着按钮按下时,BUTTON 引脚连接到地
}
  • loop() 函数
void loop() {
  int buttonState = digitalRead(BUTTON);   // 读取按钮的当前状态,返回 LOW 如果按钮被按下,HIGH 如果按钮松开
  • 按钮按下且防抖延时:判断按钮是否按下,并且判断距离上次按下的时间是否已经超过了 debounceDelay(50 毫秒)。如果是,这意味着按钮按下操作已经“稳定”,可以触发操作
  // 检查按钮是否按下,并且是否经过防抖延时
  if (buttonState == LOW && (millis() - lastButtonPress) > debounceDelay) {
    lastButtonPress = millis();                          // millis() 返回程序启动后经过的毫秒数,用来判断防抖延时
    stateLED = stateLED ^ 1;                             // 通过异或运算符 ^ 切换 stateLED 的状态。如果 stateLED 是 0(关闭),就变成 1(打开);如果是 1(打开),就变成 0(关闭)
    digitalWrite(LED, stateLED);                         // 根据切换后的状态更新 LED 的亮灭
  • 等待按钮松开:while (digitalRead(BUTTON) == LOW) 循环会一直执行,直到按钮松开(即 BUTTON 引脚的电平变为 HIGH)。这保证了每次按下按钮后,LED 只会切换一次
    // 等待按钮松开,并加上轻微延时,避免 CPU 占用过高
    while (digitalRead(BUTTON) == LOW) {
      delay(5);                                          // 每 5 毫秒检查一次按钮状态
    }
  }
}

运行效果

  • 按键控制LED灯亮灭(演示视频使用的LED灯珠,3V3---LED+;AD---LED-;GND悬空)

03_GPIOInterrupt

程序说明

  • 示例程序:03_GPIOInterrupt
  • 本示例使用中断控制一个 LED 灯的开关。通过按钮按下触发中断,每次按钮按下时,LED 状态在开和关之间切换

硬件连接

使用 ESP32-S3-Touch-LCD-7 开发板

  • 无需修改程序,配置参数后烧录
  • 将板子接入电脑,将Sensor AD接口与LED灯连接
ESP32-S3 LED
3V3 VCC
GND GND
AD LED
  • ESP32-S3-Touch-LCD-7 原理图部分

Esp32-4.3b-gpio-02.png
Esp32-4.3b-gpio-03.png

使用其他开发板

  • 修改 LED 引脚为对应所使用的 LED 引脚
  • 修改 BUTTON 引脚为对应所使用的按钮引脚

代码分析

  • 常量定义与变量声明,后续修改引脚编号时只需修改宏定义这两行
#define LED    6                                                          // 定义 LED 的引脚为 6
#define BUTTON 0                                                          // 定义按钮的引脚为 0

volatile uint8_t stateLED = 0;                                            // LED 状态(0 为关闭,1 为打开)
volatile unsigned long lastButtonPress = 0;                               // 记录上次按钮按下的时间
const unsigned long debounceDelay = 50;                                   // 防抖延时,单位毫秒
volatile bool buttonPressed = false;                                      // 按钮按下标志位
  • 中断服务函数(ISR):isrButton 是中断服务函数,当按钮按下时触发;使用 millis() 获取当前时间,并与 lastButtonPress 进行比较,确保按钮按下事件的时间间隔大于防抖动延时(50ms);如果防抖动判断通过,就更新 lastButtonPress 为当前时间,并将 buttonPressed 设置为 true,标志按钮被按下
void IRAM_ATTR isrButton() {
  unsigned long currentMillis = millis();                                   // 获取当前时间

  // 防抖动判断
  if (currentMillis - lastButtonPress > debounceDelay) {
    lastButtonPress = currentMillis;                                        // 更新上次按钮按下时间
    buttonPressed = true;                                                   // 设置按钮按下标志
  }
}
  • setup() 函数
void setup() {
  pinMode(LED, OUTPUT);                                                     // 设置 LED 引脚为输出模式
  pinMode(BUTTON, INPUT_PULLUP);                                            // 设置按钮引脚为输入模式,并启用上拉电阻

  attachInterrupt(BUTTON, isrButton, FALLING);       // 将按钮引脚的状态变化(按下,FALLING)与中断服务函数 isrButton() 关联。当按钮按下时会触发 isrButton()
}
  • loop() 函数:当 buttonPressed 为 true 时,切换 LED 状态,并等待按钮松开
void loop() {
  if (buttonPressed) {
    buttonPressed = false;                                                  // 重置按钮按下标志
    stateLED = stateLED ^ 1;                                                // 切换 LED 状态(0 -> 1 或 1 -> 0)
    digitalWrite(LED, stateLED);                                            // 根据切换后的状态更新 LED

    // 等待按钮松开,并加上轻微延时,避免 CPU 占用过高
    unsigned long startMillis = millis();
    while (digitalRead(BUTTON) == LOW) {
      // 非阻塞检查按钮状态
      if (millis() - startMillis > debounceDelay) {
        break;                                                              // 如果超过防抖动延时,则跳出循环,继续处理其他逻辑
      }
    }
  }
}

运行效果

  • 使用中断通过按键控制 LED 灯的开关(演示视频使用的LED灯珠,3V3---LED+;AD---LED-;GND悬空)

更多学习资料

ESP32-GPIO 官方文档