mirror of
https://github.com/ImplFerris/esp32-book.git
synced 2025-09-24 14:31:13 +00:00
pwm simulation
This commit is contained in:
parent
985b498feb
commit
efedc23c04
@ -1,6 +1,75 @@
|
||||
|
||||
# Pulse Width Modulation (PWM)
|
||||
|
||||
<style>
|
||||
|
||||
.slider-container {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.led-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.led-body {
|
||||
width: 30px;
|
||||
height: 40px;
|
||||
background: radial-gradient(circle at center, #ff5555, #cc0000);
|
||||
border-radius: 50% 50% 0 0;
|
||||
border: 2px solid #990000;
|
||||
position: relative;
|
||||
box-shadow: 0 0 10px rgba(255, 85, 85, 0.8);
|
||||
}
|
||||
|
||||
.led-body::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 7px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.led-pin {
|
||||
width: 2px;
|
||||
height: 40px;
|
||||
background-color: #333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.anode {
|
||||
height: 50px; /* Longer pin for the anode */
|
||||
margin-right: 15px;
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
.cathode {
|
||||
height: 40px; /* Shorter pin for the cathode */
|
||||
margin-left: 15px;
|
||||
background-color: #333;
|
||||
position: absolute;
|
||||
margin-top: 45px;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border: 1px solid #ccc;
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
}
|
||||
#pwmCanvas {
|
||||
background-color: #fefefe;
|
||||
</style>
|
||||
|
||||
In this section, we will explore what is PWM and why we need it.
|
||||
|
||||
## Digital vs Analog
|
||||
@ -29,10 +98,34 @@ The percentage of time the signal is on during one cycle.
|
||||
<img style="display: block; margin: auto;" alt="Duty Cycle" src="../images/pwm-duty-cycle.png" />
|
||||
<span style="text-align: center;display: block; margin: auto; font-size: 12px;">Image Credit: Wikipedia</span>
|
||||
|
||||
|
||||
## PWM Simulation
|
||||
|
||||
Here is the interactive simulation. Use the sliders to adjust the duty cycle and frequency, and watch how the pulse width and LED brightness change.
|
||||
|
||||
|
||||
<canvas id="pwmCanvas" width="800" height="200"></canvas>
|
||||
<div class="led-container">
|
||||
<div class="led-body" id="ledBody"></div>
|
||||
<div class="led-pin anode"></div>
|
||||
<div class="led-pin cathode"></div>
|
||||
</div>
|
||||
|
||||
<div class="slider-container">
|
||||
<label for="dutyCycle">Duty Cycle (%): </label>
|
||||
<input type="range" id="dutyCycle" min="0" max="100" value="50">
|
||||
<span id="dutyCycleValue">50</span>%
|
||||
</div>
|
||||
<div class="slider-container">
|
||||
<label for="frequency">Frequency (Hz): </label>
|
||||
<input type="range" id="frequency" min="1" max="50" value="10">
|
||||
<!-- <span id="frequencyValue">x</span> Hz -->
|
||||
</div>
|
||||
|
||||
## Period and Frequency
|
||||
Period is the total time for one on-off cycle to complete.
|
||||
|
||||
The frequency of a PWM signal is the number of cycles it completes in one second, measured in Hertz (Hz). Frequency is the inverse of the period:
|
||||
The frequency of a PWM signal is the number of cycles it completes in one second, measured in Hertz (Hz). Frequency is the inverse of the period. So, a higher frequency means a shorter period, resulting in faster switching between HIGH and LOW states.
|
||||
|
||||
\\[
|
||||
\text{Frequency (Hz)} = \\frac{1}{\text{Period (s)}}
|
||||
@ -51,7 +144,6 @@ For example, if the period is 20ms(0.02s), the frequency will be 50Hz.
|
||||
\\]
|
||||
|
||||
|
||||
|
||||
**Calculating Cycle count from Frequency per second**
|
||||
|
||||
The Formula to calculate cycle count:
|
||||
@ -60,3 +152,81 @@ The Formula to calculate cycle count:
|
||||
\\]
|
||||
|
||||
If a PWM signal has a frequency of 50Hz, it means it completes 50 cycles in one second.
|
||||
|
||||
<script>
|
||||
const pwmCanvas = document.getElementById('pwmCanvas');
|
||||
const pwmCtx = pwmCanvas.getContext('2d');
|
||||
|
||||
const dutyCycleSlider = document.getElementById('dutyCycle');
|
||||
const dutyCycleValue = document.getElementById('dutyCycleValue');
|
||||
const frequencySlider = document.getElementById('frequency');
|
||||
const frequencyValue = document.getElementById('frequencyValue');
|
||||
const ledBody = document.getElementById('ledBody');
|
||||
|
||||
let dutyCycle = 50; // Initial duty cycle in percentage
|
||||
let frequency = 10; // Initial frequency in Hz
|
||||
|
||||
function drawPWM() {
|
||||
pwmCtx.clearRect(0, 0, pwmCanvas.width, pwmCanvas.height);
|
||||
|
||||
const period = 1000 / frequency; // Period in ms
|
||||
const onTime = period * (dutyCycle / 100); // On time in ms
|
||||
const offTime = period - onTime; // Off time in ms
|
||||
|
||||
const totalWidth = pwmCanvas.width;
|
||||
const cycles = frequency; // Number of cycles to display
|
||||
const cycleWidth = totalWidth / cycles;
|
||||
|
||||
pwmCtx.strokeStyle = 'black';
|
||||
pwmCtx.lineWidth = 2;
|
||||
pwmCtx.beginPath();
|
||||
|
||||
let x = 0;
|
||||
|
||||
if (dutyCycle === 100) {
|
||||
pwmCtx.moveTo(0, 50);
|
||||
pwmCtx.lineTo(pwmCanvas.width, 50);
|
||||
} else if (dutyCycle === 0) {
|
||||
pwmCtx.moveTo(0, 150);
|
||||
pwmCtx.lineTo(pwmCanvas.width, 150);
|
||||
} else {
|
||||
for (let i = 0; i < cycles; i++) {
|
||||
const highWidth = (onTime / period) * cycleWidth;
|
||||
const lowWidth = (offTime / period) * cycleWidth;
|
||||
|
||||
pwmCtx.moveTo(x, 50);
|
||||
pwmCtx.lineTo(x + highWidth, 50);
|
||||
pwmCtx.lineTo(x + highWidth, 150);
|
||||
pwmCtx.lineTo(x + highWidth + lowWidth, 150);
|
||||
pwmCtx.lineTo(x + highWidth + lowWidth, 50);
|
||||
|
||||
x += cycleWidth;
|
||||
}
|
||||
}
|
||||
pwmCtx.stroke();
|
||||
}
|
||||
|
||||
function updateLED() {
|
||||
const brightness = dutyCycle / 100;
|
||||
|
||||
ledBody.style.background = `radial-gradient(circle at center, rgba(255, 85, 85, ${brightness}), #cc0000)`;
|
||||
}
|
||||
|
||||
function update() {
|
||||
dutyCycle = parseInt(dutyCycleSlider.value, 10);
|
||||
frequency = parseInt(frequencySlider.value, 10);
|
||||
|
||||
dutyCycleValue.textContent = dutyCycle;
|
||||
// frequencyValue.textContent = frequency;
|
||||
|
||||
drawPWM();
|
||||
updateLED();
|
||||
}
|
||||
|
||||
dutyCycleSlider.addEventListener('input', update);
|
||||
frequencySlider.addEventListener('input', update);
|
||||
|
||||
// Initial draw
|
||||
drawPWM();
|
||||
updateLED();
|
||||
</script>
|
||||
|
@ -4,5 +4,13 @@ The ESP32 has LED PWM Controller(LEDC) that generates PWM signals for controllin
|
||||
|
||||
The LEDC includes 16 independent PWM generators and supports a maximum PWM duty cycle resolution of 20 bits. The 16 PWM channels further classified into two types: 8 high speed channel and 8 low speed channels.
|
||||
|
||||
<div class="alert-box alert-box-info">
|
||||
<span class="icon"><i class="fa fa-info"></i></span>
|
||||
<div class="alert-content">
|
||||
<b class="alert-title">High vs Low Speed channels</b>
|
||||
<p>High-speed channels use hardware to automatically adjust the PWM duty cycle in a glitch-free manner, ensuring smooth operation. In contrast, low-speed channels rely on software to manually adjust the duty cycle.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
The PWM controller can automatically increase or decrease the duty cycle gradually, allowing for smooth fades without using the processor. For more details, refer to page 390 of the [ESP32 Technical Reference Manual](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf#ledpwm).
|
||||
|
||||
|
@ -240,3 +240,292 @@ sup {
|
||||
.result-no-output {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Wire body */
|
||||
.wire {
|
||||
position: relative;
|
||||
width: 200px;
|
||||
height: 4px;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 20px; /* Space between wires */
|
||||
|
||||
}
|
||||
|
||||
/* Wire colors */
|
||||
.wire.red { background: #ff0000; } /* Red wire */
|
||||
.wire.black {
|
||||
background: #000000;
|
||||
border: 2px solid #FFFFFF;
|
||||
} /* Black wire */
|
||||
.wire.green { background: #13d613; } /* Green wire */
|
||||
.wire.yellow { background: #e6e61c; } /* Yellow wire */
|
||||
.wire.blue { background: #057af7; } /* Default blue wire */
|
||||
.wire.orange { background: #fc5310; } /* Orange wire */
|
||||
.wire.brown { background: #824101; } /* Orange wire */
|
||||
.wire.purple {background: #800080;}
|
||||
|
||||
/* White wire with border to make it visible */
|
||||
.white {
|
||||
background: #ffffff; /* White color */
|
||||
/* border: 1px dotted #AAAAAA; */
|
||||
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Male connector (left) */
|
||||
.male-left {
|
||||
position: absolute;
|
||||
left: -19px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 8px;
|
||||
background: #000;
|
||||
border: 2px solid #CCC;
|
||||
}
|
||||
|
||||
/* Pin extending from left connector */
|
||||
.male-left::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -12px; /* Reduced gap between connector and pin */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
background: #555; /* Pin color */
|
||||
}
|
||||
|
||||
/* Male connector (right) */
|
||||
.male-right {
|
||||
position: absolute;
|
||||
right: -8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 8px;
|
||||
background: #000;
|
||||
border: 2px solid #CCC;
|
||||
|
||||
}
|
||||
|
||||
/* Pin extending from right connector */
|
||||
.male-right::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -12px; /* Reduced gap between connector and pin */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 12px;
|
||||
height: 2px;
|
||||
background: #555; /* Pin color */
|
||||
}
|
||||
|
||||
/* Female connector (left) */
|
||||
.female-left {
|
||||
position: absolute;
|
||||
left: -19px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 8px;
|
||||
background: #000;
|
||||
border: 2px solid #CCC;
|
||||
}
|
||||
|
||||
.female-left::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 1px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #999;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Female connector (right) */
|
||||
.female-right {
|
||||
position: absolute;
|
||||
right: -19px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 24px;
|
||||
height: 8px;
|
||||
background: #000;
|
||||
border: 2px solid #CCC;
|
||||
}
|
||||
|
||||
.female-right::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 1px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #999;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.boxed-text {
|
||||
display: inline-block;
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
background-clip: padding-box;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.alert-box {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
/* padding: 16px; */
|
||||
border-radius: 8px;
|
||||
margin: 16px 0;
|
||||
font-family: Arial, sans-serif;
|
||||
position: relative;
|
||||
padding: 12px 0px 0px 10px;
|
||||
}
|
||||
|
||||
.alert-box .icon {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.5); /* Semi-transparent background */
|
||||
border: 2px solid currentColor;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 8px;
|
||||
color: inherit; /* Matches the alert's text color */
|
||||
}
|
||||
|
||||
.alert-box .alert-content {
|
||||
margin-left: 48px; /* Leaves space for the round icon */
|
||||
}
|
||||
|
||||
.alert-box .alert-title {
|
||||
margin: 0px 8px 0px 0px;
|
||||
font-size: 18px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
/* .alert-box p { */
|
||||
/* margin: 0; */
|
||||
/* font-size: 14px; */
|
||||
/* } */
|
||||
|
||||
.alert-box.alert-box-warning {
|
||||
background-color: #fff4cc;
|
||||
border: 1px solid #ffcc00;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.alert-box.alert-box-info {
|
||||
background-color: #E8F0Fe;
|
||||
border: 1px solid #17a2b8;
|
||||
color: #0c5460;
|
||||
}
|
||||
|
||||
.alert-box.alert-box-danger {
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #dc3545;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.alert-box.alert-box-success {
|
||||
background-color: #d4edda;
|
||||
border: 1px solid #28a745;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.icon i {
|
||||
background: none !important; /* Remove any background set by Font Awesome */
|
||||
box-shadow: none !important; /* Remove shadow if applied */
|
||||
color: inherit; /* Inherit alert text color */
|
||||
}
|
||||
|
||||
/* General styles for slanted text */
|
||||
.slanted-text {
|
||||
display: inline-block;
|
||||
padding: 3px 6px;
|
||||
margin: 0 5px;
|
||||
font-weight: bold;
|
||||
transform: skew(-8deg);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Individual color themes */
|
||||
.slanted-text.red {
|
||||
/* background: linear-gradient(135deg, #e74c3c, #c0392b); */
|
||||
background: #e74c3c;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.slanted-text.yellow {
|
||||
/* background: linear-gradient(135deg, #f39c12, #e67e22); */
|
||||
background: #efe012;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.slanted-text.teal {
|
||||
/* background: linear-gradient(135deg, #1abc9c, #16a085); */
|
||||
background: #1abc9c;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.slanted-text.blue {
|
||||
/* background: linear-gradient(135deg, #00bcd4, #008c8c); */
|
||||
background: #3457D5;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.slanted-text.pink {
|
||||
/* background: linear-gradient(135deg, #e91e63, #c2185b); */
|
||||
background: #e91e63;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.slanted-text.purple {
|
||||
/* background: linear-gradient(135deg, #8e44ad, #6f4f37); */
|
||||
background: #8e44ad;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.slanted-text.brown {
|
||||
/* background: linear-gradient(135deg, #6f4f37, #a0522d); */
|
||||
background: #a0522d;
|
||||
color: black;
|
||||
}
|
||||
.slanted-text.green {
|
||||
/* background: linear-gradient(135deg, #2ecc71, #27ae60); */
|
||||
background: #00A550;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.slanted-text.gray {
|
||||
background: linear-gradient(135deg, #bdc3c7, #95a5a6);
|
||||
color: black;
|
||||
}
|
||||
|
||||
.slanted-text.indigo {
|
||||
background: linear-gradient(135deg, #3f51b5, #303f9f);
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
.slanted-text.black {
|
||||
background: #333333;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.slanted-text span {
|
||||
display: inline-block;
|
||||
transform: skew(10deg);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user