《超圖解ESP32應用實作》採用ESP32 Arduino開發平台2.0版(基於ESP-IDF 4.4),以Arduino IDE 1.8.19為例,請在「開發板管理員」中搜尋“esp32”,並且安裝2.x版,筆者安裝的是2.0.17版。
2024年五月底推出的ESP32 Arduino開發平台3.0版(基於ESP-IDF 5.1),修改了一些語法,相關說明請參閱樂鑫官方的“Migration from 2.x to 3.0”(從2.x版遷移到3.0版)文件,本文將重點說明其中與PWM訊號有關的部分以及程式範例。
ESP32 Arduino開發平台3.x版的PWM訊號輸出指令改動,主要是把「通道(channel)」設置變成自動,並且刪除原先的ledcSetup()和ledcAttachPin()函式,改成ledcAttach()或ledcAttachChannel()函式。
ledcAttach()函式用於設置PWM輸出的接腳、頻率和解析度,並自動選擇LEDC 通道,其原型如下。若設置成功,則傳回true;如果傳回false,代表發生錯誤且未配置LEDC 通道。
bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution);
函式參數說明:
- pin:PWM輸出的腳位
- freq:PWM頻率
- resolution:PWM的解析度,有效範圍為1~14位元(ESP32-S3為1~20 位元)。
ledcAttachChannel()函式的作用跟上面的函式相同,只是多了設置通道的“channel”參數,其原型如下:
bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, int8_t channel);
輸出PWM訊號的函式同樣是ledcWrite(),但第一個參數改成“pin”(接腳編號),其原型如下。若設定PWM成功,函式將傳回 true;如果傳回false,則代表發生錯誤且未設定工作週期(佔空比)。
bool ledcWrite(uint8_t pin, uint32_t duty);
為了提升跟其他非ESP32晶片的Arduino程式相容性,ESP32 Arduino開發平台也新增analogWrite()函式,不過,從函式的原始碼(位於esp32-hal-ledc.c)可看出,analogWrite()函式其實在內部呼叫了ledcWrite(),而且呼叫analogWrite()函式之前,仍要先執行ledcAttach()或ledcAttachChannel()設置PWM,所以在ESP32平台,沒有必要使用analogWrite()函式。
呼吸燈效果範例程式一
底下是採用ledcAttach()設置LED PWM值的呼吸燈效果程式,編譯執行此範例程式,需要把ESP32 Arduino開發平台升級到3.x版。
#define LED_PIN 5 // LED腳位 #define MAX_BRIGHTNESS 255 // 亮度上限 #define PWM_RES 12 // 12位元解析度 #define PWM_FREQ 5000 // PWM頻率500Hz #define MAXDUTY ((1 << PWM_RES) - 1) // 工作週期上限 #define FADE_DELAY 30 // 變化間隔時間 unsigned long lastTime = 0; // 淡入淡出LED效果(呼吸燈) void fadeLED() { static int brightness = 0; // 亮度值 static int fadeAmount = 5; // 每次的亮度變化量 int duty = (MAXDUTY / MAX_BRIGHTNESS) * min(brightness, MAX_BRIGHTNESS); ledcWrite(LED_PIN, duty); // 上面這一行可以改成: // analogWrite(LED_PIN, duty); brightness += fadeAmount; // 調整亮度 // 完成淡入或淡出效果後,反轉亮度變化量。 if (brightness <= 0 || brightness >= MAX_BRIGHTNESS) { fadeAmount = -fadeAmount; } } void setup() { // 設定PWM輸出腳位、頻率和解析度 ledcAttach(LED_PIN, PWM_FREQ, PWM_RES); } void loop() { unsigned long now = millis(); if (now - lastTime >= FADE_DELAY) { lastTime = now; fadeLED(); } }
這個程式指定LEDC採用12位元解析度,因此工作週期(duty,佔空比)最大值為212 -1 = 4095;MAXDUTY透過左移運算子(<<)計算出指定解析度的最大工作週期值;亮度上限255,代表亮度有255個層次變化。
呼吸燈效果範例程式二
ESP32 Arduino開發平台3.0版內建呼吸燈效果的ledcFade()函式,傳入LED腳位、起始工作週期值、目標工作週期值和變化時間,即可產生淡入(fade in,從低亮度逐漸調升到高亮度)或淡出(fade out,從高亮度逐漸降低為低亮度)效果,此函式原型如下:
bool ledcFade(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms);
參數說明:
- pin:LED腳位
- start_duty:淡入淡出變化的起始工作週期。
- target_duty:淡入淡出的目標週期。
- max_fade_time_ms:淡入淡出的變化時間。
若配置成功,函式將傳回true;如果傳回 false,代表發生錯誤且未配置淡入淡出效果。
底下是採用ledcFade()函式編寫的呼吸燈效果,loop()將每隔3000毫秒交替「淡入」和「淡出」變化。
#define LED_PIN 5 // LED腳位 #define PWM_RES 12 // PWM解析度:12位元 #define PWM_FREQ 5000 // PWM頻率:5KHz #define START_DUTY 0 // 起始工作週期 #define TARGET_DUTY 4095 // 目標工作週期 #define FADE_TIME 3000 // 變化時間 unsigned long lastTime = 0; bool fadeIn = true; void setup() { // 設定LED腳位、頻率和解析度,自動選擇PWM通道。 ledcAttach(LED_PIN, PWM_FREQ, PWM_RES); // 設定淡出淡入變化的LED腳位、起始值、結束值、變化時間 // 燈光的PWM值從0淡入變化到4095 ledcFade(LED_PIN, START_DUTY, TARGET_DUTY, FADE_TIME); delay(FADE_TIME); // 等到淡入結束 fadeIn = false; } void loop() { unsigned long now = millis(); if (now - lastTime >= FADE_TIME) { lastTime = now; if (fadeIn) { // 設定淡出淡入變化的LED腳位、起始值、結束值、變化時間 // 燈光的PWM值從0淡入變化到4095 ledcFade(LED_PIN, START_DUTY, TARGET_DUTY, FADE_TIME); fadeIn = false; } else { // 設定淡出淡入變化的LED腳位、起始值、結束值、變化時間 // 燈光的PWM值從0淡入變化到4095 ledcFade(LED_PIN, TARGET_DUTY, START_DUTY, FADE_TIME); fadeIn = true; } } }
使用中斷常式的呼吸燈效果
ledcFadeWithInterrupt()同樣是執行「淡入淡出效果」的函式,但是透過中斷告知「變化完成」,函式原型如下:
bool ledcFadeWithInterrupt(uint8_t pin, uint32_t start_duty, uint32_t target_duty, int max_fade_time_ms, void (*userFunc)(void));
參數說明:
- pin:LED腳位
- start_duty:淡入淡出變化的起始工作週期。
- target_duty:淡入淡出的目標週期。
- max_fade_time_ms:淡入淡出的變化時間。
- 觸發中斷時呼叫的處理函式
若配置成功,函式將傳回true;如果傳回 false,代表發生錯誤且未配置淡入淡出效果。
底下是採用ledcFadeWithInterrupt ()函式編寫的呼吸燈效果,每隔3000毫秒交替「淡入」和「淡出」變化。
#define LED_PIN 5 // LED腳位 #define PWM_RES 12 // PWM解析度:12位元 #define PWM_FREQ 5000 // PWM頻率:5KHz #define START_DUTY 0 // 起始工作週期 #define TARGET_DUTY 4095 // 目標工作週期 #define FADE_TIME 3000 // 變化時間 volatile bool fadeEnded = false; // 表示LED變化結束與否 bool fadeOut = false; // 是否為「淡出」狀態 // 中斷處理常式 void ARDUINO_ISR_ATTR FADE_ISR() { fadeEnded = true; // 變化結束 } void setup() { // 設定LED腳位、頻率和解析度,自動選擇PWM通道。 ledcAttach(LED_PIN, PWM_FREQ, PWM_RES); // 使用中斷設置LED淡入效果 // 燈光的PWM值從0淡出變化到4095 ledcFadeWithInterrupt(LED_PIN, START_DUTY, TARGET_DUTY, FADE_TIME, FADE_ISR); } void loop() { if (fadeEnded) { // 若「LED變化效果」結束… fadeEnded = false; if (fadeOut) { // 若上次是「淡出」效果… // 開始進行「淡入」效果 ledcFadeWithInterrupt(LED_PIN, START_DUTY, TARGET_DUTY, FADE_TIME, FADE_ISR); fadeOut = false; } else { // 開始進行「淡出」效果 ledcFadeWithInterrupt(LED_PIN, TARGET_DUTY, START_DUTY, FADE_TIME, FADE_ISR); fadeOut = true; } } }