docs: restructure MCPWM programming guide

This commit is contained in:
morris
2026-07-23 23:53:06 +08:00
parent 53c8b9402d
commit 13777d2d93
66 changed files with 10976 additions and 2643 deletions

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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;
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

95
docs/_static/mcpwm/brake_cbc_ost.svg vendored Normal file
View File

@@ -0,0 +1,95 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 370" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0,0 8,3 0,6" fill="#555"/>
</marker>
</defs>
<rect width="900" height="370" fill="#ffffff" rx="8"/>
<!-- Title -->
<text x="450" y="32" text-anchor="middle" font-size="14" fill="#333" font-weight="700">CBC vs. OST Brake Behavior</text>
<line x1="180" y1="40" x2="720" y2="40" stroke="#ddd" stroke-width="1"/>
<g transform="translate(50, 55)">
<!-- ===== Timeline ===== -->
<!-- X-axis: 0 to 560px = 14 PWM periods (40px per period) -->
<!-- Row baselines at translate offsets 40/80/120/160, low at y=20, high at y=0 -->
<!-- Subtle period gridlines -->
<g stroke="#e0e0e0" stroke-width="1" opacity="0.5">
<line x1="0" y1="40" x2="0" y2="180"/>
<line x1="40" y1="40" x2="40" y2="180"/>
<line x1="80" y1="40" x2="80" y2="180"/>
<line x1="120" y1="40" x2="120" y2="180"/>
<line x1="160" y1="40" x2="160" y2="180"/>
<line x1="200" y1="40" x2="200" y2="180"/>
<line x1="240" y1="40" x2="240" y2="180"/>
<line x1="280" y1="40" x2="280" y2="180"/>
<line x1="320" y1="40" x2="320" y2="180"/>
<line x1="360" y1="40" x2="360" y2="180"/>
<line x1="400" y1="40" x2="400" y2="180"/>
<line x1="440" y1="40" x2="440" y2="180"/>
<line x1="480" y1="40" x2="480" y2="180"/>
<line x1="520" y1="40" x2="520" y2="180"/>
<line x1="560" y1="40" x2="560" y2="180"/>
</g>
<!-- ===== Braked windows ===== -->
<rect x="200" y="120" width="200" height="20" fill="#fce4ec" opacity="0.6"/>
<text x="300" y="134" text-anchor="middle" font-size="9" fill="#c62828">braked</text>
<rect x="200" y="160" width="280" height="20" fill="#fce4ec" opacity="0.6"/>
<text x="340" y="174" text-anchor="middle" font-size="9" fill="#c62828">still braked</text>
<!-- ===== PWM reference signal ===== -->
<text x="-8" y="62" text-anchor="end" font-size="11" fill="#333" font-weight="600">PWM</text>
<g transform="translate(0, 40)">
<polyline points="0,0 20,0 20,20 40,20 40,0 60,0 60,20 80,20 80,0 100,0 100,20 120,20 120,0 140,0 140,20 160,20 160,0 180,0 180,20 200,20 200,0 220,0 220,20 240,20 240,0 260,0 260,20 280,20 280,0 300,0 300,20 320,20 320,0 340,0 340,20 360,20 360,0 380,0 380,20 400,20 400,0 420,0 420,20 440,20 440,0 460,0 460,20 480,20 480,0 500,0 500,20 520,20 520,0 540,0 540,20 560,20" fill="none" stroke="#1e88e5" stroke-width="2"/>
</g>
<!-- ===== fault signal ===== -->
<text x="-8" y="102" text-anchor="end" font-size="11" fill="#333" font-weight="600">fault</text>
<g transform="translate(0, 80)">
<polyline points="0,20 200,20 200,0 350,0 350,20 560,20" fill="none" stroke="#e53935" stroke-width="2"/>
</g>
<!-- ===== CBC output ===== -->
<text x="-8" y="142" text-anchor="end" font-size="11" fill="#333" font-weight="600">CBC</text>
<g transform="translate(0, 120)">
<polyline points="0,0 20,0 20,20 40,20 40,0 60,0 60,20 80,20 80,0 100,0 100,20 120,20 120,0 140,0 140,20 160,20 160,0 180,0 180,20 200,20 400,20 400,0 420,0 420,20 440,20 440,0 460,0 460,20 480,20 480,0 500,0 500,20 520,20 520,0 540,0 540,20 560,20" fill="none" stroke="#00897b" stroke-width="2"/>
</g>
<!-- ===== OST output ===== -->
<text x="-8" y="182" text-anchor="end" font-size="11" fill="#333" font-weight="600">OST</text>
<g transform="translate(0, 160)">
<polyline points="0,0 20,0 20,20 40,20 40,0 60,0 60,20 80,20 80,0 100,0 100,20 120,20 120,0 140,0 140,20 160,20 160,0 180,0 180,20 200,20 480,20 480,0 500,0 500,20 520,20 520,0 540,0 540,20 560,20" fill="none" stroke="#f57c00" stroke-width="2"/>
</g>
<!-- ===== Event annotations ===== -->
<!-- fault asserted -->
<line x1="200" y1="40" x2="200" y2="180" stroke="#e53935" stroke-width="1.5" stroke-dasharray="4,4"/>
<text x="200" y="30" text-anchor="middle" font-size="10" fill="#c62828" font-weight="600">fault asserted</text>
<!-- fault cleared -->
<line x1="350" y1="40" x2="350" y2="180" stroke="#1e88e5" stroke-width="1.5" stroke-dasharray="4,4"/>
<text x="350" y="30" text-anchor="middle" font-size="10" fill="#1565c0" font-weight="600">fault cleared</text>
<!-- CBC recovery -->
<line x1="400" y1="40" x2="400" y2="185" stroke="#43a047" stroke-width="1.5" stroke-dasharray="4,4"/>
<text x="400" y="198" text-anchor="middle" font-size="9" fill="#2e7d32">CBC recovers here</text>
<!-- OST recovery -->
<line x1="480" y1="40" x2="480" y2="185" stroke="#43a047" stroke-width="1.5" stroke-dasharray="4,4"/>
<text x="480" y="212" text-anchor="middle" font-size="9" fill="#2e7d32">OST recovers (software)</text>
<!-- ===== Bottom note ===== -->
<text x="280" y="236" text-anchor="middle" font-size="9" fill="#777">Shaded region: output held at the brake level, which is LOW in this example</text>
<!-- ===== Right-hand labels ===== -->
<g transform="translate(592, 0)">
<text x="0" y="58" font-size="9" fill="#888">Runs continuously</text>
<text x="0" y="98" font-size="9" fill="#888">Active while asserted</text>
<text x="0" y="132" font-size="9" fill="#888">Brakes while fault is active,</text>
<text x="0" y="144" font-size="9" fill="#888">recovers at next boundary</text>
<text x="0" y="172" font-size="9" fill="#888">Stays braked after fault clears,</text>
<text x="0" y="184" font-size="9" fill="#888">resumes only after software recovery</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -0,0 +1,75 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 330" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<defs>
<marker id="caparrow" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0,0 8,3 0,6" fill="#555"/>
</marker>
<marker id="caparrow2" markerWidth="8" markerHeight="6" refX="0" refY="3" orient="auto">
<polygon points="8,0 0,3 8,6" fill="#555"/>
</marker>
</defs>
<rect width="900" height="330" fill="#ffffff" rx="8"/>
<!-- Title -->
<text x="450" y="32" text-anchor="middle" font-size="14" fill="#333" font-weight="700">Capture: Measure a Pulse Width</text>
<line x1="180" y1="40" x2="720" y2="40" stroke="#ddd" stroke-width="1"/>
<g transform="translate(70, 55)">
<!-- input signal -->
<text x="-12" y="44" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">input</text>
<polyline points="0,30 130,30 130,10 330,10 330,30 500,30" fill="none" stroke="#1e88e5" stroke-width="2"/>
<!-- edge node dots and labels a, b -->
<circle cx="130" cy="10" r="3" fill="#1e88e5"/>
<circle cx="330" cy="30" r="3" fill="#1e88e5"/>
<text x="130" y="0" text-anchor="middle" font-size="10" fill="#1e88e5" font-weight="700">a (rising)</text>
<text x="330" y="46" text-anchor="middle" font-size="10" fill="#1e88e5" font-weight="700">b (falling)</text>
<!-- capture pulses -->
<text x="-12" y="114" text-anchor="end" font-size="11" fill="#e53935" font-weight="600">capture</text>
<line x1="0" y1="100" x2="500" y2="100" stroke="#e53935" stroke-width="1.5"/>
<polyline points="130,100 130,80 142,80 142,100" fill="none" stroke="#e53935" stroke-width="2"/>
<polyline points="330,100 330,80 342,80 342,100" fill="none" stroke="#e53935" stroke-width="2"/>
<text x="136" y="72" text-anchor="middle" font-size="10" fill="#e53935" font-weight="700">t1</text>
<text x="336" y="72" text-anchor="middle" font-size="10" fill="#e53935" font-weight="700">t2</text>
<!-- timer free-running counter -->
<text x="-12" y="190" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">timer</text>
<!-- continuous rising ramp: the counter counts up without ever stopping -->
<line x1="0" y1="200" x2="500" y2="200" stroke="#43a047" stroke-width="1" opacity="0.35"/>
<line x1="0" y1="200" x2="500" y2="70" stroke="#43a047" stroke-width="2"/>
<!-- latched values at t1 / t2 lie on the same continuous ramp -->
<circle cx="130" cy="166" r="3.5" fill="#43a047"/>
<circle cx="330" cy="114" r="3.5" fill="#43a047"/>
<text x="130" y="158" text-anchor="middle" font-size="9" fill="#43a047">cnt_t1</text>
<text x="330" y="106" text-anchor="middle" font-size="9" fill="#43a047">cnt_t2</text>
<text x="490" y="205" text-anchor="end" font-size="9" fill="#888">counts continuously</text>
<!-- dashed drop lines: edge -> captured timestamp -> latched timer value -->
<line x1="130" y1="10" x2="130" y2="166" stroke="#999" stroke-width="1" stroke-dasharray="3,3"/>
<line x1="330" y1="30" x2="330" y2="132" stroke="#999" stroke-width="1" stroke-dasharray="3,3"/>
<!-- width annotation -->
<line x1="130" y1="240" x2="130" y2="252" stroke="#333" stroke-width="1"/>
<line x1="330" y1="240" x2="330" y2="252" stroke="#333" stroke-width="1"/>
<line x1="130" y1="246" x2="330" y2="246" stroke="#333" stroke-width="1"/>
<text x="230" y="268" text-anchor="middle" font-size="12" fill="#333" font-weight="600">width = t2 - t1</text>
<!-- rising / falling labels -->
<line x1="130" y1="130" x2="180" y2="130" stroke="#1e88e5" stroke-width="1"/>
<text x="188" y="134" font-size="10" fill="#1e88e5">rising@t1</text>
<line x1="330" y1="130" x2="380" y2="130" stroke="#1e88e5" stroke-width="1"/>
<text x="388" y="134" font-size="10" fill="#1e88e5">falling@t2</text>
<!-- legend -->
<g transform="translate(560, 0)">
<text x="0" y="24" font-size="9" fill="#888">Input pulse under test</text>
<text x="0" y="74" font-size="9" fill="#888">Capture fires a pulse on</text>
<text x="0" y="84" font-size="9" fill="#888">each configured edge and</text>
<text x="0" y="94" font-size="9" fill="#888">latches the timer count</text>
<text x="0" y="154" font-size="9" fill="#888">Capture timer counts freely</text>
<text x="0" y="164" font-size="9" fill="#888">at resolution_hz ticks/s</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,82 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 370" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0,0 8,3 0,6" fill="#555"/>
</marker>
</defs>
<rect width="900" height="370" fill="#ffffff" rx="8"/>
<!-- Title -->
<text x="450" y="32" text-anchor="middle" font-size="14" fill="#333" font-weight="700">Carrier Modulation</text>
<line x1="180" y1="40" x2="720" y2="40" stroke="#ddd" stroke-width="1"/>
<g transform="translate(75, 55)">
<!-- ===== Timeline ===== -->
<!-- X-axis: 0 to 450px = 150 us (3 px/us). Carrier period 10 us = 30 px; base period 50 us = 150 px -->
<!-- Row baselines at translate offsets 40/80/120, low at y=20, high at y=0 -->
<!-- Subtle carrier-period gridlines -->
<g stroke="#e0e0e0" stroke-width="1" opacity="0.5">
<line x1="0" y1="40" x2="0" y2="140"/>
<line x1="30" y1="40" x2="30" y2="140"/>
<line x1="60" y1="40" x2="60" y2="140"/>
<line x1="90" y1="40" x2="90" y2="140"/>
<line x1="120" y1="40" x2="120" y2="140"/>
<line x1="150" y1="40" x2="150" y2="140"/>
<line x1="180" y1="40" x2="180" y2="140"/>
<line x1="210" y1="40" x2="210" y2="140"/>
<line x1="240" y1="40" x2="240" y2="140"/>
<line x1="270" y1="40" x2="270" y2="140"/>
<line x1="300" y1="40" x2="300" y2="140"/>
<line x1="330" y1="40" x2="330" y2="140"/>
<line x1="360" y1="40" x2="360" y2="140"/>
<line x1="390" y1="40" x2="390" y2="140"/>
<line x1="420" y1="40" x2="420" y2="140"/>
<line x1="450" y1="40" x2="450" y2="140"/>
</g>
<!-- ===== Base PWM signal (50% duty) ===== -->
<text x="-8" y="62" text-anchor="end" font-size="11" fill="#333" font-weight="600">PWM</text>
<g transform="translate(0, 40)">
<polyline points="0,0 150,0 150,20 300,20 300,0 450,0" fill="none" stroke="#1e88e5" stroke-width="2"/>
</g>
<!-- ===== Carrier signal ===== -->
<text x="-8" y="102" text-anchor="end" font-size="11" fill="#333" font-weight="600">carrier</text>
<g transform="translate(0, 80)">
<polyline points="0,0 15,0 15,20 30,20 30,0 45,0 45,20 60,20 60,0 75,0 75,20 90,20 90,0 105,0 105,20 120,20 120,0 135,0 135,20 150,20 150,0 165,0 165,20 180,20 180,0 195,0 195,20 210,20 210,0 225,0 225,20 240,20 240,0 255,0 255,20 270,20 270,0 285,0 285,20 300,20 300,0 315,0 315,20 330,20 330,0 345,0 345,20 360,20 360,0 375,0 375,20 390,20 390,0 405,0 405,20 420,20 420,0 435,0 435,20 450,20" fill="none" stroke="#00897b" stroke-width="2"/>
</g>
<!-- ===== Modulated output ===== -->
<!-- Base-low window (150..300 px) shaded: no chopping there -->
<rect x="150" y="120" width="150" height="20" fill="#f5f5f5" opacity="0.8"/>
<text x="225" y="134" text-anchor="middle" font-size="9" fill="#888">no carrier while base is low</text>
<text x="-8" y="142" text-anchor="end" font-size="11" fill="#333" font-weight="600">modulated</text>
<g transform="translate(0, 120)">
<polyline points="0,0 60,0 60,20 75,20 75,0 90,0 90,20 105,20 105,0 120,0 120,20 135,20 135,0 150,0 150,20 300,20 300,0 315,0 315,20 330,20 330,0 345,0 345,20 360,20 360,0 375,0 375,20 390,20 390,0 405,0 405,20 420,20 420,0 435,0 435,20 450,20" fill="none" stroke="#f57c00" stroke-width="2"/>
</g>
<!-- ===== First-pulse annotation ===== -->
<line x1="60" y1="40" x2="60" y2="145" stroke="#f57c00" stroke-width="1.5" stroke-dasharray="4,4"/>
<text x="60" y="30" text-anchor="middle" font-size="10" fill="#ef6c00" font-weight="600">first pulse</text>
<!-- ===== Time axis ===== -->
<text x="0" y="225" text-anchor="middle" font-size="9" fill="#999">0</text>
<text x="150" y="225" text-anchor="middle" font-size="9" fill="#999">50 µs</text>
<text x="300" y="225" text-anchor="middle" font-size="9" fill="#999">100 µs</text>
<text x="450" y="225" text-anchor="middle" font-size="9" fill="#999">150 µs</text>
<!-- ===== Bottom note ===== -->
<text x="225" y="246" text-anchor="middle" font-size="9" fill="#777">The first pulse is held HIGH for first_pulse_duration_us</text>
<text x="225" y="258" text-anchor="middle" font-size="9" fill="#777">(20 µs = 2 carrier periods) before normal chopping resumes.</text>
<!-- ===== Right-hand labels ===== -->
<g transform="translate(480, 0)">
<text x="0" y="58" font-size="9" fill="#888">50% duty, 50 µs period</text>
<text x="0" y="98" font-size="9" fill="#888">100 kHz, 50% duty</text>
<text x="0" y="132" font-size="9" fill="#888">Carrier gated by base PWM;</text>
<text x="0" y="144" font-size="9" fill="#888">first pulse stretched to 20 µs</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

139
docs/_static/mcpwm/compare_event.svg vendored Normal file
View File

@@ -0,0 +1,139 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 420" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0,0 8,3 0,6" fill="#555"/>
</marker>
</defs>
<rect width="900" height="420" fill="#ffffff" rx="8"/>
<!-- Title -->
<text x="450" y="32" text-anchor="middle" font-size="14" fill="#333" font-weight="700">Compare Event: Timer + Comparator + Generator</text>
<line x1="180" y1="40" x2="720" y2="40" stroke="#ddd" stroke-width="1"/>
<g transform="translate(64, 55)">
<!-- X-axis: 0 to 500px = 50 ticks (10px per tick) -->
<!-- Clock signal -->
<text x="-8" y="72" text-anchor="end" font-size="11" fill="#333" font-weight="600">clk</text>
<g transform="translate(0, 60)">
<polyline points="0,20 0,0 10,0 10,20 20,20 20,0 30,0 30,20 40,20 40,0 50,0 50,20 60,20 60,0 70,0 70,20 80,20 80,0 90,0 90,20 100,20 100,0 110,0 110,20 120,20 120,0 130,0 130,20 140,20 140,0 150,0 150,20 160,20 160,0 170,0 170,20 180,20 180,0 190,0 190,20 200,20 200,0 210,0 210,20 220,20 220,0 230,0 230,20 240,20 240,0 250,0 250,20 260,20 260,0 270,0 270,20 280,20 280,0 290,0 290,20 300,20 300,0 310,0 310,20 320,20 320,0 330,0 330,20 340,20 340,0 350,0 350,20 360,20 360,0 370,0 370,20 380,20 380,0 390,0 390,20 400,20 400,0 410,0 410,20 420,20 420,0 430,0 430,20 440,20 440,0 450,0 450,20 460,20 460,0 470,0 470,20 480,20 480,0 490,0 490,20 500,20 500,0" fill="none" stroke="#666" stroke-width="1.5"/>
</g>
<!-- Count Bus (decimal values every 5 ticks) -->
<text x="-8" y="112" text-anchor="end" font-size="11" fill="#333" font-weight="600">count</text>
<g transform="translate(0, 90)">
<rect x="-8" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="0" y="20" text-anchor="middle" font-size="10" fill="#1565c0">0</text>
<rect x="42" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="50" y="20" text-anchor="middle" font-size="10" fill="#1565c0">5</text>
<rect x="92" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="100" y="20" text-anchor="middle" font-size="10" fill="#1565c0">10</text>
<!-- Compare match at tick 15 -->
<rect x="142" y="0" width="16" height="30" fill="#fff3e0" stroke="#ff9800" stroke-width="2" rx="2"/>
<text x="150" y="20" text-anchor="middle" font-size="10" fill="#e65100" font-weight="700">15</text>
<rect x="192" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="200" y="20" text-anchor="middle" font-size="10" fill="#1565c0">20</text>
<rect x="242" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="250" y="20" text-anchor="middle" font-size="10" fill="#1565c0">25</text>
<rect x="292" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="300" y="20" text-anchor="middle" font-size="10" fill="#1565c0">30</text>
<rect x="342" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="350" y="20" text-anchor="middle" font-size="10" fill="#1565c0">35</text>
<rect x="392" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="400" y="20" text-anchor="middle" font-size="10" fill="#1565c0">40</text>
<rect x="442" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="450" y="20" text-anchor="middle" font-size="10" fill="#1565c0">45</text>
<!-- Peak: count reaches 50, then reload -->
<rect x="492" y="0" width="16" height="30" fill="#fff3e0" stroke="#ffb74d" stroke-width="1.5" rx="2"/>
<text x="500" y="20" text-anchor="middle" font-size="10" fill="#e65100" font-weight="700">50</text>
</g>
<!-- cmp_match signal -->
<text x="-8" y="155" text-anchor="end" font-size="10" fill="#333" font-weight="600">cmp_match</text>
<g transform="translate(0, 135)">
<line x1="0" y1="20" x2="500" y2="20" stroke="#ff9800" stroke-width="1.5"/>
<!-- Pulse at tick 15 -->
<polyline points="150,20 150,0 160,0 160,20" fill="none" stroke="#ff9800" stroke-width="1.5"/>
</g>
<!-- TEZ signal -->
<text x="-8" y="195" text-anchor="end" font-size="11" fill="#333" font-weight="600">TEZ</text>
<g transform="translate(0, 175)">
<line x1="0" y1="20" x2="500" y2="20" stroke="#e53935" stroke-width="1.5"/>
<!-- Pulse at count = 0 -->
<polyline points="0,20 0,0 10,0 10,20" fill="none" stroke="#e53935" stroke-width="1.5"/>
<!-- Pulse on reload (count returns to 0) -->
<polyline points="500,20 500,0 510,0 510,20" fill="none" stroke="#e53935" stroke-width="1.5"/>
</g>
<!-- TEP signal -->
<text x="-8" y="235" text-anchor="end" font-size="11" fill="#333" font-weight="600">TEP</text>
<g transform="translate(0, 215)">
<line x1="0" y1="20" x2="500" y2="20" stroke="#1e88e5" stroke-width="1.5"/>
<polyline points="480,20 480,0 500,0 500,20" fill="none" stroke="#1e88e5" stroke-width="1.5"/>
</g>
<!-- PWM Output -->
<text x="-8" y="275" text-anchor="end" font-size="11" fill="#333" font-weight="600">PWM</text>
<g transform="translate(0, 255)">
<!-- Action: set high on TEZ, set low on compare match -->
<!-- tick 0-15: high, tick 15-50: low, tick 50: high again -->
<polyline points="0,0 150,0" fill="none" stroke="#43a047" stroke-width="1.5"/>
<polyline points="150,0 150,20" fill="none" stroke="#43a047" stroke-width="1.5"/>
<polyline points="150,20 500,20" fill="none" stroke="#43a047" stroke-width="1.5"/>
<polyline points="500,20 500,0" fill="none" stroke="#43a047" stroke-width="1.5"/>
</g>
<!-- Period bracket -->
<g transform="translate(0, 320)">
<line x1="0" y1="0" x2="0" y2="12" stroke="#999" stroke-width="1"/>
<line x1="500" y1="0" x2="500" y2="12" stroke="#999" stroke-width="1"/>
<line x1="0" y1="6" x2="500" y2="6" stroke="#999" stroke-width="1"/>
<text x="250" y="30" text-anchor="middle" font-size="12" fill="#555">period = 50 ticks</text>
</g>
<!-- Relation lines -->
<g transform="translate(0, 0)">
<!-- Compare match at tick 15 → cmp_match pulse → PWM falling edge -->
<line x1="150" y1="100" x2="150" y2="135" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<line x1="150" y1="155" x2="150" y2="255" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<!-- TEZ → PWM rising edge -->
<line x1="0" y1="195" x2="0" y2="255" stroke="#e53935" stroke-width="1" stroke-dasharray="4,4"/>
<!-- TEZ on reload → PWM rising edge -->
<line x1="500" y1="195" x2="500" y2="255" stroke="#e53935" stroke-width="1" stroke-dasharray="4,4"/>
<!-- Duty cycle annotation: PWM high from tick 0 to tick 15 -->
<line x1="0" y1="284" x2="0" y2="292" stroke="#43a047" stroke-width="1"/>
<line x1="150" y1="284" x2="150" y2="292" stroke="#43a047" stroke-width="1"/>
<line x1="0" y1="288" x2="150" y2="288" stroke="#43a047" stroke-width="1"/>
<text x="75" y="308" text-anchor="middle" font-size="11" fill="#43a047" font-weight="600">duty = 15 ticks (30 %)</text>
<!-- Cmp threshold annotation -->
<line x1="160" y1="128" x2="200" y2="128" stroke="#ff9800" stroke-width="1"/>
<text x="210" y="132" font-size="11" fill="#e65100">cmp_ticks = 15</text>
</g>
<!-- Signal labels -->
<g transform="translate(580, 0)">
<text x="0" y="75" font-size="9" fill="#888">Timer clock source</text>
<text x="0" y="115" font-size="9" fill="#888">Counter value (decimal)</text>
<text x="0" y="153" font-size="9" fill="#888">Pulse when count = cmp_ticks</text>
<text x="0" y="193" font-size="9" fill="#888">Pulse when counter = 0</text>
<text x="0" y="233" font-size="9" fill="#888">Pulse when counter = peak</text>
<text x="0" y="273" font-size="9" fill="#888">PWM: set high on TEZ,</text>
<text x="0" y="283" font-size="9" fill="#888">set low on cmp_match</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 244" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="244" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Basic Dead-Time Effect (Active High)</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
<marker id="arre53935" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#e53935"/></marker>
<marker id="arr1e88e5" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#1e88e5"/></marker>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin</text>
<polyline points="0,20 100,20 100,0 300,0 300,20 500,20" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,66 120,66 120,46 300,46 300,66 500,66" fill="none" stroke="#43a047" stroke-width="2"/>
<text x="-12" y="106" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">pwm_B</text>
<polyline points="0,112 100,112 100,92 320,92 320,112 500,112" fill="none" stroke="#1e88e5" stroke-width="2"/>
<line x1="100" y1="10" x2="120" y2="56" stroke="#e53935" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arre53935)"/>
<text x="110.0" y="33.0" text-anchor="middle" font-size="10" fill="#e53935" font-weight="600">RED</text>
<line x1="300" y1="10" x2="320" y2="102" stroke="#1e88e5" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arr1e88e5)"/>
<text x="310.0" y="56.0" text-anchor="middle" font-size="10" fill="#1e88e5" font-weight="600">FED</text>
</g>
<text x="450.0" y="232" text-anchor="middle" font-size="10" fill="#888888">Rising edge of pwm_A delayed; falling edge of pwm_B delayed.</text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 244" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="244" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Complementary PWM with Dead Time (Active High)</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
<marker id="arre53935" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#e53935"/></marker>
<marker id="arr1e88e5" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#1e88e5"/></marker>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin</text>
<polyline points="0,20 100,20 100,0 300,0 300,20 500,20" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,66 120,66 120,46 300,46 300,66 500,66" fill="none" stroke="#43a047" stroke-width="2"/>
<text x="-12" y="106" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">pwm_B</text>
<polyline points="0,112 0,92 100,92 100,112 320,112 320,92 500,92 500,112" fill="none" stroke="#1e88e5" stroke-width="2"/>
<rect x="100" y="-4" width="20" height="134" fill="#ffcdd2" opacity="0.55"/>
<rect x="100" y="-4" width="20" height="134" fill="none" stroke="#e53935" stroke-width="1" stroke-dasharray="3,3"/>
<text x="110.0" y="-12" text-anchor="middle" font-size="10" fill="#c62828" font-weight="600">dead time</text>
<rect x="300" y="-4" width="20" height="134" fill="#ffcdd2" opacity="0.55"/>
<rect x="300" y="-4" width="20" height="134" fill="none" stroke="#e53935" stroke-width="1" stroke-dasharray="3,3"/>
<text x="310.0" y="-12" text-anchor="middle" font-size="10" fill="#c62828" font-weight="600">dead time</text>
<line x1="100" y1="10" x2="120" y2="56" stroke="#e53935" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arre53935)"/>
<text x="110.0" y="33.0" text-anchor="middle" font-size="10" fill="#e53935" font-weight="600">RED</text>
<line x1="300" y1="10" x2="320" y2="102" stroke="#1e88e5" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arr1e88e5)"/>
<text x="310.0" y="56.0" text-anchor="middle" font-size="10" fill="#1e88e5" font-weight="600">FED</text>
</g>
<text x="450.0" y="232" text-anchor="middle" font-size="10" fill="#888888">pwm_A delays its rising edge; pwm_B is the inverted falling-edge-delayed copy.</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 244" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="244" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Basic Dead-Time Effect (Active Low)</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
<marker id="arre53935" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#e53935"/></marker>
<marker id="arr1e88e5" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#1e88e5"/></marker>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin</text>
<polyline points="0,20 100,20 100,0 300,0 300,20 500,20" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,66 0,46 120,46 120,66 300,66 300,46 500,46 500,66" fill="none" stroke="#43a047" stroke-width="2"/>
<text x="-12" y="106" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">pwm_B</text>
<polyline points="0,112 0,92 100,92 100,112 320,112 320,92 500,92 500,112" fill="none" stroke="#1e88e5" stroke-width="2"/>
<line x1="100" y1="10" x2="120" y2="56" stroke="#e53935" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arre53935)"/>
<text x="110.0" y="33.0" text-anchor="middle" font-size="10" fill="#e53935" font-weight="600">RED</text>
<line x1="300" y1="10" x2="320" y2="102" stroke="#1e88e5" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arr1e88e5)"/>
<text x="310.0" y="56.0" text-anchor="middle" font-size="10" fill="#1e88e5" font-weight="600">FED</text>
</g>
<text x="450.0" y="232" text-anchor="middle" font-size="10" fill="#888888">Same delay resources; polarity inverted after the delay stage.</text>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 244" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="244" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Complementary PWM with Dead Time (Active Low)</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
<marker id="arre53935" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#e53935"/></marker>
<marker id="arr1e88e5" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#1e88e5"/></marker>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin</text>
<polyline points="0,20 100,20 100,0 300,0 300,20 500,20" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,66 0,46 120,46 120,66 300,66 300,46 500,46 500,66" fill="none" stroke="#43a047" stroke-width="2"/>
<text x="-12" y="106" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">pwm_B</text>
<polyline points="0,112 100,112 100,92 320,92 320,112 500,112" fill="none" stroke="#1e88e5" stroke-width="2"/>
<rect x="100" y="-4" width="20" height="134" fill="#ffcdd2" opacity="0.55"/>
<rect x="100" y="-4" width="20" height="134" fill="none" stroke="#e53935" stroke-width="1" stroke-dasharray="3,3"/>
<text x="110.0" y="-12" text-anchor="middle" font-size="10" fill="#c62828" font-weight="600">dead time</text>
<rect x="300" y="-4" width="20" height="134" fill="#ffcdd2" opacity="0.55"/>
<rect x="300" y="-4" width="20" height="134" fill="none" stroke="#e53935" stroke-width="1" stroke-dasharray="3,3"/>
<text x="310.0" y="-12" text-anchor="middle" font-size="10" fill="#c62828" font-weight="600">dead time</text>
<line x1="100" y1="10" x2="120" y2="56" stroke="#e53935" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arre53935)"/>
<text x="110.0" y="33.0" text-anchor="middle" font-size="10" fill="#e53935" font-weight="600">RED</text>
<line x1="300" y1="10" x2="320" y2="102" stroke="#1e88e5" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arr1e88e5)"/>
<text x="310.0" y="56.0" text-anchor="middle" font-size="10" fill="#1e88e5" font-weight="600">FED</text>
</g>
<text x="450.0" y="232" text-anchor="middle" font-size="10" fill="#888888">Same timing resources; output polarity inverted after the delay stage.</text>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 290" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="290" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">FED on B, A Bypasses Dead Time</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
<marker id="arr1e88e5" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#1e88e5"/></marker>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin_A</text>
<polyline points="0,20 100,20 100,0 300,0 300,20 500,20" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin_B</text>
<polyline points="0,66 100,66 100,46 300,46 300,66 500,66" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="106" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,112 100,112 100,92 300,92 300,112 500,112" fill="none" stroke="#43a047" stroke-width="2"/>
<text x="-12" y="152" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">pwm_B</text>
<polyline points="0,158 100,158 100,138 320,138 320,158 500,158" fill="none" stroke="#1e88e5" stroke-width="2"/>
<line x1="300" y1="56" x2="320" y2="148" stroke="#1e88e5" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arr1e88e5)"/>
<text x="310.0" y="102.0" text-anchor="middle" font-size="10" fill="#1e88e5" font-weight="600">FED</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 290" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="290" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">RED on A, B Bypasses Dead Time</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
<marker id="arre53935" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#e53935"/></marker>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin_A</text>
<polyline points="0,20 100,20 100,0 300,0 300,20 500,20" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin_B</text>
<polyline points="0,66 100,66 100,46 300,46 300,66 500,66" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="106" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,112 120,112 120,92 300,92 300,112 500,112" fill="none" stroke="#43a047" stroke-width="2"/>
<text x="-12" y="152" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">pwm_B</text>
<polyline points="0,158 100,158 100,138 300,138 300,158 500,158" fill="none" stroke="#1e88e5" stroke-width="2"/>
<line x1="100" y1="10" x2="120" y2="102" stroke="#e53935" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arre53935)"/>
<text x="110.0" y="56.0" text-anchor="middle" font-size="10" fill="#e53935" font-weight="600">RED</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 290" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="290" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Bypass A, RED + FED on B</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
<marker id="arre53935" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#e53935"/></marker>
<marker id="arr1e88e5" markerWidth="7" markerHeight="7" refX="7" refY="3.5" orient="auto"><polygon points="0,0 7,3.5 0,7" fill="#1e88e5"/></marker>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin_A</text>
<polyline points="0,20 100,20 100,0 300,0 300,20 500,20" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#757575" font-weight="600">origin_B</text>
<polyline points="0,66 100,66 100,46 300,46 300,66 500,66" fill="none" stroke="#757575" stroke-width="2"/>
<text x="-12" y="106" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,112 100,112 100,92 300,92 300,112 500,112" fill="none" stroke="#43a047" stroke-width="2"/>
<text x="-12" y="152" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">pwm_B</text>
<polyline points="0,158 120,158 120,138 320,138 320,158 500,158" fill="none" stroke="#1e88e5" stroke-width="2"/>
<line x1="100" y1="56" x2="120" y2="148" stroke="#e53935" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arre53935)"/>
<text x="110.0" y="102.0" text-anchor="middle" font-size="10" fill="#e53935" font-weight="600">RED</text>
<line x1="300" y1="56" x2="320" y2="148" stroke="#1e88e5" stroke-width="1.2" stroke-dasharray="3,3" marker-end="url(#arr1e88e5)"/>
<text x="310.0" y="102.0" text-anchor="middle" font-size="10" fill="#1e88e5" font-weight="600">FED</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 198" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="198" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Dual-Edge Asymmetric PWM (Complementary)</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,20 150,20 150,0 350,0 350,20 500,20" fill="none" stroke="#43a047" stroke-width="2"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">pwm_B</text>
<polyline points="0,66 0,46 150,46 150,66 350,66 350,46 500,46 500,66" fill="none" stroke="#1e88e5" stroke-width="2"/>
<line x1="150" y1="-4" x2="150" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="150" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp_a</text>
<line x1="350" y1="-4" x2="350" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="350" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp_b</text>
</g>
<text x="450.0" y="186" text-anchor="middle" font-size="10" fill="#888888">gen_a and gen_b use opposite compare actions.</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 198" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="198" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Center-Aligned PWM</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#333333" font-weight="600">count</text>
<line x1="0" y1="20" x2="250" y2="0" stroke="#8e24aa" stroke-width="2"/>
<line x1="250" y1="0" x2="500" y2="20" stroke="#8e24aa" stroke-width="2"/>
<line x1="500" y1="0" x2="500" y2="20" stroke="#8e24aa" stroke-width="1" stroke-dasharray="3,3"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,66 150,66 150,46 350,46 350,66 500,66" fill="none" stroke="#43a047" stroke-width="2"/>
<line x1="150" y1="-4" x2="150" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="150" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp = 15</text>
<line x1="350" y1="-4" x2="350" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="350" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp = 15</text>
</g>
<text x="450.0" y="186" text-anchor="middle" font-size="10" fill="#888888">High while counting up to cmp, low while counting down past it.</text>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 198" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="198" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Complementary Center-Aligned PWM (No Dead Time)</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,20 150,20 150,0 350,0 350,20 500,20" fill="none" stroke="#43a047" stroke-width="2"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#1e88e5" font-weight="600">pwm_B</text>
<polyline points="0,66 0,46 150,46 150,66 350,66 350,46 500,46 500,66" fill="none" stroke="#1e88e5" stroke-width="2"/>
<line x1="150" y1="-4" x2="150" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="150" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp = 15</text>
<line x1="350" y1="-4" x2="350" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="350" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp = 15</text>
</g>
<text x="450.0" y="186" text-anchor="middle" font-size="10" fill="#888888">Logical complement only; not yet safe for a power stage.</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 186 KiB

78
docs/_static/mcpwm/phase_shift.svg vendored Normal file
View File

@@ -0,0 +1,78 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 370" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="0" orient="auto">
<polygon points="0,-3 8,0 0,3" fill="#555"/>
</marker>
</defs>
<rect width="900" height="370" fill="#ffffff" rx="8"/>
<!-- Title -->
<text x="450" y="32" text-anchor="middle" font-size="14" fill="#333" font-weight="700">PWM Phase Shift — 90° Lag</text>
<line x1="180" y1="40" x2="720" y2="40" stroke="#ddd" stroke-width="1"/>
<g transform="translate(50, 55)">
<!-- ===== Timeline ===== -->
<!-- X-axis: 0 to 600px = 200 ticks (3 px/tick). Period = 100 ticks = 300 px. -->
<!-- Row baselines at translate offsets 40/80, low at y=20, high at y=0 -->
<!-- Subtle period gridlines -->
<g stroke="#e0e0e0" stroke-width="1" opacity="0.5">
<line x1="0" y1="40" x2="0" y2="100"/>
<line x1="150" y1="40" x2="150" y2="100"/>
<line x1="300" y1="40" x2="300" y2="100"/>
<line x1="450" y1="40" x2="450" y2="100"/>
<line x1="600" y1="40" x2="600" y2="100"/>
</g>
<!-- Period labels -->
<text x="150" y="34" text-anchor="middle" font-size="9" fill="#999">Period 1</text>
<text x="450" y="34" text-anchor="middle" font-size="9" fill="#999">Period 2</text>
<!-- ===== PWM_A signal ===== -->
<text x="-8" y="62" text-anchor="end" font-size="11" fill="#333" font-weight="600">PWM_A</text>
<rect x="-6" y="52" width="10" height="10" rx="2" fill="#1e88e5"/>
<g transform="translate(0, 40)">
<polyline points="0,20 0,0 150,0 150,20 300,20 300,0 450,0 450,20 600,20" fill="none" stroke="#1e88e5" stroke-width="2.5"/>
</g>
<!-- ===== PWM_B signal ===== -->
<text x="-8" y="102" text-anchor="end" font-size="11" fill="#333" font-weight="600">PWM_B</text>
<rect x="-6" y="92" width="10" height="10" rx="2" fill="#f57c00"/>
<g transform="translate(0, 80)">
<polyline points="0,20 75,20 75,0 225,0 225,20 375,20 375,0 525,0 525,20 600,20" fill="none" stroke="#f57c00" stroke-width="2.5"/>
</g>
<!-- ===== Phase shift arrow ===== -->
<line x1="0" y1="126" x2="75" y2="126" stroke="#555" stroke-width="1.5" marker-end="url(#arrowhead)"/>
<line x1="75" y1="126" x2="0" y2="126" stroke="#555" stroke-width="1.5" marker-end="url(#arrowhead)"/>
<text x="37.5" y="122" text-anchor="middle" font-size="10" fill="#555" font-weight="600">25 ticks = 90°</text>
<text x="37.5" y="140" text-anchor="middle" font-size="9" fill="#777" font-style="italic">PWM_B lags PWM_A</text>
<!-- ===== Time axis ===== -->
<g stroke="#ddd" stroke-width="1">
<line x1="0" y1="158" x2="600" y2="158"/>
</g>
<g font-size="9" fill="#999" text-anchor="middle">
<text x="0" y="172">0</text>
<text x="75" y="172">25</text>
<text x="150" y="172">50</text>
<text x="225" y="172">75</text>
<text x="300" y="172">100</text>
<text x="375" y="172">125</text>
<text x="450" y="172">150</text>
<text x="525" y="172">175</text>
<text x="600" y="172">200</text>
</g>
<text x="600" y="158" font-size="9" fill="#999" text-anchor="start" dy="-2"> ticks</text>
<!-- ===== Bottom note ===== -->
<text x="300" y="195" text-anchor="middle" font-size="9" fill="#777">PWM period = 100 ticks, 50% duty cycle. A 25-tick offset produces a 90° phase shift.</text>
<!-- ===== Right-hand labels ===== -->
<g transform="translate(640, 0)">
<text x="0" y="58" font-size="9" fill="#888">50% duty, period = 100 ticks</text>
<text x="0" y="98" font-size="9" fill="#888">Starts 25 ticks after PWM_A</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,19 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 198" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="198" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Pulse Placement Inside the Period</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#333333" font-weight="600">count</text>
<line x1="0" y1="20" x2="500" y2="0" stroke="#8e24aa" stroke-width="2"/>
<line x1="500" y1="0" x2="500" y2="20" stroke="#8e24aa" stroke-width="1" stroke-dasharray="3,3"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,66 150,66 150,46 350,46 350,66 500,66" fill="none" stroke="#43a047" stroke-width="2"/>
<line x1="150" y1="-4" x2="150" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="150" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp_a</text>
<line x1="350" y1="-4" x2="350" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="350" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp_b</text>
</g>
<text x="450.0" y="186" text-anchor="middle" font-size="10" fill="#888888">cmp_a opens the pulse, cmp_b closes it.</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 198" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="198" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Single-Edge Asymmetric PWM, Active High</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#333333" font-weight="600">count</text>
<line x1="0" y1="20" x2="500" y2="0" stroke="#8e24aa" stroke-width="2"/>
<line x1="500" y1="0" x2="500" y2="20" stroke="#8e24aa" stroke-width="1" stroke-dasharray="3,3"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,66 0,46 150,46 150,66 500,66" fill="none" stroke="#43a047" stroke-width="2"/>
<line x1="150" y1="-4" x2="150" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="150" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp = 15</text>
</g>
<text x="450.0" y="186" text-anchor="middle" font-size="10" fill="#888888">Set high at timer zero, low at the comparator.</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 198" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<rect width="900" height="198" fill="#ffffff" rx="8"/>
<text x="450.0" y="30" text-anchor="middle" font-size="14" fill="#333333" font-weight="700">Single-Edge Asymmetric PWM, Active Low</text>
<line x1="170" y1="38" x2="730" y2="38" stroke="#ddd" stroke-width="1"/>
<defs>
</defs>
<g transform="translate(70, 66)">
<text x="-12" y="14" text-anchor="end" font-size="11" fill="#333333" font-weight="600">count</text>
<line x1="0" y1="20" x2="500" y2="0" stroke="#8e24aa" stroke-width="2"/>
<line x1="500" y1="0" x2="500" y2="20" stroke="#8e24aa" stroke-width="1" stroke-dasharray="3,3"/>
<text x="-12" y="60" text-anchor="end" font-size="11" fill="#43a047" font-weight="600">pwm_A</text>
<polyline points="0,66 150,66 150,46 500,46 500,66" fill="none" stroke="#43a047" stroke-width="2"/>
<line x1="150" y1="-4" x2="150" y2="84" stroke="#ff9800" stroke-width="1" stroke-dasharray="4,4"/>
<text x="150" y="-12" text-anchor="middle" font-size="10" fill="#ff9800">cmp = 15</text>
</g>
<text x="450.0" y="186" text-anchor="middle" font-size="10" fill="#888888">Set low at the period boundary, high at the comparator.</text>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

127
docs/_static/mcpwm/timer_up_count.svg vendored Normal file
View File

@@ -0,0 +1,127 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 370" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0,0 8,3 0,6" fill="#555"/>
</marker>
</defs>
<rect width="900" height="370" fill="#ffffff" rx="8"/>
<!-- Title -->
<text x="450" y="32" text-anchor="middle" font-size="14" fill="#333" font-weight="700">Up-Counting Timer Timing Diagram</text>
<line x1="180" y1="40" x2="720" y2="40" stroke="#ddd" stroke-width="1"/>
<g transform="translate(50, 55)">
<!-- ===== Parameters ===== -->
<!-- X-axis: 0 to 500px = 50 ticks (10px per tick) -->
<!-- Signal height: 36px per signal, start at y=50 -->
<!-- Clock signal -->
<text x="-8" y="72" text-anchor="end" font-size="11" fill="#333" font-weight="600">clk</text>
<g transform="translate(0, 60)">
<polyline points="0,20 0,0 10,0 10,20 20,20 20,0 30,0 30,20 40,20 40,0 50,0 50,20 60,20 60,0 70,0 70,20 80,20 80,0 90,0 90,20 100,20 100,0 110,0 110,20 120,20 120,0 130,0 130,20 140,20 140,0 150,0 150,20 160,20 160,0 170,0 170,20 180,20 180,0 190,0 190,20 200,20 200,0 210,0 210,20 220,20 220,0 230,0 230,20 240,20 240,0 250,0 250,20 260,20 260,0 270,0 270,20 280,20 280,0 290,0 290,20 300,20 300,0 310,0 310,20 320,20 320,0 330,0 330,20 340,20 340,0 350,0 350,20 360,20 360,0 370,0 370,20 380,20 380,0 390,0 390,20 400,20 400,0 410,0 410,20 420,20 420,0 430,0 430,20 440,20 440,0 450,0 450,20 460,20 460,0 470,0 470,20 480,20 480,0 490,0 490,20 500,20 500,0" fill="none" stroke="#666" stroke-width="1.5"/>
</g>
<!-- ===== Count Bus (decimal values) ===== -->
<text x="-8" y="112" text-anchor="end" font-size="11" fill="#333" font-weight="600">count</text>
<g transform="translate(0, 90)">
<!-- Value at every 5 ticks, decimal -->
<rect x="-8" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="0" y="20" text-anchor="middle" font-size="10" fill="#1565c0">0</text>
<rect x="42" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="50" y="20" text-anchor="middle" font-size="10" fill="#1565c0">5</text>
<rect x="92" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="100" y="20" text-anchor="middle" font-size="10" fill="#1565c0">10</text>
<rect x="142" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="150" y="20" text-anchor="middle" font-size="10" fill="#1565c0">15</text>
<rect x="192" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="200" y="20" text-anchor="middle" font-size="10" fill="#1565c0">20</text>
<rect x="242" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="250" y="20" text-anchor="middle" font-size="10" fill="#1565c0">25</text>
<rect x="292" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="300" y="20" text-anchor="middle" font-size="10" fill="#1565c0">30</text>
<rect x="342" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="350" y="20" text-anchor="middle" font-size="10" fill="#1565c0">35</text>
<rect x="392" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="400" y="20" text-anchor="middle" font-size="10" fill="#1565c0">40</text>
<rect x="442" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="450" y="20" text-anchor="middle" font-size="10" fill="#1565c0">45</text>
<!-- Peak: count reaches 50, then reload -->
<rect x="492" y="0" width="16" height="30" fill="#fff3e0" stroke="#ffb74d" stroke-width="1.5" rx="2"/>
<text x="500" y="20" text-anchor="middle" font-size="10" fill="#e65100" font-weight="700">50</text>
<rect x="524" y="0" width="86" height="30" fill="#fce4ec" stroke="#e57373" stroke-width="1.5" rx="2"/>
<text x="567" y="20" text-anchor="middle" font-size="10" fill="#c62828">reset / reload</text>
<rect x="622" y="0" width="16" height="30" fill="#e8f5e9" stroke="#81c784" stroke-width="1.5" rx="2"/>
<text x="630" y="20" text-anchor="middle" font-size="10" fill="#2e7d32">0</text>
</g>
<!-- ===== TEZ Signal ===== -->
<text x="-8" y="152" text-anchor="end" font-size="11" fill="#333" font-weight="600">TEZ</text>
<g transform="translate(0, 130)">
<!-- Baseline low -->
<line x1="0" y1="20" x2="500" y2="20" stroke="#e53935" stroke-width="1.5"/>
<!-- Pulse at period start (count = 0) -->
<polyline points="0,20 0,0 10,0 10,20" fill="none" stroke="#e53935" stroke-width="1.5"/>
<!-- Pulse on reload (count returns to 0) -->
<polyline points="626,20 626,0 634,0 634,20" fill="none" stroke="#e53935" stroke-width="1.5"/>
</g>
<!-- ===== TEP Signal ===== -->
<text x="-8" y="192" text-anchor="end" font-size="11" fill="#333" font-weight="600">TEP</text>
<g transform="translate(0, 170)">
<!-- Baseline low -->
<line x1="0" y1="20" x2="500" y2="20" stroke="#1e88e5" stroke-width="1.5"/>
<!-- Pulse at peak (count = 50) -->
<polyline points="498,20 498,0 504,0 504,20" fill="none" stroke="#1e88e5" stroke-width="1.5"/>
</g>
<!-- ===== Waveform (sawtooth) ===== -->
<text x="-8" y="232" text-anchor="end" font-size="11" fill="#333" font-weight="600">wave</text>
<g transform="translate(0, 210)">
<!-- Sawtooth: rise 0 -> 50 across one period, reset at the period boundary, then rise again -->
<polyline points="0,20 500,0 500,20 630,15" fill="none" stroke="#8e24aa" stroke-width="2"/>
</g>
<!-- ===== Period bracket ===== -->
<g transform="translate(0, 250)">
<line x1="0" y1="0" x2="0" y2="12" stroke="#999" stroke-width="1"/>
<line x1="500" y1="0" x2="500" y2="12" stroke="#999" stroke-width="1"/>
<line x1="0" y1="6" x2="500" y2="6" stroke="#999" stroke-width="1"/>
<text x="250" y="30" text-anchor="middle" font-size="12" fill="#555">auto-reload period = 50 ticks</text>
</g>
<!-- ===== Annotation: relation lines ===== -->
<g transform="translate(0, 0)">
<!-- Count=0 → TEZ -->
<line x1="0" y1="120" x2="0" y2="150" stroke="#e53935" stroke-width="1" stroke-dasharray="4,4"/>
<!-- Count=50 → TEP -->
<line x1="500" y1="120" x2="500" y2="190" stroke="#1e88e5" stroke-width="1" stroke-dasharray="4,4"/>
<!-- Peak marker -->
<line x1="500" y1="60" x2="500" y2="250" stroke="#ffb74d" stroke-width="1" stroke-dasharray="6,4" opacity="0.5"/>
<text x="500" y="50" text-anchor="middle" font-size="9" fill="#e65100">peak</text>
</g>
<!-- Signal labels on the right -->
<g transform="translate(660, 0)">
<text x="0" y="75" font-size="9" fill="#888">Timer clock source</text>
<text x="0" y="115" font-size="9" fill="#888">Counter value (decimal)</text>
<text x="0" y="155" font-size="9" fill="#888">Pulse when counter = 0</text>
<text x="0" y="195" font-size="9" fill="#888">Pulse when counter = peak</text>
<text x="0" y="235" font-size="9" fill="#888">Sawtooth: count 0 to 50, then reset</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@@ -0,0 +1,122 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 370" font-family="'DejaVu Sans Mono', 'Consolas', 'Liberation Mono', monospace">
<defs>
<marker id="arrowhead" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto">
<polygon points="0,0 8,3 0,6" fill="#555"/>
</marker>
</defs>
<rect width="900" height="370" fill="#ffffff" rx="8"/>
<!-- Title -->
<text x="450" y="32" text-anchor="middle" font-size="14" fill="#333" font-weight="700">Up-Down Counting Timer Timing Diagram</text>
<line x1="180" y1="40" x2="720" y2="40" stroke="#ddd" stroke-width="1"/>
<g transform="translate(50, 55)">
<!-- ===== Parameters ===== -->
<!-- X-axis: 0 to 500px = 50 ticks (10px per tick) -->
<!-- Peak at 25: 50 ticks total, up 0→25 (250px), down 25→0 (250px) -->
<!-- Clock signal -->
<text x="-8" y="72" text-anchor="end" font-size="11" fill="#333" font-weight="600">clk</text>
<g transform="translate(0, 60)">
<polyline points="0,20 0,0 10,0 10,20 20,20 20,0 30,0 30,20 40,20 40,0 50,0 50,20 60,20 60,0 70,0 70,20 80,20 80,0 90,0 90,20 100,20 100,0 110,0 110,20 120,20 120,0 130,0 130,20 140,20 140,0 150,0 150,20 160,20 160,0 170,0 170,20 180,20 180,0 190,0 190,20 200,20 200,0 210,0 210,20 220,20 220,0 230,0 230,20 240,20 240,0 250,0 250,20 260,20 260,0 270,0 270,20 280,20 280,0 290,0 290,20 300,20 300,0 310,0 310,20 320,20 320,0 330,0 330,20 340,20 340,0 350,0 350,20 360,20 360,0 370,0 370,20 380,20 380,0 390,0 390,20 400,20 400,0 410,0 410,20 420,20 420,0 430,0 430,20 440,20 440,0 450,0 450,20 460,20 460,0 470,0 470,20 480,20 480,0 490,0 490,20 500,20 500,0" fill="none" stroke="#666" stroke-width="1.5"/>
</g>
<!-- ===== Count Bus (decimal values, up then down) ===== -->
<text x="-8" y="112" text-anchor="end" font-size="11" fill="#333" font-weight="600">count</text>
<g transform="translate(0, 90)">
<!-- Rising segment: 0 → 25 -->
<rect x="-8" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="0" y="20" text-anchor="middle" font-size="10" fill="#1565c0">0</text>
<rect x="42" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="50" y="20" text-anchor="middle" font-size="10" fill="#1565c0">5</text>
<rect x="92" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="100" y="20" text-anchor="middle" font-size="10" fill="#1565c0">10</text>
<rect x="142" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="150" y="20" text-anchor="middle" font-size="10" fill="#1565c0">15</text>
<rect x="192" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="200" y="20" text-anchor="middle" font-size="10" fill="#1565c0">20</text>
<!-- Peak: count reaches 25 -->
<rect x="242" y="0" width="16" height="30" fill="#fff3e0" stroke="#ffb74d" stroke-width="1.5" rx="2"/>
<text x="250" y="20" text-anchor="middle" font-size="10" fill="#e65100" font-weight="700">25</text>
<!-- Falling segment: 20 → 0 -->
<rect x="292" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="300" y="20" text-anchor="middle" font-size="10" fill="#1565c0">20</text>
<rect x="342" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="350" y="20" text-anchor="middle" font-size="10" fill="#1565c0">15</text>
<rect x="392" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="400" y="20" text-anchor="middle" font-size="10" fill="#1565c0">10</text>
<rect x="442" y="0" width="16" height="30" fill="#e3f2fd" stroke="#90caf9" stroke-width="1" rx="2"/>
<text x="450" y="20" text-anchor="middle" font-size="10" fill="#1565c0">5</text>
<!-- End of cycle: back to 0, then the next cycle counts up again -->
<rect x="492" y="0" width="16" height="30" fill="#e8f5e9" stroke="#81c784" stroke-width="1.5" rx="2"/>
<text x="500" y="20" text-anchor="middle" font-size="10" fill="#2e7d32">0</text>
</g>
<!-- ===== TEZ Signal ===== -->
<text x="-8" y="152" text-anchor="end" font-size="11" fill="#333" font-weight="600">TEZ</text>
<g transform="translate(0, 130)">
<line x1="0" y1="20" x2="500" y2="20" stroke="#e53935" stroke-width="1.5"/>
<!-- Pulse at cycle start (count = 0) -->
<polyline points="0,20 0,0 10,0 10,20" fill="none" stroke="#e53935" stroke-width="1.5"/>
<!-- Pulse at cycle end (count returns to 0) -->
<polyline points="490,20 490,0 500,0 500,20" fill="none" stroke="#e53935" stroke-width="1.5"/>
</g>
<!-- ===== TEP Signal ===== -->
<text x="-8" y="192" text-anchor="end" font-size="11" fill="#333" font-weight="600">TEP</text>
<g transform="translate(0, 170)">
<line x1="0" y1="20" x2="500" y2="20" stroke="#1e88e5" stroke-width="1.5"/>
<!-- Pulse at peak (count = 25) -->
<polyline points="240,20 240,0 260,0 260,20" fill="none" stroke="#1e88e5" stroke-width="1.5"/>
</g>
<!-- ===== Waveform (triangle) ===== -->
<text x="-8" y="232" text-anchor="end" font-size="11" fill="#333" font-weight="600">wave</text>
<g transform="translate(0, 210)">
<!-- Triangle: rise 0 -> 25, fall 25 -> 0, then rise again for the next cycle -->
<polyline points="0,20 250,0 500,20 630,10" fill="none" stroke="#8e24aa" stroke-width="2"/>
</g>
<!-- ===== Period bracket ===== -->
<g transform="translate(0, 250)">
<line x1="0" y1="0" x2="0" y2="12" stroke="#999" stroke-width="1"/>
<line x1="500" y1="0" x2="500" y2="12" stroke="#999" stroke-width="1"/>
<line x1="0" y1="6" x2="500" y2="6" stroke="#999" stroke-width="1"/>
<text x="250" y="30" text-anchor="middle" font-size="12" fill="#555">auto-reload period = 50 ticks (peak = 25)</text>
</g>
<!-- ===== Annotation: relation lines ===== -->
<g transform="translate(0, 0)">
<!-- Count=0 → TEZ -->
<line x1="0" y1="120" x2="0" y2="150" stroke="#e53935" stroke-width="1" stroke-dasharray="4,4"/>
<line x1="500" y1="120" x2="500" y2="150" stroke="#e53935" stroke-width="1" stroke-dasharray="4,4"/>
<!-- Count=25 → TEP -->
<line x1="250" y1="120" x2="250" y2="190" stroke="#1e88e5" stroke-width="1" stroke-dasharray="4,4"/>
<!-- Peak marker -->
<line x1="250" y1="60" x2="250" y2="250" stroke="#ffb74d" stroke-width="1" stroke-dasharray="6,4" opacity="0.5"/>
<text x="250" y="50" text-anchor="middle" font-size="9" fill="#e65100">peak</text>
</g>
<!-- Signal labels on the right -->
<g transform="translate(545, 0)">
<text x="0" y="75" font-size="9" fill="#888">Timer clock source</text>
<text x="0" y="115" font-size="9" fill="#888">Counter value (decimal), up then down</text>
<text x="0" y="155" font-size="9" fill="#888">Pulse when counter = 0</text>
<text x="0" y="195" font-size="9" fill="#888">Pulse when counter = peak</text>
<text x="0" y="235" font-size="9" fill="#888">Triangle: count 0 to 25, back to 0</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -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']

View File

@@ -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.

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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 <mcpwm_timer>` supplies time, an :doc:`operator <mcpwm_operator>` owns the waveform resources, a :doc:`comparator <mcpwm_cmpr>` chooses an edge position, and a :doc:`generator <mcpwm_gen>` 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<br/>mcpwm_new_timer"]:::time
O1["2. Create operator<br/>mcpwm_new_operator"]:::core
LINK["3. Connect time base<br/>mcpwm_operator_connect_timer"]:::core
C1["4. Create comparator<br/>mcpwm_new_comparator"]:::wave
G1["5. Create generator<br/>mcpwm_new_generator"]:::wave
A1["6. Describe edges<br/>mcpwm_generator_set_action_on_*_event"]:::wave
RUN["7. Enable and start<br/>mcpwm_timer_enable<br/>mcpwm_timer_start_stop"]:::run
PIN["PWM on GPIO"]:::output
T1 --> O1 --> LINK --> C1 --> G1 --> A1 --> RUN --> PIN
DT["Dead time<br/>mcpwm_generator_set_dead_time"]:::optional
BR["Fault and brake<br/>mcpwm_new_*_fault<br/>mcpwm_operator_set_brake_on_fault"]:::safety
SY["Phase synchronization<br/>mcpwm_new_*_sync_src<br/>mcpwm_timer_set_phase_on_sync"]:::optional
CA["Carrier modulation<br/>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 <mcpwm_timer>` -> :doc:`operator <mcpwm_operator>` -> :doc:`comparator <mcpwm_cmpr>`
:doc:`generator <mcpwm_gen>`
- :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 <mcpwm_gen>` + :doc:`fault <mcpwm_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 <mcpwm_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 <mcpwm_cap>`
- :cpp:func:`mcpwm_new_capture_timer`
:cpp:func:`mcpwm_capture_channel_register_event_callbacks`
- HC-SR04, tachometer, RC input
* - Hardware peripheral linking
- :doc:`ETM <mcpwm_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 <mcpwm_advanced>` 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

View File

@@ -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<br/>(e.g. 80 MHz)"]:::src -->|"÷ group prescale<br/>shared by the group"| grp["Group clock<br/>(e.g. 40 MHz)"]:::grp
grp -->|"÷ timer prescale"| pwm["PWM timer<br/>resolution_hz = 10 MHz"]:::mod
grp -->|"÷ capture prescale"| cap["Capture timer<br/>resolution_hz = 20 MHz"]:::mod
classDef src fill:#fef3c7,stroke:#d97706,color:#78350f
classDef grp fill:#dbeafe,stroke:#2563eb,color:#172554
classDef mod fill:#dcfce7,stroke:#16a34a,color:#14532d
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 <mcpwm_timer_config_t::flags::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.

View File

@@ -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 <mcpwm_capture_timer_config_t::group_id>` — the MCPWM group the capture timer is allocated from.
- :cpp:member:`clk_src <mcpwm_capture_timer_config_t::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 <mcpwm_capture_timer_config_t::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 <mcpwm_capture_timer_config_t::flags::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 <mcpwm_capture_channel_config_t::gpio_num>` — the GPIO carrying the input signal.
- :cpp:member:`prescale <mcpwm_capture_channel_config_t::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 <mcpwm_capture_channel_config_t::flags::pos_edge>` and :cpp:member:`neg_edge <mcpwm_capture_channel_config_t::flags::neg_edge>` — which edges are captured. The example captures both, which is what a pulse-width measurement needs.
- :cpp:member:`invert_cap_signal <mcpwm_capture_channel_config_t::flags::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 <mcpwm_capture_channel_config_t::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 <mcpwm_capture_timer_sync_phase_config_t::sync_src>` — the sync source; pass ``NULL`` to detach synchronization.
- :cpp:member:`count_value <mcpwm_capture_timer_sync_phase_config_t::count_value>` — the count loaded when the sync edge arrives.
- :cpp:member:`direction <mcpwm_capture_timer_sync_phase_config_t::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 <mcpwm_sync>` for how to create the sync sources and other details.
API Reference
=============
MCPWM Capture Driver Functions
------------------------------
.. include-build-file:: inc/mcpwm_cap.inc

View File

@@ -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 <mcpwm_timer>`, 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 <mcpwm_comparator_config_t::intr_priority>` — the interrupt priority used by the :cpp:member:`on_reach <mcpwm_comparator_event_callbacks_t::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 <mcpwm_comparator_config_t::flags>` — the update points explained below. The example enables :cpp:member:`update_cmp_on_tez <mcpwm_comparator_config_t::flags::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 <mcpwm_comparator_config_t::flags::update_cmp_on_tez>` buffers it until the counter reaches zero, :cpp:member:`update_cmp_on_tep <mcpwm_comparator_config_t::flags::update_cmp_on_tep>` until it reaches the peak, and :cpp:member:`update_cmp_on_sync <mcpwm_comparator_config_t::flags::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 <mcpwm_comparator_event_callbacks_t::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 </api-reference/peripherals/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

View File

@@ -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 </api-reference/peripherals/etm>` documentation.
.. mermaid::
flowchart LR
T["MCPWM Timer<br/>TEZ/TEP event"]:::source --> E["ETM Channel"]:::route
C["MCPWM Comparator<br/>compare event"]:::source --> E
E --> D["Destination<br/>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.

View File

@@ -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 <mcpwm-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 <mcpwm_gpio_fault_config_t::group_id>` — the MCPWM group the fault belongs to. It must match the group of the operator consuming the fault.
- :cpp:member:`gpio_num <mcpwm_gpio_fault_config_t::gpio_num>` — the GPIO carrying the fault signal.
- :cpp:member:`active_level <mcpwm_gpio_fault_config_t::flags::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 <mcpwm_gpio_fault_config_t::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 <mcpwm-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 <mcpwm-brake>`.
.. 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 <mcpwm_fault_event_callbacks_t::on_fault_enter>` and :cpp:member:`on_fault_exit <mcpwm_fault_event_callbacks_t::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

View File

@@ -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 <mcpwm_timer>`/:doc:`operator <mcpwm_operator>`/:doc:`comparator <mcpwm_cmpr>` 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 <mcpwm_generator_config_t::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 <mcpwm_generator_config_t::flags::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 <mcpwm_generator_action_t::MCPWM_GEN_ACTION_HIGH>`, :cpp:enumerator:`MCPWM_GEN_ACTION_LOW <mcpwm_generator_action_t::MCPWM_GEN_ACTION_LOW>`, or :cpp:enumerator:`MCPWM_GEN_ACTION_TOGGLE <mcpwm_generator_action_t::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 <mcpwm_timer_event_t::MCPWM_TIMER_EVENT_EMPTY>` is the zero boundary and :cpp:enumerator:`MCPWM_TIMER_EVENT_FULL <mcpwm_timer_event_t::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 <mcpwm_timer_direction_t::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 <mcpwm_timer_direction_t::MCPWM_TIMER_DIRECTION_UP>` and :cpp:enumerator:`MCPWM_TIMER_DIRECTION_DOWN <mcpwm_timer_direction_t::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 <mcpwm-dead-time>` 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) <mcpwm_generator_set_dead_time>` 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 <mcpwm_dead_time_config_t::posedge_delay_ticks>` delays a rising edge and :cpp:member:`negedge_delay_ticks <mcpwm_dead_time_config_t::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 <mcpwm_dead_time_config_t::flags::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 <mcpwm_operator_config_t::flags::update_dead_time_on_tez>`, :cpp:member:`update_dead_time_on_tep <mcpwm_operator_config_t::flags::update_dead_time_on_tep>`, or :cpp:member:`update_dead_time_on_sync <mcpwm_operator_config_t::flags::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 <mcpwm-brake>`. It is set per brake mode and timer direction; see :ref:`Fault connection <mcpwm-brake-fault-connection>` for a full example.
For safety policy and persistent braking, prefer the operator :ref:`brake mechanism <mcpwm-brake>`. 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

View File

@@ -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 <mcpwm_operator_config_t::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 <mcpwm_operator_config_t::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 <mcpwm_operator_config_t::flags::update_gen_action_on_tez>`, :cpp:member:`update_gen_action_on_tep <mcpwm_operator_config_t::flags::update_gen_action_on_tep>`, and :cpp:member:`update_gen_action_on_sync <mcpwm_operator_config_t::flags::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 <mcpwm_operator_config_t::flags::update_dead_time_on_tez>`, :cpp:member:`update_dead_time_on_tep <mcpwm_operator_config_t::flags::update_dead_time_on_tep>`, and :cpp:member:`update_dead_time_on_sync <mcpwm_operator_config_t::flags::update_dead_time_on_sync>` — buffer dead-time changes the same way; see :ref:`dead time <mcpwm-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 <mcpwm_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 <mcpwm_brake_config_t::flags::cbc_recover_on_tez>` or :cpp:member:`cbc_recover_on_tep <mcpwm_brake_config_t::flags::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 <mcpwm_operator_event_callbacks_t::on_brake_cbc>` and :cpp:member:`on_brake_ost <mcpwm_operator_event_callbacks_t::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 <mcpwm_carrier_config_t::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 <mcpwm_carrier_config_t::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 <mcpwm_carrier_config_t::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 <mcpwm_carrier_config_t::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 <mcpwm_carrier_config_t::flags::invert_before_modulate>` when the raw PWM needs a polarity change and :cpp:member:`invert_after_modulate <mcpwm_carrier_config_t::flags::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

View File

@@ -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 <mcpwm_gpio_sync_src_config_t::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 <mcpwm_gpio_sync_src_config_t::gpio_num>` — the GPIO carrying the sync signal.
- :cpp:member:`active_neg <mcpwm_gpio_sync_src_config_t::flags::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 <mcpwm_timer_sync_src_config_t::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 <mcpwm_timer_sync_src_config_t::flags::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 <mcpwm_timer_sync_phase_config_t::sync_src>` — the source object. Set it to ``NULL`` to detach synchronization.
- :cpp:member:`count_value <mcpwm_timer_sync_phase_config_t::count_value>` — the count loaded when the sync event arrives. Keep it within the timer period.
- :cpp:member:`direction <mcpwm_timer_sync_phase_config_t::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

View File

@@ -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 <mcpwm_timer_config_t::resolution_hz>` and wraps around at :cpp:member:`period_ticks <mcpwm_timer_config_t::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 <mcpwm_timer_config_t::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 <mcpwm_timer_config_t::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 <mcpwm_timer_config_t::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 <mcpwm_timer_config_t::period_ticks>` — the length of one full PWM cycle in ticks. The frequency is ``resolution_hz / period_ticks``.
- :cpp:member:`count_mode <mcpwm_timer_config_t::count_mode>` — whether the counter counts up only (edge-aligned PWM) or up and down (center-aligned PWM). See :ref:`Counting modes and waveforms <mcpwm-timer-counting-modes>` for the two shapes; the hardware also supports counting down.
- :cpp:member:`intr_priority <mcpwm_timer_config_t::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 <mcpwm_timer_config_t::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 <mcpwm_timer_config_t::flags::update_period_on_empty>` and :cpp:member:`update_period_on_sync <mcpwm_timer_config_t::flags::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 <mcpwm-timer-safe-update>`.
- :cpp:member:`allow_pd <mcpwm_timer_config_t::flags::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 <mcpwm_timer_config_t::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 <mcpwm_timer_config_t::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 <mcpwm_timer_config_t::resolution_hz>` = 1 MHz and :cpp:member:`period_ticks <mcpwm_timer_config_t::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 <mcpwm_timer_config_t::flags::update_period_on_empty>` to defer the new period until the counter reaches zero, or :cpp:member:`update_period_on_sync <mcpwm_timer_config_t::flags::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 <mcpwm_timer_config_t::flags::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 <mcpwm_timer_event_callbacks_t::on_full>`), zero (:cpp:member:`on_empty <mcpwm_timer_event_callbacks_t::on_empty>`), or when it stops (:cpp:member:`on_stop <mcpwm_timer_event_callbacks_t::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 <mcpwm_advanced>` 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 <mcpwm_sync>` 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

View File

@@ -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 </api-reference/peripherals/mcpwm/index>`), 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.

View File

@@ -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"

View File

@@ -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`。

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,228 @@
===========================
电机控制脉宽调制器 (MCPWM)
===========================
:link_to_translation:`en:[English]`
从这里开始
============
MCPWM 将计数器转换为时序精确的输出边沿。当 LEDC 的简单 PWM 已无法满足需求时,可使用 MCPWM电机桥需要互补输出和死区逆变器需要同步相位传感器则需要精确测量脉宽。
最小可用的 MCPWM 设计由四个对象构成::doc:`定时器 <mcpwm_timer>` 提供时间基准,:doc:`操作器 <mcpwm_operator>` 管理波形资源,:doc:`比较器 <mcpwm_cmpr>` 决定边沿位置,:doc:`生成器 <mcpwm_gen>` 驱动 GPIO。其他模块均在此基础上扩展。
构建一路 PWM 输出
==================
初次生成 PWM 输出时,请按下图从左至右创建对象。主线上各阶段按角色配色:时间基准(蓝色)、操作器核心(紫色)、波形配置(青色)、启用与输出(绿色)。琥珀色节点为基础输出正常后按需添加的扩展,红色节点为安全制动。只有在所有输出动作均已配置完成后,才启动定时器。
.. mermaid::
flowchart LR
T1["1. 创建定时器<br/>mcpwm_new_timer"]:::time
O1["2. 创建操作器<br/>mcpwm_new_operator"]:::core
LINK["3. 连接时间基准<br/>mcpwm_operator_connect_timer"]:::core
C1["4. 创建比较器<br/>mcpwm_new_comparator"]:::wave
G1["5. 创建生成器<br/>mcpwm_new_generator"]:::wave
A1["6. 描述边沿<br/>mcpwm_generator_set_action_on_*_event"]:::wave
RUN["7. 启用并启动<br/>mcpwm_timer_enable<br/>mcpwm_timer_start_stop"]:::run
PIN["GPIO 输出 PWM"]:::output
T1 --> O1 --> LINK --> C1 --> G1 --> A1 --> RUN --> PIN
DT["死区<br/>mcpwm_generator_set_dead_time"]:::optional
BR["故障与制动<br/>mcpwm_new_*_fault<br/>mcpwm_operator_set_brake_on_fault"]:::safety
SY["相位同步<br/>mcpwm_new_*_sync_src<br/>mcpwm_timer_set_phase_on_sync"]:::optional
CA["载波调制<br/>mcpwm_operator_apply_carrier"]:::optional
A1 -. 扩展 .-> DT
O1 -. 保护 .-> BR
T1 -. 对齐 .-> SY
O1 -. 调制 .-> CA
classDef time fill:#dbeafe,stroke:#2563eb,color:#172554
classDef core fill:#ede9fe,stroke:#7c3aed,color:#2e1065
classDef wave fill:#cffafe,stroke:#0891b2,color:#164e63
classDef run fill:#dcfce7,stroke:#16a34a,color:#14532d
classDef output fill:#bbf7d0,stroke:#15803d,color:#14532d
classDef optional fill:#fef3c7,stroke:#d97706,color:#78350f
classDef safety fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
下面这段代码创建一路 20 kHz、30% 占空比的 PWM 输出,可作为阅读后续各页前的整体参考。它展示了对象的创建顺序,也说明了运行时最常改动的其实是比较器,而不是重新配置整条链路。
.. code-block:: c
mcpwm_timer_handle_t timer = NULL;
mcpwm_oper_handle_t oper = NULL;
mcpwm_cmpr_handle_t comparator = NULL;
mcpwm_gen_handle_t generator = NULL;
// 1 MHz → 1 tick = 1 µs
// 50 ticks → 50 µs 周期 → 20 kHz
ESP_ERROR_CHECK(mcpwm_new_timer(
&(mcpwm_timer_config_t) {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1000000,
.period_ticks = 50,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
},
&timer));
ESP_ERROR_CHECK(mcpwm_new_operator(
&(mcpwm_operator_config_t) {
.group_id = 0,
},
&oper));
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer));
ESP_ERROR_CHECK(mcpwm_new_comparator(
oper,
&(mcpwm_comparator_config_t) {
.flags.update_cmp_on_tez = true,
},
&comparator));
// 15 / 50 = 30% 占空比
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 15));
ESP_ERROR_CHECK(mcpwm_new_generator(
oper,
&(mcpwm_generator_config_t) {
.gen_gpio_num = 18,
},
&generator));
// 定时器归零 → 输出 HIGH比较器匹配 → 输出 LOW
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
generator,
MCPWM_GEN_TIMER_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP,
MCPWM_TIMER_EVENT_EMPTY,
MCPWM_GEN_ACTION_HIGH)));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
generator,
MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP,
comparator,
MCPWM_GEN_ACTION_LOW)));
ESP_ERROR_CHECK(mcpwm_timer_enable(timer));
ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
// 运行时修改比较值即可调整占空比,无需重建生成器动作。
// 25 / 50 = 50% 占空比
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 25));
定时器的 ``resolution_hz````period_ticks`` 确定时序刻度;比较器的 ``compare_value`` 在该刻度中选择边沿位置;生成器动作 API 决定在定时器边界或比较器越过阈值时输出何种电平。这种分工也便于调参:改变定时器可调整频率,改变比较器可调整占空比或边沿位置,改变生成器动作可调整极性或波形形状。
波形配置完成后,调用 :cpp:func:`mcpwm_timer_enable()`:cpp:func:`mcpwm_timer_start_stop()`。运行时应通过 :cpp:func:`mcpwm_comparator_set_compare_value()` 更新比较器,而不是重新配置生成器动作。仅在应用需要时添加对应扩展:半桥使用死区,安全路径使用故障与制动,相位对齐使用同步,隔离式驱动使用载波。
功能地图
========
.. list-table::
:header-rows: 1
:widths: 16 36 34 24
* - 目标
- 先看哪些页
- 关键 API
- 典型应用
* - 输出单路 PWM
- :doc:`定时器 <mcpwm_timer>` -> :doc:`操作器 <mcpwm_operator>` -> :doc:`比较器 <mcpwm_cmpr>`
:doc:`生成器 <mcpwm_gen>`
- :cpp:func:`mcpwm_new_timer`
:cpp:func:`mcpwm_new_comparator`
``mcpwm_generator_set_action_on_*_event``
- 舵机、调光、基础功率控制
* - 输出互补半桥 PWM
- :doc:`生成器 <mcpwm_gen>` 中的死区小节 + :doc:`故障 <mcpwm_fault>`
- :cpp:func:`mcpwm_generator_set_dead_time`
:cpp:func:`mcpwm_operator_set_brake_on_fault`
- 半桥、逆变桥臂
* - 多路同频对齐或移相
- :doc:`同步 <mcpwm_sync>`
- :cpp:func:`mcpwm_timer_set_phase_on_sync`
:cpp:func:`mcpwm_new_timer_sync_src`
- 多相电机、并联变换器
* - 测量输入脉宽或周期
- :doc:`捕获 <mcpwm_cap>`
- :cpp:func:`mcpwm_new_capture_timer`
:cpp:func:`mcpwm_capture_channel_register_event_callbacks`
- HC-SR04、转速计、RC 输入
* - 外设间硬件联动
- :doc:`ETM <mcpwm_etm>`
- :cpp:func:`mcpwm_timer_new_etm_event`
:cpp:func:`mcpwm_new_event_comparator`
- ADC 触发、跨外设定时链路
本指南中每个页面介绍一个 MCPWM 模块:
.. toctree::
:maxdepth: 1
mcpwm_timer
mcpwm_operator
mcpwm_cmpr
mcpwm_gen
mcpwm_fault
mcpwm_sync
mcpwm_cap
mcpwm_etm
mcpwm_advanced
资源与生命周期
==============
所有对象都属于一个 MCPWM 组。连接的定时器与操作器必须位于同一组GPIO 故障源和 GPIO 同步源也只能在本组中使用。硬件资源有限,创建时可能返回 :c:macro:`ESP_ERR_NOT_FOUND`
每个对象都由 ``mcpwm_new_*()`` 工厂函数创建并返回一个不透明句柄,由对应的 ``mcpwm_del_*()`` 函数释放,例如 :cpp:func:`mcpwm_new_timer()`:cpp:func:`mcpwm_del_timer()`。先创建父对象,再创建子对象;释放时按相反顺序执行:先删除生成器/比较器,再删除操作器,最后删除定时器。删除定时器前必须禁用它;删除捕获定时器前必须删除其通道。
组时钟分频器由定时器共享,部分芯片的捕获定时器也共享它。按目标分辨率单调顺序(从高到低或从低到高)创建对象,可避免分频冲突。详见 :doc:`高级主题 <mcpwm_advanced>`
术语速查
========
.. list::
- **TEZ** Timer equals zero定时器计数等于零时触发的事件。
- **TEP** Timer equals peak定时器计数达到峰值时触发的事件。
- **定时器Timer** MCPWM 的时间基准,决定频率和 Tick 刻度。
- **操作器Operator** 连接定时器与输出逻辑的容器,管理比较器、生成器、制动、死区和载波。
- **比较器Comparator** 当计数达到阈值时发出事件,常用于决定边沿位置和占空比。
- **生成器Generator** 根据定时器/比较器/故障/同步事件输出 GPIO 电平。
- **死区Dead Time** 在半桥上下管切换之间插入的非重叠时间,避免直通。
- **故障Fault** 进入保护路径的异常源,可来自 GPIO 或软件。
- **制动Brake** 故障触发后的输出安全策略。
- **CBC** Cycle By Cycle故障有效时制动清除后在周期边界自动恢复。
- **OST** One Shot一次制动后保持锁存需软件显式恢复。
- **同步Sync** 在同步边沿把定时器加载到指定计数值和方向,以实现对齐或移相。
- **捕获Capture** 对输入边沿打时间戳,用于测脉宽、周期或转速。
应用示例
========
.. list::
- :example:`peripherals/mcpwm/mcpwm_servo_control`RC 舵机的单路 PWM。
- :example:`peripherals/mcpwm/mcpwm_bdc_speed_control`:有刷直流电机及速度反馈。
- :example:`peripherals/mcpwm/mcpwm_bldc_hall_control`:使用霍尔传感器反馈进行 BLDC 换相。
- :example:`peripherals/mcpwm/mcpwm_capture_hc_sr04`:使用 HC-SR04 测量脉宽。
- :example:`peripherals/mcpwm/mcpwm_sync`GPIO、定时器和软件同步。
- :example:`peripherals/mcpwm/mcpwm_foc_svpwm_open_loop`:开环 FOC 的三对互补 PWM。
API 参考
========
通用类型
--------
.. include-build-file:: inc/components/esp_driver_mcpwm/include/driver/mcpwm_types.inc
.. include-build-file:: inc/components/esp_hal_mcpwm/include/hal/mcpwm_types.inc

View File

@@ -0,0 +1,62 @@
==============
MCPWM 高级主题
==============
.. contents::
:local:
:depth: 2
分辨率与共享时钟
================
``resolution_hz`` 是定时器 Tick 频率,每个 Tick 持续 ``1 / resolution_hz`` 秒。驱动从所选源时钟计算分频器:请求频率能整除时优先精确满足;不能整除时,优先使组时钟尽可能高,再选择最接近的子模块频率。
组分频器由定时器共享。在捕获时钟来自组时钟的芯片上,捕获定时器也共享它。多个对象需要不同分辨率时,按同一单调顺序(从高到低或从低到高)创建,避免任意混排。
下面这张时钟树图展示单一源时钟如何逐级分频展开。组分频器在整个组内只固定一次,每个定时器只在它之上再加自己的预分频器:
.. mermaid::
flowchart LR
src["源时钟<br/>(如 80 MHz)"]:::src -->|"÷ 组分频<br/>整组共享"| grp["组时钟<br/>(如 40 MHz)"]:::grp
grp -->|"÷ 定时器预分频"| pwm["PWM 定时器<br/>resolution_hz = 10 MHz"]:::mod
grp -->|"÷ 捕获预分频"| cap["捕获定时器<br/>resolution_hz = 20 MHz"]:::mod
classDef src fill:#fef3c7,stroke:#d97706,color:#78350f
classDef grp fill:#dbeafe,stroke:#2563eb,color:#172554
classDef mod fill:#dcfce7,stroke:#16a34a,color:#14532d
因为组分频器是共享的,只会为所有定时器确定一次。之后若再请求不同分辨率,就无法改动这个已固定的分频器,驱动会尽量让组时钟保持较高,并为新子模块选择最接近的预分频。
电源管理与睡眠
==============
启用电源管理后,:cpp:func:`mcpwm_timer_enable()`:cpp:func:`mcpwm_capture_timer_enable()` 会持有 :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP` 锁,以确保定时器时钟频率稳定;调用 :cpp:func:`mcpwm_timer_disable()`:cpp:func:`mcpwm_capture_timer_disable()` 释放该锁。
定时器配置和捕获定时器配置中都有一个同名的 :cpp:member:`allow_pd <mcpwm_timer_config_t::flags::allow_pd>` 字段(后者见 :cpp:type:`mcpwm_capture_timer_config_t`),设置它可允许睡眠时关闭 MCPWM 电源域,驱动会备份和恢复寄存器,但会消耗额外 RAM。此功能取决于目标芯片。
ISR 与线程安全
==============
定时器、比较器、故障、操作器制动和捕获回调都在 ISR 上下文运行,必须非阻塞并使用 ISR 安全的 RTOS 调用。组中第一个注册的回调确定共享中断优先级;后续事件应使用相同优先级。
:cpp:func:`mcpwm_new_timer()` 等工厂函数线程安全。:cpp:func:`mcpwm_timer_set_period()`:cpp:func:`mcpwm_comparator_set_compare_value()` 可在 ISR 中调用。其他控制 API 通常不是线程安全的,多任务访问同一对象时应自行串行化。
缓存安全与实时操作
==================
正常情况下,缓存禁用时(如 Flash 操作期间MCPWM 中断响应会被延迟。当回调必须在缓存禁用时继续运行时,启用 :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE`;这会将 ISR 所需代码放入 IRAM对象放入 DRAM增加内部 RAM 使用。
.. note::
启用该选项后即使缓存被禁用MCPWM 中断仍会立即响应,不会被推迟。但中断处理链路上的所有代码——包括你注册的回调函数以及回调内部调用的所有子函数——都必须位于 IRAM 中:缓存关闭时 CPU 无法从 Flash 取指,一旦调用任何仍留在 Flash 中的函数CPU 就会崩溃。该选项只负责把驱动自身的 ISR 代码放入 IRAM你的回调函数及其调用的子函数需要自行显式放到 IRAM例如用 ``IRAM_ATTR`` 修饰)。
:menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` 还会将 :cpp:func:`mcpwm_timer_set_period()`:cpp:func:`mcpwm_comparator_set_compare_value()` 放入 IRAM这样即使缓存关闭这些调用仍可正常工作——例如你可以在缓存被禁用的上下文如 Flash 擦写期间)中重新调整 PWM 周期或占空比,而无需等待缓存重新启用。
Kconfig 选项
==============
.. list::
- :menuitem:`CONFIG_MCPWM_ISR_CACHE_SAFE` 启用缓存安全中断。
- :menuitem:`CONFIG_MCPWM_CTRL_FUNC_IN_IRAM` 将选定控制函数放入 IRAM。
- :menuitem:`CONFIG_MCPWM_ENABLE_DEBUG_LOG` 强制 MCPWM 驱动编译并打印自身的调试日志,忽略全局日志设置,仅对驱动本身将运行时日志等级提升到冗长——不影响其他模块。代价是固件体积增大。

View File

@@ -0,0 +1,148 @@
=========================
MCPWM 捕获:测量输入脉冲
=========================
.. contents::
:local:
:depth: 2
捕获是独立的 MCPWM 路径:捕获定时器为捕获通道 GPIO 的边沿打时间戳,不需要 PWM 定时器、操作器、比较器或生成器,适用于回波脉冲、转速计、霍尔传感器和 RC 接收机。
它适合"把外部世界发生的时刻带进芯片里"。当你关心的是脉宽、周期、相位差或转速,而不是输出 PWM本页就是 MCPWM 的另一条主线入口。
测量脉宽
========
同时捕获两个边沿保存上升沿时间戳再用下降沿时间戳减去它。1 MHz 分辨率下,差值的单位直接是微秒。
.. code-block:: c
mcpwm_cap_timer_handle_t cap_timer = NULL;
mcpwm_cap_channel_handle_t cap_channel = NULL;
ESP_ERROR_CHECK(mcpwm_new_capture_timer(
&(mcpwm_capture_timer_config_t) {
.group_id = 0,
.clk_src = MCPWM_CAPTURE_CLK_SRC_DEFAULT,
.resolution_hz = 1000000,
}, &cap_timer));
ESP_ERROR_CHECK(mcpwm_new_capture_channel(cap_timer,
&(mcpwm_capture_channel_config_t) {
.gpio_num = 6,
.prescale = 1,
.flags.pos_edge = true,
.flags.neg_edge = true,
}, &cap_channel));
仅分配对象并不会开始测量。还需要使能通道并启动捕获定时器:
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_capture_channel_enable(cap_channel));
ESP_ERROR_CHECK(mcpwm_capture_timer_enable(cap_timer));
ESP_ERROR_CHECK(mcpwm_capture_timer_start(cap_timer));
:cpp:func:`mcpwm_capture_channel_enable()`:cpp:func:`mcpwm_capture_timer_enable()` 负责捕获所需的系统服务准备,二者都尚未开始测量。:cpp:func:`mcpwm_capture_timer_start()` 才真正启动计数器,边沿开始被打上时间戳。
捕获到的边沿值通过回调送达应用,详见下一节。
.. figure:: /../_static/mcpwm/capture_measurement.svg
:align: center
:alt: 捕获上升沿与下降沿时间戳,相减得到高电平脉宽。
捕获上升沿和下降沿时间戳,相减得到高电平脉宽。
两个配置结构体分开讲解:
捕获定时器配置
--------------
.. list::
- :cpp:member:`group_id <mcpwm_capture_timer_config_t::group_id>` — 捕获定时器从哪个 MCPWM 组分配。
- :cpp:member:`clk_src <mcpwm_capture_timer_config_t::clk_src>` — 捕获定时器的时钟源。:c:macro:`MCPWM_CAPTURE_CLK_SRC_DEFAULT` 适合绝大多数应用。当默认时钟源可能被关闭时需要显式选择其他源——例如低功耗场景下,若所选时钟被关断,捕获定时器会停止,时间戳就会失真。
- :cpp:member:`resolution_hz <mcpwm_capture_timer_config_t::resolution_hz>` — 捕获定时器的 Tick 频率。一个 Tick 持续 ``1 / resolution_hz``1 MHz 即微秒级分辨率,直接决定每次捕获时间戳的精度。
- :cpp:member:`allow_pd <mcpwm_capture_timer_config_t::flags::allow_pd>` — 允许睡眠时关闭 MCPWM 电源域,在睡眠前后备份并恢复捕获寄存器,代价是额外占用 RAM。
捕获通道配置
------------
.. list::
- :cpp:member:`gpio_num <mcpwm_capture_channel_config_t::gpio_num>` — 承载输入信号的 GPIO。
- :cpp:member:`prescale <mcpwm_capture_channel_config_t::prescale>` — 捕获前对输入信号分频,有效输入频率为捕获时钟除以 ``prescale``。提高它可扩展可测周期范围,但会降低时间分辨率。
- :cpp:member:`pos_edge <mcpwm_capture_channel_config_t::flags::pos_edge>`:cpp:member:`neg_edge <mcpwm_capture_channel_config_t::flags::neg_edge>` — 捕获哪些边沿。示例同时捕获两个边沿,这正是脉宽测量所需的。
- :cpp:member:`invert_cap_signal <mcpwm_capture_channel_config_t::flags::invert_cap_signal>` — 捕获前反相输入信号,让引脚上的逻辑 ``1`` 在捕获外设看来是 ``0``,反之亦然。
- :cpp:member:`intr_priority <mcpwm_capture_channel_config_t::intr_priority>` — 捕获回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级。
.. note::
捕获驱动会把 GPIO 配置为输入,但不会设置任何上拉或下拉电阻。如果输入信号并非主动驱动到两个电平,请调用 :cpp:func:`gpio_set_pull_mode()` 选择上拉或下拉方向,让引脚空闲时保持在你期望的电平。
捕获事件回调
============
事件数据告知边沿类型和锁存计数值。真实应用中应将耗时工作留给任务。
.. code-block:: c
static uint32_t rise_tick;
static bool IRAM_ATTR on_capture(mcpwm_cap_channel_handle_t channel,
const mcpwm_capture_event_data_t *edata,
void *user_data)
{
if (edata->cap_edge == MCPWM_CAP_EDGE_POS) {
rise_tick = edata->cap_value;
} else {
uint32_t width_ticks = edata->cap_value - rise_tick;
// 使用 ISR 安全方式将 width_ticks 通知任务
}
return false;
}
ESP_ERROR_CHECK(mcpwm_capture_channel_register_event_callbacks(cap_channel,
&(mcpwm_capture_event_callbacks_t) { .on_cap = on_capture }, NULL));
通过 :cpp:func:`mcpwm_capture_timer_get_resolution()` 获取实际分辨率后,再将 Tick 转换为时间。在捕获时钟与 MCPWM 组时钟共享的芯片上,应保持一致的请求分辨率顺序创建捕获和 PWM 定时器。
若要测转速或周期,可记录两次同类边沿(例如两次上升沿)的时间戳,相减得到周期 Tick 数,再结合实际分辨率换算为频率或转速。
实用控制
========
:cpp:func:`mcpwm_capture_channel_trigger_soft_catch()` 生成软件捕获事件,常用于测试,也可借此把重要软件事件的发生时刻落到捕获时间轴上,与硬件边沿时间戳对齐;该调用同样会触发回调。:cpp:func:`mcpwm_capture_get_latched_value()` 在不注册回调的情况下也能读取最新时间戳。
:cpp:func:`mcpwm_capture_timer_stop()` 使计数器停止,:cpp:func:`mcpwm_capture_channel_disable()` 可关闭单个输入,停止定时器则关闭整个测量引擎。删除对象前,先调用 :cpp:func:`mcpwm_capture_timer_disable()` 撤销 :cpp:func:`mcpwm_capture_timer_enable()` 做的准备工作。
捕获定时器同步
==============
捕获定时器默认自由运行,计数值的“零点”是任意的,时间戳只能互相比较。同步让运行中的捕获定时器在同步边沿到来时加载一个指定计数值,从而把时间戳对应到有意义的参考系。
最常见的场景是让捕获定时器与 PWM 定时器对齐相位:以 PWM 定时器每周期零TEZ发出的同步为源、计数值设为 0捕获定时器每个周期归零捕获时间戳就直接表示周期内的相位。这在电机控制或功率变换中尤为重要——霍尔、编码器或电流检测的反馈边沿只有落在 PWM 周期的具体相位才有意义。
同步源与 PWM 定时器共用GPIO、软件、定时器三种须与捕获定时器同组接收端用 :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()` 配置:
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_capture_timer_set_phase_on_sync(cap_timer,
&(mcpwm_capture_timer_sync_phase_config_t) {
.sync_src = timer_a_sync, // 由 mcpwm_new_timer_sync_src() 创建的同步源
.count_value = 0,
.direction = MCPWM_TIMER_DIRECTION_UP,
}));
.. list::
- :cpp:member:`sync_src <mcpwm_capture_timer_sync_phase_config_t::sync_src>` — 同步源,设为 ``NULL`` 可解除同步。
- :cpp:member:`count_value <mcpwm_capture_timer_sync_phase_config_t::count_value>` — 同步边沿到达时加载的计数值。
- :cpp:member:`direction <mcpwm_capture_timer_sync_phase_config_t::direction>` — 加载后的计数方向,捕获定时器只向上计数,固定为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP`
软件同步与 GPIO 同步源也可让捕获定时器建立已知起点或对齐外部参考,同步源的创建与其余用法详见 :doc:`同步 <mcpwm_sync>`
API 参考
========
MCPWM 捕获驱动函数
------------------
.. include-build-file:: inc/mcpwm_cap.inc

View File

@@ -0,0 +1,116 @@
=================================
MCPWM 比较器:将比例转换为边沿
=================================
.. contents::
:local:
:depth: 2
当定时器计数达到 ``cmp_ticks`` 时,比较器产生事件;生成器再将该事件转换为 GPIO 电平变化。通常在向上计数 PWM 中,比较 Tick 数就是高电平时间。
比较器的实际意义是把"我想要多宽、多晚、多早出现一个边沿"转换成硬件里的具体时刻。运行中的占空比调节,通常就是不断更新比较器阈值。
设置 30% 占空比
===============
从已有操作器创建比较器,再设置阈值。对 :doc:`定时器 <mcpwm_timer>` 中 50 Tick 的周期15 Tick 即为 30%。下图展示了比较事件在 Tick 15 处触发——生成器可用此事件结束高电平脉冲。
.. figure:: /../_static/mcpwm/compare_event.svg
:align: center
:alt: 定时器向上计数;比较器在 Tick 15 触发。生成器将其转换为下降沿。
定时器向上计数;比较器在 Tick 15 触发。生成器将其转换为下降沿。
.. code-block:: c
mcpwm_cmpr_handle_t comparator = NULL;
mcpwm_comparator_config_t comparator_config = {
.flags.update_cmp_on_tez = true, // 仅在周期边界改变占空比
};
ESP_ERROR_CHECK(mcpwm_new_comparator(oper, &comparator_config, &comparator));
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 15));
比较器配置除 flags 外只有一个字段:
.. list::
- :cpp:member:`intr_priority <mcpwm_comparator_config_t::intr_priority>`:cpp:member:`on_reach <mcpwm_comparator_event_callbacks_t::on_reach>` 回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级;当回调需要抢占其他中断时可适当提高。
- :cpp:member:`flags <mcpwm_comparator_config_t::flags>` — 即下文介绍的更新点。示例启用了 :cpp:member:`update_cmp_on_tez <mcpwm_comparator_config_t::flags::update_cmp_on_tez>`,这也是在周期边界改变占空比的常见做法。
运行时百分比占空比的计算公式为 ``period_ticks * percent / 100``,结果必须不超过定时器周期。
这也是为什么改变占空比应优先改比较器,而不是改生成器动作:动作定义的是波形规则,比较器定义的是当前边沿位置。前者更像拓扑,后者更像运行参数。
为何要推迟更新
==============
立即更新比较器可能改变当前周期中的边沿。:cpp:member:`update_cmp_on_tez <mcpwm_comparator_config_t::flags::update_cmp_on_tez>` 会等待计数归零,:cpp:member:`update_cmp_on_tep <mcpwm_comparator_config_t::flags::update_cmp_on_tep>` 会等待计数到达峰值,:cpp:member:`update_cmp_on_sync <mcpwm_comparator_config_t::flags::update_cmp_on_sync>` 则等待同步事件。在向上计数或向下计数模式下,峰值即周期边界,与归零点几乎重合,因此 tez 与 tep 对应同一个更新点;仅在向上-向下计数模式下峰值处于周期中点tez 与 tep 才是两个不同的更新点。电机和电源转换通常应使用推迟更新。
两个比较器实现脉冲放置
=======================
一个比较器每周期产生一个边沿。在同一操作器中使用两个比较器,可在周期内任意位置放置脉冲——一个比较器打开脉冲,另一个关闭。适用于采样窗口、触发信号或非对称死区补偿。
这类需求在电机控制里很常见,例如想把 ADC 采样窗口放在开关噪声较小的位置,或者给外部器件输出一个不位于 PWM 边界的同步脉冲。
.. code-block:: c
mcpwm_cmpr_handle_t cmp_a, cmp_b;
mcpwm_new_comparator(oper, &comparator_config, &cmp_a);
mcpwm_new_comparator(oper, &comparator_config, &cmp_b);
mcpwm_comparator_set_compare_value(cmp_a, 10);
mcpwm_comparator_set_compare_value(cmp_b, 30);
比较事件也可用于精确的软件时间标记
====================================
:cpp:member:`on_reach <mcpwm_comparator_event_callbacks_t::on_reach>` 回调在比较值到达时触发,适用于软件需要观察 PWM 周期中精确位置的场景。应在启动时间关键型工作前注册。回调在 ISR 上下文运行,应保持简短。
.. code-block:: c
static bool IRAM_ATTR on_compare(mcpwm_cmpr_handle_t cmpr,
const mcpwm_compare_event_data_t *edata,
void *user_ctx)
{
// 通知任务或仅执行 ISR 安全工作
return false;
}
mcpwm_comparator_event_callbacks_t callbacks = { .on_reach = on_compare };
ESP_ERROR_CHECK(mcpwm_comparator_register_event_callbacks(comparator,
&callbacks, NULL));
比较器的种类
============
**操作器比较器** 通过 :cpp:func:`mcpwm_new_comparator()` 创建,其比较事件驱动生成器,从而塑造 PWM 输出。
.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR
还存在第二种: **事件比较器**,通过 :cpp:func:`mcpwm_new_event_comparator()` 创建。其比较事件只能经 :doc:`ETM </api-reference/peripherals/etm>` 传给其他外设,不驱动生成器,也不会影响 PWM 输出。
.. note::
名字容易误导:操作器比较器同样能产生 ETM 事件,因此事件比较器并非 MCPWM 接入 ETM 的唯一途径。事件比较器的定位是 *补充* 操作器比较器,而非替代。事件比较器不占用操作器比较器名额,也不会移动任何 PWM 边沿,当你只需要一个额外比较点作为纯定时标记时——例如在不扰动 PWM 波形的前提下,为 ADC 触发找到采样窗口——它就是更灵活的选择。
两种比较器使用相同的比较值设置与 ETM 事件创建方式:
.. code-block:: c
mcpwm_event_comparator_config_t evt_cmp_cfg = {};
mcpwm_cmpr_handle_t evt_cmpr = NULL;
ESP_ERROR_CHECK(mcpwm_new_event_comparator(oper, &evt_cmp_cfg, &evt_cmpr));
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(evt_cmpr, 25));
esp_etm_event_handle_t evt = NULL;
ESP_ERROR_CHECK(mcpwm_comparator_new_etm_event(evt_cmpr,
&(mcpwm_cmpr_etm_event_config_t){ .event_type = MCPWM_CMPR_ETM_EVENT_EQUAL },
&evt));
API 参考
========
MCPWM 比较器驱动函数
--------------------
.. include-build-file:: inc/mcpwm_cmpr.inc

View File

@@ -0,0 +1,66 @@
=============================
MCPWM ETM外设间的硬件级联动
=============================
.. contents::
:local:
:depth: 2
.. only:: SOC_MCPWM_SUPPORT_ETM
事件任务矩阵ETM可将 MCPWM 定时器或比较器事件直接路由到 ETM 任务,避免 ISR 延迟。适用于其他外设必须在精确 PWM 相位响应的场景。
先从定时器或比较器创建 ETM 事件,再创建目标外设的兼容任务,最后通过 ETM 通道连接两者。目标外设驱动文档定义了其任务和完整通道配置。关于 ETM 的整体用法——分配通道、把事件连接到任务——请参阅 :doc:`ETM </api-reference/peripherals/etm>` 文档。
.. mermaid::
flowchart LR
T["MCPWM 定时器<br/>TEZ/TEP 事件"]:::source --> E["ETM 通道"]:::route
C["MCPWM 比较器<br/>比较事件"]:::source --> E
E --> D["目标<br/>外设任务"]:::dest
classDef source fill:#dbeafe,stroke:#2563eb,color:#172554
classDef route fill:#ede9fe,stroke:#7c3aed,color:#2e1065
classDef dest fill:#dcfce7,stroke:#16a34a,color:#14532d
定时器会产生 ``TEZ`` (定时器归零)或 ``TEP`` (定时器到达峰值)事件。要获取一个事件:
.. code-block:: c
esp_etm_event_handle_t timer_event = NULL;
ESP_ERROR_CHECK(mcpwm_timer_new_etm_event(timer,
&(mcpwm_timer_etm_event_config_t) {
.event_type = MCPWM_TIMER_ETM_EVENT_TEZ,
}, &timer_event));
// 创建目标 ETM task、分配 channel然后连接
// esp_etm_channel_connect(channel, timer_event, destination_task);
比较器提供 ``EQUAL`` 事件,每当定时器计数值等于比较器取值时触发。它能把事件固定到 PWM 周期中的任意相位,而不只是波峰或波谷。要获取一个事件:
.. code-block:: cpp
esp_etm_event_handle_t cmp_event = NULL;
ESP_ERROR_CHECK(mcpwm_comparator_new_etm_event(cmp,
&(mcpwm_cmpr_etm_event_config_t) {
.event_type = MCPWM_CMPR_ETM_EVENT_EQUAL,
}, &cmp_event));
// esp_etm_channel_connect(channel, cmp_event, destination_task);
使用完毕后调用 :cpp:func:`esp_etm_del_event()` 释放事件。
比较器事件一个非常常见的用途是触发 ADC 采样:把比较器设置在想要采样的相位点,再由比较器事件启动 ADC使转换器在与 PWM 周期精确同步的相位点采样稳定、干净的波形。由于整条链路完全由硬件完成,采样点能无 CPU 参与、无 ISR 延迟地跟随 PWM。
.. only:: SOC_MCPWM_SUPPORT_EVENT_COMPARATOR
用哪种比较器提供事件很关键。 **运算比较器** :cpp:func:`mcpwm_new_comparator()`)同时驱动生成器,其比较值会定义实际的 PWM 输出边沿,因此它的 ETM 事件被限制在对应的 PWM 边沿上——你无法要求它在不是自己产生的边沿相位上触发。 **事件比较器** :cpp:func:`mcpwm_new_event_comparator()`)则是专门的 ETM 定时标记:它不驱动任何生成器,也不占用运算比较器槽位,因此可以在 PWM 周期内的 *任意* 相位触发,且对 PWM 波形毫无影响。这种自由度正是 ADC 触发所需要的,所以更推荐用事件比较器作为事件源——把采样点放在电压已稳定的位置,而不只是恰好有边沿的地方。
API 参考
========
MCPWM ETM 驱动函数
------------------
.. include-build-file:: inc/mcpwm_etm.inc
.. only:: not SOC_MCPWM_SUPPORT_ETM
{IDF_TARGET_NAME} 不支持 MCPWM ETM 事件。

View File

@@ -0,0 +1,84 @@
==================================
MCPWM 故障:将保护信号接入 MCPWM
==================================
.. contents::
:local:
:depth: 2
故障对象表示异常条件。GPIO 故障适合过流比较器等硬件信号;软件故障可让应用逻辑触发同一保护路径。随后使用操作器 :ref:`制动 <mcpwm-brake>` 定义输出响应。
故障模块存在的目的,是让保护路径尽可能不依赖软件轮询或任务调度。对于电机驱动和功率变换器,过流、互锁或急停通常要求在硬件路径里先把输出拉到安全状态,再由软件决定如何记录和恢复。
创建低电平有效 GPIO 故障源
==========================
故障源必须和将使用它的操作器位于同一组。引脚上拉/下拉属于独立 GPIO 配置MCPWM 驱动不会为故障 GPIO 引脚自动使能内部上拉/下拉。如果故障信号在其他状态下并不驱动引脚电平,请自行用 :cpp:func:`gpio_set_pull_mode()` 配置上拉/下拉方向;启动功率级前应确保非故障电平明确。
.. code-block:: c
mcpwm_fault_handle_t fault = NULL;
mcpwm_gpio_fault_config_t fault_config = {
.group_id = 0,
.gpio_num = 4,
.flags.active_level = 0,
};
ESP_ERROR_CHECK(mcpwm_new_gpio_fault(&fault_config, &fault));
GPIO 故障配置中有几个字段值得了解:
.. list::
- :cpp:member:`group_id <mcpwm_gpio_fault_config_t::group_id>` — 故障所属的 MCPWM 组。必须与使用该故障的操作器所在组一致。
- :cpp:member:`gpio_num <mcpwm_gpio_fault_config_t::gpio_num>` — 承载故障信号的 GPIO。
- :cpp:member:`active_level <mcpwm_gpio_fault_config_t::flags::active_level>` — 何种电平视为有效。示例使用 ``0``,即故障为低有效;无故障时引脚上拉方向应使其保持非有效(高电平)。驱动不会改动引脚的上下拉配置,请按需调用 :cpp:func:`gpio_set_pull_mode()` 选择上拉或下拉。
- :cpp:member:`intr_priority <mcpwm_gpio_fault_config_t::intr_priority>` — 故障事件回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级。
创建软件故障
============
对于应用检测到的条件,可创建 :cpp:func:`mcpwm_new_soft_fault()`,并在条件发生时调用 :cpp:func:`mcpwm_soft_fault_activate()`,而无需接入 GPIO 故障引脚。激活是一次性的故障事件,输出策略仍由操作器 :ref:`制动机制 <mcpwm-brake>` 决定。
.. note::
激活前必须先用 :cpp:func:`mcpwm_operator_set_brake_on_fault()` 将软件故障绑定到某个操作器。驱动在 :cpp:func:`mcpwm_new_soft_fault()` 时并不会关联操作器;操作器关联及其制动模式都由该绑定调用设置,且一个软件故障只能绑定一个操作器。在绑定前调用 :cpp:func:`mcpwm_soft_fault_activate()` 属于未定义行为。
故障作为生成器动作的触发源
==========================
GPIO 故障也可通过 :cpp:func:`mcpwm_generator_set_action_on_fault_event()` 直接触发生成器动作。这是局部的边沿级响应——在故障边沿改变输出,但不会锁存安全状态。如需带恢复的持续制动,应使用操作器 :ref:`制动机制 <mcpwm-brake>`
.. list-table::
:header-rows: 1
:widths: 18 34 28
* - 机制
- 行为
- 适合场景
* - 生成器故障动作
- 单个输出在故障边沿立即改变
- 某一路输出的局部快速响应
* - 操作器制动
- 为整个输出级定义安全状态、是否锁存和恢复策略
- 主保护路径、半桥/全桥安全关断
故障事件回调
============
:cpp:member:`on_fault_enter <mcpwm_fault_event_callbacks_t::on_fault_enter>`:cpp:member:`on_fault_exit <mcpwm_fault_event_callbacks_t::on_fault_exit>` 回调报告 GPIO 故障跳变,且仅 GPIO 故障可用——驱动会拒绝在软件故障上注册这些回调。软件故障会在硬件中立即触发制动,不会产生回调。这些回调在 ISR 上下文执行。应只进行时间戳记录或使用 ISR 安全的方式通知任务,在任务中做日志和恢复决策。
.. code-block:: c
mcpwm_fault_event_callbacks_t cbs = {
.on_fault_enter = my_fault_enter_cb,
.on_fault_exit = my_fault_exit_cb,
};
ESP_ERROR_CHECK(mcpwm_fault_register_event_callbacks(fault, &cbs, NULL));
API 参考
========
MCPWM 故障驱动函数
------------------
.. include-build-file:: inc/mcpwm_fault.inc

View File

@@ -0,0 +1,327 @@
============================
MCPWM 生成器:生成 PWM 波形
============================
.. contents::
:local:
:depth: 2
生成器是最终的数字输出。它没有固定的"占空比"属性,而是配置在定时器和比较器事件发生时输出何种电平,因此可实现简单、非对称、互补和相位相关的波形。
这也是 MCPWM 与简单 PWM 外设的重要区别之一:它不是只让你填一个频率和占空比,而是让你直接描述"在什么时刻做什么动作"。代价是初学时概念更多,但换来的是对波形结构更高的控制力。
生成第一路 PWM
===============
这是对 :doc:`定时器 <mcpwm_timer>`:doc:`操作器 <mcpwm_operator>`:doc:`比较器 <mcpwm_cmpr>` 配置的收尾:定时器归零时输出高电平,比较器到达 15 时输出低电平。对于 50 Tick 的周期,输出高电平 15 Tick即 30% 占空比。
应用场景:基础单路输出
----------------------
适用于最基础的单路 PWM 输出,例如 RC 舵机控制信号、LED 调光,或后级电路已处理好极性和保护关系的简单占空比输出。
.. code-block:: c
mcpwm_gen_handle_t generator = NULL;
mcpwm_generator_config_t gen_config = { .gen_gpio_num = 18 };
ESP_ERROR_CHECK(mcpwm_new_generator(oper, &gen_config, &generator));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
generator, MCPWM_GEN_TIMER_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_EMPTY,
MCPWM_GEN_ACTION_HIGH)));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_LOW)));
.. figure:: /../_static/mcpwm/single_edge_asym_active_high.svg
:align: center
:alt: 向上计数、有效高 PWM归零时置高比较点置低。
向上计数、有效高 PWM归零时置高比较点置低。
生成器配置很简单:
.. list::
- :cpp:member:`gen_gpio_num <mcpwm_generator_config_t::gen_gpio_num>` — 输出 PWM 的 GPIO。同一操作器中另一个生成器若配置相同的动作即可从同一时间基准驱动第二个引脚。
- :cpp:member:`invert_pwm <mcpwm_generator_config_t::flags::invert_pwm>` — 通过 GPIO 矩阵反相 PWM 信号。这是对最终输出的硬件反相,与修改动作不同;两者选其一,不要同时使用。
动作配置
========
配置名称直接描述了行为:在某个事件发生时执行 :cpp:enumerator:`MCPWM_GEN_ACTION_HIGH <mcpwm_generator_action_t::MCPWM_GEN_ACTION_HIGH>`:cpp:enumerator:`MCPWM_GEN_ACTION_LOW <mcpwm_generator_action_t::MCPWM_GEN_ACTION_LOW>`:cpp:enumerator:`MCPWM_GEN_ACTION_TOGGLE <mcpwm_generator_action_t::MCPWM_GEN_ACTION_TOGGLE>`。辅助宏在调用处清晰展示三个关键选择——方向、事件源和输出电平。
对于向上计数定时器,:cpp:enumerator:`MCPWM_TIMER_EVENT_EMPTY <mcpwm_timer_event_t::MCPWM_TIMER_EVENT_EMPTY>` 是归零边界,:cpp:enumerator:`MCPWM_TIMER_EVENT_FULL <mcpwm_timer_event_t::MCPWM_TIMER_EVENT_FULL>` 在定时器到达峰值时触发。向上计数模式下峰值即周期,因此 ``FULL`` 落在周期边界;向上-向下模式下峰值是 ``period_ticks / 2``,因此 ``FULL`` 落在周期中间。比较动作使用比较器阈值。因此,第一个例子的含义是"周期开始时置高,计数到 15 时结束有效部分"。超出定时器范围的比较值不会产生比较事件。
每个动作都必须指定定时器方向,尽管这一选择只在向上-向下模式下才有可见差异。向上计数模式下计数器只向上走,因此为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP <mcpwm_timer_direction_t::MCPWM_TIMER_DIRECTION_UP>` 配置的动作才会触发——仍需显式写出。在向上-向下模式中,两个边沿都重要时,应同时为 :cpp:enumerator:`MCPWM_TIMER_DIRECTION_UP <mcpwm_timer_direction_t::MCPWM_TIMER_DIRECTION_UP>`:cpp:enumerator:`MCPWM_TIMER_DIRECTION_DOWN <mcpwm_timer_direction_t::MCPWM_TIMER_DIRECTION_DOWN>` 配置动作;这样便能用一个比较器生成中心对齐 PWM。
经典波形案例
============
下面的示例都基于上一节的第一路 PWM复用相同的定时器、操作器和比较器对象生成其他常用波形。
反转有效极性
------------
某些栅极驱动器和 LED 为低有效,例如低有效驱动器使能、反相 LED 接法,或板级电路已固定反相的接口。与其增加 GPIO 反相,不如在周期边界置低、比较点置高:
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_timer_event(
generator, MCPWM_GEN_TIMER_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, MCPWM_TIMER_EVENT_FULL, MCPWM_GEN_ACTION_LOW)));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_HIGH)));
.. figure:: /../_static/mcpwm/single_edge_asym_active_low.svg
:align: center
:alt: 向上计数、有效低 PWM。当输出逻辑极性是设计的一部分时修改动作而非接线。
向上计数、有效低 PWM。当输出逻辑极性是设计的一部分时修改动作而非接线。
在周期内放置脉冲
----------------
当需要把某个短脉冲精确放到周期内部——例如 ADC 采样窗口、外设触发脉冲或锁存控制信号——两个比较值分别决定脉冲的开始和结束边沿:
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, comparator_a, MCPWM_GEN_ACTION_HIGH)));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, comparator_b, MCPWM_GEN_ACTION_LOW)));
.. figure:: /../_static/mcpwm/pulse_placement_asym.svg
:align: center
:alt: 脉冲放置:两个比较值之差就是脉冲宽度。
脉冲放置:两个比较值之差就是脉冲宽度。
应使 ``comparator_a`` 小于 ``comparator_b``。同时以相同 Tick 数移动二者可改变位置而不改变宽度;只移动其中一个则改变宽度。硬件会放置两个边沿,因此比定时器回调更精确。
双沿非对称 PWM
--------------
当需要在一个周期内独立布置多个边沿、且高电平区间不必围绕周期中心对称时——例如某些非对称逆变器调制或定制驱动时序——使用两个生成器和两个比较器。每个生成器在每周期有自己的边沿,使高电平时间分布在周期边界两侧:
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
gen_a, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, cmp_a, MCPWM_GEN_ACTION_HIGH)));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
gen_a, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, cmp_b, MCPWM_GEN_ACTION_LOW)));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
gen_b, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, cmp_a, MCPWM_GEN_ACTION_LOW)));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
gen_b, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, cmp_b, MCPWM_GEN_ACTION_HIGH)));
.. figure:: /../_static/mcpwm/dual_edge_asym_active_low.svg
:align: center
:alt: 双沿非对称边沿对齐PWM两个生成器产生互补输出每周期两个边沿。
双沿非对称边沿对齐PWM两个生成器产生互补输出每周期两个边沿。
中心对齐 PWM
------------
电机控制、逆变器等关注谐波表现的功率级通常更偏好中心对齐 PWM因为它能带来更对称的开关时序和更低的谐波失真。创建定时器时选择 ``MCPWM_TIMER_COUNT_MODE_UP_DOWN``,随后在两个方向使用同一阈值:
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, comparator, MCPWM_GEN_ACTION_HIGH)));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_compare_event(
generator, MCPWM_GEN_COMPARE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_DOWN, comparator, MCPWM_GEN_ACTION_LOW)));
.. figure:: /../_static/mcpwm/dual_edge_sym_active_low.svg
:align: center
:alt: 中心对齐 PWM向上和向下计数动作在周期中心两侧形成对称边沿。
中心对齐 PWM向上和向下计数动作在周期中心两侧形成对称边沿。
一次完整的向上-向下周期会到达峰值后回到零点,计算频率时需计入两个计数阶段。为第二个生成器配置相反动作可形成逻辑互补输出:
.. figure:: /../_static/mcpwm/dual_edge_sym_complementary.svg
:align: center
:alt: 仅靠互补生成器动作不含死区,不能直接连接到功率级。
仅靠互补生成器动作不含死区,不能直接连接到功率级。
.. warning::
逻辑互补输出不等于可直接驱动半桥的安全互补输出。只要上下管存在关断延迟,就必须继续配置死区,并在实际栅极处验证无重叠导通。
占空比调整
==========
改变占空比时修改比较器阈值,而不是生成器动作。对于向上计数的有效高波形,``compare_value / period_ticks`` 即为占空比。应选择足够高的定时器分辨率,使一个 Tick 满足应用所需的调节精度。
.. code-block:: c
mcpwm_comparator_set_compare_value(comparator, 25); // 50 %
强制电平
========
调节比较器阈值改变的是正常占空比。若需要临时接管输出、忽略所有事件动作而直接固定某个电平时,可改用强制电平接口。:func:`mcpwm_generator_set_force_level` 有两个关键参数:``level````hold_on``
- ``level``(第二个参数)指定要强制输出的原始生成器电平:``0````1`` 会覆盖所有事件动作,``-1`` 则解除强制并恢复事件控制。
- ``hold_on``(第三个参数)决定强制电平持续多久:``true`` 会一直保持直到下一次调用解除;``false`` 则允许下一个事件动作将其覆盖。
例如 ``mcpwm_generator_set_force_level(generator, 0, true)`` 会覆盖所有事件动作并保持原始生成器为低电平。强制电平位于死区与 GPIO 反相之前,应使用示波器确认实际引脚电平。
强制电平常用于上电自检、故障后的临时安全输出、切换工作模式时的过渡状态,而不是长期替代正常 PWM 配置。
对于半桥,添加第二个生成器并配置 :ref:`死区模块 <mcpwm-dead-time>` 以产生非重叠互补输出。
.. _mcpwm-dead-time:
死区与半桥驱动
==============
死区会延迟输出边沿,在半桥两个开关之间留出同时关闭的短暂间隔,以补偿晶体管关断延迟并降低直通风险。连接功率级前应配置并验证死区。
半桥通过高边与低边两个开关将直流母线连接到负载。两个开关通常都采用 N 沟道 MOSFET低边源极接 GND、易于驱动而高边源极随输出摆动其栅极需要高于母线电压的驱动电压。MCPWM 输出是 3.3 V 逻辑信号,不能直接驱动栅极。以 IRS2101 为例,它使用独立的低压驱动电源 VCC通常为 10-20 VCOM 接功率地。自举二极管应从 VCC 接到 VB外部自举电容接在 VB 与 VS 之间VS 必须连接到 OUT 半桥开关节点。高边输出为 HO低边输出为 LO两者都应通过栅极电阻连接到对应 MOSFET 的栅极。VCC 不是高压直流母线:低边开关导通时,自举二极管从受调节的驱动电源为自举电容充电。若两个开关同时开通和关断,仍在关断的开关会与已经开通的开关重叠导通,使母线经两个开关直通到地。死区留出两者同时关断的间隔,让下一个开关在前一个完全关断之后再开通:
.. figure:: /../_static/mcpwm/half_bridge_dead_time.svg
:align: center
:alt: 带自举电源和死区对比的半桥栅极驱动电路。
带自举电源和死区对比的半桥栅极驱动电路。
创建互补输出
------------
在同一操作器中创建两个生成器。将生成器 A 的上升沿延迟后输出到自身,再将其下降沿延迟并反相后输出到生成器 B。
.. note::
这里约定,生成器 A 是通过操作器句柄申请的第一个生成器,生成器 B 是通过操作器句柄申请的第二个生成器。
.. code-block:: c
mcpwm_dead_time_config_t dead_time = { .posedge_delay_ticks = 2 };
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_a, &dead_time));
dead_time = (mcpwm_dead_time_config_t) {
.negedge_delay_ticks = 2,
.flags.invert_output = true,
};
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_b, &dead_time));
.. figure:: /../_static/mcpwm/deadtime_active_high_complementary.svg
:align: center
:alt: 带死区间隔的互补 PWM。
带死区间隔的互补 PWM。
理解路由与参数
--------------
:func:`mcpwm_generator_set_dead_time(in_generator, out_generator, config) <mcpwm_generator_set_dead_time>` 将死区视为一个小型信号处理级。两个生成器句柄相同会原地改变该输出;将 ``gen_a`` 作为输入、 ``gen_b`` 作为输出则从 A 派生 B这正是互补例子共享一个 PWM 源的方式。
:cpp:member:`posedge_delay_ticks <mcpwm_dead_time_config_t::posedge_delay_ticks>` 延迟上升沿,:cpp:member:`negedge_delay_ticks <mcpwm_dead_time_config_t::negedge_delay_ticks>` 延迟下降沿,单位为连接定时器的 Tick。例如分辨率为 10 MHz 时2 Tick 等于 200 ns。下图展示了基本效果``pwm_A`` 的上升沿被延迟RED``pwm_B`` 的下降沿被延迟FED。应先采用开关管和栅极驱动器数据手册中的最大关断延迟并留出余量在晶体管栅极实测后确认工艺、温度和布局仍有足够余量才可减小该值。两个延迟均设为零可旁路死区模块。:cpp:member:`invert_output <mcpwm_dead_time_config_t::flags::invert_output>` 在该级之后改变极性。
.. figure:: /../_static/mcpwm/deadtime_active_high.svg
:align: center
:alt: 基本死区效果上升沿延迟RED和下降沿延迟FED
基本死区效果上升沿延迟RED和下降沿延迟FED
每个操作器的资源限制
--------------------
每个操作器仅有一份上升沿延迟和一份下降沿延迟资源,不能将同一类延迟分别分配给两个生成器。下面的配置两次请求同一上升沿资源,因此无效:
.. code-block:: c
mcpwm_dead_time_config_t rise_delay = { .posedge_delay_ticks = 10 };
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_a, gen_a, &rise_delay));
// 无法再分配第二份独立的上升沿延迟。
ESP_ERROR_CHECK(mcpwm_generator_set_dead_time(gen_b, gen_b, &rise_delay));
可以将上升沿延迟分配给 A、下降沿延迟分配给 B也可以让 A 旁路模块,而让 B 使用两种延迟资源。如果第一个生成器同时使用两种延迟资源,另一个生成器便不能使用死区。
更多输出模式
------------
上面的互补配置是半桥的常用起点。交换输出反相设置可使两路输出均为低有效,同时保留非重叠时间:
.. figure:: /../_static/mcpwm/deadtime_active_low_complementary.svg
:align: center
:alt: 低有效互补输出。时序资源相同,仅改变死区后的输出极性。
低有效互补输出。时序资源相同,仅改变死区后的输出极性。
仅有一个通道需要边沿延迟时,死区同样有用。向其中一个输出传入零延迟配置使其旁路,再将可用延迟应用到另一个输出:
.. figure:: /../_static/mcpwm/deadtime_reda_bypassb.svg
:align: center
:alt: 延迟 A 的上升沿B 旁路死区。这不是互补半桥,而是独立的边沿放置工具。
延迟 A 的上升沿B 旁路死区。这不是互补半桥,而是独立的边沿放置工具。
.. figure:: /../_static/mcpwm/deadtime_redb_fedb_bypassa.svg
:align: center
:alt: 旁路 A延迟 B 的两个边沿,并占用两种延迟资源。
旁路 A延迟 B 的两个边沿,并占用两种延迟资源。
单个边沿延迟也可独立应用。下图展示了仅对 B 的下降沿延迟A 被旁路,仅使用 FED 资源:
.. figure:: /../_static/mcpwm/deadtime_fedb_bypassa.svg
:align: center
:alt: 仅对 B 应用下降沿延迟A 保持不变。使用一个延迟资源。
仅对 B 应用下降沿延迟A 保持不变。使用一个延迟资源。
输出反相时,死区行为随之变化。下图展示了基本延迟的低有效版本,反相标志翻转了两个输出的极性:
.. figure:: /../_static/mcpwm/deadtime_active_low.svg
:align: center
:alt: 低有效死区:相同的延迟资源,但输出极性在延迟级后被反相。
低有效死区:相同的延迟资源,但输出极性在延迟级后被反相。
.. note::
若波形的每个边沿都需要独立调整位置,应使用两个比较器和生成器动作。需求明确是"延迟边沿并控制极性"时,死区模块更合适。
在安全边界更新
--------------
若要让改变仅在安全边界生效,请设置操作器的 :cpp:member:`update_dead_time_on_tez <mcpwm_operator_config_t::flags::update_dead_time_on_tez>`:cpp:member:`update_dead_time_on_tep <mcpwm_operator_config_t::flags::update_dead_time_on_tep>`:cpp:member:`update_dead_time_on_sync <mcpwm_operator_config_t::flags::update_dead_time_on_sync>`
.. note::
应探测两个实际栅极引脚GPIO 反相、载波调制和栅极驱动器极性都可能改变晶体管端看到的信号。当多个环节都会反转信号时,两次反相可能相互抵消,软件里看着正确,硬件却并非预期行为,务必以真实波形为准进行验证。
其他事件源
============
生成器也可直接响应 GPIO 故障和同步事件:
.. list::
- :cpp:func:`mcpwm_generator_set_action_on_fault_event()` — 立即硬件响应 GPIO 故障。会占用有限的操作器触发槽。
- :cpp:func:`mcpwm_generator_set_action_on_sync_event()` — 在同步边沿转换。每个生成器只有一个同步动作槽。
- :cpp:func:`mcpwm_generator_set_action_on_brake_event()` — 操作器 :ref:`制动 <mcpwm-brake>` 期间各生成器的输出状态。需要按制动模式和定时器方向分别设置,完整示例见 :ref:`故障连接 <mcpwm-brake-fault-connection>`
对于安全策略和持续制动,应使用操作器 :ref:`制动机制 <mcpwm-brake>`。生成器故障动作适合局部的边沿级响应;制动则为整个输出级定义安全状态和恢复行为。
API 参考
========
MCPWM 生成器驱动函数
--------------------
.. include-build-file:: inc/mcpwm_gen.inc

View File

@@ -0,0 +1,187 @@
=========================
MCPWM 操作器:组装输出级
=========================
.. contents::
:local:
:depth: 2
操作器位于定时器与生成器之间,管理比较器、生成器动作、制动、死区路由和载波调制。一个定时器可驱动同组的多个操作器,而一个操作器只连接一个定时器。
如果把定时器看成时钟源,操作器就是一套输出级的资源容器。它让多个输出共享同一时间基准,同时把保护、死区和载波这类功率级特性收拢到一个地方管理。
连接基本模块
============
操作器必须与定时器在同一组。先通过 :cpp:func:`mcpwm_operator_connect_timer()` 创建并连接操作器;连接前,生成器不能使用定时器事件。
.. code-block:: c
mcpwm_oper_handle_t oper = NULL;
mcpwm_operator_config_t oper_config = {
.group_id = 0,
.flags.update_gen_action_on_tez = true,
.flags.update_dead_time_on_tez = true,
};
ESP_ERROR_CHECK(mcpwm_new_operator(&oper_config, &oper));
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper, timer));
操作器配置很小,但有两个字段没有出现在示例中:
.. list::
- :cpp:member:`group_id <mcpwm_operator_config_t::group_id>` — 操作器从哪个 MCPWM 组分配。它必须与定时器所在组一致,因为操作器只能连接同组内的定时器。
- :cpp:member:`intr_priority <mcpwm_operator_config_t::intr_priority>` — 制动事件回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级;当制动通知需要抢占其他中断时可适当提高。
``flags`` 用于选择新的生成器动作和死区配置在何时生效。默认全部关闭,改动会立即生效——可能在 PWM 周期中间:
.. list::
- :cpp:member:`update_gen_action_on_tez <mcpwm_operator_config_t::flags::update_gen_action_on_tez>`:cpp:member:`update_gen_action_on_tep <mcpwm_operator_config_t::flags::update_gen_action_on_tep>`:cpp:member:`update_gen_action_on_sync <mcpwm_operator_config_t::flags::update_gen_action_on_sync>` — 将生成器动作的改动缓冲到计数归零、峰值或同步事件时。
- :cpp:member:`update_dead_time_on_tez <mcpwm_operator_config_t::flags::update_dead_time_on_tez>`:cpp:member:`update_dead_time_on_tep <mcpwm_operator_config_t::flags::update_dead_time_on_tep>`:cpp:member:`update_dead_time_on_sync <mcpwm_operator_config_t::flags::update_dead_time_on_sync>` — 死区改动同样缓冲,更新点规则见 :ref:`死区 <mcpwm-dead-time>`
对于运行中的功率级,应使用归零(``tez``)、峰值(``tep``)或同步点更新,以避免部分周期。
一个定时器,多个操作器
======================
同一定时器可驱动多个操作器,每个操作器产生不同的波形。这适用于多相逆变器或多个同频但独立占空比的电机。
反过来,一个操作器只能连接一个定时器,因此该操作器下的所有比较器和生成器天然共享同一时间基准。这正是同相、互补或成对输出容易实现的原因。
.. code-block:: c
mcpwm_oper_handle_t oper_b = NULL;
mcpwm_operator_config_t oper_config_b = {
.group_id = 0,
.flags.update_gen_action_on_tez = true,
};
ESP_ERROR_CHECK(mcpwm_new_operator(&oper_config_b, &oper_b));
ESP_ERROR_CHECK(mcpwm_operator_connect_timer(oper_b, timer));
// 在 oper_b 下创建独立的比较器和生成器。
.. _mcpwm-brake:
制动与安全输出
==============
操作器将 :doc:`故障 <mcpwm_fault>` 转换为制动动作。应在启动 PWM 前配置生成器的制动状态,使故障路径完全由硬件驱动,避免软件延迟。
恢复策略
--------
.. list::
- **CBC逐周期** 故障有效期间制动,并在配置的定时器归零或峰值处恢复,适合瞬态限流。
- **OST单次** 故障消失后仍保持制动,必须由软件显式恢复,适合互锁或严重过流。
对于 CBC设置 :cpp:member:`cbc_recover_on_tez <mcpwm_brake_config_t::flags::cbc_recover_on_tez>` 或 :cpp:member:`cbc_recover_on_tep <mcpwm_brake_config_t::flags::cbc_recover_on_tep>`,选择已清除故障后释放输出的边界。在边界恢复可避免在 PWM 周期中间重新导通开关。
.. warning::
不要同时启用 ``cbc_recover_on_tez````cbc_recover_on_tep``;应选择与波形和栅极驱动时序相匹配的边界。
.. _mcpwm-brake-fault-connection:
故障连接
--------
将故障连接到操作器,然后为每个生成器指定制动模式下的状态。此例在两个计数方向将原始生成器置低,使用 OST 制动:
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_operator_set_brake_on_fault(oper,
&(mcpwm_brake_config_t) {
.fault = fault,
.brake_mode = MCPWM_OPER_BRAKE_MODE_OST,
}));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_brake_event(
generator, MCPWM_GEN_BRAKE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_UP, MCPWM_OPER_BRAKE_MODE_OST,
MCPWM_GEN_ACTION_LOW)));
ESP_ERROR_CHECK(mcpwm_generator_set_action_on_brake_event(
generator, MCPWM_GEN_BRAKE_EVENT_ACTION(
MCPWM_TIMER_DIRECTION_DOWN, MCPWM_OPER_BRAKE_MODE_OST,
MCPWM_GEN_ACTION_LOW)));
半桥必须为两个生成器都配置相同的制动动作。逻辑低电平可能被死区、GPIO 矩阵或外部电路反相,因此应在栅极驱动器处确认实际安全状态。
生成器级故障动作与操作器制动的区别在于:前者适合单个输出的即时边沿响应,后者负责整个输出级的安全状态、锁存行为和恢复策略。主保护路径通常应优先使用操作器制动。
OST 故障恢复
------------
CBC 在故障消失后在配置的边界自动恢复。OST 恢复前,先移除并确认故障根因已经消失,然后调用:
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_operator_recover_from_fault(oper, fault));
故障仍有效时该调用会失败。
.. figure:: /../_static/mcpwm/brake_cbc_ost.svg
:align: center
:alt: PWM 运行期间故障被触发。CBC 在故障有效期间将输出保持在制动电平并在下一个周期边界恢复OST 则保持锁存直至软件恢复。
CBC 仅在故障有效期间制动并在下一个周期边界恢复OST 保持锁存直至软件恢复。
制动事件回调
------------
操作器可通过 :cpp:member:`on_brake_cbc <mcpwm_operator_event_callbacks_t::on_brake_cbc>` 和 :cpp:member:`on_brake_ost <mcpwm_operator_event_callbacks_t::on_brake_ost>` 回调报告制动事件。二者均在 ISR 上下文运行,只应用于通知,不应用于阻塞式恢复。
.. code-block:: c
mcpwm_operator_event_callbacks_t cbs = {
.on_brake_cbc = my_brake_cbc_cb,
.on_brake_ost = my_brake_ost_cb,
};
ESP_ERROR_CHECK(mcpwm_operator_register_event_callbacks(oper, &cbs, NULL));
.. _mcpwm-carrier:
载波调制
========
载波调制在操作器的 PWM 输出上叠加高频载波,常用于变压器隔离式栅极驱动:即使基础 PWM 保持 100% 占空比,调制后仍有可穿过隔离栅的跳变。应先完成基础 PWM 的配置;载波设置影响此操作器的所有生成器。
载波配置
--------
.. code-block:: c
mcpwm_carrier_config_t carrier = {
.clk_src = MCPWM_CARRIER_CLK_SRC_DEFAULT,
.frequency_hz = 100000,
.duty_cycle = 0.5f,
.first_pulse_duration_us = 20,
};
ESP_ERROR_CHECK(mcpwm_operator_apply_carrier(oper, &carrier));
.. figure:: /../_static/mcpwm/carrier_modulation.svg
:align: center
:alt: 50% 占空比基础 PWM 的载波调制
100 kHz 载波与 50% 占空比基础 PWM 做与操作。首个脉冲被拉伸到 20 us两个载波周期基础 PWM 为低电平时不做斩波。
载波参数
--------
.. list::
- :cpp:member:`clk_src <mcpwm_carrier_config_t::clk_src>` 选择载波时钟源。默认指向一个内部 PLL 时钟(如 PLL_F160M部分芯片还提供 RC_FAST 或 XTAL 作为备选。不同时钟源的分辨率与功耗不同默认时钟源即可满足大多数应用仅在需要避开某个时钟引入的噪声、PLL 精度不够,或对功耗有要求时,才需要手动指定。
- :cpp:member:`frequency_hz <mcpwm_carrier_config_t::frequency_hz>` 是载波频率;应选择同时满足隔离变压器、栅极驱动器、开关损耗预算和目标芯片时钟分辨率的值。
- :cpp:member:`duty_cycle <mcpwm_carrier_config_t::duty_cycle>` 只能取硬件支持的 0.125、0.25、0.375、0.5、0.625、0.75 或 0.875,而非任意比例。
- :cpp:member:`first_pulse_duration_us <mcpwm_carrier_config_t::first_pulse_duration_us>` 控制调制开始后的首个脉冲。它必须非零,且至少为一个载波周期。较长的首脉冲可帮助在感性隔离路径中建立电流,但必须保持在栅极驱动系统允许范围内。
- :cpp:member:`invert_before_modulate <mcpwm_carrier_config_t::flags::invert_before_modulate>` 改变原始 PWM 极性,:cpp:member:`invert_after_modulate <mcpwm_carrier_config_t::flags::invert_after_modulate>` 改变调制后极性。
不需要载波调制时,向 :cpp:func:`mcpwm_operator_apply_carrier` 传入 ``NULL`` 配置即可禁用。
API 参考
========
MCPWM 操作器驱动函数
--------------------
.. include-build-file:: inc/mcpwm_oper.inc

View File

@@ -0,0 +1,157 @@
===========================
MCPWM 同步:对齐 PWM 相位
===========================
.. contents::
:local:
:depth: 2
为什么需要同步
==============
每个 MCPWM 定时器是独立的硬件计数器。当你调用 :cpp:func:`mcpwm_timer_start()` 启动两个定时器时CPU 会依次发出两条写指令——第二个定时器比第一个晚几十个 CPU 周期才启动。即使两者的周期配置完全相同它们的计数器在周期中的位置也是不同的PWM 输出的相位关系无法预测。
同步通过在同步边沿到达时给**正在运行**的定时器加载指定的计数值和方向来解决这个问题。定时器必须已经在运行;同步不会启动或停止定时器。它是在运行时修正相位的一种机制。
如果同步边沿在每个周期都到达(例如来自 TEZ 处的定时器同步源),那么修正每周期重复一次,相位关系可以无限期保持。这就是典型的用法:一个定时器作为参考,其他定时器在每个周期都重新对齐到它。
MCPWM 提供三种同步源。所有源都产生 :cpp:type:`mcpwm_sync_handle_t` 类型的句柄,且任何源都可以供给同组内的任意定时器。
GPIO 同步源
============
GPIO 同步源对外部引脚上的边沿做出反应——当外部控制器、传感器或编码器提供周期性参考信号时非常有用。
.. code-block:: c
mcpwm_sync_handle_t sync = NULL;
ESP_ERROR_CHECK(mcpwm_new_gpio_sync_src(
&(mcpwm_gpio_sync_src_config_t) {
.group_id = 0,
.gpio_num = 5,
.flags.active_neg = false,
}, &sync));
GPIO 同步源配置很简单:
.. list::
- :cpp:member:`group_id <mcpwm_gpio_sync_src_config_t::group_id>` — 同步源所属的 MCPWM 组。必须与所有接收该同步的定时器所在组一致。
- :cpp:member:`gpio_num <mcpwm_gpio_sync_src_config_t::gpio_num>` — 承载同步信号的 GPIO。
- :cpp:member:`active_neg <mcpwm_gpio_sync_src_config_t::flags::active_neg>` — 默认上升沿为有效边沿;设置后改为下降沿有效。
软件同步源
==========
软件同步源由应用代码按需产生同步边沿。它没有配置字段;创建后即可在需要时激活。
.. code-block:: c
mcpwm_sync_handle_t soft_sync = NULL;
ESP_ERROR_CHECK(mcpwm_new_soft_sync_src(NULL, &soft_sync));
// 后续当应用决定同步时:
ESP_ERROR_CHECK(mcpwm_soft_sync_activate(soft_sync));
.. note::
必须先通过 :cpp:func:`mcpwm_timer_set_phase_on_sync()`:cpp:func:`mcpwm_capture_timer_set_phase_on_sync()` 将软件同步源绑定到定时器,再调用激活。驱动在创建时不会分配定时器;在绑定前调用 :cpp:func:`mcpwm_soft_sync_activate()` 属于未定义行为。
这在定时器已经在运行、应用需要触发一次性的相位修正时有用——例如故障恢复后或开始新的控制周期之前。由于软件同步是一次性的如果后续没有更多同步边沿到来相位关系会随时间漂移。如需持续锁相应使用周期性源GPIO 或定时器同步源)。
定时器同步源
============
定时器同步源在定时器到达指定事件时产生同步边沿——例如每次定时器计到零TEZ。这可以让一个定时器作为其他定时器的周期性参考每周期都保持相位锁定。
.. code-block:: c
mcpwm_sync_handle_t timer_sync = NULL;
ESP_ERROR_CHECK(mcpwm_new_timer_sync_src(
timer_a,
&(mcpwm_timer_sync_src_config_t) {
.timer_event = MCPWM_TIMER_EVENT_EMPTY,
},
&timer_sync));
.. list::
- :cpp:member:`timer_event <mcpwm_timer_sync_src_config_t::timer_event>` — 触发同步输出的定时器事件。常用 :cpp:enumerator:`MCPWM_TIMER_EVENT_EMPTY` (零)表示每个周期开始,或 :cpp:enumerator:`MCPWM_TIMER_EVENT_PEAK` 表示峰值位置。在向上计数模式中峰值就是周期边界;在向上-向下计数模式中峰值是周期的中点。
- :cpp:member:`propagate_input_sync <mcpwm_timer_sync_src_config_t::flags::propagate_input_sync>` — 设置后,该定时器会将其接收到的输入同步转发到其输出,无需额外 GPIO 接线即可实现定时器同步链。此模式下硬件选择输入同步作为输出源,因此 :cpp:member:`timer_event` 字段会被忽略。
每个定时器最多只能创建一个同步源。多个定时器可以接收同一个同步源。
由于定时器同步源每周期都会触发,接收定时器在每个周期都会得到修正。这是维持多路 PWM 通道间稳定相位关系最常用的方式。
设置接收相位
============
无论选择哪种同步源,接收定时器都使用相同的 API。调用 :cpp:func:`mcpwm_timer_set_phase_on_sync()` 配置同步边沿到达时的行为。定时器必须已经在运行,同步才会生效。
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timer,
&(mcpwm_timer_sync_phase_config_t) {
.sync_src = sync,
.count_value = 25,
.direction = MCPWM_TIMER_DIRECTION_UP,
}));
.. list::
- :cpp:member:`sync_src <mcpwm_timer_sync_phase_config_t::sync_src>` — 源对象。设为 ``NULL`` 可取消同步。
- :cpp:member:`count_value <mcpwm_timer_sync_phase_config_t::count_value>` — 同步事件到达时加载的计数值。应保持在定时器周期范围内。
- :cpp:member:`direction <mcpwm_timer_sync_phase_config_t::direction>` — 加载后的计数方向。
两路 90 度移相
==============
现在你已经了解了三种同步源以及如何设置接收相位,下面是一个完整示例。它使用定时器同步源:``timer_a`` 每次到达零时发出同步,``timer_b`` 收到后加载 ``count_value = 25``,产生 90 度相位滞后。由于同步每周期重复一次,两路输出的相位关系可以无限期保持。
.. code-block:: c
mcpwm_timer_handle_t timer_a = NULL;
mcpwm_timer_handle_t timer_b = NULL;
mcpwm_sync_handle_t timer_a_sync = NULL;
// timer_a 和 timer_b 均已创建period_ticks = 100
ESP_ERROR_CHECK(mcpwm_new_timer_sync_src(
timer_a,
&(mcpwm_timer_sync_src_config_t) {
.timer_event = MCPWM_TIMER_EVENT_EMPTY,
},
&timer_a_sync));
ESP_ERROR_CHECK(mcpwm_timer_set_phase_on_sync(timer_b,
&(mcpwm_timer_sync_phase_config_t) {
.sync_src = timer_a_sync,
.count_value = 25,
.direction = MCPWM_TIMER_DIRECTION_UP,
}));
// timer_a 在 TEZ 输出同步timer_b 收到后从 Tick 25 开始继续计数。
理解领先与滞后
--------------
下面的示意中,``PWM_A`` 先开始一个周期,``PWM_B`` 在其后四分之一周期出现,因此 ``PWM_B`` 滞后 ``PWM_A`` 90 度;反过来说,``PWM_A`` 领先 ``PWM_B`` 90 度。
.. figure:: /../_static/mcpwm/phase_shift.svg
:align: center
:alt: PWM 相移 90 度滞后
PWM_A 与 PWM_B 之间的 90 度相移PWM_B 在 PWM_A 之后 25 个 tick 处开始上升。
其他注意事项
============
捕获定时器也可通过 :cpp:func:`mcpwm_capture_timer_set_phase_on_sync()` 使用同一同步源,捕获始终向上计数。接收端和源必须保留在同一组中。删除源之前,应先取消同步或删除所有使用它的对象。
API 参考
========
MCPWM 同步驱动函数
------------------
.. include-build-file:: inc/mcpwm_sync.inc

View File

@@ -0,0 +1,146 @@
=======================
MCPWM 定时器:设定频率
=======================
.. contents::
:local:
:depth: 2
定时器为与其操作器相连的每一路 PWM 提供时间基准。它以 :cpp:member:`resolution_hz <mcpwm_timer_config_t::resolution_hz>` 的频率计数,并在达到 :cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` 时回绕。先选择分辨率——它决定了边沿放置的最小步长——再选择目标频率对应的周期。
无论是舵机、调速还是逆变器,定时器都回答两个最基本的问题:"一个 Tick 有多细?" 和 "一个 PWM 周期有多长?"。后续比较器和生成器都只是在这个时间基准上放置边沿。
构建 20 kHz 时间基准
=====================
对于向上计数模式, ``period_ticks = resolution_hz / frequency_hz`` 。以下配置的 Tick 为 1 MHz每 Tick 1 微秒),周期为 50 Tick频率即为 20 kHz。下图展示了计数器从 0 上升到 50 然后复位的过程 —— ``TEZ`` (定时器归零事件)和 ``TEP`` (定时器峰值事件)是生成器使用的两个边界。
.. figure:: /../_static/mcpwm/timer_up_count.svg
:align: center
:alt: 向上计数:计数器形成锯齿波,从 0 上升到 50TEZ 在归零时触发TEP 在峰值时触发。
向上计数:计数器形成锯齿波,从 0 上升到 50TEZ 在归零时触发TEP 在峰值时触发。
.. code-block:: c
mcpwm_timer_handle_t timer = NULL;
mcpwm_timer_config_t timer_config = {
.group_id = 0,
.clk_src = MCPWM_TIMER_CLK_SRC_DEFAULT,
.resolution_hz = 1000000,
.period_ticks = 50,
.count_mode = MCPWM_TIMER_COUNT_MODE_UP,
};
ESP_ERROR_CHECK(mcpwm_new_timer(&timer_config, &timer));
示例代码只覆盖了最核心的配置,但 :cpp:type:`mcpwm_timer_config_t` 中还有一些未出现的字段,在特定场景下同样重要:
.. list::
- :cpp:member:`group_id <mcpwm_timer_config_t::group_id>` — 定时器从哪个 MCPWM 组分配。不同芯片会提供不同数量的组,每个组包含一组共享时钟分频器的定时器、操作器、比较器和生成器。 ``0`` 表示第一个组,大多数设计用它就够了。
- :cpp:member:`clk_src <mcpwm_timer_config_t::clk_src>` — 定时器的时钟源。:c:macro:`MCPWM_TIMER_CLK_SRC_DEFAULT` 选择 PLL 时钟,适合绝大多数应用。部分芯片还提供其他时钟源,可在 PLL 被关闭(例如浅睡眠)时显式指定,让定时器继续计数。
- :cpp:member:`resolution_hz <mcpwm_timer_config_t::resolution_hz>` — 计数器的 Tick 频率。一个 Tick 持续 ``1 / resolution_hz``1 MHz 即每 Tick 1 微秒。它决定了比较器能放置的边沿最细粒度。
- :cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` — 一个完整 PWM 周期包含的 Tick 数。频率为 ``resolution_hz / period_ticks``
- :cpp:member:`count_mode <mcpwm_timer_config_t::count_mode>` — 计数器是只向上计数(边沿对齐 PWM还是先上后下中心对齐 PWM。两种计数形态见 :ref:`计数模式与波形 <mcpwm-timer-counting-modes>`;硬件还支持向下计数。
- :cpp:member:`intr_priority <mcpwm_timer_config_t::intr_priority>` — 定时器回调使用的中断优先级。不设置(``0``)时由驱动选择较低优先级;当回调需要抢占其他中断时(例如对时序敏感的电机控制),可适当提高。
示例没有设置 :cpp:member:`flags <mcpwm_timer_config_t::flags>`,因此这些开关都处于关闭状态——这也是安全的默认值。其中两个值得了解:
.. list::
- :cpp:member:`update_period_on_empty <mcpwm_timer_config_t::flags::update_period_on_empty>`:cpp:member:`update_period_on_sync <mcpwm_timer_config_t::flags::update_period_on_sync>` — 默认关闭,因此 :cpp:func:`mcpwm_timer_set_period()` 会立即生效。打开后可把频率切换推迟到安全边界,见 :ref:`频率更新 <mcpwm-timer-safe-update>`
- :cpp:member:`allow_pd <mcpwm_timer_config_t::flags::allow_pd>` — 允许在睡眠时关闭 MCPWM 电源域。驱动会在睡眠前后备份并恢复定时器寄存器,从而省电,代价是额外占用 RAM。
在完成操作器、比较器和生成器的配置后,再启用并启动定时器:
.. code-block:: c
ESP_ERROR_CHECK(mcpwm_timer_enable(timer));
ESP_ERROR_CHECK(mcpwm_timer_start_stop(timer, MCPWM_TIMER_START_NO_STOP));
:cpp:func:`mcpwm_timer_enable()` 启用定时器运行所需的系统服务:使能定时器中断,并在启用电源管理时持有该组电源管理锁,避免时钟变频干扰 PWM 时序。:cpp:func:`mcpwm_timer_start_stop()` 则负责启动和停止计数。删除定时器前,先调用 :cpp:func:`mcpwm_timer_disable()` 恢复原状,再调用 :cpp:func:`mcpwm_del_timer()`
:cpp:func:`mcpwm_timer_start_stop()` 的第三个参数选择停止行为:
.. list::
- :c:macro:`MCPWM_TIMER_START_NO_STOP` — 持续运行,直到显式停止。
- :c:macro:`MCPWM_TIMER_START_STOP_EMPTY` — 下次计数到零TEZ时自动停止。适用于单次或需要完整周期后再停止的同步启动。
- :c:macro:`MCPWM_TIMER_START_STOP_FULL` — 下次计数到峰值TEP时自动停止。适用于单个完整周期后停止。
.. _mcpwm-timer-counting-modes:
计数模式与波形
==============
**向上计数** 模式下,计数器从 0 计数到 :cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` 然后复位。波形为锯齿波PWM 边沿对齐在周期的一侧——这称为 *边沿对齐* PWM。
**向上-向下计数** 模式下,计数器先向上到 ``period_ticks / 2``,再向下回 0。波形为三角波PWM 边沿围绕周期中心对称 —— *中心对齐* PWM。中心对齐 PWM 因谐波失真更小,常用于电机控制。
.. figure:: /../_static/mcpwm/timer_up_down_count.svg
:align: center
:alt: 向上-向下计数:计数器形成三角波,上升到 2550 的一半),再下降回 0。
向上-向下计数:计数器形成三角波,上升到 2550 的一半),再下降回 0。
两种模式的频率均为 ``resolution_hz / period_ticks``。选择足够高的分辨率以满足所需的占空比精度,再选择周期以达到目标频率。
.. important::
:cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` 表示完整 PWM 周期的总 Tick 数,而不是所有模式下都表示计数器峰值。
.. list::
- 在 ``MCPWM_TIMER_COUNT_MODE_UP`` 中,计数器范围是 ``0 -> period_ticks``
- 在 ``MCPWM_TIMER_COUNT_MODE_UP_DOWN`` 中,硬件峰值是 ``period_ticks / 2``,完整周期是 ``0 -> peak -> 0``
例如 :cpp:member:`resolution_hz <mcpwm_timer_config_t::resolution_hz>` = 1 MHz、:cpp:member:`period_ticks <mcpwm_timer_config_t::period_ticks>` = 50 时:向上计数模式为 ``0 -> 50``,向上-向下模式为 ``0 -> 25 -> 0``。两者完整周期都是 50 微秒,因此频率都为 20 kHz只是边沿分布不同。
.. _mcpwm-timer-safe-update:
频率更新
========
默认情况下,:cpp:func:`mcpwm_timer_set_period()` 立即生效,可能截断当前周期并产生不完整的脉冲。设置 :cpp:member:`update_period_on_empty <mcpwm_timer_config_t::flags::update_period_on_empty>` 可在计数归零时更新,设置 :cpp:member:`update_period_on_sync <mcpwm_timer_config_t::flags::update_period_on_sync>` 则在同步事件时更新。若需保持占空比不变,应同时按比例更新比较值:
.. code-block:: c
// 周期由 50 改为 100同时保持 40% 占空比
ESP_ERROR_CHECK(mcpwm_comparator_set_compare_value(comparator, 40));
ESP_ERROR_CHECK(mcpwm_timer_set_period(timer, 100));
对大多数实时调参场景,优先修改比较器以改变占空比,只有在确实需要改 PWM 频率时才修改定时器周期。电机和电源应用通常还应配合 :cpp:member:`update_period_on_empty <mcpwm_timer_config_t::flags::update_period_on_empty>` 或同步更新,避免在周期中间切换参数。
定时器事件回调
==============
定时器可在达到峰值(:cpp:member:`on_full <mcpwm_timer_event_callbacks_t::on_full>`)、归零(:cpp:member:`on_empty <mcpwm_timer_event_callbacks_t::on_empty>`)或停止(:cpp:member:`on_stop <mcpwm_timer_event_callbacks_t::on_stop>`)时通知应用。必须在启用定时器前注册回调。回调在 ISR 上下文执行,不能阻塞、分配内存或调用普通 FreeRTOS API应使用 ``...FromISR`` 变体。
.. note::
定时器和捕获定时器可能与同组其他对象共享分频器。若同一组里需要多种分辨率,请按请求分辨率单调顺序创建对象,避免分频冲突。详细规则见 :doc:`高级主题 <mcpwm_advanced>`
.. code-block:: c
static bool IRAM_ATTR on_timer_empty(mcpwm_timer_handle_t timer,
const mcpwm_timer_event_data_t *edata,
void *user_ctx)
{
BaseType_t high_task_woken = pdFALSE;
vTaskNotifyGiveFromISR((TaskHandle_t)user_ctx, &high_task_woken);
return high_task_woken == pdTRUE;
}
mcpwm_timer_event_callbacks_t cbs = { .on_empty = on_timer_empty };
ESP_ERROR_CHECK(mcpwm_timer_register_event_callbacks(timer, &cbs,
xTaskGetCurrentTaskHandle()));
如何在同步边沿将定时器加载到指定相位,见 :doc:`同步 <mcpwm_sync>`
API 参考
========
MCPWM 定时器驱动函数
--------------------
.. include-build-file:: inc/mcpwm_timer.inc

View File

@@ -397,7 +397,7 @@ LCD
旧版 MCPWM 驱动已弃用
-----------------------------------
MCPWM 驱动已更新(详见 :doc:`MCPWM <../../../api-reference/peripherals/mcpwm>`)。同时,旧版驱动已被弃用。
MCPWM 驱动已更新(详见 :doc:`MCPWM </api-reference/peripherals/mcpwm/index>`)。同时,旧版驱动已被弃用。
新驱动中,每个 MCPWM 子模块相互独立,用户可以自由进行资源连接。