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_empty
和mcpwm_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_EMPTY | MCPWM 定时器在下一次计数达到零时停止 |
MCPWM_TIMER_STOP_FULL | MCPWM 定时器在下一次计数达到峰值时停止 |
MCPWM_TIMER_START_NO_STOP | MCPWM 定时器开始计数,直到收到停止命令才停止 |
MCPWM_TIMER_START_STOP_EMPTY | MCPWM 定时器开始计数并在下一次计数达到零时停止 |
MCPWM_TIMER_START_STOP_FULL | MCPWM 定时器开始计数并在下一次计数达到峰值时停止 |
- “零” 指的是定时器计数值为 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_data
的mcpwm_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 时有两种选择:
- 通过两个 MCPWM 外设进行输出
- 对于 >6 路的 PWM 输出或 >3 路频率不同的 PWM 输出,只能采用这种方式
- 通过一个 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));
}