SBK_BarDrive Library 2.1.2
LED bar meter control and queued animations for Arduino
Loading...
Searching...
No Matches
animationShowcase.ino
Go to the documentation of this file.
1
2/**
3 * @file animationShowcase.ino
4 * @brief Demonstrates various SBK_BarDrive animations with signal simulation and automatic cycling.
5 *
6 * This example runs multiple LED bar animations sequentially, including:
7 * - Random fill/empty
8 * - Fill up/down
9 * - Scrolling blocks
10 * - Signal-following animations
11 *
12 * Requirements:
13 * - Supported driver with complatible library (SBK_MAX72xx or SBK_HT16K33 libraries)
14 * - Bar meter display or leds array wired to driver
15 *
16 * @author
17 * Samuel Barabé (Smart Builds & Kits)
18 *
19 * @version 2.1.2
20 * @license MIT
21 */
22
23#include <Arduino.h>
24#include <math.h>
25
26// ──────────────────────────────────────────────
27// SBK BarDrive Configuration Flags
28// ──────────────────────────────────────────────
29#define SBK_BARDRIVE_WITH_ANIM // Give access to preset animations and controls.
30
31// ──────────────────────────────────────────────
32// SELECT YOUR DRIVER SETUP
33// Uncomment one of the following driver configurations
34// ──────────────────────────────────────────────
35
36/* === [A] Using MAX7219/MAX7221 via SOFTWARE SPI (any 3 digital pins) === */
37// #define DIN_PIN A4 // Define software SPI Data In pin
38// #define CLK_PIN A5 // Define software SPI Clock pin
39// #define CS_PIN A3 // Define SPI Chip Select pin
40// #include <SBK_MAX72xxSoft.h>
41// SBK_MAX72xxSoft driver(DIN_PIN, CLK_PIN, CS_PIN, 1); // Construct MAX72xx software SPI driver instance for 1 device : (DataIn pin, Clock pin, Chip Select pin, devices number)
42// #include <SBK_BarDrive.h>
43// SBK_BarDrive<SBK_MAX72xxSoft> bar(&driver, 0, MatrixPreset::BL28_3005SK); // Construct using matrix type bar meter preset (auto-mapped layout) : (driver, device index, MatrixPreset type)
44
45/* === [B] Using MAX7219/MAX7221 via HARDWARE SPI (dedicated MCU SPI pins) === */
46// #define CS_PIN A3
47// #include <SBK_MAX72xxHard.h>
48// SBK_MAX72xxHard driver(CS_PIN, 1); // Construct MAX72xx hardware SPI driver instance for 1 device : (Chip Select pin, devices number)
49// #include <SBK_BarDrive.h>
50// SBK_BarDrive<SBK_MAX72xxHard> bar(&driver, 0, MatrixPreset::BL28_3005SK); // Construct using matrix type bar meter preset (auto-mapped layout) : (driver, device index, MatrixPreset type)
51
52/* === [C] Using HT16K33 via I2C === */
53#include <SBK_HT16K33.h>
54const uint8_t NUM_DEV = 1; // Only one device : DEV0
55const uint8_t DEV0_IDX = 0; // Device DEV0 index
56const uint8_t DEV0_ADD = 0x70; // I2C Address (typically 0x70–0x77)
57const uint8_t DEV0_NUM_ROWS = 8; // 20-SOP HT16K33 with only 8 rows, 24-SOP has 12 rows, 28-SOP has 16 rows
58SBK_HT16K33 driver(NUM_DEV);
59#include <SBK_BarDrive.h>
60SBK_BarDrive<SBK_HT16K33> bar(&driver, 0, MatrixPreset::BL28_3005SK); // Construct using matrix type bar meter preset (auto-mapped layout) : (driver, device index, MatrixPreset type)
61
62/*
63 * 💡 Default example assumes a BL28_3005SK 28-segment matrix type bar meter.
64 * You may change the MatrixPreset or constructor method to match your specific hardware.
65 * See README and other examples for linear 1D (non matrix) type or custom mapping options.
66 */
67
68uint8_t demoMode = 0;
69unsigned long lastSwitch = 0;
70const unsigned long switchInterval = 10000;
71bool lastIsRunning = false;
72bool setupFlag = false;
73bool update = false;
74bool lastUpdate = false;
75
76uint16_t fakeSignal1 = 0;
77uint16_t fakeSignal2 = 0;
78uint16_t generateFakeSignal(uint32_t tMillis);
79
80void setup()
81{
82 Serial.begin(115200);
83
84 Serial.print(F("Animations showcase setup..."));
85
86#ifdef SBK_HT16K33_IS_DEFINED
87 // HT16K33 driver instance setup (demo uses a single device)
88 driver.setAddress(DEV0_IDX, DEV0_ADD); // Set I2C address for device 0
89 driver.setDriverRows(DEV0_IDX, DEV0_NUM_ROWS); // Set number of active anode outputs (rows)
90#endif
91 driver.begin(); // Initialize the driver
92 driver.setBrightness(0, 10); // Set brightness level (0 = dim, 15 = bright)
93
94 bar.setDirection(BarDirection::FORWARD); // Set initial bar fill direction
95
96 demoMode = 12; // Setup demoMode index to show demoMode 0 at fisrt itération;
97 Serial.println(F("done! Showcase begin!"));
98}
99
100/*
101 * 🔄 Direction vs. Logic
102 *
103 * SBK_BarDrive separates animation logic from display direction.
104 *
105 * - Logic: Defines how the animation progresses internally.
106 * Examples: fill vs. empty, emit vs. absorb, colliding vs. exploding.
107 * Changing the logic affects how segments are activated over time.
108 *
109 * - Direction: Controls how the animation is rendered (mirrored or not).
110 * FORWARD = segments 0 → N−1
111 * REVERSE = segments N−1 → 0
112 * This affects visual orientation only — not the underlying logic.
113 *
114 * Examples:
115 * - fillUpIntv() always fills from the first to the last segment logically.
116 * - bar.setDirection(REVERSE) mirrors that fill so it appears to fill from top to bottom.
117 * - Inverting fillUpIntv() will result in emptying fromtop to bottom (emptyDown),
118 * not a top-down fill. The logic changed, not the direction.
119 *
120 * 🛑 Not all animations support inverted logic:
121 * - Animations like scrolling blocks or bounce fill ignore logic inversion.
122 * - For these, only direction (setDirection, toggleDirection, resetDirection) will alter appearance.
123 *
124 * ✅ You can combine logic and direction to create symmetric or inverted animations
125 * without modifying the animation source.
126 */
127
128void loop()
129{
130
131 /*
132 * Update current animation.
133 * IMPORTANT : animations().update() must be called every loop to render the current animation.
134 */
135 bar.animations().update();
136 /*
137 * Push animation into bar meter driver.
138 * IMPORTANT : must be call every loop after animations updates.
139 */
140 bar.show();
141
142 if (update != lastUpdate)
143 {
144 Serial.println(update);
145 lastUpdate = update;
146 }
147 /*
148 * If animation is not running, stop current animation,
149 * reset animation logic, and increment demoMode to start next animation.
150 *
151 * IMPORTANT : animations().update() must be called every loop to render the current animation.
152 */
153 if (bar.animations().isRunning() == false) // update animation and check if it's done
154 {
155 lastSwitch = millis();
156 bar.animations().stop().resetLogic(); // Clear previous
157
158 if (setupFlag == false)
159 setupFlag = true;
160 else
161 Serial.println(F(" Current animation is stopped!"));
162
163 Serial.println();
164 demoMode = (demoMode + 1) % 12; // Increment demoMode by 1 and reset it to 0 if greater then 11.
165 }
166
167 /*
168 * Some selected animations for demonstration purposes.
169 * Also demonstrate some possibles animations helpers.
170 *
171 * Check README file and Doxygen documentation for full animations and controls list.
172 */
173 switch (demoMode)
174 {
175 case 0:
176 if (!bar.animations().isRunning())
177 {
178 bar.animations().animInit().fillUpIntv(30).loop();
179 Serial.println(F("Demo 0 : fillUp at intv and loop."));
180 }
181 // When logic is inverted, animation empty down instead of fill up.
182 break;
183 case 1:
184 if (!bar.animations().isRunning())
185 {
186 bar.animations().animInit().bounceFillUpIntv(20, 60, 20, 80).loop();
187 Serial.println(F("Demo 1 : fillUp fast and emptyDown slow at intv with range limits and loop."));
188 }
189 // When logic is inverted nothing change : it's a non inverting logic animation.
190 // A reverseDir() or toggleDir() could be use to change the animation direction to bounce down...
191 break;
192 case 2:
193 if (!bar.animations().isRunning())
194 {
195 bar.animations().animInit().collidingBlocks(25, 4, 4, 6).loop();
196 Serial.println(F("Demo 2 : 6 blocks are emitted from edges and collide at center."));
197 // 6 blocks (4 pixels length, 4 pixels space) are emitted at both edges and collide at the display center.
198 // The blocks move at fixed interval (25ms), when all blocks are exited, animation loops...
199 }
200 // When logic is inverted, animation emits blocks from the center.
201 break;
202 case 3:
203 if (!bar.animations().isRunning())
204 {
205 bar.animations().animInit().explodingBlocks(25, 8, 10);
206 Serial.println(F("Demo 3 : Infinite blocks are emitted from center."));
207 // Infinite blocks (8 pixels length, 10 pixels space) are emitted from display center.
208 // The blocks move at fixed interval (25ms), this animation is an ever running animation,
209 // loop()/noLoop() have no effect since there is an infinite blocks number...
210 }
211 // When logic is inverted, animation emits blocks from the edges toward the center.
212 break;
213 case 4:
214 if (!bar.animations().isRunning())
215 {
216 bar.animations().animInit().scrollingUpBlocks(25, 5, 3, 8).loop();
217 Serial.println(F("Demo 4 : 4 blocks scrolling from bottom to top."));
218 // 4 blocks (5 pixels length, 3 pixels space) from bottom edge and scroll to top.
219 // The blocks move at fixed interval (25ms), when all blocks are exited, animation loops...
220 }
221 // When logic is inverted nothing change : it's a non inverting logic animation.
222 // A reverseDir() or toggleDir() could be use to change the animation direction tio scroll down block...
223 break;
224 case 5:
225 if (!bar.animations().isRunning())
226 {
227 bar.animations().animInit().scrollingDownBlocks(25, 2, 4);
228 Serial.println(F("Demo 5 : Infinite blocks scrolling from top to bottom."));
229 // Infinite blocks (2 pixels length, 4 pixels space) from top to bottom edge.
230 // The blocks move at fixed interval (25ms), this animation is an ever running animation,
231 // loop()/noLoop() have no effect since there is an infinite blocks number...
232 }
233 // When logic is inverted nothing change : it's a non inverting logic animation.
234 // A reverseDir() or toggleDir() could be use to change the animation direction tio scroll down block...
235 break;
236 case 6:
237 if (!bar.animations().isRunning())
238 {
239 bar.animations().animInit().followSignalSmooth(&fakeSignal1, 100, 0, 1023, 70, 5);
240 Serial.println(F("Demo 6 : Bar fill up to smoothed signal pointer."));
241 // Visual update are made at fixed interval (100ms).
242 // Display range is mapped from signal min/max values (min 0, max 1023
243 // A 30% smooth factor is applied, sampling is made at 5ms.
244 // loop()/noLoop() have no effect since it's an ever running animation
245 }
246 // When logic is inverted nothing change : it's a non inverting logic animation.
247 // A reverseDir() or toggleDir() could be use to change the animation direction to fill down to signal level...
248 break;
249 case 7:
250 if (!bar.animations().isRunning())
251 {
252 bar.animations().animInit().followDualSignalFromCenter(&fakeSignal1, 100);
253 Serial.println(F("Demo 7 : Bar fill from center toward edges at smoothed signal level."));
254 // fakeSignal1 is mirror at center.
255 // Visual update are made at fixed interval (100ms).
256 // Display range is mapped from signal min/max default values (min 0, max 1023).
257 // A 30% default smooth factor is applied, sampling is made at 5ms default value.
258 // loop()/noLoop() have no effect since it's an ever running animation
259 }
260 // When logic is inverted, the signal level fill is made from the edges toward center.
261 // A reverseDir() or toggleDir() would not affect this animation because it's already mirrored at the cent4er.
262 break;
263 case 8:
264 if (!bar.animations().isRunning())
265 {
266 bar.animations().animInit().followSignalFloatingPeak(&fakeSignal1);
267 Serial.println(F("Demo 8 : Bar fill at smoothed signal level with floating peak pixel."));
268 }
269 // When logic is inverted nothing change : it's a non inverting logic animation.
270 // A reverseDir() or toggleDir() could be use to change the animation direction pulse from top...
271
272 break;
273 case 9:
274 if (!bar.animations().isRunning())
275 {
276 bar.animations().animInit().downStackingBlocks(20, 1, 0).loop();
277 Serial.println(F("Demo 9 : blocks fall from top and stack at the bottom"));
278 }
279 // When logic is inverted blocks are unstacking and flying out.
280 break;
281 case 10:
282 if (!bar.animations().isRunning())
283 {
284 bar.animations().setAllOff().animInit().randomFill(50);
285 Serial.println(F("Demo 10 : OFF pixels are turned ON in a random pattern at intv."));
286 }
287 break;
288 case 11:
289 if (!bar.animations().isRunning())
290 {
291 bar.animations().animInit().randomEmpty(50);
292 Serial.println(F("Demo 11 : ON pixels are turned OFF in a random pattern at intv."));
293 }
294 break;
295 }
296
297 // Toggle logic at after 5 secondes trough animation
298 if (bar.animations().isNonInvertingLogicAnim() == false && bar.animations().isLogicInverted() == false && millis() - lastSwitch >= 5000)
299 {
300 if ((demoMode == 3 || demoMode == 5 || demoMode == 7) || (bar.animations().animPendingLoop() == true))
301 {
302 bar.animations().invertLogic();
303 Serial.println(F(" Animation logic inverted !"));
304 }
305 }
306
307 // End this demoMode
308 if (millis() - lastSwitch > 10000)
309 {
310 if (bar.animations().isLoopEnabled() == true)
311 {
312 bar.animations().noLoop();
313 Serial.println(F(" Loop stopped !"));
314 }
315 if (demoMode == 6 || demoMode == 7 || demoMode == 8)
316 {
317 bar.animations().stop();
318 }
319 if ((demoMode == 3 || demoMode == 5) && (bar.animations().isBlockEmissionEnabled() == true))
320 {
321 bar.animations().stopBlockEmission();
322 Serial.println(F(" Blocks emission stopped !"));
323 }
324 }
325
326 /* Simulate a pair of sine-wave analog signals for demonstration purposes */
327 fakeSignal1 = generateFakeSignal(millis());
328 fakeSignal2 = generateFakeSignal(millis() + 2157);
329}
330
331uint16_t generateFakeSignal(uint32_t tMillis)
332{
333 // Simulates a noisy analog signal using a sine wave + random jitter
334
335 float baseFreq = 0.0015; // Controls main wave speed
336 float noiseFreq = 0.006; // Adds irregularity
337 float noiseAmp = 300; // Max +/- variation in amplitude
338
339 float base = sin(tMillis * baseFreq) * 400 + 500; // sine from ~100 to 900
340 float noise = sin(tMillis * noiseFreq + random(0, 1000)) * noiseAmp;
341
342 return constrain(base + noise, 0, 1023);
343}