目錄
一、前言
二、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);
? ? ? ? 該函數的形參描述如下表所示:
參數 | 描述 |
---|---|
pGPIOConfig | GPIO結構體 |
? ? ? ? 返回值: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_num | GPIO 引腳號。(在 gpio_types.h 文件中枚舉 gpio_num_t 有定義) |
level | GPIO 引腳輸出電平。0:低電平,1:高電平 |
? ? ? ? 返回值:ESP_OK 表示設置成功,ESP_FAIL 表示設置失敗。
● 獲取管腳電平
????????該函數用于獲取某個管腳的電平,該函數原型如下所示:
int gpio_get_level(gpio_num_t gpio_num);
????????該函數的形參描述如下:
參數 | 描述 |
---|---|
gpio_num | GPIO 引腳號。(在 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上) |
.duty_resolution | LEDC_TIMER_X_BIT(X=1~14) | |
.timer_num PWM通道的定時器源,由 ledc_timer_t 枚舉類型定義。 | LEDC_TIMER_0 | |
.freq_hz | uint32_t大小的值 | |
.clk_cfg | LEDC_AUTO_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) | |
.intr_type 中斷配置 | LEDC_INTR_DISABLE 使能漸變結束中斷 (用于檢查模式有效性,不可作為實際的模式配置) | |
.timer_sel 選擇通道的定時器源。由?ledc_timer_t 枚舉類型定義,和之前配置定時器一樣 | LEDC_TIMER_0 LEDC_TIMER_1 LEDC_TIMER_2 LEDC_TIMER_3 LEDC_TIMER_MAX (同樣用于檢查模式有效性,不可作為實際的模式配置) | |
.duty | 范圍為[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 模式上限(用于檢查模式有效性,不可作為實際的模式配置) |
channel | LEDC通道: (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_mode | LEDC_HIGH_SPEED_MODE(僅存在于ESP32上) 高速模式 LEDC_LOW_SPEED_MODE 低速模式 LEDC_SPEED_MODE_MAX 模式上限(用于檢查模式有效性,不可作為實際的模式配置) |
channel | LEDC通道: (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); |
*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等。