From 13777d2d93db7257c522602666f8b7175563af5f Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 23 Jul 2026 23:53:06 +0800 Subject: [PATCH] docs: restructure MCPWM programming guide --- .../diagrams/mcpwm/deadtime_active_high.json | 26 - .../deadtime_active_high_complementary.json | 27 - .../diagrams/mcpwm/deadtime_active_low.json | 28 - .../deadtime_active_low_complementary.json | 27 - .../diagrams/mcpwm/deadtime_fedb_bypassa.json | 28 - .../diagrams/mcpwm/deadtime_reda_bypassb.json | 28 - .../mcpwm/deadtime_redb_fedb_bypassa.json | 29 - .../mcpwm/dual_edge_asym_active_low.json | 15 - .../mcpwm/dual_edge_sym_active_low.json | 15 - .../mcpwm/dual_edge_sym_complementary.json | 15 - .../diagrams/mcpwm/mcpwm_overview.diag | 59 - .../diagrams/mcpwm/pulse_placement_asym.json | 15 - .../mcpwm/single_edge_asym_active_high.json | 15 - .../mcpwm/single_edge_asym_active_low.json | 15 - docs/_static/mcpwm-bldc-control.png | Bin 45687 -> 0 bytes docs/_static/mcpwm/brake_cbc_ost.svg | 95 + docs/_static/mcpwm/capture_measurement.svg | 75 + docs/_static/mcpwm/carrier_modulation.svg | 82 + docs/_static/mcpwm/compare_event.svg | 139 + docs/_static/mcpwm/deadtime_active_high.svg | 22 + .../deadtime_active_high_complementary.svg | 28 + docs/_static/mcpwm/deadtime_active_low.svg | 22 + .../deadtime_active_low_complementary.svg | 28 + docs/_static/mcpwm/deadtime_fedb_bypassa.svg | 20 + docs/_static/mcpwm/deadtime_reda_bypassb.svg | 20 + .../mcpwm/deadtime_redb_fedb_bypassa.svg | 23 + .../mcpwm/dual_edge_asym_active_low.svg | 18 + .../mcpwm/dual_edge_sym_active_low.svg | 20 + .../mcpwm/dual_edge_sym_complementary.svg | 18 + docs/_static/mcpwm/half_bridge_dead_time.svg | 6925 +++++++++++++++++ docs/_static/mcpwm/phase_shift.svg | 78 + docs/_static/mcpwm/pulse_placement_asym.svg | 19 + .../mcpwm/single_edge_asym_active_high.svg | 17 + .../mcpwm/single_edge_asym_active_low.svg | 17 + docs/_static/mcpwm/timer_up_count.svg | 127 + docs/_static/mcpwm/timer_up_down_count.svg | 122 + docs/conf_common.py | 13 +- docs/en/api-reference/peripherals/etm.rst | 2 +- docs/en/api-reference/peripherals/index.rst | 2 +- docs/en/api-reference/peripherals/mcpwm.rst | 1147 --- .../api-reference/peripherals/mcpwm/index.rst | 228 + .../peripherals/mcpwm/mcpwm_advanced.rst | 62 + .../peripherals/mcpwm/mcpwm_cap.rst | 148 + .../peripherals/mcpwm/mcpwm_cmpr.rst | 116 + .../peripherals/mcpwm/mcpwm_etm.rst | 66 + .../peripherals/mcpwm/mcpwm_fault.rst | 84 + .../peripherals/mcpwm/mcpwm_gen.rst | 327 + .../peripherals/mcpwm/mcpwm_operator.rst | 187 + .../peripherals/mcpwm/mcpwm_sync.rst | 157 + .../peripherals/mcpwm/mcpwm_timer.rst | 146 + .../release-5.x/5.0/peripherals.rst | 2 +- docs/page_redirects.txt | 1 + docs/zh_CN/api-reference/peripherals/etm.rst | 2 +- .../zh_CN/api-reference/peripherals/index.rst | 2 +- .../zh_CN/api-reference/peripherals/mcpwm.rst | 1147 --- .../api-reference/peripherals/mcpwm/index.rst | 228 + .../peripherals/mcpwm/mcpwm_advanced.rst | 62 + .../peripherals/mcpwm/mcpwm_cap.rst | 148 + .../peripherals/mcpwm/mcpwm_cmpr.rst | 116 + .../peripherals/mcpwm/mcpwm_etm.rst | 66 + .../peripherals/mcpwm/mcpwm_fault.rst | 84 + .../peripherals/mcpwm/mcpwm_gen.rst | 327 + .../peripherals/mcpwm/mcpwm_operator.rst | 187 + .../peripherals/mcpwm/mcpwm_sync.rst | 157 + .../peripherals/mcpwm/mcpwm_timer.rst | 146 + .../release-5.x/5.0/peripherals.rst | 2 +- 66 files changed, 10976 insertions(+), 2643 deletions(-) delete mode 100644 docs/_static/diagrams/mcpwm/deadtime_active_high.json delete mode 100644 docs/_static/diagrams/mcpwm/deadtime_active_high_complementary.json delete mode 100644 docs/_static/diagrams/mcpwm/deadtime_active_low.json delete mode 100644 docs/_static/diagrams/mcpwm/deadtime_active_low_complementary.json delete mode 100644 docs/_static/diagrams/mcpwm/deadtime_fedb_bypassa.json delete mode 100644 docs/_static/diagrams/mcpwm/deadtime_reda_bypassb.json delete mode 100644 docs/_static/diagrams/mcpwm/deadtime_redb_fedb_bypassa.json delete mode 100644 docs/_static/diagrams/mcpwm/dual_edge_asym_active_low.json delete mode 100644 docs/_static/diagrams/mcpwm/dual_edge_sym_active_low.json delete mode 100644 docs/_static/diagrams/mcpwm/dual_edge_sym_complementary.json delete mode 100644 docs/_static/diagrams/mcpwm/mcpwm_overview.diag delete mode 100644 docs/_static/diagrams/mcpwm/pulse_placement_asym.json delete mode 100644 docs/_static/diagrams/mcpwm/single_edge_asym_active_high.json delete mode 100644 docs/_static/diagrams/mcpwm/single_edge_asym_active_low.json delete mode 100644 docs/_static/mcpwm-bldc-control.png create mode 100644 docs/_static/mcpwm/brake_cbc_ost.svg create mode 100644 docs/_static/mcpwm/capture_measurement.svg create mode 100644 docs/_static/mcpwm/carrier_modulation.svg create mode 100644 docs/_static/mcpwm/compare_event.svg create mode 100644 docs/_static/mcpwm/deadtime_active_high.svg create mode 100644 docs/_static/mcpwm/deadtime_active_high_complementary.svg create mode 100644 docs/_static/mcpwm/deadtime_active_low.svg create mode 100644 docs/_static/mcpwm/deadtime_active_low_complementary.svg create mode 100644 docs/_static/mcpwm/deadtime_fedb_bypassa.svg create mode 100644 docs/_static/mcpwm/deadtime_reda_bypassb.svg create mode 100644 docs/_static/mcpwm/deadtime_redb_fedb_bypassa.svg create mode 100644 docs/_static/mcpwm/dual_edge_asym_active_low.svg create mode 100644 docs/_static/mcpwm/dual_edge_sym_active_low.svg create mode 100644 docs/_static/mcpwm/dual_edge_sym_complementary.svg create mode 100644 docs/_static/mcpwm/half_bridge_dead_time.svg create mode 100644 docs/_static/mcpwm/phase_shift.svg create mode 100644 docs/_static/mcpwm/pulse_placement_asym.svg create mode 100644 docs/_static/mcpwm/single_edge_asym_active_high.svg create mode 100644 docs/_static/mcpwm/single_edge_asym_active_low.svg create mode 100644 docs/_static/mcpwm/timer_up_count.svg create mode 100644 docs/_static/mcpwm/timer_up_down_count.svg delete mode 100644 docs/en/api-reference/peripherals/mcpwm.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/index.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/mcpwm_advanced.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/mcpwm_cap.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/mcpwm_cmpr.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/mcpwm_etm.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/mcpwm_fault.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/mcpwm_gen.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/mcpwm_operator.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/mcpwm_sync.rst create mode 100644 docs/en/api-reference/peripherals/mcpwm/mcpwm_timer.rst delete mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/index.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_advanced.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cap.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cmpr.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_etm.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_fault.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_gen.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_operator.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_sync.rst create mode 100644 docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_timer.rst diff --git a/docs/_static/diagrams/mcpwm/deadtime_active_high.json b/docs/_static/diagrams/mcpwm/deadtime_active_high.json deleted file mode 100644 index 8a396f18f50..00000000000 --- a/docs/_static/diagrams/mcpwm/deadtime_active_high.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "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 deleted file mode 100644 index 9b6653acc6f..00000000000 --- a/docs/_static/diagrams/mcpwm/deadtime_active_high_complementary.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "signal": [ - { - "name": "origin", - "wave": "0...1.....0...", - "node": "....a.e...b..." - }, - { - "name": "pwm_A", - "wave": "0....1....0...", - "node": ".....c....." - }, - { - "name": "pwm_B", - "wave": "1...0......1..", - "node": "......f....d.." - } - ], - "edge": [ - "a|->c RED", - "b|->d FED", - "e<->f Invert" - ], - "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 deleted file mode 100644 index 510e619b1a9..00000000000 --- a/docs/_static/diagrams/mcpwm/deadtime_active_low.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "signal": [ - { - "name": "origin", - "wave": "0...1.....0...", - "node": "....a..ef.b..." - }, - { - "name": "pwm_A", - "wave": "1....0....1...", - "node": ".....c.g..." - }, - { - "name": "pwm_B", - "wave": "1...0......1..", - "node": "........h..d.." - } - ], - "edge": [ - "a|->c RED", - "b|->d FED", - "e<->g Invert", - "f<->h Invert" - ], - "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 deleted file mode 100644 index a1fef0d6d19..00000000000 --- a/docs/_static/diagrams/mcpwm/deadtime_active_low_complementary.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "signal": [ - { - "name": "origin", - "wave": "0...1.....0...", - "node": "....a..e..b..." - }, - { - "name": "pwm_A", - "wave": "1....0....1...", - "node": ".....c.f..." - }, - { - "name": "pwm_B", - "wave": "0...1......0..", - "node": "...........d.." - } - ], - "edge": [ - "a|->c RED", - "b|->d FED", - "e<->f Invert" - ], - "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 deleted file mode 100644 index d4fcfc0d8b0..00000000000 --- a/docs/_static/diagrams/mcpwm/deadtime_fedb_bypassa.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "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 deleted file mode 100644 index f65f031223f..00000000000 --- a/docs/_static/diagrams/mcpwm/deadtime_reda_bypassb.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "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 deleted file mode 100644 index 936978185ab..00000000000 --- a/docs/_static/diagrams/mcpwm/deadtime_redb_fedb_bypassa.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "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 deleted file mode 100644 index b5981ef7b3c..00000000000 --- a/docs/_static/diagrams/mcpwm/dual_edge_asym_active_low.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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 deleted file mode 100644 index 680b3295bfb..00000000000 --- a/docs/_static/diagrams/mcpwm/dual_edge_sym_active_low.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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 deleted file mode 100644 index 44b27470ab0..00000000000 --- a/docs/_static/diagrams/mcpwm/dual_edge_sym_complementary.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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 deleted file mode 100644 index ea3a6debb4b..00000000000 --- a/docs/_static/diagrams/mcpwm/mcpwm_overview.diag +++ /dev/null @@ -1,59 +0,0 @@ -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 deleted file mode 100644 index ae0097d3516..00000000000 --- a/docs/_static/diagrams/mcpwm/pulse_placement_asym.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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 deleted file mode 100644 index f7b4bd64467..00000000000 --- a/docs/_static/diagrams/mcpwm/single_edge_asym_active_high.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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 deleted file mode 100644 index 68386ffd9f1..00000000000 --- a/docs/_static/diagrams/mcpwm/single_edge_asym_active_low.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "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-bldc-control.png b/docs/_static/mcpwm-bldc-control.png deleted file mode 100644 index d4d83b691c3b55cb78e82da5213099a65f4cac4b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45687 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 ZJ>Hk$%IErNk_UzXQB%=SE>$x3{}-ixCJg`p diff --git a/docs/_static/mcpwm/brake_cbc_ost.svg b/docs/_static/mcpwm/brake_cbc_ost.svg new file mode 100644 index 00000000000..95a270c9172 --- /dev/null +++ b/docs/_static/mcpwm/brake_cbc_ost.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + CBC vs. OST Brake Behavior + + + + + + + + + + + + + + + + + + + + + + + + + + + + + braked + + still braked + + + PWM + + + + + + fault + + + + + + CBC + + + + + + OST + + + + + + + + fault asserted + + + fault cleared + + + CBC recovers here + + + OST recovers (software) + + + Shaded region: output held at the brake level, which is LOW in this example + + + + Runs continuously + Active while asserted + Brakes while fault is active, + recovers at next boundary + Stays braked after fault clears, + resumes only after software recovery + + + diff --git a/docs/_static/mcpwm/capture_measurement.svg b/docs/_static/mcpwm/capture_measurement.svg new file mode 100644 index 00000000000..7d3a8c505b9 --- /dev/null +++ b/docs/_static/mcpwm/capture_measurement.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + Capture: Measure a Pulse Width + + + + + + input + + + + + + a (rising) + b (falling) + + + capture + + + + t1 + t2 + + + timer + + + + + + + cnt_t1 + cnt_t2 + counts continuously + + + + + + + + + + width = t2 - t1 + + + + rising@t1 + + falling@t2 + + + + Input pulse under test + Capture fires a pulse on + each configured edge and + latches the timer count + Capture timer counts freely + at resolution_hz ticks/s + + + diff --git a/docs/_static/mcpwm/carrier_modulation.svg b/docs/_static/mcpwm/carrier_modulation.svg new file mode 100644 index 00000000000..b959ea3ac6b --- /dev/null +++ b/docs/_static/mcpwm/carrier_modulation.svg @@ -0,0 +1,82 @@ + + + + + + + + + + + Carrier Modulation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PWM + + + + + + carrier + + + + + + + + no carrier while base is low + modulated + + + + + + + first pulse + + + 0 + 50 µs + 100 µs + 150 µs + + + The first pulse is held HIGH for first_pulse_duration_us + (20 µs = 2 carrier periods) before normal chopping resumes. + + + + 50% duty, 50 µs period + 100 kHz, 50% duty + Carrier gated by base PWM; + first pulse stretched to 20 µs + + + diff --git a/docs/_static/mcpwm/compare_event.svg b/docs/_static/mcpwm/compare_event.svg new file mode 100644 index 00000000000..e01cfc078f5 --- /dev/null +++ b/docs/_static/mcpwm/compare_event.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + Compare Event: Timer + Comparator + Generator + + + + + + + clk + + + + + + count + + + 0 + + + 5 + + + 10 + + + + 15 + + + 20 + + + 25 + + + 30 + + + 35 + + + 40 + + + 45 + + + + 50 + + + + cmp_match + + + + + + + + TEZ + + + + + + + + + + TEP + + + + + + + PWM + + + + + + + + + + + + + + + period = 50 ticks + + + + + + + + + + + + + + + + + + duty = 15 ticks (30 %) + + + + cmp_ticks = 15 + + + + + Timer clock source + Counter value (decimal) + Pulse when count = cmp_ticks + Pulse when counter = 0 + Pulse when counter = peak + PWM: set high on TEZ, + set low on cmp_match + + + diff --git a/docs/_static/mcpwm/deadtime_active_high.svg b/docs/_static/mcpwm/deadtime_active_high.svg new file mode 100644 index 00000000000..3691e0a04b9 --- /dev/null +++ b/docs/_static/mcpwm/deadtime_active_high.svg @@ -0,0 +1,22 @@ + + +Basic Dead-Time Effect (Active High) + + + + + + +origin + +pwm_A + +pwm_B + + +RED + +FED + +Rising edge of pwm_A delayed; falling edge of pwm_B delayed. + diff --git a/docs/_static/mcpwm/deadtime_active_high_complementary.svg b/docs/_static/mcpwm/deadtime_active_high_complementary.svg new file mode 100644 index 00000000000..6cca46d14de --- /dev/null +++ b/docs/_static/mcpwm/deadtime_active_high_complementary.svg @@ -0,0 +1,28 @@ + + +Complementary PWM with Dead Time (Active High) + + + + + + +origin + +pwm_A + +pwm_B + + + +dead time + + +dead time + +RED + +FED + +pwm_A delays its rising edge; pwm_B is the inverted falling-edge-delayed copy. + diff --git a/docs/_static/mcpwm/deadtime_active_low.svg b/docs/_static/mcpwm/deadtime_active_low.svg new file mode 100644 index 00000000000..fc6988b091f --- /dev/null +++ b/docs/_static/mcpwm/deadtime_active_low.svg @@ -0,0 +1,22 @@ + + +Basic Dead-Time Effect (Active Low) + + + + + + +origin + +pwm_A + +pwm_B + + +RED + +FED + +Same delay resources; polarity inverted after the delay stage. + diff --git a/docs/_static/mcpwm/deadtime_active_low_complementary.svg b/docs/_static/mcpwm/deadtime_active_low_complementary.svg new file mode 100644 index 00000000000..01abb2e2dd0 --- /dev/null +++ b/docs/_static/mcpwm/deadtime_active_low_complementary.svg @@ -0,0 +1,28 @@ + + +Complementary PWM with Dead Time (Active Low) + + + + + + +origin + +pwm_A + +pwm_B + + + +dead time + + +dead time + +RED + +FED + +Same timing resources; output polarity inverted after the delay stage. + diff --git a/docs/_static/mcpwm/deadtime_fedb_bypassa.svg b/docs/_static/mcpwm/deadtime_fedb_bypassa.svg new file mode 100644 index 00000000000..0ea186caa2b --- /dev/null +++ b/docs/_static/mcpwm/deadtime_fedb_bypassa.svg @@ -0,0 +1,20 @@ + + +FED on B, A Bypasses Dead Time + + + + + +origin_A + +origin_B + +pwm_A + +pwm_B + + +FED + + diff --git a/docs/_static/mcpwm/deadtime_reda_bypassb.svg b/docs/_static/mcpwm/deadtime_reda_bypassb.svg new file mode 100644 index 00000000000..9f05e82c4a3 --- /dev/null +++ b/docs/_static/mcpwm/deadtime_reda_bypassb.svg @@ -0,0 +1,20 @@ + + +RED on A, B Bypasses Dead Time + + + + + +origin_A + +origin_B + +pwm_A + +pwm_B + + +RED + + diff --git a/docs/_static/mcpwm/deadtime_redb_fedb_bypassa.svg b/docs/_static/mcpwm/deadtime_redb_fedb_bypassa.svg new file mode 100644 index 00000000000..21d86cd689c --- /dev/null +++ b/docs/_static/mcpwm/deadtime_redb_fedb_bypassa.svg @@ -0,0 +1,23 @@ + + +Bypass A, RED + FED on B + + + + + + +origin_A + +origin_B + +pwm_A + +pwm_B + + +RED + +FED + + diff --git a/docs/_static/mcpwm/dual_edge_asym_active_low.svg b/docs/_static/mcpwm/dual_edge_asym_active_low.svg new file mode 100644 index 00000000000..11d4af04497 --- /dev/null +++ b/docs/_static/mcpwm/dual_edge_asym_active_low.svg @@ -0,0 +1,18 @@ + + +Dual-Edge Asymmetric PWM (Complementary) + + + + +pwm_A + +pwm_B + + +cmp_a + +cmp_b + +gen_a and gen_b use opposite compare actions. + diff --git a/docs/_static/mcpwm/dual_edge_sym_active_low.svg b/docs/_static/mcpwm/dual_edge_sym_active_low.svg new file mode 100644 index 00000000000..03924817cc4 --- /dev/null +++ b/docs/_static/mcpwm/dual_edge_sym_active_low.svg @@ -0,0 +1,20 @@ + + +Center-Aligned PWM + + + + +count + + + +pwm_A + + +cmp = 15 + +cmp = 15 + +High while counting up to cmp, low while counting down past it. + diff --git a/docs/_static/mcpwm/dual_edge_sym_complementary.svg b/docs/_static/mcpwm/dual_edge_sym_complementary.svg new file mode 100644 index 00000000000..7ade4bdde6f --- /dev/null +++ b/docs/_static/mcpwm/dual_edge_sym_complementary.svg @@ -0,0 +1,18 @@ + + +Complementary Center-Aligned PWM (No Dead Time) + + + + +pwm_A + +pwm_B + + +cmp = 15 + +cmp = 15 + +Logical complement only; not yet safe for a power stage. + diff --git a/docs/_static/mcpwm/half_bridge_dead_time.svg b/docs/_static/mcpwm/half_bridge_dead_time.svg new file mode 100644 index 00000000000..565d7bdff55 --- /dev/null +++ b/docs/_static/mcpwm/half_bridge_dead_time.svg @@ -0,0 +1,6925 @@ + + +Half-Bridge Drive: Why Add Dead Time + + + + version="1.1" + width="91.387405mm" + height="55.246201mm" + viewBox="0 0 91.387406 55.2462" + id="svg1978" + sodipodi:docname="half_bridge.svg" + inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.02 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VCC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + HIN + + + + + + + + + + + 2 + + + + + + + + + + + + + + + LIN + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + COM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4 + + + + + + + + + LO + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + VS + + + + + + + + + + + + + + + + + + + + + + + + + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + HO + + + + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + VB + + + + + + + + + + + + + + + + + + + + + + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Half Bridge Drive IC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GND + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +12V + + + + + + + + + + + + + + + + + + + + + + + + + GND + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VBUS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 22 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + G + + + + + + + + + + + + + + + + + + + + + + S + + + + + + + + + + + + + + + + + + + + + + + + NMOS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 22 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + D + + + + + + + + + + + + + + + + + + G + + + + + + + + + + + + + + + + + + + + + + S + + + + + + + + + + + + + + + + + + + + + + + + NMOS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1uF/50V + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VS/OUT/MIDPOINT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pwm_H + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pwm_L + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1uF/50V + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 22 + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Half Bridge Drive IC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VCC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + HIN + + + + + + + + + + + 2 + + + + + + + + + + + + + + + LIN + + + + + + + + + + 3 + + + + + + + + + + + + + + + + + + + COM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4 + + + + + + + + + LO + + + + + + + + + + + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + VS + + + + + + + + + + + + + + + + + + + + + + + + + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + HO + + + + + + + + + + + + + + + + + + + + + + + 7 + + + + + + + VB + + + + + + + + + + + + + + + + + + + + + + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SVG Image created as half_bridge.svg date 2026-08-02T17:56:11 + + + + + + + +Without dead time (shoot-through) +pwm_H + + +pwm_L + + + + + + +shoot-through +With dead time (both off gap) +pwm_H + + +pwm_L + + + + + + +dead time + + \ No newline at end of file diff --git a/docs/_static/mcpwm/phase_shift.svg b/docs/_static/mcpwm/phase_shift.svg new file mode 100644 index 00000000000..c2ee730ce32 --- /dev/null +++ b/docs/_static/mcpwm/phase_shift.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + PWM Phase Shift — 90° Lag + + + + + + + + + + + + + + + + + + Period 1 + Period 2 + + + PWM_A + + + + + + + PWM_B + + + + + + + + + 25 ticks = 90° + PWM_B lags PWM_A + + + + + + + 0 + 25 + 50 + 75 + 100 + 125 + 150 + 175 + 200 + + ticks + + + PWM period = 100 ticks, 50% duty cycle. A 25-tick offset produces a 90° phase shift. + + + + 50% duty, period = 100 ticks + Starts 25 ticks after PWM_A + + + \ No newline at end of file diff --git a/docs/_static/mcpwm/pulse_placement_asym.svg b/docs/_static/mcpwm/pulse_placement_asym.svg new file mode 100644 index 00000000000..550b1e4bc5e --- /dev/null +++ b/docs/_static/mcpwm/pulse_placement_asym.svg @@ -0,0 +1,19 @@ + + +Pulse Placement Inside the Period + + + + +count + + +pwm_A + + +cmp_a + +cmp_b + +cmp_a opens the pulse, cmp_b closes it. + diff --git a/docs/_static/mcpwm/single_edge_asym_active_high.svg b/docs/_static/mcpwm/single_edge_asym_active_high.svg new file mode 100644 index 00000000000..f8535082a54 --- /dev/null +++ b/docs/_static/mcpwm/single_edge_asym_active_high.svg @@ -0,0 +1,17 @@ + + +Single-Edge Asymmetric PWM, Active High + + + + +count + + +pwm_A + + +cmp = 15 + +Set high at timer zero, low at the comparator. + diff --git a/docs/_static/mcpwm/single_edge_asym_active_low.svg b/docs/_static/mcpwm/single_edge_asym_active_low.svg new file mode 100644 index 00000000000..ca7ed8131c2 --- /dev/null +++ b/docs/_static/mcpwm/single_edge_asym_active_low.svg @@ -0,0 +1,17 @@ + + +Single-Edge Asymmetric PWM, Active Low + + + + +count + + +pwm_A + + +cmp = 15 + +Set low at the period boundary, high at the comparator. + diff --git a/docs/_static/mcpwm/timer_up_count.svg b/docs/_static/mcpwm/timer_up_count.svg new file mode 100644 index 00000000000..43594ae807d --- /dev/null +++ b/docs/_static/mcpwm/timer_up_count.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + Up-Counting Timer Timing Diagram + + + + + + + + + clk + + + + + + count + + + + 0 + + + 5 + + + 10 + + + 15 + + + 20 + + + 25 + + + 30 + + + 35 + + + 40 + + + 45 + + + + 50 + + + reset / reload + + + 0 + + + + TEZ + + + + + + + + + + + TEP + + + + + + + + + wave + + + + + + + + + + + auto-reload period = 50 ticks + + + + + + + + + + + + + peak + + + + + Timer clock source + Counter value (decimal) + Pulse when counter = 0 + Pulse when counter = peak + Sawtooth: count 0 to 50, then reset + + + diff --git a/docs/_static/mcpwm/timer_up_down_count.svg b/docs/_static/mcpwm/timer_up_down_count.svg new file mode 100644 index 00000000000..3a42b8bf4fe --- /dev/null +++ b/docs/_static/mcpwm/timer_up_down_count.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + Up-Down Counting Timer Timing Diagram + + + + + + + + + clk + + + + + + count + + + + 0 + + + 5 + + + 10 + + + 15 + + + 20 + + + + 25 + + + + 20 + + + 15 + + + 10 + + + 5 + + + + 0 + + + + TEZ + + + + + + + + + + TEP + + + + + + + + wave + + + + + + + + + + + auto-reload period = 50 ticks (peak = 25) + + + + + + + + + + + + + + peak + + + + + Timer clock source + Counter value (decimal), up then down + Pulse when counter = 0 + Pulse when counter = peak + Triangle: count 0 to 25, back to 0 + + + diff --git a/docs/conf_common.py b/docs/conf_common.py index 5dfaa8f75bb..f70ba92ecc2 100644 --- a/docs/conf_common.py +++ b/docs/conf_common.py @@ -145,7 +145,18 @@ SDMMC_DOCS = ['api-reference/peripherals/sdmmc_host.rst'] SDIO_SLAVE_DOCS = ['api-reference/peripherals/sdio_slave.rst', 'api-reference/protocols/esp_sdio_slave_protocol.rst'] -MCPWM_DOCS = ['api-reference/peripherals/mcpwm.rst'] +MCPWM_DOCS = [ + 'api-reference/peripherals/mcpwm/index.rst', + 'api-reference/peripherals/mcpwm/mcpwm_advanced.rst', + 'api-reference/peripherals/mcpwm/mcpwm_cap.rst', + 'api-reference/peripherals/mcpwm/mcpwm_cmpr.rst', + 'api-reference/peripherals/mcpwm/mcpwm_etm.rst', + 'api-reference/peripherals/mcpwm/mcpwm_fault.rst', + 'api-reference/peripherals/mcpwm/mcpwm_gen.rst', + 'api-reference/peripherals/mcpwm/mcpwm_operator.rst', + 'api-reference/peripherals/mcpwm/mcpwm_sync.rst', + 'api-reference/peripherals/mcpwm/mcpwm_timer.rst', +] DEDIC_GPIO_DOCS = ['api-reference/peripherals/dedic_gpio.rst'] diff --git a/docs/en/api-reference/peripherals/etm.rst b/docs/en/api-reference/peripherals/etm.rst index fe18a301f69..a3663e15c55 100644 --- a/docs/en/api-reference/peripherals/etm.rst +++ b/docs/en/api-reference/peripherals/etm.rst @@ -70,7 +70,7 @@ Other Peripheral Events :SOC_SYSTIMER_SUPPORT_ETM: - Refer to :doc:`/api-reference/system/esp_timer` for how to get the ETM event handle from esp_timer. :SOC_TIMER_SUPPORT_ETM: - Refer to :ref:`gptimer-etm-event-and-task` for how to get the ETM event handle from GPTimer. :SOC_GDMA_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/async_memcpy` for how to get the ETM event handle from async memcpy. - :SOC_MCPWM_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/mcpwm` for how to get the ETM event handle from MCPWM. + :SOC_MCPWM_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/mcpwm/mcpwm_etm` for how to get the ETM event handle from MCPWM. :SOC_ANA_CMPR_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/ana_cmpr` for how to get the ETM event handle from analog comparator. :SOC_TEMPERATURE_SENSOR_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/temp_sensor` for how to get the ETM event handle from temperature sensor. :SOC_I2S_SUPPORTS_ETM: - Refer to :doc:`/api-reference/peripherals/i2s` for how to get the ETM event handle from I2S. diff --git a/docs/en/api-reference/peripherals/index.rst b/docs/en/api-reference/peripherals/index.rst index f6459453860..7e4c551a434 100644 --- a/docs/en/api-reference/peripherals/index.rst +++ b/docs/en/api-reference/peripherals/index.rst @@ -32,7 +32,7 @@ Peripherals API lcd/index :SOC_GP_LDO_SUPPORTED: ldo_regulator :SOC_LEDC_SUPPORTED: ledc - :SOC_MCPWM_SUPPORTED: mcpwm + :SOC_MCPWM_SUPPORTED: mcpwm/index :SOC_PARLIO_SUPPORTED: parlio/index :SOC_PCNT_SUPPORTED: pcnt :SOC_PPA_SUPPORTED: ppa diff --git a/docs/en/api-reference/peripherals/mcpwm.rst b/docs/en/api-reference/peripherals/mcpwm.rst deleted file mode 100644 index ed5b8b8a4fd..00000000000 --- a/docs/en/api-reference/peripherals/mcpwm.rst +++ /dev/null @@ -1,1147 +0,0 @@ -Motor Control Pulse Width Modulator (MCPWM) -=========================================== - -:link_to_translation:`zh_CN:[中文]` - - -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: - -- 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 values 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 - -- **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 compares it to the threshold value configured. When the timer is equal to any of the threshold values, a 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 by other submodules like MCPWM Timer and MCPWM Comparator. -- **MCPWM Fault**: The fault module is used to detect the fault condition from outside, mainly via the 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 the GPIO matrix or from an MCPWM Timer event. -- **Dead Time**: This submodule is used to insert extra delay to the existing PWM edges generated in the previous steps. -- **Carrier Modulation**: The carrier submodule can modulate a high-frequency carrier signal into PWM waveforms by the generator and dead time submodules. This capability is mandatory for controlling the power-switching elements. -- **Brake**: MCPWM operator can set how to brake the generators when a particular fault is detected. You can shut down the PWM output immediately or regulate the PWM output cycle by cycle, depending on how critical the fault is. -- **MCPWM Capture**: This is a standalone submodule that can work even without the above MCPWM operators. The capture consists one dedicated timer and several independent channels, with each channel connected to the GPIO. A pulse on the GPIO triggers the capture timer to store the time-base count value and then notify you by an interrupt. Using this feature, you can measure a pulse width precisely. What is more, the capture timer can also be synchronized by the MCPWM Sync submodule. - -Functional Overview -------------------- - -Description of the MCPWM functionality is divided into the following sections: - -.. list:: - - - :ref:`mcpwm-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. - - :ref:`mcpwm-timer-operations-and-events` - describes control functions and event callbacks supported by the MCPWM timer. - - :ref:`mcpwm-comparator-operations-and-events` - describes control functions and event callbacks supported by the MCPWM comparator. - - :ref:`mcpwm-generator-actions-on-events` - describes how to set actions for MCPWM generators on particular events that are generated by the MCPWM timer and comparators. - - :ref:`mcpwm-classical-pwm-waveforms-and-generator-configurations` - demonstrates some classical PWM waveforms that can be achieved by configuring generator actions. - - :ref:`mcpwm-dead-time` - describes how to set dead time for MCPWM generators. - - :ref:`mcpwm-classical-pwm-waveforms-and-dead-time-configurations` - demonstrates some classical PWM waveforms that can be achieved by configuring dead time. - - :ref:`mcpwm-carrier-modulation` - describes how to set and modulate a high frequency onto the final PWM waveforms. - - :ref:`mcpwm-faults-and-brake-actions` - describes how to set brake actions for MCPWM operators on particular fault events. - - :ref:`mcpwm-generator-force-actions` - describes how to control the generator output level asynchronously in a forceful way. - - :ref:`mcpwm-synchronization` - describes how to synchronize the MCPWM timers and get a fixed phase difference between the generated PWM signals. - - :ref:`mcpwm-capture` - describes how to use the MCPWM capture module to measure the pulse width of a signal. - :SOC_MCPWM_SUPPORT_ETM and SOC_ETM_SUPPORTED: - :ref:`mcpwm-etm-event-and-task` - describes what the events and tasks can be connected to the ETM channel. - - :ref:`mcpwm-power-management` - describes how different source clocks affects power consumption. - - :ref:`mcpwm-resolution-config` - describes the resolution configuration rules for the MCPWM submodule. - - :ref:`mcpwm-iram-safe` - describes tips on how to make the RMT interrupt work better along with a disabled cache. - - :ref:`mcpwm-thread-safety` - lists which APIs are guaranteed to be thread-safe by the driver. - - :ref:`mcpwm-kconfig-options` - lists the supported Kconfig options that can bring different effects to the driver. - -.. _mcpwm-resource-allocation-and-initialization: - -Resource Allocation and Initialization -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -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. - -MCPWM Timers -~~~~~~~~~~~~ - -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: - -- :cpp:member:`mcpwm_timer_config_t::group_id` specifies the MCPWM group ID. The ID should belong to [0, ``MCPWM_GROUP_NUM`` - 1] range, where ``MCPWM_GROUP_NUM`` is the number of MCPWM groups available on the chip. Please note, timers located in different groups are totally independent. - -.. note:: - - For the number of MCPWM groups available on the chip, please refer to *{IDF_TARGET_NAME} Technical Reference Manual* > *Motor Control PWM (MCPWM)* [`PDF <{IDF_TARGET_TRM_EN_URL}#mcpwm>`__]. - -- :cpp:member:`mcpwm_timer_config_t::intr_priority` sets the priority of the interrupt. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority. -- :cpp:member:`mcpwm_timer_config_t::clk_src` sets the clock source of the timer. -- :cpp:member:`mcpwm_timer_config_t::resolution_hz` sets the expected resolution of the timer. The driver internally sets 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::flags::update_period_on_empty` sets whether to update the period value when the timer counts to zero. -- :cpp:member:`mcpwm_timer_config_t::flags::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 an error code. Specifically, when there are no more free timers in the MCPWM group, this function will return the :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ - -On the contrary, calling the :cpp:func:`mcpwm_del_timer` function will free the allocated timer object. - -.. note:: - - The prescale for the MCPWM group will be calculated with the resolution of the first timer, and the driver will find the appropriate prescale from low to high. If there is a prescale conflict when allocating multiple timers, allocate timers in order of their target resolution, either from highest to lowest or lowest to highest. For more information, please refer to :ref:`mcpwm-resolution-config`. - -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, ``MCPWM_GROUP_NUM`` - 1] range, where ``MCPWM_GROUP_NUM`` is the number of MCPWM groups available on the chip. Please note, operators located in different groups are totally independent. -- :cpp:member:`mcpwm_operator_config_t::intr_priority` sets the priority of the interrupt. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority. -- :cpp:member:`mcpwm_operator_config_t::flags::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::flags::update_gen_action_on_tep` sets whether to update the generator action when the timer counts to peak. -- :cpp:member:`mcpwm_operator_config_t::flags::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::flags::update_dead_time_on_tez` sets whether to update the dead time when the timer counts to zero. -- :cpp:member:`mcpwm_operator_config_t::flags::update_dead_time_on_tep` sets whether to update the dead time when the timer counts to the peak. -- :cpp:member:`mcpwm_operator_config_t::flags::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 an error code. Specifically, when there are no more free operators in the MCPWM group, this function will return the :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: - -MCPWM Comparators -~~~~~~~~~~~~~~~~~ - -You can allocate a MCPWM comparator object by calling the :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::intr_priority` sets the priority of the interrupt. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority. -- :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_tez` sets whether to update the compare threshold when the timer counts to zero. -- :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_tep` sets whether to update the compare threshold when the timer counts to the peak. -- :cpp:member:`mcpwm_comparator_config_t::flags::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 an error code. Specifically, when there are no more free comparators in the MCPWM operator, this function will return the :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ - -On the contrary, calling the :cpp:func:`mcpwm_del_comparator` function will free the allocated comparator object. - -.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR and SOC_MCPWM_SUPPORT_ETM and SOC_ETM_SUPPORTED - - There's another kind of comparator called "Event Comparator", which **can not** control the final PWM directly but only generates the ETM events at a configurable time stamp. You can allocate an event comparator by calling the :cpp:func:`mcpwm_new_event_comparator` function. This function will return the same handle type as :cpp:func:`mcpwm_new_comparator`, but with a different configuration structure :cpp:type:`mcpwm_event_comparator_config_t`. For more information, please refer to :ref:`mcpwm-etm-event-and-task`. - -MCPWM Generators -~~~~~~~~~~~~~~~~ - -You can allocate a MCPWM generator object by calling the :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::flags::invert_pwm` sets whether to invert the PWM signal. -- :cpp:member:`mcpwm_generator_config_t::pull_up` and :cpp:member:`mcpwm_generator_config_t::pull_down` controls whether to enable the internal pull-up and pull-down resistors accordingly. - -The :cpp:func:`mcpwm_new_generator` will return a pointer to the allocated generator object if the allocation succeeds. Otherwise, it will return an error code. Specifically, when there are no more free generators in the MCPWM operator, this function will return the :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ - -On the contrary, calling the :cpp:func:`mcpwm_del_generator` function will free the allocated generator object. - -.. _mcpwm-faults: - -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 the :cpp:func:`mcpwm_new_gpio_fault` function, with the 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, ``MCPWM_GROUP_NUM`` - 1] range, where ``MCPWM_GROUP_NUM`` is the number of MCPWM groups available on the chip. Please note, GPIO faults located in different groups are totally independent, i.e., GPIO faults in group 0 can not be detected by the operator in group 1. -- :cpp:member:`mcpwm_gpio_fault_config_t::intr_priority` sets the priority of the interrupt. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority. -- :cpp:member:`mcpwm_gpio_fault_config_t::gpio_num` sets the GPIO number used by the fault. -- :cpp:member:`mcpwm_gpio_fault_config_t::flags::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. - -The :cpp:func:`mcpwm_new_gpio_fault` will return a pointer to the allocated fault object if the allocation succeeds. Otherwise, it will return an error code. Specifically, when there are no more free GPIO faults in the MCPWM group, this function will return the :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ - -Software fault object can be used to trigger a fault by calling the 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 the :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 purposes. - -The :cpp:func:`mcpwm_new_soft_fault` function will return a pointer to the allocated fault object if the allocation succeeds. Otherwise, it will return an error code. Specifically, when there is no memory left for the fault object, this function will return the :c:macro:`ESP_ERR_NO_MEM` error. Although the software fault and GPIO fault are of different types, the returned fault handle is of the same type. - -On the contrary, calling the :cpp:func:`mcpwm_del_fault` function will free the allocated fault object, this function works for both software and GPIO fault. - -.. _mcpwm-sync-sources: - -MCPWM Sync Sources -~~~~~~~~~~~~~~~~~~ - -The sync source is what can be used to synchronize the MCPWM timer and MCPWM capture timer. There are three types of sync sources: a sync source reflected from the GPIO, a sync source generated by software, and a sync source generated by an MCPWM timer event. - -To allocate a GPIO sync source, you can call the :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, ``MCPWM_GROUP_NUM`` - 1] range, where ``MCPWM_GROUP_NUM`` is the number of MCPWM groups available on the chip. Please note, the GPIO sync sources 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::flags::active_neg` sets whether the sync signal is active on falling edges. -- :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. - -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 an error code. Specifically, when there are no more free GPIO sync sources in the MCPWM group, this function will return the :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ - -To allocate a timer event sync source, you can call the :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 an error code. Specifically, if a sync source has been allocated from the same timer before, this function will return the :c:macro:`ESP_ERR_INVALID_STATE` error. - -Last but not least, to allocate a software sync source, you can call the :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 purposes. - -: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 an error code. Specifically, when there is no memory left for the sync source object, this function will return the :c:macro:`ESP_ERR_NO_MEM` error. Please note, to make a software sync source take effect, do not forget to call :cpp:func:`mcpwm_soft_sync_activate`. - -On the contrary, calling the :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: - -MCPWM Capture Timer and Channels -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The MCPWM group has a dedicated timer which is used to capture the timestamp when a specific event occurred. The capture timer is connected to several independent channels, each channel is assigned a GPIO. - -To allocate a capture timer, you can call the :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, ``MCPWM_GROUP_NUM`` - 1] range, where ``MCPWM_GROUP_NUM`` is the number of MCPWM groups available on the chip. -- :cpp:member:`mcpwm_capture_timer_config_t::clk_src` sets the clock source of the capture timer. -- :cpp:member:`mcpwm_capture_timer_config_t::resolution_hz` The driver internally will set a proper divider based on the clock source and the resolution. If it is set to ``0``, the driver will pick an appropriate resolution on its own, and you can subsequently view the current timer resolution via :cpp:func:`mcpwm_capture_timer_get_resolution`. - -.. only:: not SOC_MCPWM_CAPTURE_CLK_FROM_GROUP - - .. note:: - - In {IDF_TARGET_NAME}, :cpp:member:`mcpwm_capture_timer_config_t::resolution_hz` parameter is invalid, the capture timer resolution is always equal to the :cpp:enumerator:`MCPWM_CAPTURE_CLK_SRC_APB`. - -.. only:: SOC_MCPWM_CAPTURE_CLK_FROM_GROUP - - .. note:: - - Timers and capture timers share the MCPWM group clock source. The prescale for the MCPWM group will be calculated with the resolution of the first allocated (capture)timer. The driver will search for the appropriate prescale from low to high. If there is a prescale conflict when allocating multiple (capture)timers, allocate (capture)timers in order of their target resolution, either from highest to lowest or lowest to highest. For more information, please refer to :ref:`mcpwm-resolution-config`. - -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 an error code. Specifically, when there is no free capture timer left in the MCPWM group, this function will return the :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ - -Next, to allocate a capture channel, you can call the :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::intr_priority` sets the priority of the interrupt. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority. -- :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::extra_capture_channel_flags::pos_edge` and :cpp:member:`mcpwm_capture_channel_config_t::extra_capture_channel_flags::neg_edge` set whether to capture on the positive and/or falling edge of the input signal. -- :cpp:member:`mcpwm_capture_channel_config_t::extra_capture_channel_flags::pull_up` and :cpp:member:`mcpwm_capture_channel_config_t::extra_capture_channel_flags::pull_down` set whether to pull up and/or pull down the GPIO internally. -- :cpp:member:`mcpwm_capture_channel_config_t::extra_capture_channel_flags::invert_cap_signal` sets whether to invert the capture signal. - -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 an error code. Specifically, when there is no free capture channel left in the capture timer, this function will return the :c:macro:`ESP_ERR_NOT_FOUND` error. - -On the contrary, calling :cpp:func:`mcpwm_del_capture_channel` and :cpp:func:`mcpwm_del_capture_timer` will free the allocated capture channel and timer object accordingly. - -MCPWM Interrupt Priority -~~~~~~~~~~~~~~~~~~~~~~~~ - -MCPWM allows configuring interrupts separately for timer, operator, comparator, fault, and capture events. The interrupt priority is determined by the respective ``config_t::intr_priority``. Additionally, events within the same MCPWM group share a common interrupt source. When registering multiple interrupt events, the interrupt priorities need to remain consistent. - -.. note:: - - When registering multiple interrupt events within an MCPWM group, the driver will use the interrupt priority of the first registered event as the MCPWM group's interrupt priority. - - -.. _mcpwm-timer-operations-and-events: - -Timer Operations and Events -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Update Period -~~~~~~~~~~~~~ - -The timer period is initialized by the :cpp:member:`mcpwm_timer_config_t::period_ticks` parameter in :cpp:type:`mcpwm_timer_config_t`. You can update the period at runtime by calling :cpp:func:`mcpwm_timer_set_period` function. The new period will take effect based on how you set the :cpp:member:`mcpwm_timer_config_t::flags::update_period_on_empty` and :cpp:member:`mcpwm_timer_config_t::flags::update_period_on_sync` parameters in :cpp:type:`mcpwm_timer_config_t`. If none of them are set, the timer period will take effect immediately. - -Register Timer Event Callbacks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The MCPWM timer can generate different events at runtime. If you have some function that should be called when a 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 the callback function for the timer when it counts to peak value. -- :cpp:member:`mcpwm_timer_event_callbacks_t::on_empty` sets the callback function for the timer when it counts to zero. -- :cpp:member:`mcpwm_timer_event_callbacks_t::on_stop` sets the callback function for the timer when it is stopped. - -The callback functions above are called within the ISR context, so they should **not** attempt to block. For example, you may make sure that only FreeRTOS APIs with the ``ISR`` suffix are called within the function. - -The parameter ``user_data`` of the :cpp:func:`mcpwm_timer_register_event_callbacks` function is used to save your own context. It is passed to each callback function directly. - -This function will lazy the install interrupt service for the MCPWM timer without enabling it. It is only allowed to be called before :cpp:func:`mcpwm_timer_enable`, otherwise the :c:macro:`ESP_ERR_INVALID_STATE` error will be returned. See also :ref:`mcpwm-enable-and-disable-timer` for more information. - -.. _mcpwm-enable-and-disable-timer: - -Enable and Disable Timer -~~~~~~~~~~~~~~~~~~~~~~~~ - -Before doing IO control to the timer, you need to enable the timer first, by calling :cpp:func:`mcpwm_timer_enable`. This function: - -* switches the timer state from **init** to **enable**. -* enables 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 :ref:`mcpwm-power-management` for more information. - -On the contrary, calling :cpp:func:`mcpwm_timer_disable` will put the timer driver back to the **init** state, disable the interrupt 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 is more, you can even start the timer for only one round, which 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 an 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. Please make sure the MCPWM timer and operator are in the same group. Otherwise, this function will return the :c:macro:`ESP_ERR_INVALID_ARG` error. - - -.. _mcpwm-comparator-operations-and-events: - -Comparator Operations and Events -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Register Comparator Event Callbacks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The MCPWM comparator can inform you when the timer counter equals 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 the callback function for the comparator when the timer counter equals the compare value. - -The callback function provides event-specific data of type :cpp:type:`mcpwm_compare_event_data_t` to you. The callback function is called within the ISR context, so it should **not** attempt to block. For example, you may make sure that only FreeRTOS APIs with the ``ISR`` suffix are called within the function. - -The parameter ``user_data`` of :cpp:func:`mcpwm_comparator_register_event_callbacks` function is used to save your own context. It is passed to the callback function directly. - -This function will lazy the installation of interrupt service for the MCPWM comparator, whereas the service can only be removed in :cpp:type:`mcpwm_del_comparator`. - -.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR - - .. note:: - - It is not supported to register event callbacks for an **Event Comparator** because it can not generate any interrupt. - -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 are a few points to note: - -- A new compare value might not take effect immediately. The update time for the compare value is set by :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_tez` or :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_tep` or :cpp:member:`mcpwm_comparator_config_t::flags::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 the error code :c:macro:`ESP_ERR_INVALID_STATE`. -- The compare value should not exceed the timer's count peak, otherwise, the compare event will never get triggered. - - -.. _mcpwm-generator-actions-on-events: - -Generator Actions on Events -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Set Generator Action on Timer Event -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A single generator can be configured to perform multiple actions in response to different timer events. To achieve this, invoke :cpp:func:`mcpwm_generator_set_action_on_timer_event` for each desired event-action pair. The details of each action are specified using the :cpp:type:`mcpwm_gen_timer_event_action_t` structure. - -- :cpp:member:`mcpwm_gen_timer_event_action_t::direction` specifies 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 is a helper macro :c:macro:`MCPWM_GEN_TIMER_EVENT_ACTION` to simplify the construction of a timer event action entry. - -Set Generator Action on Compare Event -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A single generator can be configured to perform multiple actions in response to different compare events. To achieve this, invoke :cpp:func:`mcpwm_generator_set_action_on_compare_event` for each desired compare event and action pair. The specific action settings are encapsulated in the :cpp:type:`mcpwm_gen_compare_event_action_t` structure. - -- :cpp:member:`mcpwm_gen_compare_event_action_t::direction` specifies 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 :ref:`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 is a helper macro :c:macro:`MCPWM_GEN_COMPARE_EVENT_ACTION` to simplify the construction of a compare event action entry. - -Set Generator Action on Fault Event -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A single generator can be configured to perform multiple actions in response to fault events. To achieve this, invoke :cpp:func:`mcpwm_generator_set_action_on_fault_event` for each desired action. The specific actions to be taken are described by the :cpp:type:`mcpwm_gen_fault_event_action_t` structure. - -- :cpp:member:`mcpwm_gen_fault_event_action_t::direction` specifies the timer direction. The supported directions are listed in :cpp:type:`mcpwm_timer_direction_t`. -- :cpp:member:`mcpwm_gen_fault_event_action_t::fault` specifies the fault used for the trigger. See :ref:`mcpwm-faults` for how to allocate a fault. -- :cpp:member:`mcpwm_gen_fault_event_action_t::action` specifies the generator action to be taken. The supported actions are listed in :cpp:type:`mcpwm_generator_action_t`. - -When no free trigger slot is left in the operator to which the generator belongs, this function will return the :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ - -The trigger only support GPIO fault. when the input is not a GPIO fault, this function will return the :c:macro:`ESP_ERR_NOT_SUPPORTED` error. - -There is a helper macro :c:macro:`MCPWM_GEN_FAULT_EVENT_ACTION` to simplify the construction of a trigger event action entry. - -Set Generator Action on Sync Event -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A single generator can be configured to perform multiple actions in response to different synchronization events. This is achieved by invoking :cpp:func:`mcpwm_generator_set_action_on_sync_event` for each desired event-action pair. The specific action to be taken for each synchronization event is described by the :cpp:type:`mcpwm_gen_sync_event_action_t` structure. - -- :cpp:member:`mcpwm_gen_sync_event_action_t::direction` specifies the timer direction. The supported directions are listed in :cpp:type:`mcpwm_timer_direction_t`. -- :cpp:member:`mcpwm_gen_sync_event_action_t::sync` specifies the sync source used for the trigger. See :ref:`mcpwm-sync-sources` for how to allocate a sync source. -- :cpp:member:`mcpwm_gen_sync_event_action_t::action` specifies the generator action to be taken. The supported actions are listed in :cpp:type:`mcpwm_generator_action_t`. - -When no free trigger slot is left in the operator to which the generator belongs, this function will return the :c:macro:`ESP_ERR_NOT_FOUND` error. [1]_ - -The trigger only support one sync action, regardless of the kinds. When set sync actions more than once, this function will return the :c:macro:`ESP_ERR_INVALID_STATE` error. - -There is a helper macro :c:macro:`MCPWM_GEN_SYNC_EVENT_ACTION` to simplify the construction of a trigger event action entry. - - -.. _mcpwm-classical-pwm-waveforms-and-generator-configurations: - -Generator Configurations for Classical PWM Waveforms -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This section will demonstrate the classical PWM waveforms that can be generated by the pair of 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 is 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. - -Single Edge Asymmetric Waveform - 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - } - -Single Edge Asymmetric Waveform - 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH))); - } - -Pulse Placement Asymmetric Waveform -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. 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_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_TOGGLE))); - } - -Dual Edge Asymmetric Waveform - 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_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_HIGH))); - } - -Dual Edge Symmetric Waveform - 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_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW))); - } - -Dual Edge Symmetric Waveform - 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_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_HIGH))); - } - - -.. _mcpwm-dead-time: - -Dead Time -^^^^^^^^^ - -In power electronics, the rectifier and inverter are commonly used. This requires the use of a rectifier bridge and an inverter bridge. Each bridge arm has two power electronic devices, such as MOSFET, IGBT, etc. The two MOSFETs on the same arm can not 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, 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 generated by setting :ref:`mcpwm-generator-actions-on-events`. - -The dead time driver works like a **decorator**. This 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 new generator (``out_generator``) after applying the dead time. Please note, if the ``out_generator`` and ``in_generator`` are the same, it means you are adding the time delay to the PWM waveform in an "in-place" fashion. In turn, if the ``out_generator`` and ``in_generator`` are different, it means you are deriving a new PWM waveform from the existing ``in_generator``. - -Dead time specific configuration is listed in the :cpp:type:`mcpwm_dead_time_config_t` structure: - -- :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 bypassing the dead time module. The resolution of the dead time tick is the same as the timer that is connected with the operator by :cpp:func:`mcpwm_operator_connect_timer`. -- :cpp:member:`mcpwm_dead_time_config_t::invert_output` sets whether to invert the signal after applying the dead time, which can be used to control the delay edge polarity. - -.. warning:: - - Due to the hardware limitation, one delay module (either ``posedge delay`` or ``negedge delay``) can not be applied to multiple MCPWM generators at the same time. e.g., the following configuration is **invalid**: - - .. code:: c - - mcpwm_dead_time_config_t dt_config = { - .posedge_delay_ticks = 10, - }; - // Set posedge delay to generator A - mcpwm_generator_set_dead_time(mcpwm_gen_a, mcpwm_gen_a, &dt_config); - // NOTE: This is invalid, you can not apply the posedge delay to another generator - mcpwm_generator_set_dead_time(mcpwm_gen_b, mcpwm_gen_b, &dt_config); - - However, you can apply ``posedge delay`` to generator A and ``negedge delay`` to generator B. You can also set both ``posedge delay`` and ``negedge delay`` for generator B, while letting generator A bypass the dead time module. Note that if ``negedge delay`` and ``posedge delay`` are both set for generator A, generator B will not be available. Where generator A is the first generator requested through the operator handle and generator B is the second generator requested through an operator handle. - -.. note:: - - It is also possible to generate the required dead time by setting :ref:`mcpwm-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. - - -.. _mcpwm-classical-pwm-waveforms-and-dead-time-configurations: - -Dead Time Configurations for Classical PWM Waveforms -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This section demonstrates 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. - -Active High Complementary -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. wavedrom:: /../_static/diagrams/mcpwm/deadtime_active_high_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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - } - - 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; - dead_time_config.flags.invert_output = true; - ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config)); - } - -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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - } - - 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - } - - 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - } - - 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 and Bypass Dead Time 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - } - - 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 and Bypass Dead Time 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - } - - 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 and Bypass Dead Time 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - } - - 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)); - } - - -.. _mcpwm-carrier-modulation: - -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 the PWM output signals may be at 100% duty and not changing whenever a motor is required to run steadily at the full load. Coupling with 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::clk_src` sets the clock source of the carrier. -- :cpp:member:`mcpwm_carrier_config_t::frequency_hz` indicates carrier frequency in Hz. For more information, please refer to :ref:`mcpwm-resolution-config`. -- :cpp:member:`mcpwm_carrier_config_t::duty_cycle` indicates the duty cycle of the carrier. Note that, there are only 7 supported duty cycles: 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875. -- :cpp:member:`mcpwm_carrier_config_t::first_pulse_duration_us` indicates 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 not 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. - - -.. _mcpwm-faults-and-brake-actions: - -Faults and Brake Actions -^^^^^^^^^^^^^^^^^^^^^^^^ - -The MCPWM operator is able to sense external signals with information about the failure of the motor, the power driver or any other device connected. These failure signals are encapsulated into MCPWM fault objects. - -You should determine possible failure modes of the motor and what action should be performed on detection of a particular fault, e.g., drive all outputs low for a brushed motor, lock current state for a stepper motor, etc. Because of this action, the motor should be put into a safe state to reduce the likelihood of 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`. Specific brake configuration is passed as a structure :cpp:type:`mcpwm_brake_config_t`: - -- :cpp:member:`mcpwm_brake_config_t::fault` sets which fault the operator should react to. -- :cpp:member:`mcpwm_brake_config_t::brake_mode` sets 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 recovers 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 not recover even though the fault disappears. You have to call :cpp:func:`mcpwm_operator_recover_from_fault` to manually recover it. - -Set Generator Action on Brake Event -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A single generator can be configured to perform multiple actions in response to brake events. To achieve this, invoke :cpp:func:`mcpwm_generator_set_action_on_brake_event` for each desired action. The specific actions available for configuration are defined in :cpp:type:`mcpwm_gen_brake_event_action_t`. - -- :cpp:member:`mcpwm_gen_brake_event_action_t::direction` specifies 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 is a helper macro :c:macro:`MCPWM_GEN_BRAKE_EVENT_ACTION` to simplify the construction of a brake event action entry. - -Register Fault Event Callbacks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The MCPWM fault detector can inform you when it detects a valid fault or a fault signal disappears. If you have some function that should be called when such an 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 the callback function that will be called when a fault is detected. -- :cpp:member:`mcpwm_fault_event_callbacks_t::on_fault_exit` sets the callback function that will be called when a fault is cleared. - -The callback function is called within the ISR context, so it should **not** attempt to block. For example, you may make sure that only FreeRTOS APIs with the ``ISR`` suffix are called within the function. - -The parameter ``user_data`` of :cpp:func:`mcpwm_fault_register_event_callbacks` function is used to save your own context. It is passed to the callback function directly. - -This function will lazy the 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 you when it is 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 the 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 the 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 it should **not** attempt to block. For example, you may make sure that only FreeRTOS APIs with the ``ISR`` suffix are called within the function. - -The parameter ``user_data`` of the :cpp:func:`mcpwm_operator_register_event_callbacks` function is used to save your own context. It will be passed to the callback function directly. - -This function will lazy the install interrupt service for the MCPWM operator, whereas the service can only be removed in :cpp:type:`mcpwm_del_operator`. - - -.. _mcpwm-generator-force-actions: - -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_action_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, and the force output level will keep alive until it is removed by assigning ``level`` to -1. -- Set the ``hole_on`` to false, the force output level will only be active for a short time, and any upcoming event can override it. - - -.. _mcpwm-synchronization: - -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 :ref:`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. - -.. note:: - - When the MCPWM timer is working in :cpp:enumerator:`MCPWM_TIMER_COUNT_MODE_UP_DOWN` mode, special attention needs to be taken. In this mode, counter range ``[0 -> peak-1]`` belongs to the **increment** phase, and counter range ``[peak -> 1]`` belongs to the **decrement** phase. Thus if you set the :cpp:member:`mcpwm_timer_sync_phase_config_t::count_value` to zero, you may also want to set the :cpp:member:`mcpwm_timer_sync_phase_config_t::direction` to :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP`. Otherwise, the timer will be continue with the decrement phase, and the count value underflows to ``peak``. - -Likewise, the :ref:`MCPWM Capture Timer ` 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 :ref:`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.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)); - } - } - - -.. _mcpwm-capture: - -Capture -^^^^^^^ - -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 quantities like distance or speed in the capture callback function. For example, in the BLDC (Brushless DC, see figure below) scenario, you can use the capture submodule to sense the rotor position from the Hall sensor. - -.. figure:: ../../../_static/mcpwm-bldc-control.png - :align: center - :alt: MCPWM BLDC with Hall Sensor - - MCPWM BLDC with Hall Sensor - -The capture timer is usually connected to several capture channels. Please refer to :ref:`mcpwm-capture-timer-and-channels` for more information about resource allocation. - -Register Capture Event Callbacks -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The MCPWM capture channel can inform you when there is a valid edge detected on the signal. You have to register a callback function to get the timer count value of the captured 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`: - -- :cpp:member:`mcpwm_capture_event_callbacks_t::on_cap` sets the callback function for the capture channel when a valid edge is detected. - -The callback function provides event-specific data of type :cpp:type:`mcpwm_capture_event_data_t`, so that you can get 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`. To convert the capture count into a timestamp, you need to know the resolution of the capture timer by calling :cpp:func:`mcpwm_capture_timer_get_resolution`. - -The callback function is called within the ISR context, so it should **not** attempt to block. For example, you may make sure that only FreeRTOS APIs with the ``ISR`` suffix are called within the function. - -The parameter ``user_data`` of :cpp:func:`mcpwm_capture_channel_register_event_callbacks` function is used to save your context. It is passed to the callback function directly. - -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`. - -Enable and Disable Capture Channel -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The capture channel is not enabled after allocation by :cpp:func:`mcpwm_new_capture_channel`. You should call :cpp:func:`mcpwm_capture_channel_enable` and :cpp:func:`mcpwm_capture_channel_disable` accordingly to enable or disable the channel. If the interrupt service is lazy installed during registering event callbacks for the channel in :cpp:func:`mcpwm_capture_channel_register_event_callbacks`, :cpp:func:`mcpwm_capture_channel_enable` will enable the interrupt service as well. - -Enable and Disable Capture Timer -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Before doing IO control to the capture timer, you need to enable the timer first, by calling :cpp:func:`mcpwm_capture_timer_enable`. Internally, this function: - -* switches the capture timer state from **init** to **enable**. -* acquires a proper power management lock if a specific clock source (e.g., APB clock) is selected. See also :ref:`mcpwm-power-management` for more information. - -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. - -Start and Stop Capture Timer -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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. - -Trigger a Software Capture Event -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sometimes, 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 is a "fake" capture event, it can still cause an interrupt, thus your capture event callback function gets invoked as well. - -Get the Last Captured Value -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If you don't want to process the captured value in the capture event callback function, but want to process it in other places, you can call :cpp:func:`mcpwm_capture_get_latched_value` to get the last captured value. - -.. only:: SOC_MCPWM_SUPPORT_ETM and SOC_ETM_SUPPORTED - - .. _mcpwm-etm-event-and-task: - - ETM Event and Task - ^^^^^^^^^^^^^^^^^^ - - MCPWM timer and comparator can generate events that can be connected to the :doc:`ETM ` module. The event types for the timer and comparator are listed in :cpp:type:`mcpwm_timer_etm_event_type_t` and :cpp:type:`mcpwm_comparator_etm_event_type_t` respectively. You can get the ETM event handle by calling :cpp:func:`mcpwm_timer_new_etm_event` or :cpp:func:`mcpwm_comparator_new_etm_event`. - - For how to connect the MCPWM events to an ETM channel, please refer to the :doc:`ETM ` documentation. - - .. _mcpwm-power-management: - -.. only:: not (SOC_MCPWM_SUPPORT_ETM and SOC_ETM_SUPPORTED) - - .. _mcpwm-power-management: - -Power Management -^^^^^^^^^^^^^^^^ - -When power management is enabled (i.e., :menuitem:`CONFIG_PM_ENABLE` is on), the system will adjust the PLL and APB frequency before going into Light-sleep, thus potentially changing the period of an MCPWM timers' counting step and leading to inaccurate time-keeping. - -However, the driver can prevent the system from going into Light-sleep by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_NO_LIGHT_SLEEP`. Whenever the driver creates an MCPWM timer instance that has selected PLL as its clock source, the driver guarantees that the power management lock is acquired when enabling the timer by :cpp:func:`mcpwm_timer_enable`. On the contrary, the driver releases the lock when :cpp:func:`mcpwm_timer_disable` is called for that timer. - -Likewise, whenever the driver creates an MCPWM capture timer instance, the driver guarantees that the power management lock is acquired when enabling the timer by :cpp:func:`mcpwm_capture_timer_enable`. And releases the lock in :cpp:func:`mcpwm_capture_timer_disable`. - -.. only:: SOC_MCPWM_SUPPORT_SLEEP_RETENTION - - {IDF_TARGET_NAME} supports to retain the MCPWM register context before entering **Light-sleep** and restore them after woke up. Which means you don't have to re-init the MCPWM driver after the **Light-sleep**. - - This feature can be enabled by setting the flag :cpp:member:`mcpwm_timer_config_t::flags::allow_pd` or :cpp:member:`mcpwm_capture_timer_config_t::flags::allow_pd`. It will allow the system to power down the MCPWM in Light-sleep, meanwhile save the MCPWM register context. It can help to save more power consumption with some extra cost of the memory. - -.. _mcpwm-resolution-config: - -Resolution Configuration -^^^^^^^^^^^^^^^^^^^^^^^^ - -The MCPWM group has clock dividers and some sub-modules will have their own clock dividers. The final clock frequency of the sub-module depends on the group clock divider and its own divider (if any). The group clock divider affects all submodules. When configuring the clock frequency (also called resolution) of an MCPWM submodule, the driver sets the divider according to the following rules: - -1. If the clock frequency of the submodule can be divisible by the clock source, the frequency of the submodule is prioritized to ensure the accuracy of the submodule. -2. If it cannot be divisible by the clock source, the group clock is guaranteed to have the highest frequency possible, and the submodule frequency is adjusted to the closest frequency that can be divisible by the clock source. - -When multiple MCPWM submodules coexist, you need to consider whether there is a clock divider conflict. When there is a group clock divider conflict, try adjusting the submodule allocation order. See [`TRM <{IDF_TARGET_TRM_EN_URL}#mcpwm>`__] for details on group frequency divider and submodule frequency divider ranges. - -.. _mcpwm-iram-safe: - -IRAM Safe -^^^^^^^^^ - -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 is a Kconfig option :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE` that: - -* enables the interrupt to be serviced even when the cache is disabled -* places all functions used by the ISR into IRAM [2]_ -* places the driver object into DRAM (in case it is mapped to PSRAM by accident) - -This allows the interrupt to run while the cache is disabled but comes at the cost of increased IRAM consumption. - -There is another Kconfig option :menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` that can put commonly used IO control functions into IRAM as well. So, these functions can also be executable when the cache is disabled. The IO control function is as follows: - -- :cpp:func:`mcpwm_comparator_set_compare_value` -- :cpp:func:`mcpwm_timer_set_period` - - -.. _mcpwm-thread-safety: - -Thread Safety -^^^^^^^^^^^^^ - -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 following function is allowed to run under the ISR context, as the driver uses a critical section to prevent them from being called concurrently in the task and ISR. - -- :cpp:func:`mcpwm_comparator_set_compare_value` -- :cpp:func:`mcpwm_timer_set_period` - -Other functions that are not related to :ref:`mcpwm-resource-allocation-and-initialization`, are not thread-safe. Thus, you should avoid calling them in different tasks without mutex protection. - - -.. _mcpwm-kconfig-options: - -Kconfig Options -^^^^^^^^^^^^^^^ - -- :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE` controls whether the default ISR handler can work when the cache is disabled, see :ref:`mcpwm-iram-safe` for more information. -- :menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` controls where to place the MCPWM control functions (IRAM or flash), see :ref:`mcpwm-iram-safe` for more information. -- :menuitem:`CONFIG_MCPWM_ENABLE_DEBUG_LOG` is used to enable the debug log output. Enabling this option will increase the firmware binary size. - -Application Examples --------------------- - -* :example:`peripherals/mcpwm/mcpwm_bdc_speed_control` demonstrates how to drive a brushed DC motor using two specific PWM signals, measure the motor speed with a photoelectric encoder, and maintain a stable motor speed using a simple PID algorithm. -* :example:`peripherals/mcpwm/mcpwm_bldc_hall_control` demonstrates how to use the MCPWM peripheral to control a BLDC motor using a six-step commutation scheme, with the motor's spin direction and speed being adjusted based on the readings from a Hall sensor. -* :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04` demonstrates how to use the MCPWM peripheral's capture module to decode the pulse width signals from an HC-SR04 sonar sensor, which can measure distance based on the width of the pulse. -* :example:`peripherals/mcpwm/mcpwm_foc_svpwm_open_loop` demonstrates how to use the MCPWM peripheral to generate three pairs of PWM signals for Field-Oriented Control (FOC), which can be used to drive a BLDC or PMSM motor, or a three-phase power inverter, using an open-loop FOC algorithm. -* :example:`peripherals/mcpwm/mcpwm_servo_control` demonstrates how to control a typical RC Servo by sending a PWM signal using the MCPWM driver, with the servo rotating from -60° to 60° and then turning back again. -* :example:`peripherals/mcpwm/mcpwm_sync` demonstrates how to generate three PWMs in perfect synchronization using MCPWM timers, with options to synchronize the timers via GPIO, Timer TEZ, or software. - - -API Reference -------------- - -MCPWM APIs are divided into the following sections: - -* `Timer API`_ -* `Operator API`_ -* `Comparator API`_ -* `Generator API`_ -* `Fault API`_ -* `Sync API`_ -* `Capture API`_ -* `ETM API`_ -* `Driver Types`_ -* `HAL Types`_ - - -Timer API -^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_timer.inc - - -Operator API -^^^^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_oper.inc - - -Comparator API -^^^^^^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_cmpr.inc - - -Generator API -^^^^^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_gen.inc - - -Fault API -^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_fault.inc - - -Sync API -^^^^^^^^ - -.. include-build-file:: inc/mcpwm_sync.inc - - -Capture API -^^^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_cap.inc - - -ETM API -^^^^^^^ - -.. include-build-file:: inc/mcpwm_etm.inc - - -Driver Types -^^^^^^^^^^^^ - -.. include-build-file:: inc/components/esp_driver_mcpwm/include/driver/mcpwm_types.inc - - -HAL Types -^^^^^^^^^ - -.. include-build-file:: inc/components/esp_hal_mcpwm/include/hal/mcpwm_types.inc - - -.. [1] - Different ESP chip series might have a different number of MCPWM resources (e.g., groups, timers, comparators, operators, generators, triggers and so on). Please refer to the [`TRM <{IDF_TARGET_TRM_EN_URL}#mcpwm>`__] for details. The driver does not forbid you from applying for more MCPWM resources, but it returns an error when there are no hardware resources available. Please always check the return value when doing :ref:`mcpwm-resource-allocation-and-initialization`. - -.. [2] - The callback function and the sub-functions invoked by itself should also be placed in IRAM. You need to take care of this by yourself. diff --git a/docs/en/api-reference/peripherals/mcpwm/index.rst b/docs/en/api-reference/peripherals/mcpwm/index.rst new file mode 100644 index 00000000000..c6d899ccbcc --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/index.rst @@ -0,0 +1,228 @@ +============================================ +Motor Control Pulse Width Modulator (MCPWM) +============================================ + +:link_to_translation:`zh_CN:[中文]` + +Start Here +========== + +MCPWM turns a counter into accurately timed output edges. It is a good fit when an LEDC-style PWM is no longer enough: motor bridges need complementary outputs and dead time, inverters need synchronized phases, and sensors need pulse-width measurement. + +The smallest useful MCPWM design has four objects: a :doc:`timer ` supplies time, an :doc:`operator ` owns the waveform resources, a :doc:`comparator ` chooses an edge position, and a :doc:`generator ` drives a GPIO. The other modules extend that design without changing its foundation. + +Build a PWM output +================== + +For a first PWM output, create objects from left to right in the following flow. Each stage of the main path is color-coded by role: time base (blue), operator core (purple), waveform setup (cyan), start and output (green). Amber nodes are optional additions; the red node is the safety brake. Start the timer only after all output actions are configured. + +.. mermaid:: + + flowchart LR + T1["1. Create timer
mcpwm_new_timer"]:::time + O1["2. Create operator
mcpwm_new_operator"]:::core + LINK["3. Connect time base
mcpwm_operator_connect_timer"]:::core + C1["4. Create comparator
mcpwm_new_comparator"]:::wave + G1["5. Create generator
mcpwm_new_generator"]:::wave + A1["6. Describe edges
mcpwm_generator_set_action_on_*_event"]:::wave + RUN["7. Enable and start
mcpwm_timer_enable
mcpwm_timer_start_stop"]:::run + PIN["PWM on GPIO"]:::output + + T1 --> O1 --> LINK --> C1 --> G1 --> A1 --> RUN --> PIN + + DT["Dead time
mcpwm_generator_set_dead_time"]:::optional + BR["Fault and brake
mcpwm_new_*_fault
mcpwm_operator_set_brake_on_fault"]:::safety + SY["Phase synchronization
mcpwm_new_*_sync_src
mcpwm_timer_set_phase_on_sync"]:::optional + CA["Carrier modulation
mcpwm_operator_apply_carrier"]:::optional + + A1 -. extend .-> DT + O1 -. protect .-> BR + T1 -. align .-> SY + O1 -. modulate .-> CA + + classDef time fill:#dbeafe,stroke:#2563eb,color:#172554 + classDef core fill:#ede9fe,stroke:#7c3aed,color:#2e1065 + classDef wave fill:#cffafe,stroke:#0891b2,color:#164e63 + classDef run fill:#dcfce7,stroke:#16a34a,color:#14532d + classDef output fill:#bbf7d0,stroke:#15803d,color:#14532d + classDef optional fill:#fef3c7,stroke:#d97706,color:#78350f + classDef safety fill:#fee2e2,stroke:#dc2626,color:#7f1d1d + +The following code creates one 20 kHz PWM output with a 30% duty cycle. It is meant to be read before the individual pages so a first-time user can see the whole object chain in one place. It also shows the most common runtime adjustment: changing the comparator rather than rebuilding the waveform. + +.. code-block:: c + + mcpwm_timer_handle_t timer = NULL; + mcpwm_oper_handle_t oper = NULL; + mcpwm_cmpr_handle_t comparator = NULL; + mcpwm_gen_handle_t generator = NULL; + + // 1 MHz → 1 tick = 1 µs + // 50 ticks → 50 µs period → 20 kHz + ESP_ERROR_CHECK(mcpwm_new_timer( + &(mcpwm_timer_config_t) { + .group_id = 0, + .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + .period_ticks = 50, + .count_mode = MCPWM_TIMER_COUNT_MODE_UP, + }, + &timer)); + + ESP_ERROR_CHECK(mcpwm_new_operator( + &(mcpwm_operator_config_t) { + .group_id = 0, + }, + &oper)); + ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer)); + + ESP_ERROR_CHECK(mcpwm_new_comparator( + oper, + &(mcpwm_comparator_config_t) { + .flags.update_cmp_on_tez = true, + }, + &comparator)); + // 15 / 50 = 30% duty cycle + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 15)); + + ESP_ERROR_CHECK(mcpwm_new_generator( + oper, + &(mcpwm_generator_config_t) { + .gen_gpio_num = 18, + }, + &generator)); + + // timer empty → output HIGH; compare match → output LOW + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event( + generator, + MCPWM_GEN_TIMER_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, + MCPWM_TIMER_EVENT_EMPTY, + MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, + MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, + comparator, + MCPWM_GEN_ACTION_LOW))); + + ESP_ERROR_CHECK(mcpwm_timer_enable(timer)); + ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP)); + + // Change duty at run time by moving the edge. + // 25 / 50 = 50% duty + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 25)); + +The timer's ``resolution_hz`` and ``period_ticks`` set the timing scale. The comparator's ``compare_value`` chooses an edge in that scale, and the generator action APIs decide the output level at the timer boundary or comparator crossing. This division is useful when tuning: change the timer for frequency, the comparator for duty or edge position, and generator actions for polarity or waveform shape. + +After the waveform is configured, call :cpp:func:`mcpwm_timer_enable()` and :cpp:func:`mcpwm_timer_start_stop()`. At run time, update the comparator with :cpp:func:`mcpwm_comparator_set_compare_value()` rather than rebuilding the generator actions. Use the relevant optional branch only when the application needs it: dead time for a half bridge, fault and brake for a safety path, sync for phase alignment, and carrier for isolated drive. + +Feature map +=========== + +.. list-table:: + :header-rows: 1 + :widths: 18 38 34 24 + + * - Goal + - Read first + - Key APIs + - Typical use + * - One PWM output + - :doc:`timer ` -> :doc:`operator ` -> :doc:`comparator ` + + :doc:`generator ` + - :cpp:func:`mcpwm_new_timer` + + :cpp:func:`mcpwm_new_comparator` + + ``mcpwm_generator_set_action_on_*_event`` + - Servo, dimming, basic control + * - Complementary half-bridge PWM + - dead-time section in :doc:`generator ` + :doc:`fault ` + - :cpp:func:`mcpwm_generator_set_dead_time` + + :cpp:func:`mcpwm_operator_set_brake_on_fault` + - Half bridge, inverter leg + * - Aligned or phase-shifted outputs + - :doc:`sync ` + - :cpp:func:`mcpwm_timer_set_phase_on_sync` + + :cpp:func:`mcpwm_new_timer_sync_src` + - Multi-phase motor, paralleled converters + * - Measure pulse width or period + - :doc:`capture ` + - :cpp:func:`mcpwm_new_capture_timer` + + :cpp:func:`mcpwm_capture_channel_register_event_callbacks` + - HC-SR04, tachometer, RC input + * - Hardware peripheral linking + - :doc:`ETM ` + - :cpp:func:`mcpwm_timer_new_etm_event` + + :cpp:func:`mcpwm_new_event_comparator` + - ADC trigger, timing chains + +Each page in this guide covers one MCPWM module: + +.. toctree:: + :maxdepth: 1 + + mcpwm_timer + mcpwm_operator + mcpwm_cmpr + mcpwm_gen + mcpwm_fault + mcpwm_sync + mcpwm_cap + mcpwm_etm + mcpwm_advanced + +Resource and lifetime rules +=========================== + +All objects belong to an MCPWM group. A timer and the operator connected to it must be in the same group; GPIO fault and GPIO sync sources can likewise be consumed only inside their group. Hardware resources are limited, so creation can return :c:macro:`ESP_ERR_NOT_FOUND`. + +Every object is created by a ``mcpwm_new_*()`` factory that returns an opaque handle, and released with the matching ``mcpwm_del_*()`` function — for example :cpp:func:`mcpwm_new_timer()` and :cpp:func:`mcpwm_del_timer()`. Create parent objects before their children and destroy them in reverse order: generators/comparators first, then their operator, then the timer. A timer must be disabled before it can be deleted. Capture channels must be deleted before their capture timer. + +The group clock divider is shared by timers and, on some targets, capture timers. Allocate objects in monotonic requested-resolution order (high-to-low or low-to-high) to avoid a divider conflict. See :doc:`advanced topics ` for the exact resolution rules. + +Glossary +======== + +.. list:: + + - **TEZ:** Timer equals zero, when the timer count reaches zero. + - **TEP:** Timer equals peak, when the timer count reaches its peak. + - **Timer:** The time base that defines PWM frequency and tick granularity. + - **Operator:** The container between the timer and the outputs; it manages comparators, generators, brake, dead time, and carrier. + - **Comparator:** Emits an event when the count reaches a threshold; often used to place an edge or define duty. + - **Generator:** Drives the GPIO level in response to timer, comparator, fault, or sync events. + - **Dead time:** A non-overlap interval between half-bridge transitions to avoid shoot-through. + - **Fault:** An abnormal condition source, from GPIO or software. + - **Brake:** The output safety policy applied after a fault. + - **CBC:** Cycle-by-cycle braking that recovers automatically at a cycle boundary after the fault clears. + - **OST:** One-shot braking that stays latched until software recovers it. + - **Sync:** Loading a timer to a chosen count and direction on a sync edge. + - **Capture:** Timestamping external input edges to measure pulse width, period, or speed. + +Application examples +==================== + +.. list:: + + - :example:`peripherals/mcpwm/mcpwm_servo_control` — one PWM output for an RC servo. + - :example:`peripherals/mcpwm/mcpwm_bdc_speed_control` — brushed DC motor and speed feedback. + - :example:`peripherals/mcpwm/mcpwm_bldc_hall_control` — BLDC commutation using Hall-sensor feedback. + - :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04` — pulse width measurement with an HC-SR04. + - :example:`peripherals/mcpwm/mcpwm_sync` — GPIO, timer, and software synchronization. + - :example:`peripherals/mcpwm/mcpwm_foc_svpwm_open_loop` — three complementary PWM pairs for open-loop FOC. + +API Reference +============= + +Common types +------------ + +.. include-build-file:: inc/components/esp_driver_mcpwm/include/driver/mcpwm_types.inc +.. include-build-file:: inc/components/esp_hal_mcpwm/include/hal/mcpwm_types.inc diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_advanced.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_advanced.rst new file mode 100644 index 00000000000..3de428e12e4 --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_advanced.rst @@ -0,0 +1,62 @@ +====================== +MCPWM Advanced Topics +====================== + +.. contents:: + :local: + :depth: 2 + +Resolution and shared clocks +============================ + +``resolution_hz`` is the timer tick rate; one tick lasts ``1 / resolution_hz`` seconds. MCPWM selects dividers from the selected source clock. If the requested rate is exactly divisible, it is preferred. Otherwise the driver keeps the group clock as high as possible and selects the closest achievable submodule rate. + +The group divider is shared. On targets with capture clock from the group, PWM and capture timers share it too. When multiple objects need different resolutions, allocate them in one monotonic order (all high-to-low or all low-to-high), rather than allocating an arbitrary mix. + +The clock tree below shows how a single source clock fans out. The group divider is fixed once for the whole group, and each timer only gets its own prescaler on top of it: + +.. mermaid:: + + flowchart LR + src["Source clock
(e.g. 80 MHz)"]:::src -->|"÷ group prescale
shared by the group"| grp["Group clock
(e.g. 40 MHz)"]:::grp + grp -->|"÷ timer prescale"| pwm["PWM timer
resolution_hz = 10 MHz"]:::mod + grp -->|"÷ capture prescale"| cap["Capture timer
resolution_hz = 20 MHz"]:::mod + classDef src fill:#fef3c7,stroke:#d97706,color:#78350f + classDef grp fill:#dbeafe,stroke:#2563eb,color:#172554 + classDef mod fill:#dcfce7,stroke:#16a34a,color:#14532d + +Because the group prescale is shared, it is chosen once for all timers. A later request for a different resolution cannot move that fixed divider, so the driver keeps the group clock as high as possible and picks the closest achievable prescale for the new submodule instead. + +Power management and sleep +========================== + +With power management enabled, :cpp:func:`mcpwm_timer_enable()` and :cpp:func:`mcpwm_capture_timer_enable()` hold an :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP` lock so the timer keeps a stable clock frequency. Call :cpp:func:`mcpwm_timer_disable()` or :cpp:func:`mcpwm_capture_timer_disable()` to release the lock. + +Both the timer config and the capture timer config carry an :cpp:member:`allow_pd ` field (the latter in :cpp:type:`mcpwm_capture_timer_config_t`). Set it if the application permits MCPWM's power domain to power down during sleep. The driver then saves and restores registers, at the cost of extra RAM. This capability is target dependent. + +ISR and task safety +=================== + +Callbacks for timer, comparator, fault, operator brake, and capture run in ISR context. Keep them non-blocking and use ISR-safe RTOS calls only. The first callback registered in a group fixes its shared interrupt priority; make all later event users in that group use the same priority. + +Factory functions such as :cpp:func:`mcpwm_new_timer()` are thread-safe. :cpp:func:`mcpwm_timer_set_period()` and :cpp:func:`mcpwm_comparator_set_compare_value()` may run from ISR context. Other control APIs are not generally thread-safe; serialize them if more than one task can access the same object. + +Cache-safe real-time operation +============================== + +Normally, MCPWM interrupts are deferred while cache is disabled (for example during flash operations). Enable :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE` when callbacks must continue to run; this places ISR-required code in IRAM and objects in DRAM, increasing internal RAM use. + +.. note:: + + With this option enabled, MCPWM interrupts still fire immediately and are not deferred even while cache is disabled. However, every function on the interrupt path — including your registered callback and all functions it calls — must live in IRAM: with cache off, the CPU cannot fetch instructions from flash, so calling any function still resident in flash crashes the CPU. The option only moves the driver's own ISR code to IRAM; you must place your callbacks and the functions they call in IRAM explicitly (for example with ``IRAM_ATTR``). + +:menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` additionally places :cpp:func:`mcpwm_timer_set_period()` and :cpp:func:`mcpwm_comparator_set_compare_value()` in IRAM, so those calls keep working even while cache is off — for example you can retune the PWM period or duty from a cache-disabled context, such as inside a flash-writing routine, without waiting for the cache to be re-enabled. + +Kconfig options +=============== + +.. list:: + + - :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE` enables cache-safe interrupts. + - :menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` moves selected control functions to IRAM. + - :menuitem:`CONFIG_MCPWM_ENABLE_DEBUG_LOG` forces the MCPWM driver to compile in and print its own debug logs, ignoring the global log settings, and raises the runtime log level to verbose for the driver only — other modules are unaffected. The price is increased firmware size. diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_cap.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_cap.rst new file mode 100644 index 00000000000..4632d155fd8 --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_cap.rst @@ -0,0 +1,148 @@ +====================================== +MCPWM Capture: Measure an Input Pulse +====================================== + +.. contents:: + :local: + :depth: 2 + +Capture is an independent MCPWM path: a capture timer timestamps edges on capture-channel GPIOs. It does not require a PWM timer, operator, comparator, or generator. This makes it ideal for echo pulses, tachometers, Hall sensors, and RC receiver signals. + +It is the MCPWM path for bringing external timing into the chip. Use it when the problem is pulse width, period, phase, or speed rather than PWM generation. + +Measure a pulse width +===================== + +Configure both edges, save the rising timestamp, and subtract it from the falling timestamp. With a 1 MHz capture resolution, the difference is directly in microseconds. + +.. code-block:: c + + mcpwm_cap_timer_handle_t cap_timer = NULL; + mcpwm_cap_channel_handle_t cap_channel = NULL; + ESP_ERROR_CHECK(mcpwm_new_capture_timer( + &(mcpwm_capture_timer_config_t) { + .group_id = 0, + .clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + }, &cap_timer)); + ESP_ERROR_CHECK(mcpwm_new_capture_channel(cap_timer, + &(mcpwm_capture_channel_config_t) { + .gpio_num = 6, + .prescale = 1, + .flags.pos_edge = true, + .flags.neg_edge = true, + }, &cap_channel)); + +Allocation alone does not start measurement. Arm the channel and run the capture timer: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_capture_channel_enable(cap_channel)); + ESP_ERROR_CHECK(mcpwm_capture_timer_enable(cap_timer)); + ESP_ERROR_CHECK(mcpwm_capture_timer_start(cap_timer)); + +:cpp:func:`mcpwm_capture_channel_enable()` and :cpp:func:`mcpwm_capture_timer_enable()` set up the system services the capture needs; neither starts the measurement yet. :cpp:func:`mcpwm_capture_timer_start()` finally makes the counter run, so edges start being timestamped. + +The captured edge values reach the application through a callback, described in the next section. + +.. figure:: /../_static/mcpwm/capture_measurement.svg + :align: center + :alt: Capture timestamps the rising and falling edges; subtracting them yields the high-pulse width. + + Capture the rising and falling edge timestamps, then subtract to get the high-pulse width. + +The two configuration structs are worth reading separately: + +Capture timer configuration +--------------------------- + +.. list:: + + - :cpp:member:`group_id ` — the MCPWM group the capture timer is allocated from. + - :cpp:member:`clk_src ` — the clock feeding the capture timer. :c:macro:`MCPWM_CAPTURE_CLK_SRC_DEFAULT` is right for most applications. Pick a specific source when the default one may be gated — for example, in low-power scenarios where a clock that can be switched off would stop the capture timer and corrupt your timestamps. + - :cpp:member:`resolution_hz ` — the tick rate of the capture timer. One tick lasts ``1 / resolution_hz`` seconds, so 1 MHz gives microsecond resolution. It directly sets the precision of every captured timestamp. + - :cpp:member:`allow_pd ` — lets the MCPWM power domain switch off during sleep, backing up and restoring the capture registers around the sleep transition at the cost of extra RAM. + +Capture channel configuration +----------------------------- + +.. list:: + + - :cpp:member:`gpio_num ` — the GPIO carrying the input signal. + - :cpp:member:`prescale ` — divides the input signal before capture; the effective input frequency is the capture clock divided by ``prescale``. Raise it to extend the measurable period range, at the cost of time resolution. + - :cpp:member:`pos_edge ` and :cpp:member:`neg_edge ` — which edges are captured. The example captures both, which is what a pulse-width measurement needs. + - :cpp:member:`invert_cap_signal ` — inverts the input signal before capture, so a logical ``1`` on the pin is seen as ``0`` by the capture peripheral and vice versa. + - :cpp:member:`intr_priority ` — the interrupt priority used by the capture callbacks. Not setting it (``0``) lets the driver choose a low priority. + +.. note:: + + The capture driver configures the GPIO as an input but does not set any pull-up or pull-down resistor. If the input signal is not actively driven to both levels, call :cpp:func:`gpio_set_pull_mode()` to select the pull direction that keeps the pin at the level you expect when the line is idle. + +Capture event callbacks +======================= + +The event data tells you the edge and latched count. The calculation below leaves heavy work to a task in a real application. + +.. code-block:: c + + static uint32_t rise_tick; + static bool IRAM_ATTR on_capture(mcpwm_cap_channel_handle_t channel, + const mcpwm_capture_event_data_t *edata, + void *user_data) + { + if (edata->cap_edge == MCPWM_CAP_EDGE_POS) { + rise_tick = edata->cap_value; + } else { + uint32_t width_ticks = edata->cap_value - rise_tick; + // Notify a task with width_ticks; do not printf here. + } + return false; + } + + ESP_ERROR_CHECK(mcpwm_capture_channel_register_event_callbacks(cap_channel, + &(mcpwm_capture_event_callbacks_t) { .on_cap = on_capture }, NULL)); + +Obtain the actual resolution with :cpp:func:`mcpwm_capture_timer_get_resolution()` before converting ticks to time. On targets where capture shares the MCPWM group clock, create capture and PWM timers in a consistent requested-resolution order. + +To measure speed or period, record two timestamps of the same edge type, subtract them to get period ticks, then convert that value to frequency or RPM with the actual capture resolution. + +Useful controls +=============== + +:cpp:func:`mcpwm_capture_channel_trigger_soft_catch()` generates a software capture event, commonly used for testing but also handy to land the timing of important software events on the same capture timebase as hardware edges; it invokes the callback as well. :cpp:func:`mcpwm_capture_get_latched_value()` reads the latest timestamp without registering any callback. + +:cpp:func:`mcpwm_capture_timer_stop()` halts the counter, :cpp:func:`mcpwm_capture_channel_disable()` gates an individual input, and stopping the timer gates the whole measurement engine. Call :cpp:func:`mcpwm_capture_timer_disable()` to undo the setup done by :cpp:func:`mcpwm_capture_timer_enable()` before deleting the objects. + +Capture timer synchronization +============================= + +The capture timer free-runs by default, so the zero point of its count is arbitrary and timestamps can only be compared with each other. Synchronization makes the running capture timer load a given count value when a sync edge arrives, anchoring the timestamps to a meaningful reference. + +The most common use is aligning the capture timer with a PWM timer: use the sync emitted by the PWM timer at each period zero (TEZ) as the source and set the count value to 0, so the capture timer restarts from zero every period. A captured timestamp then directly represents the phase within the PWM period. This matters in motor control and power conversion, where feedback edges from a Hall sensor, encoder, or current sense are only meaningful at a specific phase of the PWM cycle. + +Sync sources are shared with the PWM timers (GPIO, software, or timer — all in the same MCPWM group). Configure the receiving side with :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()`: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_capture_timer_set_phase_on_sync(cap_timer, + &(mcpwm_capture_timer_sync_phase_config_t) { + .sync_src = timer_a_sync, // created with mcpwm_new_timer_sync_src() + .count_value = 0, + .direction = MCPWM_TIMER_DIRECTION_UP, + })); + +.. list:: + + - :cpp:member:`sync_src ` — the sync source; pass ``NULL`` to detach synchronization. + - :cpp:member:`count_value ` — the count loaded when the sync edge arrives. + - :cpp:member:`direction ` — the counting direction after loading; the capture timer only counts up, so it is always :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP`. + +Software and GPIO sync sources can also give the capture timer a known origin or align it to an external reference. See :doc:`synchronization ` for how to create the sync sources and other details. + +API Reference +============= + +MCPWM Capture Driver Functions +------------------------------ + +.. include-build-file:: inc/mcpwm_cap.inc diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_cmpr.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_cmpr.rst new file mode 100644 index 00000000000..56838a6fc31 --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_cmpr.rst @@ -0,0 +1,116 @@ +============================================ +MCPWM Comparator: Turn a Ratio into an Edge +============================================ + +.. contents:: + :local: + :depth: 2 + +A comparator emits an event when the timer count reaches ``cmp_ticks``. A generator converts that event into a GPIO transition. In the usual up-counting PWM arrangement, the comparator value is the high-time in ticks. + +In practice, a comparator is how you turn "I want the edge earlier, later, narrower, or wider" into hardware timing. Runtime duty control is usually nothing more than changing the comparator threshold. + +Set a 30% duty cycle +===================== + +Create a comparator from an existing operator, then set its threshold. With the 50-tick timer from the :doc:`timer page `, a value of 15 represents 30% duty. The waveform below shows the compare event at tick 15 — the generator can use this to end the high pulse. + +.. figure:: /../_static/mcpwm/compare_event.svg + :align: center + :alt: Timer counts up; the comparator fires at tick 15. The generator turns this into a falling edge. + + Timer counts up; the comparator fires at tick 15. The generator turns this into a falling edge. + +.. code-block:: c + + mcpwm_cmpr_handle_t comparator = NULL; + mcpwm_comparator_config_t comparator_config = { + .flags.update_cmp_on_tez = true, // Change duty only at cycle boundary + }; + ESP_ERROR_CHECK(mcpwm_new_comparator(oper, &comparator_config, &comparator)); + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 15)); + +The comparator configuration has only one field besides the flags: + +.. list:: + + - :cpp:member:`intr_priority ` — the interrupt priority used by the :cpp:member:`on_reach ` callback. Not setting it (``0``) lets the driver choose a low priority; raise it when the callback must preempt other ISRs. + - :cpp:member:`flags ` — the update points explained below. The example enables :cpp:member:`update_cmp_on_tez `, which is the usual choice for changing duty at the cycle boundary. + +For a runtime duty request in percent, calculate ``period_ticks * percent / 100``. Keep the result within the timer period. + +This is why duty updates normally change the comparator rather than the generator actions: actions describe the waveform rule, while the comparator is the runtime edge position. + +Why buffer the update? +====================== + +Updating a comparator immediately can move an edge in the middle of the active cycle. :cpp:member:`update_cmp_on_tez ` buffers it until the counter reaches zero, :cpp:member:`update_cmp_on_tep ` until it reaches the peak, and :cpp:member:`update_cmp_on_sync ` until a sync event. In up-counting or down-counting mode the peak coincides with the cycle boundary, so ``tez`` and ``tep`` select almost the same update point; only in up-down mode does the peak sit at the midpoint of the cycle, making ``tez`` and ``tep`` two distinct update points. The buffered choice is usually the right one for motors and power conversion. + +Two comparators for pulse placement +==================================== + +A single comparator gives one edge per cycle. With two comparators in the same operator, you can place a pulse anywhere inside the period — one comparator opens the pulse and the other closes it. This is useful for sampling windows, trigger signals, or asymmetric dead-time compensation. + +That pattern appears often in motor control, for example when an ADC sample window should sit away from switching noise, or when an external device needs a timing pulse that is not tied to the PWM boundary. + +.. code-block:: c + + mcpwm_cmpr_handle_t cmp_a, cmp_b; + mcpwm_new_comparator(oper, &comparator_config, &cmp_a); + mcpwm_new_comparator(oper, &comparator_config, &cmp_b); + mcpwm_comparator_set_compare_value(cmp_a, 10); + mcpwm_comparator_set_compare_value(cmp_b, 30); + +Use compare events as a timing marker +===================================== + +The :cpp:member:`on_reach ` callback fires when the compare value is reached. This is useful when software must observe a precise point in the PWM cycle. Register it before starting time-critical work. The callback runs in ISR context, so keep it short. + +.. code-block:: c + + static bool IRAM_ATTR on_compare(mcpwm_cmpr_handle_t cmpr, + const mcpwm_compare_event_data_t *edata, + void *user_ctx) + { + // Signal a task or trigger only ISR-safe work. + return false; + } + + mcpwm_comparator_event_callbacks_t callbacks = { .on_reach = on_compare }; + ESP_ERROR_CHECK(mcpwm_comparator_register_event_callbacks(comparator, + &callbacks, NULL)); + +Comparator kinds +================ + +The **operator comparator**, created with :cpp:func:`mcpwm_new_comparator()`, drives the generators so it can shape the PWM output. + +.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR + + The **event comparator**, created with :cpp:func:`mcpwm_new_event_comparator()`. Its compare event only reaches other peripherals through :doc:`ETM `; it never drives a generator and does not affect the PWM output. + + .. note:: + + The name is the trap: the operator comparator can also produce ETM events, so the event comparator is not the only way to link MCPWM to ETM. The event comparator exists to *supplement* the operator comparator, not to replace it. An event comparator consumes none of the operator-comparator slots and never moves a PWM edge, which makes it the flexible choice when you need an extra compare point purely as a timing marker — for example, to find a sampling window for an ADC trigger that must not disturb the PWM waveform. + + Both types accept the same compare value and ETM event setup: + + .. code-block:: c + + mcpwm_event_comparator_config_t evt_cmp_cfg = {}; + mcpwm_cmpr_handle_t evt_cmpr = NULL; + ESP_ERROR_CHECK(mcpwm_new_event_comparator(oper, &evt_cmp_cfg, &evt_cmpr)); + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(evt_cmpr, 25)); + + esp_etm_event_handle_t evt = NULL; + ESP_ERROR_CHECK(mcpwm_comparator_new_etm_event(evt_cmpr, + &(mcpwm_cmpr_etm_event_config_t){ .event_type = MCPWM_CMPR_ETM_EVENT_EQUAL }, + &evt)); + +API Reference +============= + +MCPWM Comparator Driver Functions +---------------------------------- + +.. include-build-file:: inc/mcpwm_cmpr.inc diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_etm.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_etm.rst new file mode 100644 index 00000000000..e5a3b5d2e58 --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_etm.rst @@ -0,0 +1,66 @@ +=============================================== +MCPWM ETM: Hardware Linking Between Peripherals +=============================================== + +.. contents:: + :local: + :depth: 2 + +.. only:: SOC_MCPWM_SUPPORT_ETM + + The Event Task Matrix (ETM) can route an MCPWM timer or comparator event directly to an ETM task, avoiding ISR latency. Use it when another peripheral must respond at an exact PWM phase. + + Create an ETM event from the timer or comparator, create a compatible task from the destination peripheral, then connect both with an ETM channel. The destination driver's documentation defines its task and the complete channel setup. For the general ETM workflow — allocating a channel and connecting an event to a task — see the :doc:`ETM ` documentation. + + .. mermaid:: + + flowchart LR + T["MCPWM Timer
TEZ/TEP event"]:::source --> E["ETM Channel"]:::route + C["MCPWM Comparator
compare event"]:::source --> E + E --> D["Destination
peripheral task"]:::dest + classDef source fill:#dbeafe,stroke:#2563eb,color:#172554 + classDef route fill:#ede9fe,stroke:#7c3aed,color:#2e1065 + classDef dest fill:#dcfce7,stroke:#16a34a,color:#14532d + + A timer emits a `TEZ` (timer reaches zero) or `TEP` (timer reaches peak) event. To get one: + + .. code-block:: c + + esp_etm_event_handle_t timer_event = NULL; + ESP_ERROR_CHECK(mcpwm_timer_new_etm_event(timer, + &(mcpwm_timer_etm_event_config_t) { + .event_type = MCPWM_TIMER_ETM_EVENT_TEZ, + }, &timer_event)); + // Create a destination ETM task, allocate a channel, then connect: + // esp_etm_channel_connect(channel, timer_event, destination_task); + + A comparator provides an `EQUAL` event, firing each time the timer count equals the comparator value. This pins the event to an arbitrary phase of the PWM period rather than just the crest or trough. To get one: + + .. code-block:: c + + esp_etm_event_handle_t cmp_event = NULL; + ESP_ERROR_CHECK(mcpwm_comparator_new_etm_event(cmp, + &(mcpwm_cmpr_etm_event_config_t) { + .event_type = MCPWM_CMPR_ETM_EVENT_EQUAL, + }, &cmp_event)); + // esp_etm_channel_connect(channel, cmp_event, destination_task); + + Release the event with :cpp:func:`esp_etm_del_event()` when no longer needed. + + A very common use of a comparator event is to trigger an ADC sampling: place the comparator at the phase you want to sample, then have the comparator event start an ADC so the converter samples a clean, settled waveform exactly in phase with the PWM cycle. Because the whole link is done in hardware, the ADC sample point tracks the PWM with no CPU and no ISR latency. + + .. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR + + Which comparator feeds the event matters. The **operator comparator** (:cpp:func:`mcpwm_new_comparator()`) also drives the generators, so its compare value defines an actual PWM output edge, and its ETM event is limited to that edge — you cannot ask it for a phase that is not one of the edges it produces. The **event comparator** (:cpp:func:`mcpwm_new_event_comparator()`) is a dedicated ETM timing marker: it drives no generator, consumes none of the operator-comparator slots, and therefore can fire at *any* phase inside the PWM period, with no effect on the PWM waveform. That freedom is exactly what an ADC trigger needs, so the event comparator is the recommended source — pick a sample point where the voltage has settled, not just where an edge happens to be. + + API Reference + ============= + + MCPWM ETM Driver Functions + -------------------------- + + .. include-build-file:: inc/mcpwm_etm.inc + +.. only:: not SOC_MCPWM_SUPPORT_ETM + + {IDF_TARGET_NAME} does not support MCPWM ETM events. diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_fault.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_fault.rst new file mode 100644 index 00000000000..0ccee7411f3 --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_fault.rst @@ -0,0 +1,84 @@ +================================================== +MCPWM Fault: Bring a Protection Signal into MCPWM +================================================== + +.. contents:: + :local: + :depth: 2 + +A fault object represents an abnormal condition. A GPIO fault is for a hardware signal such as an over-current comparator; a software fault lets application logic trigger the same protection route. Use the object with the operator :ref:`brake ` to define the output response. + +The purpose of the fault path is to make protection depend as little as possible on software polling or task scheduling. In motor drives and power converters, over-current, interlock, or emergency-stop conditions usually need hardware to force a safe output state first, then let software decide how to log and recover. + +Create an active-low GPIO fault +=============================== + +Create the fault in the same group as the operator that will consume it. The pin's electrical pull configuration is separate GPIO setup, so make the inactive level unambiguous before starting the power stage. The MCPWM driver does not enable internal pull resistors for a GPIO fault pin; if the fault signal does not drive the pin in the inactive state, configure the pull direction yourself with :cpp:func:`gpio_set_pull_mode()`. + +.. code-block:: c + + mcpwm_fault_handle_t fault = NULL; + mcpwm_gpio_fault_config_t fault_config = { + .group_id = 0, + .gpio_num = 4, + .flags.active_level = 0, + }; + ESP_ERROR_CHECK(mcpwm_new_gpio_fault(&fault_config, &fault)); + +The GPIO fault configuration has a few fields to consider: + +.. list:: + + - :cpp:member:`group_id ` — the MCPWM group the fault belongs to. It must match the group of the operator consuming the fault. + - :cpp:member:`gpio_num ` — the GPIO carrying the fault signal. + - :cpp:member:`active_level ` — the level treated as active. The example uses ``0``, so the fault is active low; the pin's pull direction must keep it inactive (high) when nothing asserts the fault. The driver leaves the pad's pull configuration untouched, so call :cpp:func:`gpio_set_pull_mode()` to select the pull-up/pull-down as appropriate. + - :cpp:member:`intr_priority ` — the interrupt priority used by the fault event callbacks. Not setting it (``0``) lets the driver choose a low priority. + +Create a software fault +======================= + +For an application-detected condition, create :cpp:func:`mcpwm_new_soft_fault()` and invoke :cpp:func:`mcpwm_soft_fault_activate()` when the condition occurs, instead of wiring a GPIO fault pin. The activation is a one-time fault event; its output policy is still configured by the operator :ref:`brake ` mechanism. + +.. note:: + + Bind the soft fault to an operator with :cpp:func:`mcpwm_operator_set_brake_on_fault()` before activating it. The driver does not attach the soft fault to an operator at :cpp:func:`mcpwm_new_soft_fault()` time; the operator association and its brake mode are set by the bind call, and a soft fault can be bound to only one operator. Calling :cpp:func:`mcpwm_soft_fault_activate()` before binding is undefined behavior. + +Fault as a trigger for generator actions +========================================= + +A GPIO fault can also directly trigger a generator action via :cpp:func:`mcpwm_generator_set_action_on_fault_event()`. This is a local edge-level response — it changes the output at the fault edge but does not latch a safe state. For persistent braking with recovery, use the operator :ref:`brake mechanism `. + +.. list-table:: + :header-rows: 1 + :widths: 22 34 28 + + * - Mechanism + - Behavior + - Best fit + * - Generator fault action + - Immediately changes one output at the fault edge + - Local fast reaction on a single output + * - Operator brake + - Defines safe state, latching, and recovery policy + - Primary protection path for a power stage + +Fault event callbacks +===================== + +The :cpp:member:`on_fault_enter ` and :cpp:member:`on_fault_exit ` callbacks report GPIO fault transitions and are only available for GPIO faults — the driver rejects registering them on a soft fault. Soft faults trigger the brake immediately in hardware, with no callback. The callbacks run in ISR context. Timestamp the event or notify a task with an ISR-safe primitive, then make logging and recovery decisions in the task. + +.. code-block:: c + + mcpwm_fault_event_callbacks_t cbs = { + .on_fault_enter = my_fault_enter_cb, + .on_fault_exit = my_fault_exit_cb, + }; + ESP_ERROR_CHECK(mcpwm_fault_register_event_callbacks(fault, &cbs, NULL)); + +API Reference +============= + +MCPWM Fault Driver Functions +---------------------------- + +.. include-build-file:: inc/mcpwm_fault.inc diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_gen.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_gen.rst new file mode 100644 index 00000000000..c44bd8137f7 --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_gen.rst @@ -0,0 +1,327 @@ +========================================= +MCPWM Generator: Create the PWM Waveform +========================================= + +.. contents:: + :local: + :depth: 2 + +The generator is the final digital output. It does not have a fixed duty-cycle setting; instead, you teach it what level to drive at timer and comparator events. This makes simple PWM easy and leaves room for asymmetric, complementary, and phase-sensitive waveforms. + +This is one of the biggest differences from a simpler PWM peripheral: MCPWM does not just ask for frequency and duty, it lets you describe what should happen at each important event. That adds concepts up front, but gives much tighter control over waveform structure. + +Your first PWM output +===================== + +This is the completion of the :doc:`timer `/:doc:`operator `/:doc:`comparator ` setup in the preceding pages. At timer zero, drive the GPIO high. When the comparator reaches 15, drive it low. With a 50-tick period, the output is high for 15 ticks (30%). + +Application: basic single-output PWM +------------------------------------ + +Use this for the simplest single-output PWM cases, such as an RC servo control signal, LED dimming, or a basic duty-controlled output where polarity and protection are already handled elsewhere. + +.. code-block:: c + + mcpwm_gen_handle_t generator = NULL; + mcpwm_generator_config_t gen_config = { .gen_gpio_num = 18 }; + ESP_ERROR_CHECK(mcpwm_new_generator(oper, &gen_config, &generator)); + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event( + generator, MCPWM_GEN_TIMER_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, + MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_LOW))); + +.. figure:: /../_static/mcpwm/single_edge_asym_active_high.svg + :align: center + :alt: Up-counting, active-high PWM: set high at zero and low at compare. + + Up-counting, active-high PWM: set high at zero and low at compare. + +The generator configuration is small: + +.. list:: + + - :cpp:member:`gen_gpio_num ` — the GPIO that carries the PWM output. A second generator in the same operator, configured with the same actions, drives a second pin from the same time base. + - :cpp:member:`invert_pwm ` — inverts the PWM signal through the GPIO matrix. This is a hardware inversion of the final output, distinct from changing the actions; choose one of the two, not both. + +Action configuration +==================== + +The configuration names say exactly what happens: :cpp:enumerator:`MCPWM_GEN_ACTION_HIGH `, :cpp:enumerator:`MCPWM_GEN_ACTION_LOW `, or :cpp:enumerator:`MCPWM_GEN_ACTION_TOGGLE ` at a particular event. The helper macros make the three important choices visible at the call site — direction, event source, and output level. + +For an up-counting timer, :cpp:enumerator:`MCPWM_TIMER_EVENT_EMPTY ` is the zero boundary and :cpp:enumerator:`MCPWM_TIMER_EVENT_FULL ` fires at the timer peak. In up-counting mode the peak equals the period, so ``FULL`` lands on the period boundary; in up-down mode the peak is ``period_ticks / 2``, so ``FULL`` lands in the middle of the cycle. A compare action uses the comparator's threshold. The first example therefore means "start the cycle high; end the active part when the count reaches 15." A compare value outside the timer range never produces its event. + +Every action must specify a timer direction, even though the choice only makes a visible difference in up-down mode. In up-counting mode the counter only runs upward, so the action configured for :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP ` is the one that fires — you still have to write it explicitly. In up-down mode, configure actions for both :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP ` and :cpp:enumerator:`MCPWM_TIMER_DIRECTION_DOWN ` when both edges matter. This is what turns one comparator into a center-aligned PWM. + +Classic Waveform Examples +========================= + +The examples below build on the first PWM output from the previous section, reusing the same timer, operator, and comparator objects to create other common waveforms. + +Invert the active polarity +-------------------------- + +Some gate drivers and LEDs are active low, such as a low-active gate-driver enable, an inverted LED path, or a board-level interface that is already inverted. Instead of adding GPIO inversion, set low at the period boundary and high at the comparator: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event( + generator, MCPWM_GEN_TIMER_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_HIGH))); + +.. figure:: /../_static/mcpwm/single_edge_asym_active_low.svg + :align: center + :alt: Up-counting, active-low PWM. Change the actions, rather than the wiring, when the output polarity is part of the design. + + Up-counting, active-low PWM. Change the actions, rather than the wiring, when the output polarity is part of the design. + +Place a pulse inside the period +------------------------------- + +When a short pulse must sit at a controlled position inside the cycle — an ADC sample window, a peripheral trigger pulse, or a latch strobe — two compare values choose its opening and closing edges: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator_a, MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator_b, MCPWM_GEN_ACTION_LOW))); + +.. figure:: /../_static/mcpwm/pulse_placement_asym.svg + :align: center + :alt: Pulse placement: the distance between the two compare values is the pulse width. + + Pulse placement: the distance between the two compare values is the pulse width. + +Set ``comparator_a`` below ``comparator_b``. Moving both by the same tick offset changes the position without changing width; moving only one changes width. Hardware places both edges, so this is more precise than a timer callback. + +Two-edge asymmetric PWM +----------------------- + +When several edges must be placed independently within one cycle and the active interval does not need to stay symmetric around the center — for example in certain asymmetric inverter modulation or custom gate-drive timing — use two generators and two comparators. Each generator has its own edge per cycle, so the high time splits across the period boundary: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + gen_a, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, cmp_a, MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + gen_a, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, cmp_b, MCPWM_GEN_ACTION_LOW))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + gen_b, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, cmp_a, MCPWM_GEN_ACTION_LOW))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + gen_b, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, cmp_b, MCPWM_GEN_ACTION_HIGH))); + +.. figure:: /../_static/mcpwm/dual_edge_asym_active_low.svg + :align: center + :alt: Dual-edge asymmetric (edge-aligned) PWM: two generators produce complementary outputs with two edges per cycle. + + Dual-edge asymmetric (edge-aligned) PWM: two generators produce complementary outputs with two edges per cycle. + +Center-aligned PWM +------------------ + +Motor drives, inverters, and power stages that care about harmonic behavior often prefer center-aligned PWM because it gives more symmetric switching and lower harmonic distortion. Select ``MCPWM_TIMER_COUNT_MODE_UP_DOWN`` when creating the timer, then use the same threshold in both directions: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_DOWN, comparator, MCPWM_GEN_ACTION_LOW))); + +.. figure:: /../_static/mcpwm/dual_edge_sym_active_low.svg + :align: center + :alt: Center-aligned PWM: the up-count and down-count actions create matching edges around the period center. + + Center-aligned PWM: the up-count and down-count actions create matching edges around the period center. + +The timer reaches its peak and returns to zero in each complete up-down cycle, so account for both legs when calculating frequency. Adding a second generator with opposite actions creates complementary *logical* outputs: + +.. figure:: /../_static/mcpwm/dual_edge_sym_complementary.svg + :align: center + :alt: Complementary generator actions have no dead time by themselves; do not connect them directly to a power stage. + + Complementary generator actions have no dead time by themselves; do not connect them directly to a power stage. + +.. warning:: + + Logical complementary outputs are not yet safe half-bridge outputs. If the high-side and low-side devices have finite turn-off delay, add dead time and verify non-overlap at the actual gate pins. + +Duty updates +============ + +Change duty by setting the comparator threshold, not the generator actions. The threshold is expressed in timer ticks: for an up-counting active-high waveform, ``compare_value / period_ticks`` is the duty ratio. Choose a timer resolution high enough that one tick gives the adjustment granularity the application needs. + +.. code-block:: c + + mcpwm_comparator_set_compare_value(comparator, 25); // 50 % + +Forced levels +============= + +Adjusting the comparator threshold changes the normal duty. When you instead need to temporarily take over the output, ignore all event actions, and hold a fixed level, use the force-level API. :func:`mcpwm_generator_set_force_level` takes a ``level`` and a ``hold_on`` flag: + +- ``level`` (second parameter) is the raw generator level to force: ``0`` or ``1`` overrides all event actions, while ``-1`` releases the force and returns control to event actions. +- ``hold_on`` (third parameter) decides how long the forced level lasts: ``true`` holds it until another call releases it, whereas ``false`` lets the next event action override it. + +For example, ``mcpwm_generator_set_force_level(generator, 0, true)`` overrides all event actions and holds the raw generator low. Force acts before dead time and GPIO inversion, so confirm the physical pin level with a scope when using those features. + +Force level is useful for power-up checks, a temporary post-fault safe output, or a mode-transition state. It is not a long-term replacement for a proper PWM configuration. + +For a half bridge, add a second generator and configure the :ref:`dead-time module ` to produce non-overlapping complementary outputs. + +.. _mcpwm-dead-time: + +Dead time and half-bridge drive +=============================== + +Dead time delays an output edge, leaving a short interval in which both switches in a half bridge are off. It compensates for transistor turn-off delay and helps prevent shoot-through. Configure and verify it before connecting a power stage. + +A half bridge drives a load from a DC bus through a high-side and a low-side switch. Both switches are usually N-channel MOSFETs: the low-side source sits at ground and is easy to drive, while the high-side source swings with the output, so its gate needs a drive voltage above the bus voltage. The MCPWM outputs are 3.3 V logic and cannot drive the gates directly. For example, an IRS2101 uses a separate low-voltage driver supply (VCC, typically 10-20 V), with COM connected to power ground. Its bootstrap diode should be connected from VCC to VB, and the external bootstrap capacitor between VB and VS; VS must be connected to the OUT switch node. The high-side output is HO and the low-side output is LO, and both drive their MOSFET gates through gate resistors. VCC is not the high-voltage DC bus: the bootstrap diode charges the bootstrap capacitor from the regulated driver supply while the low-side switch is on. If the two switches were turned on and off simultaneously, the switch that is still turning off would overlap the one already turning on, shorting the bus to ground through both switches. Dead time inserts a both-off gap so the next switch turns on only after the previous one has fully turned off: + +.. figure:: /../_static/mcpwm/half_bridge_dead_time.svg + :align: center + :alt: Half-bridge gate-driver circuit with bootstrap supply and dead-time comparison. + + Half-bridge gate-driver circuit with bootstrap supply and dead-time comparison. + +Create complementary outputs +---------------------------- + +Create two generators in one operator. Feed generator A into its own output with a rising-edge delay, then feed it into generator B with a falling-edge delay and inversion. + +.. note:: + + Here, generator A is the first generator allocated from the operator handle, and generator B is the second. + +.. code-block:: c + + mcpwm_dead_time_config_t dead_time = { .posedge_delay_ticks = 2 }; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_a, &dead_time)); + dead_time = (mcpwm_dead_time_config_t) { + .negedge_delay_ticks = 2, + .flags.invert_output = true, + }; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_b, &dead_time)); + +.. figure:: /../_static/mcpwm/deadtime_active_high_complementary.svg + :align: center + :alt: Complementary PWM with a dead-time interval between the switch transitions. + + Complementary PWM with a dead-time interval between the switch transitions. + +Understanding the routing and parameters +---------------------------------------- + +:func:`mcpwm_generator_set_dead_time(in_generator, out_generator, config) ` treats dead time as a small signal-processing stage. Passing the same generator for both handles changes that output in place. Passing ``gen_a`` as input and ``gen_b`` as output derives B from A, which is how the complementary example shares one PWM source. + +:cpp:member:`posedge_delay_ticks ` delays a rising edge and :cpp:member:`negedge_delay_ticks ` delays a falling edge. Ticks use the connected timer's resolution, so a 2-tick setting at 10 MHz is 200 ns. The diagram below shows the basic effect: the rising edge of ``pwm_A`` is delayed (RED) and the falling edge of ``pwm_B`` is delayed (FED) relative to the original signal. Start with the maximum turn-off delay from the switch and gate-driver data sheets plus margin; then measure at the transistor gates and reduce it only after confirming that process, temperature, and layout still leave enough margin. Set both delays to zero to bypass the dead-time stage. :cpp:member:`invert_output ` changes polarity after that stage. + +.. figure:: /../_static/mcpwm/deadtime_active_high.svg + :align: center + :alt: Basic dead-time effect: rising edge delayed (RED) and falling edge delayed (FED) relative to the original. + + Basic dead-time effect: rising edge delayed (RED) and falling edge delayed (FED) relative to the original. + +Resource limits per operator +---------------------------- + +The hardware has one rising-edge and one falling-edge delay resource per operator, so do not assign the same delay type independently to both generators. The following requests the one rising-edge resource twice and is invalid: + +.. code-block:: c + + mcpwm_dead_time_config_t rise_delay = { .posedge_delay_ticks = 10 }; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_a, &rise_delay)); + // This second independent rising-edge delay cannot be allocated. + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_b, gen_b, &rise_delay)); + +You may assign the rising delay to A and the falling delay to B. You may also use both delay resources for B while A bypasses the module. If the first generator uses both delay resources, the other generator cannot use dead time. + +More output patterns +-------------------- + +The complementary configuration above is the usual half-bridge starting point. Swap the output inversions to make both outputs active low while retaining the non-overlap: + +.. figure:: /../_static/mcpwm/deadtime_active_low_complementary.svg + :align: center + :alt: Active-low complementary outputs. The timing resources are the same; only the post-dead-time polarity changes. + + Active-low complementary outputs. The timing resources are the same; only the post-dead-time polarity changes. + +Dead time is also useful when only one channel needs an edge delay. Keep one output bypassed by passing a zero-delay configuration, and apply the available delay to the other: + +.. figure:: /../_static/mcpwm/deadtime_reda_bypassb.svg + :align: center + :alt: Delay A's rising edge while B bypasses dead time. This is not a complementary half bridge; it is an independent edge-placement tool. + + Delay A's rising edge while B bypasses dead time. This is not a complementary half bridge; it is an independent edge-placement tool. + +.. figure:: /../_static/mcpwm/deadtime_redb_fedb_bypassa.svg + :align: center + :alt: Bypass A and delay both edges of B, consuming both delay resources. + + Bypass A and delay both edges of B, consuming both delay resources. + +A single-edge delay can also be applied individually. The next diagram shows the falling edge delayed on B while A is bypassed, using only the FED resource: + +.. figure:: /../_static/mcpwm/deadtime_fedb_bypassa.svg + :align: center + :alt: Apply only the falling-edge delay to B, leaving A unchanged. This uses one delay resource. + + Apply only the falling-edge delay to B, leaving A unchanged. This uses one delay resource. + +When the output is inverted, the dead-time behavior shifts accordingly. The following shows the active-low version of the basic delay, where the invert flag flips the polarity of both outputs: + +.. figure:: /../_static/mcpwm/deadtime_active_low.svg + :align: center + :alt: Active-low dead time: same delay resources, but the output polarity is inverted after the delay stage. + + Active-low dead time: same delay resources, but the output polarity is inverted after the delay stage. + +.. note:: + + For a waveform where each edge must have an independently movable position, use two comparators and generator actions instead. The dead-time module is the better choice when the requirement is specifically a delayed edge plus polarity control. + +Update at a safe boundary +------------------------- + +Set the operator's :cpp:member:`update_dead_time_on_tez `, :cpp:member:`update_dead_time_on_tep `, or :cpp:member:`update_dead_time_on_sync ` flag when a changed dead-time value must take effect only at a known boundary. + +.. note:: + + Probe both physical gate pins: GPIO inversion, carrier modulation, and gate-driver polarity can all alter what appears at the transistor. When several stages invert the signal, two inversions can cancel out and look correct in software while the hardware does something unexpected, so always verify against the real waveform. + +Other event sources +=================== + +Generator actions can also react directly to GPIO fault events or a sync event: + +.. list:: + + - :cpp:func:`mcpwm_generator_set_action_on_fault_event()` — immediate hardware reaction to a GPIO fault. Uses limited operator trigger slots. + - :cpp:func:`mcpwm_generator_set_action_on_sync_event()` — transition at a synchronization edge. Each generator has one sync-action slot. + - :cpp:func:`mcpwm_generator_set_action_on_brake_event()` — per-generator output state during an operator :ref:`brake `. It is set per brake mode and timer direction; see :ref:`Fault connection ` for a full example. + +For safety policy and persistent braking, prefer the operator :ref:`brake mechanism `. A generator fault action is best for a local edge-level response; a brake defines the safe state and recovery behavior for the whole output stage. + +API Reference +============= + +MCPWM Generator Driver Functions +--------------------------------- + +.. include-build-file:: inc/mcpwm_gen.inc diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_operator.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_operator.rst new file mode 100644 index 00000000000..460f08ec4ba --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_operator.rst @@ -0,0 +1,187 @@ +========================================= +MCPWM Operator: Assemble an Output Stage +========================================= + +.. contents:: + :local: + :depth: 2 + +An operator is the container between a timer and its generators. It owns the comparators, generator actions, brake handling, dead-time routing, and carrier modulation. One timer can drive multiple operators in the same group, while an operator connects to exactly one timer. + +If the timer is the clock source, the operator is the output-stage container. It lets several outputs share one time base while keeping protection, dead time, and carrier features grouped with the power stage they belong to. + +Connect the building blocks +=========================== + +Create the operator in the same group as the timer, then connect them with :cpp:func:`mcpwm_operator_connect_timer()`. The connection must exist before the generator can use timer events. + +.. code-block:: c + + mcpwm_oper_handle_t oper = NULL; + mcpwm_operator_config_t oper_config = { + .group_id = 0, + .flags.update_gen_action_on_tez = true, + .flags.update_dead_time_on_tez = true, + }; + ESP_ERROR_CHECK(mcpwm_new_operator(&oper_config, &oper)); + ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer)); + +The operator configuration is small, but a few fields do not appear in the example: + +.. list:: + + - :cpp:member:`group_id ` — the MCPWM group the operator is allocated from. It must match the timer's group, because an operator can only connect to a timer inside the same group. + - :cpp:member:`intr_priority ` — the interrupt priority used by the brake event callbacks. Not setting it (``0``) lets the driver choose a low priority; raise it when brake notifications must preempt other ISRs. + +The ``flags`` choose when new generator actions and dead-time settings take effect. They are all off by default, so changes apply immediately — possibly in the middle of a PWM cycle: + +.. list:: + + - :cpp:member:`update_gen_action_on_tez `, :cpp:member:`update_gen_action_on_tep `, and :cpp:member:`update_gen_action_on_sync ` — buffer generator action changes until the counter reaches zero, the peak, or a sync event. + - :cpp:member:`update_dead_time_on_tez `, :cpp:member:`update_dead_time_on_tep `, and :cpp:member:`update_dead_time_on_sync ` — buffer dead-time changes the same way; see :ref:`dead time ` for the update-point rules. + +For a running power stage, use the zero (``tez``), peak (``tep``), or sync update point to avoid partial cycles. + +One timer, multiple operators +============================= + +The same timer can drive several operators, each producing a different waveform. This is useful for multi-phase inverters or multiple motors running at the same frequency but with independent duty cycles. + +The reverse is also important: one operator connects to exactly one timer, so all comparators and generators inside that operator inherently share the same time base. That is why in-phase, complementary, and paired outputs are easy to build there. + +.. code-block:: c + + mcpwm_oper_handle_t oper_b = NULL; + mcpwm_operator_config_t oper_config_b = { + .group_id = 0, + .flags.update_gen_action_on_tez = true, + }; + ESP_ERROR_CHECK(mcpwm_new_operator(&oper_config_b, &oper_b)); + ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper_b, timer)); + // Create separate comparators and generators under oper_b. + +.. _mcpwm-brake: + +Brake and safe output state +=========================== + +The operator turns a :doc:`fault ` into a brake action. Arrange brake actions before starting PWM: this makes the reaction entirely hardware driven and avoids software latency in the fault path. + +Recovery policy +--------------- + +.. list:: + + - **CBC (cycle by cycle):** brakes while the fault is active and recovers at the configured timer zero or peak. This suits a transient current limit. + - **OST (one shot):** stays braked after the fault disappears. Software must explicitly recover it. Use it for an interlock or serious over-current condition. + +For CBC, set :cpp:member:`cbc_recover_on_tez ` or :cpp:member:`cbc_recover_on_tep ` to choose the boundary at which a cleared fault releases the outputs. A boundary avoids restoring a switch in the middle of a PWM cycle. + +.. warning:: + + Do not enable both ``cbc_recover_on_tez`` and ``cbc_recover_on_tep`` at the same time; choose the boundary that matches the waveform and gate-driver timing. + +.. _mcpwm-brake-fault-connection: + +Fault connection +---------------- + +Connect the fault to the operator, then specify the state of every generator during that brake mode. This example drives the raw generator low in both timer directions for OST braking: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_operator_set_brake_on_fault(oper, + &(mcpwm_brake_config_t) { + .fault = fault, + .brake_mode = MCPWM_OPER_BRAKE_MODE_OST, + })); + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_brake_event( + generator, MCPWM_GEN_BRAKE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_OST, + MCPWM_GEN_ACTION_LOW))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_brake_event( + generator, MCPWM_GEN_BRAKE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_DOWN, MCPWM_OPER_BRAKE_MODE_OST, + MCPWM_GEN_ACTION_LOW))); + +For a bridge, configure both generators with the same brake action. Confirm the electrical safe state at the gate driver; a logical low can be inverted by dead time, GPIO matrix, or external circuitry. + +The distinction from a generator fault action is important: generator fault actions are best for a local immediate edge response, while operator brake defines the safe state, latch behavior, and recovery policy for the entire output stage. The primary protection path should usually use operator brake. + +OST fault recovery +------------------ + +CBC recovers on its configured boundary after the fault goes inactive. For OST, remove and validate the root cause first, then call: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_operator_recover_from_fault(oper, fault)); + +The call fails while the source is still active. + +.. figure:: /../_static/mcpwm/brake_cbc_ost.svg + :align: center + :alt: A fault asserts while PWM runs. CBC holds the output at the brake level while the fault is active and resumes at the next cycle boundary; OST stays latched until software recovery. + + CBC brakes only while the fault is active and recovers at the next cycle boundary; OST stays latched until software recovery. + +Brake event callbacks +--------------------- + +The operator can report brake events through the :cpp:member:`on_brake_cbc ` and :cpp:member:`on_brake_ost ` callbacks. They run in ISR context; use them for notification, not blocking recovery. + +.. code-block:: c + + mcpwm_operator_event_callbacks_t cbs = { + .on_brake_cbc = my_brake_cbc_cb, + .on_brake_ost = my_brake_ost_cb, + }; + ESP_ERROR_CHECK(mcpwm_operator_register_event_callbacks(oper, &cbs, NULL)); + +.. _mcpwm-carrier: + +Carrier modulation +================== + +Carrier modulation superimposes a high-frequency carrier on an operator's PWM output. It is commonly used with transformer-isolated gate-drive schemes: even a base PWM held at 100% duty then contains transitions that can cross the isolation barrier. Configure the base PWM first; carrier settings affect the operator's all generators. + +Carrier configuration +--------------------- + +.. code-block:: c + + mcpwm_carrier_config_t carrier = { + .clk_src = MCPWM_CARRIER_CLK_SRC_DEFAULT, + .frequency_hz = 100000, + .duty_cycle = 0.5f, + .first_pulse_duration_us = 20, + }; + ESP_ERROR_CHECK(mcpwm_operator_apply_carrier(oper, &carrier)); + +.. figure:: /../_static/mcpwm/carrier_modulation.svg + :align: center + :alt: Carrier modulation of a 50% duty base PWM + + A 100 kHz carrier gates a 50% duty base PWM. The first pulse is stretched to 20 us (two carrier periods), and no chopping occurs while the base PWM is low. + +Carrier parameters +------------------ + +.. list:: + + - :cpp:member:`clk_src ` selects the carrier clock source. It defaults to an internal PLL clock (e.g. PLL_F160M); some chips also expose RC_FAST or XTAL as alternatives. Different sources offer different resolution and power consumption. The default is fine for most applications; pick a different source only to avoid noise from a particular clock, when PLL precision is insufficient, or when power consumption matters. + - :cpp:member:`frequency_hz ` is the carrier frequency; select a value compatible with the isolation transformer, gate driver, switching loss budget, and target clock resolution. + - :cpp:member:`duty_cycle ` accepts the hardware steps 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, or 0.875, rather than an arbitrary ratio. + - :cpp:member:`first_pulse_duration_us ` controls the first pulse after modulation begins. It must be nonzero and at least one carrier period. A longer first pulse can help establish current in an inductive isolation path, but must stay within the gate-drive system's limits. + - Use :cpp:member:`invert_before_modulate ` when the raw PWM needs a polarity change and :cpp:member:`invert_after_modulate ` when the modulated output needs one. + +Pass ``NULL`` as the configuration to :cpp:func:`mcpwm_operator_apply_carrier` when carrier modulation is not needed. + +API Reference +============= + +MCPWM Operator Driver Functions +------------------------------- + +.. include-build-file:: inc/mcpwm_oper.inc diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_sync.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_sync.rst new file mode 100644 index 00000000000..d5502ffe0d2 --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_sync.rst @@ -0,0 +1,157 @@ +======================================== +MCPWM Synchronization: Align PWM Phases +======================================== + +.. contents:: + :local: + :depth: 2 + +Why sync is needed +================== + +Each MCPWM timer is an independent hardware counter. When you call :cpp:func:`mcpwm_timer_start()` on two timers, the two writes are issued sequentially by the CPU — the second timer starts a few dozen CPU cycles after the first. Even if both are configured with the same period, their counters will be at different positions relative to the cycle, and the phase relationship between their PWM outputs is unpredictable. + +Synchronization solves this by loading a chosen count and direction into a **running** timer when a sync edge arrives. The timers must already be running; sync does not start or stop them. It is a runtime phase correction mechanism. + +If the sync edge arrives every period (for example, from a timer sync source at TEZ), the correction repeats each cycle, keeping the phase locked indefinitely. This is the typical use case: one timer acts as the reference, and other timers re-align to it on every period. + +MCPWM provides three types of sync sources. All sources produce a handle of type :cpp:type:`mcpwm_sync_handle_t`, and any source can feed any timer in the same group. + +GPIO sync source +================ + +A GPIO sync source reacts to an edge on an external pin — useful when an external controller, sensor, or encoder provides a periodic reference. + +.. code-block:: c + + mcpwm_sync_handle_t sync = NULL; + ESP_ERROR_CHECK(mcpwm_new_gpio_sync_src( + &(mcpwm_gpio_sync_src_config_t) { + .group_id = 0, + .gpio_num = 5, + .flags.active_neg = false, + }, &sync)); + +The GPIO sync source configuration is small: + +.. list:: + + - :cpp:member:`group_id ` — the MCPWM group the source belongs to. It must match the group of every timer that receives this sync. + - :cpp:member:`gpio_num ` — the GPIO carrying the sync signal. + - :cpp:member:`active_neg ` — by default the rising edge is the active edge; set it to treat the falling edge as active instead. + +Software sync source +==================== + +A software sync source produces a sync edge on demand from application code. It has no configuration fields; create it and activate it when needed. + +.. code-block:: c + + mcpwm_sync_handle_t soft_sync = NULL; + ESP_ERROR_CHECK(mcpwm_new_soft_sync_src(NULL, &soft_sync)); + + // later, when the application decides to synchronize: + ESP_ERROR_CHECK(mcpwm_soft_sync_activate(soft_sync)); + +.. note:: + + Activate the soft sync only after binding it to a timer via :cpp:func:`mcpwm_timer_set_phase_on_sync()` or :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()`. The driver does not assign a timer at creation time; calling :cpp:func:`mcpwm_soft_sync_activate()` before binding is undefined behavior. + +This is useful when timers are already running and the application needs to trigger a one-time phase correction — for example, after recovering from a fault, or before starting a new control cycle. Because the sync is one-shot, the phase relationship will drift over time if no further sync edges arrive. For sustained phase lock, use a periodic source (GPIO or timer sync). + +Timer sync source +================= + +A timer sync source emits a sync edge when the timer reaches a chosen event — for example, every time the timer hits zero (TEZ). This lets one timer act as a periodic reference for other timers, keeping their phase locked every cycle. + +.. code-block:: c + + mcpwm_sync_handle_t timer_sync = NULL; + ESP_ERROR_CHECK(mcpwm_new_timer_sync_src( + timer_a, + &(mcpwm_timer_sync_src_config_t) { + .timer_event = MCPWM_TIMER_EVENT_EMPTY, + }, + &timer_sync)); + +.. list:: + + - :cpp:member:`timer_event ` — the timer event that triggers the sync output. Common choices are :cpp:enumerator:`MCPWM_TIMER_EVENT_EMPTY` (zero) for the start of each period or :cpp:enumerator:`MCPWM_TIMER_EVENT_PEAK` for the peak value. In up-counting mode the peak is the period boundary; in up-down mode the peak is the midpoint of the period. + - :cpp:member:`propagate_input_sync ` — when set, the timer forwards its own received input sync to its sync output, enabling a chain of timers without extra GPIO wiring. In this mode the hardware selects the input sync as the output source, so the :cpp:member:`timer_event` field is ignored. + +A timer can create at most one sync source. Multiple timers can receive the same sync source. + +Because the timer sync source fires every period, the receiving timer gets corrected on every cycle. This is the most common way to maintain a stable phase relationship between multiple PWM channels. + +Set the receiving phase +======================= + +No matter which source type you chose, the receiving timer uses the same API. Call :cpp:func:`mcpwm_timer_set_phase_on_sync()` to configure what happens when the sync edge arrives. The timer must already be running for the sync to take effect. + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timer, + &(mcpwm_timer_sync_phase_config_t) { + .sync_src = sync, + .count_value = 25, + .direction = MCPWM_TIMER_DIRECTION_UP, + })); + +.. list:: + + - :cpp:member:`sync_src ` — the source object. Set it to ``NULL`` to detach synchronization. + - :cpp:member:`count_value ` — the count loaded when the sync event arrives. Keep it within the timer period. + - :cpp:member:`direction ` — the counting direction after loading. + +Two outputs with a 90-degree phase shift +======================================== + +Now that you know all three source types and how to set the receiving phase, here is a complete example. It uses a timer sync source: ``timer_a`` emits a sync every time it reaches zero, and ``timer_b`` receives that sync and loads ``count_value = 25``, producing a 90-degree phase lag. Because the sync repeats each period, the phase relationship between the two channels is maintained indefinitely. + +.. code-block:: c + + mcpwm_timer_handle_t timer_a = NULL; + mcpwm_timer_handle_t timer_b = NULL; + mcpwm_sync_handle_t timer_a_sync = NULL; + + // timer_a and timer_b already exist, both with period_ticks = 100 + + ESP_ERROR_CHECK(mcpwm_new_timer_sync_src( + timer_a, + &(mcpwm_timer_sync_src_config_t) { + .timer_event = MCPWM_TIMER_EVENT_EMPTY, + }, + &timer_a_sync)); + + ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timer_b, + &(mcpwm_timer_sync_phase_config_t) { + .sync_src = timer_a_sync, + .count_value = 25, + .direction = MCPWM_TIMER_DIRECTION_UP, + })); + + // timer_a emits sync at TEZ; timer_b receives it and continues from tick 25. + +Understand lead and lag +----------------------- + +In the sketch below, ``PWM_A`` starts its cycle first and ``PWM_B`` appears one quarter cycle later. That means ``PWM_B`` lags ``PWM_A`` by 90 degrees; equivalently, ``PWM_A`` leads ``PWM_B`` by 90 degrees. + +.. figure:: /../_static/mcpwm/phase_shift.svg + :align: center + :alt: PWM phase shift 90 degree lag + + PWM_A and PWM_B with a 90-degree phase shift: PWM_B starts 25 ticks after PWM_A. + +Other considerations +==================== + +The capture timer can use the same source through :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()`; capture always counts up. The receiver and source must remain in the same group. Delete a source only after detaching or deleting every object that uses it. + +API Reference +============= + +MCPWM Synchronization Driver Functions +-------------------------------------- + +.. include-build-file:: inc/mcpwm_sync.inc diff --git a/docs/en/api-reference/peripherals/mcpwm/mcpwm_timer.rst b/docs/en/api-reference/peripherals/mcpwm/mcpwm_timer.rst new file mode 100644 index 00000000000..29cc8be33ea --- /dev/null +++ b/docs/en/api-reference/peripherals/mcpwm/mcpwm_timer.rst @@ -0,0 +1,146 @@ +=============================== +MCPWM Timer: Set the Frequency +=============================== + +.. contents:: + :local: + :depth: 2 + +The timer is the time base for every PWM output attached to its operator. It counts ticks at :cpp:member:`resolution_hz ` and wraps around at :cpp:member:`period_ticks `. Choose the resolution first — it determines the finest step of edge placement — then choose the period for the target frequency. + +For a servo, speed loop, or inverter, the timer answers the two most basic questions: how fine is one tick, and how long is one PWM cycle. Comparators and generators only place edges on top of that time base. + +Build a 20 kHz time base +========================= + +For an up-counting timer, ``period_ticks = resolution_hz / frequency_hz``. The following timer has a 1 MHz tick (one microsecond per tick) and a 50-tick period, giving 20 kHz. The diagram below shows the counter climbing from 0 to 50, then resetting — the ``TEZ`` (timer event zero) and ``TEP`` (timer event peak) markers are the two boundaries that generators use. + +.. figure:: /../_static/mcpwm/timer_up_count.svg + :align: center + :alt: Up-counting timer: the counter forms a sawtooth, rising from 0 to 50, firing TEZ at zero and TEP at peak. + + Up-counting timer: the counter forms a sawtooth, rising from 0 to 50, firing TEZ at zero and TEP at peak. + +.. code-block:: c + + mcpwm_timer_handle_t timer = NULL; + mcpwm_timer_config_t timer_config = { + .group_id = 0, + .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + .period_ticks = 50, + .count_mode = MCPWM_TIMER_COUNT_MODE_UP, + }; + ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timer)); + +The timer configuration is worth reading field by field, because a few important knobs are not shown in the example: + +.. list:: + + - :cpp:member:`group_id ` — the MCPWM group the timer is allocated from. Chips may expose more than one groups; each group bundles timers, operators, comparators, and generators that share clock dividers. ``0`` selects the first group, which is enough for most designs. + - :cpp:member:`clk_src ` — the clock that feeds the timer. :c:macro:`MCPWM_TIMER_CLK_SRC_DEFAULT` selects a PLL clock and is right for almost every application. On targets with extra sources, you can pick one explicitly — for example to keep the timer counting when the PLL is switched off, such as during light sleep. + - :cpp:member:`resolution_hz ` — the tick rate of the counter. One tick lasts ``1 / resolution_hz`` seconds, so 1 MHz means one microsecond per tick. This sets the finest edge step available to the comparator. + - :cpp:member:`period_ticks ` — the length of one full PWM cycle in ticks. The frequency is ``resolution_hz / period_ticks``. + - :cpp:member:`count_mode ` — whether the counter counts up only (edge-aligned PWM) or up and down (center-aligned PWM). See :ref:`Counting modes and waveforms ` for the two shapes; the hardware also supports counting down. + - :cpp:member:`intr_priority ` — the interrupt priority used by the timer callbacks. Not setting it (``0``) lets the driver choose a low priority; raise it when a callback must preempt other ISRs, for example in tightly timed motor control. + +The example does not touch :cpp:member:`flags `, so all of them are off — which is the safe default. Two of them are worth knowing: + +.. list:: + + - :cpp:member:`update_period_on_empty ` and :cpp:member:`update_period_on_sync ` — off by default, so :cpp:func:`mcpwm_timer_set_period()` takes effect immediately. Turn them on to defer frequency changes to a safe boundary; see :ref:`Safe frequency updates `. + - :cpp:member:`allow_pd ` — lets the MCPWM power domain switch off during sleep. The driver then backs up and restores the timer registers around the sleep transition, saving power at the cost of extra RAM. + +Do not start the timer yet. First create and connect the operator, comparator, and generator (see the following pages), then enable and start: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_timer_enable(timer)); + ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP)); + +:cpp:func:`mcpwm_timer_enable()` activates the services the timer needs to run: it enables the timer interrupt and, with power management on, holds the group's power-management lock so clock scaling cannot disturb PWM timing. :cpp:func:`mcpwm_timer_start_stop()` then starts and later stops the counter. Call :cpp:func:`mcpwm_timer_disable()` to reverse the enable before freeing the timer with :cpp:func:`mcpwm_del_timer()`. + +The third argument of :cpp:func:`mcpwm_timer_start_stop()` selects the stop behavior: + +.. list:: + + - :c:macro:`MCPWM_TIMER_START_NO_STOP` — runs continuously until you explicitly stop it. + - :c:macro:`MCPWM_TIMER_START_STOP_EMPTY` — stops automatically when the next count reaches zero (TEZ). Use this for a single-shot or synchronized start where the cycle should complete before stopping. + - :c:macro:`MCPWM_TIMER_START_STOP_FULL` — stops automatically when the next count reaches the peak (TEP). Use this for a single cycle that ends at the period boundary. + +.. _mcpwm-timer-counting-modes: + +Counting modes and waveforms +============================ + +In **up mode**, the counter counts from 0 to :cpp:member:`period_ticks ` and resets. The waveform is a sawtooth and the PWM edges align to one side of the period — this is called *edge-aligned* PWM. + +In **up-down mode**, the counter counts up to ``period_ticks / 2`` and then down to 0. The waveform is a triangle and the PWM edges are centered around the middle of the period — *center-aligned* PWM. Center-aligned PWM is preferred for motor control because it produces less harmonic distortion. + +.. figure:: /../_static/mcpwm/timer_up_down_count.svg + :align: center + :alt: Up-down counting: the counter forms a triangle, rising to 25 (half of 50), then falling back to 0. + + Up-down counting: the counter forms a triangle, rising to 25 (half of 50), then falling back to 0. + +The frequency is still ``resolution_hz / period_ticks`` in both modes. Choose a resolution high enough for the duty precision you need, then choose a period for the desired frequency. + +.. important:: + + :cpp:member:`period_ticks ` is the total number of ticks in one full PWM cycle. It is not always the same thing as the timer peak value. + + .. list:: + + - In ``MCPWM_TIMER_COUNT_MODE_UP``, the counter runs ``0 -> period_ticks``. + - In ``MCPWM_TIMER_COUNT_MODE_UP_DOWN``, the hardware peak is ``period_ticks / 2``, and the full cycle is ``0 -> peak -> 0``. + + For example, with :cpp:member:`resolution_hz ` = 1 MHz and :cpp:member:`period_ticks ` = 50: up mode counts ``0 -> 50``, while up-down mode counts ``0 -> 25 -> 0``. Both still take 50 microseconds for a full cycle, so both are 20 kHz. What changes is the edge placement, not the period length. + +.. _mcpwm-timer-safe-update: + +Safe frequency updates +====================== + +:cpp:func:`mcpwm_timer_set_period()` takes effect immediately by default. That can truncate the current cycle and produce a runt pulse. Set :cpp:member:`update_period_on_empty ` to defer the new period until the counter reaches zero, or :cpp:member:`update_period_on_sync ` to defer it until a sync event. When changing the period, also scale the comparator threshold if the duty ratio must remain unchanged: + +.. code-block:: c + + // Keep 40 % duty while changing a 50-tick period to 100 ticks. + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 40)); + ESP_ERROR_CHECK(mcpwm_timer_set_period(timer, 100)); + +In most runtime tuning paths, change the comparator to change duty and touch the timer only when the PWM frequency itself must change. Motor-control and power-conversion applications should usually combine this with :cpp:member:`update_period_on_empty ` or a sync-triggered update to avoid mid-cycle changes. + +Timer event callbacks +===================== + +The timer can notify your application at peak (:cpp:member:`on_full `), zero (:cpp:member:`on_empty `), or when it stops (:cpp:member:`on_stop `). Register callbacks before enabling the timer. They run in ISR context: do not block, allocate memory, or call normal FreeRTOS APIs; use ``...FromISR`` variants when needed. + +.. note:: + + The timer and capture timer may share a divider with other objects in the same group. When one group needs several resolutions, create objects in monotonic requested-resolution order to avoid divider conflicts. See :doc:`advanced topics ` for the full rule. + +.. code-block:: c + + static bool IRAM_ATTR on_timer_empty(mcpwm_timer_handle_t timer, + const mcpwm_timer_event_data_t *edata, + void *user_ctx) + { + BaseType_t high_task_woken = pdFALSE; + vTaskNotifyGiveFromISR((TaskHandle_t)user_ctx, &high_task_woken); + return high_task_woken == pdTRUE; + } + + mcpwm_timer_event_callbacks_t cbs = { .on_empty = on_timer_empty }; + ESP_ERROR_CHECK(mcpwm_timer_register_event_callbacks(timer, &cbs, + xTaskGetCurrentTaskHandle())); + +The :doc:`synchronization ` page shows how a timer can reset to a chosen phase on a sync edge. + +API Reference +============= + +MCPWM Timer Driver Functions +---------------------------- + +.. include-build-file:: inc/mcpwm_timer.inc diff --git a/docs/en/migration-guides/release-5.x/5.0/peripherals.rst b/docs/en/migration-guides/release-5.x/5.0/peripherals.rst index 34b9a3babce..febda638809 100644 --- a/docs/en/migration-guides/release-5.x/5.0/peripherals.rst +++ b/docs/en/migration-guides/release-5.x/5.0/peripherals.rst @@ -397,7 +397,7 @@ LCD Legacy MCPWM Driver is Deprecated --------------------------------- - MCPWM driver was redesigned (see :doc:`MCPWM <../../../api-reference/peripherals/mcpwm>`), meanwhile, the legacy driver is deprecated. + MCPWM driver was redesigned (see :doc:`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. diff --git a/docs/page_redirects.txt b/docs/page_redirects.txt index 40ef88dc628..5953b530302 100644 --- a/docs/page_redirects.txt +++ b/docs/page_redirects.txt @@ -16,6 +16,7 @@ api-reference/ethernet/esp_eth api-reference/network/esp_eth api-reference/mesh/index api-reference/network/index api-reference/mesh/esp_mesh api-reference/network/esp_mesh api-reference/peripherals/can api-reference/peripherals/twai +api-reference/peripherals/mcpwm api-reference/peripherals/mcpwm/index api-reference/peripherals/usb "https://docs.espressif.com/projects/esp-usb/en/latest/{IDF_TARGET_PATH_NAME}/introduction.html" api-reference/peripherals/usb_device "https://docs.espressif.com/projects/esp-usb/en/latest/{IDF_TARGET_PATH_NAME}/usb_device.html" api-reference/peripherals/usb_host "https://docs.espressif.com/projects/esp-usb/en/latest/{IDF_TARGET_PATH_NAME}/usb_host.html" diff --git a/docs/zh_CN/api-reference/peripherals/etm.rst b/docs/zh_CN/api-reference/peripherals/etm.rst index 2b06c5ed71f..a17b7a0aeba 100644 --- a/docs/zh_CN/api-reference/peripherals/etm.rst +++ b/docs/zh_CN/api-reference/peripherals/etm.rst @@ -70,7 +70,7 @@ GPIO **边沿** 事件是最常见的事件类型,任何 GPIO 管脚均可触 :SOC_SYSTIMER_SUPPORT_ETM: - 要了解如何从 esp_timer 获取 ETM 事件句柄,请参阅 :doc:`/api-reference/system/esp_timer`。 :SOC_TIMER_SUPPORT_ETM: - 要了解如何从 GPTimer 获取 ETM 事件句柄,请参阅 :ref:`gptimer-etm-event-and-task`。 :SOC_GDMA_SUPPORT_ETM: - 要了解如何从 async memcpy 获取 ETM 事件句柄,请参阅 :doc:`/api-reference/peripherals/async_memcpy`。 - :SOC_MCPWM_SUPPORT_ETM: - 要了解如何从 MCPWM 中获取 ETM 事件句柄,请参阅 :doc:`/api-reference/peripherals/mcpwm`。 + :SOC_MCPWM_SUPPORT_ETM: - 要了解如何从 MCPWM 中获取 ETM 事件句柄,请参阅 :doc:`/api-reference/peripherals/mcpwm/mcpwm_etm`。 :SOC_ANA_CMPR_SUPPORT_ETM: - 要了解如何从模拟比较器获取 ETM 事件句柄,请参阅 :doc:`/api-reference/peripherals/ana_cmpr`。 :SOC_TEMPERATURE_SENSOR_SUPPORT_ETM: - 要了解如何从温度传感器获取 ETM 事件句柄,请参阅 :doc:`/api-reference/peripherals/temp_sensor`。 :SOC_I2S_SUPPORTS_ETM: - 要了解如何从 I2S 获取 ETM 事件句柄,请参阅 :doc:`/api-reference/peripherals/i2s`。 diff --git a/docs/zh_CN/api-reference/peripherals/index.rst b/docs/zh_CN/api-reference/peripherals/index.rst index e0de40dd7c1..344b23e76a4 100644 --- a/docs/zh_CN/api-reference/peripherals/index.rst +++ b/docs/zh_CN/api-reference/peripherals/index.rst @@ -32,7 +32,7 @@ lcd/index :SOC_GP_LDO_SUPPORTED: ldo_regulator :SOC_LEDC_SUPPORTED: ledc - :SOC_MCPWM_SUPPORTED: mcpwm + :SOC_MCPWM_SUPPORTED: mcpwm/index :SOC_PARLIO_SUPPORTED: parlio/index :SOC_PCNT_SUPPORTED: pcnt :SOC_PPA_SUPPORTED: ppa diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm.rst b/docs/zh_CN/api-reference/peripherals/mcpwm.rst deleted file mode 100644 index 4971a9cd471..00000000000 --- a/docs/zh_CN/api-reference/peripherals/mcpwm.rst +++ /dev/null @@ -1,1147 +0,0 @@ -电机控制脉宽调制器 (MCPWM) -=========================================== - -:link_to_translation:`en:[English]` - - -MCPWM 外设是一个多功能 PWM 生成器,集成多个子模块,在电力电子应用(如电机控制、数字电源等)中至关重要。MCPWM 外设通常适用于以下场景: - -- 数字电机控制,如有刷/无刷直流电机、RC 伺服电机 -- 基于开关模式的数字电源转换 -- 功率数模转换器 (Power DAC),其中占空比等于 DAC 的模拟值 -- 计算外部脉宽,并将其转换为其他模拟值,如速度、距离 -- 为磁场定向控制 (FOC) 生成空间矢量调制 (SVPWM) 信号 - -外设的主要子模块如下图所示: - -.. blockdiag:: /../_static/diagrams/mcpwm/mcpwm_overview.diag - :caption: MCPWM 概述 - :align: center - -- **MCPWM 定时器模块**:最终输出 PWM 信号的时间基准。它也决定了其他子模块的事件时序。 -- **MCPWM 操作器模块**:生成 PWM 波形的关键模块。它由其他子模块组成,如比较器、PWM 生成器、死区生成器和载波调制器。 -- **MCPWM 比较器模块**:输入时间基准值,并不断与配置的阈值进行比较。当定时器计数值等于任何一个阈值时,生成一个比较事件,MCPWM 生成器随即相应更新其电平。 -- **MCPWM 生成器模块**:根据 MCPWM 定时器、MCPWM 比较器等子模块触发的各种事件,生成一对独立或互补的 PWM 波形。 -- **MCPWM 故障检测模块**:通过 GPIO 交换矩阵检测外部的故障情况。检测到故障信号时,MCPWM 操作器将强制所有生成器进入预先定义的状态,从而保护系统。 -- **MCPWM 同步模块**:同步 MCPWM 定时器,以确保由不同的 MCPWM 生成器最终生成的 PWM 信号具有固定的相位差。可以通过 GPIO 交换矩阵和 MCPWM 定时器事件生成同步信号。 -- **死区生成器模块**:在此前生成的 PWM 边沿上插入额外的延迟。 -- **载波模块**:可通过 PWM 波形生成器和死区生成器,将一个高频载波信号调制为 PWM 波形,这是控制功率开关器件的必需功能。 -- **制动控制**:MCPWM 操作器支持配置检测到特定故障时生成器的制动控制方式。根据故障的严重程度,可以选择立即关闭或是逐周期调节 PWM 输出。 -- **MCPWM 捕获模块**:独立子模块,不依赖于上述 MCPWM 操作器工作。捕获模块包括一个专用的定时器和几个独立的通道,每个通道都与 GPIO 相连。GPIO 上的脉冲触发捕获定时器以存储时间基准值,随后通过中断进行通知。此模块有助于更加精准地测量脉宽。此外,捕获定时器也可以通过 MCPWM 同步子模块进行同步。 - -功能概述 -------------------- - -下文将分节概述 MCPWM 的功能: - -.. list:: - - - :ref:`mcpwm-resource-allocation-and-initialization` - 介绍各类 MCPWM 模块的分配,如定时器、操作器、比较器、生成器等。随后介绍的 IO 设置和控制功能也将围绕这些模块进行。 - - :ref:`mcpwm-timer-operations-and-events` - 介绍 MCPWM 定时器支持的控制功能和事件回调。 - - :ref:`mcpwm-comparator-operations-and-events` - 介绍 MCPWM 比较器支持的控制功能和事件回调。 - - :ref:`mcpwm-generator-actions-on-events` - 介绍如何针对 MCPWM 定时器和比较器生成的特定事件,设置 MCPWM 生成器的相应执行操作。 - - :ref:`mcpwm-classical-pwm-waveforms-and-generator-configurations` - 介绍一些经典 PWM 波形的生成器配置。 - - :ref:`mcpwm-dead-time` - 介绍如何设置 MCPWM 生成器的死区时间。 - - :ref:`mcpwm-classical-pwm-waveforms-and-dead-time-configurations` - 介绍一些经典 PWM 波形的死区配置。 - - :ref:`mcpwm-carrier-modulation` - 介绍如何在最终输出的 PWM 波形上调制高频载波。 - - :ref:`mcpwm-faults-and-brake-actions` - 介绍如何为 MCPWM 操作器配置特定故障事件下的制动操作。 - - :ref:`mcpwm-generator-force-actions` - 介绍如何强制异步控制生成器的输出水平。 - - :ref:`mcpwm-synchronization` - 介绍如何同步 MCPWM 定时器,并确保生成的最终输出 PWM 信号具有固定的相位差。 - - :ref:`mcpwm-capture` - 介绍如何使用 MCPWM 捕获模块测量信号脉宽。 - :SOC_MCPWM_SUPPORT_ETM and SOC_ETM_SUPPORTED: - :ref:`mcpwm-etm-event-and-task` - MCPWM 提供了哪些事件和任务可以连接到 ETM 通道上。 - - :ref:`mcpwm-power-management` - 介绍不同的时钟源对功耗的影响。 - - :ref:`mcpwm-resolution-config` - 介绍 MCPWM 子模块的分辨率配置规则。 - - :ref:`mcpwm-iram-safe` - 介绍如何协调 RMT 中断与禁用缓存。 - - :ref:`mcpwm-thread-safety` - 列出了由驱动程序认证为线程安全的 API。 - - :ref:`mcpwm-kconfig-options` - 列出了针对驱动的数个 Kconfig 支持选项。 - -.. _mcpwm-resource-allocation-and-initialization: - -资源配置及初始化 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -如上图所示,MCPWM 外设由数个子模块组成。本节将介绍各个子模块的资源配置方式。 - -MCPWM 定时器 -~~~~~~~~~~~~~~~ - -调用 :cpp:func:`mcpwm_new_timer` 函数,以配置结构体 :cpp:type:`mcpwm_timer_config_t` 为参数,分配一个 MCPWM 定时器为对象。结构体定义为: - -- :cpp:member:`mcpwm_timer_config_t::group_id` 指定 MCPWM 组 ID,范围为 [0, ``MCPWM_GROUP_NUM`` - 1],其中 ``MCPWM_GROUP_NUM`` 是芯片上可用的 MCPWM 组数量。需注意,位于不同组的定时器彼此独立。 - -.. note:: - - 对于芯片上可用的 MCPWM 组数量,请参阅 *{IDF_TARGET_NAME} 技术参考手册* > *电机控制脉宽调制器(MCPWM)* [`PDF <{IDF_TARGET_TRM_CN_URL}#mcpwm>`__]。 - -- :cpp:member:`mcpwm_timer_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。 -- :cpp:member:`mcpwm_timer_config_t::clk_src` 设置定时器的时钟源。 -- :cpp:member:`mcpwm_timer_config_t::resolution_hz` 设置定时器的预期分辨率。内部驱动将根据时钟源和分辨率设置合适的分频器。 -- :cpp:member:`mcpwm_timer_config_t::count_mode` 设置定时器的计数模式。 -- :cpp:member:`mcpwm_timer_config_t::period_ticks` 设置定时器的周期,以 Tick 为单位(通过 :cpp:member:`mcpwm_timer_config_t::resolution_hz` 设置 Tick 分辨率)。 -- :cpp:member:`mcpwm_timer_config_t::flags::update_period_on_empty` 设置当定时器计数为零时是否更新周期值。 -- :cpp:member:`mcpwm_timer_config_t::flags::update_period_on_sync` 设置当定时器接收同步信号时是否更新周期值。 - -分配成功后,:cpp:func:`mcpwm_new_timer` 将返回一个指向已分配定时器的指针。否则,函数将返回错误代码。具体来说,当 MCPWM 组中没有空闲定时器时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。[1]_ - -反之,调用 :cpp:func:`mcpwm_del_timer` 函数将释放已分配的定时器。 - -.. note:: - - 同时分配多个 MCPWM 定时器时,MCPWM 组的分频系数将以第一个定时器的分辨率来计算,驱动会从低到高寻找合适的分频系数。若分配多个定时器时出现分频系数冲突,请调整分配定时器的顺序,按照目标分辨率的大小按序申请定时器(从大到小或者从小到大)。更多相关内容请参阅 :ref:`mcpwm-resolution-config`。 - -MCPWM 操作器 -~~~~~~~~~~~~~~~ - -调用 :cpp:func:`mcpwm_new_operator` 函数,以配置结构体 :cpp:type:`mcpwm_operator_config_t` 为参数,分配一个 MCPWM 操作器为对象。结构体定义为: - -- :cpp:member:`mcpwm_operator_config_t::group_id` 指定 MCPWM 组 ID,范围为 [0, ``MCPWM_GROUP_NUM`` - 1],其中 ``MCPWM_GROUP_NUM`` 是芯片上可用的 MCPWM 组数量。需注意,位于不同组的操作器彼此独立。 -- :cpp:member:`mcpwm_operator_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。 -- :cpp:member:`mcpwm_operator_config_t::flags::update_gen_action_on_tez` 设置是否在定时器计数为零时更新生成器操作。此处及下文提到的定时器指通过 :cpp:func:`mcpwm_operator_connect_timer` 连接到操作器的定时器。 -- :cpp:member:`mcpwm_operator_config_t::flags::update_gen_action_on_tep` 设置当定时器计数达到峰值时是否更新生成器操作。 -- :cpp:member:`mcpwm_operator_config_t::flags::update_gen_action_on_sync` 设置当定时器接收同步信号时是否更新生成器操作。 -- :cpp:member:`mcpwm_operator_config_t::flags::update_dead_time_on_tez` 设置当定时器计数为零时是否更新死区时间。 -- :cpp:member:`mcpwm_operator_config_t::flags::update_dead_time_on_tep` 设置当定时器计数达到峰值时是否更新死区时间。 -- :cpp:member:`mcpwm_operator_config_t::flags::update_dead_time_on_sync` 设置当定时器接收同步信号时是否更新死区时间。 - -分配成功后,:cpp:func:`mcpwm_new_operator` 将返回一个指向已分配操作器的指针。否则,函数将返回错误代码。具体来说,当 MCPWM 组中没有空闲操作器时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。[1]_ - -反之,调用 :cpp:func:`mcpwm_del_operator` 函数将释放已分配的操作器。 - -.. _mcpwm-comparators: - -MCPWM 比较器 -~~~~~~~~~~~~~~~~~ - -调用 :cpp:func:`mcpwm_new_comparator` 函数,以一个 MCPWM 操作器句柄和配置结构体 :cpp:type:`mcpwm_comparator_config_t` 为参数,分配一个 MCPWM 比较器为对象。操作器句柄由 :cpp:func:`mcpwm_new_operator` 生成,结构体定义为: - -- :cpp:member:`mcpwm_comparator_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。 -- :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_tez` 设置当定时器计数为零时是否更新比较阈值。 -- :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_tep` 设置当定时器计数达到峰值时是否更新比较阈值。 -- :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_sync` 设置当定时器接收同步信号时是否更新比较阈值。 - -分配成功后,:cpp:func:`mcpwm_new_comparator` 将返回一个指向已分配比较器的指针。否则,函数将返回错误代码。具体来说,当 MCPWM 操作器中没有空闲比较器时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。[1]_ - -反之,调用 :cpp:func:`mcpwm_del_comparator` 函数将释放已分配的比较器。 - -.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR and SOC_MCPWM_SUPPORT_ETM and SOC_ETM_SUPPORTED - - MCPWM 中还有另外一种比较器 —— “事件比较器”,它不能直接控制 PWM 的输出,只能用来产生 EMT 子系统中使用到的事件。事件比较器能够设置的阈值也是可配的。调用 :cpp:func:`mcpwm_new_event_comparator` 函数可以申请一个事件比较器,该函数返回的句柄类型和 :cpp:func:`mcpwm_new_comparator` 函数一样,但是需要的配置结构体是不同的。事件比较器的配置位于 :cpp:type:`mcpwm_event_comparator_config_t`。更多相关内容请参阅 :ref:`mcpwm-etm-event-and-task`。 - -MCPWM 生成器 -~~~~~~~~~~~~~~~~ - -调用 :cpp:func:`mcpwm_new_generator` 函数,以一个 MCPWM 操作器句柄和配置结构体 :cpp:type:`mcpwm_generator_config_t` 为参数,分配一个 MCPWM 生成器为对象。操作器句柄由 :cpp:func:`mcpwm_new_operator` 生成,结构体定义为: - -- :cpp:member:`mcpwm_generator_config_t::gen_gpio_num` 设置生成器使用的 GPIO 编号。 -- :cpp:member:`mcpwm_generator_config_t::flags::invert_pwm` 设置是否反相 PWM 信号。 -- :cpp:member:`mcpwm_generator_config_t::pull_up` 和 :cpp:member:`mcpwm_generator_config_t::pull_down` 用来设置是否启用内部上下拉电阻。 - -分配成功后,:cpp:func:`mcpwm_new_generator` 将返回一个指向已分配生成器的指针。否则,函数将返回错误代码。具体来说,当 MCPWM 操作器中没有空闲生成器时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。[1]_ - -反之,调用 :cpp:func:`mcpwm_del_generator` 函数将释放已分配的生成器。 - -.. _mcpwm-faults: - -MCPWM 故障 -~~~~~~~~~~~~ - -MCPWM 故障分为两种类型:来自 GPIO 的故障信号和软件故障。 - -调用 :cpp:func:`mcpwm_new_gpio_fault` 函数,以配置结构体 :cpp:type:`mcpwm_gpio_fault_config_t` 为参数,分配一个 GPIO 故障为对象。结构体定义为: - -- :cpp:member:`mcpwm_gpio_fault_config_t::group_id` 设置 MCPWM 组 ID,范围为 [0, ``MCPWM_GROUP_NUM`` - 1],其中 ``MCPWM_GROUP_NUM`` 是芯片上可用的 MCPWM 组数量。需注意,位于不同组的 GPIO 故障彼此独立,也就是说,1 组的操作器无法检测到 0 组的 GPIO 故障。 -- :cpp:member:`mcpwm_gpio_fault_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。 -- :cpp:member:`mcpwm_gpio_fault_config_t::gpio_num` 设置故障所使用的 GPIO 编号。 -- :cpp:member:`mcpwm_gpio_fault_config_t::flags::active_level` 设置故障信号的有效电平。 -- :cpp:member:`mcpwm_gpio_fault_config_t::pull_up` 和 :cpp:member:`mcpwm_gpio_fault_config_t::pull_down` 设置是否在内部拉高和/或拉低 GPIO。 - -分配成功后,:cpp:func:`mcpwm_new_gpio_fault` 将返回一个指向已分配故障的指针。否则,函数将返回错误代码。具体来说,当指定 MCPWM 组中没有空闲 GPIO 故障时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。[1]_ - -调用函数 :cpp:func:`mcpwm_soft_fault_activate` 使一个软件故障对象触发故障,无需等待来自 GPIO 的真实故障信号。调用 :cpp:func:`mcpwm_new_soft_fault` 函数,以配置结构体 :cpp:type:`mcpwm_soft_fault_config_t` 为参数,分配一个软件故障为对象。该结构体暂时保留,供后续使用。 - -分配成功后,:cpp:func:`mcpwm_new_soft_fault` 将返回一个指向已分配故障的指针。否则,函数将返回错误代码。具体来说,当内存不足以支持该故障对象时,将返回 :c:macro:`ESP_ERR_NO_MEM` 错误。虽然软件故障和 GPIO 故障是不同类型的故障,但返回的故障句柄为同一类型。 - -反之,调用 :cpp:func:`mcpwm_del_fault` 函数将释放已分配的故障。此函数同时适用于软件故障和 GPIO 故障。 - -.. _mcpwm-sync-sources: - -MCPWM 同步源 -~~~~~~~~~~~~~~~~~~ - -同步源用于同步 MCPWM 定时器和 MCPWM 捕获定时器,分为三种类型:来自 GPIO 的同步源、软件生成的同步源和 MCPWM 定时器事件生成的同步源。 - -调用 :cpp:func:`mcpwm_new_gpio_sync_src` 函数,以配置结构体 :cpp:type:`mcpwm_gpio_sync_src_config_t` 为参数,分配一个 GPIO 同步源。结构体定义为: - -- :cpp:member:`mcpwm_gpio_sync_src_config_t::group_id` 指定 MCPWM 组 ID,范围为 [0, ``MCPWM_GROUP_NUM`` - 1],其中 ``MCPWM_GROUP_NUM`` 是芯片上可用的 MCPWM 组数量。需注意,位于不同组的 GPIO 同步源彼此独立,也就是说,1 组的定时器无法检测到 0 组的 GPIO 同步源。 -- :cpp:member:`mcpwm_gpio_sync_src_config_t::gpio_num` 设置同步源使用的 GPIO 编号。 -- :cpp:member:`mcpwm_gpio_sync_src_config_t::flags::active_neg` 设置同步信号在下降沿是否有效。 -- :cpp:member:`mcpwm_gpio_sync_src_config_t::pull_up` 和 :cpp:member:`mcpwm_gpio_sync_src_config_t::pull_down` 设置是否在内部拉高和/或拉低 GPIO。 - -分配成功后,:cpp:func:`mcpwm_new_gpio_sync_src` 将返回一个指向已分配同步源的指针。否则,函数将返回错误代码。具体来说,当 MCPWM 组中没有空闲 GPIO 时钟源时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。[1]_ - -调用 :cpp:func:`mcpwm_new_timer_sync_src` 函数,以配置结构体 :cpp:type:`mcpwm_timer_sync_src_config_t` 为参数,分配一个定时器事件同步源。结构体定义为: - -- :cpp:member:`mcpwm_timer_sync_src_config_t::timer_event` 指定产生同步信号的定时器事件。 -- :cpp:member:`mcpwm_timer_sync_src_config_t::propagate_input_sync` 是否广播输入同步信号(即将输入同步信号传输到其同步输出)。 - -分配成功后,:cpp:func:`mcpwm_new_timer_sync_src` 将返回一个指向已分配同步源的指针。否则,函数将返回错误代码。具体来说,若是分配的同步源此前已分配给了同一个定时器,将返回 :c:macro:`ESP_ERR_INVALID_STATE` 错误。 - -也可以调用 :cpp:func:`mcpwm_new_soft_sync_src` 函数,以配置结构体 :cpp:type:`mcpwm_soft_sync_config_t` 为参数,分配一个软件同步源。该结构体暂时保留,供后续使用。 - -分配成功后,:cpp:func:`mcpwm_new_soft_sync_src` 将返回一个指向已分配同步源的指针。否则,函数将返回错误代码。具体来说,当内存不足以支持分配的同步源时,将返回 :c:macro:`ESP_ERR_NO_MEM` 错误。需注意,为确保软件同步源能够正常工作,应预先调用 :cpp:func:`mcpwm_soft_sync_activate`。 - -相反,调用 :cpp:func:`mcpwm_del_sync_src` 函数将释放分配的同步源对象。此函数适用于所有类型的同步源。 - -.. _mcpwm-capture-timer-and-channels: - -MCPWM 捕获定时器和通道 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -MCPWM 组有一个专用定时器,用于捕获特定事件发生时的时间戳。捕获定时器连接了数个独立通道,每个通道都分配了各自的 GPIO。 - -调用 :cpp:func:`mcpwm_new_capture_timer` 函数,以配置结构体 :cpp:type:`mcpwm_capture_timer_config_t` 为参数,分配一个捕获定时器。结构体定义为: - -- :cpp:member:`mcpwm_capture_timer_config_t::group_id` 设置 MCPWM 组 ID,范围为 [0, ``MCPWM_GROUP_NUM`` - 1],其中 ``MCPWM_GROUP_NUM`` 是芯片上可用的 MCPWM 组数量。 -- :cpp:member:`mcpwm_capture_timer_config_t::clk_src` 设置捕获定时器的时钟源。 -- :cpp:member:`mcpwm_capture_timer_config_t::resolution_hz` 设置捕获定时器的预期分辨率。内部驱动将根据时钟源和分辨率设置合适的分频器。设置为 ``0`` 时,驱动会自己选取一个适当的分辨率,后续你可以通过 :cpp:func:`mcpwm_capture_timer_get_resolution` 查看当前定时器的分辨率。 - -.. only:: not SOC_MCPWM_CAPTURE_CLK_FROM_GROUP - - .. note:: - - 在 {IDF_TARGET_NAME} 中,:cpp:member:`mcpwm_capture_timer_config_t::resolution_hz` 参数无效,捕获定时器的分辨率始终等于 :cpp:enumerator:`MCPWM_CAPTURE_CLK_SRC_APB`。 - -.. only:: SOC_MCPWM_CAPTURE_CLK_FROM_GROUP - - .. note:: - - 定时器和捕获定时器共享 MCPWM 组时钟源。MCPWM 组的分频系数将以第一个分配的(捕获)定时器的分辨率来计算,驱动会从低到高寻找合适的分频系数。若分配多个(捕获)定时器时出现分频系数冲突,请调整分配定时器的顺序,按照目标分辨率的大小按序申请(捕获)定时器(从大到小或者从小到大)。更多相关内容请参阅 :ref:`mcpwm-resolution-config`。 - -分配成功后,:cpp:func:`mcpwm_new_capture_timer` 将返回一个指向已分配捕获定时器的指针。否则,函数将返回错误代码。具体来说,当 MCPWM 组中没有空闲捕获定时器时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。[1]_ - -接下来,可以调用 :cpp:func:`mcpwm_new_capture_channel` 函数,以一个捕获定时器句柄和配置结构体 :cpp:type:`mcpwm_capture_channel_config_t` 为参数,分配一个捕获通道。结构体定义为: - -- :cpp:member:`mcpwm_capture_channel_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。 -- :cpp:member:`mcpwm_capture_channel_config_t::gpio_num` 设置捕获通道使用的 GPIO 编号。 -- :cpp:member:`mcpwm_capture_channel_config_t::prescale` 设置输入信号的预分频器。 -- :cpp:member:`mcpwm_capture_channel_config_t::extra_capture_channel_flags::pos_edge` 和 :cpp:member:`mcpwm_capture_channel_config_t::extra_capture_channel_flags::neg_edge` 设置是否在输入信号的上升沿和/或下降沿捕获时间戳。 -- :cpp:member:`mcpwm_capture_channel_config_t::extra_capture_channel_flags::pull_up` 和 :cpp:member:`mcpwm_capture_channel_config_t::extra_capture_channel_flags::pull_down` 设置是否在内部拉高和/或拉低 GPIO。 -- :cpp:member:`mcpwm_capture_channel_config_t::extra_capture_channel_flags::invert_cap_signal` 设置是否取反捕获信号。 - -分配成功后,:cpp:func:`mcpwm_new_capture_channel` 将返回一个指向已分配捕获通道的指针。否则,函数将返回错误代码。具体来说,当捕获定时器中没有空闲捕获通道时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。 - -反之,调用 :cpp:func:`mcpwm_del_capture_channel` 和 :cpp:func:`mcpwm_del_capture_timer` 将释放已分配的捕获通道和定时器。 - -MCPWM 中断优先级 -~~~~~~~~~~~~~~~~~~~~~~~~ - -MCPWM 允许为 定时器、操作器、比较器、故障以及捕获事件分别配置中断,中断优先级由各自的 ``config_t::intr_priority`` 决定。且同一个 MCPWM 组中的事件共享同一个中断源。注册多个中断事件时,中断优先级需要保持一致。 - -.. note:: - - MCPWM 组注册多个中断事件时,驱动将以第一个事件的中断优先级作为 MCPWM 组的中断优先级。 - - -.. _mcpwm-timer-operations-and-events: - -定时器操作和事件 -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -更新定时器周期 -~~~~~~~~~~~~~~ - -定时器周期在创建定时器时就已经通过 :cpp:member:`mcpwm_timer_config_t::period_ticks` 被初始化过了。你还可以在运行期间,调用 :cpp:func:`mcpwm_timer_set_period` 函数来更新定时周期。新周期的生效时机由 :cpp:member:`mcpwm_timer_config_t::flags::update_period_on_empty` 和 :cpp:member:`mcpwm_timer_config_t::flags::update_period_on_sync` 共同决定。如果他们两个参数都是 ``false``, 那么新的定时周期会立即生效。 - -注册定时器事件回调 -~~~~~~~~~~~~~~~~~~~~~~~~ - -MCPWM 定时器运行时会生成不同的事件。若有函数需在特定事件发生时调用,则应预先调用 :cpp:func:`mcpwm_timer_register_event_callbacks`,将所需函数挂载至中断服务程序 (ISR) 中。驱动中定时器回调函数原型声明为 :cpp:type:`mcpwm_timer_event_cb_t`,其所支持的事件回调类型则列在 :cpp:type:`mcpwm_timer_event_callbacks_t` 中: - -- :cpp:member:`mcpwm_timer_event_callbacks_t::on_full` 设置定时器计数达到峰值时的回调函数。 -- :cpp:member:`mcpwm_timer_event_callbacks_t::on_empty` 设置定时器计数为零时的回调函数。 -- :cpp:member:`mcpwm_timer_event_callbacks_t::on_stop` 设置定时器停止时的回调函数。 - -由于上述回调函数是在 ISR 中调用的,因此,这些函数 **不应** 涉及 block 操作。可以检查调用 API 的后缀,确保在函数中只调用了后缀为 ``ISR`` 的 FreeRTOS API。 - -函数 :cpp:func:`mcpwm_timer_register_event_callbacks` 中的 ``user_data`` 参数用于保存用户上下文,将直接传递至各个回调函数。 - -此函数会在不启用 MCPWM 定时器的情况下延迟安装其中断服务。因此,需在调用 :cpp:func:`mcpwm_timer_enable` 函数前调用该函数,否则将返回 :c:macro:`ESP_ERR_INVALID_STATE` 错误。更多信息请参见 `启用和禁用定时器`_。 - -.. _mcpwm-enable-and-disable-timer: - -启用和禁用定时器 -~~~~~~~~~~~~~~~~~~~~~~~~ - -在对定时器进行 IO 控制前,需要预先调用 :cpp:func:`mcpwm_timer_enable` 函数启用定时器。这个函数将: - -* 将定时器的状态从 **init** 切换到 **enable**。 -* 若中断服务此前已通过 :cpp:func:`mcpwm_timer_register_event_callbacks` 函数延迟安装,则启用中断服务。 -* 若选择了特定时钟源(例如 PLL_160M 时钟),则获取相应的电源管理锁。更多信息请参见 :ref:`mcpwm-power-management`。 - -反之,调用 :cpp:func:`mcpwm_timer_disable` 会将定时器切换回 **init** 状态、禁用中断服务并释放电源管理锁。 - -启动和停止定时器 -~~~~~~~~~~~~~~~~~~~~ - -通过基本的 IO 控制,即可启动和停止定时器。使用不同的 :cpp:type:`mcpwm_timer_start_stop_cmd_t` 命令调用 :cpp:func:`mcpwm_timer_start_stop` 便可立即启动定时器,或在发生特定事件时停止定时器。此外,还可以通过配置,让定时器仅计数一轮。也就是说,在计数达到峰值或零后,定时器自行停止。 - -连接定时器和操作器 -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -调用 :cpp:func:`mcpwm_operator_connect_timer` 函数,连接分配的 MCPWM 定时器和 MCPWM 操作器。连接后,操作器即可将定时器作为时基,生成所需的 PWM 波形。需注意,MCPWM 定时器和操作器必须位于同一个组中。否则,将返回 :c:macro:`ESP_ERR_INVALID_ARG` 错误。 - - -.. _mcpwm-comparator-operations-and-events: - -比较器操作和事件 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -注册比较器事件回调 -~~~~~~~~~~~~~~~~~~~~~~~~ - -MCPWM 比较器可以在定时器计数器等于比较值时发送通知。若有函数需在比较事件发生时调用,则应预先调用 :cpp:func:`mcpwm_comparator_register_event_callbacks`,将所需函数挂载至中断服务程序 (ISR) 中。驱动中比较器回调函数原型声明为 :cpp:type:`mcpwm_compare_event_cb_t`,其所支持的事件回调类型则列在 :cpp:type:`mcpwm_comparator_event_callbacks_t` 中: - -- :cpp:member:`mcpwm_comparator_event_callbacks_t::on_reach` 设置当定时器计数器等于比较值时的比较器回调函数。 - -回调函数会提供类型为 :cpp:type:`mcpwm_compare_event_data_t` 的事件特定数据。由于上述回调函数是在 ISR 中调用的,因此,这些函数 **不应** 涉及 block 操作。可以检查调用 API 的后缀,确保在函数中只调用了后缀为 ``ISR`` 的 FreeRTOS API。 - -函数 :cpp:func:`mcpwm_comparator_register_event_callbacks` 中的 ``user_data`` 参数用于保存用户上下文,将直接传递至各个回调函数。 - -此函数会延迟安装 MCPWM 比较器的中断服务。中断服务只能通过 :cpp:type:`mcpwm_del_comparator` 移除。 - -.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR - - .. note:: - - 对于事件比较器,你无法通过该函数来注册回调函数,因为事件比较器触发产生任何中断事件。 - -设置比较值 -~~~~~~~~~~~~~~~~~ - -运行 MCPWM 比较器时,可以调用 :cpp:func:`mcpwm_comparator_set_compare_value` 设置比较值。需注意以下几点: - -- 重新设置的比较值可能不会立即生效。比较值的更新时间通过 :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_tez` 或 :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_tep` 或 :cpp:member:`mcpwm_comparator_config_t::flags::update_cmp_on_sync` 配置。 -- 请确保已经预先调用 :cpp:func:`mcpwm_operator_connect_timer` 将操作器连接至 MCPWM 定时器。否则,将返回 :c:macro:`ESP_ERR_INVALID_STATE` 错误。 -- 比较值不应超过定时器的计数峰值。否则,将无法触发比较事件。 - - -.. _mcpwm-generator-actions-on-events: - -生成器对事件执行的操作 -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -设置生成器对定时器事件执行的操作 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -单个生成器可以针对不同的定时器事件配置多种操作。为此,应针对每个事件-操作对分别调用 :cpp:func:`mcpwm_generator_set_action_on_timer_event`。每个操作的详细配置通过结构体 :cpp:type:`mcpwm_gen_timer_event_action_t` 指定。 - -- :cpp:member:`mcpwm_gen_timer_event_action_t::direction` 指定定时器计数方向,可以调用 :cpp:type:`mcpwm_timer_direction_t` 查看支持的方向。 -- :cpp:member:`mcpwm_gen_timer_event_action_t::event` 指定定时器事件,可以调用 :cpp:type:`mcpwm_timer_event_t` 查看支持的定时器事件。 -- :cpp:member:`mcpwm_gen_timer_event_action_t::action` 指定随即进行的生成器操作,可以调用 :cpp:type:`mcpwm_generator_action_t` 查看支持的操作。 - -可借助辅助宏 :c:macro:`MCPWM_GEN_TIMER_EVENT_ACTION` 构建定时器事件操作条目。 - -设置生成器对比较器事件执行的操作 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -单个生成器可以针对不同的比较器事件配置多种操作。为此,应针对每个比较事件-操作对分别调用 :cpp:func:`mcpwm_generator_set_action_on_compare_event`。每个操作的详细配置通过结构体 :cpp:type:`mcpwm_gen_compare_event_action_t` 指定。 - -- :cpp:member:`mcpwm_gen_compare_event_action_t::direction` 指定定时器计数方向,可以调用 :cpp:type:`mcpwm_timer_direction_t` 查看支持的方向。 -- :cpp:member:`mcpwm_gen_compare_event_action_t::comparator` 指定比较器句柄。有关分配比较器的方法,请参见 :ref:`mcpwm-comparators`。 -- :cpp:member:`mcpwm_gen_compare_event_action_t::action` 指定随即进行的生成器操作,可以调用 :cpp:type:`mcpwm_generator_action_t` 查看支持的操作。 - -可借助辅助宏 :c:macro:`MCPWM_GEN_COMPARE_EVENT_ACTION` 构建比较事件操作条目。 - -设置生成器对故障事件执行的操作 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -单个发生器可以配置为在故障事件发生时执行多个操作。要实现此功能,请针对每个期望的操作调用 :cpp:func:`mcpwm_generator_set_action_on_fault_event`。具体的操作由 :cpp:type:`mcpwm_gen_fault_event_action_t` 结构体描述。 - -- :cpp:member:`mcpwm_gen_fault_event_action_t::direction` 指定定时器计数方向,可以调用 :cpp:type:`mcpwm_timer_direction_t` 查看支持的方向。 -- :cpp:member:`mcpwm_gen_fault_event_action_t::fault` 指定用于触发器的故障。有关分配故障的方法,请参见 :ref:`mcpwm-faults`。 -- :cpp:member:`mcpwm_gen_fault_event_action_t::action` 指定随即进行的生成器操作,可以调用 :cpp:type:`mcpwm_generator_action_t` 查看支持的操作。 - -当生成器所属的操作器中没有空闲触发器时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。[1]_ - -触发器支持的故障仅为 GPIO 故障,当传入故障不为 GPIO 故障时,将返回 :c:macro:`ESP_ERR_NOT_SUPPORTED` 错误。 - -可借助辅助宏 :c:macro:`MCPWM_GEN_FAULT_EVENT_ACTION` 构建触发事件操作条目。 - -设置生成器对同步事件执行的操作 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -单个生成器可以针对不同的同步事件配置多种操作。为此,应针对每个同步事件-操作对分别调用 :cpp:func:`mcpwm_generator_set_action_on_sync_event`。每个操作的详细配置通过结构体 :cpp:type:`mcpwm_gen_sync_event_action_t` 指定。 - -- :cpp:member:`mcpwm_gen_sync_event_action_t::direction` 指定定时器计数方向,可以调用 :cpp:type:`mcpwm_timer_direction_t` 查看支持的方向。 -- :cpp:member:`mcpwm_gen_sync_event_action_t::sync` 指定用于触发器的同步源。有关分配同步源的方法,请参见 `MCPWM 同步源`_。 -- :cpp:member:`mcpwm_gen_sync_event_action_t::action` 指定随即进行的生成器操作,可以调用 :cpp:type:`mcpwm_generator_action_t` 查看支持的操作。 - -当生成器所属的操作器中没有空闲触发器时,将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。[1]_ - -无论同步为何种类型,触发器仅支持一种同步操作,如果多次设置同步操作,将返回 :c:macro:`ESP_ERR_INVALID_STATE` 错误。 - -可借助辅助宏 :c:macro:`MCPWM_GEN_SYNC_EVENT_ACTION` 构建触发事件操作条目。 - - -.. _mcpwm-classical-pwm-waveforms-and-generator-configurations: - -经典 PWM 波形的生成器配置 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -本节提供了一些生成器支持生成的经典 PWM 波形,同时提供用于生成这些波形的代码片段。总的来说: - -- 生成波形为 **对称波形** 还是 **不对称波形** 取决于 MCPWM 定时器的计数模式。 -- 波形对的 **激活电平** 取决于占空比较小的 PWM 波形的电平。 -- PWM 波形的周期取决于定时器的周期和计数模式。 -- PWM 波形的占空比取决于生成器的各种操作配置组合。 - -单边不对称波形 - 高电平 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - } - -单边不对称波形 - 低电平 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH))); - } - -脉冲位置不对称波形 -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. 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_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_TOGGLE))); - } - -双沿不对称波形 - 低电平有效 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. 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_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_HIGH))); - } - -双沿对称波形 - 低电平有效 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. 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_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_LOW))); - } - -双沿对称波形 - 互补 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. 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_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_DOWN, cmpb, MCPWM_GEN_ACTION_HIGH))); - } - - -.. _mcpwm-dead-time: - -死区 -^^^^^^^^^ - -在电力电子学中,常常会用到整流器和逆变器,这就涉及到了整流桥和逆变桥的应用。每个桥臂配有两个功率电子器件,例如 MOSFET、IGBT 等。同一桥臂上的两个 MOSFET 不能同时导通,否则会造成短路。实际应用中,在 PWM 波形显示 MOSFET 开关已关闭后,仍需要一段时间窗口才能完全关闭 MOSFET。因此,需要设置 :ref:`mcpwm-generator-actions-on-events`,在已生成的 PWM 波形上添加额外延迟。 - -死区驱动器的工作方式与 **装饰器** 类似。在 :cpp:func:`mcpwm_generator_set_dead_time` 函数的参数中,驱动接收主要生成器句柄 (``in_generator``),并在应用死区后返回一个新的生成器 (``out_generator``)。需注意,如果 ``out_generator`` 和 ``in_generator`` 相同,这表示 PWM 波形中的时间延迟是以“就地”的方式添加的。反之,如果 ``out_generator`` 和 ``in_generator`` 不同,则代表在原 ``in_generator`` 的基础上派生出了一个新的 PWM 波形。 - -结构体 :cpp:type:`mcpwm_dead_time_config_t` 中列出了死区相关的具体配置: - -- :cpp:member:`mcpwm_dead_time_config_t::posedge_delay_ticks` 和 :cpp:member:`mcpwm_dead_time_config_t::negedge_delay_ticks` 设置 PWM 波形上升沿和下降沿上的延迟时间,以 Tick 为单位。若将这两个参数设置为 0,则代表绕过死区模块。死区的 Tick 分辨率与通过 :cpp:func:`mcpwm_operator_connect_timer` 连接操作器的定时器相同。 -- :cpp:member:`mcpwm_dead_time_config_t::invert_output` 设置是否在应用死区后取反信号,以控制延迟边沿的极性。 - -.. warning:: - - 由于硬件限制,同一种 delay 模块(``posedge delay`` 或者 ``negedge delay``)不能同时被应用在不同的 MCPWM 生成器中。例如,以下配置是无效的: - - .. code:: c - - mcpwm_dead_time_config_t dt_config = { - .posedge_delay_ticks = 10, - }; - // 给 generator A 叠加上升沿 delay - mcpwm_generator_set_dead_time(mcpwm_gen_a, mcpwm_gen_a, &dt_config); - // NOTE: 下面的操作是无效的,不能将同一种 delay 应用于不同的 generator 上 - mcpwm_generator_set_dead_time(mcpwm_gen_b, mcpwm_gen_b, &dt_config); - - 然而,你可以为生成器 A 设置 ``posedge delay``,为生成器 B 设置 ``negedge delay``。另外,也可以为生成器 B 同时设置 ``posedge delay`` 和 ``negedge delay``,而让生成器 A 绕过死区模块。注意,如果对生成器 A 同时设置 ``negedge delay`` 和 ``posedge delay``,生成器 B 将无法正常工作。其中,生成器 A 为通过操作器句柄申请的第一个生成器,生成器 B 为通过操作器句柄申请的第二个生成器。 - -.. note:: - - 也可以通过设置 :ref:`mcpwm-generator-actions-on-events` 来生成所需的死区,通过不同的比较器来控制边沿位置。但是,如果需要使用经典的基于边沿延迟并附带极性控制的死区,则应使用死区子模块。 - - -.. _mcpwm-classical-pwm-waveforms-and-dead-time-configurations: - -经典 PWM 波形的死区配置 -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -本节提供了一些死区子模块支持生成的经典 PWM 波形,同时在图片下方提供用于生成这些波形的代码片段。 - -高电平有效互补 -~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. wavedrom:: /../_static/diagrams/mcpwm/deadtime_active_high_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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - } - - 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; - dead_time_config.flags.invert_output = true; - ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gena, genb, &dead_time_config)); - } - -低电平有效互补 -~~~~~~~~~~~~~~~~~~~~~~~~ - -.. 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - } - - 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)); - } - -高电平有效 -~~~~~~~~~~~ - -.. 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - } - - 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)); - } - -低电平有效 -~~~~~~~~~~ - -.. 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - } - - 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)); - } - -PWMA 上升沿延迟,绕过 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - } - - 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)); - } - -PWMB 下降沿延迟,绕过 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - } - - 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)); - - } - -PWMB 上升下降沿延迟,绕过 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_action_on_timer_event(gena, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(gena, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpa, MCPWM_GEN_ACTION_LOW))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(genb, - MCPWM_GEN_TIMER_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, MCPWM_GEN_ACTION_HIGH))); - ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(genb, - MCPWM_GEN_COMPARE_EVENT_ACTION(MCPWM_TIMER_DIRECTION_UP, cmpb, MCPWM_GEN_ACTION_LOW))); - } - - 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)); - } - - -.. _mcpwm-carrier-modulation: - -载波调制 -^^^^^^^^^^^^^^^^^^ - -MCPWM 操作器具有载波子模块,可以根据需要(例如隔离式数字电源应用中)使用变压器传递 PWM 输出信号,实现电机驱动器的电气隔离。在电机需要在全负荷下稳定运行时,各个 PWM 输出信号都将占空比稳定保持在 100% 左右。由于变压器无法直接耦合非交替信号,需要使用载波子模块调制信号,生成交流电波形,从而实现耦合。 - -调用 :cpp:func:`mcpwm_operator_apply_carrier`,并提供配置结构体 :cpp:type:`mcpwm_carrier_config_t`,配置载波子模块: - -- :cpp:member:`mcpwm_carrier_config_t::clk_src` 设置载波的时钟源。 -- :cpp:member:`mcpwm_carrier_config_t::frequency_hz` 表示载波频率,单位为赫兹。内部驱动将根据时钟源和载波频率设置合适的分频器。有关频率限制的相关内容请参阅 :ref:`mcpwm-resolution-config`。 -- :cpp:member:`mcpwm_carrier_config_t::duty_cycle` 表示载波的占空比。需注意,占空比仅支持的7种,分别为 0.125、0.25、0.375、0.5、0.625、0.75、0.875。 -- :cpp:member:`mcpwm_carrier_config_t::first_pulse_duration_us` 表示第一个脉冲的脉宽,单位为微秒。该脉冲的分辨率由 :cpp:member:`mcpwm_carrier_config_t::frequency_hz` 中的配置决定。第一个脉冲的脉宽不能为零,且至少为一个载波周期。脉宽越长,电感传导越快。 -- :cpp:member:`mcpwm_carrier_config_t::invert_before_modulate` 和 :cpp:member:`mcpwm_carrier_config_t::invert_after_modulate` 设置是否在调制前和调制后取反载波输出。 - -具体而言,可调用 :cpp:func:`mcpwm_operator_apply_carrier` 并将其配置为 ``NULL``,禁用载波子模块。 - - -.. _mcpwm-faults-and-brake-actions: - -故障检测和制动控制 -^^^^^^^^^^^^^^^^^^^^^^^^ - -MCPWM 操作器能够感知外部信号,接收有关电机故障、功率驱动器及其他连接设备的信息。这些故障信号封装在 MCPWM 故障对象中。 - -电机需配置故障模式以及检测到特定故障时的对应操作,例如拉低有刷电机的所有输出,或是锁定步进电机的电流状态等。此操作应使电机重回安全状态,降低故障导致损坏的可能性。 - -设置故障时操作器的制动模式 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -MCPWM 操作器对故障的响应方式为 **制动**。可以调用 :cpp:func:`mcpwm_operator_set_brake_on_fault`,为每个故障对象配置不同的制动模式。制动的相关配置包含在结构体 :cpp:type:`mcpwm_brake_config_t` 中: - -- :cpp:member:`mcpwm_brake_config_t::fault` 设置操作器响应的故障类型。 -- :cpp:member:`mcpwm_brake_config_t::brake_mode` 设置对应故障的制动模式,可以调用 :cpp:type:`mcpwm_operator_brake_mode_t` 查看支持的制动模式。在 :cpp:enumerator:`MCPWM_OPER_BRAKE_MODE_CBC` 模式下,操作器将在故障消失后自行恢复正常,可以通过 :cpp:member:`mcpwm_brake_config_t::cbc_recover_on_tez` 和 :cpp:member:`mcpwm_brake_config_t::cbc_recover_on_tep` 配置恢复时间。在 :cpp:enumerator:`MCPWM_OPER_BRAKE_MODE_OST` 模式下,即使故障消失,操作器也无法恢复正常。此时,需要调用 :cpp:func:`mcpwm_operator_recover_from_fault`,手动恢复操作器。 - -设置发生制动事件时的生成器操作 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -单个生成器可以针对不同的制动事件配置多种操作。为此,应针对每个期望的操作分别调用 :cpp:func:`mcpwm_generator_set_action_on_brake_event`。每个操作的详细配置通过结构体 :cpp:type:`mcpwm_gen_brake_event_action_t` 指定。 - -- :cpp:member:`mcpwm_gen_brake_event_action_t::direction` 指定定时器的方向,可以调用 :cpp:type:`mcpwm_timer_direction_t` 查看支持的方向。 -- :cpp:member:`mcpwm_gen_brake_event_action_t::brake_mode` 指定制动模式,可以调用 :cpp:type:`mcpwm_operator_brake_mode_t` 查看支持的制动模式。 -- :cpp:member:`mcpwm_gen_brake_event_action_t::action` 指定生成器操作,可以调用 :cpp:type:`mcpwm_generator_action_t` 查看支持的操作。 - -可借助辅助宏 :c:macro:`MCPWM_GEN_BRAKE_EVENT_ACTION` 构建制动事件操作条目。 - -注册故障事件回调 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -MCPWM 故障检测器支持在检测到实际故障或故障信号消失时发送通知。若有函数需在特定事件发生时调用,则应预先调用 :cpp:func:`mcpwm_fault_register_event_callbacks`,将所需函数挂载至中断服务程序 (ISR) 中。驱动中故障事件回调函数原型声明为 :cpp:type:`mcpwm_fault_event_cb_t`,其所支持的事件回调类型则列在 :cpp:type:`mcpwm_fault_event_callbacks_t` 中: - -- :cpp:member:`mcpwm_fault_event_callbacks_t::on_fault_enter` 设置检测到故障时调用的回调函数。 -- :cpp:member:`mcpwm_fault_event_callbacks_t::on_fault_exit` 设置故障消失后调用的回调函数。 - -由于上述回调函数在 ISR 中调用,因此,这些函数 **不应** 涉及 block 操作。可以检查调用 API 的后缀,确保在函数中只调用了后缀为 ``ISR`` 的 FreeRTOS API。 - -函数 :cpp:func:`mcpwm_fault_register_event_callbacks` 中的 ``user_data`` 参数用于保存用户上下文,将直接传递至各个回调函数。 - -此函数会延迟安装 MCPWM 故障的中断服务。中断服务只能通过 :cpp:type:`mcpwm_del_fault` 移除。 - -寄存器制动事件回调 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -MCPWM 操作器支持在进行制动操作前发送通知。若有函数需在特定事件发生时调用,则应预先调用 :cpp:func:`mcpwm_operator_register_event_callbacks`,将所需函数挂载至中断服务程序 (ISR) 中。驱动中制动事件回调函数原型声明为 :cpp:type:`mcpwm_brake_event_cb_t`,其所支持的事件回调类型则列在 :cpp:type:`mcpwm_operator_event_callbacks_t` 中: - -- :cpp:member:`mcpwm_operator_event_callbacks_t::on_brake_cbc` 设置操作器进行 **逐周期 (CBC)** 操作前调用的回调函数。 -- :cpp:member:`mcpwm_operator_event_callbacks_t::on_brake_ost` 设置操作器进行 **一次性 (OST)** 操作前调用的回调函数。 - -由于上述回调函数在 ISR 中调用,因此,这些函数 **不应** 涉及 block 操作。可以检查调用 API 的后缀,确保在函数中只调用了后缀为 ``ISR`` 的 FreeRTOS API。 - -函数 :cpp:func:`mcpwm_operator_register_event_callbacks` 中的 ``user_data`` 参数用于保存用户上下文,将直接传递至各个回调函数。 - -此函数会延迟安装 MCPWM 故障的中断服务。中断服务只能通过 :cpp:type:`mcpwm_del_operator` 移除。 - - -.. _mcpwm-generator-force-actions: - -生成器强制操作 -^^^^^^^^^^^^^^^^^^^^^^^ - -调用 :cpp:func:`mcpwm_generator_set_force_level`,使能软件强制决定运行时的生成器输出电平。相较于通过 :cpp:func:`mcpwm_generator_set_action_on_timer_event` 配置的其他事件操作,软件强制事件优先级最高。 - -- 设置 ``level`` 为 -1,代表禁用强制操作,生成器的输出电平重新交由事件操作控制。 -- 设置 ``hold_on`` 为 true,代表强制输出电平将保持不变,直到设置 ``level`` 为 -1 来移除该电平。 -- 设置 ``hole_on`` 为 false,代表强制输出电平仅在短时间有效,随后发生的任何事件都可以改变该电平。 - - -.. _mcpwm-synchronization: - -同步模块 -^^^^^^^^^^^^^^^ - -MCPWM 定时器接收到同步信号后,定时器将强制进入一个预定义的 **相位**,该相位由计数值和计数方向共同决定。调用 :cpp:func:`mcpwm_timer_set_phase_on_sync`,设置同步相位。同步相位配置定义在 :cpp:type:`mcpwm_timer_sync_phase_config_t` 结构体中: - -- :cpp:member:`mcpwm_timer_sync_phase_config_t::sync_src` 设置同步信号源。创建同步源对象的相关操作,请参见 `MCPWM 同步源`_。具体来说,当此参数设置为 ``NULL`` 时,驱动器将禁用 MCPWM 定时器的同步功能。 -- :cpp:member:`mcpwm_timer_sync_phase_config_t::count_value` 设置接收同步信号后加载至计数器的值。 -- :cpp:member:`mcpwm_timer_sync_phase_config_t::direction` 设置接收同步信号后的计数方向。 - -.. note:: - - 当 MCPWM 定时器在 :cpp:enumerator:`MCPWM_TIMER_COUNT_MODE_UP_DOWN` 模式下工作时,需要特别注意。在该模式下,计数器范围 ``[0 -> peak-1]`` 属于 **递增** 阶段, ``[peak -> 1]`` 属于 **递减** 阶段。因此,如果你将 :cpp:member:`mcpwm_timer_sync_phase_config_t::count_value` 设置为零,则可能还需要将 :cpp:member:`mcpwm_timer_sync_phase_config_t::direction` 设置为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP`。否则,计时器将继续维持递减阶段,计数值会下溢至峰值。 - -同理,:ref:`MCPWM 捕获定时器 ` 也支持同步。调用 :cpp:func:`mcpwm_capture_timer_set_phase_on_sync`,设置捕获定时器的同步相位。同步相位配置定义在 :cpp:type:`mcpwm_capture_timer_sync_phase_config_t` 结构体中: - -- :cpp:member:`mcpwm_capture_timer_sync_phase_config_t::sync_src` 设置同步信号源。关于如何创建一个同步源对象,请参见 `MCPWM 同步源`_。具体来说,当此参数设置为 ``NULL`` 时,驱动器将禁用 MCPWM 捕获定时器的同步功能。 -- :cpp:member:`mcpwm_capture_timer_sync_phase_config_t::count_value` 设置接收同步信号后加载至计数器的值。 -- :cpp:member:`mcpwm_capture_timer_sync_phase_config_t::direction` 设置接收同步信号后的计数方向。需注意,不同于 MCPWM 定时器,捕获定时器只支持 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP` 这一个计数方向。 - -使用 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 故障应与以上定时器位于同一组中 - .gpio_num = EXAMPLE_SYNC_GPIO, - .flags.active_neg = false, // 默认情况下,一个上升沿脉冲可以触发一个同步事件 - }; - 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, // 同步相位:目标计数值 - .direction = MCPWM_TIMER_DIRECTION_UP, // 同步相位:计数方向 - .sync_src = gpio_sync_source, // 同步源 - }; - for (int i = 0; i < 3; i++) { - ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timers[i], &sync_phase_config)); - } - } - - -.. _mcpwm-capture: - -捕获模块 -^^^^^^^^^^^^^ - -MCPWM 捕获的主要功能是记录捕获信号的脉冲边沿的有效时间。可以通过捕获得到脉宽,随后使用捕获回调函数将脉宽转换为其他物理量,如距离或速度。例如,在下图的无刷直流电机 (BLDC) 方案中,可以使用捕获子模块来确认来自霍尔传感器的转子位置。 - -.. figure:: ../../../_static/mcpwm-bldc-control.png - :align: center - :alt: 带霍尔传感器的 MCPWM 无刷直流电机 - - 带霍尔传感器的 MCPWM 无刷直流电机 - -通常,捕获定时器连接了数个捕获通道。有关资源分配的相关信息,请参见 `MCPWM 捕获定时器和通道`_。 - -注册捕获事件回调 -~~~~~~~~~~~~~~~~~~~~~~~~ - -MCPWM 捕获通道支持在信号上检测到有效边沿时发送通知。须调用 :cpp:func:`mcpwm_capture_channel_register_event_callbacks`,注册一个回调函数来获得捕获的定时器计数值。回调函数原型声明在 :cpp:type:`mcpwm_capture_event_cb_t` 中,可以调用 :cpp:type:`mcpwm_capture_event_callbacks_t` 查看支持的捕获回调: - -- :cpp:member:`mcpwm_capture_event_callbacks_t::on_cap` 设置检测到有效边沿时捕获通道的回调函数。 - -回调函数会针对特定事件,提供 :cpp:type:`mcpwm_capture_event_data_t` 类型的数据,由此,可以通过 :cpp:member:`mcpwm_capture_event_data_t::cap_edge` 和 :cpp:member:`mcpwm_capture_event_data_t::cap_value` 分别得到捕获信号的边沿及该捕获的计数值。随后,调用 :cpp:func:`mcpwm_capture_timer_get_resolution`,获取捕获定时器的分辨率,以将捕获计数转换为时间戳。 - -由于上述回调函数在 ISR 中调用,因此,这些函数 **不应** 涉及 block 操作。可以检查调用 API 的后缀,确保在函数中只调用了后缀为 ``ISR`` 的 FreeRTOS API。 - -函数 :cpp:func:`mcpwm_capture_channel_register_event_callbacks` 中的 ``user_data`` 参数用于保存用户上下文,将直接传递至各个回调函数。 - -此函数会延迟安装 MCPWM 捕获的中断服务。中断服务只能通过 :cpp:type:`mcpwm_del_capture_channel` 移除。 - -启用或禁用捕获通道 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -调用 :cpp:func:`mcpwm_new_capture_channel` 进行分配后,捕获通道不会自动启用。应调用 :cpp:func:`mcpwm_capture_channel_enable` 或 :cpp:func:`mcpwm_capture_channel_disable` 来启用或禁用该通道。如果在为通道注册事件回调时,由于调用了 :cpp:func:`mcpwm_capture_channel_register_event_callbacks`,致使延迟安装中断服务,则调用 :cpp:func:`mcpwm_capture_channel_enable` 启用通道时,也将启用中断服务。 - -启用或禁用捕获定时器 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -在对捕获定时器进行 IO 控制之前,需要首先调用 :cpp:func:`mcpwm_capture_timer_enable`,启用定时器。此函数将进行如下内部操作: - -* 将捕获定时器的状态从 **init** 切换到 **enable**。 -* 如果选择了一个特定时钟源(例如 APB 时钟),则获取一个对应的电源管理锁。更多信息请参见 :ref:`mcpwm-power-management`。 - -反之,调用 :cpp:func:`mcpwm_capture_timer_disable` 将使定时器驱动程序切换回 **init** 状态,并释放电源管理锁。 - -启动或停止捕获定时器 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -通过基本的 IO 控制,即可启动或停止捕获定时器。调用 :cpp:func:`mcpwm_capture_timer_start` 启动捕获定时器,或调用 :cpp:func:`mcpwm_capture_timer_stop` 立即停止捕获定时器。 - -触发软件捕获事件 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -某些场景下,可能存在需要软件触发“虚假”捕获事件的需求。此时,可以调用 :cpp:func:`mcpwm_capture_channel_trigger_soft_catch` 实现。需注意,此类“虚假”捕获事件仍然会触发中断,并从而调用捕获事件回调函数。 - -获得上一次锁存的捕获值 -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -如果不想在捕获事件回调函数中处理捕获值,而是想在其他地方处理,可以调用 :cpp:func:`mcpwm_capture_get_latched_value` 获得上一次锁存的捕获值。 - -.. only:: SOC_MCPWM_SUPPORT_ETM and SOC_ETM_SUPPORTED - - .. _mcpwm-etm-event-and-task: - - ETM 事件与任务 - ^^^^^^^^^^^^^^^^^^ - - MCPWM 的定时器和比较器可以产生事件,这些事件可以连接到 :doc:`ETM ` 模块。:cpp:type:`mcpwm_timer_etm_event_type_t` 和 :cpp:type:`mcpwm_comparator_etm_event_type_t` 中分别列出了 MCPWM 定时器和比较器能够产生的事件类型。用户可以通过调用 :cpp:func:`mcpwm_timer_new_etm_event` 或 :cpp:func:`mcpwm_comparator_new_etm_event` 来获得相应事件的 ETM event 句柄。 - - 关于如何将 MCPWM 事件连接到 ETM 通道中,请参阅 :doc:`ETM ` 文档。 - - .. _mcpwm-power-management: - -.. only:: not (SOC_MCPWM_SUPPORT_ETM and SOC_ETM_SUPPORTED) - - .. _mcpwm-power-management: - -电源管理 -^^^^^^^^^^^^^^^^ - -启用电源管理(即开启 :menuitem:`CONFIG_PM_ENABLE`)时,系统会在进入 Light-sleep 前调整 PLL 和 APB 频率。该操作有可能会改变 MCPWM 定时器的计数步长,导致计时偏差。 - -不过,驱动程序可以获取 :cpp:enumerator:`ESP_PM_NO_LIGHT_SLEEP` 类型的电源管理锁,防止系统进入 Light-sleep。每当驱动创建以 PLL 作为时钟源的 MCPWM 定时器实例时,都会在通过 :cpp:func:`mcpwm_timer_enable` 启用定时器时获取电源管理锁。反之,调用 :cpp:func:`mcpwm_timer_disable` 时,驱动程序释放锁。 - -同理,每当驱动创建 MCPWM 捕获定时器实例时,都会在通过 :cpp:func:`mcpwm_capture_timer_enable` 启用定时器时获取电源管理锁,并在调用 :cpp:func:`mcpwm_capture_timer_disable` 时释放锁。 - -.. only:: SOC_MCPWM_SUPPORT_SLEEP_RETENTION - - {IDF_TARGET_NAME} 支持在进入 **Light-sleep** 之前保留 MCPWM 寄存器中的内容,并在唤醒后恢复。也就是说程序不需要在 **Light-sleep** 唤醒后重新配置 MCPWM。 - - 该特性可以通过置位配置中的 :cpp:member:`mcpwm_timer_config_t::flags::allow_pd` 或 :cpp:member:`mcpwm_capture_timer_config_t::flags::allow_pd` 标志位启用。启用后驱动允许系统在 Light-sleep 时对 MCPWM 掉电,同时保存 MCPWM 的寄存器内容。它可以帮助降低 Light-sleep 时的功耗,但需要花费一些额外的存储来保存寄存器的配置。 - -.. _mcpwm-resolution-config: - -分辨率配置 -^^^^^^^^^^^^^ - -MCPWM 组拥有时钟分频器,并且部分子模块会有自己的时钟分频器,子模块最终的时钟频率取决于组时钟分频器和自身的分频器(如果有)。而组时钟分频器会影响所有子模块。当配置 MCPWM 子模块的时钟频率(或者叫分辨率)时,驱动会按照以下规则设置分频器: - -1. 如果子模块的时钟频率可以被时钟源整除,优先保证子模块的频率准确。 -2. 如果无法被时钟源整除,则尽可能保证组时钟的频率最高,将子模块频率调整到最接近的能够被时钟源整除的频率。 - -当多个 MCPWM 子模块共存时,需要考虑是否存在时钟分频器冲突。当出现组时钟分频器冲突,可以尝试调整子模块分配顺序。组分频器和子模块分频器范围详情请参见 [`TRM <{IDF_TARGET_TRM_EN_URL}#mcpwm>`__]。 - -.. _mcpwm-iram-safe: - -IRAM 安全 -^^^^^^^^^^^^^ - -默认情况下,禁用 cache 时,写入/擦除 flash 等原因将导致 MCPWM 中断延迟,事件回调函数也将延迟执行。在实时应用程序中,应避免此类情况。 - -因此,可以启用 Kconfig 选项 :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE`,该选项: - -* 支持在禁用 cache 时启用所需中断 -* 支持将 ISR 使用的所有函数存放在 IRAM 中 [2]_ -* 支持将驱动程序存放在 DRAM 中(以防其意外映射到 PSRAM 中) - -启用该选项可以保证 cache 禁用时的中断运行,但会相应增加 IRAM 占用。 - -另一个 Kconfig 选项 :menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` 也支持将常用的 IO 控制函数存放在 IRAM 中,以保证在禁用 cache 时可以正常使用函数。IO 控制函数如下所示: - -- :cpp:func:`mcpwm_comparator_set_compare_value` -- :cpp:func:`mcpwm_timer_set_period` - - -.. _mcpwm-thread-safety: - -线程安全 -^^^^^^^^^^^^^ - -驱动程序会确保工厂函数(如 :cpp:func:`mcpwm_new_timer`)的线程安全,使用时,可以直接从不同的 RTOS 任务中调用此类函数,无需额外锁保护。 - -驱动程序设置了临界区,以防函数同时在任务和 ISR 中调用。因此,以下函数支持在 ISR 上下文运行: - -- :cpp:func:`mcpwm_comparator_set_compare_value` -- :cpp:func:`mcpwm_timer_set_period` - -:ref:`mcpwm-resource-allocation-and-initialization` 中尚未提及的函数并非线程安全。在没有设置互斥锁保护的任务中,应避免调用这些函数。 - - -.. _mcpwm-kconfig-options: - -Kconfig 选项 -^^^^^^^^^^^^^^^ - -- :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE` 控制默认 ISR 处理程序能否在禁用 cache 的情况下工作。更多信息请参见 :ref:`mcpwm-iram-safe`。 -- :menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` 控制 MCPWM 控制函数的存放位置(IRAM 或 flash)。更多信息请参见 :ref:`mcpwm-iram-safe`。 -- :menuitem:`CONFIG_MCPWM_ENABLE_DEBUG_LOG` 用于启用调试日志输出。启用此选项将增加固件的二进制文件大小。 - -应用示例 --------------------- - -* :example:`peripherals/mcpwm/mcpwm_bdc_speed_control` 演示了如何使用两个特定 PWM 信号驱动有刷直流电机,通过光电编码器测量电机速度,并通过 PID 算法保持稳定的电机速度。 -* :example:`peripherals/mcpwm/mcpwm_bldc_hall_control` 演示了如何使用 MCPWM 外设,通过六步换向方案控制无刷直流电机,根据霍尔传感器的读数调整电机的旋转方向和速度。 -* :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04` 演示了如何使用 MCPWM 外设的捕获模块,解码超声波传感器 (HC-SR04) 的脉冲宽度信号。HC-SR04 传感器可根据脉冲的宽度测量距离。 -* :example:`peripherals/mcpwm/mcpwm_foc_svpwm_open_loop` 演示了如何使用 MCPWM 外设生成三对 PWM 信号,用于磁场定向控制 (FOC),从而驱动无刷直流电机或永磁同步电机,或驱动三相功率逆变器(使用开环 FOC 算法)。 -* :example:`peripherals/mcpwm/mcpwm_servo_control` 演示了如何使用 MCPWM 驱动程序发送 PWM 信号来控制 RC 伺服电机,它可以在 -60° 到 60° 间来回旋转。 -* :example:`peripherals/mcpwm/mcpwm_sync` 演示了如何使用 MCPWM 定时器生成三个 PWM 同步信号,可以通过 GPIO、Timer TEZ 或软件来同步定时器。 - - -API 参考 -------------- - -MCPWM API 分为以下几部分: - -* `定时器 API`_ -* `操作器 API`_ -* `比较器 API`_ -* `生成器 API`_ -* `故障 API`_ -* `同步 API`_ -* `捕获 API`_ -* `ETM API`_ -* `驱动类型`_ -* `HAL 类型`_ - - -定时器 API -^^^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_timer.inc - - -操作器 API -^^^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_oper.inc - - -比较器 API -^^^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_cmpr.inc - - -生成器 API -^^^^^^^^^^^ - -.. include-build-file:: inc/mcpwm_gen.inc - - -故障 API -^^^^^^^^ - -.. include-build-file:: inc/mcpwm_fault.inc - - -同步 API -^^^^^^^^ - -.. include-build-file:: inc/mcpwm_sync.inc - - -捕获 API -^^^^^^^^ - -.. include-build-file:: inc/mcpwm_cap.inc - - -ETM API -^^^^^^^ - -.. include-build-file:: inc/mcpwm_etm.inc - - -驱动类型 -^^^^^^^^ - -.. include-build-file:: inc/components/esp_driver_mcpwm/include/driver/mcpwm_types.inc - - -HAL 类型 -^^^^^^^^ - -.. include-build-file:: inc/components/esp_hal_mcpwm/include/hal/mcpwm_types.inc - - -.. [1] - 不同的 ESP 芯片上的 MCPWM 资源数量可能存在差异(如组、定时器、比较器、操作器、生成器、触发器等)。详情请参见 [`TRM <{IDF_TARGET_TRM_EN_URL}#mcpwm>`__]。当分配了超出资源数量的 MCPWM 资源时,在检测到没有可用硬件资源后,驱动程序将返回错误。请在进行 :ref:`mcpwm-resource-allocation-and-initialization` 时务必检查返回值。 - -.. [2] - 回调函数及其调用的子函数需手动存放进 IRAM 中。 diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/index.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/index.rst new file mode 100644 index 00000000000..be621411790 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/index.rst @@ -0,0 +1,228 @@ +=========================== +电机控制脉宽调制器 (MCPWM) +=========================== + +:link_to_translation:`en:[English]` + +从这里开始 +============ + +MCPWM 将计数器转换为时序精确的输出边沿。当 LEDC 的简单 PWM 已无法满足需求时,可使用 MCPWM:电机桥需要互补输出和死区,逆变器需要同步相位,传感器则需要精确测量脉宽。 + +最小可用的 MCPWM 设计由四个对象构成::doc:`定时器 ` 提供时间基准,:doc:`操作器 ` 管理波形资源,:doc:`比较器 ` 决定边沿位置,:doc:`生成器 ` 驱动 GPIO。其他模块均在此基础上扩展。 + +构建一路 PWM 输出 +================== + +初次生成 PWM 输出时,请按下图从左至右创建对象。主线上各阶段按角色配色:时间基准(蓝色)、操作器核心(紫色)、波形配置(青色)、启用与输出(绿色)。琥珀色节点为基础输出正常后按需添加的扩展,红色节点为安全制动。只有在所有输出动作均已配置完成后,才启动定时器。 + +.. mermaid:: + + flowchart LR + T1["1. 创建定时器
mcpwm_new_timer"]:::time + O1["2. 创建操作器
mcpwm_new_operator"]:::core + LINK["3. 连接时间基准
mcpwm_operator_connect_timer"]:::core + C1["4. 创建比较器
mcpwm_new_comparator"]:::wave + G1["5. 创建生成器
mcpwm_new_generator"]:::wave + A1["6. 描述边沿
mcpwm_generator_set_action_on_*_event"]:::wave + RUN["7. 启用并启动
mcpwm_timer_enable
mcpwm_timer_start_stop"]:::run + PIN["GPIO 输出 PWM"]:::output + + T1 --> O1 --> LINK --> C1 --> G1 --> A1 --> RUN --> PIN + + DT["死区
mcpwm_generator_set_dead_time"]:::optional + BR["故障与制动
mcpwm_new_*_fault
mcpwm_operator_set_brake_on_fault"]:::safety + SY["相位同步
mcpwm_new_*_sync_src
mcpwm_timer_set_phase_on_sync"]:::optional + CA["载波调制
mcpwm_operator_apply_carrier"]:::optional + + A1 -. 扩展 .-> DT + O1 -. 保护 .-> BR + T1 -. 对齐 .-> SY + O1 -. 调制 .-> CA + + classDef time fill:#dbeafe,stroke:#2563eb,color:#172554 + classDef core fill:#ede9fe,stroke:#7c3aed,color:#2e1065 + classDef wave fill:#cffafe,stroke:#0891b2,color:#164e63 + classDef run fill:#dcfce7,stroke:#16a34a,color:#14532d + classDef output fill:#bbf7d0,stroke:#15803d,color:#14532d + classDef optional fill:#fef3c7,stroke:#d97706,color:#78350f + classDef safety fill:#fee2e2,stroke:#dc2626,color:#7f1d1d + +下面这段代码创建一路 20 kHz、30% 占空比的 PWM 输出,可作为阅读后续各页前的整体参考。它展示了对象的创建顺序,也说明了运行时最常改动的其实是比较器,而不是重新配置整条链路。 + +.. code-block:: c + + mcpwm_timer_handle_t timer = NULL; + mcpwm_oper_handle_t oper = NULL; + mcpwm_cmpr_handle_t comparator = NULL; + mcpwm_gen_handle_t generator = NULL; + + // 1 MHz → 1 tick = 1 µs + // 50 ticks → 50 µs 周期 → 20 kHz + ESP_ERROR_CHECK(mcpwm_new_timer( + &(mcpwm_timer_config_t) { + .group_id = 0, + .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + .period_ticks = 50, + .count_mode = MCPWM_TIMER_COUNT_MODE_UP, + }, + &timer)); + + ESP_ERROR_CHECK(mcpwm_new_operator( + &(mcpwm_operator_config_t) { + .group_id = 0, + }, + &oper)); + ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer)); + + ESP_ERROR_CHECK(mcpwm_new_comparator( + oper, + &(mcpwm_comparator_config_t) { + .flags.update_cmp_on_tez = true, + }, + &comparator)); + // 15 / 50 = 30% 占空比 + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 15)); + + ESP_ERROR_CHECK(mcpwm_new_generator( + oper, + &(mcpwm_generator_config_t) { + .gen_gpio_num = 18, + }, + &generator)); + + // 定时器归零 → 输出 HIGH;比较器匹配 → 输出 LOW + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event( + generator, + MCPWM_GEN_TIMER_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, + MCPWM_TIMER_EVENT_EMPTY, + MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, + MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, + comparator, + MCPWM_GEN_ACTION_LOW))); + + ESP_ERROR_CHECK(mcpwm_timer_enable(timer)); + ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP)); + + // 运行时修改比较值即可调整占空比,无需重建生成器动作。 + // 25 / 50 = 50% 占空比 + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 25)); + +定时器的 ``resolution_hz`` 和 ``period_ticks`` 确定时序刻度;比较器的 ``compare_value`` 在该刻度中选择边沿位置;生成器动作 API 决定在定时器边界或比较器越过阈值时输出何种电平。这种分工也便于调参:改变定时器可调整频率,改变比较器可调整占空比或边沿位置,改变生成器动作可调整极性或波形形状。 + +波形配置完成后,调用 :cpp:func:`mcpwm_timer_enable()` 和 :cpp:func:`mcpwm_timer_start_stop()`。运行时应通过 :cpp:func:`mcpwm_comparator_set_compare_value()` 更新比较器,而不是重新配置生成器动作。仅在应用需要时添加对应扩展:半桥使用死区,安全路径使用故障与制动,相位对齐使用同步,隔离式驱动使用载波。 + +功能地图 +======== + +.. list-table:: + :header-rows: 1 + :widths: 16 36 34 24 + + * - 目标 + - 先看哪些页 + - 关键 API + - 典型应用 + * - 输出单路 PWM + - :doc:`定时器 ` -> :doc:`操作器 ` -> :doc:`比较器 ` + + :doc:`生成器 ` + - :cpp:func:`mcpwm_new_timer` + + :cpp:func:`mcpwm_new_comparator` + + ``mcpwm_generator_set_action_on_*_event`` + - 舵机、调光、基础功率控制 + * - 输出互补半桥 PWM + - :doc:`生成器 ` 中的死区小节 + :doc:`故障 ` + - :cpp:func:`mcpwm_generator_set_dead_time` + + :cpp:func:`mcpwm_operator_set_brake_on_fault` + - 半桥、逆变桥臂 + * - 多路同频对齐或移相 + - :doc:`同步 ` + - :cpp:func:`mcpwm_timer_set_phase_on_sync` + + :cpp:func:`mcpwm_new_timer_sync_src` + - 多相电机、并联变换器 + * - 测量输入脉宽或周期 + - :doc:`捕获 ` + - :cpp:func:`mcpwm_new_capture_timer` + + :cpp:func:`mcpwm_capture_channel_register_event_callbacks` + - HC-SR04、转速计、RC 输入 + * - 外设间硬件联动 + - :doc:`ETM ` + - :cpp:func:`mcpwm_timer_new_etm_event` + + :cpp:func:`mcpwm_new_event_comparator` + - ADC 触发、跨外设定时链路 + +本指南中每个页面介绍一个 MCPWM 模块: + +.. toctree:: + :maxdepth: 1 + + mcpwm_timer + mcpwm_operator + mcpwm_cmpr + mcpwm_gen + mcpwm_fault + mcpwm_sync + mcpwm_cap + mcpwm_etm + mcpwm_advanced + +资源与生命周期 +============== + +所有对象都属于一个 MCPWM 组。连接的定时器与操作器必须位于同一组;GPIO 故障源和 GPIO 同步源也只能在本组中使用。硬件资源有限,创建时可能返回 :c:macro:`ESP_ERR_NOT_FOUND`。 + +每个对象都由 ``mcpwm_new_*()`` 工厂函数创建并返回一个不透明句柄,由对应的 ``mcpwm_del_*()`` 函数释放,例如 :cpp:func:`mcpwm_new_timer()` 与 :cpp:func:`mcpwm_del_timer()`。先创建父对象,再创建子对象;释放时按相反顺序执行:先删除生成器/比较器,再删除操作器,最后删除定时器。删除定时器前必须禁用它;删除捕获定时器前必须删除其通道。 + +组时钟分频器由定时器共享,部分芯片的捕获定时器也共享它。按目标分辨率单调顺序(从高到低或从低到高)创建对象,可避免分频冲突。详见 :doc:`高级主题 `。 + +术语速查 +======== + +.. list:: + + - **TEZ:** Timer equals zero,定时器计数等于零时触发的事件。 + - **TEP:** Timer equals peak,定时器计数达到峰值时触发的事件。 + - **定时器(Timer):** MCPWM 的时间基准,决定频率和 Tick 刻度。 + - **操作器(Operator):** 连接定时器与输出逻辑的容器,管理比较器、生成器、制动、死区和载波。 + - **比较器(Comparator):** 当计数达到阈值时发出事件,常用于决定边沿位置和占空比。 + - **生成器(Generator):** 根据定时器/比较器/故障/同步事件输出 GPIO 电平。 + - **死区(Dead Time):** 在半桥上下管切换之间插入的非重叠时间,避免直通。 + - **故障(Fault):** 进入保护路径的异常源,可来自 GPIO 或软件。 + - **制动(Brake):** 故障触发后的输出安全策略。 + - **CBC:** Cycle By Cycle,故障有效时制动,清除后在周期边界自动恢复。 + - **OST:** One Shot,一次制动后保持锁存,需软件显式恢复。 + - **同步(Sync):** 在同步边沿把定时器加载到指定计数值和方向,以实现对齐或移相。 + - **捕获(Capture):** 对输入边沿打时间戳,用于测脉宽、周期或转速。 + +应用示例 +======== + +.. list:: + + - :example:`peripherals/mcpwm/mcpwm_servo_control`:RC 舵机的单路 PWM。 + - :example:`peripherals/mcpwm/mcpwm_bdc_speed_control`:有刷直流电机及速度反馈。 + - :example:`peripherals/mcpwm/mcpwm_bldc_hall_control`:使用霍尔传感器反馈进行 BLDC 换相。 + - :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04`:使用 HC-SR04 测量脉宽。 + - :example:`peripherals/mcpwm/mcpwm_sync`:GPIO、定时器和软件同步。 + - :example:`peripherals/mcpwm/mcpwm_foc_svpwm_open_loop`:开环 FOC 的三对互补 PWM。 + +API 参考 +======== + +通用类型 +-------- + +.. include-build-file:: inc/components/esp_driver_mcpwm/include/driver/mcpwm_types.inc +.. include-build-file:: inc/components/esp_hal_mcpwm/include/hal/mcpwm_types.inc diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_advanced.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_advanced.rst new file mode 100644 index 00000000000..1839bc578bd --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_advanced.rst @@ -0,0 +1,62 @@ +============== +MCPWM 高级主题 +============== + +.. contents:: + :local: + :depth: 2 + +分辨率与共享时钟 +================ + +``resolution_hz`` 是定时器 Tick 频率,每个 Tick 持续 ``1 / resolution_hz`` 秒。驱动从所选源时钟计算分频器:请求频率能整除时优先精确满足;不能整除时,优先使组时钟尽可能高,再选择最接近的子模块频率。 + +组分频器由定时器共享。在捕获时钟来自组时钟的芯片上,捕获定时器也共享它。多个对象需要不同分辨率时,按同一单调顺序(从高到低或从低到高)创建,避免任意混排。 + +下面这张时钟树图展示单一源时钟如何逐级分频展开。组分频器在整个组内只固定一次,每个定时器只在它之上再加自己的预分频器: + +.. mermaid:: + + flowchart LR + src["源时钟
(如 80 MHz)"]:::src -->|"÷ 组分频
整组共享"| grp["组时钟
(如 40 MHz)"]:::grp + grp -->|"÷ 定时器预分频"| pwm["PWM 定时器
resolution_hz = 10 MHz"]:::mod + grp -->|"÷ 捕获预分频"| cap["捕获定时器
resolution_hz = 20 MHz"]:::mod + classDef src fill:#fef3c7,stroke:#d97706,color:#78350f + classDef grp fill:#dbeafe,stroke:#2563eb,color:#172554 + classDef mod fill:#dcfce7,stroke:#16a34a,color:#14532d + +因为组分频器是共享的,只会为所有定时器确定一次。之后若再请求不同分辨率,就无法改动这个已固定的分频器,驱动会尽量让组时钟保持较高,并为新子模块选择最接近的预分频。 + +电源管理与睡眠 +============== + +启用电源管理后,:cpp:func:`mcpwm_timer_enable()` 和 :cpp:func:`mcpwm_capture_timer_enable()` 会持有 :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP` 锁,以确保定时器时钟频率稳定;调用 :cpp:func:`mcpwm_timer_disable()` 或 :cpp:func:`mcpwm_capture_timer_disable()` 释放该锁。 + +定时器配置和捕获定时器配置中都有一个同名的 :cpp:member:`allow_pd ` 字段(后者见 :cpp:type:`mcpwm_capture_timer_config_t`),设置它可允许睡眠时关闭 MCPWM 电源域,驱动会备份和恢复寄存器,但会消耗额外 RAM。此功能取决于目标芯片。 + +ISR 与线程安全 +============== + +定时器、比较器、故障、操作器制动和捕获回调都在 ISR 上下文运行,必须非阻塞并使用 ISR 安全的 RTOS 调用。组中第一个注册的回调确定共享中断优先级;后续事件应使用相同优先级。 + +:cpp:func:`mcpwm_new_timer()` 等工厂函数线程安全。:cpp:func:`mcpwm_timer_set_period()` 和 :cpp:func:`mcpwm_comparator_set_compare_value()` 可在 ISR 中调用。其他控制 API 通常不是线程安全的,多任务访问同一对象时应自行串行化。 + +缓存安全与实时操作 +================== + +正常情况下,缓存禁用时(如 Flash 操作期间)MCPWM 中断响应会被延迟。当回调必须在缓存禁用时继续运行时,启用 :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE`;这会将 ISR 所需代码放入 IRAM,对象放入 DRAM,增加内部 RAM 使用。 + +.. note:: + + 启用该选项后,即使缓存被禁用,MCPWM 中断仍会立即响应,不会被推迟。但中断处理链路上的所有代码——包括你注册的回调函数以及回调内部调用的所有子函数——都必须位于 IRAM 中:缓存关闭时 CPU 无法从 Flash 取指,一旦调用任何仍留在 Flash 中的函数,CPU 就会崩溃。该选项只负责把驱动自身的 ISR 代码放入 IRAM,你的回调函数及其调用的子函数需要自行显式放到 IRAM(例如用 ``IRAM_ATTR`` 修饰)。 + +:menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` 还会将 :cpp:func:`mcpwm_timer_set_period()` 和 :cpp:func:`mcpwm_comparator_set_compare_value()` 放入 IRAM,这样即使缓存关闭,这些调用仍可正常工作——例如你可以在缓存被禁用的上下文(如 Flash 擦写期间)中重新调整 PWM 周期或占空比,而无需等待缓存重新启用。 + +Kconfig 选项 +============== + +.. list:: + + - :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE` 启用缓存安全中断。 + - :menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` 将选定控制函数放入 IRAM。 + - :menuitem:`CONFIG_MCPWM_ENABLE_DEBUG_LOG` 强制 MCPWM 驱动编译并打印自身的调试日志,忽略全局日志设置,仅对驱动本身将运行时日志等级提升到冗长——不影响其他模块。代价是固件体积增大。 diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cap.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cap.rst new file mode 100644 index 00000000000..e514f62edad --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cap.rst @@ -0,0 +1,148 @@ +========================= +MCPWM 捕获:测量输入脉冲 +========================= + +.. contents:: + :local: + :depth: 2 + +捕获是独立的 MCPWM 路径:捕获定时器为捕获通道 GPIO 的边沿打时间戳,不需要 PWM 定时器、操作器、比较器或生成器,适用于回波脉冲、转速计、霍尔传感器和 RC 接收机。 + +它适合"把外部世界发生的时刻带进芯片里"。当你关心的是脉宽、周期、相位差或转速,而不是输出 PWM,本页就是 MCPWM 的另一条主线入口。 + +测量脉宽 +======== + +同时捕获两个边沿,保存上升沿时间戳,再用下降沿时间戳减去它。1 MHz 分辨率下,差值的单位直接是微秒。 + +.. code-block:: c + + mcpwm_cap_timer_handle_t cap_timer = NULL; + mcpwm_cap_channel_handle_t cap_channel = NULL; + ESP_ERROR_CHECK(mcpwm_new_capture_timer( + &(mcpwm_capture_timer_config_t) { + .group_id = 0, + .clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + }, &cap_timer)); + ESP_ERROR_CHECK(mcpwm_new_capture_channel(cap_timer, + &(mcpwm_capture_channel_config_t) { + .gpio_num = 6, + .prescale = 1, + .flags.pos_edge = true, + .flags.neg_edge = true, + }, &cap_channel)); + +仅分配对象并不会开始测量。还需要使能通道并启动捕获定时器: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_capture_channel_enable(cap_channel)); + ESP_ERROR_CHECK(mcpwm_capture_timer_enable(cap_timer)); + ESP_ERROR_CHECK(mcpwm_capture_timer_start(cap_timer)); + +:cpp:func:`mcpwm_capture_channel_enable()` 和 :cpp:func:`mcpwm_capture_timer_enable()` 负责捕获所需的系统服务准备,二者都尚未开始测量。:cpp:func:`mcpwm_capture_timer_start()` 才真正启动计数器,边沿开始被打上时间戳。 + +捕获到的边沿值通过回调送达应用,详见下一节。 + +.. figure:: /../_static/mcpwm/capture_measurement.svg + :align: center + :alt: 捕获上升沿与下降沿时间戳,相减得到高电平脉宽。 + + 捕获上升沿和下降沿时间戳,相减得到高电平脉宽。 + +两个配置结构体分开讲解: + +捕获定时器配置 +-------------- + +.. list:: + + - :cpp:member:`group_id ` — 捕获定时器从哪个 MCPWM 组分配。 + - :cpp:member:`clk_src ` — 捕获定时器的时钟源。:c:macro:`MCPWM_CAPTURE_CLK_SRC_DEFAULT` 适合绝大多数应用。当默认时钟源可能被关闭时需要显式选择其他源——例如低功耗场景下,若所选时钟被关断,捕获定时器会停止,时间戳就会失真。 + - :cpp:member:`resolution_hz ` — 捕获定时器的 Tick 频率。一个 Tick 持续 ``1 / resolution_hz`` 秒,1 MHz 即微秒级分辨率,直接决定每次捕获时间戳的精度。 + - :cpp:member:`allow_pd ` — 允许睡眠时关闭 MCPWM 电源域,在睡眠前后备份并恢复捕获寄存器,代价是额外占用 RAM。 + +捕获通道配置 +------------ + +.. list:: + + - :cpp:member:`gpio_num ` — 承载输入信号的 GPIO。 + - :cpp:member:`prescale ` — 捕获前对输入信号分频,有效输入频率为捕获时钟除以 ``prescale``。提高它可扩展可测周期范围,但会降低时间分辨率。 + - :cpp:member:`pos_edge ` 和 :cpp:member:`neg_edge ` — 捕获哪些边沿。示例同时捕获两个边沿,这正是脉宽测量所需的。 + - :cpp:member:`invert_cap_signal ` — 捕获前反相输入信号,让引脚上的逻辑 ``1`` 在捕获外设看来是 ``0``,反之亦然。 + - :cpp:member:`intr_priority ` — 捕获回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级。 + +.. note:: + + 捕获驱动会把 GPIO 配置为输入,但不会设置任何上拉或下拉电阻。如果输入信号并非主动驱动到两个电平,请调用 :cpp:func:`gpio_set_pull_mode()` 选择上拉或下拉方向,让引脚空闲时保持在你期望的电平。 + +捕获事件回调 +============ + +事件数据告知边沿类型和锁存计数值。真实应用中应将耗时工作留给任务。 + +.. code-block:: c + + static uint32_t rise_tick; + static bool IRAM_ATTR on_capture(mcpwm_cap_channel_handle_t channel, + const mcpwm_capture_event_data_t *edata, + void *user_data) + { + if (edata->cap_edge == MCPWM_CAP_EDGE_POS) { + rise_tick = edata->cap_value; + } else { + uint32_t width_ticks = edata->cap_value - rise_tick; + // 使用 ISR 安全方式将 width_ticks 通知任务 + } + return false; + } + + ESP_ERROR_CHECK(mcpwm_capture_channel_register_event_callbacks(cap_channel, + &(mcpwm_capture_event_callbacks_t) { .on_cap = on_capture }, NULL)); + +通过 :cpp:func:`mcpwm_capture_timer_get_resolution()` 获取实际分辨率后,再将 Tick 转换为时间。在捕获时钟与 MCPWM 组时钟共享的芯片上,应保持一致的请求分辨率顺序创建捕获和 PWM 定时器。 + +若要测转速或周期,可记录两次同类边沿(例如两次上升沿)的时间戳,相减得到周期 Tick 数,再结合实际分辨率换算为频率或转速。 + +实用控制 +======== + +:cpp:func:`mcpwm_capture_channel_trigger_soft_catch()` 生成软件捕获事件,常用于测试,也可借此把重要软件事件的发生时刻落到捕获时间轴上,与硬件边沿时间戳对齐;该调用同样会触发回调。:cpp:func:`mcpwm_capture_get_latched_value()` 在不注册回调的情况下也能读取最新时间戳。 + +:cpp:func:`mcpwm_capture_timer_stop()` 使计数器停止,:cpp:func:`mcpwm_capture_channel_disable()` 可关闭单个输入,停止定时器则关闭整个测量引擎。删除对象前,先调用 :cpp:func:`mcpwm_capture_timer_disable()` 撤销 :cpp:func:`mcpwm_capture_timer_enable()` 做的准备工作。 + +捕获定时器同步 +============== + +捕获定时器默认自由运行,计数值的“零点”是任意的,时间戳只能互相比较。同步让运行中的捕获定时器在同步边沿到来时加载一个指定计数值,从而把时间戳对应到有意义的参考系。 + +最常见的场景是让捕获定时器与 PWM 定时器对齐相位:以 PWM 定时器每周期零(TEZ)发出的同步为源、计数值设为 0,捕获定时器每个周期归零,捕获时间戳就直接表示周期内的相位。这在电机控制或功率变换中尤为重要——霍尔、编码器或电流检测的反馈边沿只有落在 PWM 周期的具体相位才有意义。 + +同步源与 PWM 定时器共用(GPIO、软件、定时器三种,须与捕获定时器同组),接收端用 :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()` 配置: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_capture_timer_set_phase_on_sync(cap_timer, + &(mcpwm_capture_timer_sync_phase_config_t) { + .sync_src = timer_a_sync, // 由 mcpwm_new_timer_sync_src() 创建的同步源 + .count_value = 0, + .direction = MCPWM_TIMER_DIRECTION_UP, + })); + +.. list:: + + - :cpp:member:`sync_src ` — 同步源,设为 ``NULL`` 可解除同步。 + - :cpp:member:`count_value ` — 同步边沿到达时加载的计数值。 + - :cpp:member:`direction ` — 加载后的计数方向,捕获定时器只向上计数,固定为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP`。 + +软件同步与 GPIO 同步源也可让捕获定时器建立已知起点或对齐外部参考,同步源的创建与其余用法详见 :doc:`同步 `。 + +API 参考 +======== + +MCPWM 捕获驱动函数 +------------------ + +.. include-build-file:: inc/mcpwm_cap.inc diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cmpr.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cmpr.rst new file mode 100644 index 00000000000..c3ec6dd172a --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_cmpr.rst @@ -0,0 +1,116 @@ +================================= +MCPWM 比较器:将比例转换为边沿 +================================= + +.. contents:: + :local: + :depth: 2 + +当定时器计数达到 ``cmp_ticks`` 时,比较器产生事件;生成器再将该事件转换为 GPIO 电平变化。通常在向上计数 PWM 中,比较 Tick 数就是高电平时间。 + +比较器的实际意义是把"我想要多宽、多晚、多早出现一个边沿"转换成硬件里的具体时刻。运行中的占空比调节,通常就是不断更新比较器阈值。 + +设置 30% 占空比 +=============== + +从已有操作器创建比较器,再设置阈值。对 :doc:`定时器 ` 中 50 Tick 的周期,15 Tick 即为 30%。下图展示了比较事件在 Tick 15 处触发——生成器可用此事件结束高电平脉冲。 + +.. figure:: /../_static/mcpwm/compare_event.svg + :align: center + :alt: 定时器向上计数;比较器在 Tick 15 触发。生成器将其转换为下降沿。 + + 定时器向上计数;比较器在 Tick 15 触发。生成器将其转换为下降沿。 + +.. code-block:: c + + mcpwm_cmpr_handle_t comparator = NULL; + mcpwm_comparator_config_t comparator_config = { + .flags.update_cmp_on_tez = true, // 仅在周期边界改变占空比 + }; + ESP_ERROR_CHECK(mcpwm_new_comparator(oper, &comparator_config, &comparator)); + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 15)); + +比较器配置除 flags 外只有一个字段: + +.. list:: + + - :cpp:member:`intr_priority ` — :cpp:member:`on_reach ` 回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级;当回调需要抢占其他中断时可适当提高。 + - :cpp:member:`flags ` — 即下文介绍的更新点。示例启用了 :cpp:member:`update_cmp_on_tez `,这也是在周期边界改变占空比的常见做法。 + +运行时百分比占空比的计算公式为 ``period_ticks * percent / 100``,结果必须不超过定时器周期。 + +这也是为什么改变占空比应优先改比较器,而不是改生成器动作:动作定义的是波形规则,比较器定义的是当前边沿位置。前者更像拓扑,后者更像运行参数。 + +为何要推迟更新 +============== + +立即更新比较器可能改变当前周期中的边沿。:cpp:member:`update_cmp_on_tez ` 会等待计数归零,:cpp:member:`update_cmp_on_tep ` 会等待计数到达峰值,:cpp:member:`update_cmp_on_sync ` 则等待同步事件。在向上计数或向下计数模式下,峰值即周期边界,与归零点几乎重合,因此 tez 与 tep 对应同一个更新点;仅在向上-向下计数模式下,峰值处于周期中点,tez 与 tep 才是两个不同的更新点。电机和电源转换通常应使用推迟更新。 + +两个比较器实现脉冲放置 +======================= + +一个比较器每周期产生一个边沿。在同一操作器中使用两个比较器,可在周期内任意位置放置脉冲——一个比较器打开脉冲,另一个关闭。适用于采样窗口、触发信号或非对称死区补偿。 + +这类需求在电机控制里很常见,例如想把 ADC 采样窗口放在开关噪声较小的位置,或者给外部器件输出一个不位于 PWM 边界的同步脉冲。 + +.. code-block:: c + + mcpwm_cmpr_handle_t cmp_a, cmp_b; + mcpwm_new_comparator(oper, &comparator_config, &cmp_a); + mcpwm_new_comparator(oper, &comparator_config, &cmp_b); + mcpwm_comparator_set_compare_value(cmp_a, 10); + mcpwm_comparator_set_compare_value(cmp_b, 30); + +比较事件也可用于精确的软件时间标记 +==================================== + +:cpp:member:`on_reach ` 回调在比较值到达时触发,适用于软件需要观察 PWM 周期中精确位置的场景。应在启动时间关键型工作前注册。回调在 ISR 上下文运行,应保持简短。 + +.. code-block:: c + + static bool IRAM_ATTR on_compare(mcpwm_cmpr_handle_t cmpr, + const mcpwm_compare_event_data_t *edata, + void *user_ctx) + { + // 通知任务或仅执行 ISR 安全工作 + return false; + } + + mcpwm_comparator_event_callbacks_t callbacks = { .on_reach = on_compare }; + ESP_ERROR_CHECK(mcpwm_comparator_register_event_callbacks(comparator, + &callbacks, NULL)); + +比较器的种类 +============ + +**操作器比较器** 通过 :cpp:func:`mcpwm_new_comparator()` 创建,其比较事件驱动生成器,从而塑造 PWM 输出。 + +.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR + + 还存在第二种: **事件比较器**,通过 :cpp:func:`mcpwm_new_event_comparator()` 创建。其比较事件只能经 :doc:`ETM ` 传给其他外设,不驱动生成器,也不会影响 PWM 输出。 + + .. note:: + + 名字容易误导:操作器比较器同样能产生 ETM 事件,因此事件比较器并非 MCPWM 接入 ETM 的唯一途径。事件比较器的定位是 *补充* 操作器比较器,而非替代。事件比较器不占用操作器比较器名额,也不会移动任何 PWM 边沿,当你只需要一个额外比较点作为纯定时标记时——例如在不扰动 PWM 波形的前提下,为 ADC 触发找到采样窗口——它就是更灵活的选择。 + + 两种比较器使用相同的比较值设置与 ETM 事件创建方式: + + .. code-block:: c + + mcpwm_event_comparator_config_t evt_cmp_cfg = {}; + mcpwm_cmpr_handle_t evt_cmpr = NULL; + ESP_ERROR_CHECK(mcpwm_new_event_comparator(oper, &evt_cmp_cfg, &evt_cmpr)); + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(evt_cmpr, 25)); + + esp_etm_event_handle_t evt = NULL; + ESP_ERROR_CHECK(mcpwm_comparator_new_etm_event(evt_cmpr, + &(mcpwm_cmpr_etm_event_config_t){ .event_type = MCPWM_CMPR_ETM_EVENT_EQUAL }, + &evt)); + +API 参考 +======== + +MCPWM 比较器驱动函数 +-------------------- + +.. include-build-file:: inc/mcpwm_cmpr.inc diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_etm.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_etm.rst new file mode 100644 index 00000000000..019d171ba88 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_etm.rst @@ -0,0 +1,66 @@ +============================= +MCPWM ETM:外设间的硬件级联动 +============================= + +.. contents:: + :local: + :depth: 2 + +.. only:: SOC_MCPWM_SUPPORT_ETM + + 事件任务矩阵(ETM)可将 MCPWM 定时器或比较器事件直接路由到 ETM 任务,避免 ISR 延迟。适用于其他外设必须在精确 PWM 相位响应的场景。 + + 先从定时器或比较器创建 ETM 事件,再创建目标外设的兼容任务,最后通过 ETM 通道连接两者。目标外设驱动文档定义了其任务和完整通道配置。关于 ETM 的整体用法——分配通道、把事件连接到任务——请参阅 :doc:`ETM ` 文档。 + + .. mermaid:: + + flowchart LR + T["MCPWM 定时器
TEZ/TEP 事件"]:::source --> E["ETM 通道"]:::route + C["MCPWM 比较器
比较事件"]:::source --> E + E --> D["目标
外设任务"]:::dest + classDef source fill:#dbeafe,stroke:#2563eb,color:#172554 + classDef route fill:#ede9fe,stroke:#7c3aed,color:#2e1065 + classDef dest fill:#dcfce7,stroke:#16a34a,color:#14532d + + 定时器会产生 ``TEZ`` (定时器归零)或 ``TEP`` (定时器到达峰值)事件。要获取一个事件: + + .. code-block:: c + + esp_etm_event_handle_t timer_event = NULL; + ESP_ERROR_CHECK(mcpwm_timer_new_etm_event(timer, + &(mcpwm_timer_etm_event_config_t) { + .event_type = MCPWM_TIMER_ETM_EVENT_TEZ, + }, &timer_event)); + // 创建目标 ETM task、分配 channel,然后连接: + // esp_etm_channel_connect(channel, timer_event, destination_task); + + 比较器提供 ``EQUAL`` 事件,每当定时器计数值等于比较器取值时触发。它能把事件固定到 PWM 周期中的任意相位,而不只是波峰或波谷。要获取一个事件: + + .. code-block:: cpp + + esp_etm_event_handle_t cmp_event = NULL; + ESP_ERROR_CHECK(mcpwm_comparator_new_etm_event(cmp, + &(mcpwm_cmpr_etm_event_config_t) { + .event_type = MCPWM_CMPR_ETM_EVENT_EQUAL, + }, &cmp_event)); + // esp_etm_channel_connect(channel, cmp_event, destination_task); + + 使用完毕后调用 :cpp:func:`esp_etm_del_event()` 释放事件。 + + 比较器事件一个非常常见的用途是触发 ADC 采样:把比较器设置在想要采样的相位点,再由比较器事件启动 ADC,使转换器在与 PWM 周期精确同步的相位点采样稳定、干净的波形。由于整条链路完全由硬件完成,采样点能无 CPU 参与、无 ISR 延迟地跟随 PWM。 + + .. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR + + 用哪种比较器提供事件很关键。 **运算比较器** (:cpp:func:`mcpwm_new_comparator()`)同时驱动生成器,其比较值会定义实际的 PWM 输出边沿,因此它的 ETM 事件被限制在对应的 PWM 边沿上——你无法要求它在不是自己产生的边沿相位上触发。 **事件比较器** (:cpp:func:`mcpwm_new_event_comparator()`)则是专门的 ETM 定时标记:它不驱动任何生成器,也不占用运算比较器槽位,因此可以在 PWM 周期内的 *任意* 相位触发,且对 PWM 波形毫无影响。这种自由度正是 ADC 触发所需要的,所以更推荐用事件比较器作为事件源——把采样点放在电压已稳定的位置,而不只是恰好有边沿的地方。 + + API 参考 + ======== + + MCPWM ETM 驱动函数 + ------------------ + + .. include-build-file:: inc/mcpwm_etm.inc + +.. only:: not SOC_MCPWM_SUPPORT_ETM + + {IDF_TARGET_NAME} 不支持 MCPWM ETM 事件。 diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_fault.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_fault.rst new file mode 100644 index 00000000000..d587d704640 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_fault.rst @@ -0,0 +1,84 @@ +================================== +MCPWM 故障:将保护信号接入 MCPWM +================================== + +.. contents:: + :local: + :depth: 2 + +故障对象表示异常条件。GPIO 故障适合过流比较器等硬件信号;软件故障可让应用逻辑触发同一保护路径。随后使用操作器 :ref:`制动 ` 定义输出响应。 + +故障模块存在的目的,是让保护路径尽可能不依赖软件轮询或任务调度。对于电机驱动和功率变换器,过流、互锁或急停通常要求在硬件路径里先把输出拉到安全状态,再由软件决定如何记录和恢复。 + +创建低电平有效 GPIO 故障源 +========================== + +故障源必须和将使用它的操作器位于同一组。引脚上拉/下拉属于独立 GPIO 配置;MCPWM 驱动不会为故障 GPIO 引脚自动使能内部上拉/下拉。如果故障信号在其他状态下并不驱动引脚电平,请自行用 :cpp:func:`gpio_set_pull_mode()` 配置上拉/下拉方向;启动功率级前应确保非故障电平明确。 + +.. code-block:: c + + mcpwm_fault_handle_t fault = NULL; + mcpwm_gpio_fault_config_t fault_config = { + .group_id = 0, + .gpio_num = 4, + .flags.active_level = 0, + }; + ESP_ERROR_CHECK(mcpwm_new_gpio_fault(&fault_config, &fault)); + +GPIO 故障配置中有几个字段值得了解: + +.. list:: + + - :cpp:member:`group_id ` — 故障所属的 MCPWM 组。必须与使用该故障的操作器所在组一致。 + - :cpp:member:`gpio_num ` — 承载故障信号的 GPIO。 + - :cpp:member:`active_level ` — 何种电平视为有效。示例使用 ``0``,即故障为低有效;无故障时引脚上拉方向应使其保持非有效(高电平)。驱动不会改动引脚的上下拉配置,请按需调用 :cpp:func:`gpio_set_pull_mode()` 选择上拉或下拉。 + - :cpp:member:`intr_priority ` — 故障事件回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级。 + +创建软件故障 +============ + +对于应用检测到的条件,可创建 :cpp:func:`mcpwm_new_soft_fault()`,并在条件发生时调用 :cpp:func:`mcpwm_soft_fault_activate()`,而无需接入 GPIO 故障引脚。激活是一次性的故障事件,输出策略仍由操作器 :ref:`制动机制 ` 决定。 + +.. note:: + + 激活前必须先用 :cpp:func:`mcpwm_operator_set_brake_on_fault()` 将软件故障绑定到某个操作器。驱动在 :cpp:func:`mcpwm_new_soft_fault()` 时并不会关联操作器;操作器关联及其制动模式都由该绑定调用设置,且一个软件故障只能绑定一个操作器。在绑定前调用 :cpp:func:`mcpwm_soft_fault_activate()` 属于未定义行为。 + +故障作为生成器动作的触发源 +========================== + +GPIO 故障也可通过 :cpp:func:`mcpwm_generator_set_action_on_fault_event()` 直接触发生成器动作。这是局部的边沿级响应——在故障边沿改变输出,但不会锁存安全状态。如需带恢复的持续制动,应使用操作器 :ref:`制动机制 `。 + +.. list-table:: + :header-rows: 1 + :widths: 18 34 28 + + * - 机制 + - 行为 + - 适合场景 + * - 生成器故障动作 + - 单个输出在故障边沿立即改变 + - 某一路输出的局部快速响应 + * - 操作器制动 + - 为整个输出级定义安全状态、是否锁存和恢复策略 + - 主保护路径、半桥/全桥安全关断 + +故障事件回调 +============ + +:cpp:member:`on_fault_enter ` 和 :cpp:member:`on_fault_exit ` 回调报告 GPIO 故障跳变,且仅 GPIO 故障可用——驱动会拒绝在软件故障上注册这些回调。软件故障会在硬件中立即触发制动,不会产生回调。这些回调在 ISR 上下文执行。应只进行时间戳记录或使用 ISR 安全的方式通知任务,在任务中做日志和恢复决策。 + +.. code-block:: c + + mcpwm_fault_event_callbacks_t cbs = { + .on_fault_enter = my_fault_enter_cb, + .on_fault_exit = my_fault_exit_cb, + }; + ESP_ERROR_CHECK(mcpwm_fault_register_event_callbacks(fault, &cbs, NULL)); + +API 参考 +======== + +MCPWM 故障驱动函数 +------------------ + +.. include-build-file:: inc/mcpwm_fault.inc diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_gen.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_gen.rst new file mode 100644 index 00000000000..8ebba1bdfa6 --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_gen.rst @@ -0,0 +1,327 @@ +============================ +MCPWM 生成器:生成 PWM 波形 +============================ + +.. contents:: + :local: + :depth: 2 + +生成器是最终的数字输出。它没有固定的"占空比"属性,而是配置在定时器和比较器事件发生时输出何种电平,因此可实现简单、非对称、互补和相位相关的波形。 + +这也是 MCPWM 与简单 PWM 外设的重要区别之一:它不是只让你填一个频率和占空比,而是让你直接描述"在什么时刻做什么动作"。代价是初学时概念更多,但换来的是对波形结构更高的控制力。 + +生成第一路 PWM +=============== + +这是对 :doc:`定时器 `、:doc:`操作器 ` 和 :doc:`比较器 ` 配置的收尾:定时器归零时输出高电平,比较器到达 15 时输出低电平。对于 50 Tick 的周期,输出高电平 15 Tick,即 30% 占空比。 + +应用场景:基础单路输出 +---------------------- + +适用于最基础的单路 PWM 输出,例如 RC 舵机控制信号、LED 调光,或后级电路已处理好极性和保护关系的简单占空比输出。 + +.. code-block:: c + + mcpwm_gen_handle_t generator = NULL; + mcpwm_generator_config_t gen_config = { .gen_gpio_num = 18 }; + ESP_ERROR_CHECK(mcpwm_new_generator(oper, &gen_config, &generator)); + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event( + generator, MCPWM_GEN_TIMER_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY, + MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_LOW))); + +.. figure:: /../_static/mcpwm/single_edge_asym_active_high.svg + :align: center + :alt: 向上计数、有效高 PWM:归零时置高,比较点置低。 + + 向上计数、有效高 PWM:归零时置高,比较点置低。 + +生成器配置很简单: + +.. list:: + + - :cpp:member:`gen_gpio_num ` — 输出 PWM 的 GPIO。同一操作器中另一个生成器若配置相同的动作,即可从同一时间基准驱动第二个引脚。 + - :cpp:member:`invert_pwm ` — 通过 GPIO 矩阵反相 PWM 信号。这是对最终输出的硬件反相,与修改动作不同;两者选其一,不要同时使用。 + +动作配置 +======== + +配置名称直接描述了行为:在某个事件发生时执行 :cpp:enumerator:`MCPWM_GEN_ACTION_HIGH `、:cpp:enumerator:`MCPWM_GEN_ACTION_LOW ` 或 :cpp:enumerator:`MCPWM_GEN_ACTION_TOGGLE `。辅助宏在调用处清晰展示三个关键选择——方向、事件源和输出电平。 + +对于向上计数定时器,:cpp:enumerator:`MCPWM_TIMER_EVENT_EMPTY ` 是归零边界,:cpp:enumerator:`MCPWM_TIMER_EVENT_FULL ` 在定时器到达峰值时触发。向上计数模式下峰值即周期,因此 ``FULL`` 落在周期边界;向上-向下模式下峰值是 ``period_ticks / 2``,因此 ``FULL`` 落在周期中间。比较动作使用比较器阈值。因此,第一个例子的含义是"周期开始时置高,计数到 15 时结束有效部分"。超出定时器范围的比较值不会产生比较事件。 + +每个动作都必须指定定时器方向,尽管这一选择只在向上-向下模式下才有可见差异。向上计数模式下计数器只向上走,因此为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP ` 配置的动作才会触发——仍需显式写出。在向上-向下模式中,两个边沿都重要时,应同时为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP ` 和 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_DOWN ` 配置动作;这样便能用一个比较器生成中心对齐 PWM。 + +经典波形案例 +============ + +下面的示例都基于上一节的第一路 PWM,复用相同的定时器、操作器和比较器对象,生成其他常用波形。 + +反转有效极性 +------------ + +某些栅极驱动器和 LED 为低有效,例如低有效驱动器使能、反相 LED 接法,或板级电路已固定反相的接口。与其增加 GPIO 反相,不如在周期边界置低、比较点置高: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event( + generator, MCPWM_GEN_TIMER_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_HIGH))); + +.. figure:: /../_static/mcpwm/single_edge_asym_active_low.svg + :align: center + :alt: 向上计数、有效低 PWM。当输出逻辑极性是设计的一部分时,修改动作而非接线。 + + 向上计数、有效低 PWM。当输出逻辑极性是设计的一部分时,修改动作而非接线。 + +在周期内放置脉冲 +---------------- + +当需要把某个短脉冲精确放到周期内部——例如 ADC 采样窗口、外设触发脉冲或锁存控制信号——两个比较值分别决定脉冲的开始和结束边沿: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator_a, MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator_b, MCPWM_GEN_ACTION_LOW))); + +.. figure:: /../_static/mcpwm/pulse_placement_asym.svg + :align: center + :alt: 脉冲放置:两个比较值之差就是脉冲宽度。 + + 脉冲放置:两个比较值之差就是脉冲宽度。 + +应使 ``comparator_a`` 小于 ``comparator_b``。同时以相同 Tick 数移动二者可改变位置而不改变宽度;只移动其中一个则改变宽度。硬件会放置两个边沿,因此比定时器回调更精确。 + +双沿非对称 PWM +-------------- + +当需要在一个周期内独立布置多个边沿、且高电平区间不必围绕周期中心对称时——例如某些非对称逆变器调制或定制驱动时序——使用两个生成器和两个比较器。每个生成器在每周期有自己的边沿,使高电平时间分布在周期边界两侧: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + gen_a, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, cmp_a, MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + gen_a, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, cmp_b, MCPWM_GEN_ACTION_LOW))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + gen_b, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, cmp_a, MCPWM_GEN_ACTION_LOW))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + gen_b, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, cmp_b, MCPWM_GEN_ACTION_HIGH))); + +.. figure:: /../_static/mcpwm/dual_edge_asym_active_low.svg + :align: center + :alt: 双沿非对称(边沿对齐)PWM:两个生成器产生互补输出,每周期两个边沿。 + + 双沿非对称(边沿对齐)PWM:两个生成器产生互补输出,每周期两个边沿。 + +中心对齐 PWM +------------ + +电机控制、逆变器等关注谐波表现的功率级通常更偏好中心对齐 PWM,因为它能带来更对称的开关时序和更低的谐波失真。创建定时器时选择 ``MCPWM_TIMER_COUNT_MODE_UP_DOWN``,随后在两个方向使用同一阈值: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_HIGH))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event( + generator, MCPWM_GEN_COMPARE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_DOWN, comparator, MCPWM_GEN_ACTION_LOW))); + +.. figure:: /../_static/mcpwm/dual_edge_sym_active_low.svg + :align: center + :alt: 中心对齐 PWM:向上和向下计数动作在周期中心两侧形成对称边沿。 + + 中心对齐 PWM:向上和向下计数动作在周期中心两侧形成对称边沿。 + +一次完整的向上-向下周期会到达峰值后回到零点,计算频率时需计入两个计数阶段。为第二个生成器配置相反动作可形成逻辑互补输出: + +.. figure:: /../_static/mcpwm/dual_edge_sym_complementary.svg + :align: center + :alt: 仅靠互补生成器动作不含死区,不能直接连接到功率级。 + + 仅靠互补生成器动作不含死区,不能直接连接到功率级。 + +.. warning:: + + 逻辑互补输出不等于可直接驱动半桥的安全互补输出。只要上下管存在关断延迟,就必须继续配置死区,并在实际栅极处验证无重叠导通。 + +占空比调整 +========== + +改变占空比时修改比较器阈值,而不是生成器动作。对于向上计数的有效高波形,``compare_value / period_ticks`` 即为占空比。应选择足够高的定时器分辨率,使一个 Tick 满足应用所需的调节精度。 + +.. code-block:: c + + mcpwm_comparator_set_compare_value(comparator, 25); // 50 % + +强制电平 +======== + +调节比较器阈值改变的是正常占空比。若需要临时接管输出、忽略所有事件动作而直接固定某个电平时,可改用强制电平接口。:func:`mcpwm_generator_set_force_level` 有两个关键参数:``level`` 和 ``hold_on``。 + +- ``level``(第二个参数)指定要强制输出的原始生成器电平:``0`` 或 ``1`` 会覆盖所有事件动作,``-1`` 则解除强制并恢复事件控制。 +- ``hold_on``(第三个参数)决定强制电平持续多久:``true`` 会一直保持直到下一次调用解除;``false`` 则允许下一个事件动作将其覆盖。 + +例如 ``mcpwm_generator_set_force_level(generator, 0, true)`` 会覆盖所有事件动作并保持原始生成器为低电平。强制电平位于死区与 GPIO 反相之前,应使用示波器确认实际引脚电平。 + +强制电平常用于上电自检、故障后的临时安全输出、切换工作模式时的过渡状态,而不是长期替代正常 PWM 配置。 + +对于半桥,添加第二个生成器并配置 :ref:`死区模块 ` 以产生非重叠互补输出。 + +.. _mcpwm-dead-time: + +死区与半桥驱动 +============== + +死区会延迟输出边沿,在半桥两个开关之间留出同时关闭的短暂间隔,以补偿晶体管关断延迟并降低直通风险。连接功率级前应配置并验证死区。 + +半桥通过高边与低边两个开关将直流母线连接到负载。两个开关通常都采用 N 沟道 MOSFET:低边源极接 GND、易于驱动,而高边源极随输出摆动,其栅极需要高于母线电压的驱动电压。MCPWM 输出是 3.3 V 逻辑信号,不能直接驱动栅极。以 IRS2101 为例,它使用独立的低压驱动电源 VCC(通常为 10-20 V),COM 接功率地。自举二极管应从 VCC 接到 VB,外部自举电容接在 VB 与 VS 之间,VS 必须连接到 OUT 半桥开关节点。高边输出为 HO,低边输出为 LO,两者都应通过栅极电阻连接到对应 MOSFET 的栅极。VCC 不是高压直流母线:低边开关导通时,自举二极管从受调节的驱动电源为自举电容充电。若两个开关同时开通和关断,仍在关断的开关会与已经开通的开关重叠导通,使母线经两个开关直通到地。死区留出两者同时关断的间隔,让下一个开关在前一个完全关断之后再开通: + +.. figure:: /../_static/mcpwm/half_bridge_dead_time.svg + :align: center + :alt: 带自举电源和死区对比的半桥栅极驱动电路。 + + 带自举电源和死区对比的半桥栅极驱动电路。 + +创建互补输出 +------------ + +在同一操作器中创建两个生成器。将生成器 A 的上升沿延迟后输出到自身,再将其下降沿延迟并反相后输出到生成器 B。 + +.. note:: + + 这里约定,生成器 A 是通过操作器句柄申请的第一个生成器,生成器 B 是通过操作器句柄申请的第二个生成器。 + +.. code-block:: c + + mcpwm_dead_time_config_t dead_time = { .posedge_delay_ticks = 2 }; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_a, &dead_time)); + dead_time = (mcpwm_dead_time_config_t) { + .negedge_delay_ticks = 2, + .flags.invert_output = true, + }; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_b, &dead_time)); + +.. figure:: /../_static/mcpwm/deadtime_active_high_complementary.svg + :align: center + :alt: 带死区间隔的互补 PWM。 + + 带死区间隔的互补 PWM。 + +理解路由与参数 +-------------- + +:func:`mcpwm_generator_set_dead_time(in_generator, out_generator, config) ` 将死区视为一个小型信号处理级。两个生成器句柄相同会原地改变该输出;将 ``gen_a`` 作为输入、 ``gen_b`` 作为输出则从 A 派生 B,这正是互补例子共享一个 PWM 源的方式。 + +:cpp:member:`posedge_delay_ticks ` 延迟上升沿,:cpp:member:`negedge_delay_ticks ` 延迟下降沿,单位为连接定时器的 Tick。例如分辨率为 10 MHz 时,2 Tick 等于 200 ns。下图展示了基本效果:``pwm_A`` 的上升沿被延迟(RED),``pwm_B`` 的下降沿被延迟(FED)。应先采用开关管和栅极驱动器数据手册中的最大关断延迟并留出余量;在晶体管栅极实测后,确认工艺、温度和布局仍有足够余量,才可减小该值。两个延迟均设为零可旁路死区模块。:cpp:member:`invert_output ` 在该级之后改变极性。 + +.. figure:: /../_static/mcpwm/deadtime_active_high.svg + :align: center + :alt: 基本死区效果:上升沿延迟(RED)和下降沿延迟(FED)。 + + 基本死区效果:上升沿延迟(RED)和下降沿延迟(FED)。 + +每个操作器的资源限制 +-------------------- + +每个操作器仅有一份上升沿延迟和一份下降沿延迟资源,不能将同一类延迟分别分配给两个生成器。下面的配置两次请求同一上升沿资源,因此无效: + +.. code-block:: c + + mcpwm_dead_time_config_t rise_delay = { .posedge_delay_ticks = 10 }; + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_a, &rise_delay)); + // 无法再分配第二份独立的上升沿延迟。 + ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_b, gen_b, &rise_delay)); + +可以将上升沿延迟分配给 A、下降沿延迟分配给 B;也可以让 A 旁路模块,而让 B 使用两种延迟资源。如果第一个生成器同时使用两种延迟资源,另一个生成器便不能使用死区。 + +更多输出模式 +------------ + +上面的互补配置是半桥的常用起点。交换输出反相设置可使两路输出均为低有效,同时保留非重叠时间: + +.. figure:: /../_static/mcpwm/deadtime_active_low_complementary.svg + :align: center + :alt: 低有效互补输出。时序资源相同,仅改变死区后的输出极性。 + + 低有效互补输出。时序资源相同,仅改变死区后的输出极性。 + +仅有一个通道需要边沿延迟时,死区同样有用。向其中一个输出传入零延迟配置使其旁路,再将可用延迟应用到另一个输出: + +.. figure:: /../_static/mcpwm/deadtime_reda_bypassb.svg + :align: center + :alt: 延迟 A 的上升沿,B 旁路死区。这不是互补半桥,而是独立的边沿放置工具。 + + 延迟 A 的上升沿,B 旁路死区。这不是互补半桥,而是独立的边沿放置工具。 + +.. figure:: /../_static/mcpwm/deadtime_redb_fedb_bypassa.svg + :align: center + :alt: 旁路 A,延迟 B 的两个边沿,并占用两种延迟资源。 + + 旁路 A,延迟 B 的两个边沿,并占用两种延迟资源。 + +单个边沿延迟也可独立应用。下图展示了仅对 B 的下降沿延迟,A 被旁路,仅使用 FED 资源: + +.. figure:: /../_static/mcpwm/deadtime_fedb_bypassa.svg + :align: center + :alt: 仅对 B 应用下降沿延迟,A 保持不变。使用一个延迟资源。 + + 仅对 B 应用下降沿延迟,A 保持不变。使用一个延迟资源。 + +输出反相时,死区行为随之变化。下图展示了基本延迟的低有效版本,反相标志翻转了两个输出的极性: + +.. figure:: /../_static/mcpwm/deadtime_active_low.svg + :align: center + :alt: 低有效死区:相同的延迟资源,但输出极性在延迟级后被反相。 + + 低有效死区:相同的延迟资源,但输出极性在延迟级后被反相。 + +.. note:: + + 若波形的每个边沿都需要独立调整位置,应使用两个比较器和生成器动作。需求明确是"延迟边沿并控制极性"时,死区模块更合适。 + +在安全边界更新 +-------------- + +若要让改变仅在安全边界生效,请设置操作器的 :cpp:member:`update_dead_time_on_tez `、:cpp:member:`update_dead_time_on_tep ` 或 :cpp:member:`update_dead_time_on_sync `。 + +.. note:: + + 应探测两个实际栅极引脚:GPIO 反相、载波调制和栅极驱动器极性都可能改变晶体管端看到的信号。当多个环节都会反转信号时,两次反相可能相互抵消,软件里看着正确,硬件却并非预期行为,务必以真实波形为准进行验证。 + +其他事件源 +============ + +生成器也可直接响应 GPIO 故障和同步事件: + +.. list:: + + - :cpp:func:`mcpwm_generator_set_action_on_fault_event()` — 立即硬件响应 GPIO 故障。会占用有限的操作器触发槽。 + - :cpp:func:`mcpwm_generator_set_action_on_sync_event()` — 在同步边沿转换。每个生成器只有一个同步动作槽。 + - :cpp:func:`mcpwm_generator_set_action_on_brake_event()` — 操作器 :ref:`制动 ` 期间各生成器的输出状态。需要按制动模式和定时器方向分别设置,完整示例见 :ref:`故障连接 `。 + +对于安全策略和持续制动,应使用操作器 :ref:`制动机制 `。生成器故障动作适合局部的边沿级响应;制动则为整个输出级定义安全状态和恢复行为。 + +API 参考 +======== + +MCPWM 生成器驱动函数 +-------------------- + +.. include-build-file:: inc/mcpwm_gen.inc diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_operator.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_operator.rst new file mode 100644 index 00000000000..c6a486dce6d --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_operator.rst @@ -0,0 +1,187 @@ +========================= +MCPWM 操作器:组装输出级 +========================= + +.. contents:: + :local: + :depth: 2 + +操作器位于定时器与生成器之间,管理比较器、生成器动作、制动、死区路由和载波调制。一个定时器可驱动同组的多个操作器,而一个操作器只连接一个定时器。 + +如果把定时器看成时钟源,操作器就是一套输出级的资源容器。它让多个输出共享同一时间基准,同时把保护、死区和载波这类功率级特性收拢到一个地方管理。 + +连接基本模块 +============ + +操作器必须与定时器在同一组。先通过 :cpp:func:`mcpwm_operator_connect_timer()` 创建并连接操作器;连接前,生成器不能使用定时器事件。 + +.. code-block:: c + + mcpwm_oper_handle_t oper = NULL; + mcpwm_operator_config_t oper_config = { + .group_id = 0, + .flags.update_gen_action_on_tez = true, + .flags.update_dead_time_on_tez = true, + }; + ESP_ERROR_CHECK(mcpwm_new_operator(&oper_config, &oper)); + ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer)); + +操作器配置很小,但有两个字段没有出现在示例中: + +.. list:: + + - :cpp:member:`group_id ` — 操作器从哪个 MCPWM 组分配。它必须与定时器所在组一致,因为操作器只能连接同组内的定时器。 + - :cpp:member:`intr_priority ` — 制动事件回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级;当制动通知需要抢占其他中断时可适当提高。 + +``flags`` 用于选择新的生成器动作和死区配置在何时生效。默认全部关闭,改动会立即生效——可能在 PWM 周期中间: + +.. list:: + + - :cpp:member:`update_gen_action_on_tez `、:cpp:member:`update_gen_action_on_tep ` 和 :cpp:member:`update_gen_action_on_sync ` — 将生成器动作的改动缓冲到计数归零、峰值或同步事件时。 + - :cpp:member:`update_dead_time_on_tez `、:cpp:member:`update_dead_time_on_tep ` 和 :cpp:member:`update_dead_time_on_sync ` — 死区改动同样缓冲,更新点规则见 :ref:`死区 `。 + +对于运行中的功率级,应使用归零(``tez``)、峰值(``tep``)或同步点更新,以避免部分周期。 + +一个定时器,多个操作器 +====================== + +同一定时器可驱动多个操作器,每个操作器产生不同的波形。这适用于多相逆变器或多个同频但独立占空比的电机。 + +反过来,一个操作器只能连接一个定时器,因此该操作器下的所有比较器和生成器天然共享同一时间基准。这正是同相、互补或成对输出容易实现的原因。 + +.. code-block:: c + + mcpwm_oper_handle_t oper_b = NULL; + mcpwm_operator_config_t oper_config_b = { + .group_id = 0, + .flags.update_gen_action_on_tez = true, + }; + ESP_ERROR_CHECK(mcpwm_new_operator(&oper_config_b, &oper_b)); + ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper_b, timer)); + // 在 oper_b 下创建独立的比较器和生成器。 + +.. _mcpwm-brake: + +制动与安全输出 +============== + +操作器将 :doc:`故障 ` 转换为制动动作。应在启动 PWM 前配置生成器的制动状态,使故障路径完全由硬件驱动,避免软件延迟。 + +恢复策略 +-------- + +.. list:: + + - **CBC(逐周期):** 故障有效期间制动,并在配置的定时器归零或峰值处恢复,适合瞬态限流。 + - **OST(单次):** 故障消失后仍保持制动,必须由软件显式恢复,适合互锁或严重过流。 + +对于 CBC,设置 :cpp:member:`cbc_recover_on_tez ` 或 :cpp:member:`cbc_recover_on_tep `,选择已清除故障后释放输出的边界。在边界恢复可避免在 PWM 周期中间重新导通开关。 + +.. warning:: + + 不要同时启用 ``cbc_recover_on_tez`` 和 ``cbc_recover_on_tep``;应选择与波形和栅极驱动时序相匹配的边界。 + +.. _mcpwm-brake-fault-connection: + +故障连接 +-------- + +将故障连接到操作器,然后为每个生成器指定制动模式下的状态。此例在两个计数方向将原始生成器置低,使用 OST 制动: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_operator_set_brake_on_fault(oper, + &(mcpwm_brake_config_t) { + .fault = fault, + .brake_mode = MCPWM_OPER_BRAKE_MODE_OST, + })); + + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_brake_event( + generator, MCPWM_GEN_BRAKE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_OST, + MCPWM_GEN_ACTION_LOW))); + ESP_ERROR_CHECK(mcpwm_generator_set_action_on_brake_event( + generator, MCPWM_GEN_BRAKE_EVENT_ACTION( + MCPWM_TIMER_DIRECTION_DOWN, MCPWM_OPER_BRAKE_MODE_OST, + MCPWM_GEN_ACTION_LOW))); + +半桥必须为两个生成器都配置相同的制动动作。逻辑低电平可能被死区、GPIO 矩阵或外部电路反相,因此应在栅极驱动器处确认实际安全状态。 + +生成器级故障动作与操作器制动的区别在于:前者适合单个输出的即时边沿响应,后者负责整个输出级的安全状态、锁存行为和恢复策略。主保护路径通常应优先使用操作器制动。 + +OST 故障恢复 +------------ + +CBC 在故障消失后在配置的边界自动恢复。OST 恢复前,先移除并确认故障根因已经消失,然后调用: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_operator_recover_from_fault(oper, fault)); + +故障仍有效时该调用会失败。 + +.. figure:: /../_static/mcpwm/brake_cbc_ost.svg + :align: center + :alt: PWM 运行期间故障被触发。CBC 在故障有效期间将输出保持在制动电平,并在下一个周期边界恢复;OST 则保持锁存直至软件恢复。 + + CBC 仅在故障有效期间制动,并在下一个周期边界恢复;OST 保持锁存直至软件恢复。 + +制动事件回调 +------------ + +操作器可通过 :cpp:member:`on_brake_cbc ` 和 :cpp:member:`on_brake_ost ` 回调报告制动事件。二者均在 ISR 上下文运行,只应用于通知,不应用于阻塞式恢复。 + +.. code-block:: c + + mcpwm_operator_event_callbacks_t cbs = { + .on_brake_cbc = my_brake_cbc_cb, + .on_brake_ost = my_brake_ost_cb, + }; + ESP_ERROR_CHECK(mcpwm_operator_register_event_callbacks(oper, &cbs, NULL)); + +.. _mcpwm-carrier: + +载波调制 +======== + +载波调制在操作器的 PWM 输出上叠加高频载波,常用于变压器隔离式栅极驱动:即使基础 PWM 保持 100% 占空比,调制后仍有可穿过隔离栅的跳变。应先完成基础 PWM 的配置;载波设置影响此操作器的所有生成器。 + +载波配置 +-------- + +.. code-block:: c + + mcpwm_carrier_config_t carrier = { + .clk_src = MCPWM_CARRIER_CLK_SRC_DEFAULT, + .frequency_hz = 100000, + .duty_cycle = 0.5f, + .first_pulse_duration_us = 20, + }; + ESP_ERROR_CHECK(mcpwm_operator_apply_carrier(oper, &carrier)); + +.. figure:: /../_static/mcpwm/carrier_modulation.svg + :align: center + :alt: 50% 占空比基础 PWM 的载波调制 + + 100 kHz 载波与 50% 占空比基础 PWM 做与操作。首个脉冲被拉伸到 20 us(两个载波周期);基础 PWM 为低电平时不做斩波。 + +载波参数 +-------- + +.. list:: + + - :cpp:member:`clk_src ` 选择载波时钟源。默认指向一个内部 PLL 时钟(如 PLL_F160M),部分芯片还提供 RC_FAST 或 XTAL 作为备选。不同时钟源的分辨率与功耗不同;默认时钟源即可满足大多数应用,仅在需要避开某个时钟引入的噪声、PLL 精度不够,或对功耗有要求时,才需要手动指定。 + - :cpp:member:`frequency_hz ` 是载波频率;应选择同时满足隔离变压器、栅极驱动器、开关损耗预算和目标芯片时钟分辨率的值。 + - :cpp:member:`duty_cycle ` 只能取硬件支持的 0.125、0.25、0.375、0.5、0.625、0.75 或 0.875,而非任意比例。 + - :cpp:member:`first_pulse_duration_us ` 控制调制开始后的首个脉冲。它必须非零,且至少为一个载波周期。较长的首脉冲可帮助在感性隔离路径中建立电流,但必须保持在栅极驱动系统允许范围内。 + - :cpp:member:`invert_before_modulate ` 改变原始 PWM 极性,:cpp:member:`invert_after_modulate ` 改变调制后极性。 + +不需要载波调制时,向 :cpp:func:`mcpwm_operator_apply_carrier` 传入 ``NULL`` 配置即可禁用。 + +API 参考 +======== + +MCPWM 操作器驱动函数 +-------------------- + +.. include-build-file:: inc/mcpwm_oper.inc diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_sync.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_sync.rst new file mode 100644 index 00000000000..60437406eaf --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_sync.rst @@ -0,0 +1,157 @@ +=========================== +MCPWM 同步:对齐 PWM 相位 +=========================== + +.. contents:: + :local: + :depth: 2 + +为什么需要同步 +============== + +每个 MCPWM 定时器是独立的硬件计数器。当你调用 :cpp:func:`mcpwm_timer_start()` 启动两个定时器时,CPU 会依次发出两条写指令——第二个定时器比第一个晚几十个 CPU 周期才启动。即使两者的周期配置完全相同,它们的计数器在周期中的位置也是不同的,PWM 输出的相位关系无法预测。 + +同步通过在同步边沿到达时给**正在运行**的定时器加载指定的计数值和方向来解决这个问题。定时器必须已经在运行;同步不会启动或停止定时器。它是在运行时修正相位的一种机制。 + +如果同步边沿在每个周期都到达(例如来自 TEZ 处的定时器同步源),那么修正每周期重复一次,相位关系可以无限期保持。这就是典型的用法:一个定时器作为参考,其他定时器在每个周期都重新对齐到它。 + +MCPWM 提供三种同步源。所有源都产生 :cpp:type:`mcpwm_sync_handle_t` 类型的句柄,且任何源都可以供给同组内的任意定时器。 + +GPIO 同步源 +============ + +GPIO 同步源对外部引脚上的边沿做出反应——当外部控制器、传感器或编码器提供周期性参考信号时非常有用。 + +.. code-block:: c + + mcpwm_sync_handle_t sync = NULL; + ESP_ERROR_CHECK(mcpwm_new_gpio_sync_src( + &(mcpwm_gpio_sync_src_config_t) { + .group_id = 0, + .gpio_num = 5, + .flags.active_neg = false, + }, &sync)); + +GPIO 同步源配置很简单: + +.. list:: + + - :cpp:member:`group_id ` — 同步源所属的 MCPWM 组。必须与所有接收该同步的定时器所在组一致。 + - :cpp:member:`gpio_num ` — 承载同步信号的 GPIO。 + - :cpp:member:`active_neg ` — 默认上升沿为有效边沿;设置后改为下降沿有效。 + +软件同步源 +========== + +软件同步源由应用代码按需产生同步边沿。它没有配置字段;创建后即可在需要时激活。 + +.. code-block:: c + + mcpwm_sync_handle_t soft_sync = NULL; + ESP_ERROR_CHECK(mcpwm_new_soft_sync_src(NULL, &soft_sync)); + + // 后续当应用决定同步时: + ESP_ERROR_CHECK(mcpwm_soft_sync_activate(soft_sync)); + +.. note:: + + 必须先通过 :cpp:func:`mcpwm_timer_set_phase_on_sync()` 或 :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()` 将软件同步源绑定到定时器,再调用激活。驱动在创建时不会分配定时器;在绑定前调用 :cpp:func:`mcpwm_soft_sync_activate()` 属于未定义行为。 + +这在定时器已经在运行、应用需要触发一次性的相位修正时有用——例如故障恢复后,或开始新的控制周期之前。由于软件同步是一次性的,如果后续没有更多同步边沿到来,相位关系会随时间漂移。如需持续锁相,应使用周期性源(GPIO 或定时器同步源)。 + +定时器同步源 +============ + +定时器同步源在定时器到达指定事件时产生同步边沿——例如每次定时器计到零(TEZ)。这可以让一个定时器作为其他定时器的周期性参考,每周期都保持相位锁定。 + +.. code-block:: c + + mcpwm_sync_handle_t timer_sync = NULL; + ESP_ERROR_CHECK(mcpwm_new_timer_sync_src( + timer_a, + &(mcpwm_timer_sync_src_config_t) { + .timer_event = MCPWM_TIMER_EVENT_EMPTY, + }, + &timer_sync)); + +.. list:: + + - :cpp:member:`timer_event ` — 触发同步输出的定时器事件。常用 :cpp:enumerator:`MCPWM_TIMER_EVENT_EMPTY` (零)表示每个周期开始,或 :cpp:enumerator:`MCPWM_TIMER_EVENT_PEAK` 表示峰值位置。在向上计数模式中峰值就是周期边界;在向上-向下计数模式中峰值是周期的中点。 + - :cpp:member:`propagate_input_sync ` — 设置后,该定时器会将其接收到的输入同步转发到其输出,无需额外 GPIO 接线即可实现定时器同步链。此模式下硬件选择输入同步作为输出源,因此 :cpp:member:`timer_event` 字段会被忽略。 + +每个定时器最多只能创建一个同步源。多个定时器可以接收同一个同步源。 + +由于定时器同步源每周期都会触发,接收定时器在每个周期都会得到修正。这是维持多路 PWM 通道间稳定相位关系最常用的方式。 + +设置接收相位 +============ + +无论选择哪种同步源,接收定时器都使用相同的 API。调用 :cpp:func:`mcpwm_timer_set_phase_on_sync()` 配置同步边沿到达时的行为。定时器必须已经在运行,同步才会生效。 + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timer, + &(mcpwm_timer_sync_phase_config_t) { + .sync_src = sync, + .count_value = 25, + .direction = MCPWM_TIMER_DIRECTION_UP, + })); + +.. list:: + + - :cpp:member:`sync_src ` — 源对象。设为 ``NULL`` 可取消同步。 + - :cpp:member:`count_value ` — 同步事件到达时加载的计数值。应保持在定时器周期范围内。 + - :cpp:member:`direction ` — 加载后的计数方向。 + +两路 90 度移相 +============== + +现在你已经了解了三种同步源以及如何设置接收相位,下面是一个完整示例。它使用定时器同步源:``timer_a`` 每次到达零时发出同步,``timer_b`` 收到后加载 ``count_value = 25``,产生 90 度相位滞后。由于同步每周期重复一次,两路输出的相位关系可以无限期保持。 + +.. code-block:: c + + mcpwm_timer_handle_t timer_a = NULL; + mcpwm_timer_handle_t timer_b = NULL; + mcpwm_sync_handle_t timer_a_sync = NULL; + + // timer_a 和 timer_b 均已创建,period_ticks = 100 + + ESP_ERROR_CHECK(mcpwm_new_timer_sync_src( + timer_a, + &(mcpwm_timer_sync_src_config_t) { + .timer_event = MCPWM_TIMER_EVENT_EMPTY, + }, + &timer_a_sync)); + + ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timer_b, + &(mcpwm_timer_sync_phase_config_t) { + .sync_src = timer_a_sync, + .count_value = 25, + .direction = MCPWM_TIMER_DIRECTION_UP, + })); + + // timer_a 在 TEZ 输出同步;timer_b 收到后从 Tick 25 开始继续计数。 + +理解领先与滞后 +-------------- + +下面的示意中,``PWM_A`` 先开始一个周期,``PWM_B`` 在其后四分之一周期出现,因此 ``PWM_B`` 滞后 ``PWM_A`` 90 度;反过来说,``PWM_A`` 领先 ``PWM_B`` 90 度。 + +.. figure:: /../_static/mcpwm/phase_shift.svg + :align: center + :alt: PWM 相移 90 度滞后 + + PWM_A 与 PWM_B 之间的 90 度相移:PWM_B 在 PWM_A 之后 25 个 tick 处开始上升。 + +其他注意事项 +============ + +捕获定时器也可通过 :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()` 使用同一同步源,捕获始终向上计数。接收端和源必须保留在同一组中。删除源之前,应先取消同步或删除所有使用它的对象。 + +API 参考 +======== + +MCPWM 同步驱动函数 +------------------ + +.. include-build-file:: inc/mcpwm_sync.inc diff --git a/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_timer.rst b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_timer.rst new file mode 100644 index 00000000000..46b211f524c --- /dev/null +++ b/docs/zh_CN/api-reference/peripherals/mcpwm/mcpwm_timer.rst @@ -0,0 +1,146 @@ +======================= +MCPWM 定时器:设定频率 +======================= + +.. contents:: + :local: + :depth: 2 + +定时器为与其操作器相连的每一路 PWM 提供时间基准。它以 :cpp:member:`resolution_hz ` 的频率计数,并在达到 :cpp:member:`period_ticks ` 时回绕。先选择分辨率——它决定了边沿放置的最小步长——再选择目标频率对应的周期。 + +无论是舵机、调速还是逆变器,定时器都回答两个最基本的问题:"一个 Tick 有多细?" 和 "一个 PWM 周期有多长?"。后续比较器和生成器都只是在这个时间基准上放置边沿。 + +构建 20 kHz 时间基准 +===================== + +对于向上计数模式, ``period_ticks = resolution_hz / frequency_hz`` 。以下配置的 Tick 为 1 MHz(每 Tick 1 微秒),周期为 50 Tick,频率即为 20 kHz。下图展示了计数器从 0 上升到 50 然后复位的过程 —— ``TEZ`` (定时器归零事件)和 ``TEP`` (定时器峰值事件)是生成器使用的两个边界。 + +.. figure:: /../_static/mcpwm/timer_up_count.svg + :align: center + :alt: 向上计数:计数器形成锯齿波,从 0 上升到 50,TEZ 在归零时触发,TEP 在峰值时触发。 + + 向上计数:计数器形成锯齿波,从 0 上升到 50,TEZ 在归零时触发,TEP 在峰值时触发。 + +.. code-block:: c + + mcpwm_timer_handle_t timer = NULL; + mcpwm_timer_config_t timer_config = { + .group_id = 0, + .clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + .period_ticks = 50, + .count_mode = MCPWM_TIMER_COUNT_MODE_UP, + }; + ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timer)); + +示例代码只覆盖了最核心的配置,但 :cpp:type:`mcpwm_timer_config_t` 中还有一些未出现的字段,在特定场景下同样重要: + +.. list:: + + - :cpp:member:`group_id ` — 定时器从哪个 MCPWM 组分配。不同芯片会提供不同数量的组,每个组包含一组共享时钟分频器的定时器、操作器、比较器和生成器。 ``0`` 表示第一个组,大多数设计用它就够了。 + - :cpp:member:`clk_src ` — 定时器的时钟源。:c:macro:`MCPWM_TIMER_CLK_SRC_DEFAULT` 选择 PLL 时钟,适合绝大多数应用。部分芯片还提供其他时钟源,可在 PLL 被关闭(例如浅睡眠)时显式指定,让定时器继续计数。 + - :cpp:member:`resolution_hz ` — 计数器的 Tick 频率。一个 Tick 持续 ``1 / resolution_hz`` 秒,1 MHz 即每 Tick 1 微秒。它决定了比较器能放置的边沿最细粒度。 + - :cpp:member:`period_ticks ` — 一个完整 PWM 周期包含的 Tick 数。频率为 ``resolution_hz / period_ticks``。 + - :cpp:member:`count_mode ` — 计数器是只向上计数(边沿对齐 PWM),还是先上后下(中心对齐 PWM)。两种计数形态见 :ref:`计数模式与波形 `;硬件还支持向下计数。 + - :cpp:member:`intr_priority ` — 定时器回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级;当回调需要抢占其他中断时(例如对时序敏感的电机控制),可适当提高。 + +示例没有设置 :cpp:member:`flags `,因此这些开关都处于关闭状态——这也是安全的默认值。其中两个值得了解: + +.. list:: + + - :cpp:member:`update_period_on_empty ` 和 :cpp:member:`update_period_on_sync ` — 默认关闭,因此 :cpp:func:`mcpwm_timer_set_period()` 会立即生效。打开后可把频率切换推迟到安全边界,见 :ref:`频率更新 `。 + - :cpp:member:`allow_pd ` — 允许在睡眠时关闭 MCPWM 电源域。驱动会在睡眠前后备份并恢复定时器寄存器,从而省电,代价是额外占用 RAM。 + +在完成操作器、比较器和生成器的配置后,再启用并启动定时器: + +.. code-block:: c + + ESP_ERROR_CHECK(mcpwm_timer_enable(timer)); + ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP)); + +:cpp:func:`mcpwm_timer_enable()` 启用定时器运行所需的系统服务:使能定时器中断,并在启用电源管理时持有该组电源管理锁,避免时钟变频干扰 PWM 时序。:cpp:func:`mcpwm_timer_start_stop()` 则负责启动和停止计数。删除定时器前,先调用 :cpp:func:`mcpwm_timer_disable()` 恢复原状,再调用 :cpp:func:`mcpwm_del_timer()`。 + +:cpp:func:`mcpwm_timer_start_stop()` 的第三个参数选择停止行为: + +.. list:: + + - :c:macro:`MCPWM_TIMER_START_NO_STOP` — 持续运行,直到显式停止。 + - :c:macro:`MCPWM_TIMER_START_STOP_EMPTY` — 下次计数到零(TEZ)时自动停止。适用于单次或需要完整周期后再停止的同步启动。 + - :c:macro:`MCPWM_TIMER_START_STOP_FULL` — 下次计数到峰值(TEP)时自动停止。适用于单个完整周期后停止。 + +.. _mcpwm-timer-counting-modes: + +计数模式与波形 +============== + +**向上计数** 模式下,计数器从 0 计数到 :cpp:member:`period_ticks ` 然后复位。波形为锯齿波,PWM 边沿对齐在周期的一侧——这称为 *边沿对齐* PWM。 + +**向上-向下计数** 模式下,计数器先向上到 ``period_ticks / 2``,再向下回 0。波形为三角波,PWM 边沿围绕周期中心对称 —— *中心对齐* PWM。中心对齐 PWM 因谐波失真更小,常用于电机控制。 + +.. figure:: /../_static/mcpwm/timer_up_down_count.svg + :align: center + :alt: 向上-向下计数:计数器形成三角波,上升到 25(50 的一半),再下降回 0。 + + 向上-向下计数:计数器形成三角波,上升到 25(50 的一半),再下降回 0。 + +两种模式的频率均为 ``resolution_hz / period_ticks``。选择足够高的分辨率以满足所需的占空比精度,再选择周期以达到目标频率。 + +.. important:: + + :cpp:member:`period_ticks ` 表示完整 PWM 周期的总 Tick 数,而不是所有模式下都表示计数器峰值。 + + .. list:: + + - 在 ``MCPWM_TIMER_COUNT_MODE_UP`` 中,计数器范围是 ``0 -> period_ticks``。 + - 在 ``MCPWM_TIMER_COUNT_MODE_UP_DOWN`` 中,硬件峰值是 ``period_ticks / 2``,完整周期是 ``0 -> peak -> 0``。 + + 例如 :cpp:member:`resolution_hz ` = 1 MHz、:cpp:member:`period_ticks ` = 50 时:向上计数模式为 ``0 -> 50``,向上-向下模式为 ``0 -> 25 -> 0``。两者完整周期都是 50 微秒,因此频率都为 20 kHz,只是边沿分布不同。 + +.. _mcpwm-timer-safe-update: + +频率更新 +======== + +默认情况下,:cpp:func:`mcpwm_timer_set_period()` 立即生效,可能截断当前周期并产生不完整的脉冲。设置 :cpp:member:`update_period_on_empty ` 可在计数归零时更新,设置 :cpp:member:`update_period_on_sync ` 则在同步事件时更新。若需保持占空比不变,应同时按比例更新比较值: + +.. code-block:: c + + // 周期由 50 改为 100,同时保持 40% 占空比 + ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 40)); + ESP_ERROR_CHECK(mcpwm_timer_set_period(timer, 100)); + +对大多数实时调参场景,优先修改比较器以改变占空比,只有在确实需要改 PWM 频率时才修改定时器周期。电机和电源应用通常还应配合 :cpp:member:`update_period_on_empty ` 或同步更新,避免在周期中间切换参数。 + +定时器事件回调 +============== + +定时器可在达到峰值(:cpp:member:`on_full `)、归零(:cpp:member:`on_empty `)或停止(:cpp:member:`on_stop `)时通知应用。必须在启用定时器前注册回调。回调在 ISR 上下文执行,不能阻塞、分配内存或调用普通 FreeRTOS API,应使用 ``...FromISR`` 变体。 + +.. note:: + + 定时器和捕获定时器可能与同组其他对象共享分频器。若同一组里需要多种分辨率,请按请求分辨率单调顺序创建对象,避免分频冲突。详细规则见 :doc:`高级主题 `。 + +.. code-block:: c + + static bool IRAM_ATTR on_timer_empty(mcpwm_timer_handle_t timer, + const mcpwm_timer_event_data_t *edata, + void *user_ctx) + { + BaseType_t high_task_woken = pdFALSE; + vTaskNotifyGiveFromISR((TaskHandle_t)user_ctx, &high_task_woken); + return high_task_woken == pdTRUE; + } + + mcpwm_timer_event_callbacks_t cbs = { .on_empty = on_timer_empty }; + ESP_ERROR_CHECK(mcpwm_timer_register_event_callbacks(timer, &cbs, + xTaskGetCurrentTaskHandle())); + +如何在同步边沿将定时器加载到指定相位,见 :doc:`同步 `。 + +API 参考 +======== + +MCPWM 定时器驱动函数 +-------------------- + +.. include-build-file:: inc/mcpwm_timer.inc diff --git a/docs/zh_CN/migration-guides/release-5.x/5.0/peripherals.rst b/docs/zh_CN/migration-guides/release-5.x/5.0/peripherals.rst index febc3f3f5a0..69308d15f0a 100644 --- a/docs/zh_CN/migration-guides/release-5.x/5.0/peripherals.rst +++ b/docs/zh_CN/migration-guides/release-5.x/5.0/peripherals.rst @@ -397,7 +397,7 @@ LCD 旧版 MCPWM 驱动已弃用 ----------------------------------- - MCPWM 驱动已更新(详见 :doc:`MCPWM <../../../api-reference/peripherals/mcpwm>`)。同时,旧版驱动已被弃用。 + MCPWM 驱动已更新(详见 :doc:`MCPWM `)。同时,旧版驱动已被弃用。 新驱动中,每个 MCPWM 子模块相互独立,用户可以自由进行资源连接。