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