1. 程式人生 > 其它 >啟明智顯分享|ESP32學習筆記參考--LED PWM控制器與MCPWM的配置

啟明智顯分享|ESP32學習筆記參考--LED PWM控制器與MCPWM的配置

LED 控制器 (LEDC) 主要用於控制 LED,也可產生 PWM 訊號用於其他裝置的控制。 該控制器有 16 路通道,可以產生獨立的波形來驅動 RGB LED 等裝置。

LEDC 通道共有兩組,分別為 8 路高速通道和 8 路低速通道。高速通道模式在硬體中實現,可以自動且無干擾地改變 PWM 佔空比。低速通道模式下,PWM 佔空比需要由軟體中的驅動器改變。每組通道都可以使用不同的時鐘源。

LED PWM 控制器可在無需 CPU 干預的情況下自動改變佔空比,實現亮度和顏色漸變。

[參考](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html)

設定 LEDC 通道在 [高速模式或低速模式](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-high-low-speed-mode) 下執行,需要進行如下配置:

1. [定時器配置](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-configure-timer) 指定 PWM 訊號的頻率和佔空比解析度。
2. [通道配置](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-configure-channel) 繫結定時器和輸出 PWM 訊號的 GPIO。
3. [改變 PWM 訊號](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-change-pwm-signal) 輸出 PWM 訊號來驅動 LED。可通過軟體控制或使用硬體漸變功能來改變 LED 的亮度。

另一個可選步驟是可以在漸變終端設定一箇中斷。

在解釋ESP32上的PWM功能之前,讓我們討論一些與PWM相關的術語。

● **TON(導通時間)**:訊號為高電平時的持續時間。

● **TOFF(關斷時間)**:訊號為低電平時的持續時間。

● **週期**:PWM訊號的導通時間和關斷時間之和。

● **佔空比**:PWM訊號週期內訊號為高電平的時間百分比。

​ 例如,如果總週期為10ms的脈衝保持 ON(高)5ms。然後,佔空比將為:佔空比 = 5/10 * 100 = 50%

● **PWM的頻率:**PWM訊號的頻率決定了PWM完成一個週期的速度。一個週期是一個PWM訊號的完整ON和OFF,如上圖所示。

### 2、配置定時器

要設定定時器,可呼叫函式 [`ledc_timer_config()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv417ledc_timer_configPK19ledc_timer_config_t),並將包括如下配置引數的資料結構 [`ledc_timer_config_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv419ledc_timer_config_t) 傳遞給該函式:

- 速度模式 [`ledc_mode_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv411ledc_mode_t)
- 定時器索引 [`ledc_timer_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv412ledc_timer_t)
- PWM 訊號頻率
- PWM 佔空比解析度
- 時鐘源 [`ledc_clk_cfg_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv414ledc_clk_cfg_t)

頻率和佔空比解析度相互關聯。PWM 頻率越高,佔空比解析度越低,反之亦然。如果 API 不是用來改變 LED 亮度,而是用於其它目的,這種相互關係可能會很重要。更多資訊詳見 [頻率和佔空比解析度支援範圍](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#ledc-api-supported-range-frequency-duty-resolution) 一節。

時鐘源同樣可以限制PWM頻率。選擇的時鐘源頻率越高,可以配置的PWM頻率上限就越高。

時鐘名稱  時鐘頻率 速度模式 時鐘功能
APB_CLK 80 MHz 高速 / 低速
REF_TICK    1 MHz 高速 / 低速 支援動態調頻(DFS)功能 
RTC8M_CLK ~8 MHz   低速 支援動態調頻(DFS)功能,支援Light-sleep模式

**通道配置**

定時器設定好後,請配置所需的通道([`ledc_channel_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv414ledc_channel_t) 之一)。配置通道需呼叫函式 [`ledc_channel_config()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv419ledc_channel_configPK21ledc_channel_config_t)。

通道的配置與定時器設定類似,需向通道配置函式傳遞包括通道配置引數的結構體 [`ledc_channel_config_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv421ledc_channel_config_t) 。

此時,通道會按照 [`ledc_channel_config_t`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv421ledc_channel_config_t) 的配置開始運作,並在選定的 GPIO 上生成由定時器設定指定的頻率和佔空比的 PWM 訊號。在通道運作過程中,可以隨時通過呼叫函式 [`ledc_stop()`](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/peripherals/ledc.html#_CPPv49ledc_stop11ledc_mode_t14ledc_channel_t8uint32_t) 將其暫停。

### 3、示例

```c
//1. ESP32 下面是一個呼吸燈的整理,可以直接使用,歡迎參考。
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_err.h"
#define LEDC_HS_TIMER LEDC_TIMER_0
#define LEDC_HS_MODE LEDC_HIGH_SPEED_MODE
#define LEDC_HS_CH0_CHANNEL LEDC_CHANNEL_0
#define LEDC_HS_CH0_GPIO (2)
#define LEDC_TEST_DUTY (4000)
#define LEDC_TEST_FADE_TIME (3000)
void app_main(void)
{
//1. PWM: 定時器配置

ledc_timer_config_t ledc_timer = {
.duty_resolution = LEDC_TIMER_13_BIT, // resolution of PWM duty
.freq_hz = 5000, // frequency of PWM signal
.speed_mode = LEDC_HS_MODE, // timer mode
.timer_num = LEDC_HS_TIMER, // timer index
.clk_cfg = LEDC_AUTO_CLK, // Auto select the source clock
};

ledc_timer_config(&ledc_timer);
//2. PWM:通道配置
ledc_channel_config_t ledc_channel= {
.channel = LEDC_HS_CH0_CHANNEL,
.duty = 0,
.gpio_num = LEDC_HS_CH0_GPIO, //這裡是SDK帶的呼吸燈案例,你看下能看懂不,我現在給你修改個引腳,你的燈就用上了, 2表示你板子上的那個燈 2引腳的那個
.speed_mode = LEDC_HS_MODE,
.hpoint = 0,
.timer_sel = LEDC_HS_TIMER
};

ledc_channel_config(&ledc_channel);
//3.PWM:使用硬體漸變
ledc_fade_func_install(0);
//4. 輸出PWM訊號控制燈

while (1) {
//燈:由滅慢慢變亮
printf("1. LEDC fade up to duty = %d\n", LEDC_TEST_DUTY);
ledc_set_fade_with_time(ledc_channel.speed_mode,
ledc_channel.channel, LEDC_TEST_DUTY, LEDC_TEST_FADE_TIME);
ledc_fade_start(ledc_channel.speed_mode,
ledc_channel.channel, LEDC_FADE_NO_WAIT);
vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS);
//燈:由亮慢慢變滅
printf("2. LEDC fade down to duty = 0\n");
ledc_set_fade_with_time(ledc_channel.speed_mode,
ledc_channel.channel, 0, LEDC_TEST_FADE_TIME);
ledc_fade_start(ledc_channel.speed_mode,
ledc_channel.channel, LEDC_FADE_NO_WAIT);
vTaskDelay(LEDC_TEST_FADE_TIME / portTICK_PERIOD_MS);
}
}
```

## 四、Motor Control Pulse Width Modulator(MCPWM)

### 1、簡介

ESP32有兩個MCPWM單元,可用於控制不同型別的電機。每個單元有三對PWM輸出。

**作用**:

-PWM輸出

- 每個單元還能夠收集諸如同步訊號等輸入,檢測電機過電流或過電壓等故障訊號,
- 以及在例如轉子位置上獲得捕獲訊號的反饋。

每個A/B對可由三個定時器中的任何一個MCPWM定時器0、1和2中的任何一個時鐘。(相同的定時器可用於時鐘多對PWM輸出)

從上圖我們不難發現,MCPWM具有的功能(上圖彩色的虛線框)有:

- `OPERATOR `操作器模組
- `CAPTURE `狀態捕獲模組
- `FAULT DETECT`故障處理器模組
- `CLOCK/TIMER`時鐘、時鐘預分頻器模組
(黑色虛線框指的是GPIO矩陣 (GPIO Matrix))

**1、操作器模組 Operator**

**操作員 (Operator) 用於操作連線到MCPWM單元的電機。**例如改變旋轉方向(順時針或逆時針),或改變轉速。

​ 操作員輸出一共有 3 對,我們可以對其施加控制訊號。標記為“A”和“B”的稱為一對。A、B均有自己對應的名為“Generator”的子模組來驅動諸如PWM的輸出訊號。

​ 為了提供PWM訊號,每個Operator本身由三個可用的定時器(MCPWM Timer)中的任何一個進行計時。

​ 為了簡化API,API會 自動關聯 具有相同索引`Timer`以驅動`Operator`。例如`Timer 0`與`Operator 0`關聯。

**2、捕獲模組 Capture**

捕獲模組在功能上相當於由沿中斷控制的捕獲定時器

​ 對於無刷直流電機,控制的要求之一是感應轉子位置。

​ 為了完成這一任務,每個 MCPWM單元提供三個感測輸入以及專用的硬體。該硬體能夠檢測輸入訊號的邊緣,並測量訊號之間的時間。

​ 因此,控制軟體更簡單,CPU功率可能用於其他任務。

注意:3個Capture可以在不使用PWM輸出時**單獨使用**,即只配置Capture實現邊緣捕獲功能。**因此MCPWM還可用於非電機外設**。例如,使用MCPWM的Capture0去捕獲**HC-SR04**超聲波模組ECHO引腳的高電平時間,進而實現測距。

**3、故障處理器模組 Fault Detect**
MCPWM的每個單元都能夠感知外部訊號,包括有關電機、電機驅動器或連線到MCPWM的任何其他裝置的故障資訊。每個單元有三個錯誤輸入,可以路由到使用者可選擇的GPIO。當接收到故障訊號時,MCPWM可以配置為對A/B輸出執行四種預定義的動作之一:

- 鎖定輸出的當前狀態

- 設定低輸出

- 設定高輸出

- 開關輸出

使用者應確定電機可能的故障模式以及在檢測到特定故障時應採取的行動。

例如:對有刷電機驅動所有輸出為低,或對步進電機鎖定電流狀態等。這個動作會使電機處於安全狀態,以減少故障造成的損壞的可能性。

**4、載波 Carrier和中斷 Interrupts**
MCPWM有一個載波子模組,如果使用互感原理(如通過變壓器)向電機驅動傳遞A/B輸出訊號(例如需要讓電機驅動器輸入電流與ESP32 GPIO輸出電流相互隔離)。任何A和B輸出訊號都可以100%佔空,並且當電機在滿載時需要穩定執行時,訊號不會改變。

通過呼叫`mcpwm_isr_register()`可以註冊MCPWM中斷處理程式。
注意,如果使用了`mcpwm_capture_enable_channel()`,那麼將安裝一個預設的ISR例程來實現簡化API的回撥。因此,如果使用了`mcpwm_capture_enable_channel()`,請不要再呼叫mcpwm_isr_register()這個函式來註冊中斷。

### 2、使用 MCPWM 輸出 PWM 訊號

#### 2.1 建立MCPWM

初始化MCPWM需要的步驟:

- 配置GPIO口

- 在一個`mcpwm_config_t`結構體中設定定時器頻率和初始任務的設定。

- 非必須:設定定時器解析度(預設為10,000,000)。使用函式`mcpwm_group_set_resolution() `和 `mcpwm_timer_set_resolution()`
- 使用上述引數呼叫mcpwm_init()以使配置生效。

1,配置GPIO口:使用函式mcpwm_gpio_init()或函式mcpwm_set_pin()。

兩者的區別是前者為指定的功能配置 GPIO,而後者是一次性配置所有的GPIO。

**mcpwm_gpio_init()** //初始化一個GPIO

```c
esp_err_t mcpwm_gpio_init(mcpwm_unit_t mcpwm_num, mcpwm_io_signals_t io_signal, int gpio_num)
//mcpwm_num型別為:mcpwm_unit_t;MCPWM單元
//io_signal型別為:mcpwm_io_signals_t;MCPWM功能signal,如MCPWM0A表示某MCPWM的A輸出
//gpio_num型別為:int;表示想要配置為哪個GPIO
```

**mcpwm_set_pin()** //配置所有與MCPWM有關的GPIO

```c
esp_err_t mcpwm_set_pin(mcpwm_unit_t mcpwm_num, const mcpwm_pin_config_t *mcpwm_pin)
//mcpwm_num型別為:mcpwm_unit_t;表示MCPWM單元索引
//*mcpwm_pin型別為:mcpwm_pin_config_t指標;表示一個結構體,包含所有與MCPWM功能對於的GPIO
```

2,配置mcpwm引數

通過函式`mcpwm_init()`,傳遞一個`mcpwm_config_t`結構體指標

**mcpwm_init()**

```c
esp_err_t mcpwm_init(mcpwm_unit_t mcpwm_num,
mcpwm_timer_t timer_num,
const mcpwm_config_t *mcpwm_conf)
//mcpwm_num型別為:mcpwm_unit_t;表示MCPWM索引
//timer_num型別為:mcpwm_timer_t;表示初始化哪個MCPWM定時器,對應與其相同索引的Operator
//*mcpwm_conf型別為:const mcpwm_config_t;表示配置結構體指標

typedef struct {
uint32_t frequency;//頻率
float cmpr_a;//A輸出的佔空比
float cmpr_b;//B輸出的佔空比
mcpwm_duty_type_t duty_mode;//佔空比模式 (對應高還是低)
mcpwm_counter_type_t counter_mode;//定時器計數方向
}

//示例
mcpwm_config_t mcpwmConfig = {
.frequency = 1000,
.cmpr_a = 0,
.cmpr_b = 0,
.counter_mode = MCPWM_UP_COUNTER,
.duty_mode = MCPWM_DUTY_MODE_0,
};
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &mcpwmConfig);
```

#### 2.2  PWM訊號控制

- **1、設定為全速(非PWM訊號)**


我們可以使用`mcpwm_set_signal_high()`或`mcpwm_set_signal_low()`函式來驅動特定的訊號穩定為高或低。這將使電機以最大速度旋轉或停止。

**mcpwm_set_signal_high(或low) ()** // 設定MCPWM的某個generator訊號為高[或低]

```c
esp_err_t mcpwm_set_signal_high(mcpwm_unit_t mcpwm_num,
mcpwm_timer_t timer_num,
mcpwm_generator_t gen)
//mcpwm_num型別為:mcpwm_unit_t;表示MCPWM單元
//timer_num型別為:mcpwm_timer_t;表示哪組MCPWM Operator
//gen型別為:mcpwm_generator_t;表示對應的A還是B
```

- **2、設定PWM訊號**


若要更改PWM的佔空比,呼叫`mcpwm_set_duty()`並以%為單位提供佔空比的百分數值。如果您希望以微秒為單位設定任務,則可以選擇呼叫`mcpwm_set_duty_in_us()`。可以通過呼叫`mcpwm_set_duty_type()`來改變PWM佔空比的模式(佔空比數值對應高還是對應低)。

**mcpwm_set_duty (in_us) ()** //設定佔空比

```c
esp_err_t mcpwm_set_duty(mcpwm_unit_t mcpwm_num,
mcpwm_timer_t timer_num,
mcpwm_generator_t gen,
float duty)
//mcpwm_num型別為:mcpwm_unit_t;表示MCPWM單元
//timer_num型別為:mcpwm_timer_t;表示哪組MCPWM輸出
//gen型別為:mcpwm_generator_t;表示A輸出還是B輸出
//duty[_in_us]型別為:float;表示佔空比百分數%[或微秒]
```

**mcpwm_set_duty_type()** //設定佔空比型別,並恢復PWM輸出

```c
esp_err_t mcpwm_set_duty_type(mcpwm_unit_t mcpwm_num,
mcpwm_timer_t timer_num,
mcpwm_generator_t gen,
mcpwm_duty_type_t duty_type)
//mcpwm_num型別為:mcpwm_unit_t;表示MCPWM單元
//timer_num型別為:mcpwm_timer_t;表示哪組MCPWM輸出
//gen型別為:mcpwm_generator_t;表示A輸出還是B輸出
//duty_type型別為:mcpwm_duty_type_t;表示佔空比
```

- **3、啟動輸出**


通過呼叫`mcpwm_start()`或`mcpwm_stop()`來驅動PWM訊號的輸出。 當使用`mcpwm_init()`後,ESP32 會自動呼叫`mcpwm_start()`啟動電機

**mcpwm_start(或stop)()** // 啟動[或關閉]MCPWM輸出

```c
esp_err_t mcpwm_start(mcpwm_unit_t mcpwm_num,
//mcpwm_num型別為:mcpwm_unit_t;表示MCPWM單元
mcpwm_timer_t timer_num)
//timer_num型別為:mcpwm_timer_t;表示哪組MCPWM
```

### 3、示例

使用mcpwm驅動直流電機

```c
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/mcpwm.h"

#define GPIO_PWM0A_OUT 15 //設定 GPIO 15 作為 PWM0A
#define GPIO_PWM0B_OUT 16 //設定 GPIO 16 作為 PWM0B

//---------電機向前移動
static void brushed_motor_forward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, float duty_cycle)
{
mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B);
mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_A, duty_cycle);
mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_A, MCPWM_DUTY_MODE_1);
}

//--------- 電機向後移動
static void brushed_motor_backward(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num, float duty_cycle)
{
mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A);
//設定MCPWM的某個generator訊號為高[或低]
mcpwm_set_duty(mcpwm_num, timer_num, MCPWM_OPR_B, duty_cycle);
//設定佔空比
//mcpwm_num型別為:mcpwm_unit_t;表示MCPWM單元
//timer_num型別為:mcpwm_timer_t;表示哪組MCPWM輸出
//gen型別為:mcpwm_generator_t;表示A輸出還是B輸出
//duty[_in_us]型別為:float;表示佔空比百分數%[或微秒]

mcpwm_set_duty_type(mcpwm_num, timer_num, MCPWM_OPR_B, MCPWM_DUTY_MODE_1);
// 設定佔空比型別,並恢復PWM輸出
}

//---------電機停止
static void brushed_motor_stop(mcpwm_unit_t mcpwm_num, mcpwm_timer_t timer_num)
{
mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_A);
mcpwm_set_signal_low(mcpwm_num, timer_num, MCPWM_OPR_B);
}

//---------為直流電機配置MCPWM
static void mcpwm_task(void *arg)
{
//1. mcpwm gpio 初始化
printf("initializing mcpwm gpio!\n");
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0A, GPIO_PWM0A_OUT); //配置GPIO口
mcpwm_gpio_init(MCPWM_UNIT_0, MCPWM0B, GPIO_PWM0B_OUT);

//2. 初始化 mcpwm 配置
printf("Setting mcpwm!\n");
mcpwm_config_t pwm_config = {
.frequency = 1000, //頻率 = 500Hz,
.cmpr_a = 0, // PWMxA佔空比 = 0
.cmpr_b = 0, // PWMxB佔空比 = 0
.duty_mode = MCPWM_DUTY_MODE_0, //佔空比模式 (對應高還是低)
.counter_mode = MCPWM_UP_COUNTER, //定時器計數方向
};
mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);

while (1)
{
brushed_motor_forward(MCPWM_UNIT_0, MCPWM_TIMER_0, 50.0);
printf("正轉\n");
vTaskDelay(2000 / portTICK_RATE_MS);
brushed_motor_backward(MCPWM_UNIT_0, MCPWM_TIMER_0, 30.0);
printf("逆轉\n");
vTaskDelay(2000 / portTICK_RATE_MS);
brushed_motor_stop(MCPWM_UNIT_0, MCPWM_TIMER_0);
printf("停\n");
vTaskDelay(2000 / portTICK_RATE_MS);
}
}

void app_main(void)
{
printf("Testing motor !\n");
xTaskCreate(mcpwm_task, "Mcpwm_task", 4096, NULL, 5, NULL);
}
```