This program sends a cascading light effect through the LED bar that fades on either side of a strong central point. It normally flows in one direction, but if you press the button, it changes direction.
## Lessons Learned
I learned several things from this project:
- How to use the Arduino IDE to deploy code to the ESP32-S3 WROOM
- How to use the Arduino IDE debugger to step through code running on the device
- How to configure GPIO pins on the ESP32-S3 as input or output
- How to read and write data to and from those GPIO pins
- Using a physical button to control program flow in the microcontroller
- Debouncing mechanical buttons
- What is pulse width modulation and how do we control it on the ESP32-S3
- Using pulse width modulation with an LED to create a smooth visual effect simulating an analog curve
Buttons and other physical contacts have jitter, being a time where they are becoming connected but not yet fully stable. This means we may read the input and get many different readings over the course of the first few milliseconds. The tutorial I was following had a very simplistic way to manage this:
... This is not great. On simpler microcontrollers, `delay()` burns CPU cycles (The ESP32-S3 has an Xtensa dual-core 32-bit LX7 running up to 240Mhz - stunning when you think about the price and size of the package, but still, not enough to be wasteful). But on the ESP32-S3 in particular, the solution package as a whole isn't losing those cycles - it runs Arduino on top of an RTOS, so `delay()` is actually yielding back to the RTOS scheduler so that things like bluetooth and wifi stacks can continue. But *our* program is fully blocked here. If I want to scan additional buttons, or perform some other task, we're locked in here. So I improved that by using a timer in a function that yields time back to the CPU while it's waiting to verify that the button is debounced.
Pulse Width Modulation (PWM) is essentially a way for digital circuits to pretend to be analog. Essentially, analog circuits can produce signals that have gentle sweeping curves, elegant waves, like the waves on the sea. Digital circuits can only produce on or off values - hard square shapes with sharp edges, like man chiseling facts into stone.
Basically pulse width modulation allows us to say that, over a given period of time T, we can use a digital value V with a precision of P bits, to express that for a given percentage of the period (known as the "duty cycle"), the circuit should be ON (usually HIGH), while for the remainder of the duty cycle, it should be OFF (normally LOW). For example:
... We can send a value of `512` to turn the signal HIGH for 50% duty cycle (50% of the period, approximately 500ms). Sending `256` would be approximately 25%, `768` approximately 75%, and `1023` 100%. While the output pin is only ever sending two values (ON at 5v or OFF at 0v), most circuits and devices actually average the voltage on the circuit. This makes our PWM output seem like a wave, when in reality, we are just providing the same voltage for more or less of the time that the circuit is on.
Time period T is also related to frequency, F. The time period in milliseconds has an inverse relationship to the frequency in hertz.
As T increases positive from zero, F decreases. So longer time results in lower frequencies. This will become more important in future tutorials where we start using PWM for audio devices like buzzers.
In the case of an LED, by controlling the voltage with PWM, we can create a glowing or fading effect. As the duty cycle increases, the LED gets brighter; as it decreases, the LED gets dimmer.
The code connects several GPIO pins of the ESP32 to the input pins of an LED bar array to drive their power. In code, it uses an array, `pwmvalues`, to track the PWM value of each output pin. It initializes them in a ladder - each one being higher than the next. This creates the initial appearance of the waterfall/fade effect.
The code also tracks the velocity (UP or DOWN) for each pin. This tells the code if it should be increasing or decreasing the value on that pin. (*I should have named this 'direction' rather than 'velocity' but whatever.*)
During the main program loop, it modifies the values of the pins according to their velocities (bouncing them UP at 0 and DOWN at the maximum value), and then outputs the value to the given GPIO pin.
```arduino
for ( int i = 0; i < LED_COUNT ; i++ ) {
if ( pwmvalues[i] == 0 ) {
velocities[i] = UP;
pwmvalues[i] = 1;
} else if ( pwmvalues[i] == PWM_VALUE_MAX ) {
velocities[i] = DOWN;
}
if ( velocities[i] == UP ) {
pwmvalues[i] = pwmvalues[i] << 1;
} else if ( velocities[i] == DOWN ) {
pwmvalues[i] = pwmvalues[i] >> 1;
}
ledcWrite(ledPins[i], pwmvalues[i]);
}
```
## Changing direction with the button
Now that we have working visual flow on the LEDs and the button debounce mechanic works, implementing the button is simple. When the user presses the buttons, all we have to do is invert the velocities for each pin, to reverse the visual effect.
```arduino
checkButtonPressed();
if ( buttonState == BUTTON_DOWN ) {
for ( int i = 0; i < LED_COUNT ; i++ ) {
if ( velocities[i] == UP) {
velocities[i] = DOWN;
} else {
velocities[i] = UP;
}
}
buttonState = BUTTON_HELD;
}
```
... We set `buttonState` to `BUTTON_HELD` after doing this, so that we only change direction once for each button press. If we didn't do this, then as long as the user held down the button, we would continually change direction on the bar. This usually had the appearance of a PAUSE button, which was neat, but not what I was going for.
[Here is a video of it in action](https://x.com/AKLabsDotNet/status/2058655798591476058)