【ESP32-IDF】高级外设开发5:PWM(LEDC、MCPWM)
本文详细介绍了ESP32系列(如ESP32-S3)的两种PWM控制器:LEDC和MCPWM。LEDC模块提供8个通道,支持LED调光、蜂鸣器等应用,通过定时器实现PWM输出,但需权衡频率与分辨率。MCPWM模块则针对电机控制,具有互补输出、死区时间等高级功能,适用于电机驱动、舵机控制和数字电源等场景。文章还分析了两种控制器的工作原理、配置方法及典型应用,并提供了相关API说明。
系列文章目录
持续更新…
文章目录
前言
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灯光渐变、马达调速、信号测量等丰富的功能。
更多推荐

所有评论(0)