系列文章目录

持续更新…



前言

PWM(Pulse Width Modulation,脉宽调制)是一种通过改变脉冲宽度来控制模拟量的方法,广泛应用于LED调光、电机调速等场景。ESP32系列(如 ESP32-S3)内置两种 PWM 控制外设:LEDC(LED PWM 控制器)和MCPWM(电机控制 PWM)。LEDC 提供高分辨率、多通道的PWM输出,适用于LED亮度调节、蜂鸣器发声等;MCPWM 则面向电机等功率控制,支持互补输出、死区控制、故障保护和捕获输入等高级功能,可实现复杂的电机驱动和数字电源控制。本文将基于最新版IDF(v5.5),详解 ESP32 上 PWM 控制器的原理、使用方法及示例程序。

参考文档:ESP32-S3技术参考手册ESP32-S3编程指南


一、PWM概述

(1)LEDC(LED 控制器)

1.模块结构与通道配置

LEDC(LED PWM 控制器)是 ESP32-S3 上用于生成 PWM 信号的外设模块,主要用于控制 LED 等设备的亮度。LEDC 提供 8 个独立通道(PWM 0-7),每个通道都可输出独立的 PWM 波形,并通过将通道绑定到指定的 定时器 和 GPIO 引脚 来产生信号。
LED PWM 控制器架构
在这里插入图片描述

ESP32-S3 中的 LEDC 内部包含 4 个可独立配置的定时器(时间基准),每个定时器可以为多个通道提供计时参考。也就是说,不同通道可以共享同一个定时器来保证同步,或使用不同定时器以生成互不相关的PWM输出。LEDC 硬件还支持无需 CPU 介入即可自动渐变调整占空比,实现 LED 渐亮渐暗的呼吸效果。
输出信号占空比渐变图
在这里插入图片描述

2.分辨率与频率的关系

PWM 信号的频率和占空比分辨率是一对此消彼长的参数:当要求更高的PWM频率时,可用的分辨率(占空比可划分的离散等级)会降低,反之亦然。
LED PWM 输出信号图
在这里插入图片描述

这是因为定时器时钟频率固定时,计数器在每个PWM周期内可计数的总步数有限。举例来说,在 PWM 5 kHz频率下,LEDC 可支持最高约 13 位的占空比分辨率(即占空比调节步距精细到约0.012%)。但是如果提高PWM频率到接近硬件上限,例如 40 MHz 用作输出时钟,那么占空比分辨率将只有 1 位(意味着波形固定为50%占空比,无法再细分调整)。
定时器和 PWM 生成器功能块
在这里插入图片描述
因此,在使用时需要根据应用需求权衡频率和分辨率:ESP-IDF 提供的 LEDC API 会在设置超出硬件能力范围的频率/分辨率组合时给出错误提示。

3.工作模式与典型应用场景

原始 ESP32 系列的 LEDC 模块将 8 个通道分为高速和低速两组,各自由独立的定时器组驱动,其中高速通道支持硬件级无扰切换占空比,低速通道则需要软件更新占空比。然而 ESP32-S3 中 仅支持低速模式 的 LEDC 通道,不再区分高速组。这意味着在 ESP32-S3 上所有 8 路通道都使用同一套定时器资源,按照低速模式运行。对于用户而言,使用 ESP-IDF 配置 LEDC 时需要将通道的速度模式参数设为LEDC_LOW_SPEED_MODE。尽管只有低速模式,LEDC 依然提供平滑渐变调节占空比的功能,只是占空比更新由软件驱动,相比高速模式可能会有轻微的响应延迟。一般LED调光等场景对这一差异无明显影响。

LEDC 模块适合用于需要中低频 PWM 输出的场景,包括但不限于以下应用:
LED 调光与呼吸灯:通过调整PWM占空比控制LED亮度,实现恒亮调光或周期渐亮渐暗的呼吸灯效果。
蜂鸣器/音频信号:输出几百Hz到几kHz的方波信号驱动有源或无源蜂鸣器,产生音调提示音(例如定时器报警声)。改变PWM频率可以控制蜂鸣器发出不同音调。

(以上应用中,LEDC 的硬件渐变功能尤其适用于LED的平滑亮度变化,而固定频率输出则可用于简单声音/信号发生。)

(2)MCPWM(电机控制 PWM)

1.模块结构与信号路径

MCPWM(Motor Control PWM)外设是一个功能强大的 PWM 波形发生器,内部由多个子模块组成,常用于电机驱动和电力电子控制等应用。ESP32-S3 内置有 2 个独立的 MCPWM 单元(称为 MCPWM0 和 MCPWM1),每个单元包含 3 个 PWM定时器(TIMER) 和 3 个 PWM操作器(OPERATOR),以及故障保护和输入捕获等附加模块。
MCPWM 外设概览
在这里插入图片描述
MCPWM 的信号生成路径可以分解如下:
PWM定时器模块:提供PWM波形的时间基准信号(计数器基础)。定时器可以自由运行计数,也可被同步信号重置,用于协调多个PWM的相位。每个定时器带有可编程的预分频器和计数模式(如向上计数、向下计数或上下交替计数),用于决定PWM周期长度和波形形态。
在这里插入图片描述
PWM操作器模块:每个操作器是生成PWM输出波形的核心,由比较器、PWM发生器、死区插入等子模块组成。一个操作器与其中一个定时器相关联(可自由选择绑定哪一个定时器作为其时间基准),并负责产生一对 PWM 输出信号。
在这里插入图片描述
比较器模块:将定时器的实时计数值与预设的阈值进行比较。当计数值达到阈值时产生比较事件,触发后续PWM生成器模块更新输出电平。通常通过设置不同阈值,比较器决定PWM波形在一个周期中占空比的起止时刻。
PWM生成器模块:根据定时器和比较器等产生的事件,控制实际输出引脚的电平变化。每个操作器含有两个输出通道(称为A路和B路),因此每个操作器可生成一对 PWM 信号。这两路输出既可配置为互补输出(一路高电平时另一路低电平),也可独立输出不同的PWM波形。互补模式常用于驱动全桥/半桥电路,一个操作器的A/B两输出分别连接上下桥臂的两个开关元件,从而控制如直流电机的正反转或无刷电机的三相绕组。独立模式下则两路输出互不相关,可用于驱动两个不同负载。

2.定时器与操作器机制

在 MCPWM 模块中,定时器和操作器之间具有灵活的映射关系和协作机制。例如,在 ESP32-S3 的每个 MCPWM 单元中,3 个操作器并不像固定绑定各自的定时器,而是可以任意组合:每个操作器都能选择使用任意一个PWM定时器作为其时间基准。这意味着多个操作器可以共享同一个定时器,从而产生同步的 PWM 输出(例如多路输出保持相同频率和相位),也可以各自使用不同定时器以产生彼此独立(不同频率或相位)的PWM波形。
PWM 操作器的子模块
在这里插入图片描述
这种设计使 MCPWM 能灵活支持多种控制拓扑。例如,同一定时器驱动的多个操作器可确保多个PWM通道严格同步,用于驱动三相无刷电机等;而分配不同定时器则可独立控制不同设备的PWM而互不干扰。不同 MCPWM 组内部的资源相互独立,操作器和定时器仅能在同一单元内组合使用。

3.死区时间、同步、互补输出

MCPWM 针对电机和开关电源控制提供了一系列高级功能。

其中,互补输出允许每个操作器的两路PWM实现一高一低的互补关系,常用于半桥驱动以确保推挽式两个晶体管不会同时导通。在互补输出模式下,可以设置死区时间(Dead Time)以提高安全性:即在一路PWM切换状态后,延迟一小段时间再切换另一补输出,从而给硬件预留关断时间,避免上下管同时导通造成短路。

MCPWM 硬件允许分别设置上升沿和下降沿的死区插入时长,以适配不同功率器件的开关特性。

另外,MCPWM 支持同步机制,可以通过外部引脚触发或软件命令,将多个 PWM 定时器在计数上同步复位,实现多路PWM信号之间固定的相位差。利用同步功能,开发者能够生成诸如三相正弦波PWM(空间矢量调制SVPWM)等需要精确定相的波形,用于多相电机控制或多相逆变电源。综上,这些功能使 MCPWM 特别适合复杂的电机与功率控制场景。

4.应用方向

MCPWM 外设广泛用于高阶PWM控制的场景,包括但不限于以下几个方向:

电机驱动:如直流电机调速(利用全桥PWM控制电机转速和方向)或无刷电机控制(通过三相PWM产生旋转磁场),MCPWM 的互补输出和死区控制非常适合驱动功率开关元件。
舵机控制:生成标准 RC 舵机所需的控制脉冲(通常为 50 Hz 频率、1~2 ms 脉宽的 PWM 信号)。借助 MCPWM 定时器的高精度和多个操作器,可同时控制多路舵机的角度位置。
逆变器/数字电源:用于开关电源和逆变器的控制,例如利用 PWM 将直流转换为交流(逆变)或调整直流-直流转换的占空比。MCPWM 的同步和载波调制功能可以实现多相电源的相位控制,以及在 PWM 上叠加高频载波以驱动隔离式变压器等。

(通过充分利用 MCPWM 的比较器、同步、死区及故障保护等特性,开发者可以实现复杂的电机控制算法和高可靠性的电源转换控制。)

二、PWM类型定义及相关API

1.LEDC

LEDC类型定义

/* ======== 基本类型与通道 ======== */
typedef enum {
    LEDC_HIGH_SPEED_MODE = 0,    // 高速模式(仅ESP32具备)
    LEDC_LOW_SPEED_MODE,         // 低速模式(ESP32-S3全为低速模式)
    LEDC_SPEED_MODE_MAX,
} ledc_mode_t;

typedef enum {
    LEDC_CHANNEL_0 = 0,          // 通道0 
    LEDC_CHANNEL_1,              // 通道1 
    LEDC_CHANNEL_2,              // 通道2 
    LEDC_CHANNEL_3,              // 通道3 
    LEDC_CHANNEL_4,              // 通道4 
    LEDC_CHANNEL_5,              // 通道5 
#if SOC_LEDC_CHANNEL_NUM > 6
    LEDC_CHANNEL_6,              // 通道6 
    LEDC_CHANNEL_7,              // 通道7 
#endif
    LEDC_CHANNEL_MAX,
} ledc_channel_t;

typedef enum {
    LEDC_TIMER_0 = 0,            // 定时器0 
    LEDC_TIMER_1,                // 定时器1 
    LEDC_TIMER_2,                // 定时器2 
    LEDC_TIMER_3,                // 定时器3 
    LEDC_TIMER_MAX,
} ledc_timer_t;

typedef enum {
    LEDC_TIMER_1_BIT = 1,        // PWM占空比分辨率 1位
    LEDC_TIMER_2_BIT,            // 2位
    /* ... */
    LEDC_TIMER_13_BIT = 13,      // 13位
    LEDC_TIMER_14_BIT,           // 14位(ESP32-S3支持16位)
#if SOC_LEDC_TIMER_BIT_WIDE_NUM > 14
    LEDC_TIMER_15_BIT,
    LEDC_TIMER_16_BIT,
#endif
    LEDC_TIMER_BIT_MAX,
} ledc_timer_bit_t;

/* 渐变功能相关 */
typedef enum {
    LEDC_DUTY_DIR_DECREASE = 0,  // 渐变方向:占空比减小
    LEDC_DUTY_DIR_INCREASE,      // 渐变方向:占空比增加
    LEDC_DUTY_DIR_MAX,
} ledc_duty_direction_t;

typedef enum {
    LEDC_FADE_NO_WAIT = 0,       // 渐变函数立即返回,不等待完成
    LEDC_FADE_WAIT_DONE,         // 渐变函数阻塞,等待渐变完成
    LEDC_FADE_MAX,
} ledc_fade_mode_t;

/* ======== 配置结构体 ======== */
/** LEDC通道配置参数 */
typedef struct {
    int gpio_num;                   // 输出GPIO编号
    ledc_mode_t speed_mode;         // 通道速度模式(高速或低速)
    ledc_channel_t channel;         // 通道编号
    ledc_intr_type_t intr_type;     // 中断类型(渐变完成中断启用/禁用)
    ledc_timer_t timer_sel;         // 选择绑定的定时器编号
    uint32_t duty;                  // 初始占空值(0 ~ 2^bit_width)
    int hpoint;                     // 输出高电平起始计数值(0 ~ 2^bit_width-1)
    struct {
        unsigned int output_invert: 1; // 输出极性反转标志
    } flags;
} ledc_channel_config_t;

/** LEDC定时器配置参数 */
typedef struct {
    ledc_mode_t speed_mode;          // 通道速度模式(高速或低速)
    union {
        ledc_timer_bit_t duty_resolution;  // PWM占空比的位宽(分辨率)
        ledc_timer_bit_t bit_num __attribute__((deprecated));
    };
    ledc_timer_t timer_num;          // 定时器编号 (0-3)
    uint32_t freq_hz;                // PWM信号频率 (Hz)
    ledc_clk_cfg_t clk_cfg;          // 时钟源选择(如APB时钟、REF_TICK等)
    bool deconfigure;                // 是否反初始化定时器(用于复位定时器配置)
} ledc_timer_config_t;

LEDC相关API

/* ======== 定时器和通道配置 ======== */
/** 初始化配置一个 LEDC 定时器 */
esp_err_t ledc_timer_config(const ledc_timer_config_t *timer_conf);
/** 初始化配置一个 LEDC 通道并绑定到指定 GPIO */
esp_err_t ledc_channel_config(const ledc_channel_config_t *channel_conf);

/** 将 LEDC 通道与定时器重新绑定(更换通道所用的定时器) */
esp_err_t ledc_bind_channel_timer(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_timer_t timer_sel);

/* ======== PWM 信号控制 ======== */
/** 设置 PWM 占空比(不立即生效,需要调用 ledc_update_duty 应用新值) */
esp_err_t ledc_set_duty(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty);
/** 设置 PWM 占空比和 hpoint(高电平起始点),不立即生效 */
esp_err_t ledc_set_duty_with_hpoint(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, uint32_t hpoint);
/** 获取当前 PWM 占空比值 */
uint32_t ledc_get_duty(ledc_mode_t speed_mode, ledc_channel_t channel);
/** 获取当前 hpoint 值 */
int ledc_get_hpoint(ledc_mode_t speed_mode, ledc_channel_t channel);
/** 应用占空比设置(将通过 ledc_set_duty 设置的值写入寄存器,使之生效) */
esp_err_t ledc_update_duty(ledc_mode_t speed_mode, ledc_channel_t channel);

/** 停止 PWM 输出,并配置停止后的空闲电平 */
esp_err_t ledc_stop(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t idle_level);
/** 直接设置某通道的GPIO输出(不配置频率等其他参数,一般不直接使用) */
esp_err_t ledc_set_pin(int gpio_num, ledc_mode_t speed_mode, ledc_channel_t channel);

/** 修改定时器频率(调整占空比分辨率可能导致失败) */
esp_err_t ledc_set_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num, uint32_t freq_hz);
/** 获取定时器当前频率 */
uint32_t ledc_get_freq(ledc_mode_t speed_mode, ledc_timer_t timer_num);

/* ======== 渐变与中断回调 ======== */
/** 配置 LEDC 渐变(设置渐变的目标占空、方向、步进等参数,不启动) */
esp_err_t ledc_set_fade(ledc_mode_t speed_mode, ledc_channel_t channel, uint32_t duty, 
                        ledc_duty_direction_t dir, uint32_t step_num, uint32_t duty_cyle_num, uint32_t duty_scale);
/** 按给定步进启动渐变(需先调用 ledc_set_fade 设置参数) */
esp_err_t ledc_set_fade_with_step(ledc_mode_t speed_mode, ledc_channel_t channel, 
                                  uint32_t target_duty, uint32_t scale, uint32_t cycle_num);
/** 按给定时间启动渐变(驱动自动计算步进参数) */
esp_err_t ledc_set_fade_with_time(ledc_mode_t speed_mode, ledc_channel_t channel, 
                                  uint32_t target_duty, int fade_time_ms);
/** 开始执行渐变(非阻塞启动,由 fade_mode 决定是否阻塞等待) */
esp_err_t ledc_fade_start(ledc_mode_t speed_mode, ledc_channel_t channel, ledc_fade_mode_t fade_mode);
/** 停止渐变(在支持停止的芯片上可用) */
esp_err_t ledc_fade_stop(ledc_mode_t speed_mode, ledc_channel_t channel);

/** 注册LEDC事件回调(目前主要用于渐变完成事件) */
typedef struct {
    ledc_cb_t fade_cb;   // 渐变完成回调函数
} ledc_cbs_t;
esp_err_t ledc_cb_register(ledc_mode_t speed_mode, ledc_channel_t channel, const ledc_cbs_t *cbs, void *user_arg);

2.MCPWM

MCPWM类型定义

/* ======== 对象句柄 ======== */
typedef struct mcpwm_timer_t      *mcpwm_timer_handle_t;    // 定时器句柄
typedef struct mcpwm_oper_t       *mcpwm_oper_handle_t;     // 操作器(生成PWM波形单元)句柄
typedef struct mcpwm_cmpr_t       *mcpwm_cmpr_handle_t;     // 比较器句柄
typedef struct mcpwm_gen_t        *mcpwm_gen_handle_t;      // PWM发生器(输出)句柄
typedef struct mcpwm_fault_t      *mcpwm_fault_handle_t;    // 故障检测源句柄
typedef struct mcpwm_sync_t       *mcpwm_sync_handle_t;     // 同步信号源句柄
typedef struct mcpwm_cap_timer_t  *mcpwm_cap_timer_handle_t;// 捕获定时器句柄
typedef struct mcpwm_cap_channel_t* mcpwm_cap_channel_handle_t;// 捕获通道句柄

/* ======== 枚举类型 ======== */
/** MCPWM定时器计数方向 */
typedef enum {
    MCPWM_TIMER_DIRECTION_UP,     // 向上计数(递增)
    MCPWM_TIMER_DIRECTION_DOWN,   // 向下计数(递减)
} mcpwm_timer_direction_t;

/** MCPWM定时器事件 */
typedef enum {
    MCPWM_TIMER_EVENT_EMPTY,      // 定时器计数到 0
    MCPWM_TIMER_EVENT_FULL,       // 定时器计数到周期峰值
    MCPWM_TIMER_EVENT_INVALID,
} mcpwm_timer_event_t;

/** MCPWM定时器计数模式 */
typedef enum {
    MCPWM_TIMER_COUNT_MODE_PAUSE,   // 暂停计数
    MCPWM_TIMER_COUNT_MODE_UP,      // 只升计数
    MCPWM_TIMER_COUNT_MODE_DOWN,    // 只降计数
    MCPWM_TIMER_COUNT_MODE_UP_DOWN, // 上下计数(三角波)
} mcpwm_timer_count_mode_t;

/** MCPWM定时器启停命令 */
typedef enum {
    MCPWM_TIMER_STOP_EMPTY,        // 等待计数减到0时停止
    MCPWM_TIMER_STOP_FULL,         // 等待计数加到周期峰值时停止
    MCPWM_TIMER_START_NO_STOP,     // 启动计数,不自动停止
    MCPWM_TIMER_START_STOP_EMPTY,  // 启动计数,下一次计数到0时停止
    MCPWM_TIMER_START_STOP_FULL,   // 启动计数,下一次计数到峰值时停止
} mcpwm_timer_start_stop_cmd_t;

/** MCPWM PWM发生器输出行为 */
typedef enum {
    MCPWM_GEN_ACTION_KEEP,    // 保持原电平
    MCPWM_GEN_ACTION_LOW,     // 强制输出低电平
    MCPWM_GEN_ACTION_HIGH,    // 强制输出高电平
    MCPWM_GEN_ACTION_TOGGLE,  // 翻转输出电平
} mcpwm_generator_action_t;

/** MCPWM刹车模式 */
typedef enum {
    MCPWM_OPER_BRAKE_MODE_CBC,     // 循环逐周期刹车(CBC)
    MCPWM_OPER_BRAKE_MODE_OST,     // 一次性刹车(OST,触发后输出保持停)
    MCPWM_OPER_BRAKE_MODE_INVALID,
} mcpwm_operator_brake_mode_t;


/* ======== 配置结构体 ======== */
/** MCPWM定时器配置 */
typedef struct {
    int group_id;                           // 选择MCPWM组(0或1)
    mcpwm_timer_clock_source_t clk_src;     // 定时器时钟源
    uint32_t resolution_hz;                 // 计数器分辨率(Hz)
    mcpwm_timer_count_mode_t count_mode;    // 计数模式(上下/单向)
    uint32_t period_ticks;                  // 周期时钟计数数(定时器周期)
    struct {
        unsigned update_period_on_empty: 1; // 计数器归零时更新period_ticks
        unsigned update_period_on_sync: 1;  // 收到同步信号时更新period_ticks
    } flags;
} mcpwm_timer_config_t;

/** MCPWM操作器配置 */
typedef struct {
    int group_id;                           // 选择MCPWM组 ID
    int intr_priority;                      // 中断优先级(0为默认)
    struct {
        unsigned update_gen_action_on_tez: 1; // 定时器归零时是否更新发生器动作
        unsigned update_gen_action_on_tep: 1; // 定时器到达峰值时更新发生器动作
        unsigned update_gen_action_on_sync:1; // 收到同步信号时更新发生器动作
        unsigned update_dead_time_on_tez: 1;  // 定时器归零时是否更新死区设置
        unsigned update_dead_time_on_tep: 1;  // 定时器到峰值时更新死区设置
        unsigned update_dead_time_on_sync:1;  // 收到同步信号时更新死区设置
    } flags;
} mcpwm_operator_config_t;

/** MCPWM比较器配置 */
typedef struct {
    int intr_priority;                      // 中断优先级(0为默认)
    struct {
        unsigned update_cmp_on_tez: 1;      // 定时器归零时更新比较阈值
        unsigned update_cmp_on_tep: 1;      // 定时器到峰值时更新比较阈值
        unsigned update_cmp_on_sync:1;      // 收到同步信号时更新比较阈值
    } flags;
} mcpwm_comparator_config_t;

/** MCPWM PWM发生器配置 */
typedef struct {
    int gen_gpio_num;                       // 发生器输出的GPIO引脚号
    struct {
        unsigned invert_pwm: 1;             // 输出信号极性取反
        unsigned io_loop_back: 1;           // GPIO回环测试模式(调试用)
        unsigned io_od_mode: 1;             // GPIO开漏输出模式
        unsigned pull_up: 1;                // 是否启用内部上拉
        unsigned pull_down: 1;              // 是否启用内部下拉
    } flags;
} mcpwm_generator_config_t;

MCPWM相关API

/* ======== 资源分配与释放 ======== */
/** 创建一个 MCPWM定时器 */
esp_err_t mcpwm_new_timer(const mcpwm_timer_config_t *config, mcpwm_timer_handle_t *ret_timer);
/** 删除一个 MCPWM定时器 */
esp_err_t mcpwm_del_timer(mcpwm_timer_handle_t timer);

/** 创建一个 MCPWM操作器 */
esp_err_t mcpwm_new_operator(const mcpwm_operator_config_t *config, mcpwm_oper_handle_t *ret_oper);
/** 删除一个 MCPWM操作器 */
esp_err_t mcpwm_del_operator(mcpwm_oper_handle_t oper);

/** 创建一个 MCPWM比较器(隶属于某操作器) */
esp_err_t mcpwm_new_comparator(mcpwm_oper_handle_t oper, const mcpwm_comparator_config_t *config, mcpwm_cmpr_handle_t *ret_cmpr);
/** 删除一个 MCPWM比较器 */
esp_err_t mcpwm_del_comparator(mcpwm_cmpr_handle_t cmpr);

/** 创建一个 MCPWM发生器(隶属于某操作器) */
esp_err_t mcpwm_new_generator(mcpwm_oper_handle_t oper, const mcpwm_generator_config_t *config, mcpwm_gen_handle_t *ret_gen);
/** 删除一个 MCPWM发生器 */
esp_err_t mcpwm_del_generator(mcpwm_gen_handle_t gen);

/** 创建一个 GPIO 故障输入对象 */
esp_err_t mcpwm_new_gpio_fault(const mcpwm_gpio_fault_config_t *config, mcpwm_fault_handle_t *ret_fault);
/** 创建一个软件故障对象 */
esp_err_t mcpwm_new_soft_fault(const mcpwm_soft_fault_config_t *config, mcpwm_fault_handle_t *ret_fault);
/** 删除故障对象(通用,参数可为 GPIO或软件故障句柄) */
esp_err_t mcpwm_del_fault(mcpwm_fault_handle_t fault);

/* (同步源、捕获模块的创建API类似,这里不展开) */

/* ======== 模块初始化与控制 ======== */
/** 启用 MCPWM 定时器(使能后才能启动计数) */
esp_err_t mcpwm_timer_enable(mcpwm_timer_handle_t timer);
/** 禁用 MCPWM 定时器 */
esp_err_t mcpwm_timer_disable(mcpwm_timer_handle_t timer);
/** 向 MCPWM 定时器发送启动/停止命令 */
esp_err_t mcpwm_timer_start_stop(mcpwm_timer_handle_t timer, mcpwm_timer_start_stop_cmd_t command);

/** 将 MCPWM 操作器绑定到指定定时器(让该操作器受此定时器驱动) */
esp_err_t mcpwm_operator_connect_timer(mcpwm_oper_handle_t oper, mcpwm_timer_handle_t timer);
/** 为 MCPWM 操作器配置故障刹车行为(CBC/OST),绑定一个故障源 */
esp_err_t mcpwm_operator_set_brake_on_fault(mcpwm_oper_handle_t oper, const mcpwm_brake_config_t *config);
/** 故障恢复:让操作器从故障刹车状态恢复输出 */
esp_err_t mcpwm_operator_recover_from_fault(mcpwm_oper_handle_t oper, mcpwm_fault_handle_t fault);

/** 设置比较器比较阈值(占空比对应的计数刻度值) */
esp_err_t mcpwm_comparator_set_compare_value(mcpwm_cmpr_handle_t cmpr, uint32_t compare_ticks);

/** 为发生器设置在定时器事件上的动作 
 * ev_act 参数由宏 MCPWM_GEN_TIMER_EVENT_ACTION(dir, event, action) 生成 */
esp_err_t mcpwm_generator_set_action_on_timer_event(mcpwm_gen_handle_t gen, mcpwm_gen_timer_event_action_t ev_act);
/** 为发生器设置在比较器事件上的动作 
 * ev_act 参数由宏 MCPWM_GEN_COMPARE_EVENT_ACTION(cmp, action) 生成 */
esp_err_t mcpwm_generator_set_action_on_compare_event(mcpwm_gen_handle_t gen, mcpwm_gen_compare_event_action_t ev_act);
/** 设置发生器输出死区时间(为一对发生器genA/genB插入互补延迟) */
esp_err_t mcpwm_generator_set_dead_time(mcpwm_gen_handle_t gen_high, mcpwm_gen_handle_t gen_low, const mcpwm_dead_time_config_t *config);
/** 强制立即将发生器输出电平设置为指定状态(level:0/1),可选择是否锁存保持 */
esp_err_t mcpwm_generator_set_force_level(mcpwm_gen_handle_t gen, int level, bool hold_on);

/* ======== 事件回调 ======== */
/** 注册 MCPWM 定时器事件回调(如计数到零/峰值/停止) */
esp_err_t mcpwm_timer_register_event_callbacks(mcpwm_timer_handle_t timer, const mcpwm_timer_event_callbacks_t *cbs, void *user_data);
/** 注册 MCPWM 故障事件回调(故障发生/解除等) */
esp_err_t mcpwm_fault_register_event_callbacks(mcpwm_fault_handle_t fault, const mcpwm_fault_event_callbacks_t *cbs, void *user_data);

三、PWM示例程序

LEDC示例程序:LED呼吸灯(渐变完成时会触发中断并输出日志)

// ledc_fade_demo_s3.c  (ESP-IDF v5.x)
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "driver/ledc.h"
#include "esp_err.h"
#include "esp_log.h"

#define LED_GPIO (10)                  // 接LED的可输出GPIO
#define LEDC_MODE LEDC_LOW_SPEED_MODE // ESP32-S3 仅支持低速模式
#define LEDC_TIMER_ID LEDC_TIMER_0
#define LEDC_CHANNEL_ID LEDC_CHANNEL_0
#define LEDC_FREQ_HZ (5000)                      // 5 kHz
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT          // 13位分辨率
#define LEDC_DUTY_MAX ((1 << LEDC_DUTY_RES) - 1) // 0..8191(不要取 1<<bits)
#define FADE_TIME_MS (1500)                      // 渐变时长 1.5 s

static const char *TAG = "LED_BREATHING";
static SemaphoreHandle_t fade_complete_sem = NULL;

// 渐变完成回调函数
static IRAM_ATTR bool ledc_fade_end_callback(const ledc_cb_param_t *param, void *user_arg)
{
    BaseType_t taskAwoken = pdFALSE;

    if (param->event == LEDC_FADE_END_EVT)
    {
        // 在中断中发送信号量
        xSemaphoreGiveFromISR(fade_complete_sem, &taskAwoken);
    }

    return (taskAwoken == pdTRUE);
}

void app_main(void)
{
    ESP_LOGI(TAG, "开始初始化LED呼吸灯程序");

    // 创建信号量用于同步渐变完成事件
    fade_complete_sem = xSemaphoreCreateBinary();
    if (fade_complete_sem == NULL)
    {
        ESP_LOGE(TAG, "信号量创建失败");
        return;
    }

    // 1) 先配置定时器(频率&分辨率&时钟源)
    const ledc_timer_config_t tcfg = {
        .speed_mode = LEDC_MODE,
        .timer_num = LEDC_TIMER_ID,
        .duty_resolution = LEDC_DUTY_RES,
        .freq_hz = LEDC_FREQ_HZ,
        .clk_cfg = LEDC_AUTO_CLK,
    };
    ESP_ERROR_CHECK(ledc_timer_config(&tcfg));

    // 2) 再配置通道(绑定GPIO到该定时器)
    const ledc_channel_config_t ccfg = {
        .gpio_num = LED_GPIO,
        .speed_mode = LEDC_MODE,
        .channel = LEDC_CHANNEL_ID,
        .intr_type = LEDC_INTR_DISABLE,
        .timer_sel = LEDC_TIMER_ID,
        .duty = 0,
        .hpoint = 0,
        .flags.output_invert = 0,
    };
    ESP_ERROR_CHECK(ledc_channel_config(&ccfg));

    // 3) 使能硬件渐变功能
    ESP_ERROR_CHECK(ledc_fade_func_install(0));

    // 4) 注册渐变完成回调函数
    ledc_cbs_t callbacks = {
        .fade_cb = ledc_fade_end_callback};
    ESP_ERROR_CHECK(ledc_cb_register(LEDC_MODE, LEDC_CHANNEL_ID, &callbacks, NULL));

    uint32_t fade_count = 0;

    while (1)
    {
        fade_count++;
        ESP_LOGI(TAG, "=== 第%lu次呼吸循环开始 ===", fade_count);

        // 渐亮到最大占空比
        ESP_LOGI(TAG, "开始渐亮: 0 -> %d (耗时%dms)", LEDC_DUTY_MAX, FADE_TIME_MS);
        ESP_ERROR_CHECK(ledc_set_fade_with_time(LEDC_MODE, LEDC_CHANNEL_ID, LEDC_DUTY_MAX, FADE_TIME_MS));
        ESP_ERROR_CHECK(ledc_fade_start(LEDC_MODE, LEDC_CHANNEL_ID, LEDC_FADE_NO_WAIT));

        // 等待渐亮完成
        if (xSemaphoreTake(fade_complete_sem, pdMS_TO_TICKS(FADE_TIME_MS + 500)) == pdTRUE)
        {
            ESP_LOGI(TAG, "✓ 渐亮完成!当前占空比: %d", LEDC_DUTY_MAX);
        }
        else
        {
            ESP_LOGW(TAG, "渐亮超时警告");
        }

        // 渐灭到 0
        ESP_LOGI(TAG, "开始渐灭: %d -> 0 (耗时%dms)", LEDC_DUTY_MAX, FADE_TIME_MS);
        ESP_ERROR_CHECK(ledc_set_fade_with_time(LEDC_MODE, LEDC_CHANNEL_ID, 0, FADE_TIME_MS));
        ESP_ERROR_CHECK(ledc_fade_start(LEDC_MODE, LEDC_CHANNEL_ID, LEDC_FADE_NO_WAIT));

        // 等待渐灭完成
        if (xSemaphoreTake(fade_complete_sem, pdMS_TO_TICKS(FADE_TIME_MS + 500)) == pdTRUE)
        {
            ESP_LOGI(TAG, "✓ 渐灭完成!当前占空比: 0");
        }
        else
        {
            ESP_LOGW(TAG, "渐灭超时警告");
        }

        ESP_LOGI(TAG, "=== 第%lu次呼吸循环结束,暂停300ms ===\n", fade_count);
        vTaskDelay(pdMS_TO_TICKS(300));
    }

    // 如需卸载:ledc_fade_func_uninstall();
}

MCPWM示例程序:PWM 电机调速

// MCPWM电机控制示例 - ESP32-S3 (新版API v5.x)
// 功能:通过PWM信号控制直流电机的速度
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/mcpwm_prelude.h"  // MCPWM新版API头文件
#include "driver/gpio.h"           // GPIO控制头文件
#include "esp_log.h"               // 日志输出头文件

// 硬件引脚定义
#define MOTOR_PWM_GPIO  (10)      // PWM信号输出引脚,连接电机驱动器的PWM输入
#define MOTOR_DIR_GPIO  (11)      // 方向控制引脚,连接电机驱动器的方向控制
#define PWM_FREQ_HZ     (1000)    // PWM频率 1kHz,决定PWM波形的周期
#define TIMER_RES_HZ    (1000000) // 定时器分辨率 1MHz,决定PWM精度
#define MCPWM_GROUP     (0)       // MCPWM组号,ESP32-S3有2个MCPWM组

static const char *TAG = "MOTOR_CONTROL";  // 日志标签,用于识别输出来源

// 全局句柄,用于操作MCPWM硬件
static mcpwm_timer_handle_t timer = NULL;      // 定时器句柄,控制PWM周期
static mcpwm_cmpr_handle_t  comparator = NULL; // 比较器句柄,控制PWM占空比

// 电机方向枚举
typedef enum { 
    MOTOR_FORWARD = 0,   // 正转:GPIO输出低电平
    MOTOR_BACKWARD = 1   // 反转:GPIO输出高电平
} motor_direction_t;

/**
 * 设置电机转动方向
 * @param dir 方向选择:MOTOR_FORWARD(正转) 或 MOTOR_BACKWARD(反转)
 */
static void set_motor_direction(motor_direction_t dir) 
{
    gpio_set_level(MOTOR_DIR_GPIO, dir);  // 设置方向控制引脚的电平
    ESP_LOGI(TAG, "电机方向设置为: %s", dir == MOTOR_FORWARD ? "正转" : "反转");
}

/**
 * 设置电机转速(通过改变PWM占空比)
 * @param duty_percent 占空比百分比(0-100%),0%=停止,100%=最高速
 */
static void set_motor_speed(float duty_percent) 
{
    // 限制占空比范围在0-100%之间
    if (duty_percent < 0) duty_percent = 0;
    if (duty_percent > 100) duty_percent = 100;

    // 计算PWM参数
    uint32_t period_ticks  = TIMER_RES_HZ / PWM_FREQ_HZ;  // 一个PWM周期的计数值 = 1000000/1000 = 1000
    uint32_t compare_ticks = (uint32_t)(period_ticks * duty_percent / 100.0f);  // 高电平持续的计数值
    
    // 安全检查:比较值不能超过周期值
    if (compare_ticks > period_ticks) compare_ticks = period_ticks;

    // 设置比较器的比较值,这决定了PWM的占空比
    ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, compare_ticks));
    ESP_LOGI(TAG, "电机速度设置为 %.1f%% -> 比较值=%lu / 周期=%lu", duty_percent, compare_ticks, period_ticks);
}

/**
 * 停止电机(将速度设为0)
 */
static void stop_motor(void) 
{ 
    set_motor_speed(0); 
    ESP_LOGI(TAG, "电机已停止");
}

/**
 * 主函数:初始化MCPWM并演示电机控制
 */
void app_main(void) 
{
    ESP_LOGI(TAG, "开始初始化MCPWM电机控制程序");

    // ==================== 第1步:配置方向控制GPIO ====================
    gpio_config_t dir_cfg = {
        .pin_bit_mask = 1ULL << MOTOR_DIR_GPIO,  // 设置要配置的GPIO引脚
        .mode = GPIO_MODE_OUTPUT,                // 设为输出模式
        .pull_up_en = 0,                        // 不使能上拉
        .pull_down_en = 0,                      // 不使能下拉
        .intr_type = GPIO_INTR_DISABLE,         // 禁用中断
    };
    ESP_ERROR_CHECK(gpio_config(&dir_cfg));     // 应用GPIO配置
    set_motor_direction(MOTOR_FORWARD);         // 默认设置为正转
    ESP_LOGI(TAG, "方向控制GPIO配置完成 - GPIO: %d", MOTOR_DIR_GPIO);

    // ==================== 第2步:创建MCPWM定时器 ====================
    mcpwm_timer_config_t tcfg = {
        .group_id = MCPWM_GROUP,                        // MCPWM组号
        .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,         // 使用默认时钟源
        .resolution_hz = TIMER_RES_HZ,                  // 定时器分辨率:1MHz
        .period_ticks  = TIMER_RES_HZ / PWM_FREQ_HZ,    // PWM周期:1000000/1000=1000个时钟周期
        .count_mode    = MCPWM_TIMER_COUNT_MODE_UP,     // 向上计数模式
    };
    ESP_ERROR_CHECK(mcpwm_new_timer(&tcfg, &timer));   // 创建定时器
    ESP_LOGI(TAG, "MCPWM定时器创建完成 - 频率: %d Hz, 周期: %lu ticks", 
             PWM_FREQ_HZ, tcfg.period_ticks);
    // 定义操作器和生成器句柄
    mcpwm_oper_handle_t oper = NULL;  // 操作器句柄:连接定时器和比较器
    mcpwm_gen_handle_t  gen  = NULL;  // 生成器句柄:连接GPIO和生成PWM波形

    // ==================== 第3步:创建MCPWM操作器 ====================
    mcpwm_operator_config_t ocfg = { 
        .group_id = MCPWM_GROUP   // 指定操作器所属的MCPWM组
    };
    ESP_ERROR_CHECK(mcpwm_new_operator(&ocfg, &oper));           // 创建操作器
    ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer));  // 将操作器连接到定时器
    ESP_LOGI(TAG, "MCPWM操作器创建并连接定时器完成");

    // ==================== 第4步:创建比较器 ====================
    mcpwm_comparator_config_t ccf = { 
        .flags.update_cmp_on_tez = true  // 在定时器计数归零(TEZ)时更新比较值,确保同步
    };
    ESP_ERROR_CHECK(mcpwm_new_comparator(oper, &ccf, &comparator));  // 创建比较器
    ESP_LOGI(TAG, "MCPWM比较器创建完成");

    // ==================== 第5步:创建生成器并绑定GPIO ====================
    mcpwm_generator_config_t gcfg = { 
        .gen_gpio_num = MOTOR_PWM_GPIO  // 指定PWM输出的GPIO引脚
    };
    ESP_ERROR_CHECK(mcpwm_new_generator(oper, &gcfg, &gen));  // 创建生成器
    ESP_LOGI(TAG, "MCPWM生成器创建完成 - PWM输出GPIO: %d", MOTOR_PWM_GPIO);

    // 关键:经典边沿对齐“主动高”PWM 的两条规则
    // 1) TEZ(计数清零)时输出置高
    ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(
        gen,
        MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH),
        MCPWM_GEN_TIMER_EVENT_ACTION_END()
    ));
    // 2) 比较命中(计数向上)时输出置低
    ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(
        gen,
        MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_LOW),
        MCPWM_GEN_COMPARE_EVENT_ACTION_END()
    ));

    // 启动
    ESP_ERROR_CHECK(mcpwm_timer_enable(timer));
    ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));

    // Demo: 20% → 80% → 0%,再反转 50%
    const int duty_list[] = {20, 40, 60, 80};
    for (int i = 0; i < 4; ++i) 
    { 
        set_motor_speed(duty_list[i]); 
        vTaskDelay(pdMS_TO_TICKS(1200)); 
    }
    for (int d = 60; d >= 0; d -= 20) 
    { 
        set_motor_speed(d); 
        vTaskDelay(pdMS_TO_TICKS(600)); 
    }

    set_motor_direction(MOTOR_BACKWARD);
    set_motor_speed(50);
    vTaskDelay(pdMS_TO_TICKS(2000));
    stop_motor();
}

总结

ESP32 系列的双 PWM 控制器(LEDC 和 MCPWM)提供了从简易到复杂的全方位脉宽调制支持。LEDC 模块支持最高16位分辨率、最多8通道独立PWM输出,适合LED调光、蜂鸣器音频等对精度要求高但控制逻辑简单的场景;MCPWM 模块集成定时器、比较器、发生器、死区、故障检测、同步、捕获等子模块,能够输出多达6路互补或独立PWM,支持复杂的电机驱动和数字电源控制策略。通过本篇讲解和示例程序,读者应对 ESP32-IDF 中 PWM 外设的用法有了全面认识,可在日后的项目中根据需求选择合适的PWM方案,实现诸如LED灯光渐变、马达调速、信号测量等丰富的功能。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐