ESP32学习笔记_Peripherals(4)——MCPWM基础使用

MCPWM

摘要

ESP32 的 MCPWM(电机控制脉宽调制器)是一种多功能 PWM 生成器,广泛应用于电机控制、数字电源、LED 调光等场景,它通过多个定时器、操作器、比较器和生成器模块,实现高精度、高效率的 PWM 信号输出,并支持故障检测、同步控制和脉宽捕获等高级功能;这篇笔记介绍了 MCPWM 的原理、模块结构、初始化流程及多路 PWM 输出的实现方法,并配有代码示例


参考资料:
ESP-IDF 编程指南 MCPWM
bilibili # 没有专业术语!新手小白也能看懂的FOC科普
Espressif ESP32-S3 技术参考手册

PWM(Pulse Width Modulation,脉宽调制)是一种通过改变脉冲信号占空比来控制输出功率的技术;它在固定的频率下,通过调节高电平持续时间与整个周期时间的比例(占空比)来模拟不同的模拟电压输出

PWM 广泛应用于电机调速、LED 亮度控制、开关电源、音频信号处理等领域,具有控制精度高、效率高、数字化程度高等优点;例如,50%占空比的 PWM 信号可以提供相当于一半电源电压的平均功率输出

电机控制脉冲宽度调制器(MCPWM)
MCPWM 外设是一个多功能 PWM 生成器,集成多个子模块,在电力电子应用(如电机控制、数字电源等)中至关重要,通常适用于以下场景

  • 数字电机控制,如有刷/无刷直流电机、RC 伺服电机
  • 基于开关模式的数字电源转换
  • 功率数模转换器 (Power DAC),其中占空比等于 DAC 的模拟值
  • 计算外部脉宽,并将其转换为其他模拟值,如速度、距离
  • 为磁场定向控制 (FOC) 生成空间矢量调制 (SVPWM) 信号

FOC (Field Oriented Control,磁场定向控制) 是一种先进的电机控制算法,通过坐标变换将三相交流电机的复杂控制转换为类似直流电机的简单控制方式
将定子电流分解为产生磁通的 d 轴分量和产生转矩的 q 轴分量,实现磁通与转矩的解耦控制,从而获得高精度、快响应、高效率的电机控制性能;FOC 广泛应用于电动汽车驱动、工业伺服系统、变频家电等需要高性能电机控制的场合,是现代电机控制技术的核心算法

在使用前,需要在 ESP-IDF Project 的 main 文件夹的 CMakeLists.txt 中加入以下配置

PRIV_REQUIRES driver
// 如:
idf_component_register(SRCS "hello_world_main.c"
                    PRIV_REQUIRES spi_flash
                    INCLUDE_DIRS ""
                    PRIV_REQUIRES driver)

在这里插入图片描述

  • MCPWM Timer MCPWM 定时器模块 最终输出 PWM 信号的时间基准,也决定了其他子模块的事件时序
  • MCPWM Operator MCPWM 操作器模块 生成 PWM 波形的关键模块,它由其他子模块组成,如比较器、PWM 生成器、死区生成器和载波调制器
    • MCPWM Comparator MCPWM 比较器模块 输入时间基准值,并不断与配置的阈值进行比较;当定时器计数值等于任何一个阈值时,生成一个比较事件,MCPWM 生成器随即相应更新其电平,即控制 PWM 波形的高低电平
    • MCPWM Generator MCPWM 生成器模块 根据 MCPWM 定时器、MCPWM 比较器等子模块触发的各种事件,生成一对独立或互补的 PWM 波形
    • Dead Time 死区生成器模块 在此前生成的 PWM 边沿上插入额外的延迟。
    • Carrier Modulation 载波模块 可通过 PWM 波形生成器和死区生成器,将一个高频载波信号调制为 PWM 波形,这是控制功率开关器件的必需功能
  • MCPWM Fault MCPWM 故障检测模块 通过 GPIO 交换矩阵检测外部的故障情况;检测到故障信号时,MCPWM 操作器将强制所有生成器进入预先定义的状态,从而保护系统
  • MCPWM Sync MCPWM 同步模块 同步 MCPWM 定时器,以确保由不同的 MCPWM 生成器最终生成的 PWM 信号具有固定的相位差;可以通过 GPIO 交换矩阵和 MCPWM 定时器事件生成同步信号
  • Brake 制动控制 MCPWM 操作器支持配置检测到特定故障时生成器的制动控制方式;根据故障的严重程度,可以选择立即关闭或是逐周期调节 PWM 输出
  • MCPWM Capture MCPWM 捕获模块 独立子模块,不依赖于上述 MCPWM 操作器工作,捕获模块包括一个专用的定时器和几个独立的通道,每个通道都与 GPIO 相连;GPIO 上的脉冲触发捕获定时器以存储时间基准值,随后通过中断进行通知;此模块有助于更加精准地测量脉宽,此外,捕获定时器也可以通过 MCPWM 同步子模块进行同步

如果只是想要生成普通的 PWM 波形,只要了解以下内容:
时钟:控制频率
比较器:控制占空比
生成器:输出波形
同步模块:使波形同步

资源配置及初始化

MCPWM 定时器

调用 mcpwm_new_timer() 函数,以配置结构体 mcpwm_timer_config_t 为参数,分配一个 MCPWM 定时器为对象

结构体定义为:

配置参数描述范围/值
group_id指定 MCPWM 组 ID[0, SOC_MCPWM_GROUPS - 1]
intr_priority设置中断的优先级0:默认优先级,其他:指定优先级
clk_src设置定时器的时钟源
resolution_hz设置定时器的预期分辨率
count_mode设置定时器的计数模式
period_ticks设置定时器的周期Tick 单位
update_period_on_empty当定时器计数为零时是否更新周期值
update_period_on_sync当定时器接收同步信号时是否更新周期值
  • 不同组的定时器彼此独立

  • Tick 分辨率通过 resolution_hz 参数设置

  • 内部驱动会根据时钟源和分辨率自动设置合适的分频器

  • 分配成功后,mcpwm_new_timer() 将返回一个指向已分配定时器的指针

  • 当 MCPWM 组中没有空闲定时器时,将返回 ESP_ERR_NOT_FOUND 错误

  • 调用 mcpwm_del_timer() 函数将释放已分配的定时器

MCPWM 操作器

调用 mcpwm_new_operator() 函数,以配置结构体 mcpwm_operator_config_t 为参数,分配一个 MCPWM 操作器为对象

结构体定义为:

配置参数描述范围/值
group_id指定 MCPWM 组 ID[0, SOC_MCPWM_GROUPS - 1]
intr_priority设置中断的优先级0:默认优先级,其他:指定优先级
update_gen_action_on_tez当定时器计数为零时是否更新生成器操作
update_gen_action_on_tep当定时器计数达到峰值时是否更新生成器操作
update_gen_action_on_sync当定时器接收同步信号时是否更新生成器操作
update_dead_time_on_tez当定时器计数为零时是否更新死区时间
update_dead_time_on_tep当定时器计数达到峰值时是否更新死区时间
update_dead_time_on_sync当定时器接收同步信号时是否更新死区时间
  • 不同组的操作器彼此独立
  • 定时器通过 mcpwm_operator_connect_timer() 连接到操作器

TEZ (Timer Equal Zero) - 定时器计数为零时触发
TEP (Timer Equal Period) - 定时器计数达到峰值时触发

  • 分配成功后,mcpwm_new_operator() 将返回一个指向已分配操作器的指针否则,函数将返回错误代码
  • 当 MCPWM 组中没有空闲操作器时,将返回 ESP_ERR_NOT_FOUND 错误
  • 调用 mcpwm_del_operator() 函数将释放已分配的操作器
MCPWM 比较器

调用 mcpwm_new_comparator() 函数,以一个 MCPWM 操作器句柄和配置结构体 mcpwm_comparator_config_t 为参数,分配一个 MCPWM 比较器为对象

操作器句柄由 mcpwm_new_operator() 生成,结构体定义为:

配置参数描述范围/值
intr_priority设置中断的优先级0:默认优先级,其他:指定优先级
update_cmp_on_tez当定时器计数为零时是否更新比较阈值
update_cmp_on_tep当定时器计数达到峰值时是否更新比较阈值
update_cmp_on_sync当定时器接收同步信号时是否更新比较阈值
  • 分配成功后,mcpwm_new_comparator() 将返回一个指向已分配比较器的指针
  • 当 MCPWM 操作器中没有空闲比较器时,将返回 ESP_ERR_NOT_FOUND 错误
  • 调用 mcpwm_del_comparator() 函数将释放已分配的比较器

MCPWM 中还有另外一种比较器 —— “事件比较器”,它不能直接控制 PWM 的输出,只能用来产生 EMT 子系统中使用到的事件,事件比较器能够设置的阈值也是可配的。调用 mcpwm_new_event_comparator() 函数可以申请一个事件比较器,该函数返回的句柄类型和 mcpwm_new_comparator() 函数一样,但是需要的配置结构体是不同的。事件比较器的配置位于 mcpwm_event_comparator_config_t

MCPWM 生成器

调用 mcpwm_new_generator() 函数,以 MCPWM 操作器句柄和配置结构体 mcpwm_generator_config_t 为参数,分配一个 MCPWM 生成器为对象

结构体定义为:

配置参数描述范围/值
gen_gpio_num设置生成器使用的 GPIO 编号GPIO 引脚编号
invert_pwm设置是否反相 PWM 信号
pull_up设置是否启用内部上拉电阻
pull_down设置是否启用内部下拉电阻
  • gen_gpio_num 指定输出 PWM 信号的 GPIO 引脚,GPIO 引脚需要支持 MCPWM 功能

  • invert_pwm 启用时,PWM 信号会被反相(高电平变低电平,低电平变高电平)

  • 分配成功后,mcpwm_new_generator() 将返回一个指向已分配生成器的指针

  • 当 MCPWM 操作器中没有空闲生成器时,将返回 ESP_ERR_NOT_FOUND 错误

  • 调用 mcpwm_del_generator() 函数将释放已分配的生成器

MCPWM 同步源

同步源是用于同步 MCPWM 定时器和 MCPWM 捕获定时器的,同步源有三种类型:来自 GPIO 的同步源、由软件生成的同步源以及由 MCPWM 定时器事件生成的同步源

要分配一个 GPIO 同步源,可以调用 mcpwm_new_gpio_sync_src() 函数,并将配置结构 mcpwm_gpio_sync_src_config_t 作为参数

配置结构定义如下:

配置参数描述范围/值
group_id设置 MCPWM 组 ID[0, SOC_MCPWM_GROUPS - 1]
gpio_num设置同步源使用的 GPIO 编号GPIO 引脚编号
active_neg设置同步信号是否在下降沿上激活
pull_up设置是否启用内部上拉电阻
pull_down设置是否启用内部下拉电阻
  • 不同组的 GPIO 同步源完全独立,组 0 中的 GPIO 同步源不能被组 1 中的计时器检测到
  • gpio_num 指定用于同步信号的 GPIO 引脚

如果分配成功, mcpwm_new_gpio_sync_src() 将返回一个指向已分配的同步源对象的指针。否则,它将返回一个错误代码。具体来说,当 MCPWM 组中没有更多可用的 GPIO 同步源时,此函数将返回 ESP_ERR_NOT_FOUND 错误

要分配一个计时器事件同步源,您可以调用 mcpwm_new_timer_sync_src() 函数,以配置结构 mcpwm_timer_sync_src_config_t 作为参数。配置结构定义如下

配置参数描述
timer_event指定在什么计时器事件上生成同步信号
propagate_input_sync设置是否传播输入同步信号
  • timer_event 定义触发同步信号的计时器事件类型

  • propagate_input_sync 为启用时,输入同步信号会被路由到同步输出端

  • 同步信号可用于多个计时器之间的协调工作

  • 如果分配成功, mcpwm_new_timer_sync_src() 将返回指向已分配的同步源对象的指针

  • 如果同步源之前已从同一定时器分配,此函数将返回 ESP_ERR_INVALID_STATE 错误

要分配一个软件同步源,可以调用 mcpwm_new_soft_sync_src() 函数,并将配置结构 mcpwm_soft_sync_config_t 作为参数;目前,此配置结构保留

  • 如果分配成功, mcpwm_new_soft_sync_src() 将返回指向已分配的同步源对象的指针
  • 当没有剩余内存用于同步源对象时,此函数将返回 ESP_ERR_NO_MEM 错误
  • 为了让软件同步源生效,需要调用 mcpwm_soft_sync_activate()

调用 mcpwm_del_sync_src() 函数将释放已分配的同步源对象。此函数适用于所有类型的同步源

定时器操作和事件

定时器周期

定时器周期由 mcpwm_timer_config_t 中的 mcpwm_timer_config_t::period_ticks 参数初始化

  • 可以通过调用 mcpwm_timer_set_period() 函数在运行时更新周期
  • 新周期将根据 mcpwm_timer_config_t 中设置的 mcpwm_timer_config_t::update_period_on_emptymcpwm_timer_config_t::update_period_on_sync 参数而生效
  • 如果它们都没有被设置,定时器周期将立即生效
注册定时器回调

MCPWM 定时器可以在运行时生成不同的事件

  • 如果有一些在特定事件发生时应被调用的函数,应该通过调用 mcpwm_timer_register_event_callbacks() 将函数注册到中断服务例程中
  • 回调函数原型在 mcpwm_timer_event_cb_t 中声明,所有支持的事件回调函数列表在 mcpwm_timer_event_callbacks_t
配置参数描述范围/值
on_full设置定时器计数到峰值时的回调函数函数指针
on_empty设置定时器计数到零时的回调函数函数指针
on_stop设置定时器停止时的回调函数函数指针

上述回调函数在 ISR 上下文中被调用,因此不应尝试阻塞

  • 比如可以在函数中确保仅调用带 ISR 后缀的 FreeRTOS API

mcpwm_timer_register_event_callbacks() 函数的 user_data 参数用于保存上下文,它将直接传递给每个回调函数

这个函数将延迟安装 MCPWM 计时器的中断服务,而不会启用它,它只允许在 mcpwm_timer_enable() 之前调用,否则将返回 ESP_ERR_INVALID_STATE 错误。另见启用和禁用计时器以获取更多信息

启用和禁用定时器

在进行定时器 IO 控制之前,需要先启用定时器,通过调用 mcpwm_timer_enable() ,这个函数

  • 切换计时器状态从初始化到启用
  • 如果定时器已经被 mcpwm_timer_register_event_callbacks() 懒加载(lazy installed),则启用中断服务
  • 如果选择了特定的时钟源(例如 PLL_160M 时钟),需要获取正确的电源管理锁
  • 调用 mcpwm_timer_disable() 将使定时器驱动程序恢复到初始状态,禁用中断服务并释放电源管理锁

懒加载是指资源或服务不会在系统启动时立即分配,而是在第一次真正需要使用时才进行分配和初始化

启动和停止定时器

定时器的基本 IO 操作是启动和停止。通过调用 mcpwm_timer_start_stop() 使用不同的 mcpwm_timer_start_stop_cmd_t 命令可以立即启动定时器或在一个特定事件时停止定时器。此外,您甚至可以只启动定时器一轮,这意味着定时器会计数到峰值或零,然后自动停止

分配的 MCPWM 计时器应该通过调用 mcpwm_operator_connect_timer() 连接到一个 MCPWM 操作器,以便操作器可以将其作为时间基准,并生成所需的 PWM 波

  • 确保 MCPWM 计时器和操作器在同一组中,否则,该函数将返回 ESP_ERR_INVALID_ARG 错误。

mcpwm_timer_start_stop_cmd_t MCPWM 定时器命令枚举,用于指定启动或停止定时器的方式

枚举值描述
MCPWM_TIMER_STOP_EMPTYMCPWM 定时器在下一次计数达到零时停止
MCPWM_TIMER_STOP_FULLMCPWM 定时器在下一次计数达到峰值时停止
MCPWM_TIMER_START_NO_STOPMCPWM 定时器开始计数,直到收到停止命令才停止
MCPWM_TIMER_START_STOP_EMPTYMCPWM 定时器开始计数并在下一次计数达到零时停止
MCPWM_TIMER_START_STOP_FULLMCPWM 定时器开始计数并在下一次计数达到峰值时停止
  • “零” 指的是定时器计数值为 0 的状态
  • “峰值” 指的是定时器计数达到最大值(period_ticks)的状态

比较器操作和事件

分配的 MCPWM 计时器应该通过调用 mcpwm_operator_connect_timer() 连接到一个 MCPWM 操作器,以便操作器可以将其作为时间基准,并生成所需的 PWM 波

  • 确保 MCPWM 计时器和操作器在同一组中,否则函数将返回 ESP_ERR_INVALID_ARG 错误
  • mcpwm_comparator_event_callbacks_t::on_reach 设置当定时器计数器等于比较值时,比较器的回调函数

中断

  • 回调函数会提供类型为 mcpwm_compare_event_data_t 的事件特定数据;回调函数在 ISR 上下文中被调用,因此不应尝试阻塞
  • 参数 user_datamcpwm_comparator_register_event_callbacks() 函数用于保存作者自己的上下文,它将直接传递给回调函数
  • 这个函数将延迟安装 MCPWM 比较器的中断服务,而该服务只能在 mcpwm_del_comparator() 中移除

为事件比较器注册事件回调是不支持的,因为它无法产生任何中断

设置比较值

通过调用 mcpwm_comparator_set_compare_value() 在运行时设置 MCPWM 比较器的比较值

注意事项
新设置的比较值不会立即生效,更新时机由以下配置参数控制:

  • update_cmp_on_tez - 在定时器计数为零时更新
  • update_cmp_on_tep - 在定时器计数达到峰值时更新
  • update_cmp_on_sync - 在接收到同步信号时更新

确保操作器已经通过 mcpwm_operator_connect_timer() 连接到一个 MCPWM 定时器,否则将返回错误代码 ESP_ERR_INVALID_STATE

比较值不应超过定时器的计数值峰值,否则比较事件将永远不会被触发

经典 PWM 波形生成器

波形是对称还是非对称由 MCPWM 计时器的计数模式决定
波形对的 激活电平 取决于占空比较小的 PWM 波形的电平
PWM 波形的周期由定时器的周期和计数模式决定
PWM 波形的占空比由发生器的各种动作组合决定

单边非对称波形 - 主动高

static void gen_action_config(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb, mcpwm_cmpr_handle_t cmpa, mcpwm_cmpr_handle_t cmpb)
{
    // 配置生成器 A 在定时器事件上的动作
    // 当定时器向上计数到空值(EMPTY)时,将生成器 A 的输出设置为高电平
    ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(gena,
                    MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)));

    // 配置生成器 A 在比较器事件上的动作  
    // 当定时器向上计数且计数值等于比较器 cmpa 的阈值时,将生成器 A 的输出设置为低电平
    ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena,
                    MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW)));

    // 配置生成器B在定时器事件上的动作
    // 当定时器向上计数到空值(EMPTY)时,将生成器 B 的输出设置为高电平
    ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb,
                    MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)));
    
    // 配置生成器B在比较器事件上的动作
    // 当定时器向上计数且计数值等于比较器 cmpb 的阈值时,将生成器 B 的输出设置为低电平  
    ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb,
                    MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW)));
}

Example: 同步 PWM 波生成器

ESP-IDF 官方示例项目,项目地址:mcpwm_sync

Workflow
flowchart TD
	A[系统初始化] --> B[创建定时器,并配置每个定时器]
	B --> C[创建操作器,并初始化操作器]
	C --> D[配置操作器的定时器]
	D --> E[创建比较器]
	E --> F[配置比较器,并将比较器与操作器进行绑定]
	F --> G[设定比较器的阈值]
	G --> H[创建生成器,并配置生成器]
	H --> I[将操作器与生成器进行绑定]
	I --> J[设置生成器的工作模式]
	J --> K[逐个启动定时器]
	K --> L[同步波形]
同步波形的策略

MCPWM 定时器接收到同步信号后,定时器将强制进入一个预定义的相位,该相位由计数值和计数方向共同决定。调用 mcpwm_timer_set_phase_on_sync(),设置同步相位

同步相位配置定义在 mcpwm_timer_sync_phase_config_t 结构体中:

mcpwm_timer_sync_phase_config_t::sync_src 设置同步信号源,当这个参数设置为 NULL 时,驱动器将禁用 MCPWM 定时器的同步功能
mcpwm_timer_sync_phase_config_t::count_value 设置接收同步信号后加载至计数器的值
mcpwm_timer_sync_phase_config_t::direction 设置接收同步信号后的计数方向

当 MCPWM 定时器在 MCPWM_TIMER_COUNT_MODE_UP_DOWN 模式下工作时,需要特别注意:在该模式下,计数器范围 [0 -> peak-1] 属于 递增 阶段, [peak -> 1] 属于 递减 阶段。因此,如果将 mcpwm_timer_sync_phase_config_t:: count_value 设置为零,则可能还需要将 mcpwm_timer_sync_phase_config_t:: direction 设置为 MCPWM_TIMER_DIRECTION_UP,否则,计时器将继续维持递减阶段,计数值会下溢至峰值

  • 定时器间同步适合内部多路 PWM 协同
  • 外部 GPIO 同步适合与外部设备协同
  • 软件同步提供了最大灵活性,由软件随时触发

同步时,定时器会根据配置的相位值和方向重载计数器,实现精确的同步控制。

来自 GPIO 的同步源

这种同步策略需要额外占用一个 GPIO,定时器的同步输入源也可以来自 GPIO 矩阵的外部同步信号(如 SYNCO、SYNC1、SYNC2),这样可以通过外部事件(如外部控制器信号)来同步 PWM 定时器,实现与外部系统的协同控制

static void example_setup_sync_strategy(mcpwm_timer_handle_t timers[])
{
    //    +----GPIO----+
    //    |     |      |
    //    |     |      |
    //    v     v      v
    // timer0 timer1 timer2
    gpio_config_t sync_gpio_conf = {
        // 配置 GPIO 引脚
        .mode         = GPIO_MODE_OUTPUT,        // 设置为输出模式
        .pin_bit_mask = BIT(EXAMPLE_SYNC_GPIO),  // 设置同步 GPIO 引脚
    };
    ESP_ERROR_CHECK(gpio_config(&sync_gpio_conf));  // 配置 GPIO 引脚

    ESP_LOGI(TAG, "Create GPIO sync source");
    mcpwm_sync_handle_t gpio_sync_source = NULL;  // 创建 GPIO 同步源
    // 配置 GPIO 同步源
    mcpwm_gpio_sync_src_config_t gpio_sync_config = {
        .group_id         = 0,                  // GPIO 同步源所在的 MCPWM 组
        .gpio_num         = EXAMPLE_SYNC_GPIO,  // 同步 GPIO 引脚
        .flags.pull_down  = true,               // 同步 GPIO 引脚下拉
        .flags.active_neg = false,              // 不使用负边沿触发,这意味着同步事件将在GPIO信号的上升沿时触发
    };
    ESP_ERROR_CHECK(mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_source));  // 创建 GPIO 同步源

    ESP_LOGI(TAG, "Set timers to sync on the GPIO");
    mcpwm_timer_sync_phase_config_t sync_phase_config = {
        // 配置同步相位
        .count_value = 0,                         // 同步计数值,当同步事件发生时,计数器将被重置为此值
        .direction   = MCPWM_TIMER_DIRECTION_UP,  // 向上计数
        .sync_src    = gpio_sync_source,          // 同步源
    };
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));  // 设置定时器同步相位
    }

    ESP_LOGI(TAG, "Trigger a pulse on the GPIO as a sync event");  // 触发 GPIO 同步事件
    ESP_ERROR_CHECK(gpio_set_level(EXAMPLE_SYNC_GPIO, 0));
    ESP_ERROR_CHECK(gpio_set_level(EXAMPLE_SYNC_GPIO, 1));
    ESP_ERROR_CHECK(gpio_reset_pin(EXAMPLE_SYNC_GPIO));  // 重置 GPIO 引脚
}
由软件生成的同步源

通过写寄存器触发软件同步事件,软件同步允许 MCU 通过代码控制定时器同步,适合需要灵活、可编程控制同步时机的应用

static void example_setup_sync_strategy(mcpwm_timer_handle_t timers[])
{
    //       soft
    //        |
    //        v
    //    +-timer0--+
    //    |         |
    //    v         v
    // timer1    timer2
    ESP_LOGI(TAG, "Create software sync source");
    mcpwm_sync_handle_t soft_sync_source      = NULL;  // 创建软件同步源
    mcpwm_soft_sync_config_t soft_sync_config = {};    // 配置软件同步源,目前,此配置结构留待未来使用
    ESP_ERROR_CHECK(mcpwm_new_soft_sync_src(&soft_sync_config, &soft_sync_source));  // 创建软件同步源

    ESP_LOGI(TAG, "Create timer sync source to propagate the sync event");
    mcpwm_sync_handle_t timer_sync_source;  // 创建定时器同步源
    // 配置定时器同步源
    mcpwm_timer_sync_src_config_t timer_sync_config = {
        .flags.propagate_input_sync = true,  // 允许同步事件传播到其他定时器
    };
    ESP_ERROR_CHECK(mcpwm_new_timer_sync_src(timers[0], &timer_sync_config, &timer_sync_source));

    ESP_LOGI(TAG, "Set sync phase for timers");
    // 设置定时器同步相位
    mcpwm_timer_sync_phase_config_t sync_phase_config = {
        .count_value = 0,                         // 同步计数值,当同步事件发生时,计数器将被重置为此值
        .direction   = MCPWM_TIMER_DIRECTION_UP,  // 向上计数
        .sync_src    = soft_sync_source,          // 同步源
    };
    ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[0], &sync_phase_config));  // 设置第一个定时器的同步相位
    sync_phase_config.sync_src = timer_sync_source;  // 更新同步源为定时器同步源,以便其他定时器可以接收同步事件
    for (int i = 1; i < 2; ++i) {
        ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));  // 设置其他定时器的同步相位
    }

    ESP_LOGI(TAG, "Trigger the software sync event");
    ESP_ERROR_CHECK(mcpwm_soft_sync_activate(soft_sync_source));  // 触发软件同步事件
}
由 MCPWM 定时器事件生成的同步源

每个 MCPWM 定时器都可以选择来自其他定时器的同步输出作为自己的同步输入,例如,定时器 0 可以选择定时器 1 或定时器 2 的同步输出信号,实现多个定时器之间的相位锁定和同步启动,这种方式适合多路 PWM 输出需要严格同步的场景

static void example_setup_sync_strategy(mcpwm_timer_handle_t timers[])
{
    //          +->timer1
    // (TEZ)    |
    // timer0---+
    //          |
    //          +->timer2
    ESP_LOGI(TAG, "Create TEZ sync source from timer0");
    mcpwm_sync_handle_t timer_sync_source = NULL;  // 创建定时器同步源
    // 配置定时器同步源
    mcpwm_timer_sync_src_config_t timer_sync_config = {
        .timer_event = MCPWM_TIMER_EVENT_EMPTY,  // 在定时器空事件时生成同步事件
    };

    // 创建定时器同步源,以定时器 0 为源
    ESP_ERROR_CHECK(mcpwm_new_timer_sync_src(timers[0], &timer_sync_config, &timer_sync_source));

    ESP_LOGI(TAG, "Set other timers sync to the first timer");
    mcpwm_timer_sync_phase_config_t sync_phase_config = {
        .count_value = 0,                         // 同步计数值,当同步事件发生时,计数器将被重置为此值
        .direction   = MCPWM_TIMER_DIRECTION_UP,  // 向上计数
        .sync_src    = timer_sync_source,         // 同步源
    };
    for (int i = 1; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config));  // 设置定时器同步相位
    }

    ESP_LOGI(TAG, "Wait some time for the timer TEZ event");
    vTaskDelay(pdMS_TO_TICKS(10));
}

多路 PWM 输出

以 ESP32-S3 为例

ESP32-S3 有两个 MCPWM 控制器,分别为 MCPWM0 和 MCPWM1。这两个控制器每个都包含一个时钟分频器、三个 PWM 定时器、三个 PWM 操作器和一个捕获模块,可用于驱动数字马达和智能灯等
详见: Espressif ESP32-S3 技术参考手册 P1245 第 16 章

因为每个 MCPWM 操作器有两个 MCPWM 生成器,可以输出一对 PWM 波形(即两路输出),因此每个 MCPWM 外设可以输出 6 路 PWM;两个 MCPWM 外设合计,ESP32-S3 可以同时输出 12 路 MCPWM 信号

每个 MCPWM 操作器有两个比较器。每个 MCPWM 控制器包含三个操作器,因此单个 MCPWM 控制器最多有 6 个比较器。ESP32-S3 有两个 MCPWM 控制器,所以总共可以有 12 个 MCPWM 比较器。这意味着可以实现 12 路占空比独立可调的 PWM 输出,满足如多舵机控制等需求

在官方给的 mcpwm_sync 示例中,使用了一个 MCPWM 外设,其中的每个 MCPWM 生成器只生成了一路 PWM,因此,当需要程序生成 >3 路的 PWM 时有两种选择:

  1. 通过两个 MCPWM 外设进行输出
    1. 对于 >6 路的 PWM 输出或 >3 路频率不同的 PWM 输出,只能采用这种方式
  2. 通过一个 MCPWM 的两个 MCPWM 生成器分别输出
通过两个 MCPWM 外设进行多路输出
/**
 * @brief 使用两个 MCPWM 外设,每个外设输出两路 PWM 波形
 * @brief Use two MCPWM peripherals, each outputs two PWM waveforms
 */
void app_main(void)
{
    ESP_LOGI(TAG, "Create timers");
    // 创建两组定时器,分别用于两个 MCPWM 外设
    mcpwm_timer_handle_t timers_group_0[2];
    mcpwm_timer_handle_t timers_group_1[2];

    // 配置定时器
    mcpwm_timer_config_t timer_config_0 = {
        .clk_src       = MCPWM_TIMER_CLK_SRC_DEFAULT,  // 时钟源
        .group_id      = 0,                            // 定时器组ID
        .resolution_hz = EXAMPLE_TIMER_RESOLUTION_HZ,  // 定时器分辨率
        .period_ticks  = EXAMPLE_TIMER_PERIOD,         // 定时器周期
        .count_mode    = MCPWM_TIMER_COUNT_MODE_UP,    // 计数模式为向上计数
    };
    mcpwm_timer_config_t timer_config_1 = {
        .clk_src       = MCPWM_TIMER_CLK_SRC_DEFAULT,
        .group_id      = 1,
        .resolution_hz = EXAMPLE_TIMER_RESOLUTION_HZ,
        .period_ticks  = EXAMPLE_TIMER_PERIOD,
        .count_mode    = MCPWM_TIMER_COUNT_MODE_UP,
    };

    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config_0, &timers_group_0[i]));
        ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config_1, &timers_group_1[i]));
    }

    // 创建两组操作器,分别用于两个 MCPWM 外设
    ESP_LOGI(TAG, "Create operators");
    mcpwm_oper_handle_t operators_0[2];
    mcpwm_oper_handle_t operators_1[2];
    // 配置操作器
    mcpwm_operator_config_t operator_config_0 = {
        .group_id = 0,
        // 操作器需要在与定时器相同的组中
    };
    mcpwm_operator_config_t operator_config_1 = {
        .group_id = 1,
    };

    for (int i = 0; i < 2; ++i) {
        ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config_0, &operators_0[i]));
        ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config_1, &operators_1[i]));
    }

    // 设置操作器相应的定时器
    ESP_LOGI(TAG, "Connect timers and operators with each other");
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_operator_connect_timer(operators_0[i], timers_group_0[i]));
        ESP_ERROR_CHECK(mcpwm_operator_connect_timer(operators_1[i], timers_group_1[i]));
    }

    // 创建比较器
    ESP_LOGI(TAG, "Create comparators");
    mcpwm_cmpr_handle_t comparators_0[2];
    mcpwm_cmpr_handle_t comparators_1[2];
    mcpwm_comparator_config_t compare_config = {
        .flags.update_cmp_on_tez = true,
        // 当定时器计数为零时更新比较阈值
    };

    // 设置操作器相应的比较器
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_new_comparator(operators_0[i], &compare_config, &comparators_0[i]));
        ESP_ERROR_CHECK(mcpwm_new_comparator(operators_1[i], &compare_config, &comparators_1[i]));
        // 初始化每个比较器的比较值
        ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparators_0[i], 500));
        ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparators_1[i], 500));
        // 当比较器的值为设定值时,比较器将触发事件,此时定时器设置值为1000
        // 即生成的 PWM 波形的占空比为 比较值 / 定时器设定值 * 100%
    }

    // 创建生成器
    ESP_LOGI(TAG, "Create generators");
    mcpwm_gen_handle_t generators_0[2];
    mcpwm_gen_handle_t generators_1[2];
    // 配置生成器
    const int gen_gpios_0[2]            = {EXAMPLE_GEN_GPIO0, EXAMPLE_GEN_GPIO1};
    const int gen_gpios_1[2]            = {EXAMPLE_GEN_GPIO2, EXAMPLE_GEN_GPIO3};
    mcpwm_generator_config_t gen_config = {};
    for (int i = 0; i < 2; i++) {
        gen_config.gen_gpio_num = gen_gpios_0[i];
        // 设置操作器相应的生成器
        ESP_ERROR_CHECK(mcpwm_new_generator(operators_0[i], &gen_config, &generators_0[i]));
        gen_config.gen_gpio_num = gen_gpios_1[i];
        ESP_ERROR_CHECK(mcpwm_new_generator(operators_1[i], &gen_config, &generators_1[i]));
    }

    // 设置生成器的工作模式
    ESP_LOGI(TAG, "Set generator actions on timer and compare event");
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
            generators_0[i],
            // 当定时器计数为零且向上计数时,输出高电平
            MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)));
        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
            generators_0[i],
            // 当比较匹配且向上计数时,输出低电平
            MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparators_0[i], MCPWM_GEN_ACTION_LOW)));

        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
            generators_1[i],
            MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)));
        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
            generators_1[i],
            MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparators_1[i], MCPWM_GEN_ACTION_LOW)));
    }

    // 逐个使能并启动定时器
    ESP_LOGI(TAG, "Start timers one by one, so they are not synced");
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_timer_enable(timers_group_0[i]));
        ESP_ERROR_CHECK(mcpwm_timer_start_stop(timers_group_0[i], MCPWM_TIMER_START_NO_STOP));
        ESP_ERROR_CHECK(mcpwm_timer_enable(timers_group_1[i]));
        ESP_ERROR_CHECK(mcpwm_timer_start_stop(timers_group_1[i], MCPWM_TIMER_START_NO_STOP));
        vTaskDelay(pdMS_TO_TICKS(10));
    }
    vTaskDelay(pdMS_TO_TICKS(100));

    // 在同步之前添加一个“空闲”阶段,可以帮助区分同步前后的波形
    ESP_LOGI(TAG, "Force the output level to low, timer still running");
    // 强制输出低电平,定时器仍在运行
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators_0[i], 0, true));
        ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators_1[i], 0, true));
    }

    ESP_LOGI(TAG, "Setup sync strategy");
    // 设置同步策略
    example_setup_sync_strategy(timers_group_0);
    example_setup_sync_strategy(timers_group_1);

    ESP_LOGI(TAG, "Now the output PWMs should in sync");
    for (int i = 0; i < 2; ++i) {
        // 移除生成器上的强制电平,这样可以再次看到PWM
        ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators_0[i], -1, true));
        ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators_1[i], -1, true));
    }
    vTaskDelay(pdMS_TO_TICKS(100));
}
通过一个 MCPWM 的两个 MCPWM 生成器分别输出
/**
 * @brief 使用一个 MCPWM 外设中的两个操作器,输出四路 PWM 波形
 * @brief Use two operators in one MCPWM peripheral to output four PWM waveforms
 */
void app_main(void)
{
    // 创建定时器组
    ESP_LOGI(TAG, "Create timers");
    mcpwm_timer_handle_t timers_group[2];

    // 配置定时器
    mcpwm_timer_config_t timer_config = {
        .clk_src       = MCPWM_TIMER_CLK_SRC_DEFAULT,  // 时钟源
        .group_id      = 0,                            // 定时器组ID
        .resolution_hz = EXAMPLE_TIMER_RESOLUTION_HZ,  // 定时器分辨率
        .period_ticks  = EXAMPLE_TIMER_PERIOD,         // 定时器周期
        .count_mode    = MCPWM_TIMER_COUNT_MODE_UP,    // 计数模式为向上计数
    };

    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timers_group[i]));
    }

    // 创建操作器
    ESP_LOGI(TAG, "Create operators");
    mcpwm_oper_handle_t operators[2];
    mcpwm_operator_config_t operator_config = {
        .group_id = 0,
    };

    for (int i = 0; i < 2; ++i) {
        ESP_ERROR_CHECK(mcpwm_new_operator(&operator_config, &operators[i]));
    }

    // 设置操作器的定时器
    ESP_LOGI(TAG, "Connect timers and operators with each other");
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_operator_connect_timer(operators[i], timers_group[i]));
    }

    // 创建比较器
    // 为每一个生成器(a,b 两路)创建一个比较器,一共四个比较器
    ESP_LOGI(TAG, "Create comparators");
    mcpwm_cmpr_handle_t comparators_a[2];
    mcpwm_cmpr_handle_t comparators_b[2];
    mcpwm_comparator_config_t compare_config = {
        .flags.update_cmp_on_tez = true,
        // 当定时器计数为零时更新比较阈值
    };
    for (int i = 0; i < 2; i++) {
        // 分配比较器
        ESP_ERROR_CHECK(mcpwm_new_comparator(operators[i], &compare_config, &comparators_a[i]));
        ESP_ERROR_CHECK(mcpwm_new_comparator(operators[i], &compare_config, &comparators_b[i]));
        // 初始化每个比较器的比较值
        ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparators_a[i], 500));
        ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparators_b[i], 300));
    }

    // 创建生成器
    ESP_LOGI(TAG, "Create generators");
    mcpwm_gen_handle_t generators_a[2];
    mcpwm_gen_handle_t generators_b[2];
    // 配置生成器
    // 每个生成器对应一个GPIO引脚
    const int gen_gpios_a[2]            = {EXAMPLE_GEN_GPIO0, EXAMPLE_GEN_GPIO1};
    const int gen_gpios_b[2]            = {EXAMPLE_GEN_GPIO2, EXAMPLE_GEN_GPIO3};
    mcpwm_generator_config_t gen_config = {};
    for (int i = 0; i < 2; i++) {
        gen_config.gen_gpio_num = gen_gpios_a[i];
        // 设置操作器相应的生成器
        ESP_ERROR_CHECK(mcpwm_new_generator(operators[i], &gen_config, &generators_a[i]));
        gen_config.gen_gpio_num = gen_gpios_b[i];
        ESP_ERROR_CHECK(mcpwm_new_generator(operators[i], &gen_config, &generators_b[i]));
    }

    // 设置生成器的工作模式
    ESP_LOGI(TAG, "Set generator actions on timer and compare event");
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
            generators_a[i],
            // 当定时器计数为零且向上计数时,输出高电平
            MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)));
        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
            generators_a[i],
            // 当比较匹配且向上计数时,输出低电平
            MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparators_a[i], MCPWM_GEN_ACTION_LOW)));

        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
            generators_b[i],
            MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH)));
        ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
            generators_b[i],
            MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, comparators_b[i], MCPWM_GEN_ACTION_LOW)));
    }

    // 逐个启动定时器
    ESP_LOGI(TAG, "Start timers one by one, so they are not synced");
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_timer_enable(timers_group[i]));
        ESP_ERROR_CHECK(mcpwm_timer_start_stop(timers_group[i], MCPWM_TIMER_START_NO_STOP));
        vTaskDelay(pdMS_TO_TICKS(10));
    }
    vTaskDelay(pdMS_TO_TICKS(100));

    // 在同步之前添加一个“空闲”阶段,可以帮助区分同步前后的波形
    ESP_LOGI(TAG, "Force the output level to low, timer still running");
    // 强制输出低电平,定时器仍在运行
    for (int i = 0; i < 2; i++) {
        ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators_a[i], 0, true));
        ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators_b[i], 0, true));
    }

    ESP_LOGI(TAG, "Setup sync strategy");
    // 设置同步策略
    example_setup_sync_strategy(timers_group);

    ESP_LOGI(TAG, "Now the output PWMs should in sync");
    for (int i = 0; i < 2; ++i) {
        // 移除生成器上的强制电平,这样就可以再次看到PWM
        ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators_a[i], -1, true));
        ESP_ERROR_CHECK(mcpwm_generator_set_force_level(generators_b[i], -1, true));
    }
    vTaskDelay(pdMS_TO_TICKS(100));
}
### 解决 ESP32S3 编译时 `esp_peripherals.h` 文件缺失问题 当遇到编译错误提示无法找到 `esp_peripherals.h` 文件时,这通常意味着开发环境未能正确配置或缺少必要的库文件。以下是详细的解决方案: #### 1. 确认 Arduino IDE 或 Eclipse 配置无误 确保已成功安装适用于 ESP32 的开发板管理器 URL 并更新至最新版本[^3]。 对于 Arduino IDE 用户,在 **文件>首选项** 中添加额外的开发板管理器链接: ```plaintext https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json ``` 接着通过 **工具>开发板>开发板管理器** 来查找并安装最新的 ESP32 支持包。 #### 2. 更新 ESP-IDF 版本 如果使用的是基于 ESP-IDF 的项目,则需确认所使用的 IDF SDK 是否是最新的稳定版。可以通过官方文档获取如何升级 ESP-IDF 的指导[^1]。 #### 3. 添加外部依赖库路径 有时特定头文件可能并未包含在默认搜索范围内。此时可以在项目的 CMakeLists.txt 或 platformio.ini 文件里指定额外的 include 路径来指向这些资源的位置。例如,在 PlatformIO 中可以这样操作: ```ini lib_deps = ... extra_scripts = pre:scripts/pre:build.py build_flags = -I${PROJECT_DIR}/libs/some_lib/include/ ``` 另外一种方法是在代码顶部手动加入绝对路径导入语句(不推荐),如: ```c++ #include "/path/to/your/library/esp_peripherals.h" ``` 更优的做法是利用组件注册机制让 ESP-IDF 自动处理依赖关系。创建一个简单的 component.mk 文件放在源码目录下,并在里面声明所需的第三方库位置。 #### 4. 检查是否遗漏了某些必需的软件包 部分高级特性可能会依赖于其他非核心模块,比如 Wi-Fi 外围设备控制等功能就需要对应的驱动支持。可以从 Espressif GitHub 仓库克隆完整的 peripheral 组件集合到本地工作区中去尝试解决问题。 #### 5. 清理缓存重新构建 有时候旧有的编译残留物也会引发此类冲突。建议清理整个 build output 后再做一次全量编译动作。具体命令取决于所选用的具体IDE, 对应Eclipse而言可通过菜单栏中的 Project -> Clean... 功能实现;而在 command line 下则可执行 make clean 命令清除临时产物后再试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值