引言

在微控制器编程中,PWM(脉冲宽度调制)是一种重要的技术,它可以用于模拟模拟信号,控制LED亮度,驱动电机,以及生成音频信号等。ESP32是内置了一个高级LEDC(LED PWM Controller)硬件,用于产生高精度的PWM信号。本文将详细介绍ESP32的LEDC功能,包括其工作原理,编程方法,以及应用实例。

LEDC基础

LEDC是ESP32特有的一个硬件PWM控制模块。与传统的PWM不同,LEDC提供更高的分辨率,更多的通道,以及更灵活的频率控制。ESP32支持高速模式和低速模式两种LEDC运行模式,分别有8个独立的通道。

LEDC的特点

  • 独立通道:ESP32提供高速模式和低速模式,每种模式8个通道。
  • 高分辨率:支持高达14位的分辨率,即16384个不同的占空比水平。
  • 灵活的频率调节:频率范围从几赫兹到几十兆赫兹。
  • 波形生成:可用于生成复杂的波形序列。

LEDC的工作模式

在ESP-IDF开发框架中,LEDC模块可以工作在两种模式下:

  • 高速模式:高速模式通常用于需要高频PWM信号的应用,如LED照明。在这种模式下,PWM信号的频率可以非常高,可以达到数十甚至数百千赫兹(kHz)。这对于LED照明是非常理想的,因为高频率确保了人眼无法察觉到任何闪烁,从而提供平滑的光线调整和更好的视觉体验。在高速模式下,LEDC模块使用ESP32的高速定时器。这些定时器的时钟来源于APB_CLK,这是一个较高的系统时钟频率,由此可以生成较高频率的PWM信号。
    例子:初始化一个LEDC通道在高速模式
    ledc_timer_config_t ledc_timer = {
      .duty_resolution = LEDC_TIMER_13_BIT, // 分辨率
      .freq_hz = 5000,                      // 频率
      .speed_mode = LEDC_HIGH_SPEED_MODE,   // 高速模式
      .timer_num = LEDC_TIMER_0             // 定时器
    };
    ledc_timer_config(&ledc_timer);
    ledc_channel_config_t ledc_channel = {
      .channel    = LEDC_CHANNEL_0,
      .duty       = 0,
      .gpio_num   = 18,
      .speed_mode = LEDC_HIGH_SPEED_MODE,
      .timer_sel  = LEDC_TIMER_0
    };
    ledc_channel_config(&ledc_channel);
    
  • 低速模式:低速模式适用于不需要很高频率PWM信号的场合,例如在电机控制等应用中。在这种模式下,PWM信号的频率较低,可以是几赫兹到几千赫兹。对于许多类型的电机控制,这种较低的频率是足够的,且有助于降低系统的功耗。在低速模式下,LEDC模块使用ESP32的低速定时器。这些定时器可以选择APB_CLK或RTC_CLK(来自RTC模块的慢速时钟)作为时钟源。使用RTC_CLK时,甚至在深度睡眠模式下,PWM也可以继续运作,对于低功耗场景非常有用。
    #include "driver/ledc.h"
    // 定义LED连接的GPIO引脚
    #define LED_PIN 2
    // 初始化LEDC定时器的配置结构体
    ledc_timer_config_t ledc_timer = {
      .speed_mode = LEDC_LOW_SPEED_MODE, // 低速模式
      .duty_resolution = LEDC_TIMER_13_BIT, // 分辨率为13位
      .timer_num = LEDC_TIMER_0, // 定时器编号
      .freq_hz = 1000, // PWM信号的频率, 例如1000 Hz
      .clk_cfg = LEDC_AUTO_CLK, // 自动选择时钟源
    };
    // 初始化LEDC通道的配置结构体
    ledc_channel_config_t ledc_channel = {
      .gpio_num   = LED_PIN,
      .speed_mode = LEDC_LOW_SPEED_MODE,
      .channel    = LEDC_CHANNEL_0,
      .timer_sel  = LEDC_TIMER_0,
      .duty       = 0, // 初始占空比为0
      .hpoint     = 0
    };
    // 初始化LEDC定时器
    ledc_timer_config(&ledc_timer);
    // 初始化LEDC通道
    ledc_channel_config(&ledc_channel);
    // 设置通道的占空比到一定的值,例如50%
    ledc_set_duty(ledc_channel.speed_mode, ledc_channel.channel, 1 << (ledc_timer.duty_resolution - 1));
    ledc_update_duty(ledc_channel.speed_mode, ledc_channel.channel);
    

1. ESP-IDF中的LEDC使用步骤:

ESP-IDF为LEDC提供了一系列的API函数,用于配置和控制LEDC。以下是使用LEDC进行编程的基本步骤:

2. 配置LEDC

使用LEDC之前,需要配置LEDC通道和定时器。每个通道都必须关联一个定时器,定时器定义了PWM信号的频率和分辨率。

ledc_timer_config_t ledc_timer = {
    .duty_resolution = LEDC_TIMER_8_BIT, // 分辨率
    .freq_hz = 5000,                     // 频率
    .speed_mode = LEDC_HIGH_SPEED_MODE,   // 速度模式
    .timer_num = LEDC_TIMER_0            // 定时器编号
};

ledc_timer_config(&ledc_timer);

3. 配置LEDC通道

每个LEDC通道可以连接到一个GPIO引脚,并且可以独立控制PWM信号的占空比。

ledc_channel_config_t ledc_channel = {
    .channel    = LEDC_CHANNEL_0,
    .duty       = 0,
    .gpio_num   = 18,
    .speed_mode = LEDC_HIGH_SPEED_MODE,
    .timer_sel  = LEDC_TIMER_0
};

ledc_channel_config(&ledc_channel);

4. 控制PWM信号

配置好LEDC通道和定时器后,就可以使用API函数来控制PWM信号了。

// 设置PWM占空比
ledc_set_duty(ledc_channel.speed_mode, ledc_channel.channel, duty);
ledc_update_duty(ledc_channel.speed_mode, ledc_channel.channel);

arduino环境中的LEDC

虽然analogWrite函数现在已经支持ESP32使用,但是LEDC更加的灵活准确:

  • LEDC是针对ESP32微控制器的一组硬件特定功能,它利用ESP32的LED PWM控制器来实现精确的PWM输出。它允许用户对频率和占空比进行更精细的控制,并可以在高速模式和低速模式之间选择。
  • LEDC提供了更多的配置选项,如不同的定时器分辨率、不同的频率设置以及独立的通道管理,这使得它在需要精确控制PWM信号时更为有用。
  • LEDC允许用户选择不同的分辨率,例如从1位到16位,我们可以产生精度从2级到65536级的PWM信号。

    舵机实例


    下面我们来详细的分析一下舵机控制的代码
    #include <Arduino.h>
    // 定义LEDC通道、GPIO引脚和分辨率
    #define LEDC_CHANNEL    0
    #define LEDC_PIN        12
    #define LEDC_RESOLUTION 10 // 设置分辨率为10位
    void setup() {
    // 初始化LEDC通道
    ledcSetup(LEDC_CHANNEL, 50, LEDC_RESOLUTION); // 设置频率为50Hz,分辨率为10位
    ledcAttachPin(LEDC_PIN, LEDC_CHANNEL); // 将GPIO引脚与LEDC通道关联
    }
    void loop() {
    int dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0.075; // 使用10位分辨率计算占空比值
    ledcWrite(LEDC_CHANNEL, dutyCycle);
    // 延迟1秒
    delay(1000);
    dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0.0875; // 使用10位分辨率计算占空比值
    ledcWrite(LEDC_CHANNEL, dutyCycle);
    delay(1000);
    dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0.075; // 使用10位分辨率计算占空比值
    ledcWrite(LEDC_CHANNEL, dutyCycle);
    delay(1000);
    dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0.0625; // 使用10位分辨率计算占空比值
    ledcWrite(LEDC_CHANNEL, dutyCycle);
    delay(1000);
    }
    

这段代码是为Arduino编写的,用于在ESP32微控制器上配置LEDC(LED PWM控制器)以产生PWM信号。以下是详细解析:

包含库

#include <Arduino.h>

这行代码包含Arduino基础库,提供了使用Arduino函数和宏的基础。

预处理器指令

#define LEDC_CHANNEL    0
#define LEDC_PIN        12
#define LEDC_RESOLUTION 10

这些行为LEDC通道、GPIO引脚和PWM分辨率定义了常量。这里定义了将要使用的通道为0,PWM信号输出的GPIO引脚为12,PWM分辨率为10位(意味着PWM值的范围是0到1023)。

setup函数

void setup() {
  ledcSetup(LEDC_CHANNEL, 50, LEDC_RESOLUTION);
  ledcAttachPin(LEDC_PIN, LEDC_CHANNEL);
}

setup函数中,首先调用ledcSetup函数来初始化LEDC通道0,设置PWM频率为50Hz,分辨率为10位。然后调用ledcAttachPin函数将GPIO引脚12与LEDC通道0关联起来,这样PWM信号就会输出到这个引脚。

loop函数

void loop() {
  int dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0.075;
  ledcWrite(LEDC_CHANNEL, dutyCycle);
  delay(1000);
  // ...更多类似操作
}

loop函数中,使用ledcWrite函数来设置LEDC通道0的占空比。这里有四个不同的占空比值(7.5%、8.75%、7.5%、6.25%),每个都通过计算得到一个10位分辨率下的duty cycle值,然后这个值被用来设置PWM的占空比。每次设置占空比后,代码暂停1000毫秒(1秒)。

波形分析:

通过上述的程序我们实现生成了一个频率为50hz即周期20ms的pwm,并通过pwm控制了舵机的左右旋转
下面我们通过示波器观察pwm的波形

观察上图,我们可以看到pwm波按照我们设定的占空比依次变化。

电机调速实例

#include <Arduino.h>

// 定义LEDC通道、GPIO引脚和分辨率
#define LEDC_CHANNEL    0
#define LEDC_PIN        12
#define LEDC_RESOLUTION 10 // 设置分辨率为10位

void setup() {
  // 初始化LEDC通道
  ledcSetup(LEDC_CHANNEL, 50, LEDC_RESOLUTION); // 设置频率为50Hz,分辨率为10位
  ledcAttachPin(LEDC_PIN, LEDC_CHANNEL); // 将GPIO引脚与LEDC通道关联
}

void loop() {
  // 设置占空比为25%
  int dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0.075; // 使用10位分辨率计算占空比值
  ledcWrite(LEDC_CHANNEL, dutyCycle);
  // 延迟2秒
  delay(1000);
  dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0.0875; // 使用10位分辨率计算占空比值
  ledcWrite(LEDC_CHANNEL, dutyCycle);
  delay(1000);
  dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0.075; // 使用10位分辨率计算占空比值
  ledcWrite(LEDC_CHANNEL, dutyCycle);
  delay(1000);
  dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0.0625; // 使用10位分辨率计算占空比值
  ledcWrite(LEDC_CHANNEL, dutyCycle);
  delay(1000);
}
#include <Arduino.h>

// 定义电机A的控制针脚
#define MOTOR_A_PWM 18   // 用于速度控制的PWM针脚
#define MOTOR_A_IN1 17   // 方向控制针脚1
#define MOTOR_A_IN2 5   // 方向控制针脚2

// 定义电机B的控制针脚
#define MOTOR_B_PWM 15   // 用于速度控制的PWM针脚
#define MOTOR_B_IN1 4   // 方向控制针脚1
#define MOTOR_B_IN2 2   // 方向控制针脚2

// 定义Standby针脚
#define MOTOR_STBY 16

void setup() {
  // 初始化电机控制针脚为输出模式
  pinMode(MOTOR_A_PWM, OUTPUT);
  pinMode(MOTOR_A_IN1, OUTPUT);
  pinMode(MOTOR_A_IN2, OUTPUT);
  pinMode(MOTOR_B_PWM, OUTPUT);
  pinMode(MOTOR_B_IN1, OUTPUT);
  pinMode(MOTOR_B_IN2, OUTPUT);
  pinMode(MOTOR_STBY, OUTPUT);

  // 禁用Standby模式
  digitalWrite(MOTOR_STBY, HIGH);
}

void drive(int motorAPower, int motorBPower) {
  if (motorAPower >= 0) {
    digitalWrite(MOTOR_A_IN1, HIGH);
    digitalWrite(MOTOR_A_IN2, LOW);
  } else {
    digitalWrite(MOTOR_A_IN1, LOW);
    digitalWrite(MOTOR_A_IN2, HIGH);
    motorAPower = -motorAPower; // 取绝对值
  }

  if (motorBPower >= 0) {
    digitalWrite(MOTOR_B_IN1, HIGH);
    digitalWrite(MOTOR_B_IN2, LOW);
  } else {
    digitalWrite(MOTOR_B_IN1, LOW);
    digitalWrite(MOTOR_B_IN2, HIGH);
    motorBPower = -motorBPower; // 取绝对值
  }

  // 设置PWM值来控制速度
  analogWrite(MOTOR_A_PWM, motorAPower);
  analogWrite(MOTOR_B_PWM, motorBPower);
}

void loop() {
  // 向前移动
  drive(100, 200); // 设置两个电机的速度
  delay(2000);     // 持续2秒钟
  drive(-200, -200); // 设置两个电机的速度
  delay(2000);     // 持续2秒钟
}

相比舵机控制实例,这个电机调速实例我们直接使用了analogWrite函数生成pwm,在一般的应用场景,比如在电机调速这种对pwm精度及频率不敏感的情况下,直接使用analogWrite也是不错的选择