ESP32 外設驅動開發指南 (ESP-IDF框架)——GPIO篇:基礎配置、外部中斷與PWM(LEDC模塊)應用

目錄

一、前言

二、GPIO

2.1 GPIO簡介

2.2 GPIO函數解析

2.3 LED驅動

2.4 KEY驅動

三、EXIT

3.1 EXIT簡介

3.2 EXIT函數解析

3.3 EXIT驅動

四、LEDC

4.1 PWM原理解析

4.2 ESP32的LED PWM控制器介紹

4.3 LEDC函數解析

4.3.1 SW_PWM

4.3.2 HW_PWM

4.4 LEDC驅動

結語


一、前言

????????博主最近也是找到實習了,實習項目用的是 ESP32-S3,基于 esp-idf 開發,因此想寫博客記錄一下學習筆記。
????????esp-idf 是基于 freeRTOS 的框架,里面用到的組件,以及我們的應用程序都是基于 freeRTOS 來開發的,因此我們必須掌握 freeRTOS 的用法。如果我們不深究原理,只關注于 freeRTOS 的接口使用,我們很快就能掌握。另外,因為 freeRTOS 開源免費的特性,目前大部分芯片產商做的 SDK 都是基于 freeRTOS 系統開發的,因此我們就更有理由要學習 RTOS 了。freeRTOS 可以去看我的專欄:FreeRTOS專欄,也可以去看韋東山老師的課程,尤其是內部原理,看完大有收獲。


二、GPIO

2.1 GPIO簡介

????????GPIO 是負責控制或采集外部器件信息的外設,主要負責輸入輸出功能。ESP32-S3 芯片具有 45 個物理 GPIO 管腳,涵蓋 GPIO0 至 GPIO21 以及 GPIO26 至 GPIO48 的廣泛范圍。

2.2 GPIO函數解析

????????ESP-IDF 提供了豐富的 GPIO 操作函數,在 v5.x.x\esp-idf\components\esp_driver_gpio 路徑下找到相關的 gpio.c 和 gpio.h 文件。在 gpio.h 頭文件中,你可以找到 ESP32-S3 的所有 GPIO?函數定義。

GPIO配置函數
? ? ? ? 該函數用于配置 GPIO 的模式、上下拉、中斷等功能,函數原型如下:

esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);

? ? ? ? 該函數的形參描述如下表所示:

參數描述
pGPIOConfigGPIO結構體

? ? ? ? 返回值:ESP_OK 表示配置成功,ESP_FAIL 表示配置失敗。
????????pGPIOConfig 為 GPIO 配置結構體指針,下面來看一下 gpio_config_t 結構體中的變量。

/* GPIO配置參數 */
typedef struct {uint64_t pin_bit_mask;        /* 配置引腳位 */gpio_mode_t mode;             /* 設置引腳模式 */gpio_pullup_t pull_up_en;     /* 設置上拉 */gpio_pulldown_t pull_down_en; /* 設置下拉 */gpio_int_type_t intr_type;    /* 中斷配置 */
} gpio_config_t;

? ? ? ? 各個參數有哪些見下表:

類型類型說明可填參數參數說明
.pin_bit_mask引腳位

(1<<x)其中 x 為 ESP32S3 中可用 GPIO

要用哪個引腳,比如 IO1 引腳,則寫為:(1ull << GPIO NUM 1)
.mode引腳模式GPIO_MODE_DISABLE
GPIO_MODE_INPUT
GPIO_MODE_OUTPUT
GPIO_MODE_OUTPUT_OD
GPIO_MODE_INPUT_OUTPUT_OD
GPIO_MODE_INPUT_OUTPUT
失能輸入輸出模式
僅輸入模式
僅輸出模式
輸出開漏模式
輸入輸出開漏模式
輸入輸出模式
.pull_up_en配置上拉
GPIO_PULLUP_DISABLE
GPIO_PULLUP_ENABLE

失能上拉
使能上拉

.pull_down_en配置下拉GPIO_PULLDOWN_DISABLE
GPIO_PULLDOWN_ENABLE

失能下拉

使能下拉

.intr_type中斷配置GPIO_INTR_DISABLE
GPIO_INTR_POSEDGE
GPIO_INTR_NEGEDGE
GPIO_INTR_ANYEDGE
GPIO_INTR_LOW_LEVEL
GPIO_INTR_HIGH_LEVEL

失能中斷
上升沿觸發
下降沿觸發
上升沿和下降沿觸發
輸入低電平觸發
輸入高電平觸發

設置管腳輸出電平
????????該函數用于配置某個管腳輸出電平,該函數原型如下所示:

esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level);

? ? ? ? 該函數的形參描述如下:

參數描述
gpio_numGPIO 引腳號。(在 gpio_types.h 文件中枚舉 gpio_num_t 有定義)
levelGPIO 引腳輸出電平。0:低電平,1:高電平

? ? ? ? 返回值:ESP_OK 表示設置成功,ESP_FAIL 表示設置失敗。

獲取管腳電平
????????該函數用于獲取某個管腳的電平,該函數原型如下所示:

int gpio_get_level(gpio_num_t gpio_num);

????????該函數的形參描述如下:

參數描述
gpio_numGPIO 引腳號。(在 gpio_types.h 文件中枚舉 gpio_num_t 有定義)

? ? ? ? 返回值:0 GPIO 輸入電平為低電平,1 GPIO 輸入電平為高電平。

2.3 LED驅動

? ? ? ? 萬物先從點燈開始,下面實現led.c及led.h兩個文件。led.h負責聲明LED相關的函數和變量,led.c實現LED的驅動代碼。

led.h

/* 引腳定義 */
#define LED_GPIO_PIN GPIO_NUM_1 /* LED 連接的 GPIO 端口 *//* 引腳的輸出的電平狀態 */
enum GPIO_OUTPUT_STATE{PIN_RESET,PIN_SET
};#define LED(x) do { x ? \gpio_set_level(LED_GPIO_PIN, PIN_SET) : \gpio_set_level(LED_GPIO_PIN, PIN_RESET); \
} while(0)#define LED_TOGGLE() do { gpio_set_level(LED_GPIO_PIN, !gpio_get_level(LED_GPIO_PIN)); 
} while(0) /* LED 翻轉 *//* 函數聲明*/
void led_init(void); /* 初始化 LED */

? ? ? ? LED(x) 宏用于控制 IO1 管腳的電平狀態,使用三元運算符,傳入 1 設置引腳為高電平;反之,輸出低電平。LED_TOGGLE() 宏,實現管腳電平翻轉。

led.c

// esp封裝的庫
#include "driver/gpio.h"
#include "led.h"/*** @brief 初始化 LED* @param 無* @retval 無*/
void led_init(void)
{gpio_config_t gpio_init_struct = {.pin_bit_mask = 1ull << LED_GPIO_PIN,  //指定GPIO.mode = GPIO_MODE_OUTPUT,              //設置為輸出模式.pull_up_en = GPIO_PULLUP_DISABLE,     //禁止上拉.pull_down_en = GPIO_PULLDOWN_DISABLE, //禁止下拉.intr_type = GPIO_INTR_DISABLE,        //禁止中斷};gpio_config(&gpio_init_struct); /* 配置 GPIO */LED(1);
}

2.4 KEY驅動

? ? ? ? 配置 GPIO 為輸入模式,通常按鍵直接連接到芯片的引腳,沒有加上拉電阻,因此需要將 GPIO 配置成上拉輸入模式。本次按鍵驅動使用的是最簡單的延時消抖,下面實現 key.c 及 key.h 兩個文件。

key.h

/* 引腳定義 */
#define BOOT_GPIO_PIN GPIO_NUM_0/*IO 操作*/
#define BOOT gpio_get_level(BOOT_GPIO_PIN)/* 按鍵按下定義 */
#define BOOT_PRES 1 /* BOOT 按鍵按下 *//* 函數聲明 */
void key_init(void); /* 初始化按鍵 */
uint8_t key_scan(uint8_t mode); /* 按鍵掃描函數 */

? ? ? ? 通過 BOOT 宏來讀取連接按鍵引腳的電平。

●?key.c

#include "driver/gpio.h"
#include "key.h"/*** @brief 初始化按鍵引腳* @param 無* @retval 無*/
void key_init(void)
{gpio_config_t gpio_init_struct;gpio_init_struct.intr_type = GPIO_INTR_DISABLE; /* 失能引腳中斷 */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.pin_bit_mask = 1ull << BOOT_GPIO_PIN; /* BOOT 按鍵引腳 */gpio_config(&gpio_init_struct); /* 配置使能 */
}/*** @brief 按鍵掃描函數* @param mode:0 / 1, 具體含義如下:*     0, 不支持連續按(當按鍵按下不放時, 只有第一次調用會返回鍵值,*        必須松開以后, 再次按下才會返回其他鍵值)*     1, 支持連續按(當按鍵按下不放時, 每次調用該函數都會返回鍵值)* @retval 鍵值, 定義如下:*     BOOT_PRES, 1, BOOT 按下*/
uint8_t key_scan(uint8_t mode)
{uint8_t keyval = 0;static uint8_t key_boot = 1; /* 按鍵松開標志 */if(mode){key_boot = 1;}if (key_boot && (BOOT == 0)) /* 按鍵松開標志為 1,且有任意一個按鍵按下了 */{vTaskDelay(10); /* 去抖動 */key_boot = 0;if (BOOT == 0){keyval = BOOT_PRES;}}else if (BOOT == 1){key_boot = 1;}return keyval; /* 返回鍵值 */
}

????????此函數只有一個形參 mode,用于設置按鍵是否支持連續按下模式。當 mode 為 0 時,表示按鍵不支持連續按下;反之,則支持連續按下。


三、EXIT

3.1 EXIT簡介

????????外部中斷屬于硬件中斷,由微控制器外部事件觸發。微控制器的特定引腳被設計為對特定事件(如按鈕按壓、傳感器信號變化等)作出響應,這些引腳通常稱為“外部中斷引腳”。一旦外部中斷事件發生,當前程序執行將立即暫停,并跳轉到相應的中斷服務程序(ISR)進行處理。處理完畢后,程序會恢復執行,從被中斷的地方繼續。下圖是 CPU 中斷處理過程。

????????ESP32-S3 的外部中斷具備兩種觸發類型:
????????(1)電平觸發:高、低電平觸發,要求保持中斷的電平狀態直到 CPU 響應。
????????(2)邊沿觸發:上升沿和下降沿觸發,這類型的中斷一旦觸發,CPU 即可響應。
????????ESP32-S3 的外部中斷功能能夠以非常精確的方式捕捉外部事件的觸發。開發者可以通過配置中斷觸發方式(如上升沿、下降沿、任意電平、低電平保持、高電平保持等)來適應不同的外部事件,并在事件發生時立即中斷當前程序的執行,轉而執行中斷服務函數。ESP32-S3 支持六級中斷,同時支持中斷嵌套,也就是低優先級中斷可以被高優先級中斷打斷。數字越大表明該中斷的優先級越高。其中,NMI 中斷擁有最高優先級,此類中斷已經觸發,CPU 必須處理。

3.2 EXIT函數解析

●?注冊中斷函數
????????該函數用于注冊中斷服務,原型如下:

esp_err_t gpio_install_isr_service(int intr_alloc_flags);

? ? ? ? 該函數的形參描述如下表所示:

參數中斷標志位描述
intr_alloc_flags

?

ESP_INTR_FLAG_LEVEL1

使用Level 1中斷級別。在中斷服務程序執行期間禁用同級別中斷。

ESP_INTR_FLAG_LEVEL2

使用Level 2中斷級別。在中斷服務程序執行期間禁用同級別和Level 1的中斷。

ESP_INTR_FLAG_LEVEL3

同理。

ESP_INTR_FLAG_LEVEL4

ESP_INTR_FLAG_LEVEL5

ESP_INTR_FLAG_LEVEL6

ESP_INTR_FLAG_NMI

Level 7中斷級別(最高優先級)

ESP_INTR_FLAG_SHARED

中斷可以在ISRs之間共享

ESP_INTR_FLAG_EDGE

使用邊沿觸發方式。使能GPIO邊沿觸發中斷。

ESP_INTR_FLAG_IRAM

如果緩存被禁用,ISR可以被調用

ESP_INTR_FLAG_INTRDISABLED

返回時禁用此中斷

? ? ? ? 返回值:ESP_OK,成功;
? ? ? ? ? ? ? ? ? ? ? ESP_ERR_NO_MEM,沒有內存來安裝此服務;
? ? ? ? ? ? ? ? ? ? ? ESP_ERR_INVALID_STATE,ISR服務已經安裝;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_NOT_FOUND,沒有找到具有指定標志的空閑中斷;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_INVALID_ARG,GPIO錯誤。

分配中斷函數
????????該函數設置某個管腳的中斷服務函數,該函數原型如下所示:

esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void *args);

????????該函數的形參描述如下表所示:

參數描述

gpio_num

GPIO引腳號,指定要分配中斷處理程序的GPIO引腳

isr_handler

指向中斷處理函數的函數指針。中斷處理函數是一個用戶定義的回調函數,將在中斷發生時執行

args

傳遞給中斷處理程序的參數。這是一個指向用戶特定數據的指針,可以在中斷處理程序中使用

? ? ? ? 返回值:ESP_OK,成功;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_INVALID_STATE,狀態錯誤,ISR服務沒有初始化;
? ? ? ? ? ? ? ? ? ? ? ESP_ERR_INVALID_ARG,參數錯誤。

? ? ? ? 下面是中斷處理函數的模板,中斷處理函數需要聲明為 IRAM_ATTR,以確保其運行在內存中的可執行區域。

void IRAM_ATTR gpio_isr_handler(void *arg)
{/* 處理中斷響應 */
}

開啟外部中斷函數
????????該函數用來配置某個管腳開啟外部中斷,該函數原型如下所示:

esp_err_t gpio_intr_enable(gpio_num_t gpio_num);

? ? ? ? 參數就是要使能哪個GPIO引腳,傳入引腳號。

? ? ? ? 返回值:ESP_OK,成功;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_INVALID_ARG,參數錯誤。

? ? ? ? 注意:在使用 gpio_intr_enable() 函數之前,開發者需要先通過 gpio_install_isr_service() 函數和 gpio_isr_handler_add() 函數來安裝和注冊中斷處理程序。

3.3 EXIT驅動

exit.h

/*引腳定義*/
#define BOOT_INT_GPIO_PIN GPIO_NUM_0/*IO 操作*/
#define BOOT gpio_get_level(BOOT_INT_GPIO_PIN)/* 函數聲明 */
void exit_init(void); /* 外部中斷初始化程序 */

exit.c

/*** @brief       外部中斷服務函數* @param       arg:中斷引腳號* @note        IRAM_ATTR: 這里的IRAM_ATTR屬性用于將中斷處理函數存儲在內部RAM中,目的在于減少延遲* @retval      無*/
static void IRAM_ATTR exit_gpio_isr_handler(void *arg)
{uint32_t gpio_num = (uint32_t) arg;if (gpio_num == BOOT_INT_GPIO_PIN){/* 消抖 */esp_rom_delay_us(20000);//注意://這里的延時函數通過空循環消耗CPU時間,不會主動釋放CPU控制權//但是在ISR中,不允許使用可能阻塞的函數如vTaskDelay(會觸發上下文切換)//總的來說,還是不希望在中斷里進行耗時的操縱,這里的20ms勉強能接受if (BOOT == 0){LED0_TOGGLE();}}
}/*** @brief       外部中斷初始化程序* @param       無* @retval      無*/
void exit_init(void)
{gpio_config_t gpio_init_struct;/* 配置BOOT引腳和外部中斷 */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_NEGEDGE;             /* 下降沿觸發 */gpio_init_struct.pin_bit_mask = 1ull << BOOT_INT_GPIO_PIN;  /* 設置的引腳的位掩碼 */ESP_ERROR_CHECK(gpio_config(&gpio_init_struct));            /* 配置使能 *//* 注冊中斷服務 */ESP_ERROR_CHECK(gpio_install_isr_service(0));/* 設置BOOT的中斷回調函數 */ESP_ERROR_CHECK(gpio_isr_handler_add(BOOT_INT_GPIO_PIN, exit_gpio_isr_handler, (void*) BOOT_INT_GPIO_PIN));/* 使能GPIO模塊中斷信號 */ESP_ERROR_CHECK(gpio_intr_enable(BOOT_INT_GPIO_PIN));
}

????????開啟管腳的外部中斷操作相對簡便。首先,需要將管腳配置為下降沿觸發(GPIO_INTR_NEGEDGE)和輸入模式(GPIO_MODE_INPUT)。完成配置后,需要調用 gpio_install_isr_service 函數來注冊中斷服務,并調用 gpio_isr_handler_add 函數來注冊外部中斷的回調函數。最后,調用 gpio_intr_enable 函數啟用外部中斷功能。其中,exit_gpio_isr_handler 回調函數負責實現 LED 燈狀態的切換。


四、LEDC

4.1 PWM原理解析

? ? ? ? PWM(Pulse Width Modulation),簡稱脈寬調制,是一種將模擬信號變為脈沖信號的技術。PWM可以控制LED亮度、直流電機的轉速等。
? ? ? ? PWM的主要參數如下:
? ? ? ? ① 頻率:1s內有多少個PWM周期(一個高電平加一個低電平為一個周期),單位Hz。
? ? ? ? ② 周期:頻率倒數,T=1/f。
? ? ? ? ③ 占空比:在一個周期內,高電平的時間與整個周期時間的比例,范圍0%~100%。

?????????使用PWM控制LED時,一個PWM周期持續時間比較長,人眼就可以看出LED在閃爍。只要縮小周期,直到一個臨界值使得人眼無法分辨LED在閃爍,改變占空比,就改變了LED的亮度。這就是PWM的原理。

4.2 ESP32的LED PWM控制器介紹

? ? ? ? ESP32-S3的LED PWM控制器,簡寫為LEDC,用于生成脈沖寬度調制信號。
? ? ? ? LEDC具有八個獨立的PWM生成器(八個通道)。每個PWM生成器會從四個通用定時器中選擇一個,以該定時器的計數值作為基準生成PWM信號。LEDC定時器如下圖所示:

? ? ? ? ?想要實現PWM輸出,需要先指定PWM通道的參數:頻率、分辨率、占空比,然后將通道映射到指定的引腳,該引腳輸出對應通道的PWM信號,如下圖所示:

? ? ? ? ?LEDC可以在沒有CPU干預的情況下自動改變占空比(硬件PWM)

4.3 LEDC函數解析

4.3.1 SW_PWM

? ? ? ? ESP-IDF提供了一套API來配置PWM。要使用此功能,需要包含以下頭文件:

#include "driver/ledc.h"

配置LEDC使用的定時器的函數
? ? ? ? 注意:在首次配置LEDC時,建議先配置定時器,再配置通道。這樣可以確保IO引腳上的PWM信號自輸出開始那一刻起,其頻率就是正確的。

? ? ? ? 設置定時器函數原型如下:

esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf);

????????該函數的形參描述如下表所示:

形參描述
timer_conf指向配置LEDC定時器的結構體指針

? ? ? ? 返回值:ESP_OK,成功;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_INVALID_ARG,參數錯誤;
? ? ? ? ? ? ? ? ? ? ??ESP_FAIL,無法根據給定的頻率和當前的 PWM 分辨率找到一個合適的分頻系數;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_INVALID_STATE,無法取消定時器配置,因為定時器未配置或未處于暫停狀態。
? ? ? ? 該函數使用?ledc_timer_config_t 類型的結構體變量傳入,該結構體的定義如下所示:

結構體成員變量可選參數
ledc_timer_config_t

.speed_mode
速度模式

LEDC_HIGH_SPEED_MODE(僅存在于ESP32上)
高速模式
LEDC_LOW_SPEED_MODE
低速模式
LEDC_SPEED_MODE_MAX
模式上限(用于檢查模式有效性,不可作為實際的模式配置)

.duty_resolution
PWM占空比分辨率。由 ledc_timer_bit_t 枚舉類型定義,ESP32-S3支持的范圍是1~14位的分辨率。

LEDC_TIMER_X_BIT(X=1~14)

.timer_num

PWM通道的定時器源,由 ledc_timer_t 枚舉類型定義。

LEDC_TIMER_0
LEDC_TIMER_1
LEDC_TIMER_2
LEDC_TIMER_3
LEDC_TIMER_MAX
(同樣用于檢查模式有效性,不可作為實際的模式配置)

.freq_hz
PWM脈沖的頻率,表示LEDC模塊的定時器時鐘頻率,單位為Hz

uint32_t大小的值

.clk_cfg
時鐘源

LEDC_AUTO_CLK
在初始化計時器時,LEDC源時鐘會根據給定的分辨率和占空比被自動選定。
LEDC_USE_APB_CLK
選擇APB作為時鐘源。
LEDC_USE_RC_FAST_CLK
選擇內部快速RC時鐘作為時鐘源
LEDC_USE_XTAL_CLK

選擇外部晶體時鐘作為時鐘源
LEDC_USE_RTC8M_CLK

LEDC_USE_RC_FAST_CLK的別名

.deconfigure

執行硬件定時器的反初始化:停止定時器計數、釋放占用的硬件資源、復位內部狀態機、使定時器回歸未配置狀態。(需要完全改變定時器參數時使用)

bool值

? ? ? ? deconfigure 成員變量的使用流程如下:

// 1. 暫停定時器(必須步驟!)
ledc_timer_pause(LEDC_LOW_SPEED_MODE, LEDC_TIMER_0);// 2. 準備反配置結構體
ledc_timer_config_t timer_cfg = {.speed_mode = LEDC_LOW_SPEED_MODE, // 必須匹配原配置.timer_num = LEDC_TIMER_0,         // 指定要反配置的定時器.deconfigure = true                // 核心開關// 其他參數自動忽略
};// 3. 執行反配置
ledc_timer_config(&timer_cfg);

? ? ? ? 注意:ESP32-S3 不支持定時器專屬時鐘,所有定時器必須共享同一時鐘源。禁止混合配置(如 TIMER0 用 RC_FAST + TIMER1 用 XTAL)!!!

通道配置函數
? ? ? ? 函數原型如下:

esp_err_t ledc_channel_config(const ledc_channel_config_t *ledc_conf);

? ? ? ? 形參就是指向LEDC通道的結構體指針,來看一下返回值和結構體的具體定義。
????????返回值:ESP_OK,成功;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_INVALID_ARG,參數錯誤。
????????

結構體成員變量可選參數
ledc_channel_config_t

.gpio_num

配置輸出引腳

if you want to use gpio16, gpio_num = 16

.speed_mode

速度模式

LEDC_HIGH_SPEED_MODE(僅存在于ESP32上)
高速模式
LEDC_LOW_SPEED_MODE
低速模式
LEDC_SPEED_MODE_MAX
模式上限(用于檢查模式有效性,不可作為實際的模式配置)

.channel

LEDC的輸出通道(PWM輸出通道)

LEDC_CHANNEL_X(X=0~7)
LEDC_CHANNEL_MAX
(用于檢查模式有效性,不可作為實際的模式配置)

.intr_type

中斷配置

LEDC_INTR_DISABLE
失能
LEDC_INTR_FADE_END

使能漸變結束中斷
LEDC_INTR_MAX

(用于檢查模式有效性,不可作為實際的模式配置)

.timer_sel

選擇通道的定時器源。由?ledc_timer_t 枚舉類型定義,和之前配置定時器一樣

LEDC_TIMER_0
LEDC_TIMER_1
LEDC_TIMER_2
LEDC_TIMER_3
LEDC_TIMER_MAX
(同樣用于檢查模式有效性,不可作為實際的模式配置)

.duty
LEDC通道的占空比設置

范圍為[0, (2**duty_resolution)],duty_resolution為定時器配置時的PWM占空比分辨率

.hpoint

led通道hpoint值。一個周期中上升沿開始的時間點,一般不太關系,給0即可。

int類型的大小

.output_invert

啟用或禁用gpio輸出反相

1(啟用);0(禁用)

設置PWM占空比
? ? ? ? 調用函數 ledc_set_duty() 可以設置新的占空比,之后調用函數 ledc_update_duty() 使新配置生效。要查看當前設置的占空比,可以使用 ledc_get_duty() 函數。設置PWM占空比的函數原型如下:

esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty);

? ? ? ??該函數的形參描述如下表所示:

形參描述
speed_mode速度模式選擇:
LEDC_HIGH_SPEED_MODE(僅存在于ESP32上)
高速模式
LEDC_LOW_SPEED_MODE
低速模式
LEDC_SPEED_MODE_MAX
模式上限(用于檢查模式有效性,不可作為實際的模式配置)
channelLEDC通道:
(0~LEDC_CHANNEL_MAX-1),從 ledc_channel_t 中選擇
duty占空比,范圍為[0, (2**duty_resolution)],duty_resolution為定時器配置時的PWM占空比分辨率

? ? ? ? 返回值:ESP_OK,成功;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_INVALID_ARG,參數錯誤。

更新PWM占空比
? ? ? ??
上一步調用 ledc_set_duty() 后,調用 ledc_update_duty() 使得新配置生效,函數原型如下:

esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);

????????該函數的形參描述見上一個函數,返回值也一樣。

????????到這里就屬于ESP32-S3的軟件PWM部分,配置好定時器、LEDC通道后,就可以搭配使用上面兩個改變PWM占空比的函數,在指定引腳輸出想要的PWM脈沖。之所以叫做軟件PWM,是因為:如果想要實現呼吸燈的效果,需要我們不斷判斷當前的占空比為多少,然后手動改變占空比的遞增或遞減,這些操作都需要消耗CPU資源。下面來介紹硬件PWM的功能和用法,它可在無需CPU干預的情況下自動改變占空比。

4.3.2 HW_PWM

? ? ? ? LEDC控制器硬件可逐漸改變占空比的數值,要使用此功能,可用 ledc_fade_func_install() 使能漸變,然后使用下列漸變函數之一進行配置:
ledc_set_fade_with_time()
ledc_set_fade_with_step()
ledc_set_fade()
最后調用 ledc_fade_start() 開啟漸變。還記得配置LEDC通道的時候有個參數是使能中斷嗎,可選的只有兩項,使能漸變結束中斷和失能。我查了一下,發現這個即使不使能也不影響硬件PWM,至于軟件PWM中使能這個中斷有沒有用,暫時沒找到很明確的說明,如果有大佬懂得可以在評論區討論一下。硬件PWM可以注冊一個回調函數,在漸變完成之后就會調用回調函數,這個回調函數由中斷調用,但這個中斷是我們調用?ledc_fade_func_install() 函數時,內部會初始化LEDC的漸變中斷,和通道配置中的 intr_type 無關。

開啟硬件PWM,使能漸變
????????安裝LEDC漸變功能。該功能將占用LEDC模塊的中斷資源。

esp_err_t ledc_fade_func_install(int intr_alloc_flags);

? ? ? ? 該函數的形參見?esp_intr_alloc.h 里,帶?ESP_INTR_FLAG_ 前綴的宏定義。但是,很多例程里調用這個函數直接傳入0即可,表示默認的中斷優先級。

? ? ? ? 返回值:ESP_OK,成功;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_INVALID_ARG,參數錯誤;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_NOT_FOUND,找不到可用的中斷源;
? ? ? ? ? ? ? ? ? ? ??ESP_ERR_INVALID_STATE,漸變服務已經安裝。

●?設置LEDC漸變功能
? ? ? ? 接下來要設置占空比以及漸變時長,函數原型如下:

esp_err_t ledc_set_fade_with_time(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t target_duty, int max_fade_time_ms);

????????該函數的形參描述如下表所示:

形參描述
speed_modeLEDC_HIGH_SPEED_MODE(僅存在于ESP32上)
高速模式
LEDC_LOW_SPEED_MODE
低速模式
LEDC_SPEED_MODE_MAX
模式上限(用于檢查模式有效性,不可作為實際的模式配置)
channelLEDC通道:
(0~LEDC_CHANNEL_MAX-1),從 ledc_channel_t 中選擇
target_duty目標占空比
范圍為[0, (2**duty_resolution)],duty_resolution為定時器配置時的PWM占空比分辨率
max_fade_time_ms最大漸變時間(毫秒)

? ? ? ? 返回值:ESP_OK表示成功,其他表示配置失敗。

開啟漸變
? ? ? ??
函數原型如下:

esp_err_t ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_fade_mode_t fade_mode);

? ? ? ? 前兩個參數說爛了,來看看第三個參數:

第三個參數描述
fade_mode漸變模式,由 ledc_fade_mode_t 枚舉類型定義,有以下模式可選:
LEDC_FADE_NO_WAIT
LEDC_FADE_WAIT_DONE
LEDC_FADE_MAX
(用于檢查模式有效性,不可作為實際的模式配置)

? ? ? ? 這個參數就是設置是否阻塞,不論選擇哪個模式,都可以綁定漸變完成回調函數,不過阻塞模式下使用回調函數意義不太大,因為當阻塞模式下的函數返回時,回調函數一定已經執行完畢了(回調函數是在漸變結束時、函數返回前由內部驅動調用的)。

設置漸變完成回調函數
? ? ? ? 在非阻塞模式下,函數調用之后立即返回,想要知道什么時候漸變完成,需要綁定一個回調函數,當回調函數被調用時,在回調函數里設置某些標志位,不能調用任何可能導致阻塞的函數。函數原型如下:

esp_err_t ledc_cb_register(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_cbs_t *cbs, void *user_arg);

? ? ? ? 主要看后兩個參數:

后兩個形參描述
*cbs

指向ledc_cbs_t結構體的指針。ledc_cbs_t里面只有一個成員變量:fade_cb。它是指向回調函數的指針,回調函數的類型為ledc_cb_t,定義如下:

typedef bool (*ledc_cb_t)(const ledc_cb_param_t *param, void *user_arg);
param:系統傳入的事件參數(通道號、狀態等)
user_arg:用戶自定義的透傳參數,初始化時傳入

*user_arg傳給回調函數的參數

4.4 LEDC驅動

? ? ? ? 使用硬件PWM、非阻塞模式,在回調函數里使用事件組,實現呼吸燈的效果

#include <freertos/FreeRTOS.h>          // FreeRTOS基礎功能
#include <freertos/task.h>              // 任務相關API(xTaskCreatePinnedToCore)
#include <freertos/event_groups.h>      // 事件組(EventGroupHandle_t, xEventGroup*)
#include "driver/gpio.h"                // GPIO定義(LED_GPIO)
#include "driver/ledc.h"                // LEDC PWM驅動(所有ledc_*函數和結構體)
#include <esp_log.h>                    // 日志系統(ESP_ERROR_CHECK)//定義LED的GPIO口
#define LED_GPIO  GPIO_NUM_1#define TAG     "LEDC"#define LEDC_TIMER              LEDC_TIMER_0            //定時器0
#define LEDC_MODE               LEDC_LOW_SPEED_MODE     //低速模式
#define LEDC_OUTPUT_IO          (LED_GPIO)              //選擇GPIO端口
#define LEDC_CHANNEL            LEDC_CHANNEL_0          //PWM通道
#define LEDC_DUTY_RES           LEDC_TIMER_13_BIT       //分辨率
#define LEDC_DUTY               (4095)                  //最大占空比值,這里是2^13-1
#define LEDC_FREQUENCY          (5000)                  //PWM周期//用于通知漸變完成
static EventGroupHandle_t   s_ledc_ev = NULL;//關燈完成事件標志
#define LEDC_OFF_EV  (1<<0)//開燈完成事件標志
#define LEDC_ON_EV   (1<<1)//漸變完成回調函數
bool IRAM_ATTR ledc_finish_cb(const ledc_cb_param_t *param, void *user_arg)
{BaseType_t xHigherPriorityTaskWoken;if(param->duty){xEventGroupSetBitsFromISR(s_ledc_ev,LEDC_ON_EV,&xHigherPriorityTaskWoken);}else{xEventGroupSetBitsFromISR(s_ledc_ev,LEDC_OFF_EV,&xHigherPriorityTaskWoken);}return xHigherPriorityTaskWoken;
}//ledc 漸變任務
void ledc_breath_task(void* param)
{EventBits_t ev;while(1){ev = xEventGroupWaitBits(s_ledc_ev,LEDC_ON_EV|LEDC_OFF_EV,pdTRUE,pdFALSE,pdMS_TO_TICKS(5000));if(ev){//設置LEDC開燈漸變if(ev & LEDC_OFF_EV){ledc_set_fade_with_time(LEDC_MODE,LEDC_CHANNEL,LEDC_DUTY,2000);ledc_fade_start(LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT);}else if(ev & LEDC_ON_EV)    //設置LEDC關燈漸變{ledc_set_fade_with_time(LEDC_MODE,LEDC_CHANNEL,0,2000);ledc_fade_start(LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT);}}}
}//LED呼吸燈初始化
void led_breath_init(void)
{//初始化一個定時器ledc_timer_config_t ledc_timer = {.speed_mode       = LEDC_MODE,      //低速模式.timer_num        = LEDC_TIMER,     //定時器ID.duty_resolution  = LEDC_DUTY_RES,  //占空比分辨率,這里是13位,2^13-1.freq_hz          = LEDC_FREQUENCY,  // PWM頻率,這里是5KHZ.clk_cfg          = LEDC_AUTO_CLK    // 時鐘};ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));//ledc通道初始化ledc_channel_config_t ledc_channel = {.speed_mode     = LEDC_MODE,        //低速模式.channel        = LEDC_CHANNEL,     //PWM 通道0-7.timer_sel      = LEDC_TIMER,       //關聯定時器,也就是上面初始化好的那個定時器.intr_type      = LEDC_INTR_DISABLE,//不使能中斷.gpio_num       = LEDC_OUTPUT_IO,   //設置輸出PWM方波的GPIO管腳.duty           = 0, // 設置默認占空比為0.hpoint         = 0};ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));//開啟硬件PWMledc_fade_func_install(0);//創建一個事件組,用于通知任務漸變完成s_ledc_ev = xEventGroupCreate();//配置LEDC漸變ledc_set_fade_with_time(LEDC_MODE,LEDC_CHANNEL,LEDC_DUTY,2000);//啟動漸變ledc_fade_start(LEDC_MODE,LEDC_CHANNEL,LEDC_FADE_NO_WAIT);//設置漸變完成回調函數ledc_cbs_t cbs = {.fade_cb=ledc_finish_cb,};ledc_cb_register(LEDC_MODE,LEDC_CHANNEL,&cbs,NULL);xTaskCreatePinnedToCore(ledc_breath_task,"ledc",2048,NULL,3,NULL,1);
}// 主函數
void app_main(void)
{led_breath_init();      //呼吸燈
}

結語

? ? ? ? 該系列會持續更新,后續可能會更新實習用到的技術棧如JSON、OTA、http和UDP等。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/917641.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/917641.shtml
英文地址,請注明出處:http://en.pswp.cn/news/917641.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

鴻蒙 ArkWeb 加載優化方案詳解(2025 最佳實踐)

適用平臺&#xff1a;HarmonyOS NEXT / API 10 關鍵詞&#xff1a;ArkWeb、WebviewController、NodeController、預加載、預連接、預渲染、性能優化一、前言&#xff1a;為什么必須優化 ArkWeb 加載&#xff1f;在鴻蒙生態中&#xff0c;ArkWeb 是系統級的 Web 容器引擎&#x…

JavaScript案例(乘法答題游戲)

項目概述 使用原生JavaScript實現一個乘法答題游戲&#xff0c;隨機生成乘法題目&#xff0c;判斷答案正誤并記錄分數&#xff0c;通過localStorage實現分數持久化存儲。 核心功能需求 隨機題目生成&#xff1a;動態生成1-10之間的乘法題答題交互&#xff1a;輸入答案并提交…

EXCEL刪除數據透視表

wps版 點擊紅框內任意區域 在頂部工具欄選擇刪除Excel 版 1.點擊紅框內任意區域2. 點擊Enable Selection,再按住鍵盤上的Delete鍵&#xff0c;記住不是Backspace鍵

Python 飛機大戰:從零開發經典 2D 射擊游戲

引言&#xff1a;重溫經典游戲開發 飛機大戰作為經典的 2D 射擊游戲&#xff0c;承載了許多人的童年回憶。使用 Python 和 Pygame 開發這樣一款游戲不僅能重溫經典&#xff0c;更是學習游戲開發絕佳的實踐項目。本文將帶你從零開始&#xff0c;一步步實現一個完整的飛機大戰游…

Vue項目中實現瀏覽器串口通信:Web Serial API完整指南

前言 在現代Web開發中&#xff0c;隨著IoT設備和硬件交互需求的增長&#xff0c;瀏覽器與串口設備的通信變得越來越重要。本文將詳細介紹如何在Vue項目中使用Web Serial API實現串口通信功能&#xff0c;為開發者提供一個完整的解決方案。 技術背景 傳統方案的局限性 傳統的串口…

Github怎么只下載某個目錄文件?(Git稀疏檢出、GitZip for Github插件、在線工具DownGit)Github下載目錄

文章目錄**方法一&#xff1a;使用 Git 的稀疏檢出&#xff08;Sparse Checkout&#xff09;**&#xff08;略&#xff09;**步驟&#xff1a;****方法二&#xff1a;使用 SVN 下載特定目錄**&#xff08;略&#xff09;**步驟&#xff1a;****方法三&#xff1a;使用瀏覽器插件…

把“多視圖融合、深度傳感”組合在一起,今天分享3篇3D傳感技術干貨

關注gongzhonghao【計算機sci論文精選】3D傳感技術起源于工業領域高精度測量需求&#xff0c;早期以激光三角測量、結構光等技術為主&#xff0c;主要服務于制造業的零部件檢測與形變分析。隨著消費電子智能化升級&#xff0c;蘋果iPhone X的Face ID將結構光技術推向大眾市場&a…

dubbo源碼之消費端啟動的高性能優化方案

一、序言 dubbo作為一款最流行的服務治理框架之一,在底層做了很多的優化,比如消費端在啟動的時候做了很多性能提升的設計,接下來從連接的層面、序列化功能的層面進行介紹下。 二、優化點 1、消費端在服務啟動的時候會調用DubboProtocol類的protocolBindingRefer方法來創建…

zookeeper常見命令和常見應用

前言 ZooKeeper自帶一個交互式命令行工具&#xff08;通過zkCli.sh或zkCli.cmd啟動&#xff09;&#xff0c;提供了一系列操作ZooKeeper數據節點的命令 下面我們對zookeeper常用命令進行介紹 使用prettyZoo命令行窗口 使用prettyZoo客戶端鏈接zookeeper 打開zookeeper命令…

前端異步任務處理總結

一、異步任務常見場景網絡請求&#xff1a;fetch()、axios 等 API 調用定時操作&#xff1a;setTimeout、setInterval用戶交互&#xff1a;事件監聽回調資源加載&#xff1a;圖片/腳本動態加載Web Workers&#xff1a;后臺線程計算二、核心處理方案1. Promise&#xff08;ES6&a…

機器學習第三課之邏輯回歸(二)LogisticRegression

目錄 簡介 一.分類評估?法 1.混淆矩陣 2.精確率(Precision)與召回率(Recall) 3.F1-score 4.分類評估報告api 2.正則化懲罰 3.?擬合和過擬合 4.K折交叉驗證 5.代碼分析 簡介 接上一篇博客最后 機器學習第二課之邏輯回歸&#xff08;一&#xff09;LogisticRegres…

基于ELK Stack的實時日志分析與智能告警實踐指南

基于ELK Stack的實時日志分析與智能告警實踐指南 一、業務場景描述 在生產環境中&#xff0c;服務實例數量眾多&#xff0c;日志量激增&#xff0c;傳統的文本 grep 或 SSH 登錄方式已無法滿足實時監控與故障定位需求。我們需要搭建一個可擴展、低延遲的日志收集與分析平臺&…

需求變更過程中出現的團隊資源沖突問題處理的一些小技巧

??一、資源沖突的典型場景?? ??技術資源爭奪??:多個需求同時需要同一開發人員或技術專家支持 ??人力資源過載??:突發需求導致團隊成員工作量超負荷(如同時處理3個緊急需求) ??設備/環境沖突??:測試服務器資源不足或特定開發工具許可證被占用 ??跨團隊協…

基于Matlab圖像處理的液晶顯示器表面缺陷檢測與分類研究

本課題設計并實現了一種基于 MATLAB 的圖像缺陷檢測系統&#xff0c;系統集成中值濾波、對比度增強、梯度檢測與區域分析等圖像處理技術&#xff0c;能夠對圖像中的點狀、線狀和塊狀缺陷進行有效識別與分類。用戶可通過圖形用戶界面&#xff08;GUI&#xff09;導入待測圖像&am…

prometheus應用demo(一)接口監控

目錄 完整代碼&#xff08;純Cursor生成&#xff09; 1、pom 2、配置和啟動類 3、自定義指標bean 4、上報 5、業務代碼 一、統計API請求&#xff08;次數、響應碼等&#xff09; 1、統計總數 關鍵代碼&#xff1a; &#xff08;1&#xff09;自定義指標DTO &#xff0…

逃離智能家居“孤島”!用 Home Assistant 打造你的全屋互聯自由王國

文章目錄&#x1f914; 痛點暴擊&#xff1a;智能家居的“巴別塔困境”&#x1f6e0;? Home Assistant 是個啥&#xff1f;簡單粗暴版定義&#x1f50d; 硬核拆解&#xff1a;Home Assistant 的魅力之源&#x1f680; 上車指南&#xff1a;如何開始你的 HA 之旅&#xff1f;第…

數據結構:如何判斷一個鏈表中是否存在環(Check for LOOP in Linked List)

目錄 初始思考&#xff1a;什么叫“鏈表有環”&#xff1f; ? 第一種直接想法&#xff08;失敗&#xff09;&#xff1a;我們是不是能“記住走過的節點”&#xff1f; 那我們換一個思路&#xff1a;我們能否只用兩個指針來檢測環&#xff1f; 第一步&#xff1a;定義兩個指…

深入理解Java的SPI機制,使用auto-service庫優化SPI

文章目錄一、簡介二、使用1、服務提供者&#xff08;或者第三方公共&#xff09;&#xff1a;定義接口2、服務提供者&#xff1a;定義實現類3、服務提供者&#xff1a;注冊服務4、構建服務提供者jar包5、客戶端&#xff1a;使用 ServiceLoader 來加載服務三、源碼分析1、源碼2、…

PPT自動化 python-pptx - 10 : 表格(tables)

在日常工作中&#xff0c;我們經常需要制作包含表格的 PowerPoint 演示文稿&#xff0c;以此清晰展示數據或文本信息。手動制作不僅耗時&#xff0c;當數據更新時還需重復操作&#xff0c;效率低下。而 python-pptx 庫為我們提供了自動化操作 PowerPoint 表格的可能。本文將詳細…

在安卓中使用 FFmpegKit 剪切視頻并添加文字水印

在安卓中用到的三方庫&#xff1a;https://github.com/arthenica/ffmpeg-kit 這個庫很強大&#xff0c;支持很多平臺&#xff0c;每個平臺都有各自的分支代碼&#xff0c;用了一段時間&#xff0c;穩定性挺好的&#xff0c; 找到安卓下的分支&#xff1a;FFmpegKit for Andro…