mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 03:00:34 +03:00
docs: restructure MCPWM programming guide
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
===========================
|
||||
电机控制脉宽调制器 (MCPWM)
|
||||
===========================
|
||||
|
||||
:link_to_translation:`en:[English]`
|
||||
|
||||
从这里开始
|
||||
============
|
||||
|
||||
MCPWM 将计数器转换为时序精确的输出边沿。当 LEDC 的简单 PWM 已无法满足需求时,可使用 MCPWM:电机桥需要互补输出和死区,逆变器需要同步相位,传感器则需要精确测量脉宽。
|
||||
|
||||
最小可用的 MCPWM 设计由四个对象构成::doc:`定时器 <mcpwm_timer>` 提供时间基准,:doc:`操作器 <mcpwm_operator>` 管理波形资源,:doc:`比较器 <mcpwm_cmpr>` 决定边沿位置,:doc:`生成器 <mcpwm_gen>` 驱动 GPIO。其他模块均在此基础上扩展。
|
||||
|
||||
构建一路 PWM 输出
|
||||
==================
|
||||
|
||||
初次生成 PWM 输出时,请按下图从左至右创建对象。主线上各阶段按角色配色:时间基准(蓝色)、操作器核心(紫色)、波形配置(青色)、启用与输出(绿色)。琥珀色节点为基础输出正常后按需添加的扩展,红色节点为安全制动。只有在所有输出动作均已配置完成后,才启动定时器。
|
||||
|
||||
.. mermaid::
|
||||
|
||||
flowchart LR
|
||||
T1["1. 创建定时器<br/>mcpwm_new_timer"]:::time
|
||||
O1["2. 创建操作器<br/>mcpwm_new_operator"]:::core
|
||||
LINK["3. 连接时间基准<br/>mcpwm_operator_connect_timer"]:::core
|
||||
C1["4. 创建比较器<br/>mcpwm_new_comparator"]:::wave
|
||||
G1["5. 创建生成器<br/>mcpwm_new_generator"]:::wave
|
||||
A1["6. 描述边沿<br/>mcpwm_generator_set_action_on_*_event"]:::wave
|
||||
RUN["7. 启用并启动<br/>mcpwm_timer_enable<br/>mcpwm_timer_start_stop"]:::run
|
||||
PIN["GPIO 输出 PWM"]:::output
|
||||
|
||||
T1 --> O1 --> LINK --> C1 --> G1 --> A1 --> RUN --> PIN
|
||||
|
||||
DT["死区<br/>mcpwm_generator_set_dead_time"]:::optional
|
||||
BR["故障与制动<br/>mcpwm_new_*_fault<br/>mcpwm_operator_set_brake_on_fault"]:::safety
|
||||
SY["相位同步<br/>mcpwm_new_*_sync_src<br/>mcpwm_timer_set_phase_on_sync"]:::optional
|
||||
CA["载波调制<br/>mcpwm_operator_apply_carrier"]:::optional
|
||||
|
||||
A1 -. 扩展 .-> DT
|
||||
O1 -. 保护 .-> BR
|
||||
T1 -. 对齐 .-> SY
|
||||
O1 -. 调制 .-> CA
|
||||
|
||||
classDef time fill:#dbeafe,stroke:#2563eb,color:#172554
|
||||
classDef core fill:#ede9fe,stroke:#7c3aed,color:#2e1065
|
||||
classDef wave fill:#cffafe,stroke:#0891b2,color:#164e63
|
||||
classDef run fill:#dcfce7,stroke:#16a34a,color:#14532d
|
||||
classDef output fill:#bbf7d0,stroke:#15803d,color:#14532d
|
||||
classDef optional fill:#fef3c7,stroke:#d97706,color:#78350f
|
||||
classDef safety fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
|
||||
|
||||
下面这段代码创建一路 20 kHz、30% 占空比的 PWM 输出,可作为阅读后续各页前的整体参考。它展示了对象的创建顺序,也说明了运行时最常改动的其实是比较器,而不是重新配置整条链路。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
mcpwm_oper_handle_t oper = NULL;
|
||||
mcpwm_cmpr_handle_t comparator = NULL;
|
||||
mcpwm_gen_handle_t generator = NULL;
|
||||
|
||||
// 1 MHz → 1 tick = 1 µs
|
||||
// 50 ticks → 50 µs 周期 → 20 kHz
|
||||
ESP_ERROR_CHECK(mcpwm_new_timer(
|
||||
&(mcpwm_timer_config_t) {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1000000,
|
||||
.period_ticks = 50,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
},
|
||||
&timer));
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_new_operator(
|
||||
&(mcpwm_operator_config_t) {
|
||||
.group_id = 0,
|
||||
},
|
||||
&oper));
|
||||
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer));
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_new_comparator(
|
||||
oper,
|
||||
&(mcpwm_comparator_config_t) {
|
||||
.flags.update_cmp_on_tez = true,
|
||||
},
|
||||
&comparator));
|
||||
// 15 / 50 = 30% 占空比
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 15));
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_new_generator(
|
||||
oper,
|
||||
&(mcpwm_generator_config_t) {
|
||||
.gen_gpio_num = 18,
|
||||
},
|
||||
&generator));
|
||||
|
||||
// 定时器归零 → 输出 HIGH;比较器匹配 → 输出 LOW
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
|
||||
generator,
|
||||
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(
|
||||
generator,
|
||||
MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP,
|
||||
comparator,
|
||||
MCPWM_GEN_ACTION_LOW)));
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_timer_enable(timer));
|
||||
ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
|
||||
// 运行时修改比较值即可调整占空比,无需重建生成器动作。
|
||||
// 25 / 50 = 50% 占空比
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 25));
|
||||
|
||||
定时器的 ``resolution_hz`` 和 ``period_ticks`` 确定时序刻度;比较器的 ``compare_value`` 在该刻度中选择边沿位置;生成器动作 API 决定在定时器边界或比较器越过阈值时输出何种电平。这种分工也便于调参:改变定时器可调整频率,改变比较器可调整占空比或边沿位置,改变生成器动作可调整极性或波形形状。
|
||||
|
||||
波形配置完成后,调用 :cpp:func:`mcpwm_timer_enable()` 和 :cpp:func:`mcpwm_timer_start_stop()`。运行时应通过 :cpp:func:`mcpwm_comparator_set_compare_value()` 更新比较器,而不是重新配置生成器动作。仅在应用需要时添加对应扩展:半桥使用死区,安全路径使用故障与制动,相位对齐使用同步,隔离式驱动使用载波。
|
||||
|
||||
功能地图
|
||||
========
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:widths: 16 36 34 24
|
||||
|
||||
* - 目标
|
||||
- 先看哪些页
|
||||
- 关键 API
|
||||
- 典型应用
|
||||
* - 输出单路 PWM
|
||||
- :doc:`定时器 <mcpwm_timer>` -> :doc:`操作器 <mcpwm_operator>` -> :doc:`比较器 <mcpwm_cmpr>`
|
||||
|
||||
:doc:`生成器 <mcpwm_gen>`
|
||||
- :cpp:func:`mcpwm_new_timer`
|
||||
|
||||
:cpp:func:`mcpwm_new_comparator`
|
||||
|
||||
``mcpwm_generator_set_action_on_*_event``
|
||||
- 舵机、调光、基础功率控制
|
||||
* - 输出互补半桥 PWM
|
||||
- :doc:`生成器 <mcpwm_gen>` 中的死区小节 + :doc:`故障 <mcpwm_fault>`
|
||||
- :cpp:func:`mcpwm_generator_set_dead_time`
|
||||
|
||||
:cpp:func:`mcpwm_operator_set_brake_on_fault`
|
||||
- 半桥、逆变桥臂
|
||||
* - 多路同频对齐或移相
|
||||
- :doc:`同步 <mcpwm_sync>`
|
||||
- :cpp:func:`mcpwm_timer_set_phase_on_sync`
|
||||
|
||||
:cpp:func:`mcpwm_new_timer_sync_src`
|
||||
- 多相电机、并联变换器
|
||||
* - 测量输入脉宽或周期
|
||||
- :doc:`捕获 <mcpwm_cap>`
|
||||
- :cpp:func:`mcpwm_new_capture_timer`
|
||||
|
||||
:cpp:func:`mcpwm_capture_channel_register_event_callbacks`
|
||||
- HC-SR04、转速计、RC 输入
|
||||
* - 外设间硬件联动
|
||||
- :doc:`ETM <mcpwm_etm>`
|
||||
- :cpp:func:`mcpwm_timer_new_etm_event`
|
||||
|
||||
:cpp:func:`mcpwm_new_event_comparator`
|
||||
- ADC 触发、跨外设定时链路
|
||||
|
||||
本指南中每个页面介绍一个 MCPWM 模块:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
mcpwm_timer
|
||||
mcpwm_operator
|
||||
mcpwm_cmpr
|
||||
mcpwm_gen
|
||||
mcpwm_fault
|
||||
mcpwm_sync
|
||||
mcpwm_cap
|
||||
mcpwm_etm
|
||||
mcpwm_advanced
|
||||
|
||||
资源与生命周期
|
||||
==============
|
||||
|
||||
所有对象都属于一个 MCPWM 组。连接的定时器与操作器必须位于同一组;GPIO 故障源和 GPIO 同步源也只能在本组中使用。硬件资源有限,创建时可能返回 :c:macro:`ESP_ERR_NOT_FOUND`。
|
||||
|
||||
每个对象都由 ``mcpwm_new_*()`` 工厂函数创建并返回一个不透明句柄,由对应的 ``mcpwm_del_*()`` 函数释放,例如 :cpp:func:`mcpwm_new_timer()` 与 :cpp:func:`mcpwm_del_timer()`。先创建父对象,再创建子对象;释放时按相反顺序执行:先删除生成器/比较器,再删除操作器,最后删除定时器。删除定时器前必须禁用它;删除捕获定时器前必须删除其通道。
|
||||
|
||||
组时钟分频器由定时器共享,部分芯片的捕获定时器也共享它。按目标分辨率单调顺序(从高到低或从低到高)创建对象,可避免分频冲突。详见 :doc:`高级主题 <mcpwm_advanced>`。
|
||||
|
||||
术语速查
|
||||
========
|
||||
|
||||
.. list::
|
||||
|
||||
- **TEZ:** Timer equals zero,定时器计数等于零时触发的事件。
|
||||
- **TEP:** Timer equals peak,定时器计数达到峰值时触发的事件。
|
||||
- **定时器(Timer):** MCPWM 的时间基准,决定频率和 Tick 刻度。
|
||||
- **操作器(Operator):** 连接定时器与输出逻辑的容器,管理比较器、生成器、制动、死区和载波。
|
||||
- **比较器(Comparator):** 当计数达到阈值时发出事件,常用于决定边沿位置和占空比。
|
||||
- **生成器(Generator):** 根据定时器/比较器/故障/同步事件输出 GPIO 电平。
|
||||
- **死区(Dead Time):** 在半桥上下管切换之间插入的非重叠时间,避免直通。
|
||||
- **故障(Fault):** 进入保护路径的异常源,可来自 GPIO 或软件。
|
||||
- **制动(Brake):** 故障触发后的输出安全策略。
|
||||
- **CBC:** Cycle By Cycle,故障有效时制动,清除后在周期边界自动恢复。
|
||||
- **OST:** One Shot,一次制动后保持锁存,需软件显式恢复。
|
||||
- **同步(Sync):** 在同步边沿把定时器加载到指定计数值和方向,以实现对齐或移相。
|
||||
- **捕获(Capture):** 对输入边沿打时间戳,用于测脉宽、周期或转速。
|
||||
|
||||
应用示例
|
||||
========
|
||||
|
||||
.. list::
|
||||
|
||||
- :example:`peripherals/mcpwm/mcpwm_servo_control`:RC 舵机的单路 PWM。
|
||||
- :example:`peripherals/mcpwm/mcpwm_bdc_speed_control`:有刷直流电机及速度反馈。
|
||||
- :example:`peripherals/mcpwm/mcpwm_bldc_hall_control`:使用霍尔传感器反馈进行 BLDC 换相。
|
||||
- :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04`:使用 HC-SR04 测量脉宽。
|
||||
- :example:`peripherals/mcpwm/mcpwm_sync`:GPIO、定时器和软件同步。
|
||||
- :example:`peripherals/mcpwm/mcpwm_foc_svpwm_open_loop`:开环 FOC 的三对互补 PWM。
|
||||
|
||||
API 参考
|
||||
========
|
||||
|
||||
通用类型
|
||||
--------
|
||||
|
||||
.. include-build-file:: inc/components/esp_driver_mcpwm/include/driver/mcpwm_types.inc
|
||||
.. include-build-file:: inc/components/esp_hal_mcpwm/include/hal/mcpwm_types.inc
|
||||
@@ -0,0 +1,62 @@
|
||||
==============
|
||||
MCPWM 高级主题
|
||||
==============
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
分辨率与共享时钟
|
||||
================
|
||||
|
||||
``resolution_hz`` 是定时器 Tick 频率,每个 Tick 持续 ``1 / resolution_hz`` 秒。驱动从所选源时钟计算分频器:请求频率能整除时优先精确满足;不能整除时,优先使组时钟尽可能高,再选择最接近的子模块频率。
|
||||
|
||||
组分频器由定时器共享。在捕获时钟来自组时钟的芯片上,捕获定时器也共享它。多个对象需要不同分辨率时,按同一单调顺序(从高到低或从低到高)创建,避免任意混排。
|
||||
|
||||
下面这张时钟树图展示单一源时钟如何逐级分频展开。组分频器在整个组内只固定一次,每个定时器只在它之上再加自己的预分频器:
|
||||
|
||||
.. mermaid::
|
||||
|
||||
flowchart LR
|
||||
src["源时钟<br/>(如 80 MHz)"]:::src -->|"÷ 组分频<br/>整组共享"| grp["组时钟<br/>(如 40 MHz)"]:::grp
|
||||
grp -->|"÷ 定时器预分频"| pwm["PWM 定时器<br/>resolution_hz = 10 MHz"]:::mod
|
||||
grp -->|"÷ 捕获预分频"| cap["捕获定时器<br/>resolution_hz = 20 MHz"]:::mod
|
||||
classDef src fill:#fef3c7,stroke:#d97706,color:#78350f
|
||||
classDef grp fill:#dbeafe,stroke:#2563eb,color:#172554
|
||||
classDef mod fill:#dcfce7,stroke:#16a34a,color:#14532d
|
||||
|
||||
因为组分频器是共享的,只会为所有定时器确定一次。之后若再请求不同分辨率,就无法改动这个已固定的分频器,驱动会尽量让组时钟保持较高,并为新子模块选择最接近的预分频。
|
||||
|
||||
电源管理与睡眠
|
||||
==============
|
||||
|
||||
启用电源管理后,:cpp:func:`mcpwm_timer_enable()` 和 :cpp:func:`mcpwm_capture_timer_enable()` 会持有 :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP` 锁,以确保定时器时钟频率稳定;调用 :cpp:func:`mcpwm_timer_disable()` 或 :cpp:func:`mcpwm_capture_timer_disable()` 释放该锁。
|
||||
|
||||
定时器配置和捕获定时器配置中都有一个同名的 :cpp:member:`allow_pd <mcpwm_timer_config_t::flags::allow_pd>` 字段(后者见 :cpp:type:`mcpwm_capture_timer_config_t`),设置它可允许睡眠时关闭 MCPWM 电源域,驱动会备份和恢复寄存器,但会消耗额外 RAM。此功能取决于目标芯片。
|
||||
|
||||
ISR 与线程安全
|
||||
==============
|
||||
|
||||
定时器、比较器、故障、操作器制动和捕获回调都在 ISR 上下文运行,必须非阻塞并使用 ISR 安全的 RTOS 调用。组中第一个注册的回调确定共享中断优先级;后续事件应使用相同优先级。
|
||||
|
||||
:cpp:func:`mcpwm_new_timer()` 等工厂函数线程安全。:cpp:func:`mcpwm_timer_set_period()` 和 :cpp:func:`mcpwm_comparator_set_compare_value()` 可在 ISR 中调用。其他控制 API 通常不是线程安全的,多任务访问同一对象时应自行串行化。
|
||||
|
||||
缓存安全与实时操作
|
||||
==================
|
||||
|
||||
正常情况下,缓存禁用时(如 Flash 操作期间)MCPWM 中断响应会被延迟。当回调必须在缓存禁用时继续运行时,启用 :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE`;这会将 ISR 所需代码放入 IRAM,对象放入 DRAM,增加内部 RAM 使用。
|
||||
|
||||
.. note::
|
||||
|
||||
启用该选项后,即使缓存被禁用,MCPWM 中断仍会立即响应,不会被推迟。但中断处理链路上的所有代码——包括你注册的回调函数以及回调内部调用的所有子函数——都必须位于 IRAM 中:缓存关闭时 CPU 无法从 Flash 取指,一旦调用任何仍留在 Flash 中的函数,CPU 就会崩溃。该选项只负责把驱动自身的 ISR 代码放入 IRAM,你的回调函数及其调用的子函数需要自行显式放到 IRAM(例如用 ``IRAM_ATTR`` 修饰)。
|
||||
|
||||
:menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` 还会将 :cpp:func:`mcpwm_timer_set_period()` 和 :cpp:func:`mcpwm_comparator_set_compare_value()` 放入 IRAM,这样即使缓存关闭,这些调用仍可正常工作——例如你可以在缓存被禁用的上下文(如 Flash 擦写期间)中重新调整 PWM 周期或占空比,而无需等待缓存重新启用。
|
||||
|
||||
Kconfig 选项
|
||||
==============
|
||||
|
||||
.. list::
|
||||
|
||||
- :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE` 启用缓存安全中断。
|
||||
- :menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` 将选定控制函数放入 IRAM。
|
||||
- :menuitem:`CONFIG_MCPWM_ENABLE_DEBUG_LOG` 强制 MCPWM 驱动编译并打印自身的调试日志,忽略全局日志设置,仅对驱动本身将运行时日志等级提升到冗长——不影响其他模块。代价是固件体积增大。
|
||||
@@ -0,0 +1,148 @@
|
||||
=========================
|
||||
MCPWM 捕获:测量输入脉冲
|
||||
=========================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
捕获是独立的 MCPWM 路径:捕获定时器为捕获通道 GPIO 的边沿打时间戳,不需要 PWM 定时器、操作器、比较器或生成器,适用于回波脉冲、转速计、霍尔传感器和 RC 接收机。
|
||||
|
||||
它适合"把外部世界发生的时刻带进芯片里"。当你关心的是脉宽、周期、相位差或转速,而不是输出 PWM,本页就是 MCPWM 的另一条主线入口。
|
||||
|
||||
测量脉宽
|
||||
========
|
||||
|
||||
同时捕获两个边沿,保存上升沿时间戳,再用下降沿时间戳减去它。1 MHz 分辨率下,差值的单位直接是微秒。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_cap_timer_handle_t cap_timer = NULL;
|
||||
mcpwm_cap_channel_handle_t cap_channel = NULL;
|
||||
ESP_ERROR_CHECK(mcpwm_new_capture_timer(
|
||||
&(mcpwm_capture_timer_config_t) {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1000000,
|
||||
}, &cap_timer));
|
||||
ESP_ERROR_CHECK(mcpwm_new_capture_channel(cap_timer,
|
||||
&(mcpwm_capture_channel_config_t) {
|
||||
.gpio_num = 6,
|
||||
.prescale = 1,
|
||||
.flags.pos_edge = true,
|
||||
.flags.neg_edge = true,
|
||||
}, &cap_channel));
|
||||
|
||||
仅分配对象并不会开始测量。还需要使能通道并启动捕获定时器:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_capture_channel_enable(cap_channel));
|
||||
ESP_ERROR_CHECK(mcpwm_capture_timer_enable(cap_timer));
|
||||
ESP_ERROR_CHECK(mcpwm_capture_timer_start(cap_timer));
|
||||
|
||||
:cpp:func:`mcpwm_capture_channel_enable()` 和 :cpp:func:`mcpwm_capture_timer_enable()` 负责捕获所需的系统服务准备,二者都尚未开始测量。:cpp:func:`mcpwm_capture_timer_start()` 才真正启动计数器,边沿开始被打上时间戳。
|
||||
|
||||
捕获到的边沿值通过回调送达应用,详见下一节。
|
||||
|
||||
.. figure:: /../_static/mcpwm/capture_measurement.svg
|
||||
:align: center
|
||||
:alt: 捕获上升沿与下降沿时间戳,相减得到高电平脉宽。
|
||||
|
||||
捕获上升沿和下降沿时间戳,相减得到高电平脉宽。
|
||||
|
||||
两个配置结构体分开讲解:
|
||||
|
||||
捕获定时器配置
|
||||
--------------
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`group_id <mcpwm_capture_timer_config_t::group_id>` — 捕获定时器从哪个 MCPWM 组分配。
|
||||
- :cpp:member:`clk_src <mcpwm_capture_timer_config_t::clk_src>` — 捕获定时器的时钟源。:c:macro:`MCPWM_CAPTURE_CLK_SRC_DEFAULT` 适合绝大多数应用。当默认时钟源可能被关闭时需要显式选择其他源——例如低功耗场景下,若所选时钟被关断,捕获定时器会停止,时间戳就会失真。
|
||||
- :cpp:member:`resolution_hz <mcpwm_capture_timer_config_t::resolution_hz>` — 捕获定时器的 Tick 频率。一个 Tick 持续 ``1 / resolution_hz`` 秒,1 MHz 即微秒级分辨率,直接决定每次捕获时间戳的精度。
|
||||
- :cpp:member:`allow_pd <mcpwm_capture_timer_config_t::flags::allow_pd>` — 允许睡眠时关闭 MCPWM 电源域,在睡眠前后备份并恢复捕获寄存器,代价是额外占用 RAM。
|
||||
|
||||
捕获通道配置
|
||||
------------
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`gpio_num <mcpwm_capture_channel_config_t::gpio_num>` — 承载输入信号的 GPIO。
|
||||
- :cpp:member:`prescale <mcpwm_capture_channel_config_t::prescale>` — 捕获前对输入信号分频,有效输入频率为捕获时钟除以 ``prescale``。提高它可扩展可测周期范围,但会降低时间分辨率。
|
||||
- :cpp:member:`pos_edge <mcpwm_capture_channel_config_t::flags::pos_edge>` 和 :cpp:member:`neg_edge <mcpwm_capture_channel_config_t::flags::neg_edge>` — 捕获哪些边沿。示例同时捕获两个边沿,这正是脉宽测量所需的。
|
||||
- :cpp:member:`invert_cap_signal <mcpwm_capture_channel_config_t::flags::invert_cap_signal>` — 捕获前反相输入信号,让引脚上的逻辑 ``1`` 在捕获外设看来是 ``0``,反之亦然。
|
||||
- :cpp:member:`intr_priority <mcpwm_capture_channel_config_t::intr_priority>` — 捕获回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级。
|
||||
|
||||
.. note::
|
||||
|
||||
捕获驱动会把 GPIO 配置为输入,但不会设置任何上拉或下拉电阻。如果输入信号并非主动驱动到两个电平,请调用 :cpp:func:`gpio_set_pull_mode()` 选择上拉或下拉方向,让引脚空闲时保持在你期望的电平。
|
||||
|
||||
捕获事件回调
|
||||
============
|
||||
|
||||
事件数据告知边沿类型和锁存计数值。真实应用中应将耗时工作留给任务。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static uint32_t rise_tick;
|
||||
static bool IRAM_ATTR on_capture(mcpwm_cap_channel_handle_t channel,
|
||||
const mcpwm_capture_event_data_t *edata,
|
||||
void *user_data)
|
||||
{
|
||||
if (edata->cap_edge == MCPWM_CAP_EDGE_POS) {
|
||||
rise_tick = edata->cap_value;
|
||||
} else {
|
||||
uint32_t width_ticks = edata->cap_value - rise_tick;
|
||||
// 使用 ISR 安全方式将 width_ticks 通知任务
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_capture_channel_register_event_callbacks(cap_channel,
|
||||
&(mcpwm_capture_event_callbacks_t) { .on_cap = on_capture }, NULL));
|
||||
|
||||
通过 :cpp:func:`mcpwm_capture_timer_get_resolution()` 获取实际分辨率后,再将 Tick 转换为时间。在捕获时钟与 MCPWM 组时钟共享的芯片上,应保持一致的请求分辨率顺序创建捕获和 PWM 定时器。
|
||||
|
||||
若要测转速或周期,可记录两次同类边沿(例如两次上升沿)的时间戳,相减得到周期 Tick 数,再结合实际分辨率换算为频率或转速。
|
||||
|
||||
实用控制
|
||||
========
|
||||
|
||||
:cpp:func:`mcpwm_capture_channel_trigger_soft_catch()` 生成软件捕获事件,常用于测试,也可借此把重要软件事件的发生时刻落到捕获时间轴上,与硬件边沿时间戳对齐;该调用同样会触发回调。:cpp:func:`mcpwm_capture_get_latched_value()` 在不注册回调的情况下也能读取最新时间戳。
|
||||
|
||||
:cpp:func:`mcpwm_capture_timer_stop()` 使计数器停止,:cpp:func:`mcpwm_capture_channel_disable()` 可关闭单个输入,停止定时器则关闭整个测量引擎。删除对象前,先调用 :cpp:func:`mcpwm_capture_timer_disable()` 撤销 :cpp:func:`mcpwm_capture_timer_enable()` 做的准备工作。
|
||||
|
||||
捕获定时器同步
|
||||
==============
|
||||
|
||||
捕获定时器默认自由运行,计数值的“零点”是任意的,时间戳只能互相比较。同步让运行中的捕获定时器在同步边沿到来时加载一个指定计数值,从而把时间戳对应到有意义的参考系。
|
||||
|
||||
最常见的场景是让捕获定时器与 PWM 定时器对齐相位:以 PWM 定时器每周期零(TEZ)发出的同步为源、计数值设为 0,捕获定时器每个周期归零,捕获时间戳就直接表示周期内的相位。这在电机控制或功率变换中尤为重要——霍尔、编码器或电流检测的反馈边沿只有落在 PWM 周期的具体相位才有意义。
|
||||
|
||||
同步源与 PWM 定时器共用(GPIO、软件、定时器三种,须与捕获定时器同组),接收端用 :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()` 配置:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_capture_timer_set_phase_on_sync(cap_timer,
|
||||
&(mcpwm_capture_timer_sync_phase_config_t) {
|
||||
.sync_src = timer_a_sync, // 由 mcpwm_new_timer_sync_src() 创建的同步源
|
||||
.count_value = 0,
|
||||
.direction = MCPWM_TIMER_DIRECTION_UP,
|
||||
}));
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`sync_src <mcpwm_capture_timer_sync_phase_config_t::sync_src>` — 同步源,设为 ``NULL`` 可解除同步。
|
||||
- :cpp:member:`count_value <mcpwm_capture_timer_sync_phase_config_t::count_value>` — 同步边沿到达时加载的计数值。
|
||||
- :cpp:member:`direction <mcpwm_capture_timer_sync_phase_config_t::direction>` — 加载后的计数方向,捕获定时器只向上计数,固定为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP`。
|
||||
|
||||
软件同步与 GPIO 同步源也可让捕获定时器建立已知起点或对齐外部参考,同步源的创建与其余用法详见 :doc:`同步 <mcpwm_sync>`。
|
||||
|
||||
API 参考
|
||||
========
|
||||
|
||||
MCPWM 捕获驱动函数
|
||||
------------------
|
||||
|
||||
.. include-build-file:: inc/mcpwm_cap.inc
|
||||
@@ -0,0 +1,116 @@
|
||||
=================================
|
||||
MCPWM 比较器:将比例转换为边沿
|
||||
=================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
当定时器计数达到 ``cmp_ticks`` 时,比较器产生事件;生成器再将该事件转换为 GPIO 电平变化。通常在向上计数 PWM 中,比较 Tick 数就是高电平时间。
|
||||
|
||||
比较器的实际意义是把"我想要多宽、多晚、多早出现一个边沿"转换成硬件里的具体时刻。运行中的占空比调节,通常就是不断更新比较器阈值。
|
||||
|
||||
设置 30% 占空比
|
||||
===============
|
||||
|
||||
从已有操作器创建比较器,再设置阈值。对 :doc:`定时器 <mcpwm_timer>` 中 50 Tick 的周期,15 Tick 即为 30%。下图展示了比较事件在 Tick 15 处触发——生成器可用此事件结束高电平脉冲。
|
||||
|
||||
.. figure:: /../_static/mcpwm/compare_event.svg
|
||||
:align: center
|
||||
:alt: 定时器向上计数;比较器在 Tick 15 触发。生成器将其转换为下降沿。
|
||||
|
||||
定时器向上计数;比较器在 Tick 15 触发。生成器将其转换为下降沿。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_cmpr_handle_t comparator = NULL;
|
||||
mcpwm_comparator_config_t comparator_config = {
|
||||
.flags.update_cmp_on_tez = true, // 仅在周期边界改变占空比
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_comparator(oper, &comparator_config, &comparator));
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 15));
|
||||
|
||||
比较器配置除 flags 外只有一个字段:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`intr_priority <mcpwm_comparator_config_t::intr_priority>` — :cpp:member:`on_reach <mcpwm_comparator_event_callbacks_t::on_reach>` 回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级;当回调需要抢占其他中断时可适当提高。
|
||||
- :cpp:member:`flags <mcpwm_comparator_config_t::flags>` — 即下文介绍的更新点。示例启用了 :cpp:member:`update_cmp_on_tez <mcpwm_comparator_config_t::flags::update_cmp_on_tez>`,这也是在周期边界改变占空比的常见做法。
|
||||
|
||||
运行时百分比占空比的计算公式为 ``period_ticks * percent / 100``,结果必须不超过定时器周期。
|
||||
|
||||
这也是为什么改变占空比应优先改比较器,而不是改生成器动作:动作定义的是波形规则,比较器定义的是当前边沿位置。前者更像拓扑,后者更像运行参数。
|
||||
|
||||
为何要推迟更新
|
||||
==============
|
||||
|
||||
立即更新比较器可能改变当前周期中的边沿。:cpp:member:`update_cmp_on_tez <mcpwm_comparator_config_t::flags::update_cmp_on_tez>` 会等待计数归零,:cpp:member:`update_cmp_on_tep <mcpwm_comparator_config_t::flags::update_cmp_on_tep>` 会等待计数到达峰值,:cpp:member:`update_cmp_on_sync <mcpwm_comparator_config_t::flags::update_cmp_on_sync>` 则等待同步事件。在向上计数或向下计数模式下,峰值即周期边界,与归零点几乎重合,因此 tez 与 tep 对应同一个更新点;仅在向上-向下计数模式下,峰值处于周期中点,tez 与 tep 才是两个不同的更新点。电机和电源转换通常应使用推迟更新。
|
||||
|
||||
两个比较器实现脉冲放置
|
||||
=======================
|
||||
|
||||
一个比较器每周期产生一个边沿。在同一操作器中使用两个比较器,可在周期内任意位置放置脉冲——一个比较器打开脉冲,另一个关闭。适用于采样窗口、触发信号或非对称死区补偿。
|
||||
|
||||
这类需求在电机控制里很常见,例如想把 ADC 采样窗口放在开关噪声较小的位置,或者给外部器件输出一个不位于 PWM 边界的同步脉冲。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_cmpr_handle_t cmp_a, cmp_b;
|
||||
mcpwm_new_comparator(oper, &comparator_config, &cmp_a);
|
||||
mcpwm_new_comparator(oper, &comparator_config, &cmp_b);
|
||||
mcpwm_comparator_set_compare_value(cmp_a, 10);
|
||||
mcpwm_comparator_set_compare_value(cmp_b, 30);
|
||||
|
||||
比较事件也可用于精确的软件时间标记
|
||||
====================================
|
||||
|
||||
:cpp:member:`on_reach <mcpwm_comparator_event_callbacks_t::on_reach>` 回调在比较值到达时触发,适用于软件需要观察 PWM 周期中精确位置的场景。应在启动时间关键型工作前注册。回调在 ISR 上下文运行,应保持简短。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static bool IRAM_ATTR on_compare(mcpwm_cmpr_handle_t cmpr,
|
||||
const mcpwm_compare_event_data_t *edata,
|
||||
void *user_ctx)
|
||||
{
|
||||
// 通知任务或仅执行 ISR 安全工作
|
||||
return false;
|
||||
}
|
||||
|
||||
mcpwm_comparator_event_callbacks_t callbacks = { .on_reach = on_compare };
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_register_event_callbacks(comparator,
|
||||
&callbacks, NULL));
|
||||
|
||||
比较器的种类
|
||||
============
|
||||
|
||||
**操作器比较器** 通过 :cpp:func:`mcpwm_new_comparator()` 创建,其比较事件驱动生成器,从而塑造 PWM 输出。
|
||||
|
||||
.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR
|
||||
|
||||
还存在第二种: **事件比较器**,通过 :cpp:func:`mcpwm_new_event_comparator()` 创建。其比较事件只能经 :doc:`ETM </api-reference/peripherals/etm>` 传给其他外设,不驱动生成器,也不会影响 PWM 输出。
|
||||
|
||||
.. note::
|
||||
|
||||
名字容易误导:操作器比较器同样能产生 ETM 事件,因此事件比较器并非 MCPWM 接入 ETM 的唯一途径。事件比较器的定位是 *补充* 操作器比较器,而非替代。事件比较器不占用操作器比较器名额,也不会移动任何 PWM 边沿,当你只需要一个额外比较点作为纯定时标记时——例如在不扰动 PWM 波形的前提下,为 ADC 触发找到采样窗口——它就是更灵活的选择。
|
||||
|
||||
两种比较器使用相同的比较值设置与 ETM 事件创建方式:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_event_comparator_config_t evt_cmp_cfg = {};
|
||||
mcpwm_cmpr_handle_t evt_cmpr = NULL;
|
||||
ESP_ERROR_CHECK(mcpwm_new_event_comparator(oper, &evt_cmp_cfg, &evt_cmpr));
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(evt_cmpr, 25));
|
||||
|
||||
esp_etm_event_handle_t evt = NULL;
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_new_etm_event(evt_cmpr,
|
||||
&(mcpwm_cmpr_etm_event_config_t){ .event_type = MCPWM_CMPR_ETM_EVENT_EQUAL },
|
||||
&evt));
|
||||
|
||||
API 参考
|
||||
========
|
||||
|
||||
MCPWM 比较器驱动函数
|
||||
--------------------
|
||||
|
||||
.. include-build-file:: inc/mcpwm_cmpr.inc
|
||||
@@ -0,0 +1,66 @@
|
||||
=============================
|
||||
MCPWM ETM:外设间的硬件级联动
|
||||
=============================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
.. only:: SOC_MCPWM_SUPPORT_ETM
|
||||
|
||||
事件任务矩阵(ETM)可将 MCPWM 定时器或比较器事件直接路由到 ETM 任务,避免 ISR 延迟。适用于其他外设必须在精确 PWM 相位响应的场景。
|
||||
|
||||
先从定时器或比较器创建 ETM 事件,再创建目标外设的兼容任务,最后通过 ETM 通道连接两者。目标外设驱动文档定义了其任务和完整通道配置。关于 ETM 的整体用法——分配通道、把事件连接到任务——请参阅 :doc:`ETM </api-reference/peripherals/etm>` 文档。
|
||||
|
||||
.. mermaid::
|
||||
|
||||
flowchart LR
|
||||
T["MCPWM 定时器<br/>TEZ/TEP 事件"]:::source --> E["ETM 通道"]:::route
|
||||
C["MCPWM 比较器<br/>比较事件"]:::source --> E
|
||||
E --> D["目标<br/>外设任务"]:::dest
|
||||
classDef source fill:#dbeafe,stroke:#2563eb,color:#172554
|
||||
classDef route fill:#ede9fe,stroke:#7c3aed,color:#2e1065
|
||||
classDef dest fill:#dcfce7,stroke:#16a34a,color:#14532d
|
||||
|
||||
定时器会产生 ``TEZ`` (定时器归零)或 ``TEP`` (定时器到达峰值)事件。要获取一个事件:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
esp_etm_event_handle_t timer_event = NULL;
|
||||
ESP_ERROR_CHECK(mcpwm_timer_new_etm_event(timer,
|
||||
&(mcpwm_timer_etm_event_config_t) {
|
||||
.event_type = MCPWM_TIMER_ETM_EVENT_TEZ,
|
||||
}, &timer_event));
|
||||
// 创建目标 ETM task、分配 channel,然后连接:
|
||||
// esp_etm_channel_connect(channel, timer_event, destination_task);
|
||||
|
||||
比较器提供 ``EQUAL`` 事件,每当定时器计数值等于比较器取值时触发。它能把事件固定到 PWM 周期中的任意相位,而不只是波峰或波谷。要获取一个事件:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
esp_etm_event_handle_t cmp_event = NULL;
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_new_etm_event(cmp,
|
||||
&(mcpwm_cmpr_etm_event_config_t) {
|
||||
.event_type = MCPWM_CMPR_ETM_EVENT_EQUAL,
|
||||
}, &cmp_event));
|
||||
// esp_etm_channel_connect(channel, cmp_event, destination_task);
|
||||
|
||||
使用完毕后调用 :cpp:func:`esp_etm_del_event()` 释放事件。
|
||||
|
||||
比较器事件一个非常常见的用途是触发 ADC 采样:把比较器设置在想要采样的相位点,再由比较器事件启动 ADC,使转换器在与 PWM 周期精确同步的相位点采样稳定、干净的波形。由于整条链路完全由硬件完成,采样点能无 CPU 参与、无 ISR 延迟地跟随 PWM。
|
||||
|
||||
.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR
|
||||
|
||||
用哪种比较器提供事件很关键。 **运算比较器** (:cpp:func:`mcpwm_new_comparator()`)同时驱动生成器,其比较值会定义实际的 PWM 输出边沿,因此它的 ETM 事件被限制在对应的 PWM 边沿上——你无法要求它在不是自己产生的边沿相位上触发。 **事件比较器** (:cpp:func:`mcpwm_new_event_comparator()`)则是专门的 ETM 定时标记:它不驱动任何生成器,也不占用运算比较器槽位,因此可以在 PWM 周期内的 *任意* 相位触发,且对 PWM 波形毫无影响。这种自由度正是 ADC 触发所需要的,所以更推荐用事件比较器作为事件源——把采样点放在电压已稳定的位置,而不只是恰好有边沿的地方。
|
||||
|
||||
API 参考
|
||||
========
|
||||
|
||||
MCPWM ETM 驱动函数
|
||||
------------------
|
||||
|
||||
.. include-build-file:: inc/mcpwm_etm.inc
|
||||
|
||||
.. only:: not SOC_MCPWM_SUPPORT_ETM
|
||||
|
||||
{IDF_TARGET_NAME} 不支持 MCPWM ETM 事件。
|
||||
@@ -0,0 +1,84 @@
|
||||
==================================
|
||||
MCPWM 故障:将保护信号接入 MCPWM
|
||||
==================================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
故障对象表示异常条件。GPIO 故障适合过流比较器等硬件信号;软件故障可让应用逻辑触发同一保护路径。随后使用操作器 :ref:`制动 <mcpwm-brake>` 定义输出响应。
|
||||
|
||||
故障模块存在的目的,是让保护路径尽可能不依赖软件轮询或任务调度。对于电机驱动和功率变换器,过流、互锁或急停通常要求在硬件路径里先把输出拉到安全状态,再由软件决定如何记录和恢复。
|
||||
|
||||
创建低电平有效 GPIO 故障源
|
||||
==========================
|
||||
|
||||
故障源必须和将使用它的操作器位于同一组。引脚上拉/下拉属于独立 GPIO 配置;MCPWM 驱动不会为故障 GPIO 引脚自动使能内部上拉/下拉。如果故障信号在其他状态下并不驱动引脚电平,请自行用 :cpp:func:`gpio_set_pull_mode()` 配置上拉/下拉方向;启动功率级前应确保非故障电平明确。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_fault_handle_t fault = NULL;
|
||||
mcpwm_gpio_fault_config_t fault_config = {
|
||||
.group_id = 0,
|
||||
.gpio_num = 4,
|
||||
.flags.active_level = 0,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_gpio_fault(&fault_config, &fault));
|
||||
|
||||
GPIO 故障配置中有几个字段值得了解:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`group_id <mcpwm_gpio_fault_config_t::group_id>` — 故障所属的 MCPWM 组。必须与使用该故障的操作器所在组一致。
|
||||
- :cpp:member:`gpio_num <mcpwm_gpio_fault_config_t::gpio_num>` — 承载故障信号的 GPIO。
|
||||
- :cpp:member:`active_level <mcpwm_gpio_fault_config_t::flags::active_level>` — 何种电平视为有效。示例使用 ``0``,即故障为低有效;无故障时引脚上拉方向应使其保持非有效(高电平)。驱动不会改动引脚的上下拉配置,请按需调用 :cpp:func:`gpio_set_pull_mode()` 选择上拉或下拉。
|
||||
- :cpp:member:`intr_priority <mcpwm_gpio_fault_config_t::intr_priority>` — 故障事件回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级。
|
||||
|
||||
创建软件故障
|
||||
============
|
||||
|
||||
对于应用检测到的条件,可创建 :cpp:func:`mcpwm_new_soft_fault()`,并在条件发生时调用 :cpp:func:`mcpwm_soft_fault_activate()`,而无需接入 GPIO 故障引脚。激活是一次性的故障事件,输出策略仍由操作器 :ref:`制动机制 <mcpwm-brake>` 决定。
|
||||
|
||||
.. note::
|
||||
|
||||
激活前必须先用 :cpp:func:`mcpwm_operator_set_brake_on_fault()` 将软件故障绑定到某个操作器。驱动在 :cpp:func:`mcpwm_new_soft_fault()` 时并不会关联操作器;操作器关联及其制动模式都由该绑定调用设置,且一个软件故障只能绑定一个操作器。在绑定前调用 :cpp:func:`mcpwm_soft_fault_activate()` 属于未定义行为。
|
||||
|
||||
故障作为生成器动作的触发源
|
||||
==========================
|
||||
|
||||
GPIO 故障也可通过 :cpp:func:`mcpwm_generator_set_action_on_fault_event()` 直接触发生成器动作。这是局部的边沿级响应——在故障边沿改变输出,但不会锁存安全状态。如需带恢复的持续制动,应使用操作器 :ref:`制动机制 <mcpwm-brake>`。
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:widths: 18 34 28
|
||||
|
||||
* - 机制
|
||||
- 行为
|
||||
- 适合场景
|
||||
* - 生成器故障动作
|
||||
- 单个输出在故障边沿立即改变
|
||||
- 某一路输出的局部快速响应
|
||||
* - 操作器制动
|
||||
- 为整个输出级定义安全状态、是否锁存和恢复策略
|
||||
- 主保护路径、半桥/全桥安全关断
|
||||
|
||||
故障事件回调
|
||||
============
|
||||
|
||||
:cpp:member:`on_fault_enter <mcpwm_fault_event_callbacks_t::on_fault_enter>` 和 :cpp:member:`on_fault_exit <mcpwm_fault_event_callbacks_t::on_fault_exit>` 回调报告 GPIO 故障跳变,且仅 GPIO 故障可用——驱动会拒绝在软件故障上注册这些回调。软件故障会在硬件中立即触发制动,不会产生回调。这些回调在 ISR 上下文执行。应只进行时间戳记录或使用 ISR 安全的方式通知任务,在任务中做日志和恢复决策。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_fault_event_callbacks_t cbs = {
|
||||
.on_fault_enter = my_fault_enter_cb,
|
||||
.on_fault_exit = my_fault_exit_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_fault_register_event_callbacks(fault, &cbs, NULL));
|
||||
|
||||
API 参考
|
||||
========
|
||||
|
||||
MCPWM 故障驱动函数
|
||||
------------------
|
||||
|
||||
.. include-build-file:: inc/mcpwm_fault.inc
|
||||
@@ -0,0 +1,327 @@
|
||||
============================
|
||||
MCPWM 生成器:生成 PWM 波形
|
||||
============================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
生成器是最终的数字输出。它没有固定的"占空比"属性,而是配置在定时器和比较器事件发生时输出何种电平,因此可实现简单、非对称、互补和相位相关的波形。
|
||||
|
||||
这也是 MCPWM 与简单 PWM 外设的重要区别之一:它不是只让你填一个频率和占空比,而是让你直接描述"在什么时刻做什么动作"。代价是初学时概念更多,但换来的是对波形结构更高的控制力。
|
||||
|
||||
生成第一路 PWM
|
||||
===============
|
||||
|
||||
这是对 :doc:`定时器 <mcpwm_timer>`、:doc:`操作器 <mcpwm_operator>` 和 :doc:`比较器 <mcpwm_cmpr>` 配置的收尾:定时器归零时输出高电平,比较器到达 15 时输出低电平。对于 50 Tick 的周期,输出高电平 15 Tick,即 30% 占空比。
|
||||
|
||||
应用场景:基础单路输出
|
||||
----------------------
|
||||
|
||||
适用于最基础的单路 PWM 输出,例如 RC 舵机控制信号、LED 调光,或后级电路已处理好极性和保护关系的简单占空比输出。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_gen_handle_t generator = NULL;
|
||||
mcpwm_generator_config_t gen_config = { .gen_gpio_num = 18 };
|
||||
ESP_ERROR_CHECK(mcpwm_new_generator(oper, &gen_config, &generator));
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
|
||||
generator, 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(
|
||||
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_LOW)));
|
||||
|
||||
.. figure:: /../_static/mcpwm/single_edge_asym_active_high.svg
|
||||
:align: center
|
||||
:alt: 向上计数、有效高 PWM:归零时置高,比较点置低。
|
||||
|
||||
向上计数、有效高 PWM:归零时置高,比较点置低。
|
||||
|
||||
生成器配置很简单:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`gen_gpio_num <mcpwm_generator_config_t::gen_gpio_num>` — 输出 PWM 的 GPIO。同一操作器中另一个生成器若配置相同的动作,即可从同一时间基准驱动第二个引脚。
|
||||
- :cpp:member:`invert_pwm <mcpwm_generator_config_t::flags::invert_pwm>` — 通过 GPIO 矩阵反相 PWM 信号。这是对最终输出的硬件反相,与修改动作不同;两者选其一,不要同时使用。
|
||||
|
||||
动作配置
|
||||
========
|
||||
|
||||
配置名称直接描述了行为:在某个事件发生时执行 :cpp:enumerator:`MCPWM_GEN_ACTION_HIGH <mcpwm_generator_action_t::MCPWM_GEN_ACTION_HIGH>`、:cpp:enumerator:`MCPWM_GEN_ACTION_LOW <mcpwm_generator_action_t::MCPWM_GEN_ACTION_LOW>` 或 :cpp:enumerator:`MCPWM_GEN_ACTION_TOGGLE <mcpwm_generator_action_t::MCPWM_GEN_ACTION_TOGGLE>`。辅助宏在调用处清晰展示三个关键选择——方向、事件源和输出电平。
|
||||
|
||||
对于向上计数定时器,:cpp:enumerator:`MCPWM_TIMER_EVENT_EMPTY <mcpwm_timer_event_t::MCPWM_TIMER_EVENT_EMPTY>` 是归零边界,:cpp:enumerator:`MCPWM_TIMER_EVENT_FULL <mcpwm_timer_event_t::MCPWM_TIMER_EVENT_FULL>` 在定时器到达峰值时触发。向上计数模式下峰值即周期,因此 ``FULL`` 落在周期边界;向上-向下模式下峰值是 ``period_ticks / 2``,因此 ``FULL`` 落在周期中间。比较动作使用比较器阈值。因此,第一个例子的含义是"周期开始时置高,计数到 15 时结束有效部分"。超出定时器范围的比较值不会产生比较事件。
|
||||
|
||||
每个动作都必须指定定时器方向,尽管这一选择只在向上-向下模式下才有可见差异。向上计数模式下计数器只向上走,因此为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP <mcpwm_timer_direction_t::MCPWM_TIMER_DIRECTION_UP>` 配置的动作才会触发——仍需显式写出。在向上-向下模式中,两个边沿都重要时,应同时为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP <mcpwm_timer_direction_t::MCPWM_TIMER_DIRECTION_UP>` 和 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_DOWN <mcpwm_timer_direction_t::MCPWM_TIMER_DIRECTION_DOWN>` 配置动作;这样便能用一个比较器生成中心对齐 PWM。
|
||||
|
||||
经典波形案例
|
||||
============
|
||||
|
||||
下面的示例都基于上一节的第一路 PWM,复用相同的定时器、操作器和比较器对象,生成其他常用波形。
|
||||
|
||||
反转有效极性
|
||||
------------
|
||||
|
||||
某些栅极驱动器和 LED 为低有效,例如低有效驱动器使能、反相 LED 接法,或板级电路已固定反相的接口。与其增加 GPIO 反相,不如在周期边界置低、比较点置高:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
|
||||
generator, MCPWM_GEN_TIMER_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW)));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
|
||||
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_HIGH)));
|
||||
|
||||
.. figure:: /../_static/mcpwm/single_edge_asym_active_low.svg
|
||||
:align: center
|
||||
:alt: 向上计数、有效低 PWM。当输出逻辑极性是设计的一部分时,修改动作而非接线。
|
||||
|
||||
向上计数、有效低 PWM。当输出逻辑极性是设计的一部分时,修改动作而非接线。
|
||||
|
||||
在周期内放置脉冲
|
||||
----------------
|
||||
|
||||
当需要把某个短脉冲精确放到周期内部——例如 ADC 采样窗口、外设触发脉冲或锁存控制信号——两个比较值分别决定脉冲的开始和结束边沿:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
|
||||
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, comparator_a, MCPWM_GEN_ACTION_HIGH)));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
|
||||
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, comparator_b, MCPWM_GEN_ACTION_LOW)));
|
||||
|
||||
.. figure:: /../_static/mcpwm/pulse_placement_asym.svg
|
||||
:align: center
|
||||
:alt: 脉冲放置:两个比较值之差就是脉冲宽度。
|
||||
|
||||
脉冲放置:两个比较值之差就是脉冲宽度。
|
||||
|
||||
应使 ``comparator_a`` 小于 ``comparator_b``。同时以相同 Tick 数移动二者可改变位置而不改变宽度;只移动其中一个则改变宽度。硬件会放置两个边沿,因此比定时器回调更精确。
|
||||
|
||||
双沿非对称 PWM
|
||||
--------------
|
||||
|
||||
当需要在一个周期内独立布置多个边沿、且高电平区间不必围绕周期中心对称时——例如某些非对称逆变器调制或定制驱动时序——使用两个生成器和两个比较器。每个生成器在每周期有自己的边沿,使高电平时间分布在周期边界两侧:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
|
||||
gen_a, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, cmp_a, MCPWM_GEN_ACTION_HIGH)));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
|
||||
gen_a, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, cmp_b, MCPWM_GEN_ACTION_LOW)));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
|
||||
gen_b, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, cmp_a, MCPWM_GEN_ACTION_LOW)));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
|
||||
gen_b, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, cmp_b, MCPWM_GEN_ACTION_HIGH)));
|
||||
|
||||
.. figure:: /../_static/mcpwm/dual_edge_asym_active_low.svg
|
||||
:align: center
|
||||
:alt: 双沿非对称(边沿对齐)PWM:两个生成器产生互补输出,每周期两个边沿。
|
||||
|
||||
双沿非对称(边沿对齐)PWM:两个生成器产生互补输出,每周期两个边沿。
|
||||
|
||||
中心对齐 PWM
|
||||
------------
|
||||
|
||||
电机控制、逆变器等关注谐波表现的功率级通常更偏好中心对齐 PWM,因为它能带来更对称的开关时序和更低的谐波失真。创建定时器时选择 ``MCPWM_TIMER_COUNT_MODE_UP_DOWN``,随后在两个方向使用同一阈值:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
|
||||
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_HIGH)));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
|
||||
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_DOWN, comparator, MCPWM_GEN_ACTION_LOW)));
|
||||
|
||||
.. figure:: /../_static/mcpwm/dual_edge_sym_active_low.svg
|
||||
:align: center
|
||||
:alt: 中心对齐 PWM:向上和向下计数动作在周期中心两侧形成对称边沿。
|
||||
|
||||
中心对齐 PWM:向上和向下计数动作在周期中心两侧形成对称边沿。
|
||||
|
||||
一次完整的向上-向下周期会到达峰值后回到零点,计算频率时需计入两个计数阶段。为第二个生成器配置相反动作可形成逻辑互补输出:
|
||||
|
||||
.. figure:: /../_static/mcpwm/dual_edge_sym_complementary.svg
|
||||
:align: center
|
||||
:alt: 仅靠互补生成器动作不含死区,不能直接连接到功率级。
|
||||
|
||||
仅靠互补生成器动作不含死区,不能直接连接到功率级。
|
||||
|
||||
.. warning::
|
||||
|
||||
逻辑互补输出不等于可直接驱动半桥的安全互补输出。只要上下管存在关断延迟,就必须继续配置死区,并在实际栅极处验证无重叠导通。
|
||||
|
||||
占空比调整
|
||||
==========
|
||||
|
||||
改变占空比时修改比较器阈值,而不是生成器动作。对于向上计数的有效高波形,``compare_value / period_ticks`` 即为占空比。应选择足够高的定时器分辨率,使一个 Tick 满足应用所需的调节精度。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_comparator_set_compare_value(comparator, 25); // 50 %
|
||||
|
||||
强制电平
|
||||
========
|
||||
|
||||
调节比较器阈值改变的是正常占空比。若需要临时接管输出、忽略所有事件动作而直接固定某个电平时,可改用强制电平接口。:func:`mcpwm_generator_set_force_level` 有两个关键参数:``level`` 和 ``hold_on``。
|
||||
|
||||
- ``level``(第二个参数)指定要强制输出的原始生成器电平:``0`` 或 ``1`` 会覆盖所有事件动作,``-1`` 则解除强制并恢复事件控制。
|
||||
- ``hold_on``(第三个参数)决定强制电平持续多久:``true`` 会一直保持直到下一次调用解除;``false`` 则允许下一个事件动作将其覆盖。
|
||||
|
||||
例如 ``mcpwm_generator_set_force_level(generator, 0, true)`` 会覆盖所有事件动作并保持原始生成器为低电平。强制电平位于死区与 GPIO 反相之前,应使用示波器确认实际引脚电平。
|
||||
|
||||
强制电平常用于上电自检、故障后的临时安全输出、切换工作模式时的过渡状态,而不是长期替代正常 PWM 配置。
|
||||
|
||||
对于半桥,添加第二个生成器并配置 :ref:`死区模块 <mcpwm-dead-time>` 以产生非重叠互补输出。
|
||||
|
||||
.. _mcpwm-dead-time:
|
||||
|
||||
死区与半桥驱动
|
||||
==============
|
||||
|
||||
死区会延迟输出边沿,在半桥两个开关之间留出同时关闭的短暂间隔,以补偿晶体管关断延迟并降低直通风险。连接功率级前应配置并验证死区。
|
||||
|
||||
半桥通过高边与低边两个开关将直流母线连接到负载。两个开关通常都采用 N 沟道 MOSFET:低边源极接 GND、易于驱动,而高边源极随输出摆动,其栅极需要高于母线电压的驱动电压。MCPWM 输出是 3.3 V 逻辑信号,不能直接驱动栅极。以 IRS2101 为例,它使用独立的低压驱动电源 VCC(通常为 10-20 V),COM 接功率地。自举二极管应从 VCC 接到 VB,外部自举电容接在 VB 与 VS 之间,VS 必须连接到 OUT 半桥开关节点。高边输出为 HO,低边输出为 LO,两者都应通过栅极电阻连接到对应 MOSFET 的栅极。VCC 不是高压直流母线:低边开关导通时,自举二极管从受调节的驱动电源为自举电容充电。若两个开关同时开通和关断,仍在关断的开关会与已经开通的开关重叠导通,使母线经两个开关直通到地。死区留出两者同时关断的间隔,让下一个开关在前一个完全关断之后再开通:
|
||||
|
||||
.. figure:: /../_static/mcpwm/half_bridge_dead_time.svg
|
||||
:align: center
|
||||
:alt: 带自举电源和死区对比的半桥栅极驱动电路。
|
||||
|
||||
带自举电源和死区对比的半桥栅极驱动电路。
|
||||
|
||||
创建互补输出
|
||||
------------
|
||||
|
||||
在同一操作器中创建两个生成器。将生成器 A 的上升沿延迟后输出到自身,再将其下降沿延迟并反相后输出到生成器 B。
|
||||
|
||||
.. note::
|
||||
|
||||
这里约定,生成器 A 是通过操作器句柄申请的第一个生成器,生成器 B 是通过操作器句柄申请的第二个生成器。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_dead_time_config_t dead_time = { .posedge_delay_ticks = 2 };
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_a, &dead_time));
|
||||
dead_time = (mcpwm_dead_time_config_t) {
|
||||
.negedge_delay_ticks = 2,
|
||||
.flags.invert_output = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_b, &dead_time));
|
||||
|
||||
.. figure:: /../_static/mcpwm/deadtime_active_high_complementary.svg
|
||||
:align: center
|
||||
:alt: 带死区间隔的互补 PWM。
|
||||
|
||||
带死区间隔的互补 PWM。
|
||||
|
||||
理解路由与参数
|
||||
--------------
|
||||
|
||||
:func:`mcpwm_generator_set_dead_time(in_generator, out_generator, config) <mcpwm_generator_set_dead_time>` 将死区视为一个小型信号处理级。两个生成器句柄相同会原地改变该输出;将 ``gen_a`` 作为输入、 ``gen_b`` 作为输出则从 A 派生 B,这正是互补例子共享一个 PWM 源的方式。
|
||||
|
||||
:cpp:member:`posedge_delay_ticks <mcpwm_dead_time_config_t::posedge_delay_ticks>` 延迟上升沿,:cpp:member:`negedge_delay_ticks <mcpwm_dead_time_config_t::negedge_delay_ticks>` 延迟下降沿,单位为连接定时器的 Tick。例如分辨率为 10 MHz 时,2 Tick 等于 200 ns。下图展示了基本效果:``pwm_A`` 的上升沿被延迟(RED),``pwm_B`` 的下降沿被延迟(FED)。应先采用开关管和栅极驱动器数据手册中的最大关断延迟并留出余量;在晶体管栅极实测后,确认工艺、温度和布局仍有足够余量,才可减小该值。两个延迟均设为零可旁路死区模块。:cpp:member:`invert_output <mcpwm_dead_time_config_t::flags::invert_output>` 在该级之后改变极性。
|
||||
|
||||
.. figure:: /../_static/mcpwm/deadtime_active_high.svg
|
||||
:align: center
|
||||
:alt: 基本死区效果:上升沿延迟(RED)和下降沿延迟(FED)。
|
||||
|
||||
基本死区效果:上升沿延迟(RED)和下降沿延迟(FED)。
|
||||
|
||||
每个操作器的资源限制
|
||||
--------------------
|
||||
|
||||
每个操作器仅有一份上升沿延迟和一份下降沿延迟资源,不能将同一类延迟分别分配给两个生成器。下面的配置两次请求同一上升沿资源,因此无效:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_dead_time_config_t rise_delay = { .posedge_delay_ticks = 10 };
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_a, &rise_delay));
|
||||
// 无法再分配第二份独立的上升沿延迟。
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_b, gen_b, &rise_delay));
|
||||
|
||||
可以将上升沿延迟分配给 A、下降沿延迟分配给 B;也可以让 A 旁路模块,而让 B 使用两种延迟资源。如果第一个生成器同时使用两种延迟资源,另一个生成器便不能使用死区。
|
||||
|
||||
更多输出模式
|
||||
------------
|
||||
|
||||
上面的互补配置是半桥的常用起点。交换输出反相设置可使两路输出均为低有效,同时保留非重叠时间:
|
||||
|
||||
.. figure:: /../_static/mcpwm/deadtime_active_low_complementary.svg
|
||||
:align: center
|
||||
:alt: 低有效互补输出。时序资源相同,仅改变死区后的输出极性。
|
||||
|
||||
低有效互补输出。时序资源相同,仅改变死区后的输出极性。
|
||||
|
||||
仅有一个通道需要边沿延迟时,死区同样有用。向其中一个输出传入零延迟配置使其旁路,再将可用延迟应用到另一个输出:
|
||||
|
||||
.. figure:: /../_static/mcpwm/deadtime_reda_bypassb.svg
|
||||
:align: center
|
||||
:alt: 延迟 A 的上升沿,B 旁路死区。这不是互补半桥,而是独立的边沿放置工具。
|
||||
|
||||
延迟 A 的上升沿,B 旁路死区。这不是互补半桥,而是独立的边沿放置工具。
|
||||
|
||||
.. figure:: /../_static/mcpwm/deadtime_redb_fedb_bypassa.svg
|
||||
:align: center
|
||||
:alt: 旁路 A,延迟 B 的两个边沿,并占用两种延迟资源。
|
||||
|
||||
旁路 A,延迟 B 的两个边沿,并占用两种延迟资源。
|
||||
|
||||
单个边沿延迟也可独立应用。下图展示了仅对 B 的下降沿延迟,A 被旁路,仅使用 FED 资源:
|
||||
|
||||
.. figure:: /../_static/mcpwm/deadtime_fedb_bypassa.svg
|
||||
:align: center
|
||||
:alt: 仅对 B 应用下降沿延迟,A 保持不变。使用一个延迟资源。
|
||||
|
||||
仅对 B 应用下降沿延迟,A 保持不变。使用一个延迟资源。
|
||||
|
||||
输出反相时,死区行为随之变化。下图展示了基本延迟的低有效版本,反相标志翻转了两个输出的极性:
|
||||
|
||||
.. figure:: /../_static/mcpwm/deadtime_active_low.svg
|
||||
:align: center
|
||||
:alt: 低有效死区:相同的延迟资源,但输出极性在延迟级后被反相。
|
||||
|
||||
低有效死区:相同的延迟资源,但输出极性在延迟级后被反相。
|
||||
|
||||
.. note::
|
||||
|
||||
若波形的每个边沿都需要独立调整位置,应使用两个比较器和生成器动作。需求明确是"延迟边沿并控制极性"时,死区模块更合适。
|
||||
|
||||
在安全边界更新
|
||||
--------------
|
||||
|
||||
若要让改变仅在安全边界生效,请设置操作器的 :cpp:member:`update_dead_time_on_tez <mcpwm_operator_config_t::flags::update_dead_time_on_tez>`、:cpp:member:`update_dead_time_on_tep <mcpwm_operator_config_t::flags::update_dead_time_on_tep>` 或 :cpp:member:`update_dead_time_on_sync <mcpwm_operator_config_t::flags::update_dead_time_on_sync>`。
|
||||
|
||||
.. note::
|
||||
|
||||
应探测两个实际栅极引脚:GPIO 反相、载波调制和栅极驱动器极性都可能改变晶体管端看到的信号。当多个环节都会反转信号时,两次反相可能相互抵消,软件里看着正确,硬件却并非预期行为,务必以真实波形为准进行验证。
|
||||
|
||||
其他事件源
|
||||
============
|
||||
|
||||
生成器也可直接响应 GPIO 故障和同步事件:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:func:`mcpwm_generator_set_action_on_fault_event()` — 立即硬件响应 GPIO 故障。会占用有限的操作器触发槽。
|
||||
- :cpp:func:`mcpwm_generator_set_action_on_sync_event()` — 在同步边沿转换。每个生成器只有一个同步动作槽。
|
||||
- :cpp:func:`mcpwm_generator_set_action_on_brake_event()` — 操作器 :ref:`制动 <mcpwm-brake>` 期间各生成器的输出状态。需要按制动模式和定时器方向分别设置,完整示例见 :ref:`故障连接 <mcpwm-brake-fault-connection>`。
|
||||
|
||||
对于安全策略和持续制动,应使用操作器 :ref:`制动机制 <mcpwm-brake>`。生成器故障动作适合局部的边沿级响应;制动则为整个输出级定义安全状态和恢复行为。
|
||||
|
||||
API 参考
|
||||
========
|
||||
|
||||
MCPWM 生成器驱动函数
|
||||
--------------------
|
||||
|
||||
.. include-build-file:: inc/mcpwm_gen.inc
|
||||
@@ -0,0 +1,187 @@
|
||||
=========================
|
||||
MCPWM 操作器:组装输出级
|
||||
=========================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
操作器位于定时器与生成器之间,管理比较器、生成器动作、制动、死区路由和载波调制。一个定时器可驱动同组的多个操作器,而一个操作器只连接一个定时器。
|
||||
|
||||
如果把定时器看成时钟源,操作器就是一套输出级的资源容器。它让多个输出共享同一时间基准,同时把保护、死区和载波这类功率级特性收拢到一个地方管理。
|
||||
|
||||
连接基本模块
|
||||
============
|
||||
|
||||
操作器必须与定时器在同一组。先通过 :cpp:func:`mcpwm_operator_connect_timer()` 创建并连接操作器;连接前,生成器不能使用定时器事件。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_oper_handle_t oper = NULL;
|
||||
mcpwm_operator_config_t oper_config = {
|
||||
.group_id = 0,
|
||||
.flags.update_gen_action_on_tez = true,
|
||||
.flags.update_dead_time_on_tez = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_operator(&oper_config, &oper));
|
||||
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer));
|
||||
|
||||
操作器配置很小,但有两个字段没有出现在示例中:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`group_id <mcpwm_operator_config_t::group_id>` — 操作器从哪个 MCPWM 组分配。它必须与定时器所在组一致,因为操作器只能连接同组内的定时器。
|
||||
- :cpp:member:`intr_priority <mcpwm_operator_config_t::intr_priority>` — 制动事件回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级;当制动通知需要抢占其他中断时可适当提高。
|
||||
|
||||
``flags`` 用于选择新的生成器动作和死区配置在何时生效。默认全部关闭,改动会立即生效——可能在 PWM 周期中间:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`update_gen_action_on_tez <mcpwm_operator_config_t::flags::update_gen_action_on_tez>`、:cpp:member:`update_gen_action_on_tep <mcpwm_operator_config_t::flags::update_gen_action_on_tep>` 和 :cpp:member:`update_gen_action_on_sync <mcpwm_operator_config_t::flags::update_gen_action_on_sync>` — 将生成器动作的改动缓冲到计数归零、峰值或同步事件时。
|
||||
- :cpp:member:`update_dead_time_on_tez <mcpwm_operator_config_t::flags::update_dead_time_on_tez>`、:cpp:member:`update_dead_time_on_tep <mcpwm_operator_config_t::flags::update_dead_time_on_tep>` 和 :cpp:member:`update_dead_time_on_sync <mcpwm_operator_config_t::flags::update_dead_time_on_sync>` — 死区改动同样缓冲,更新点规则见 :ref:`死区 <mcpwm-dead-time>`。
|
||||
|
||||
对于运行中的功率级,应使用归零(``tez``)、峰值(``tep``)或同步点更新,以避免部分周期。
|
||||
|
||||
一个定时器,多个操作器
|
||||
======================
|
||||
|
||||
同一定时器可驱动多个操作器,每个操作器产生不同的波形。这适用于多相逆变器或多个同频但独立占空比的电机。
|
||||
|
||||
反过来,一个操作器只能连接一个定时器,因此该操作器下的所有比较器和生成器天然共享同一时间基准。这正是同相、互补或成对输出容易实现的原因。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_oper_handle_t oper_b = NULL;
|
||||
mcpwm_operator_config_t oper_config_b = {
|
||||
.group_id = 0,
|
||||
.flags.update_gen_action_on_tez = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_operator(&oper_config_b, &oper_b));
|
||||
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper_b, timer));
|
||||
// 在 oper_b 下创建独立的比较器和生成器。
|
||||
|
||||
.. _mcpwm-brake:
|
||||
|
||||
制动与安全输出
|
||||
==============
|
||||
|
||||
操作器将 :doc:`故障 <mcpwm_fault>` 转换为制动动作。应在启动 PWM 前配置生成器的制动状态,使故障路径完全由硬件驱动,避免软件延迟。
|
||||
|
||||
恢复策略
|
||||
--------
|
||||
|
||||
.. list::
|
||||
|
||||
- **CBC(逐周期):** 故障有效期间制动,并在配置的定时器归零或峰值处恢复,适合瞬态限流。
|
||||
- **OST(单次):** 故障消失后仍保持制动,必须由软件显式恢复,适合互锁或严重过流。
|
||||
|
||||
对于 CBC,设置 :cpp:member:`cbc_recover_on_tez <mcpwm_brake_config_t::flags::cbc_recover_on_tez>` 或 :cpp:member:`cbc_recover_on_tep <mcpwm_brake_config_t::flags::cbc_recover_on_tep>`,选择已清除故障后释放输出的边界。在边界恢复可避免在 PWM 周期中间重新导通开关。
|
||||
|
||||
.. warning::
|
||||
|
||||
不要同时启用 ``cbc_recover_on_tez`` 和 ``cbc_recover_on_tep``;应选择与波形和栅极驱动时序相匹配的边界。
|
||||
|
||||
.. _mcpwm-brake-fault-connection:
|
||||
|
||||
故障连接
|
||||
--------
|
||||
|
||||
将故障连接到操作器,然后为每个生成器指定制动模式下的状态。此例在两个计数方向将原始生成器置低,使用 OST 制动:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_operator_set_brake_on_fault(oper,
|
||||
&(mcpwm_brake_config_t) {
|
||||
.fault = fault,
|
||||
.brake_mode = MCPWM_OPER_BRAKE_MODE_OST,
|
||||
}));
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_brake_event(
|
||||
generator, MCPWM_GEN_BRAKE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_OST,
|
||||
MCPWM_GEN_ACTION_LOW)));
|
||||
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_brake_event(
|
||||
generator, MCPWM_GEN_BRAKE_EVENT_ACTION(
|
||||
MCPWM_TIMER_DIRECTION_DOWN, MCPWM_OPER_BRAKE_MODE_OST,
|
||||
MCPWM_GEN_ACTION_LOW)));
|
||||
|
||||
半桥必须为两个生成器都配置相同的制动动作。逻辑低电平可能被死区、GPIO 矩阵或外部电路反相,因此应在栅极驱动器处确认实际安全状态。
|
||||
|
||||
生成器级故障动作与操作器制动的区别在于:前者适合单个输出的即时边沿响应,后者负责整个输出级的安全状态、锁存行为和恢复策略。主保护路径通常应优先使用操作器制动。
|
||||
|
||||
OST 故障恢复
|
||||
------------
|
||||
|
||||
CBC 在故障消失后在配置的边界自动恢复。OST 恢复前,先移除并确认故障根因已经消失,然后调用:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_operator_recover_from_fault(oper, fault));
|
||||
|
||||
故障仍有效时该调用会失败。
|
||||
|
||||
.. figure:: /../_static/mcpwm/brake_cbc_ost.svg
|
||||
:align: center
|
||||
:alt: PWM 运行期间故障被触发。CBC 在故障有效期间将输出保持在制动电平,并在下一个周期边界恢复;OST 则保持锁存直至软件恢复。
|
||||
|
||||
CBC 仅在故障有效期间制动,并在下一个周期边界恢复;OST 保持锁存直至软件恢复。
|
||||
|
||||
制动事件回调
|
||||
------------
|
||||
|
||||
操作器可通过 :cpp:member:`on_brake_cbc <mcpwm_operator_event_callbacks_t::on_brake_cbc>` 和 :cpp:member:`on_brake_ost <mcpwm_operator_event_callbacks_t::on_brake_ost>` 回调报告制动事件。二者均在 ISR 上下文运行,只应用于通知,不应用于阻塞式恢复。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_operator_event_callbacks_t cbs = {
|
||||
.on_brake_cbc = my_brake_cbc_cb,
|
||||
.on_brake_ost = my_brake_ost_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_operator_register_event_callbacks(oper, &cbs, NULL));
|
||||
|
||||
.. _mcpwm-carrier:
|
||||
|
||||
载波调制
|
||||
========
|
||||
|
||||
载波调制在操作器的 PWM 输出上叠加高频载波,常用于变压器隔离式栅极驱动:即使基础 PWM 保持 100% 占空比,调制后仍有可穿过隔离栅的跳变。应先完成基础 PWM 的配置;载波设置影响此操作器的所有生成器。
|
||||
|
||||
载波配置
|
||||
--------
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_carrier_config_t carrier = {
|
||||
.clk_src = MCPWM_CARRIER_CLK_SRC_DEFAULT,
|
||||
.frequency_hz = 100000,
|
||||
.duty_cycle = 0.5f,
|
||||
.first_pulse_duration_us = 20,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_operator_apply_carrier(oper, &carrier));
|
||||
|
||||
.. figure:: /../_static/mcpwm/carrier_modulation.svg
|
||||
:align: center
|
||||
:alt: 50% 占空比基础 PWM 的载波调制
|
||||
|
||||
100 kHz 载波与 50% 占空比基础 PWM 做与操作。首个脉冲被拉伸到 20 us(两个载波周期);基础 PWM 为低电平时不做斩波。
|
||||
|
||||
载波参数
|
||||
--------
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`clk_src <mcpwm_carrier_config_t::clk_src>` 选择载波时钟源。默认指向一个内部 PLL 时钟(如 PLL_F160M),部分芯片还提供 RC_FAST 或 XTAL 作为备选。不同时钟源的分辨率与功耗不同;默认时钟源即可满足大多数应用,仅在需要避开某个时钟引入的噪声、PLL 精度不够,或对功耗有要求时,才需要手动指定。
|
||||
- :cpp:member:`frequency_hz <mcpwm_carrier_config_t::frequency_hz>` 是载波频率;应选择同时满足隔离变压器、栅极驱动器、开关损耗预算和目标芯片时钟分辨率的值。
|
||||
- :cpp:member:`duty_cycle <mcpwm_carrier_config_t::duty_cycle>` 只能取硬件支持的 0.125、0.25、0.375、0.5、0.625、0.75 或 0.875,而非任意比例。
|
||||
- :cpp:member:`first_pulse_duration_us <mcpwm_carrier_config_t::first_pulse_duration_us>` 控制调制开始后的首个脉冲。它必须非零,且至少为一个载波周期。较长的首脉冲可帮助在感性隔离路径中建立电流,但必须保持在栅极驱动系统允许范围内。
|
||||
- :cpp:member:`invert_before_modulate <mcpwm_carrier_config_t::flags::invert_before_modulate>` 改变原始 PWM 极性,:cpp:member:`invert_after_modulate <mcpwm_carrier_config_t::flags::invert_after_modulate>` 改变调制后极性。
|
||||
|
||||
不需要载波调制时,向 :cpp:func:`mcpwm_operator_apply_carrier` 传入 ``NULL`` 配置即可禁用。
|
||||
|
||||
API 参考
|
||||
========
|
||||
|
||||
MCPWM 操作器驱动函数
|
||||
--------------------
|
||||
|
||||
.. include-build-file:: inc/mcpwm_oper.inc
|
||||
@@ -0,0 +1,157 @@
|
||||
===========================
|
||||
MCPWM 同步:对齐 PWM 相位
|
||||
===========================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
为什么需要同步
|
||||
==============
|
||||
|
||||
每个 MCPWM 定时器是独立的硬件计数器。当你调用 :cpp:func:`mcpwm_timer_start()` 启动两个定时器时,CPU 会依次发出两条写指令——第二个定时器比第一个晚几十个 CPU 周期才启动。即使两者的周期配置完全相同,它们的计数器在周期中的位置也是不同的,PWM 输出的相位关系无法预测。
|
||||
|
||||
同步通过在同步边沿到达时给**正在运行**的定时器加载指定的计数值和方向来解决这个问题。定时器必须已经在运行;同步不会启动或停止定时器。它是在运行时修正相位的一种机制。
|
||||
|
||||
如果同步边沿在每个周期都到达(例如来自 TEZ 处的定时器同步源),那么修正每周期重复一次,相位关系可以无限期保持。这就是典型的用法:一个定时器作为参考,其他定时器在每个周期都重新对齐到它。
|
||||
|
||||
MCPWM 提供三种同步源。所有源都产生 :cpp:type:`mcpwm_sync_handle_t` 类型的句柄,且任何源都可以供给同组内的任意定时器。
|
||||
|
||||
GPIO 同步源
|
||||
============
|
||||
|
||||
GPIO 同步源对外部引脚上的边沿做出反应——当外部控制器、传感器或编码器提供周期性参考信号时非常有用。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_sync_handle_t sync = NULL;
|
||||
ESP_ERROR_CHECK(mcpwm_new_gpio_sync_src(
|
||||
&(mcpwm_gpio_sync_src_config_t) {
|
||||
.group_id = 0,
|
||||
.gpio_num = 5,
|
||||
.flags.active_neg = false,
|
||||
}, &sync));
|
||||
|
||||
GPIO 同步源配置很简单:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`group_id <mcpwm_gpio_sync_src_config_t::group_id>` — 同步源所属的 MCPWM 组。必须与所有接收该同步的定时器所在组一致。
|
||||
- :cpp:member:`gpio_num <mcpwm_gpio_sync_src_config_t::gpio_num>` — 承载同步信号的 GPIO。
|
||||
- :cpp:member:`active_neg <mcpwm_gpio_sync_src_config_t::flags::active_neg>` — 默认上升沿为有效边沿;设置后改为下降沿有效。
|
||||
|
||||
软件同步源
|
||||
==========
|
||||
|
||||
软件同步源由应用代码按需产生同步边沿。它没有配置字段;创建后即可在需要时激活。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_sync_handle_t soft_sync = NULL;
|
||||
ESP_ERROR_CHECK(mcpwm_new_soft_sync_src(NULL, &soft_sync));
|
||||
|
||||
// 后续当应用决定同步时:
|
||||
ESP_ERROR_CHECK(mcpwm_soft_sync_activate(soft_sync));
|
||||
|
||||
.. note::
|
||||
|
||||
必须先通过 :cpp:func:`mcpwm_timer_set_phase_on_sync()` 或 :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()` 将软件同步源绑定到定时器,再调用激活。驱动在创建时不会分配定时器;在绑定前调用 :cpp:func:`mcpwm_soft_sync_activate()` 属于未定义行为。
|
||||
|
||||
这在定时器已经在运行、应用需要触发一次性的相位修正时有用——例如故障恢复后,或开始新的控制周期之前。由于软件同步是一次性的,如果后续没有更多同步边沿到来,相位关系会随时间漂移。如需持续锁相,应使用周期性源(GPIO 或定时器同步源)。
|
||||
|
||||
定时器同步源
|
||||
============
|
||||
|
||||
定时器同步源在定时器到达指定事件时产生同步边沿——例如每次定时器计到零(TEZ)。这可以让一个定时器作为其他定时器的周期性参考,每周期都保持相位锁定。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_sync_handle_t timer_sync = NULL;
|
||||
ESP_ERROR_CHECK(mcpwm_new_timer_sync_src(
|
||||
timer_a,
|
||||
&(mcpwm_timer_sync_src_config_t) {
|
||||
.timer_event = MCPWM_TIMER_EVENT_EMPTY,
|
||||
},
|
||||
&timer_sync));
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`timer_event <mcpwm_timer_sync_src_config_t::timer_event>` — 触发同步输出的定时器事件。常用 :cpp:enumerator:`MCPWM_TIMER_EVENT_EMPTY` (零)表示每个周期开始,或 :cpp:enumerator:`MCPWM_TIMER_EVENT_PEAK` 表示峰值位置。在向上计数模式中峰值就是周期边界;在向上-向下计数模式中峰值是周期的中点。
|
||||
- :cpp:member:`propagate_input_sync <mcpwm_timer_sync_src_config_t::flags::propagate_input_sync>` — 设置后,该定时器会将其接收到的输入同步转发到其输出,无需额外 GPIO 接线即可实现定时器同步链。此模式下硬件选择输入同步作为输出源,因此 :cpp:member:`timer_event` 字段会被忽略。
|
||||
|
||||
每个定时器最多只能创建一个同步源。多个定时器可以接收同一个同步源。
|
||||
|
||||
由于定时器同步源每周期都会触发,接收定时器在每个周期都会得到修正。这是维持多路 PWM 通道间稳定相位关系最常用的方式。
|
||||
|
||||
设置接收相位
|
||||
============
|
||||
|
||||
无论选择哪种同步源,接收定时器都使用相同的 API。调用 :cpp:func:`mcpwm_timer_set_phase_on_sync()` 配置同步边沿到达时的行为。定时器必须已经在运行,同步才会生效。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timer,
|
||||
&(mcpwm_timer_sync_phase_config_t) {
|
||||
.sync_src = sync,
|
||||
.count_value = 25,
|
||||
.direction = MCPWM_TIMER_DIRECTION_UP,
|
||||
}));
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`sync_src <mcpwm_timer_sync_phase_config_t::sync_src>` — 源对象。设为 ``NULL`` 可取消同步。
|
||||
- :cpp:member:`count_value <mcpwm_timer_sync_phase_config_t::count_value>` — 同步事件到达时加载的计数值。应保持在定时器周期范围内。
|
||||
- :cpp:member:`direction <mcpwm_timer_sync_phase_config_t::direction>` — 加载后的计数方向。
|
||||
|
||||
两路 90 度移相
|
||||
==============
|
||||
|
||||
现在你已经了解了三种同步源以及如何设置接收相位,下面是一个完整示例。它使用定时器同步源:``timer_a`` 每次到达零时发出同步,``timer_b`` 收到后加载 ``count_value = 25``,产生 90 度相位滞后。由于同步每周期重复一次,两路输出的相位关系可以无限期保持。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_timer_handle_t timer_a = NULL;
|
||||
mcpwm_timer_handle_t timer_b = NULL;
|
||||
mcpwm_sync_handle_t timer_a_sync = NULL;
|
||||
|
||||
// timer_a 和 timer_b 均已创建,period_ticks = 100
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_new_timer_sync_src(
|
||||
timer_a,
|
||||
&(mcpwm_timer_sync_src_config_t) {
|
||||
.timer_event = MCPWM_TIMER_EVENT_EMPTY,
|
||||
},
|
||||
&timer_a_sync));
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timer_b,
|
||||
&(mcpwm_timer_sync_phase_config_t) {
|
||||
.sync_src = timer_a_sync,
|
||||
.count_value = 25,
|
||||
.direction = MCPWM_TIMER_DIRECTION_UP,
|
||||
}));
|
||||
|
||||
// timer_a 在 TEZ 输出同步;timer_b 收到后从 Tick 25 开始继续计数。
|
||||
|
||||
理解领先与滞后
|
||||
--------------
|
||||
|
||||
下面的示意中,``PWM_A`` 先开始一个周期,``PWM_B`` 在其后四分之一周期出现,因此 ``PWM_B`` 滞后 ``PWM_A`` 90 度;反过来说,``PWM_A`` 领先 ``PWM_B`` 90 度。
|
||||
|
||||
.. figure:: /../_static/mcpwm/phase_shift.svg
|
||||
:align: center
|
||||
:alt: PWM 相移 90 度滞后
|
||||
|
||||
PWM_A 与 PWM_B 之间的 90 度相移:PWM_B 在 PWM_A 之后 25 个 tick 处开始上升。
|
||||
|
||||
其他注意事项
|
||||
============
|
||||
|
||||
捕获定时器也可通过 :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()` 使用同一同步源,捕获始终向上计数。接收端和源必须保留在同一组中。删除源之前,应先取消同步或删除所有使用它的对象。
|
||||
|
||||
API 参考
|
||||
========
|
||||
|
||||
MCPWM 同步驱动函数
|
||||
------------------
|
||||
|
||||
.. include-build-file:: inc/mcpwm_sync.inc
|
||||
@@ -0,0 +1,146 @@
|
||||
=======================
|
||||
MCPWM 定时器:设定频率
|
||||
=======================
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
:depth: 2
|
||||
|
||||
定时器为与其操作器相连的每一路 PWM 提供时间基准。它以 :cpp:member:`resolution_hz <mcpwm_timer_config_t::resolution_hz>` 的频率计数,并在达到 :cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` 时回绕。先选择分辨率——它决定了边沿放置的最小步长——再选择目标频率对应的周期。
|
||||
|
||||
无论是舵机、调速还是逆变器,定时器都回答两个最基本的问题:"一个 Tick 有多细?" 和 "一个 PWM 周期有多长?"。后续比较器和生成器都只是在这个时间基准上放置边沿。
|
||||
|
||||
构建 20 kHz 时间基准
|
||||
=====================
|
||||
|
||||
对于向上计数模式, ``period_ticks = resolution_hz / frequency_hz`` 。以下配置的 Tick 为 1 MHz(每 Tick 1 微秒),周期为 50 Tick,频率即为 20 kHz。下图展示了计数器从 0 上升到 50 然后复位的过程 —— ``TEZ`` (定时器归零事件)和 ``TEP`` (定时器峰值事件)是生成器使用的两个边界。
|
||||
|
||||
.. figure:: /../_static/mcpwm/timer_up_count.svg
|
||||
:align: center
|
||||
:alt: 向上计数:计数器形成锯齿波,从 0 上升到 50,TEZ 在归零时触发,TEP 在峰值时触发。
|
||||
|
||||
向上计数:计数器形成锯齿波,从 0 上升到 50,TEZ 在归零时触发,TEP 在峰值时触发。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
mcpwm_timer_handle_t timer = NULL;
|
||||
mcpwm_timer_config_t timer_config = {
|
||||
.group_id = 0,
|
||||
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
|
||||
.resolution_hz = 1000000,
|
||||
.period_ticks = 50,
|
||||
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
|
||||
};
|
||||
ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timer));
|
||||
|
||||
示例代码只覆盖了最核心的配置,但 :cpp:type:`mcpwm_timer_config_t` 中还有一些未出现的字段,在特定场景下同样重要:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`group_id <mcpwm_timer_config_t::group_id>` — 定时器从哪个 MCPWM 组分配。不同芯片会提供不同数量的组,每个组包含一组共享时钟分频器的定时器、操作器、比较器和生成器。 ``0`` 表示第一个组,大多数设计用它就够了。
|
||||
- :cpp:member:`clk_src <mcpwm_timer_config_t::clk_src>` — 定时器的时钟源。:c:macro:`MCPWM_TIMER_CLK_SRC_DEFAULT` 选择 PLL 时钟,适合绝大多数应用。部分芯片还提供其他时钟源,可在 PLL 被关闭(例如浅睡眠)时显式指定,让定时器继续计数。
|
||||
- :cpp:member:`resolution_hz <mcpwm_timer_config_t::resolution_hz>` — 计数器的 Tick 频率。一个 Tick 持续 ``1 / resolution_hz`` 秒,1 MHz 即每 Tick 1 微秒。它决定了比较器能放置的边沿最细粒度。
|
||||
- :cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` — 一个完整 PWM 周期包含的 Tick 数。频率为 ``resolution_hz / period_ticks``。
|
||||
- :cpp:member:`count_mode <mcpwm_timer_config_t::count_mode>` — 计数器是只向上计数(边沿对齐 PWM),还是先上后下(中心对齐 PWM)。两种计数形态见 :ref:`计数模式与波形 <mcpwm-timer-counting-modes>`;硬件还支持向下计数。
|
||||
- :cpp:member:`intr_priority <mcpwm_timer_config_t::intr_priority>` — 定时器回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级;当回调需要抢占其他中断时(例如对时序敏感的电机控制),可适当提高。
|
||||
|
||||
示例没有设置 :cpp:member:`flags <mcpwm_timer_config_t::flags>`,因此这些开关都处于关闭状态——这也是安全的默认值。其中两个值得了解:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`update_period_on_empty <mcpwm_timer_config_t::flags::update_period_on_empty>` 和 :cpp:member:`update_period_on_sync <mcpwm_timer_config_t::flags::update_period_on_sync>` — 默认关闭,因此 :cpp:func:`mcpwm_timer_set_period()` 会立即生效。打开后可把频率切换推迟到安全边界,见 :ref:`频率更新 <mcpwm-timer-safe-update>`。
|
||||
- :cpp:member:`allow_pd <mcpwm_timer_config_t::flags::allow_pd>` — 允许在睡眠时关闭 MCPWM 电源域。驱动会在睡眠前后备份并恢复定时器寄存器,从而省电,代价是额外占用 RAM。
|
||||
|
||||
在完成操作器、比较器和生成器的配置后,再启用并启动定时器:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
ESP_ERROR_CHECK(mcpwm_timer_enable(timer));
|
||||
ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
|
||||
|
||||
:cpp:func:`mcpwm_timer_enable()` 启用定时器运行所需的系统服务:使能定时器中断,并在启用电源管理时持有该组电源管理锁,避免时钟变频干扰 PWM 时序。:cpp:func:`mcpwm_timer_start_stop()` 则负责启动和停止计数。删除定时器前,先调用 :cpp:func:`mcpwm_timer_disable()` 恢复原状,再调用 :cpp:func:`mcpwm_del_timer()`。
|
||||
|
||||
:cpp:func:`mcpwm_timer_start_stop()` 的第三个参数选择停止行为:
|
||||
|
||||
.. list::
|
||||
|
||||
- :c:macro:`MCPWM_TIMER_START_NO_STOP` — 持续运行,直到显式停止。
|
||||
- :c:macro:`MCPWM_TIMER_START_STOP_EMPTY` — 下次计数到零(TEZ)时自动停止。适用于单次或需要完整周期后再停止的同步启动。
|
||||
- :c:macro:`MCPWM_TIMER_START_STOP_FULL` — 下次计数到峰值(TEP)时自动停止。适用于单个完整周期后停止。
|
||||
|
||||
.. _mcpwm-timer-counting-modes:
|
||||
|
||||
计数模式与波形
|
||||
==============
|
||||
|
||||
**向上计数** 模式下,计数器从 0 计数到 :cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` 然后复位。波形为锯齿波,PWM 边沿对齐在周期的一侧——这称为 *边沿对齐* PWM。
|
||||
|
||||
**向上-向下计数** 模式下,计数器先向上到 ``period_ticks / 2``,再向下回 0。波形为三角波,PWM 边沿围绕周期中心对称 —— *中心对齐* PWM。中心对齐 PWM 因谐波失真更小,常用于电机控制。
|
||||
|
||||
.. figure:: /../_static/mcpwm/timer_up_down_count.svg
|
||||
:align: center
|
||||
:alt: 向上-向下计数:计数器形成三角波,上升到 25(50 的一半),再下降回 0。
|
||||
|
||||
向上-向下计数:计数器形成三角波,上升到 25(50 的一半),再下降回 0。
|
||||
|
||||
两种模式的频率均为 ``resolution_hz / period_ticks``。选择足够高的分辨率以满足所需的占空比精度,再选择周期以达到目标频率。
|
||||
|
||||
.. important::
|
||||
|
||||
:cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` 表示完整 PWM 周期的总 Tick 数,而不是所有模式下都表示计数器峰值。
|
||||
|
||||
.. list::
|
||||
|
||||
- 在 ``MCPWM_TIMER_COUNT_MODE_UP`` 中,计数器范围是 ``0 -> period_ticks``。
|
||||
- 在 ``MCPWM_TIMER_COUNT_MODE_UP_DOWN`` 中,硬件峰值是 ``period_ticks / 2``,完整周期是 ``0 -> peak -> 0``。
|
||||
|
||||
例如 :cpp:member:`resolution_hz <mcpwm_timer_config_t::resolution_hz>` = 1 MHz、:cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` = 50 时:向上计数模式为 ``0 -> 50``,向上-向下模式为 ``0 -> 25 -> 0``。两者完整周期都是 50 微秒,因此频率都为 20 kHz,只是边沿分布不同。
|
||||
|
||||
.. _mcpwm-timer-safe-update:
|
||||
|
||||
频率更新
|
||||
========
|
||||
|
||||
默认情况下,:cpp:func:`mcpwm_timer_set_period()` 立即生效,可能截断当前周期并产生不完整的脉冲。设置 :cpp:member:`update_period_on_empty <mcpwm_timer_config_t::flags::update_period_on_empty>` 可在计数归零时更新,设置 :cpp:member:`update_period_on_sync <mcpwm_timer_config_t::flags::update_period_on_sync>` 则在同步事件时更新。若需保持占空比不变,应同时按比例更新比较值:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
// 周期由 50 改为 100,同时保持 40% 占空比
|
||||
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 40));
|
||||
ESP_ERROR_CHECK(mcpwm_timer_set_period(timer, 100));
|
||||
|
||||
对大多数实时调参场景,优先修改比较器以改变占空比,只有在确实需要改 PWM 频率时才修改定时器周期。电机和电源应用通常还应配合 :cpp:member:`update_period_on_empty <mcpwm_timer_config_t::flags::update_period_on_empty>` 或同步更新,避免在周期中间切换参数。
|
||||
|
||||
定时器事件回调
|
||||
==============
|
||||
|
||||
定时器可在达到峰值(:cpp:member:`on_full <mcpwm_timer_event_callbacks_t::on_full>`)、归零(:cpp:member:`on_empty <mcpwm_timer_event_callbacks_t::on_empty>`)或停止(:cpp:member:`on_stop <mcpwm_timer_event_callbacks_t::on_stop>`)时通知应用。必须在启用定时器前注册回调。回调在 ISR 上下文执行,不能阻塞、分配内存或调用普通 FreeRTOS API,应使用 ``...FromISR`` 变体。
|
||||
|
||||
.. note::
|
||||
|
||||
定时器和捕获定时器可能与同组其他对象共享分频器。若同一组里需要多种分辨率,请按请求分辨率单调顺序创建对象,避免分频冲突。详细规则见 :doc:`高级主题 <mcpwm_advanced>`。
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static bool IRAM_ATTR on_timer_empty(mcpwm_timer_handle_t timer,
|
||||
const mcpwm_timer_event_data_t *edata,
|
||||
void *user_ctx)
|
||||
{
|
||||
BaseType_t high_task_woken = pdFALSE;
|
||||
vTaskNotifyGiveFromISR((TaskHandle_t)user_ctx, &high_task_woken);
|
||||
return high_task_woken == pdTRUE;
|
||||
}
|
||||
|
||||
mcpwm_timer_event_callbacks_t cbs = { .on_empty = on_timer_empty };
|
||||
ESP_ERROR_CHECK(mcpwm_timer_register_event_callbacks(timer, &cbs,
|
||||
xTaskGetCurrentTaskHandle()));
|
||||
|
||||
如何在同步边沿将定时器加载到指定相位,见 :doc:`同步 <mcpwm_sync>`。
|
||||
|
||||
API 参考
|
||||
========
|
||||
|
||||
MCPWM 定时器驱动函数
|
||||
--------------------
|
||||
|
||||
.. include-build-file:: inc/mcpwm_timer.inc
|
||||
Reference in New Issue
Block a user