diff --git a/docs/_static/diagrams/mcpwm/deadtime_active_high.json b/docs/_static/diagrams/mcpwm/deadtime_active_high.json new file mode 100644 index 0000000000..8a396f18f5 --- /dev/null +++ b/docs/_static/diagrams/mcpwm/deadtime_active_high.json @@ -0,0 +1,26 @@ +{ + "signal": [ + { + "name": "origin", + "wave": "0...1.....0...", + "node": "....a.....b..." + }, + { + "name": "pwm_A", + "wave": "0....1....0...", + "node": ".....c....." + }, + { + "name": "pwm_B", + "wave": "0...1......0..", + "node": "...........d.." + } + ], + "edge": [ + "a|->c RED", + "b|->d FED" + ], + "head": { + "text": "Active High" + } +} diff --git a/docs/_static/diagrams/mcpwm/deadtime_active_high_complementary.json b/docs/_static/diagrams/mcpwm/deadtime_active_high_complementary.json new file mode 100644 index 0000000000..fa11d72590 --- /dev/null +++ b/docs/_static/diagrams/mcpwm/deadtime_active_high_complementary.json @@ -0,0 +1,26 @@ +{ + "signal": [ + { + "name": "origin", + "wave": "0...1.....0...", + "node": "....a.....b..." + }, + { + "name": "pwm_A", + "wave": "0....1....0...", + "node": ".....c....." + }, + { + "name": "pwm_B", + "wave": "1...0......1..", + "node": "...........d.." + } + ], + "edge": [ + "a|->c RED", + "b|->d FED" + ], + "head": { + "text": "Active High, Complementary" + } +} diff --git a/docs/_static/diagrams/mcpwm/deadtime_active_low.json b/docs/_static/diagrams/mcpwm/deadtime_active_low.json new file mode 100644 index 0000000000..b225dff2e0 --- /dev/null +++ b/docs/_static/diagrams/mcpwm/deadtime_active_low.json @@ -0,0 +1,26 @@ +{ + "signal": [ + { + "name": "origin", + "wave": "0...1.....0...", + "node": "....a.....b..." + }, + { + "name": "pwm_A", + "wave": "1....0....1...", + "node": ".....c....." + }, + { + "name": "pwm_B", + "wave": "1...0......1..", + "node": "...........d.." + } + ], + "edge": [ + "a|->c RED", + "b|->d FED" + ], + "head": { + "text": "Active Low" + } +} diff --git a/docs/_static/diagrams/mcpwm/deadtime_active_low_complementary.json b/docs/_static/diagrams/mcpwm/deadtime_active_low_complementary.json new file mode 100644 index 0000000000..eefd2b88ff --- /dev/null +++ b/docs/_static/diagrams/mcpwm/deadtime_active_low_complementary.json @@ -0,0 +1,26 @@ +{ + "signal": [ + { + "name": "origin", + "wave": "0...1.....0...", + "node": "....a.....b..." + }, + { + "name": "pwm_A", + "wave": "1....0....1...", + "node": ".....c....." + }, + { + "name": "pwm_B", + "wave": "0...1......0..", + "node": "...........d.." + } + ], + "edge": [ + "a|->c RED", + "b|->d FED" + ], + "head": { + "text": "Active Low, Complementary" + } +} diff --git a/docs/_static/diagrams/mcpwm/deadtime_fedb_bypassa.json b/docs/_static/diagrams/mcpwm/deadtime_fedb_bypassa.json new file mode 100644 index 0000000000..d4fcfc0d8b --- /dev/null +++ b/docs/_static/diagrams/mcpwm/deadtime_fedb_bypassa.json @@ -0,0 +1,28 @@ +{ + "signal": [ + { + "name": "origin_A", + "wave": "0...1.....0..." + }, + { + "name": "origin_B", + "wave": "0...1.....0...", + "node": "..........a..." + }, + { + "name": "pwm_A", + "wave": "0...1.....0..." + }, + { + "name": "pwm_B", + "wave": "0...1......0..", + "node": "...........b..." + } + ], + "edge": [ + "a|->b FED" + ], + "head": { + "text": "FED on B, Bypass A" + } +} diff --git a/docs/_static/diagrams/mcpwm/deadtime_reda_bypassb.json b/docs/_static/diagrams/mcpwm/deadtime_reda_bypassb.json new file mode 100644 index 0000000000..f65f031223 --- /dev/null +++ b/docs/_static/diagrams/mcpwm/deadtime_reda_bypassb.json @@ -0,0 +1,28 @@ +{ + "signal": [ + { + "name": "origin_A", + "wave": "0...1.....0...", + "node": "....a........." + }, + { + "name": "origin_B", + "wave": "0...1.....0..." + }, + { + "name": "pwm_A", + "wave": "0....1....0...", + "node": ".....b....." + }, + { + "name": "pwm_B", + "wave": "0...1.....0..." + } + ], + "edge": [ + "a|->b RED" + ], + "head": { + "text": "RED on A, Bypass B" + } +} diff --git a/docs/_static/diagrams/mcpwm/deadtime_redb_fedb_bypassa.json b/docs/_static/diagrams/mcpwm/deadtime_redb_fedb_bypassa.json new file mode 100644 index 0000000000..936978185a --- /dev/null +++ b/docs/_static/diagrams/mcpwm/deadtime_redb_fedb_bypassa.json @@ -0,0 +1,29 @@ +{ + "signal": [ + { + "name": "origin_A", + "wave": "0...1.....0..." + }, + { + "name": "origin_B", + "wave": "0...1.....0...", + "node": "....a.....b..." + }, + { + "name": "pwm_A", + "wave": "0...1.....0..." + }, + { + "name": "pwm_B", + "wave": "0....1.....0..", + "node": ".....c.....d..." + } + ], + "edge": [ + "a|->c RED", + "b|->d FED" + ], + "head": { + "text": "Bypass A, RED + FED on B" + } +} diff --git a/docs/_static/diagrams/mcpwm/dual_edge_asym_active_low.json b/docs/_static/diagrams/mcpwm/dual_edge_asym_active_low.json new file mode 100644 index 0000000000..b5981ef7b3 --- /dev/null +++ b/docs/_static/diagrams/mcpwm/dual_edge_asym_active_low.json @@ -0,0 +1,15 @@ +{ + "signal": [ + { + "name": "pwm_A", + "wave": "01..0..1..0." + }, + { + "name": "pwm_B", + "wave": "0..1..0..1.." + } + ], + "head": { + "text": "Dual Edge Asymmetric Waveform, Active Low" + } +} diff --git a/docs/_static/diagrams/mcpwm/dual_edge_sym_active_low.json b/docs/_static/diagrams/mcpwm/dual_edge_sym_active_low.json new file mode 100644 index 0000000000..680b3295bf --- /dev/null +++ b/docs/_static/diagrams/mcpwm/dual_edge_sym_active_low.json @@ -0,0 +1,15 @@ +{ + "signal": [ + { + "name": "pwm_A", + "wave": "0.1..0...1..0.." + }, + { + "name": "pwm_B", + "wave": "0..10.....10..." + } + ], + "head": { + "text": "Dual Edge Symmetric Waveform, Active Low" + } +} diff --git a/docs/_static/diagrams/mcpwm/dual_edge_sym_complementary.json b/docs/_static/diagrams/mcpwm/dual_edge_sym_complementary.json new file mode 100644 index 0000000000..44b27470ab --- /dev/null +++ b/docs/_static/diagrams/mcpwm/dual_edge_sym_complementary.json @@ -0,0 +1,15 @@ +{ + "signal": [ + { + "name": "pwm_A", + "wave": "01..0...1..0" + }, + { + "name": "pwm_B", + "wave": "1.01.....01." + } + ], + "head": { + "text": "Dual Edge Symmetric Waveform, Complementary" + } +} diff --git a/docs/_static/diagrams/mcpwm/mcpwm_overview.diag b/docs/_static/diagrams/mcpwm/mcpwm_overview.diag new file mode 100644 index 0000000000..ea3a6debb4 --- /dev/null +++ b/docs/_static/diagrams/mcpwm/mcpwm_overview.diag @@ -0,0 +1,59 @@ +blockdiag mcpwm_overview { + default_fontsize = 18; + node_width = 130; + node_height = 100; + default_group_color = lightgrey; + + mcpwm_timers [label = "MCPWM\nTimer", stacked]; + timer_sync [label = "Timer Sync", stacked]; + timer_sync <-> mcpwm_timers; + + mcpwm_capture_timer [label = "MCPWM\nCapture Timer"]; + mcpwm_capture_channels [label = "MCPWM\nCapture Chan", stacked]; + mcpwm_capture_gpio [label = "Cap\nGPIO", shape = minidiamond]; + mcpwm_capture_timer -> mcpwm_capture_channels; + mcpwm_capture_channels <- mcpwm_capture_gpio; + + timer_sync -> mcpwm_capture_timer; + gpio_sync [label = "Sync\nGPIO", shape = minidiamond]; + gpio_sync -> mcpwm_timers; + gpio_sync -> mcpwm_capture_timer; + + mcpwm_compares [label = "MCPWM\nComparators", stacked]; + mcpwm_generators [label = "MCPWM\nGenerators", stacked]; + mcpwm_dead_time [label = "Dead Time"]; + mcpwm_carrier [label = "Carrier\nModulation"]; + mcpwm_brake [label = "Brake"]; + pwma [label = "PWM_A", shape = minidiamond]; + pwmb [label = "PWM_B", shape = minidiamond]; + stub [shape = none]; + mcpwm_timers -> mcpwm_generators; + mcpwm_timers -> mcpwm_compares; + mcpwm_compares -> mcpwm_generators [folded]; + mcpwm_generators -> mcpwm_dead_time; + mcpwm_dead_time -> mcpwm_carrier; + mcpwm_carrier -> mcpwm_brake; + mcpwm_brake -> stub; + stub -> pwma, pwmb; + mcpwm_generators -> mcpwm_carrier; + mcpwm_generators -> mcpwm_brake; + mcpwm_generators -> stub; + + gpio_faults [label = "Fault\nGPIO", shape = minidiamond]; + mcpwm_brake <- gpio_faults [folded]; + + group { + label = "MCPWM Operators"; + mcpwm_compares, mcpwm_generators, mcpwm_dead_time, mcpwm_carrier, mcpwm_brake; + } + + group { + label = "MCPWM Capture"; + mcpwm_capture_timer, mcpwm_capture_channels, mcpwm_capture_gpio; + } + + group { + label = "MCPWM Sync"; + gpio_sync, timer_sync; + } +} diff --git a/docs/_static/diagrams/mcpwm/pulse_placement_asym.json b/docs/_static/diagrams/mcpwm/pulse_placement_asym.json new file mode 100644 index 0000000000..ae0097d351 --- /dev/null +++ b/docs/_static/diagrams/mcpwm/pulse_placement_asym.json @@ -0,0 +1,15 @@ +{ + "signal": [ + { + "name": "pwm_A", + "wave": "0.1..0..1..0..1..0..1..0.." + }, + { + "name": "pwm_B", + "wave": "01.....0.....1.....0.....1" + } + ], + "head": { + "text": "Pulse Placement Asymmetric Waveform" + } +} diff --git a/docs/_static/diagrams/mcpwm/single_edge_asym_active_high.json b/docs/_static/diagrams/mcpwm/single_edge_asym_active_high.json new file mode 100644 index 0000000000..f7b4bd6446 --- /dev/null +++ b/docs/_static/diagrams/mcpwm/single_edge_asym_active_high.json @@ -0,0 +1,15 @@ +{ + "signal": [ + { + "name": "pwm_A", + "wave": "01....0..1....0..1" + }, + { + "name": "pwm_B", + "wave": "01..0....1..0....1" + } + ], + "head": { + "text": "Single Edge Asymmetric Waveform, Active High" + } +} diff --git a/docs/_static/diagrams/mcpwm/single_edge_asym_active_low.json b/docs/_static/diagrams/mcpwm/single_edge_asym_active_low.json new file mode 100644 index 0000000000..68386ffd9f --- /dev/null +++ b/docs/_static/diagrams/mcpwm/single_edge_asym_active_low.json @@ -0,0 +1,15 @@ +{ + "signal": [ + { + "name": "pwm_A", + "wave": "10....1..0....1..0" + }, + { + "name": "pwm_B", + "wave": "10..1....0..1....0" + } + ], + "head": { + "text": "Single Edge Asymmetric Waveform, Active Low" + } +} diff --git a/docs/_static/mcpwm-block-diagram.png b/docs/_static/mcpwm-block-diagram.png deleted file mode 100644 index 97620c29da..0000000000 Binary files a/docs/_static/mcpwm-block-diagram.png and /dev/null differ diff --git a/docs/_static/mcpwm-brushed-dc-control.png b/docs/_static/mcpwm-brushed-dc-control.png deleted file mode 100644 index f8cec6fff8..0000000000 Binary files a/docs/_static/mcpwm-brushed-dc-control.png and /dev/null differ diff --git a/docs/_static/mcpwm-overview.png b/docs/_static/mcpwm-overview.png deleted file mode 100644 index 6d3204466a..0000000000 Binary files a/docs/_static/mcpwm-overview.png and /dev/null differ diff --git a/docs/doxygen/Doxyfile_esp32 b/docs/doxygen/Doxyfile_esp32 index 960010f85d..43644961e8 100644 --- a/docs/doxygen/Doxyfile_esp32 +++ b/docs/doxygen/Doxyfile_esp32 @@ -5,7 +5,14 @@ INPUT += \ $(PROJECT_PATH)/components/driver/include/driver/i2s_pdm.h \ $(PROJECT_PATH)/components/driver/include/driver/i2s_std.h \ $(PROJECT_PATH)/components/driver/include/driver/i2s_types.h \ - $(PROJECT_PATH)/components/driver/include/driver/mcpwm.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_cap.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_cmpr.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_fault.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_gen.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_oper.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_sync.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_timer.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_types.h \ $(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \ $(PROJECT_PATH)/components/driver/include/driver/rmt_common.h \ $(PROJECT_PATH)/components/driver/include/driver/rmt_encoder.h \ diff --git a/docs/doxygen/Doxyfile_esp32s3 b/docs/doxygen/Doxyfile_esp32s3 index 7b9a67d7aa..4c247aaffa 100644 --- a/docs/doxygen/Doxyfile_esp32s3 +++ b/docs/doxygen/Doxyfile_esp32s3 @@ -5,7 +5,14 @@ INPUT += \ $(PROJECT_PATH)/components/driver/include/driver/i2s_std.h \ $(PROJECT_PATH)/components/driver/include/driver/i2s_tdm.h \ $(PROJECT_PATH)/components/driver/include/driver/i2s_types.h \ - $(PROJECT_PATH)/components/driver/include/driver/mcpwm.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_cap.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_cmpr.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_fault.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_gen.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_oper.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_sync.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_timer.h \ + $(PROJECT_PATH)/components/driver/include/driver/mcpwm_types.h \ $(PROJECT_PATH)/components/driver/include/driver/pulse_cnt.h \ $(PROJECT_PATH)/components/driver/include/driver/rmt_common.h \ $(PROJECT_PATH)/components/driver/include/driver/rmt_encoder.h \ diff --git a/docs/en/api-reference/peripherals/mcpwm.rst b/docs/en/api-reference/peripherals/mcpwm.rst index d07f3b32d9..6166899582 100644 --- a/docs/en/api-reference/peripherals/mcpwm.rst +++ b/docs/en/api-reference/peripherals/mcpwm.rst @@ -1,238 +1,902 @@ Motor Control Pulse Width Modulator (MCPWM) =========================================== -{IDF_TARGET_NAME} has two MCPWM units which can be used to control different types of motors. Each unit has three pairs of PWM outputs. +The MCPWM peripheral is a versatile PWM generator, which contains various submodules to make it a key element in power electronic applications like motor control, digital power and so on. Typically, the MCPWM peripheral can be used in the following scenarios: -.. figure:: ../../../_static/mcpwm-overview.png +- Digital motor control, e.g. brushed/brushless DC motor, RC servo motor +- Switch mode based digital power conversion +- Power DAC, where the duty cycle is equivalent to a DAC analog value +- Calculate external pulse width, and convert it into other analog value like speed, distance +- Generate Space Vector PWM (SVPWM) signals for Field Oriented Control (FOC) + +The main submodules are listed in the following diagram: + +.. blockdiag:: /../_static/diagrams/mcpwm/mcpwm_overview.diag + :caption: MCPWM Overview :align: center - :alt: MCPWM Overview - :figclass: align-center - MCPWM Overview +- **MCPWM Timer**: The time base of the final PWM signal, it also determines the event timing of other submodules. +- **MCPWM Operator**: The key module that is responsible for generating the PWM waveforms. It consists of other submodules, like comparator, PWM generator, dead-time and carrier modulator. +- **MCPWM Comparator**: The compare module takes the time-base count value as input, and continuously compare to the threshold value that configured by user. When the time-base counter is equal to any of the threshold value, an compare event will be generated and the MCPWM generator can update its level accordingly. +- **MCPWM Generator**: One MCPWM generator can generate a pair of PWM waves, complementarily or independently, based on various events triggered from other submodules like MCPWM Timer, MCPWM Comparator. +- **MCPWM Fault**: The fault module is used to detect the fault condition from outside, mainly via GPIO matrix. Once the fault signal is active, MCPWM Operator will force all the generators into a predefined state, to protect the system from damage. +- **MCPWM Sync**: The sync module is used to synchronize the MCPWM timers, so that the final PWM signals generated by different MCPWM generators can have a fixed phase difference. The sync signal can be routed from GPIO matrix or from MCPWM Timer event. +- **Dead Time**: This submodule is used to insert extra delay to the existing PWM edges that generated in the previous steps. +- **Carrier Modulation**: The carrier submodule allows a high-frequency carrier signal to modulate the PWM waveforms generated by the generator and dead time submodules. This capability is mandatory if you need pulse transformer-based gate drivers to control the power switching elements. +- **Brake**: MCPWM operator can set how to brake the generators when particular fault is detected. We can shut down the PWM output immediately or regulate the PWM output cycle by cycle, depends on how critical the fault is. +- **MCPWM Capture**: This is a standalone submodule which can work even without the above MCPWM operators. The capture consists one dedicated timer and several independent channels. Each channel is connected to the GPIO, a pulse on the GPIO will trigger the capture timer to store the time-base count value and then notify the user by interrupt. Using this feature, we can measure a pulse width precisely. What's more, the capture timer can also be synchronized by the MCPWM Sync submodule. -Further in documentation the outputs of a single unit are labeled ``PWMxA`` / ``PWMxB``. +Functional Overview +------------------- -More detailed block diagram of the MCPWM unit is shown below. Each A/B pair may be clocked by any one of the three timers Timer 0, 1 and 2. The same timer may be used to clock more than one pair of PWM outputs. Each unit is also able to collect inputs such as ``SYNC SIGNALS``, detect ``FAULT SIGNALS`` like motor overcurrent or overvoltage, as well as obtain feedback with ``CAPTURE SIGNALS`` on e.g. a rotor position. +Description of the MCPWM functionality is divided into the following sections: -.. _mcpwm_block_diagram: +- `Resource Allocation and Initialization <#resource-allocation-and-initialization>`__ - covers how to allocate various MCPWM objects, like timers, operators, comparators, generators and so on. These objects are the basis of the following IO setting and control functions. +- `Timer Operations and Events <#timer-operations-and-events>`__ - describes control functions and event callbacks that supported by the MCPWM timer. +- `Comparator Operations and Events `__ - describes control functions and event callbacks that supported by the MCPWM comparator. +- `Generator Actions on Events <#generator-actions-on-events>`__ - describes how to set actions for MCPWM generators on particular events that generated by the MCPWM timer and comparators. +- `Classical PWM Waveforms and Generator Configurations <#classical-pwm-waveforms-and-generator-configurations>`__ - demonstrates some classical PWM waveforms that can be achieved by configuring generator actions. +- `Dead Time <#dead-time>`__ - describes how to set dead time for MCPWM generators. +- `Classical PWM Waveforms and Dead Time Configurations <#classical-pwm-waveforms-and-dead-time-configurations>`__ - demonstrates some classical PWM waveforms that can be achieved by configuring dead time. +- `Carrier Modulation <#carrier-modulation>`__ - describes how to set modulate a high frequency onto the final PWM waveforms. +- `Faults and Brake Actions <#faults-and-brake-actions>`__ - describes how to set brake actions for MCPWM operators on particular fault event. +- `Generator Force Actions <#generator-force-actions>`__ - describes how to control the generator output level asynchronously in a forceful way. +- `Synchronization <#synchronization>`__ - describes how to synchronize the MCPWM timers and get a fixed phase difference between the generated PWM signals. +- `Capture <#capture>`__ - describes how to use the MCPWM capture module to measure the pulse width of a signal. +- `Power Management <#power-management>`__ - describes how different source clock will affect power consumption. +- `IRAM Safe <#iram-safe>`__ - describes tips on how to make the RMT interrupt work better along with a disabled cache. +- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver. +- `Kconfig Options <#kconfig-options>`__ - lists the supported Kconfig options that can bring different effects to the driver. -.. figure:: ../../../_static/mcpwm-block-diagram.png - :align: center - :alt: MCPWM Block Diagram - :figclass: align-center +Resource Allocation and Initialization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - MCPWM Block Diagram +As displayed in the diagram above, the MCPWM peripheral consists of several submodules. Each submodule has its own resource allocation, which is described in the following sections. -Description of this API starts with configuration of MCPWM's **Timer** and **Generator** submodules to provide the basic motor control functionality. Then it discusses more advanced submodules and functionalities of a **Fault Handler**, signal **Capture** and **Carrier**. +MCPWM Timers +~~~~~~~~~~~~ -Contents --------- +You can allocate a MCPWM timer object by calling :cpp:func:`mcpwm_new_timer` function, with a configuration structure :cpp:type:`mcpwm_timer_config_t` as the parameter. The configuration structure is defined as: -* `Configure`_ a basic functionality of the outputs -* `Operate`_ the outputs to drive a motor -* `Adjust`_ how the motor is driven -* `Synchronize`_ sync timers to work together -* `Capture`_ external signals to provide additional control over the outputs -* Use `Fault Handler`_ to detect and manage faults -* Add a higher frequency `Carrier`_, if output signals are passed through an isolation transformer -* Extra configuration of `Resolution`_. +- :cpp:member:`mcpwm_timer_config_t::group_id` specifies the MCPWM group ID. The ID should belong to [0, :c:macro:`SOC_MCPWM_GROUPS` - 1] range. Please note, timers located in different groups are totally independent. +- :cpp:member:`mcpwm_timer_config_t::clk_src` sets the clock source of the timer. +- :cpp:member:`mcpwm_timer_config_t::resolution_hz` set the expected resolution of the timer, the driver internally will set a proper divider based on the clock source and the resolution. +- :cpp:member:`mcpwm_timer_config_t::count_mode` sets the count mode of the timer. +- :cpp:member:`mcpwm_timer_config_t::period_ticks` sets the period of the timer, in ticks (the tick resolution is set in the :cpp:member:`mcpwm_timer_config_t::resolution_hz`). +- :cpp:member:`mcpwm_timer_config_t::update_period_on_empty` sets whether to update the period value when the timer counts to zero. +- :cpp:member:`mcpwm_timer_config_t::update_period_on_sync` sets whether to update the period value when the timer takes a sync signal. + +The :cpp:func:`mcpwm_new_timer` will return a pointer to the allocated timer object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no more free timers in the MCPWM group, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ + +On the contrary, calling :cpp:func:`mcpwm_del_timer` function will free the allocated timer object. + +MCPWM Operators +~~~~~~~~~~~~~~~ + +You can allocate a MCPWM operator object by calling :cpp:func:`mcpwm_new_operator` function, with a configuration structure :cpp:type:`mcpwm_operator_config_t` as the parameter. The configuration structure is defined as: + +- :cpp:member:`mcpwm_operator_config_t::group_id` specifies the MCPWM group ID. The ID should belong to [0, :c:macro:`SOC_MCPWM_GROUPS` - 1] range. Please note, operators located in different groups are totally independent. +- :cpp:member:`mcpwm_operator_config_t::update_gen_action_on_tez` sets whether to update the generator action when the timer counts to zero. Here and below, the timer refers to the one that is connected to the operator by :cpp:func:`mcpwm_operator_connect_timer`. +- :cpp:member:`mcpwm_operator_config_t::update_gen_action_on_tep` sets whether to update the generator action when the timer counts to peak. +- :cpp:member:`mcpwm_operator_config_t::update_gen_action_on_sync` sets whether to update the generator action when the timer takes a sync signal. +- :cpp:member:`mcpwm_operator_config_t::update_dead_time_on_tez` sets whether to update the dead time when the timer counts to zero. +- :cpp:member:`mcpwm_operator_config_t::update_dead_time_on_tep` sets whether to update the dead time when the timer counts to peak. +- :cpp:member:`mcpwm_operator_config_t::update_dead_time_on_sync` sets whether to update the dead time when the timer takes a sync signal. + +The :cpp:func:`mcpwm_new_operator` will return a pointer to the allocated operator object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no more free operators in the MCPWM group, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ + +On the contrary, calling :cpp:func:`mcpwm_del_operator` function will free the allocated operator object. + +MCPWM Comparators +~~~~~~~~~~~~~~~~~ + +You can allocate a MCPWM comparator object by calling :cpp:func:`mcpwm_new_comparator` function, with a MCPWM operator handle and configuration structure :cpp:type:`mcpwm_comparator_config_t` as the parameter. The operator handle is created by :cpp:func:`mcpwm_new_operator`. The configuration structure is defined as: + +- :cpp:member:`mcpwm_comparator_config_t::update_cmp_on_tez` sets whether to update the compare threshold when the timer counts to zero. +- :cpp:member:`mcpwm_comparator_config_t::update_cmp_on_tep` sets whether to update the compare threshold when the timer counts to peak. +- :cpp:member:`mcpwm_comparator_config_t::update_cmp_on_sync` sets whether to update the compare threshold when the timer takes a sync signal. + +The :cpp:func:`mcpwm_new_comparator` will return a pointer to the allocated comparator object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no more free comparators in the MCPWM operator, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ + +On the contrary, calling :cpp:func:`mcpwm_del_comparator` function will free the allocated comparator object. + +MCPWM Generators +~~~~~~~~~~~~~~~~ + +You can allocate a MCPWM generator object by calling :cpp:func:`mcpwm_new_generator` function, with a MCPWM operator handle and configuration structure :cpp:type:`mcpwm_generator_config_t` as the parameter. The operator handle is created by :cpp:func:`mcpwm_new_operator`. The configuration structure is defined as: + +- :cpp:member:`mcpwm_generator_config_t::gen_gpio_num` sets the GPIO number used by the generator. +- :cpp:member:`mcpwm_generator_config_t::invert_pwm` sets whether to invert the PWM signal. +- :cpp:member:`mcpwm_generator_config_t::io_loop_back` sets whether to enable the loop back mode. It is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral. + +The :cpp:func:`mcpwm_new_generator` will return a pointer to the allocated generator object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no more free generators in the MCPWM operator, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ + +On the contrary, calling :cpp:func:`mcpwm_del_generator` function will free the allocated generator object. + +MCPWM Faults +~~~~~~~~~~~~ + +There are two types of faults: A fault signal reflected from the GPIO and a fault generated by software. To allocate a GPIO fault object, you can call :cpp:func:`mcpwm_new_gpio_fault` function, with configuration structure :cpp:type:`mcpwm_gpio_fault_config_t` as the parameter. The configuration structure is defined as: + +- :cpp:member:`mcpwm_gpio_fault_config_t::group_id` sets the MCPWM group ID. The ID should belong to [0, :c:macro:`SOC_MCPWM_GROUPS` - 1] range. Please note, GPIO fault located in different groups are totally independent, i.e. GPIO fault in group 0 can not be detected by the operator in group 1. +- :cpp:member:`mcpwm_gpio_fault_config_t::gpio_num` sets the GPIO number used by the fault. +- :cpp:member:`mcpwm_gpio_fault_config_t::active_level` sets the active level of the fault signal. +- :cpp:member:`mcpwm_gpio_fault_config_t::pull_up` and :cpp:member:`mcpwm_gpio_fault_config_t::pull_down` set whether to pull up and/or pull down the GPIO internally. +- :cpp:member:`mcpwm_gpio_fault_config_t::io_loop_back` sets whether to enable the loop back mode. It is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral. + +The :cpp:func:`mcpwm_new_gpio_fault` will return a pointer to the allocated fault object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no more free GPIO faults in the MCPWM group, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ + +Software fault object can be used to trigger a fault by calling a function :cpp:func:`mcpwm_soft_fault_activate` instead of waiting for a real fault signal on the GPIO. A software fault object can be allocated by calling :cpp:func:`mcpwm_new_soft_fault` function, with configuration structure :cpp:type:`mcpwm_soft_fault_config_t` as the parameter. Currently this configuration structure is left for future purpose. :cpp:func:`mcpwm_new_soft_fault` function will return a pointer to the allocated fault object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no memory left for the fault object, this function will return :c:macro:`ESP_ERR_NO_MEM` error. Although the software fault and GPIO fault are of different types, but the returned fault handle is of the same type. + +On the contrary, calling :cpp:func:`mcpwm_del_fault` function will free the allocated fault object, this function works for both software and GPIO fault. + +MCPWM Sync Sources +~~~~~~~~~~~~~~~~~~ + +The sync source is what can be used to synchronize the MCPWM timer and MCPWM capture timer. There're three types of sync sources: A sync source reflected from the GPIO, a sync source generated by software and a sync source generated by MCPWM timer event. + +To allocate a GPIO sync source, you can call :cpp:func:`mcpwm_new_gpio_sync_src` function, with configuration structure :cpp:type:`mcpwm_gpio_sync_src_config_t` as the parameter. The configuration structure is defined as: + +- :cpp:member:`mcpwm_gpio_sync_src_config_t::group_id` sets the MCPWM group ID. The ID should belong to [0, :c:macro:`SOC_MCPWM_GROUPS` - 1] range. Please note, GPIO sync source located in different groups are totally independent, i.e. GPIO sync source in group 0 can not be detected by the timers in group 1. +- :cpp:member:`mcpwm_gpio_sync_src_config_t::gpio_num` sets the GPIO number used by the sync source. +- :cpp:member:`mcpwm_gpio_sync_src_config_t::active_neg` sets whether the sync signal is active on falling edge. +- :cpp:member:`mcpwm_gpio_sync_src_config_t::pull_up` and :cpp:member:`mcpwm_gpio_sync_src_config_t::pull_down` set whether to pull up and/or pull down the GPIO internally. +- :cpp:member:`mcpwm_gpio_sync_src_config_t::io_loop_back` sets whether to enable the loop back mode. It is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral. + +The :cpp:func:`mcpwm_new_gpio_sync_src` will return a pointer to the allocated sync source object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no more free GPIO sync sources in the MCPWM group, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ + +To allocate a Timer event sync source, you can call :cpp:func:`mcpwm_new_timer_sync_src` function, with configuration structure :cpp:type:`mcpwm_timer_sync_src_config_t` as the parameter. The configuration structure is defined as: + +- :cpp:member:`mcpwm_timer_sync_src_config_t::timer_event` specifies on what timer event to generate the sync signal. +- :cpp:member:`mcpwm_timer_sync_src_config_t::propagate_input_sync` sets whether to propagate the input sync signal (i.e. the input sync signal will be routed to its sync output). + +The :cpp:func:`mcpwm_new_timer_sync_src` will return a pointer to the allocated sync source object if the allocation succeeds. Otherwise, it will return error code. Specifically, if a sync source has been allocated from the same timer before, this function will return :c:macro:`ESP_ERR_INVALID_STATE` error. + +Last but not least, to allocate a software sync source, you can call :cpp:func:`mcpwm_new_soft_sync_src` function, with configuration structure :cpp:type:`mcpwm_soft_sync_config_t` as the parameter. Currently this configuration structure is left for future purpose. :cpp:func:`mcpwm_new_soft_sync_src` will return a pointer to the allocated sync source object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no memory left for the sync source object, this function will return :c:macro:`ESP_ERR_NO_MEM` error. Please note, to make a software sync source take effect, don't forget to call :cpp:func:`mcpwm_soft_sync_activate`. + +On the contrary, calling :cpp:func:`mcpwm_del_sync_src` function will free the allocated sync source object, this function works for all types of sync sources. + +MCPWM Capture Timer and Channels +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The MCPWM group has a dedicated timer which is used to capture the timestamp when specific event occurred. The capture timer is connected with several independent channels, each channel is assigned with a GPIO. + +To allocate a capture timer, you can call :cpp:func:`mcpwm_new_capture_timer` function, with configuration structure :cpp:type:`mcpwm_capture_timer_config_t` as the parameter. The configuration structure is defined as: + +- :cpp:member:`mcpwm_capture_timer_config_t::group_id` sets the MCPWM group ID. The ID should belong to [0, :c:macro:`SOC_MCPWM_GROUPS` - 1] range. +- :cpp:member:`mcpwm_capture_timer_config_t::clk_src` sets the clock source of the capture timer. + +The :cpp:func:`mcpwm_new_capture_timer` will return a pointer to the allocated capture timer object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no free capture timer left in the MCPWM group, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ + +Next, to allocate a capture channel, you can call :cpp:func:`mcpwm_new_capture_channel` function, with a capture timer handle and configuration structure :cpp:type:`mcpwm_capture_channel_config_t` as the parameter. The configuration structure is defined as: + +- :cpp:member:`mcpwm_capture_channel_config_t::gpio_num` sets the GPIO number used by the capture channel. +- :cpp:member:`mcpwm_capture_channel_config_t::prescale` sets the prescaler of the input signal. +- :cpp:member:`mcpwm_capture_channel_config_t::pos_edge` and :cpp:member:`mcpwm_capture_channel_config_t::neg_edge` set whether to capture on the positive and/or negative edge of the input signal. +- :cpp:member:`mcpwm_capture_channel_config_t::pull_up` and :cpp:member:`mcpwm_capture_channel_config_t::pull_down` set whether to pull up and/or pull down the GPIO internally. +- :cpp:member:`mcpwm_capture_channel_config_t::invert_cap_signal` sets whether to invert the capture signal. +- :cpp:member:`mcpwm_capture_channel_config_t::io_loop_back` sets whether to enable the loop back mode. It is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral. + +The :cpp:func:`mcpwm_new_capture_channel` will return a pointer to the allocated capture channel object if the allocation succeeds. Otherwise, it will return error code. Specifically, when there are no free capture channel left in the capture timer, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. + +On the contrary, calling :cpp:func:`mcpwm_del_capture_channel` and :cpp:func:`mcpwm_del_capture_timer` function will free the allocated capture channel and timer object accordingly. + +Timer Operations and Events +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Register Event Callbacks +~~~~~~~~~~~~~~~~~~~~~~~~ + +The MCPWM timer can generate different events at runtime. If you have some function that should be called when particular event happens, you should hook your function to the interrupt service routine by calling :cpp:func:`mcpwm_timer_register_event_callbacks`. The callback function prototype is declared in :cpp:type:`mcpwm_timer_event_cb_t`. All supported event callbacks are listed in the :cpp:type:`mcpwm_timer_event_callbacks_t`: + +- :cpp:member:`mcpwm_timer_event_callbacks_t::on_full` sets callback function for timer when it counts to peak value. +- :cpp:member:`mcpwm_timer_event_callbacks_t::on_empty` sets callback function for timer when it counts to zero. +- :cpp:member:`mcpwm_timer_event_callbacks_t::on_stop` sets callback function for timer when it is stopped. + +The callback functions above are called within the ISR context, so they should **not** attempt to block (e.g., make sure that only FreeRTOS APIs with ``ISR`` suffix is called within the function). + +The parameter ``user_data`` of :cpp:func:`mcpwm_timer_register_event_callbacks` function is used to save user's own context, it will be passed to each callback function directly. + +This function will lazy install interrupt service for the MCPWM timer without enabling it. It is only allowed to be called before before :cpp:func:`mcpwm_timer_enable`, otherwise the :c:macro:`ESP_ERR_INVALID_STATE` error will be returned. See also `Enable and Disable timer <#enable-and-disable-timer>`__ for more information. + +Enable and Disable Timer +~~~~~~~~~~~~~~~~~~~~~~~~ + +Before doing IO control to the timer, user needs to enable the timer first, by calling :cpp:func:`mcpwm_timer_enable`. Internally, this function will: + +* switch the timer state from **init** to **enable**. +* enable the interrupt service if it has been lazy installed by :cpp:func:`mcpwm_timer_register_event_callbacks`. +* acquire a proper power management lock if a specific clock source (e.g. PLL_160M clock) is selected. See also `Power management <#power-management>`__ for more information. + +On the contrary, calling :cpp:func:`mcpwm_timer_disable` will put the timer driver back to **init** state, disable the interrupts service and release the power management lock. + +Start and Stop Timer +~~~~~~~~~~~~~~~~~~~~ + +The basic IO operation of a timer is to start and stop. Calling :cpp:func:`mcpwm_timer_start_stop` with different :cpp:type:`mcpwm_timer_start_stop_cmd_t` commands can start the timer immediately or stop the timer at a specific event. What're more, you can even start the timer for only one round, that means, the timer will count to peak value or zero, and then stop itself. + +Connect Timer with Operator +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The allocated MCPWM Timer should be connected with a MCPWM operator by calling :cpp:func:`mcpwm_operator_connect_timer`, so that the operator can take that timer as its time base, and generate the required PWM waves. Make sure the MCPWM timer and operator are in the same group, otherwise, this function will return :c:macro:`ESP_ERR_INVALID_ARG` error. + +Comparator Operations and Events +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Register Event Callbacks +~~~~~~~~~~~~~~~~~~~~~~~~ + +The MCPWM comparator can inform the user when the timer counter equals to the compare value. If you have some function that should be called when this event happens, you should hook your function to the interrupt service routine by calling :cpp:func:`mcpwm_comparator_register_event_callbacks`. The callback function prototype is declared in :cpp:type:`mcpwm_compare_event_cb_t`. All supported event callbacks are listed in the :cpp:type:`mcpwm_comparator_event_callbacks_t`: + +- :cpp:member:`mcpwm_comparator_event_callbacks_t::on_reach` sets callback function for comparator when the timer counter equals to the compare value. + +The callback function will provide event specific data of type :cpp:type:`mcpwm_compare_event_data_t` to the user. The callback function is called within the ISR context, so is should **not** attempt to block (e.g., make sure that only FreeRTOS APIs with ``ISR`` suffix is called within the function). + +The parameter ``user_data`` of :cpp:func:`mcpwm_comparator_register_event_callbacks` function is used to save user's own context, it will be passed to the callback function directly. + +This function will lazy install interrupt service for the MCPWM comparator, whereas the service can only be removed in :cpp:type:`mcpwm_del_comparator`. + +Set Compare Value +~~~~~~~~~~~~~~~~~ + +You can set the compare value for the MCPWM comparator at runtime by calling :cpp:func:`mcpwm_comparator_set_compare_value`. There're a few points to note: + +- New compare value might won't take effect immediately. The update time for the compare value is set by :cpp:member:`mcpwm_comparator_config_t::update_cmp_on_tez` or :cpp:member:`mcpwm_comparator_config_t::update_cmp_on_tep` or :cpp:member:`mcpwm_comparator_config_t::update_cmp_on_sync`. +- Make sure the operator has connected to one MCPWM timer already by :cpp:func:`mcpwm_operator_connect_timer`. Otherwise, it will return error code :c:macro:`ESP_ERR_INVALID_STATE`. +- The compare value shouldn't exceed timer's count peak, otherwise, the compare event will never got triggered. + +Generator Actions on Events +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Set Generator Action on Timer Event +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One generator can set multiple actions on different timer events, by calling :cpp:func:`mcpwm_generator_set_actions_on_timer_event` with variable number of action configurations. The action configuration is defined in :cpp:type:`mcpwm_gen_timer_event_action_t`: + +- :cpp:member:`mcpwm_gen_timer_event_action_t::direction` specific the timer direction. The supported directions are listed in :cpp:type:`mcpwm_timer_direction_t`. +- :cpp:member:`mcpwm_gen_timer_event_action_t::event` specifies the timer event. The supported timer events are listed in :cpp:type:`mcpwm_timer_event_t`. +- :cpp:member:`mcpwm_gen_timer_event_action_t::action` specifies the generator action to be taken. The supported actions are listed in :cpp:type:`mcpwm_generator_action_t`. + +There's a helper macro :c:macro:`MCPWM_GEN_TIMER_EVENT_ACTION` to simplify the construction of a timer event action entry. + +Please note, the argument list of :cpp:func:`mcpwm_generator_set_actions_on_timer_event` **must** be terminated by :c:macro:`MCPWM_GEN_TIMER_EVENT_ACTION_END`. + +Set Generator Action on Compare Event +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One generator can set multiple actions on different compare events, by calling :cpp:func:`mcpwm_generator_set_actions_on_compare_event` with variable number of action configurations. The action configuration is defined in :cpp:type:`mcpwm_gen_compare_event_action_t`: + +- :cpp:member:`mcpwm_gen_compare_event_action_t::direction` specific the timer direction. The supported directions are listed in :cpp:type:`mcpwm_timer_direction_t`. +- :cpp:member:`mcpwm_gen_compare_event_action_t::comparator` specifies the comparator handle. See `MCPWM Comparators <#mcpwm-comparators>`__ for how to allocate a comparator. +- :cpp:member:`mcpwm_gen_compare_event_action_t::action` specifies the generator action to be taken. The supported actions are listed in :cpp:type:`mcpwm_generator_action_t`. + +There's a helper macro :c:macro:`MCPWM_GEN_COMPARE_EVENT_ACTION` to simplify the construction of a compare event action entry. + +Please note, the argument list of :cpp:func:`mcpwm_generator_set_actions_on_compare_event` **must** be terminated by :c:macro:`MCPWM_GEN_COMPARE_EVENT_ACTION_END`. + +Classical PWM Waveforms and Generator Configurations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This section will demonstrate the classical PWM waveforms that can be generated by the pair of the generators. The code snippet that is used to generate the waveforms is also provided below the diagram. Some general summary: + +- The **Symmetric** or **Asymmetric** of the waveforms are determined by the count mode of the MCPWM timer. +- The **active level** of the waveform pair is determined by the level of the PWM with a smaller duty cycle. +- The period of the PWM waveform is determined by the timer's period and count mode. +- The duty cycle of the PWM waveform is determined by the generator's various action combinations. + +Asymmetric Single Edge Active High +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/single_edge_asym_active_high.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(gena, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(genb, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(genb, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } + +Asymmetric Single Edge Active Low +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/single_edge_asym_active_low.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(gena, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(genb, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(genb, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } + +Asymmetric Pulse Placement +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/pulse_placement_asym.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(genb, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_TOGGLE), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + } + +Asymmetric Dual Edge Active Low +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/dual_edge_asym_active_low.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(genb, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + } + +Symmetric Dual Edge Active Low +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/dual_edge_sym_active_low.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(genb, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } + +Symmetric Dual Edge Complementary +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/dual_edge_sym_complementary.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(genb, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } -Configure ---------- +Dead Time +^^^^^^^^^ -The scope of configuration depends on the motor type, in particular how many outputs and inputs are required, and what will be the sequence of signals to drive the motor. +In power electronics, the rectifier and inverter are commonly used. This requires the use of rectifier bridge and inverter bridge. Each bridge arm has two power electronic devices, such as MOSFET, IGBT, etc. The two MOSFETs on the same arm can't conduct at the same time, otherwise there will be a short circuit. The fact is that, although the PWM wave shows it is turning off the switch, but the MOSFET still needs a small time window to make that happen. This requires an extra delay to be added to the existing PWM wave that generated by setting `Generator Actions on Events <#generator-actions-on-events>`__. -In this case we will describe a simple configuration to control a brushed DC motor that is using only some of the available MCPWM's resources. An example circuit is shown below. It includes a `H-Bridge `_ to switch polarization of a voltage applied to the motor (M) and to provide sufficient current to drive it. +The dead-time driver works like a *decorator*, which is also reflected in the function parameters of :cpp:func:`mcpwm_generator_set_dead_time`, where it takes the primary generator handle (``in_generator``), and returns a generator (``out_generator``) after applying the dead-time. Please note, if the ``out_generator`` and ``in_generator`` are the same, it means we're adding the time delay to the PWM waveform in a "in-place" fashion. In turn, if the ``out_generator`` and ``in_generator`` are different, it means we're deriving a new PWM waveform from the existing ``in_generator``. -.. figure:: ../../../_static/mcpwm-brushed-dc-control.png - :align: center - :alt: Example of Brushed DC Motor Control with MCPWM - :figclass: align-center +Dead-time specific configuration is listed in the :cpp:type:`mcpwm_dead_time_config_t` structure: - Example of Brushed DC Motor Control with MCPWM - -Configuration covers the following steps: - -1. Selection of a MCPWM unit that will be used to drive the motor. There are two units available on-board of {IDF_TARGET_NAME} and enumerated in :cpp:type:`mcpwm_unit_t`. -2. Initialization of two GPIOs as output signals within selected unit by calling :cpp:func:`mcpwm_gpio_init`. The two output signals are typically used to command the motor to rotate right or left. All available signal options are listed in :cpp:type:`mcpwm_io_signals_t`. To set more than a single pin at a time, use function :cpp:func:`mcpwm_set_pin` together with :cpp:type:`mcpwm_pin_config_t`. -3. Selection of a timer. There are three timers available within the unit. The timers are listed in :cpp:type:`mcpwm_timer_t`. -4. Setting of the timer frequency and initial duty within :cpp:type:`mcpwm_config_t` structure. -5. Setting timer resolution if necessary, by calling :cpp:func:`mcpwm_group_set_resolution` and :cpp:func:`mcpwm_timer_set_resolution` -6. Calling of :cpp:func:`mcpwm_init` with the above parameters to make the configuration effective. - - -Operate -------- - -To operate a motor connected to the MCPWM unit, e.g. turn it left or right, or vary the speed, we should apply some control signals to the unit's outputs. The outputs are organized into three pairs. Within a pair they are labeled "A" and "B" and each driven by a submodule called an "Generator". To provide a PWM signal, the Operator itself, which contains two Generator, should be clocked by one of three available Timers. To make the API simpler, each Timer is automatically associated by the API to drive an Operator of the same index, e.g. Timer 0 is associated with Operator 0. - -There are the following basic ways to control the outputs: - -* We can drive particular signal steady high or steady low with function :cpp:func:`mcpwm_set_signal_high` or :cpp:func:`mcpwm_set_signal_low`. This will make the motor to turn with a maximum speed or stop. Depending on selected output A or B the motor will rotate either right or left. -* Another option is to drive the outputs with the PWM signal by calling :cpp:func:`mcpwm_start` or :cpp:func:`mcpwm_stop`. The motor speed will be proportional to the PWM duty. -* To vary PWM's duty call :cpp:func:`mcpwm_set_duty` and provide the duty value in %. Optionally, you may call :cpp:func:`mcpwm_set_duty_in_us`, if you prefer to set the duty in microseconds. Checking of currently set value is possible by calling :cpp:func:`mcpwm_get_duty`. Phase of the PWM signal may be altered by calling :cpp:func:`mcpwm_set_duty_type`. The duty is set individually for each A and B output using :cpp:type:`mcpwm_generator_t` in specific function calls. The duty value refers either to high or low output signal duration. This is configured when calling :cpp:func:`mcpwm_init`, as discussed in section `Configure`_, and selecting one of options from :cpp:type:`mcpwm_duty_type_t`. +- :cpp:member:`mcpwm_dead_time_config_t::posedge_delay_ticks` and :cpp:member:`mcpwm_dead_time_config_t::negedge_delay_ticks` set the number of ticks to delay the PWM waveform on the rising and falling edge. Specifically, setting both of them to zero means to bypass the dead-time module. The resolution of the dead-time tick is the same to the timer that is connected with the operator by :cpp:func:`mcpwm_operator_connect_timer`. +- :cpp:member:`mcpwm_dead_time_config_t::invert_output`: Whether to invert the signal after applying the dead-time, which can be used to control the delay edge polarity. .. note:: - Call function :cpp:func:`mcpwm_set_duty_type` every time after :cpp:func:`mcpwm_set_signal_high` or :cpp:func:`mcpwm_set_signal_low` to resume with previously set duty cycle. + It is also possible to generate the required dead time by setting `Generator Actions on Events <#generator-actions-on-events>`__, especially by controlling edge placement using different comparators. However, if the more classical edge delay-based dead time with polarity control is required, then the dead-time submodule should be used. +Classical PWM Waveforms and Dead Time Configurations +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Adjust ------- +This section will demonstrate the classical PWM waveforms that can be generated by the dead-time submodule. The code snippet that is used to generate the waveforms is also provided below the diagram. -There are couple of ways to adjust a signal on the outputs and changing how the motor operates. +Active High Complementary +~~~~~~~~~~~~~~~~~~~~~~~~~ -* Set specific PWM frequency by calling :cpp:func:`mcpwm_set_frequency`. This may be required to adjust to electrical or mechanical characteristics of particular motor and driver. To check what frequency is set, use function :cpp:func:`mcpwm_get_frequency`. -* Introduce a dead time between outputs A and B when they are changing the state to reverse direction of the motor rotation. This is to make up for on/off switching delay of the motor driver FETs. The dead time options are defined in :cpp:type:`mcpwm_deadtime_type_t` and enabled by calling :cpp:func:`mcpwm_deadtime_enable`. To disable this functionality call :cpp:func:`mcpwm_deadtime_disable`. -* Synchronize outputs of operator submodules, e.g. to get raising edge of PWM0A/B and PWM1A/B to start exactly at the same time, or shift them between each other by a given phase. Synchronization is triggered by ``SYNC SIGNALS`` shown on the :ref:`block diagram ` of the MCPWM above, and defined in :cpp:type:`mcpwm_sync_signal_t`. To attach the signal to a GPIO call :cpp:func:`mcpwm_gpio_init`. You can then enable synchronization with function :cpp:func:`mcpwm_sync_configure`. As input parameters provide MCPWM unit, timer to synchronize, the synchronization signal and a phase to delay the timer. +.. wavedrom:: /../_static/diagrams/mcpwm/deadtime_active_high_complementary.json -.. note:: +.. code:: c - Synchronization signals are referred to using two different enumerations. First one :cpp:type:`mcpwm_io_signals_t` is used together with function :cpp:func:`mcpwm_gpio_init` when selecting a GPIO as the signal input source. The second one :cpp:type:`mcpwm_sync_signal_t` is used when enabling or disabling synchronization with :cpp:func:`mcpwm_sync_configure` or :cpp:func:`mcpwm_sync_disable`. + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(gena, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } - -* Vary the pattern of the A/B output signals by getting MCPWM counters to count up, down and up/down (automatically changing the count direction). Respective configuration is done when calling :cpp:func:`mcpwm_init`, as discussed in section `Configure`_, and selecting one of counter types from :cpp:type:`mcpwm_counter_type_t`. For explanation of how A/B PWM output signals are generated, see *{IDF_TARGET_NAME} Technical Reference Manual* > *Motor Control PWM (MCPWM)* [`PDF <{IDF_TARGET_TRM_EN_URL}#mcpwm>`__]. - - -Synchronize ------------ - -Each PWM timer has a synchronization input and a synchronization output. The synchronization input can be selected from other timers' synchronization outputs or GPIO signals via the GPIO matrix. Timer's synchronization signal can be generated from either the input sync signal or when the count value reaches peak/zero. Thus, the PWM timers can be chained together with their phase-locked. During synchronization, the PWM timer clock prescaler will reset its counter in order to synchronize the PWM timer clock. - -The functionality is enabled in following steps: - -1. Make sure the PWM timer and operator are already configured so that sync will inherit its config (count mode, freq and duty). -2. Enabling sync input of the timer by invoking :cpp:func:`mcpwm_sync_configure`, selecting desired signal input from :cpp:type:`mcpwm_sync_signal_t`, and setting the desired phase range from 0 to 999 which is mapped to 0%~99.9%. 0 means zero phase is applied and output is fired at the same time. And selecting desired counting direction. -3. Enabling one of sync event source from another timer or from external GPIO input. - -To sync with another timer: - -Enabling sync output of another timer by invoking :cpp:func:`mcpwm_set_timer_sync_output` and selecting desired event to generate sync output from :cpp:type:`mcpwm_timer_sync_trigger_t`. - -To sync with GPIO positive edge input (negative edge requires :cpp:func:`mcpwm_sync_invert_gpio_synchro`): - -Configuring GPIOs to act as the sync signal inputs by calling functions :cpp:func:`mcpwm_gpio_init` or :cpp:func:`mcpwm_set_pin`, which were described in section `Configure`_. - -It's normal condition that chained sync signal may have tens or even hundreds of nanoseconds of delay between each timer output due to hardware limitation. To sync two timers accurately it is required to have the third timer occupied to produce sync event that can be consumed parallel by other two timer, so that those two timer will have no delay between each other but have the same delay between the timer which provides events. Another solution is introducing an external GPIO event source so that all three timers can be synced together with no delay. - -.. only:: SOC_MCPWM_SWSYNC_CAN_PROPAGATE - - Software sync event which triggered on one timer can be propagated to other timers on {IDF_TARGET_NAME}, which can be used as a tricky way to get all three timers synced without any extra requirement. - - .. code-block:: c - - // configure timer0 as trigger source - mcpwm_set_timer_sync_output(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_SWSYNC_SOURCE_SYNCIN); - mcpwm_sync_config_t sync_conf = { - .sync_sig = MCPWM_SELECT_TIMER0_SYNC, - .timer_val = 0, - .count_direction = MCPWM_TIMER_DIRECTION_UP, + static void dead_time_config(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb) + { + mcpwm_dead_time_config_t dead_time_config = { + .posedge_delay_ticks = 50, + .negedge_delay_ticks = 0 }; - mcpwm_sync_configure(TARGET_MCPWM_UNIT, MCPWM_TIMER_0, &sync_conf); - mcpwm_sync_configure(TARGET_MCPWM_UNIT, MCPWM_TIMER_1, &sync_conf); - mcpwm_sync_configure(TARGET_MCPWM_UNIT, MCPWM_TIMER_2, &sync_conf); - // then send soft sync event to timer0 - mcpwm_timer_trigger_soft_sync(MCPWM_UNIT_0, MCPWM_TIMER_0); + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config)); + dead_time_config.posedge_delay_ticks = 0; + dead_time_config.negedge_delay_ticks = 100; + dead_time_config.flags.invert_output = true; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config)); + } -If not required anymore, the capture functionality may be disabled with :cpp:func:`mcpwm_sync_disable`. +Active Low Complementary +~~~~~~~~~~~~~~~~~~~~~~~~ +.. wavedrom:: /../_static/diagrams/mcpwm/deadtime_active_low_complementary.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(gena, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } + + static void dead_time_config(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb) + { + mcpwm_dead_time_config_t dead_time_config = { + .posedge_delay_ticks = 50, + .negedge_delay_ticks = 0, + .flags.invert_output = true + }; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config)); + dead_time_config.posedge_delay_ticks = 0; + dead_time_config.negedge_delay_ticks = 100; + dead_time_config.flags.invert_output = false; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config)); + } + +Active High +~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/deadtime_active_high.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(gena, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } + + static void dead_time_config(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb) + { + mcpwm_dead_time_config_t dead_time_config = { + .posedge_delay_ticks = 50, + .negedge_delay_ticks = 0, + }; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config)); + dead_time_config.posedge_delay_ticks = 0; + dead_time_config.negedge_delay_ticks = 100; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config)); + } + +Active Low +~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/deadtime_active_low.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(gena, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } + + static void dead_time_config(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb) + { + mcpwm_dead_time_config_t dead_time_config = { + .posedge_delay_ticks = 50, + .negedge_delay_ticks = 0, + .flags.invert_output = true + }; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config)); + dead_time_config.posedge_delay_ticks = 0; + dead_time_config.negedge_delay_ticks = 100; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config)); + } + +Rising Delay on PWMA, Bypass deadtime for PWMB +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/deadtime_reda_bypassb.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(gena, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(genb, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(genb, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } + + static void dead_time_config(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb) + { + mcpwm_dead_time_config_t dead_time_config = { + .posedge_delay_ticks = 50, + .negedge_delay_ticks = 0, + }; + // apply deadtime to generator_a + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config)); + // bypass deadtime module for generator_b + dead_time_config.posedge_delay_ticks = 0; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config)); + } + +Falling Delay on PWMB, Bypass deadtime for PWMA +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/deadtime_fedb_bypassa.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(gena, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(genb, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(genb, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } + + static void dead_time_config(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb) + { + mcpwm_dead_time_config_t dead_time_config = { + .posedge_delay_ticks = 0, + .negedge_delay_ticks = 0, + }; + // generator_a bypass the deadtime module (no delay) + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config)); + // apply dead time to generator_b + dead_time_config.negedge_delay_ticks = 50; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config)); + + } + +Rising and Falling Delay on PWMB, Bypass deadtime for PWMA +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. wavedrom:: /../_static/diagrams/mcpwm/deadtime_redb_fedb_bypassa.json + +.. code:: c + + 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) + { + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(gena, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(gena, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_timer_event(genb, + MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH), + MCPWM_GEN_TIMER_EVENT_ACTION_END())); + ESP_ERROR_CHECK(mcpwm_generator_set_actions_on_compare_event(genb, + MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW), + MCPWM_GEN_COMPARE_EVENT_ACTION_END())); + } + + static void dead_time_config(mcpwm_gen_handle_t gena, mcpwm_gen_handle_t genb) + { + mcpwm_dead_time_config_t dead_time_config = { + .posedge_delay_ticks = 0, + .negedge_delay_ticks = 0, + }; + // generator_a bypass the deadtime module (no delay) + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, gena, &dead_time_config)); + // apply dead time on both edge for generator_b + dead_time_config.negedge_delay_ticks = 50; + dead_time_config.posedge_delay_ticks = 50; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(genb, genb, &dead_time_config)); + } + +Carrier Modulation +^^^^^^^^^^^^^^^^^^ + +The MCPWM operator has a carrier submodule that can be used if galvanic isolation from the motor driver is required (e.g. isolated digital power application) by passing the PWM output signals through transformers. Any of PWM output signals may be at 100% duty and not changing whenever motor is required to run steady at the full load. Coupling of non alternating signals with a transformer is problematic, so the signals are modulated by the carrier submodule to create an AC waveform, to make the coupling possible. + +To configure the carrier submodule, you can call :cpp:func:`mcpwm_operator_apply_carrier`, and provide configuration structure :cpp:type:`mcpwm_carrier_config_t`: + +- :cpp:member:`mcpwm_carrier_config_t::frequency_hz`: The carrier frequency in Hz. +- :cpp:member:`mcpwm_carrier_config_t::duty_cycle`: The duty cycle of the carrier. Note that, the supported choices of duty cycle are discrete, the driver will search the nearest one based the user configuration. +- :cpp:member:`mcpwm_carrier_config_t::first_pulse_duration_us`: The duration of the first pulse in microseconds. The resolution of the first pulse duration is determined by the carrier frequency you set in the :cpp:member:`mcpwm_carrier_config_t::frequency_hz`. The first pulse duration can't be zero, and it has to be at least one period of the carrier. A longer pulse width can help conduct the inductance quicker. +- :cpp:member:`mcpwm_carrier_config_t::invert_before_modulate` and :cpp:member:`mcpwm_carrier_config_t::invert_after_modulate`: Set whether to invert the carrier output before and after modulation. + +Specifically, the carrier submodule can be disabled by calling :cpp:func:`mcpwm_operator_apply_carrier` with a ``NULL`` configuration. + +Faults and Brake Actions +^^^^^^^^^^^^^^^^^^^^^^^^ + +The MCPWM operator is able to sense external signals with information about failure of the motor, the power driver or any other device connected. These failure signals are encapsulated into `MCPWM fault objects <#mcpwm-faults>`__. + +The user should determine possible failure modes of the motor and what action should be performed on detection of particular fault, e.g. drive all outputs low for a brushed motor, or lock current state for a stepper motor, etc. As result of this action the motor should be put into a safe state to reduce likelihood of a damage caused by the fault. + +Set Operator Brake Mode on Fault +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The way that MCPWM operator reacts to the fault is called **Brake**. The MCPWM operator can be configured to perform different brake modes for each fault object by calling :cpp:func:`mcpwm_operator_set_brake_on_fault`. Brake specific configuration is passed as a structure :cpp:type:`mcpwm_brake_config_t`: + +- :cpp:member:`mcpwm_brake_config_t::fault` set which fault that the operator should react to. +- :cpp:member:`mcpwm_brake_config_t::brake_mode` set the brake mode that should be used for the fault. The supported brake modes are listed in the :cpp:type:`mcpwm_operator_brake_mode_t`. For :cpp:enumerator:`MCPWM_OPER_BRAKE_MODE_CBC` mode, the operator will recover itself automatically as long as the fault disappears. You can specify the recovery time in :cpp:member:`mcpwm_brake_config_t::cbc_recover_on_tez` and :cpp:member:`mcpwm_brake_config_t::cbc_recover_on_tep`. For :cpp:enumerator:`MCPWM_OPER_BRAKE_MODE_OST` mode, the operator can't recover even though the fault disappears. User has to call :cpp:func:`mcpwm_operator_recover_from_fault` to manually recover it. + +Set Generator Action on Brake Event +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +One generator can set multiple actions on different brake events, by calling :cpp:func:`mcpwm_generator_set_actions_on_brake_event` with variable number of action configurations. The action configuration is defined in :cpp:type:`mcpwm_gen_brake_event_action_t`: + +- :cpp:member:`mcpwm_gen_brake_event_action_t::direction` specific the timer direction. The supported directions are listed in :cpp:type:`mcpwm_timer_direction_t`. +- :cpp:member:`mcpwm_gen_brake_event_action_t::brake_mode` specifies the brake mode. The supported brake modes are listed in the :cpp:type:`mcpwm_operator_brake_mode_t`. +- :cpp:member:`mcpwm_gen_brake_event_action_t::action` specifies the generator action to be taken. The supported actions are listed in :cpp:type:`mcpwm_generator_action_t`. + +There's a helper macro :c:macro:`MCPWM_GEN_BRAKE_EVENT_ACTION` to simplify the construction of a brake event action entry. + +Please note, the argument list of :cpp:func:`mcpwm_generator_set_actions_on_brake_event` **must** be terminated by :c:macro:`MCPWM_GEN_BRAKE_EVENT_ACTION_END`. + +Register Fault Event Callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The MCPWM fault detector can inform the user when it detects a valid fault or a fault signal disappears. If you have some function that should be called when such event happens, you should hook your function to the interrupt service routine by calling :cpp:func:`mcpwm_fault_register_event_callbacks`. The callback function prototype is declared in :cpp:type:`mcpwm_fault_event_cb_t`. All supported event callbacks are listed in the :cpp:type:`mcpwm_fault_event_callbacks_t`: + +- :cpp:member:`mcpwm_fault_event_callbacks_t::on_fault_enter` sets callback function that will be called when a fault is detected. +- :cpp:member:`mcpwm_fault_event_callbacks_t::on_fault_exit` sets callback function that will be called when a fault is cleared. + +The callback function is called within the ISR context, so is should **not** attempt to block (e.g., make sure that only FreeRTOS APIs with ``ISR`` suffix is called within the function). + +The parameter ``user_data`` of :cpp:func:`mcpwm_fault_register_event_callbacks` function is used to save user's own context, it will be passed to the callback function directly. + +This function will lazy install interrupt service for the MCPWM fault, whereas the service can only be removed in :cpp:type:`mcpwm_del_fault`. + +Register Brake Event Callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The MCPWM operator can inform the user when it going to take a brake action. If you have some function that should be called when this event happens, you should hook your function to the interrupt service routine by calling :cpp:func:`mcpwm_operator_register_event_callbacks`. The callback function prototype is declared in :cpp:type:`mcpwm_brake_event_cb_t`. All supported event callbacks are listed in the :cpp:type:`mcpwm_operator_event_callbacks_t`: + +- :cpp:member:`mcpwm_operator_event_callbacks_t::on_brake_cbc` sets callback function that will be called when the operator is going to take a *CBC* action. +- :cpp:member:`mcpwm_operator_event_callbacks_t::on_brake_ost` sets callback function that will be called when the operator is going to take an *OST* action. + +The callback function is called within the ISR context, so is should **not** attempt to block (e.g., make sure that only FreeRTOS APIs with ``ISR`` suffix is called within the function). + +The parameter ``user_data`` of :cpp:func:`mcpwm_operator_register_event_callbacks` function is used to save user's own context, it will be passed to the callback function directly. + +This function will lazy install interrupt service for the MCPWM operator, whereas the service can only be removed in :cpp:type:`mcpwm_del_operator`. + +Generator Force Actions +^^^^^^^^^^^^^^^^^^^^^^^ + +Software can override generator output level at runtime, by calling :cpp:func:`mcpwm_generator_set_force_level`. The software force level always has a higher priority than other event actions set in e.g. :cpp:func:`mcpwm_generator_set_actions_on_timer_event`. + +- Set the ``level`` to -1 means to disable the force action, and the generator's output level will be controlled by the event actions again. +- Set the ``hold_on`` to true, the force output level will keep alive, until it's removed by assigning ``level`` to -1. +- Set the ``hole_on`` to false, the force output level will only be active for a short time, any upcoming event can override it. + +Synchronization +^^^^^^^^^^^^^^^ + +When a sync signal is taken by the MCPWM timer, the timer will be forced into a predefined **phase**, where the phase is determined by count value and count direction. You can set the sync phase by calling :cpp:func:`mcpwm_timer_set_phase_on_sync`. The sync phase configuration is defined in :cpp:type:`mcpwm_timer_sync_phase_config_t` structure: + +- :cpp:member:`mcpwm_timer_sync_phase_config_t::sync_src` sets the sync signal source. See `MCPWM Sync Sources <#mcpwm-sync-sources>`__ for how to create a sync source object. Specifically, if this is set to ``NULL``, the driver will disable the sync feature for the MCPWM timer. +- :cpp:member:`mcpwm_timer_sync_phase_config_t::count_value` sets the count value to load when the sync signal is taken. +- :cpp:member:`mcpwm_timer_sync_phase_config_t::direction` sets the count direction when the sync signal is taken. + +Likewise, the MCPWM capture timer `MCPWM Capture Timer <#mcpwm-capture-timer-and-channels>`__ can be synced as well. You can set the sync phase for the capture timer by calling :cpp:func:`mcpwm_capture_timer_set_phase_on_sync`. The sync phase configuration is defined in :cpp:type:`mcpwm_capture_timer_sync_phase_config_t` structure: + +- :cpp:member:`mcpwm_capture_timer_sync_phase_config_t::sync_src` sets the sync signal source. See `MCPWM Sync Sources <#mcpwm-sync-sources>`__ for how to create a sync source object. Specifically, if this is set to ``NULL``, the driver will disable the sync feature for the MCPWM capture timer. +- :cpp:member:`mcpwm_capture_timer_sync_phase_config_t::count_value` sets the count value to load when the sync signal is taken. +- :cpp:member:`mcpwm_capture_timer_sync_phase_config_t::direction` sets the count direction when the sync signal is taken. Note that, different from MCPWM Timer, the capture timer can only support one count direction: :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP`. + +Sync Timers by GPIO +~~~~~~~~~~~~~~~~~~~ + +.. blockdiag:: + :caption: GPIO Sync All MCPWM Timers + :align: center + + blockdiag { + GPIO -> Timer0, Timer1, Timer2; + } + +.. code-block:: c + + static void example_setup_sync_strategy(mcpwm_timer_handle_t timers[]) + { + mcpwm_sync_handle_t gpio_sync_source = NULL; + mcpwm_gpio_sync_src_config_t gpio_sync_config = { + .group_id = 0, // GPIO fault should be in the same group of the above timers + .gpio_num = EXAMPLE_SYNC_GPIO, + .flags.pull_down = true, + .flags.active_neg = false, // by default, a posedge pulse can trigger a sync event + }; + ESP_ERROR_CHECK(mcpwm_new_gpio_sync_src(&gpio_sync_config, &gpio_sync_source)); + + mcpwm_timer_sync_phase_config_t sync_phase_config = { + .count_value = 0, // sync phase: target count value + .direction = MCPWM_TIMER_DIRECTION_UP, // sync phase: count direction + .sync_src = gpio_sync_source, // sync source + }; + for (int i = 0; i < 3; i++) { + ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config)); + } + } Capture -------- +^^^^^^^ -One of requirements of BLDC (Brushless DC, see figure below) motor control is sensing of the rotor position. To facilitate this task each MCPWM unit provides three sensing inputs together with dedicated hardware. The hardware is able to detect the input signal's edge and measure time between signals. As result the control software is simpler and the CPU power may be used for other tasks. +The basic functionality of MCPWM capture is to record the time when any pulse edge of the capture signal turns active. Then you can get the pulse width and convert it into other physical quantity like distance or speed in the capture callback function. For example, in the BLDC (Brushless DC, see figure below) scenario, we can use the capture submodule to sense the rotor position from Hall sensor. .. figure:: ../../../_static/mcpwm-bldc-control.png :align: center :alt: Example of Brushless DC Motor Control with MCPWM :figclass: align-center - Example of Brushless DC Motor Control with MCPWM + MCPWM BLDC with Hall Sensor -The capture functionality may be used for other types of motors or tasks. The functionality is enabled in two steps: +The capture timer is usually connected with several capture channels, please refer to ``__ for resource allocation. -1. Configuration of GPIOs to act as the capture signal inputs by calling functions :cpp:func:`mcpwm_gpio_init` or :cpp:func:`mcpwm_set_pin`, that were described in section `Configure`_. -2. Enabling of the functionality itself by invoking :cpp:func:`mcpwm_capture_enable_channel`, selecting desired signal input from :cpp:type:`mcpwm_capture_channel_id_t`, setting the signal edge, signal count prescaler and user callback within :cpp:type:`mcpwm_capture_config_t` +Register Event Callbacks +~~~~~~~~~~~~~~~~~~~~~~~~ -Within the second step above a 32-bit capture timer is enabled. The timer runs continuously driven by the APB clock. The clock frequency is typically 80 MHz. On each capture event the capture timer’s value is stored in time-stamp register that may be then checked by calling :cpp:func:`mcpwm_capture_signal_get_value`. The edge of the last signal may be checked with :cpp:func:`mcpwm_capture_signal_get_edge`. Those data are also provided inside callback function as event data :cpp:type:`cap_event_data_t` +The MCPWM capture channel can inform the user when there's a valid edge detected on the signal. You have to register a callback function to get the timer count value of the capture moment, by calling :cpp:func:`mcpwm_capture_channel_register_event_callbacks`. The callback function prototype is declared in :cpp:type:`mcpwm_capture_event_cb_t`. All supported capture callbacks are listed in the :cpp:type:`mcpwm_capture_event_callbacks_t`: -If not required anymore, the capture functionality may be disabled with :cpp:func:`mcpwm_capture_disable_channel`. +- :cpp:member:`mcpwm_capture_event_callbacks_t::on_cap` sets callback function for the capture channel when a valid edge is detected. -Capture prescale is different from other modules as it is applied to the input signal, not the timer source. Prescaler has maintained its own level state with the initial value set to low and is detecting the positive edge of the input signal to change its internal state. That means if two pairs of positive and negative edges are passed to input, the prescaler's internal state will change twice. ISR will report on this internal state change, not the input signal. For example, setting prescale to 2 will generate ISR callback on each positive edge of input if both edge is selected via :cpp:type:`mcpwm_capture_config_t`. Or each 2 positive edges of input if only one edge is selected though :cpp:type:`mcpwm_capture_config_t`. +The callback function will provide event specific data of type :cpp:type:`mcpwm_capture_event_data_t`, so that you can get the the edge of the capture signal in :cpp:member:`mcpwm_capture_event_data_t::cap_edge` and the count value of that moment in :cpp:member:`mcpwm_capture_event_data_t::cap_value`. +The callback function is called within the ISR context, so is should **not** attempt to block (e.g., make sure that only FreeRTOS APIs with ``ISR`` suffix is called within the function). -Fault Handler -------------- +The parameter ``user_data`` of :cpp:func:`mcpwm_capture_channel_register_event_callbacks` function is used to save user's own context, it will be passed to the callback function directly. -Each unit of the MCPWM is able to sense external signals with information about failure of the motor, the motor driver or any other device connected to the MCPWM. There are three fault inputs per unit that may be routed to user selectable GPIOs. The MCPWM may be configured to perform one of four predefined actions on A/B outputs when a fault signal is received: +This function will lazy install interrupt service for the MCPWM capture channel, whereas the service can only be removed in :cpp:type:`mcpwm_del_capture_channel`. -* lock current state of the output -* set the output low -* set the output high -* toggle the output +Enable and Disable Capture Timer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The user should determine possible failure modes of the motor and what action should be performed on detection of particular fault, e.g. drive all outputs low for a brushed motor, or lock current state for a stepper motor, etc. As result of this action the motor should be put into a safe state to reduce likelihood of a damage caused by the fault. +Before doing IO control to the capture timer, user needs to enable the timer first, by calling :cpp:func:`mcpwm_capture_timer_enable`. Internally, this function will: -The fault handler functionality is enabled in two steps: +* switch the capture timer state from **init** to **enable**. +* acquire a proper power management lock if a specific clock source (e.g. APB clock) is selected. See also `Power management <#power-management>`__ for more information. -1. Configuration of GPIOs to act as fault signal inputs. This is done in analogous way as described for capture signals in section above. It includes setting the signal level to trigger the fault as defined in :cpp:type:`mcpwm_fault_input_level_t`. -2. Initialization of the fault handler by calling either :cpp:func:`mcpwm_fault_set_oneshot_mode` or :cpp:func:`mcpwm_fault_set_cyc_mode`. These functions set the mode that MCPWM should operate once fault signal becomes inactive. There are two modes possible: +On the contrary, calling :cpp:func:`mcpwm_capture_timer_disable` will put the timer driver back to **init** state, and release the power management lock. - * State of MCPWM unit will be locked until reset - :cpp:func:`mcpwm_fault_set_oneshot_mode`. - * The MCPWM will resume operation once fault signal becoming inactive - :cpp:func:`mcpwm_fault_set_cyc_mode`. +Start and Stop Capture Timer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - The function call parameters include selection of one of three fault inputs defined in :cpp:type:`mcpwm_fault_signal_t` and specific action on outputs A and B defined in :cpp:type:`mcpwm_action_on_pwmxa_t` and :cpp:type:`mcpwm_action_on_pwmxb_t`. +The basic IO operation of a capture timer is to start and stop. Calling :cpp:func:`mcpwm_capture_timer_start` can start the timer and calling :cpp:func:`mcpwm_capture_timer_stop` can stop the timer immediately. -Particular fault signal may be disabled at the runtime by calling :cpp:func:`mcpwm_fault_deinit`. +Trigger a Software Capture Event +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Sometime, the software also wants to trigger a "fake" capture event. The :cpp:func:`mcpwm_capture_channel_trigger_soft_catch` is provided for that purpose. Please note that, even though it's a "fake" capture event, it can still cause an interrupt, thus your capture event callback function will get invoked as well. -Carrier -------- +Power Management +^^^^^^^^^^^^^^^^ -The MCPWM has a carrier submodule used if galvanic isolation from the motor driver is required by passing the A/B output signals through transformers. Any of A and B output signals may be at 100% duty and not changing whenever motor is required to run steady at the full load. Coupling of non alternating signals with a transformer is problematic, so the signals are modulated by the carrier submodule to create an AC waveform, to make the coupling possible. +When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the PLL, APB frequency before going into light sleep, thus potentially changing the period of a MCPWM timers' counting step and leading to inaccurate time keeping. -To use the carrier submodule, it should be first initialized by calling :cpp:func:`mcpwm_carrier_init`. The carrier parameters are defined in :cpp:type:`mcpwm_carrier_config_t` structure invoked within the function call. Then the carrier functionality may be enabled by calling :cpp:func:`mcpwm_carrier_enable`. +However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever the driver creates a MCPWM timer instance that has selected :cpp:enumerator:`MCPWM_TIMER_CLK_SRC_PLL160M` as its clock source, the driver will guarantee that the power management lock is acquired when enable the timer by :cpp:func:`mcpwm_timer_enable`. Likewise, the driver releases the lock when :cpp:func:`mcpwm_timer_disable` is called for that timer. -The carrier parameters may be then altered at a runtime by calling dedicated functions to change individual fields of the :cpp:type:`mcpwm_carrier_config_t` structure, like :cpp:func:`mcpwm_carrier_set_period`, :cpp:func:`mcpwm_carrier_set_duty_cycle`, :cpp:func:`mcpwm_carrier_output_invert`, etc. +Likewise, Whenever the driver creates a MCPWM capture timer instance that has selected :cpp:enumerator:`MCPWM_CAPTURE_CLK_SRC_APB` as its clock source, the driver will guarantee that the power management lock is acquired when enable the timer by :cpp:func:`mcpwm_capture_timer_enable`. And will release the lock in :cpp:func:`mcpwm_capture_timer_disable`. -This includes enabling and setting duration of the first pulse of the career with :cpp:func:`mcpwm_carrier_oneshot_mode_enable`. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *Motor Control PWM (MCPWM)* > *PWM Carrier Submodule* [`PDF <{IDF_TARGET_TRM_EN_URL}#mcpwm>`__]. +IRAM Safe +^^^^^^^^^ -To disable carrier functionality call :cpp:func:`mcpwm_carrier_disable`. +By default, the MCPWM interrupt will be deferred when the Cache is disabled for reasons like writing/erasing Flash. Thus the event callback functions will not get executed in time, which is not expected in a real-time application. +There's a Kconfig option :ref:`CONFIG_MCPWM_ISR_IRAM_SAFE` that will: -Interrupts ----------- +1. Enable the interrupt being serviced even when cache is disabled +2. Place all functions that used by the ISR into IRAM [2]_ +3. Place driver object into DRAM (in case it's mapped to PSRAM by accident) -Registering of the MCPWM interrupt handler is possible by calling :cpp:func:`mcpwm_isr_register`. Note if :cpp:func:`mcpwm_capture_enable_channel` is used then a default ISR routine will be installed hence please do not call this function to register any more. +This will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption. +Thread Safety +^^^^^^^^^^^^^ -Resolution ----------- +The factory functions like :cpp:func:`mcpwm_new_timer` are guaranteed to be thread safe by the driver, which means, you can call it from different RTOS tasks without protection by extra locks. -The default resolution for MCPWM group and MCPWM timer are configured to **10MHz** and **1MHz** in :cpp:func:`mcpwm_init`, which might be not enough for some applications. -The driver also provides two APIs that can be used to override the default resolution: :cpp:func:`mcpwm_group_set_resolution` and :cpp:func:`mcpwm_timer_set_resolution`. +No functions are allowed to run within ISR environment. -Note that, these two APIs won't update the frequency and duty automatically, to achieve that, one has to call :cpp:func:`mcpwm_set_frequency` and :cpp:func:`mcpwm_set_duty` accordingly. +Functions that are not related to `Resource Allocation <#resource-allocation-and-initialization>`__, are not thread safe. Thus, you should avoid calling them in different tasks without mutex protection. -To get PWM pulse that is below 15Hz, please set the resolution to a lower value. For high frequency PWM with limited step range, please set them with higher value. +Kconfig Options +^^^^^^^^^^^^^^^ +- :ref:`CONFIG_MCPWM_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see `IRAM Safe <#iram-safe>`__ for more information. +- :ref:`CONFIG_MCPWM_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enable this option will increase the firmware binary size. -Application Example -------------------- +Application Examples +-------------------- -MCPWM example are located under: :example:`peripherals/mcpwm`: - -* Control of BLDC (brushless DC) motor with hall sensor feedback - :example:`peripherals/mcpwm/mcpwm_bldc_hall_control` -* Brushed DC motor control - :example:`peripherals/mcpwm/mcpwm_bdc_speed_control` -* Servo motor control - :example:`peripherals/mcpwm/mcpwm_servo_control` -* HC-SR04 sensor with capture - :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04` +* Brushed DC motor speed control by PID algorithm: :example:`peripherals/mcpwm/mcpwm_bdc_speed_control` +* BLDC motor control with hall sensor feedback: :example:`peripherals/mcpwm/mcpwm_bldc_hall_control` +* Ultrasonic sensor (HC-SR04) distance measurement: :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04` +* Servo motor angle control: :example:`peripherals/mcpwm/mcpwm_servo_control` +* MCPWM synchronization between timers: :example:`peripherals/mcpwm/mcpwm_sync` API Reference ------------- -.. include-build-file:: inc/mcpwm_types.inc -.. include-build-file:: inc/mcpwm.inc +.. include-build-file:: inc/mcpwm_timer.inc +.. include-build-file:: inc/mcpwm_oper.inc +.. include-build-file:: inc/mcpwm_cmpr.inc +.. include-build-file:: inc/mcpwm_gen.inc +.. include-build-file:: inc/mcpwm_fault.inc +.. include-build-file:: inc/mcpwm_sync.inc +.. include-build-file:: inc/mcpwm_cap.inc +.. include-build-file:: inc/components/driver/include/driver/mcpwm_types.inc +.. include-build-file:: inc/components/hal/include/hal/mcpwm_types.inc +.. [1] + Different ESP chip series might have different number of MCPWM resources (e.g. groups, timers, comparators, operators, generators and so on). Please refer to the [`TRM <{IDF_TARGET_TRM_EN_URL}#mcpwm>`__] for details. The driver won't forbid you from applying for more MCPWM resources, but it will return error when there's no hardware resources available. Please always check the return value when doing `Resource Allocation <#resource-allocation-and-initialization>`__. + +.. [2] + Callback function and the sub-functions invoked by itself should also be placed in IRAM, users need to take care of this by themselves. diff --git a/docs/en/migration-guides/release-5.x/peripherals.rst b/docs/en/migration-guides/release-5.x/peripherals.rst index fd980f58f3..c2584d4466 100644 --- a/docs/en/migration-guides/release-5.x/peripherals.rst +++ b/docs/en/migration-guides/release-5.x/peripherals.rst @@ -213,6 +213,7 @@ LEDC Breaking Changes in Usage ~~~~~~~~~~~~~~~~~~~~~~~~~ + - Channel installation has been separated for TX and RX channels into :cpp:func:`rmt_new_tx_channel` and :cpp:func:`rmt_new_rx_channel`. - ``rmt_set_clk_div`` and ``rmt_get_clk_div`` are removed. Channel clock configuration can only be done during channel installation. - ``rmt_set_rx_idle_thresh`` and ``rmt_get_rx_idle_thresh`` are removed. In the new driver, the RX channel IDLE threshold is redesigned into a new concept :cpp:member:`rmt_receive_config_t::signal_range_max_ns`. @@ -259,11 +260,63 @@ LCD MCPWM ----- - - ``mcpwm_capture_enable`` is removed. To enable capture channel, please use :cpp:func:`mcpwm_capture_enable_channel`. - - ``mcpwm_capture_disable`` is remove. To disable capture channel, please use :cpp:func:`mcpwm_capture_capture_disable_channel`. - - ``mcpwm_sync_enable`` is removed. To configure synchronization, please use :cpp:func:`mcpwm_sync_configure`. - - ``mcpwm_isr_register`` is removed. You can register event callbacks, for capture channels. e.g. :cpp:member:`mcpwm_capture_config_t::capture_cb`. - - ``mcpwm_carrier_oneshot_mode_disable`` is removed. Disable the first pulse (a.k.a the one-shot pulse) in the carrier is not supported by hardware. + MCPWM driver was redesigned (see :doc:`MCPWM <../../api-reference/peripherals/mcpwm>`), meanwhile, the legacy driver is deprecated. The new driver's aim is to make each MCPWM submodule independent to each other, and give the freedom of resource connection back to users. Although it's recommended to use the new driver APIs, the legacy driver is still available in the previous include path ``driver/mcpwm.h``. However, by default, using legacy driver will bring compile warnings like ``legacy MCPWM driver is deprecated, please migrate to the new driver (include driver/mcpwm_prelude.h)``. This warning can be suppressed by the Kconfig option :ref:`CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN`. + + The major breaking changes in concept and usage are listed as follows: + + Breaking Changes in Concepts + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + The new MCPWM driver is object-oriented, where most of the MCPWM submodule has a driver object associated with it. The driver object is created by factory function like :cpp:func:`mcpwm_new_timer`. IO control function always needs an object handle, in the first place. + + The legacy driver has an inappropriate assumption, that is the MCPWM operator should be connected to different MCPWM timer. In fact, the hardware doesn't have such limitation. In the new driver, a MCPWM timer can be connected to multiple operators, so that the operators can achieve the best synchronization performance. + + The legacy driver preset the way to generate a PWM waveform into a so called ``mcpwm_duty_type_t``, however, the duty cycle modes listed there are far from sufficient. Likewise, legacy driver has several preset ``mcpwm_deadtime_type_t``, which also doesn't cover all the use cases. What's more, user usually gets confused by the name of the duty cycle mode and dead-time mode. In the new driver, there're no such limitation, but user has to construct the generator behavior from scratch. + + In the legacy driver, the ways to synchronize the MCPWM timer by GPIO, software and other timer module are not unified. It increased learning costs for users. In the new driver, the synchronization APIs are unified. + + The legacy driver has mixed the concepts of "Fault detector" and "Fault handler". Which make the APIs very confusing to users. In the new driver, the fault object just represents a failure source, and we introduced a new concept -- **brake** to express the concept of "Fault handler". What's more, the new driver supports software fault. + + The legacy drive only provides callback functions for the capture submodule. The new driver provides more useful callbacks for various MCPWM submodules, like timer stop, compare match, fault enter, brake, etc. + + - ``mcpwm_io_signals_t`` and ``mcpwm_pin_config_t`` are not used. GPIO configuration has been moved into submodule's configuration structure. + - ``mcpwm_timer_t``, ``mcpwm_generator_t`` are not used. Timer and generator are represented by :cpp:type:`mcpwm_timer_handle_t` and :cpp:type:`mcpwm_gen_handle_t`. + - ``mcpwm_fault_signal_t`` and ``mcpwm_sync_signal_t`` are not used. Fault and sync source are represented by :cpp:type:`mcpwm_fault_handle_t` and :cpp:type:`mcpwm_sync_handle_t`. + - ``mcpwm_capture_signal_t`` is not used. A capture channel is represented by :cpp:type:`mcpwm_cap_channel_handle_t`. + + Breaking Changes in Usage + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + - ``mcpwm_gpio_init`` and ``mcpwm_set_pin``: GPIO configurations are moved to submodule's own configuration. e.g. set the PWM GPIO in :cpp:member:`mcpwm_generator_config_t::gen_gpio_num`. + - ``mcpwm_init``: To get an expected PWM waveform, you need to allocated at least one MCPWM timer and MCPWM operator, then connect them by calling :cpp:func:`mcpwm_operator_connect_timer`. After that, you should set the generator's actions on various events by calling e.g. :cpp:func:`mcpwm_generator_set_actions_on_timer_event`, :cpp:func:`mcpwm_generator_set_actions_on_compare_event`. + - ``mcpwm_group_set_resolution``: in the new driver, the group resolution is fixed to the maximum, usually it's 80MHz. + - ``mcpwm_timer_set_resolution``: MCPWM Timer resolution is set in :cpp:member:`mcpwm_timer_config_t::resolution_hz`. + - ``mcpwm_set_frequency``: PWM frequency is determined by :cpp:member:`mcpwm_timer_config_t::resolution_hz`, :cpp:member:`mcpwm_timer_config_t::count_mode` and :cpp:member:`mcpwm_timer_config_t::period_ticks`. + - ``mcpwm_set_duty``: To set the PWM duty cycle, you should call :cpp:func:`mcpwm_comparator_set_compare_value` to change comparator's threshold. + - ``mcpwm_set_duty_type``: There won't be any preset duty types, the duty type is configured by setting different generator actions. e.g. :cpp:func:`mcpwm_generator_set_actions_on_timer_event`. + - ``mcpwm_set_signal_high`` and ``mcpwm_set_signal_low`` are replaced by :cpp:func:`mcpwm_generator_set_force_level`. In the new driver, it's implemented by setting force action for the generator, instead of changing the duty cycle to 0% or 100% at the background. + - ``mcpwm_start`` and ``mcpwm_stop`` are replaced by :cpp:func:`mcpwm_timer_start_stop`. You have more modes to start and stop the MCPWM timer, see :cpp:type:`mcpwm_timer_start_stop_cmd_t`. + - ``mcpwm_carrier_init``: It's replaced by :cpp:func:`mcpwm_operator_apply_carrier`. + - ``mcpwm_carrier_enable`` and ``mcpwm_carrier_disable``: Enabling and disabling carrier submodule is done automatically by checking whether the carrier configuration structure :cpp:type:`mcpwm_carrier_config_t` is NULL. + - ``mcpwm_carrier_set_period`` is replaced by :cpp:member:`mcpwm_carrier_config_t::frequency_hz`. + - ``mcpwm_carrier_set_duty_cycle`` is replaced by :cpp:member:`mcpwm_carrier_config_t::duty_cycle`. + - ``mcpwm_carrier_oneshot_mode_enable`` is replaced by :cpp:member:`mcpwm_carrier_config_t::first_pulse_duration_us`. + - ``mcpwm_carrier_oneshot_mode_disable`` is removed. Disabling the first pulse (a.k.a the one-shot pulse) in the carrier is never supported by the hardware. + - ``mcpwm_carrier_output_invert`` is replaced by :cpp:member:`mcpwm_carrier_config_t::invert_before_modulate` and :cpp:member:`mcpwm_carrier_config_t::invert_after_modulate`. + - ``mcpwm_deadtime_enable`` and ``mcpwm_deadtime_disable`` are replaced by :cpp:func:`mcpwm_generator_set_dead_time`. + - ``mcpwm_fault_init`` is replaced by :cpp:func:`mcpwm_new_gpio_fault`. + - ``mcpwm_fault_set_oneshot_mode``, ``mcpwm_fault_set_cyc_mode`` are replaced by :cpp:func:`mcpwm_operator_set_brake_on_fault` and :cpp:func:`mcpwm_generator_set_actions_on_brake_event`. + - ``mcpwm_capture_enable`` is removed. It's duplicated to :cpp:func:`mcpwm_capture_enable_channel`. + - ``mcpwm_capture_disable`` is removed. It's duplicated to :cpp:func:`mcpwm_capture_capture_disable_channel`. + - ``mcpwm_capture_enable_channel`` and ``mcpwm_capture_disable_channel`` are replaced by :cpp:func:`mcpwm_new_capture_channel` and :cpp:func:`mcpwm_del_capture_channel`. + - ``mcpwm_capture_signal_get_value`` and ``mcpwm_capture_signal_get_edge``: Capture timer count value and capture edge are provided in the capture event callback, via :cpp:type:`mcpwm_capture_event_data_t`. Capture data are only valuable when capture event happens. Providing single API to fetch capture data is meaningless. + - ``mcpwm_sync_enable`` is removed. It's duplicated to :cpp:func:`mcpwm_sync_configure`. + - ``mcpwm_sync_configure`` is replaced by :cpp:func:`mcpwm_timer_set_phase_on_sync`. + - ``mcpwm_sync_disable`` is equivalent to setting :cpp:member:`mcpwm_timer_sync_phase_config_t::sync_src` to ``NULL``. + - ``mcpwm_set_timer_sync_output`` is replaced by :cpp:func:`mcpwm_new_timer_sync_src`. + - ``mcpwm_timer_trigger_soft_sync`` is replaced by :cpp:func:`mcpwm_soft_sync_activate`. + - ``mcpwm_sync_invert_gpio_synchro`` is equivalent to setting :cpp:member:`mcpwm_gpio_sync_src_config_t::active_neg`. + - ``mcpwm_isr_register`` is removed. You can register various event callbacks instead. For example, to register capture event callback, you can use :cpp:func:`mcpwm_capture_channel_register_event_callbacks`. .. only:: SOC_DEDICATED_GPIO_SUPPORTED diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm.rst b/docs/zh_CN/api-reference/peripherals/mcpwm.rst index a918a58c90..368f3c7f86 100644 --- a/docs/zh_CN/api-reference/peripherals/mcpwm.rst +++ b/docs/zh_CN/api-reference/peripherals/mcpwm.rst @@ -1 +1 @@ -.. include:: ../../../en/api-reference/peripherals/mcpwm.rst \ No newline at end of file +.. include:: ../../../en/api-reference/peripherals/mcpwm.rst