Lamp-Da 0.1
A compact lantern project
Loading...
Searching...
No Matches
strip.h
Go to the documentation of this file.
1
5#ifndef STRIP_H
6#define STRIP_H
7
8// this file is active only if LMBD_LAMP_TYPE=indexable
9#ifdef LMBD_LAMP_TYPE__INDEXABLE
10
11#include <cstdint>
12#include <cstring>
13#include <array>
14
16
17#ifndef LMBD_SIMULATION
19#else
21#endif
22
23#include "src/system/ext/scale8.h"
24#include "src/system/ext/random8.h"
25
29
30#include "src/user/constants.h"
31
32namespace lampda {
33
34static constexpr size_t stripNbBuffers = 3;
35static constexpr float baseCurrentConsumption = 0.4f;
36static constexpr float maxCurrentConsumption = 2.7f - baseCurrentConsumption;
37static constexpr float ampPerLed = maxCurrentConsumption / (float)LED_COUNT;
38
39namespace modes::hardware {
40struct LampTy;
41}
42
43namespace physical {
44
45using StripImpl_t = platform::strip::LampdaStrip<LED_COUNT, 3>;
46
48class LedStrip : private StripImpl_t
49{
50 using BufferTy = std::array<uint32_t, LED_COUNT>;
51 friend struct modes::hardware::LampTy;
52
54 static constexpr bool useColorDithering = true;
56 static constexpr bool useTemporalDithering = false;
57 static constexpr uint16_t refreshFramesCount = 10;
58
59public:
60 LedStrip(int16_t pin, neoPixelType type = NEO_RGB + NEO_KHZ800) : StripImpl_t(pin, type), shownCount(0)
61 {
62 assert(_colorErrors.size() > 0);
63
64 COLOR c;
65 c.color = 0;
66 for (uint16_t i = 0; i < LED_COUNT; ++i)
67 {
68 if constexpr (useTemporalDithering)
69 {
70 _colorErrors[i] = c;
71 }
72
73 _colors[i] = c;
74 }
75 }
76
77 void show()
78 {
79 if (hasSomeChanges)
80 {
81 // only show if some changes were made
82 show_now();
83 }
84 hasSomeChanges = false;
85 }
86
87 float estimateCurrentDraw() const
88 {
89 float estimatedCurrentDraw = 0.0;
90
91 const uint8_t b = getBrightness();
92 const float currentPerLed = lmpd_map<float>(b, 0, 255, 0.0f, ampPerLed);
93
94 for (uint16_t i = 0; i < LED_COUNT; ++i)
95 {
96 COLOR c;
97 c.color = getRawPixelColor(i);
98
99 const float res = max<float>(c.blue, max<float>(c.red, c.green));
100 if (res <= 0)
101 {
102 continue;
103 }
104
105 estimatedCurrentDraw += currentPerLed;
106 }
107 return max<float>(baseCurrentConsumption, estimatedCurrentDraw);
108 }
109
111 void setBrightness(uint8_t b) { brightness = b; }
112
114 uint8_t getBrightness() const { return brightness; }
115
116 // Accessor to set a color
117 void setPixelColor(uint16_t n, COLOR c)
118 {
119 if (n >= LED_COUNT)
120 return;
121
122 _colors[n] = c;
123 }
124
129 void begin() { StripImpl_t::begin(); }
130
133 uint32_t getRawPixelColor(uint16_t n) const
134 {
135 // ,o colors outside of strip
136 if (n >= LED_COUNT)
137 return 0;
138
139 COLOR c;
140 c.color = StripImpl_t::getPixelColor(n);
141
142 // We use brightnessAtShowTime here, or the colors can break when brightness changed
143 c.blue = restore_color_with_brightness(c.blue, brightnessAtShowTime);
144 c.green = restore_color_with_brightness(c.green, brightnessAtShowTime);
145 c.red = restore_color_with_brightness(c.red, brightnessAtShowTime);
146
147 return c.color;
148 }
149
151 void write_to_led_driver(const uint8_t writeBrightness)
152 {
153 // Adjust brightness to the desired output
154 const uint8_t capedShown = shownCount % refreshFramesCount;
155 for (uint16_t i = 0; i < LED_COUNT; ++i)
156 {
157 if constexpr (useTemporalDithering)
158 {
159 const COLOR c = convert_color_with_brigthness(_colors[i], writeBrightness, i + capedShown, _colorErrors[i]);
160 // set strip color
161 StripImpl_t::setPixelColor(i, c.color);
162 }
163 else
164 {
165 const COLOR c = convert_color_with_brigthness(_colors[i], writeBrightness, i + capedShown, _colorErrors[0]);
166 // set strip color
167 StripImpl_t::setPixelColor(i, c.color);
168 }
169 }
170 }
171
173 void show_now()
174 {
175 // copy the pattern to show to the display buffer
176 brightnessAtShowTime = brightness;
177 write_to_led_driver(brightnessAtShowTime);
178 // show on hardware
180 hasSomeChanges = false;
181 // increment show count
182 auto refCount = shownCount;
183 shownCount = refCount + 1;
184 }
185
197 static uint8_t restore_color_with_brightness(uint8_t colorIn, const uint8_t brightness)
198 {
199 // only special case
200 const uint16_t colorShifted = (uint16_t)colorIn << 8;
201 if (colorShifted <= brightness or brightness == 0)
202 return 0;
203
204 uint8_t fullColor = (colorShifted - brightness) / brightness;
205 return fullColor;
206 }
207
216 static std::pair<uint8_t, uint8_t> get_brightness_color_and_error(const uint8_t colorIn,
217 uint16_t errorIn,
218 const uint8_t brightness,
219 const uint16_t index)
220 {
221 // blue noise look up table
222 static constexpr std::array<uint8_t, 64> BLUE_NOISE_LUT = {
223 0, 32, 8, 40, 2, 34, 10, 42, 48, 16, 56, 24, 50, 18, 58, 26, 12, 44, 4, 36, 14, 46,
224 6, 38, 60, 28, 52, 20, 62, 30, 54, 22, 3, 35, 11, 43, 1, 33, 9, 41, 51, 19, 59, 27,
225 49, 17, 57, 25, 15, 47, 7, 39, 13, 45, 5, 37, 63, 31, 55, 23, 61, 29, 53, 21};
226
227 // only special case
228 if (colorIn == 0)
229 return {0, 0};
230
231 // scale color by brightness
232 const uint16_t scaledColor = colorIn * brightness + brightness;
233
234 // When using standard and temporal dithering, shift the pattern
235 if constexpr (useColorDithering and useTemporalDithering)
236 {
237 const uint8_t allowedError = UINT8_MAX - (scaledColor & 0xFF);
238 errorIn += min<uint16_t>(allowedError, BLUE_NOISE_LUT[index % BLUE_NOISE_LUT.size()]);
239 }
240
241 // add error
242 const uint32_t fullColor = min<uint32_t>(scaledColor + errorIn, 0xFF00);
243
244 // compute final color and new error
245 uint32_t finalColorRaw = fullColor >> 8;
246 const uint8_t finalError = fullColor & 0xFF;
247
248 // When using only color dithering, add a blue noise error to the final color
249 if constexpr (useColorDithering and not useTemporalDithering)
250 {
251 finalColorRaw += (finalError > BLUE_NOISE_LUT[index % BLUE_NOISE_LUT.size()] ? 1 : 0);
252 }
253
254 const uint8_t finalColor = (finalColorRaw > 255) ? 255 : finalColorRaw;
255 return {finalColor, finalError};
256 }
257
267 const uint8_t brightness,
268 const uint16_t index,
269 COLOR& error)
270 {
271 // no temporal dithering: no error propagation
272 if constexpr (not useTemporalDithering)
273 {
274 error.red = 0;
275 error.green = 0;
276 error.blue = 0;
277 }
278 // convert colors and errors, with small offsets for the different chanels
279 const auto& [red, redError] = get_brightness_color_and_error(c.red, error.red, brightness, index);
280 const auto& [green, greenError] = get_brightness_color_and_error(c.green, error.green, brightness, index + 1);
281 const auto& [blue, blueError] = get_brightness_color_and_error(c.blue, error.blue, brightness, index + 2);
282
283 // store error components
284 error.red = redError;
285 error.green = greenError;
286 error.blue = blueError;
287
288 COLOR result;
289 result.red = red;
290 result.green = green;
291 result.blue = blue;
292 return result;
293 }
294
295 void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b)
296 {
297 COLOR c;
298 c.red = r;
299 c.green = g;
300 c.blue = b;
301
302 setPixelColor(n, c);
303 }
304
305 void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w)
306 {
307 COLOR c;
308 c.red = r;
309 c.green = g;
310 c.blue = b;
311 c.white = w;
312
313 setPixelColor(n, c);
314 }
315
316 void setPixelColor(uint16_t n, uint32_t c)
317 {
318 COLOR co;
319 co.color = c;
320
321 setPixelColor(n, co);
322 }
323
324 static uint16_t to_strip(uint16_t screenX, uint16_t screenY)
325 {
326 if (screenX > stripXCoordinates)
327 screenX = stripXCoordinates;
328 if (screenY > stripYCoordinates)
329 screenY = stripYCoordinates;
330
331 return lmpd_constrain<uint16_t>(screenX + screenY * stripXCoordinates, 0, LED_COUNT - 1);
332 }
333
334 void setPixelColorXY(uint16_t x, uint16_t y, COLOR c) { setPixelColor(LedStrip::to_strip(x, y), c); }
335
336 void setPixelColorXY(uint16_t x, uint16_t y, uint32_t c) { setPixelColor(LedStrip::to_strip(x, y), c); }
337
338 /*
339 * Put a value 0 to 255 in to get a color value.
340 * The colours are a transition r -> g -> b -> back to r
341 * Inspired by the Adafruit examples.
342 */
343 uint32_t color_wheel(uint8_t pos)
344 {
345 pos = 255 - pos;
346 if (pos < 85)
347 {
348 return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3);
349 }
350 else if (pos < 170)
351 {
352 pos -= 85;
353 return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3);
354 }
355 else
356 {
357 pos -= 170;
358 return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0);
359 }
360 }
361
362 void blur(uint8_t blur_amount)
363 {
364 if (blur_amount == 0)
365 return; // optimization: 0 means "don't blur"
366 uint8_t keep = 255 - blur_amount;
367 uint8_t seep = blur_amount >> 1;
368 COLOR carryover;
369 carryover.color = 0;
370 for (unsigned i = 0; i < LED_COUNT; i++)
371 {
372 COLOR cur;
373 cur.color = getPixelColor(i);
374 COLOR c = cur;
375 COLOR part = utils::color_fade(c, seep);
376 cur = utils::color_add(utils::color_fade(c, keep), carryover, true);
377 if (i > 0)
378 {
379 c.color = getPixelColor(i - 1);
380 setPixelColor(i - 1, utils::color_add(c, part, true));
381 }
382 setPixelColor(i, cur);
383 carryover = part;
384 }
385 }
386
387 uint32_t getPixelColor(uint16_t n) const { return _colors[lmpd_constrain<uint16_t>(n, 0, LED_COUNT - 1)].color; }
388 uint32_t getPixelColorXY(int16_t x, int16_t y) const { return getPixelColor(LedStrip::to_strip(x, y)); }
389
390 // Adds the specified color with the existing pixel color perserving color
391 // balance.
392 void addPixelColor(uint16_t n, uint32_t color, bool fast = false)
393 {
394 COLOR c1;
395 c1.color = getPixelColor(n);
396 COLOR c2;
397 c2.color = color;
398
399 setPixelColor(n, utils::color_add(c1, c2, fast));
400 }
401
402 void addPixelColorXY(uint16_t x, uint16_t y, uint32_t color, bool fast = false)
403 {
404 addPixelColor(LedStrip::to_strip(x, y), color, fast);
405 }
406
407 // signal the strip that it can display the update
408 void signal_display() { hasSomeChanges = true; }
409
410 uint32_t* get_buffer_ptr(const uint8_t index) { return _buffers[index].data(); }
411
412 void buffer_current_colors(const uint8_t index)
413 {
414 static_assert(sizeof(BufferTy) == sizeof(_colors));
415 memcpy(_buffers[index].data(), _colors, sizeof(_colors));
416 }
417
418 void fill_buffer(const uint8_t index, const uint32_t value)
419 {
420 memset(_buffers[index].data(), value, sizeof(BufferTy));
421 }
422
424 COLOR _colors[LED_COUNT];
425
427 std::array<COLOR, useTemporalDithering ? LED_COUNT : 1> _colorErrors;
428
430 BufferTy _buffers[stripNbBuffers];
431
432private:
433 volatile bool hasSomeChanges;
434
436 volatile uint8_t brightness;
437
439 volatile uint8_t brightnessAtShowTime;
440
442 volatile uint8_t shownCount;
443};
444
445} // namespace physical
446} // namespace lampda
447
448#endif
449
450#endif
protected inheritence to avoid uncontroled hardware calls
Definition: strip.h:49
static uint8_t restore_color_with_brightness(uint8_t colorIn, const uint8_t brightness)
Convert a color to the standard range, assuming it starts as brightness level.
Definition: strip.h:197
BufferTy _buffers[stripNbBuffers]
buffers for computations
Definition: strip.h:430
void setBrightness(uint8_t b)
Brightness is an internal counter.
Definition: strip.h:111
uint8_t getBrightness() const
Brightness is an internal flag.
Definition: strip.h:114
void show_now()
Show the current data, independant of changes.
Definition: strip.h:173
std::array< COLOR, useTemporalDithering ? LED_COUNT :1 > _colorErrors
used for temporal dithering
Definition: strip.h:427
COLOR _colors[LED_COUNT]
store the display colors, with no brightness scaling
Definition: strip.h:424
static COLOR convert_color_with_brigthness(const COLOR &c, const uint8_t brightness, const uint16_t index, COLOR &error)
Convert a color to the correct brightness, with temporal dithering.
Definition: strip.h:266
uint32_t getRawPixelColor(uint16_t n) const
Return the raw color value stored in the send buffer.
Definition: strip.h:133
static std::pair< uint8_t, uint8_t > get_brightness_color_and_error(const uint8_t colorIn, uint16_t errorIn, const uint8_t brightness, const uint16_t index)
Convert a color to the desired brightness level, with an error adjustment.
Definition: strip.h:216
void begin()
Definition: strip.h:129
This class is a lightweight port of the Adafruit_Neopixel library, with compile time buffers to have ...
Definition: strip_impl.h:30
void show(void)
Definition: strip_impl.hpp:134
Program scope.
Definition: control_fixed_modes.hpp:12
Mock of the indexable strip library.
Hardware interface for a strip of LEDs. This is a simple implementation that uses the Arduino library...
Implement a led strip object.
uint8_t neoPixelType
3rd arg to Adafruit_NeoPixel constructor
Definition: strip_impl.h:17
Main interface between the user and the hardware of the lamp.
Definition: lamp_type.hpp:114
Define the system hardware constants.
Use this to convert color to bytes.
Definition: utils.h:128
User defined constants, relative to specific lamp types.
Define useful functions.
Define vectors and rotation matrices, and the possibility to rotate vectors.