Skip to content

Wiki

Tutorial

About 47 min read

Existing English translations were reused. 5 current source blocks remain in Simplified Chinese, without triggering paid translation.

下面教程我以ESP32S3和Fedora Linux为例.

The characteristic of this tutorial is that although most of it is based on the ALIENTEK tutorial, the ALIENTEK tutorial uses a very large number of macro definitions, which is not suitable for beginners to read. This tutorial will minimize the use of macro definitions and try to teach you the most original code as much as possible.

1.Basic project creation

1.1.Preparation work

(Some older versions require moving this folder) Copy this folderesp-idf/tools/templates/sample_project

alt text

Copy hereesp-idf/examples/get-started/sample_project alt text

1.2.Create New Project

ClickNew Projects alt text

alt text

Either of the following two will work; some older versions do not have the bottommost option. (For a normal latest version, just select the bottommost one.) alt text

alt text

Created successfully

alt text

alt text

  1. Configure CPU frequency SearchCPU, find the CPU frequency setting, set the CPU frequency to 240MHz (maximum) alt text

  2. Configure Flash and RAM

SearchFlash, set itFlash SPI modeas QIO; this mode has the fastest speed.

Check the ESP32S3 model I bought on Taobao,ESP32-S3 N16R8and learned that my Flash is 16M and RAM is 8M

alt text

SearchPSRAMand check

alt text

Looking at the official PSRAM introduction, we should chooseOctal SPI

alt text

alt text

alt text

  1. Configure FreeRTOS

SetconfigTICK_RATE_HZ configured as1000, so the period is 1 ms, and thevTaskDelay() unit of the function also becomes ms. alt text

  1. Configure partition table Searchpartition, find partition table, and configure as 自定义分区表CSV alt text

  2. Save alt text

Old backups can be deleted alt text

  1. Edit partition table Press ctrl shift P together, enterPartition Table, findOpen Partition Table Eidtors UI

alt text

Copy it down below without making a single mistake alt text

It can be seen that all have been generated. alt text

  1. Compile test

alt text

As shown in the figure, the compilation is successful.

alt text

2.Introduction to Partition Tables

The role of the partition table is to divide Flash into multiple storage areas and record the specific function and purpose of each area.

alt text

alt text

alt text

alt text

3.Custom engineering architecture and adding components

3.1.Introduce the engineering architecture

The following is Espressif's official project structure:

alt text

This is obviously quite a mishmash.

The following is ALIENTEK's project structure, which is more modular, offers more flexible scalability, and has clearer layering.

alt text

3.2.Create project architecture

Copy the basic project, paste it into your code folder, and then rename it to N01_LED.

alt text

Open the newly created folder with VSCode

cd ~/UserFolder/MySource/ESP32_Projects/N01_LED
code .

Create the following file

alt text

alt text

Open the top-level CMakeLists

alt text

# Set the extra component directories
set(EXTRA_COMPONENT_DIRS components/Middlewares)

# Add compile options,warning has color.
add_compile_options(-fdiagnostics-color=always)

Modify the CMakeLists in the BSP

alt text

set(src_dirs
			LED)

set(include_dirs
			LED)

# GPIO的一些组件在driver里
set(requires
			driver)

idf_component_register(SRC_DIRS ${src_dirs}
					  INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

Clean and recompile to see alt text

Compilation successful

alt text

3.3.Add component

ctrl shift P searchesp componentfoundesp component registry alt text

Select model alt text

For example, to install OpenAI, search for OpenAI

alt text

Tap to install

alt text

Installation successful

alt text

The figure below shows that the installation was successful alt text

Clean and recompile to see alt text

Compilation successful

alt text

Note that inmain.cyou can directly call the added component, but in files under thecomponentsfolder, you cannot use it directly; you need to modifyCMakeLists.txt

4.ESP32 Download and Debug

4.1.Download

First write a program, and write a hello world inside main.c.

#include <stdio.h>

void app_main(void)
{
    printf("Hello World!\n");
}

You use a USB-A to USB-C data cable, with one end on the computer and the other end plugged into the development board's USB interface, not the UART interface.

As shown in the figure

  1. Device Detection

    1. Windows If you are on Windows, right-click此电脑, click管理

    alt text

    You will see it has been detected properly as shown below

    alt text

    1. Linux If you are on Linux, open a terminal
    ls /dev | grep USB
    # 或者
     ls /dev | grep ACM

    alt text

    Check whether any device is detected. As shown above, mine is/dev/ttyACM0.

  2. Download

    1. Windows If you are on Windows, first select the mode asJTAG, set the port to the detected portCOM4, and the chip model toesp32s3, then click清理,构建,烧录.

    alt text

    Then select yes

    alt text

    It is flashed as shown in the figure

    alt text

    1. Linux If you are on Linux, first select the mode asJTAG, set the port to the detected port/dev/ttyACM0, and the chip model toesp32s3, then click清理,构建,烧录.

    alt text

    alt text

    Select yes here alt text

    The figure below indicates successful flashing (if you cannot download normally, please look below常见问题) alt text

4.2.Debugging

First, write a program

//包含FreeRTOS头文件(为了用vTaskDelay)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

int32_t i = 0;

void app_main(void)
{


    //死循环,等同于while(1),但效率比while(1)更高
    for(;;)
    {
        i++;
        vTaskDelay(500 / portTICK_PERIOD_MS);  //延时500ms
    }
}

Directly click Debug, alt text

This way, it enters debug mode normally. alt text

Below, The first one is start running. The second one is step over, running function by function. The third one is step into, which will enter the function and execute inside. The fourth one is step out, which will exit this function. The fifth one is restart the program, but there is a bug in esp32; it seems it will not restart, not sure if it will be fixed later. The sixth one is disconnect, exiting debugging.

alt text

You can also set a breakpoint, so that when the program reaches the breakpoint, it will not continue running. As shown in the figure

alt text

You can also right-click a variable and add it to the Watch, then set a breakpoint on a certain line of the program to see the value of the variable before the program runs to that line (note that wherever you set the breakpoint is exactly the line that has just been reached, and that line has not yet been executed). As shown in the figure. For example, right-click thisi alt text

Click this添加到监视(Add to Watch)

alt text

Each time you开始运行(F5)reach the breakpoint, thisiwill increase by 1.

alt text

4.2.1.Frequently Asked Questions
  1. If you are on Linux and encounter the following issue
Open On-Chip Debugger v0.12.0-esp32-20250707 (2025-07-06-17:37) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html [OpenOCD] Open On-Chip Debugger v0.12.0-esp32-20250707 (2025-07-06-17:37) Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html debug_level: 2 Info : esp_usb_jtag: VID set to 0x303a and PID to 0x1001 Info : esp_usb_jtag: capabilities descriptor set to 0x2000 Info : Listening on port 6666 for tcl connections Info : Listening on port 4444 for telnet connections ❌ Error: libusb_open() failed with LIBUSB_ERROR_ACCESS Error: libusb_open() failed with LIBUSB_ERROR_ACCESS ❌ Error: esp_usb_jtag: could not find or open device! /home/tungchiahui/.espressif/tools/openocd-esp32/v0.12.0-esp32-20250707/openocd-esp32/share/openocd/scripts/target/esp_common.cfg:9: Error: Traceback (most recent call last): File "/home/tungchiahui/.espressif/tools/openocd-esp32/v0.12.0-esp32-20250707/openocd-esp32/share/openocd/scripts/target/esp_common.cfg", line 9, in script Error: esp_usb_jtag: could not find or open device! /home/tungchiahui/.espressif/tools/openocd-esp32/v0.12.0-esp32-20250707/openocd-esp32/share/openocd/scripts/target/esp_common.cfg:9: Error: Traceback (most recent call last): File "/home/tungchiahui/.espressif/tools/openocd-esp32/v0.12.0-esp32-20250707/openocd-esp32/share/openocd/scripts/target/esp_common.cfg", line 9, in script For assistance with OpenOCD errors, please refer to our Troubleshooting FAQ: https://github.com/espressif/openocd-esp32/wiki/Troubleshooting-FAQ OpenOCD Exit with non-zero error code 1 [Stopped] : OpenOCD Server [/OpenOCD] [Flash] Can't perform JTAG flash, because OpenOCD server is not running! Flash has finished. You can monitor your device with 'ESP-IDF: Monitor command'然后tungchiahui@Dell-G15-5511:~/UserFolder/MySource/ESP32_Projects/N01_LED$ ls /dev | grep ACM ttyACM0 tungchiahui@Dell-G15-5511:~/UserFolder/MySource/ESP32_Projects/N01_LED$ lsusb Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub Bus 003 Device 002: ID 0416:b23c Winbond Electronics Corp. Gaming Keyboard Bus 003 Device 003: ID 046d:c539 Logitech, Inc. Lightspeed Receiver Bus 003 Device 004: ID 0c45:6720 Microdia Integrated_Webcam_HD Bus 003 Device 005: ID 8087:0026 Intel Corp. AX201 Bluetooth Bus 003 Device 006: ID 303a:1001 Espressif USB JTAG/serial debug unit Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub tungchiahui@Dell-G15-5511:~/UserFolder/MySource/ESP32_Projects/N01_LED

It is most likely due to permission issues.

lsusb

alt text

Find the device after this that is1001; remember the device ID before it, for example mine is303a

Next, edit udev

sudo vim /etc/udev/rules.d/99-esp32-usb-jtag.rules

Enter the following content (note that the "idVendor" here should be filled in with your corresponding value)

SUBSYSTEM=="usb", ATTR{idVendor}=="303a", MODE="0666"

After saving, run the following command first

sudo udevadm control --reload-rules
sudo udevadm trigger

Then plug and unplug the USB, and the next flashing attempt succeeds.

5.ESP32-S3 clock tree

5.1.clock

A clock is a signal that toggles periodically alt text

On each clock edge, the entire circuit completes one full state update, and in this way, the whole system advances by one step.

5.2.Clock tree

alt text

alt text

OSC is a high-speed crystal oscillator, CLK is a clock, PLL is a phase-locked loop, DIV in the figure is a frequency divider, and MUX is a selector. OSC needs to be connected to a crystal oscillator. CLK is an available clock signal. PLL is for multiplying or dividing the clock frequency. DIV is for saving power and conserving resources. MUX is for selecting which clock source.

alt text

alt text

6.GPIO

6.1.GPIO Theory

实际上就是通用输入输出端口的意思,可以输出高低电平,也可以读取高低电平.

The input principle is as shown in the figure below. If pressing the KEY causes the circuit level to become low, then a pull-up resistor is required, and the falling edge is used to determine whether the key is pressed. Because of the pull-up resistor, when the KEY is not pressed at the beginning, the IO port of the circuit below is at a high level. Once the KEY is pressed, it will become a low level, so there will be a falling edge (the level changes from high to low).

alt text

Output: as shown in the figure below, when IO is at a high level, the LED light-emitting diode is conducting, and then the LED lights up; when IO is at a low level, the LED light-emitting diode is not conducting, and the LED is off.

alt text

The following are the ESP32 IOs:

alt text

They are highly reusable, and each one can be reused as an interface for other peripherals.

However, there are some exceptions: certain pins cannot be used as inputs or outputs; they can only be used to connect to the FLASH or PSRAM on the module. You can refer to the detailed description in ALIENTEK'sesp32-s3_datasheet_cn.pdf.

6.2.GPIO-related functions

  1. gpio_config

This is the GPIO initialization function

esp_err_t gpio_config(const gpio_config_t *pGPIOConfig)

Then you right-click to selectgpio_config_t, clickgo to definition, and the following will appear, which is the structure of the entry parameters in the GPIO initialization function.

/**
 * @brief Configuration parameters of GPIO pad for gpio_config function
 */
typedef struct {
    uint64_t pin_bit_mask;          /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */
    gpio_mode_t mode;               /*!< GPIO mode: set input/output mode                     */
    gpio_pullup_t pull_up_en;       /*!< GPIO pull-up                                         */
    gpio_pulldown_t pull_down_en;   /*!< GPIO pull-down                                       */
    gpio_int_type_t intr_type;      /*!< GPIO interrupt type                                  */
#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER
    gpio_hys_ctrl_mode_t hys_ctrl_mode;       /*!< GPIO hysteresis: hysteresis filter on slope input    */
#endif
} gpio_config_t;

pin_bit_mask是用于设置你要配置的GPIO引脚,一般为1ull << x,这里的x就是你要设置的GPIO的IOx号.

modeIt is the mode for configuring input or output, you cango to definitiontake a lookgpio_mode_t.

typedef enum {
    GPIO_MODE_DISABLE = GPIO_MODE_DEF_DISABLE,                                                         /*!< GPIO mode : disable input and output             */
    GPIO_MODE_INPUT = GPIO_MODE_DEF_INPUT,                                                             /*!< GPIO mode : input only                           */
    GPIO_MODE_OUTPUT = GPIO_MODE_DEF_OUTPUT,                                                           /*!< GPIO mode : output only mode                     */
    GPIO_MODE_OUTPUT_OD = ((GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)),                               /*!< GPIO mode : output only with open-drain mode     */
    GPIO_MODE_INPUT_OUTPUT_OD = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)), /*!< GPIO mode : output and input with open-drain mode*/
    GPIO_MODE_INPUT_OUTPUT = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT)),                         /*!< GPIO mode : output and input mode                */
} gpio_mode_t;

其他的也一样,你都可以go to definition来看这个结构体到底能填什么.

pull_up_enWhether to enable the pull-up resistor means connecting another circuit in parallel with the IO line, with a resistor and VCC in that circuit. pull_down_enWhether to enable the pull-down resistor means connecting another circuit in parallel with the IO line, with a resistor and GND in that circuit.

intr_typeWhether to enable the interrupt type.

  1. gpio_set_level
esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level)
{
    GPIO_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), "GPIO output gpio_num error", ESP_ERR_INVALID_ARG);
    gpio_hal_set_level(gpio_context.gpio_hal, gpio_num, level);
    return ESP_OK;
}

gpio_numWhich IO port is selected cango to definitionTake a look; as can be seen from the code below, it can be filled withGPIO_NUM_x, here thexis to select and initializeIOx(that is, which pin IO is initialized)

levelIt is whether the output level is high or low. Filling in 0 means low, and filling in 1 means high.

/**
 * @brief GPIO number
 */
typedef enum {
    GPIO_NUM_NC = -1,    /*!< Use to signal not connected to S/W */
    GPIO_NUM_0 = 0,     /*!< GPIO0, input and output */
    GPIO_NUM_1 = 1,     /*!< GPIO1, input and output */
    GPIO_NUM_2 = 2,     /*!< GPIO2, input and output */
    GPIO_NUM_3 = 3,     /*!< GPIO3, input and output */
    GPIO_NUM_4 = 4,     /*!< GPIO4, input and output */
    GPIO_NUM_5 = 5,     /*!< GPIO5, input and output */
    GPIO_NUM_6 = 6,     /*!< GPIO6, input and output */
    GPIO_NUM_7 = 7,     /*!< GPIO7, input and output */
    GPIO_NUM_8 = 8,     /*!< GPIO8, input and output */
    GPIO_NUM_9 = 9,     /*!< GPIO9, input and output */
    GPIO_NUM_10 = 10,   /*!< GPIO10, input and output */
    GPIO_NUM_11 = 11,   /*!< GPIO11, input and output */
    GPIO_NUM_12 = 12,   /*!< GPIO12, input and output */
    GPIO_NUM_MAX,
} gpio_num_t;
  1. gpio_get_level
int gpio_get_level(gpio_num_t gpio_num)
{
    return gpio_hal_get_level(gpio_context.gpio_hal, gpio_num);
}

gpio_numWhich IO port is selected cango to definitionTake a look; as can be seen from the code below, it can be filled withGPIO_NUM_x, here thexis to select and initializeIOx(that is, which pin IO is initialized)

The int data returned by return indicates whether the read level is high or low; 0 means low, 1 means high.

6.3.LED Light Practical Practice

Two types of LED physical objects: alt text SMD LED alt text

In the following circuit, the LED lights up only when the IO port is at a high level. alt text In the following circuit, the LED lights up only when the IO port is at a low level. alt text The disadvantage of the above two is that the current flows directly through the MCU. Another transistor-based method is better; you can search for it yourself, or look at the schematic of DJI's STM32C board (STM32F407IGH6).

First, write the code forled.h; this is the minimal framework. You should have learned conditional compilation in C, which is used to prevent header files from being included repeatedly.

#ifndef __LED_H_
#define __LED_H_



#endif

Next, continue to improve.led.hThe following enumeration is intended to make the code more readable by giving names to high and low levels,PIN_RESETis low level,PIN_SETis high level.

#ifndef __LED_H_
#define __LED_H_

//包含ESP32的gpio组件的头文件
#include "driver/gpio.h"

typedef enum {
    PIN_RESET = 0,     
    PIN_SET      
} gpio_output_state_t;

void led_init(void);

#endif

Next, write the content ofled.c Because looking at my own board's schematic, IO43 on my board is the LED control pin, so next I will initialize IO43. (Here I only have IO43; IO44 is connected to an ordinary light, so there is no way around it—I can only first replace the menuconfig in Channel for console output with none, while your light does not need to change this.)

#include "led.h"

void led_init(void)
{   
    //给结构体清零(C语言知识,要给局部变量清零,防止未被初始化的地方出现很奇怪的事情)
    gpio_config_t gpio_init_struct = {0}; 

    gpio_init_struct.pin_bit_mask = (1ULL << GPIO_NUM_43);    //初始化IO43
    gpio_init_struct.mode = GPIO_MODE_OUTPUT;                 //设置为输出模式
    gpio_init_struct.pull_up_en = GPIO_PULLUP_DISABLE;        //禁用上拉电阻
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;    //禁用下拉电阻
    gpio_init_struct.intr_type = GPIO_INTR_DISABLE;           //禁用中断

    gpio_config(&gpio_init_struct);        //配置GPIO

}

What you should fill in for the above parameters can all be determined bygo to definitionquerying the structure type. If you do not understand, watch the Zhengdian Atom video in detail to see how to query the structure.

Then edit it a bitmain.c:

//包含FreeRTOS头文件(为了用vTaskDelay)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
//包含LED头文件,为了初始化LED和使用GPIO
#include "led.h"

void app_main(void)
{
    //初始化led的GPIO
    led_init();
    //死循环,等同于while(1),但效率比while(1)更高
    for(;;)
    {
        gpio_set_level(GPIO_NUM_43, PIN_SET);   //设置为高电平
        vTaskDelay(500 / portTICK_PERIOD_MS);  //延时500ms
        gpio_set_level(GPIO_NUM_43, PIN_RESET); //设置为低电平
        vTaskDelay(500 / portTICK_PERIOD_MS);  //延时500ms
    }
}

The above code will make the LED blink once every 1 second. Here vTaskDelay(500 / portTICK_PERIOD_MS);Actually, it can be simplified tovTaskDelay(500);, because we previously configuredconfigTICK_RATE_HZto 1000, this will cause the value ofportTICK_PERIOD_MSto be 1. However, the above writing style is more formal; if others want to use your code, they will not cause the delay unit to run incorrectly. It is still recommended not to simplify it, whereas ALIENTEK uses the simplified version; if others migrate and copy your code, big problems will occur.

Press the same three keys in order, and then you can see the light on the board blinking.

alt text

6.4.KEY in practice

The following is a KEY circuit; when the KEY is pressed, it becomes conductive.

alt text

In reality, pressing a key causes bouncing, which is generally caused by mechanical materials, structures, or the environment.

alt text

However, the middle section is normal, as shown in the figure below.

alt text

For everyday use, the delay method for software debouncing is sufficient.

alt text

Actual wiring

alt text

First, modifyBSPin the folderCMakeLists.txt:

set(src_dirs
			LED
			KEY)

set(include_dirs
			LED
			KEY)

# GPIO的一些组件在driver里
set(requires
			driver)

idf_component_register(SRC_DIRS ${src_dirs}
					  INCLUDE_DIRS ${include_dirs} REQUIRES ${requires})

component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

Let's open the schematic and see which pin the button is on. Since my board does not have a custom button, I chose to use the button ofBOOTas the button.

alt text

As shown in the figure above, one side of this button is connected toGNDthe other side is connected toGPIO0, so initiallyGPIO0this side must be in a high-level state; when the button is pressed, the entire line will be pulledGNDto a low level, soGPIO0when a transition from high level to low level is detected, this is called detecting a falling edge; conversely, it is called a rising edge.

How do we makeGPIO0it high level right at power-up? That is to makeGPIO0a pull-up resistor must be connected.

Conversely, ifBOOTthe left side isVCC, thenGPIO0a pull-down resistor is required, and the rising edge must be detected.

First, createkey.hcontent

#ifndef __KEY_H_
#define __KEY_H_

//包含ESP32的gpio组件的头文件
#include "driver/gpio.h"

#define BOOT_PRES 1

#endif

Here,BOOT_PRESis an identifier; when the BOOT button is pressed, this value, that is,BOOT_PRESwill be detected,1.

Then inkey.cFirst, initialize thegpio0of thegpio.

Similar toledBut note that the mode here is input mode, and then the pull-up resistor must be enabled.

void key_init(void)
{   
    //给结构体清零(C语言知识,要给局部变量清零,防止未被初始化的地方出现很奇怪的事情)
    gpio_config_t gpio_init_struct = {0}; 

    gpio_init_struct.pin_bit_mask = (1ULL << GPIO_NUM_0);    //初始化IO0
    gpio_init_struct.mode = GPIO_MODE_INPUT;                 //设置为输入模式
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;        //启用上拉电阻
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;    //禁用下拉电阻
    gpio_init_struct.intr_type = GPIO_INTR_DISABLE;           //禁用中断

    gpio_config(&gpio_init_struct);        //配置GPIO

}

Next comes the main part, the key scanning function. The comments below explain it in detail, so you can carefully analyze the code yourself.

/**
 * @brief       按键扫描函数
 * @note        无
 * @param       mode:0 / 1, 具体含义如下:
 *   @arg       0,  不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
 *                  必须松开以后, 再次按下才会返回其他键值)
 *   @arg       1,  支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
 * @retval      键值, 定义如下:
 *              BOOT_PRES, 1, BOOT按键按下
 */
uint8_t key_scan(uint8_t mode)
{
    static uint8_t key_up = 1;  /* 按键按松开标志 */
    uint8_t keyval = 0;

    if (mode)       /* 支持连按 */
    {
        key_up = 1;
    }

    if (key_up && (gpio_get_level(GPIO_NUM_0) == 0))  /* 按键松开标志为1, 且有任意一个按键按下了 */
    {
        vTaskDelay(10 / portTICK_PERIOD_MS);           /* 去抖动 */
        key_up = 0;

        if (gpio_get_level(GPIO_NUM_0) == 0)  keyval = BOOT_PRES;


    }
    else if (gpio_get_level(GPIO_NUM_0) == 1)         /* 没有任何按键按下, 标记按键松开 */
    {
        key_up = 1;
    }

    return keyval;              /* 返回键值 */
}

The main thing used isint gpio_get_level(gpio_num_t gpio_num)such a function; we discussed this API in the first section.

int gpio_get_level(gpio_num_t gpio_num)
{
    return gpio_hal_get_level(gpio_context.gpio_hal, gpio_num);
}

Then, a global variable also needs to be defined as the key variable to store the key result.

uint8_t key_value = 0;

Write the main logic inmain.c; that is, when the key is pressed, toggle the level.

//包含FreeRTOS头文件(为了用vTaskDelay)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
//包含LED头文件,为了初始化LED和使用GPIO
#include "led.h"
//包含KEY头文件,为了初始化KEY和使用GPIO
#include "key.h"

//按键外部变量声明;
extern uint8_t key_value;


void app_main(void)
{
    //初始化led的GPIO
    led_init();
    //初始化key的GPIO
    key_init();

    //死循环,等同于while(1),但效率比while(1)更高
    for(;;)
    {
        key_value = key_scan(0);

        switch (key_value)
        {
        case BOOT_PRES:
            gpio_set_level(GPIO_NUM_43, !gpio_get_level(GPIO_NUM_43));   //翻转电平
            break;
        
        default:
            break;
        }

        vTaskDelay(10 / portTICK_PERIOD_MS);  //延时10ms
    }
}

Below is thekey.handkey.cFull content:

#include "key.h"

//包含FreeRTOS头文件(为了用vTaskDelay)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"


uint8_t key_value = 0;

void key_init(void)
{   
    //给结构体清零(C语言知识,要给局部变量清零,防止未被初始化的地方出现很奇怪的事情)
    gpio_config_t gpio_init_struct = {0}; 

    gpio_init_struct.pin_bit_mask = (1ULL << GPIO_NUM_0);    //初始化IO0
    gpio_init_struct.mode = GPIO_MODE_INPUT;                 //设置为输入模式
    gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;        //启用上拉电阻
    gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;    //禁用下拉电阻
    gpio_init_struct.intr_type = GPIO_INTR_DISABLE;           //禁用中断

    gpio_config(&gpio_init_struct);        //配置GPIO

}


/**
 * @brief       按键扫描函数
 * @note        无
 * @param       mode:0 / 1, 具体含义如下:
 *   @arg       0,  不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
 *                  必须松开以后, 再次按下才会返回其他键值)
 *   @arg       1,  支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
 * @retval      键值, 定义如下:
 *              BOOT_PRES, 1, BOOT按键按下
 */
uint8_t key_scan(uint8_t mode)
{
    static uint8_t key_up = 1;  /* 按键按松开标志 */
    uint8_t keyval = 0;

    if (mode)       /* 支持连按 */
    {
        key_up = 1;
    }

    if (key_up && (gpio_get_level(GPIO_NUM_0) == 0))  /* 按键松开标志为1, 且有任意一个按键按下了 */
    {
        vTaskDelay(10 / portTICK_PERIOD_MS);           /* 去抖动 */
        key_up = 0;

        if (gpio_get_level(GPIO_NUM_0) == 0)  keyval = BOOT_PRES;


    }
    else if (gpio_get_level(GPIO_NUM_0) == 1)         /* 没有任何按键按下, 标记按键松开 */
    {
        key_up = 1;
    }

    return keyval;              /* 返回键值 */
}
#ifndef __KEY_H_
#define __KEY_H_

//包含ESP32的gpio组件的头文件
#include "driver/gpio.h"

#define BOOT_PRES 1

void key_init(void);

uint8_t key_scan(uint8_t mode);

#endif

No errors after compilation

alt text

alt text

You can test it: when the button is pressed, the LED will toggle its level.

There is another testing method: you can use debug to monitorkey_valuevalue, but it requires that you change the key scan mode to support long press. In the above code, you only need to modifykey_value = key_scan(1);this line.

You can enter debug mode and give it a try. Set a breakpoint at the position shown in the image below. Then, when you hold down the key and click Continue, its watch value will become 1. When you release it and click Continue, it will become 0. alt text

alt text

6.5.External interrupt

6.5.1.Interrupt Introduction

In the previous section, we kept running the key scanning function in the infinite loop insideapp_main, and this approach is actually called轮询, and this type of function is called阻塞式函数. If you have studied STM32, you will surely have a deep understanding of what阻塞式, and what中断式.

alt text

alt text

What exactly is the process of interrupt execution like? Please see the introduction in the link below. Just read the interrupt execution process; the rest is all STM32 stuff and does not need to be read.

Introduction to Interrupt Service Routines

6.5.2.Introduction to External Interrupts

From the link above, you will understand that external interrupt EXIT is just one type of interrupt IT. In this section, we will explain external interrupts in detail.

alt text

The ESP32's EXIT has one more level-triggered mode than the STM32.

Level-triggered: high-level and low-level triggering, requiring the interrupt's level state to be held until the CPU responds. Edge-triggered: rising-edge and falling-edge triggering; once this type of interrupt is triggered, the CPU can respond immediately.

The ESP32S3's external interrupt function can capture the triggering of external events in a highly precise manner. Developers can adapt to different external events by configuring the interrupt trigger mode (such as rising edge, falling edge, any level, low-level hold, high-level hold, etc.), and when an event occurs, immediately interrupt the execution of the current program and switch to executing the interrupt service function.

6.5.3.Interrupt Priority

When multiple interrupts are triggered simultaneously, the CPU also executes interrupts in a certain order: interrupts with higher priority are executed first, followed by interrupts with lower priority.

ESP32-S3 Supports six interrupt levels and also supports interrupt nesting, meaning a lower-priority interrupt can be preempted by a higher-priority interrupt. As shown in the priority column in the following table,a larger number indicates a higher priority for that interrupt. Among them, the NMI interrupt has the highest priority; once this type of interrupt is triggered, the CPU must handle it.

alt text

在 ESP32S3 中,中断系统被用于响应各种内部和外部事件。这些中断按照其触发方式和优 先级进行分类。上表详细列出了 ESP32S3 的中断号、类别、种类以及相应的优先级。通过配置 这些中断,开发者可以实现对各种事件的及时响应和处理,提高系统的效率和实时性。

  1. Interrupt Number: A unique identifier for each interrupt, used to reference and configure a specific interrupt in a program.
  2. Category: The source type of the interrupt, divided into external interrupts and internal interrupts. External interrupts are triggered by external devices or signals, such as buttons, sensors, etc.; internal interrupts are triggered by hardware events inside the microcontroller, such as timer overflow, software interrupts, etc.
  3. Type: The trigger mode of the interrupt, including level triggering and edge triggering. Level triggering triggers an interrupt when the input signal reaches a specific level (such as high level or low level); edge triggering triggers an interrupt when the input signal changes from one level state to another.
  4. Priority: The response priority of the interrupt. When multiple interrupts occur simultaneously, the microcontroller determines which interrupt to handle first based on the interrupt priority. A higher priority means the interrupt will be handled first. During development, developers can configure parameters such as the interrupt trigger mode and priority according to actual requirements to implement an efficient and reliable event handling mechanism.
6.5.4.External Interrupt in Practice

alt text