#pragma once #include // The order in which display() flips the dots, and how it is spread over time. // The numbers are part of the serial protocol (E command) and the ESP32 settings. enum RefreshEffect : uint8_t { EFFECT_INSTANT = 0, // column by column, left to right, as fast as possible EFFECT_SPARKLE = 1, // the changed dots one at a time, in a scrambled order EFFECT_WIPE = 2, // column by column, left to right EFFECT_RAIN = 3, // row by row, top to bottom EFFECT_CENTER = 4, // column by column, from the middle outwards EFFECT_DIAGONAL = 5, // from the top left to the bottom right corner EFFECT_SPIRAL = 6, // from the outside in, clockwise // The ones below are run by BROSE9323 itself (DotSequence uses column order for them) EFFECT_FLAP = 7, // every dot to yellow (wipe), then the new image (wipe) EFFECT_PUSH = 8, // the new image slides in from the right, pushing the old one out EFFECT_RANDOM = 9, // one of the effects 1-8, chosen for each frame EFFECT_COUNT = 10, }; // Flags for BROSE9323::setEffect() const uint8_t EFFECT_FLAG_SMALL_ONLY = 1; // big changes (> 1/10 of the dots) are instant // Visits every dot of a w x h display exactly once, in the order of an effect. Needs no // buffer: the sparkle order comes from a maximal length LFSR (all states except 0, out // of range values skipped), scrambled with a multiplication modulo the dot count. class DotSequence { public: void begin(uint8_t effect, uint8_t w, uint8_t h, uint16_t seed) { _effect = effect < EFFECT_COUNT ? effect : EFFECT_INSTANT; _w = w; _h = h; _total = (uint16_t)w * h; _pos = 0; _diagonal = 0; _left = 0; _top = 0; _right = w - 1; _bottom = h - 1; _x = 0; _y = 0; _direction = 0; if (_effect != EFFECT_SPARKLE || _total == 0) return; uint8_t bits = 2; while (bits < 16 && ((1UL << bits) - 1) < _total) bits++; // Galois LFSR feedback masks with a maximal period, for 2..16 bits static const uint16_t masks[15] = {0x3, 0x6, 0xC, 0x14, 0x30, 0x60, 0xB8, 0x110, 0x240, 0x500, 0xE08, 0x1C80, 0x3802, 0x6000, 0xD008}; _mask = masks[bits - 2]; _period = (uint16_t)((1UL << bits) - 1); _lfsr = seed % _period + 1; // any state except 0 // A multiplier without common factors with the total keeps the mapping 1:1 _mult = 997 % _total; while (_mult == 0 || gcd(_mult, _total) != 1) _mult++; } // The next dot; false when every dot was visited bool next(uint8_t &x, uint8_t &y) { if (_effect == EFFECT_SPARKLE) { while (_pos < _period) { _pos++; uint16_t index = _lfsr - 1; _lfsr = (_lfsr & 1) ? (_lfsr >> 1) ^ _mask : _lfsr >> 1; if (index < _total) { uint16_t p = (uint32_t)index * _mult % _total; x = p % _w; y = p / _w; return true; } } return false; } if (_pos >= _total) return false; if (_effect == EFFECT_DIAGONAL) { // Diagonal d holds the dots with x + y = d, top to bottom while (true) { if (_y > (int16_t)_diagonal || _y >= _h) { _diagonal++; _y = 0; continue; } uint16_t dx = _diagonal - _y; uint8_t dy = _y++; if (dx < _w) { x = dx; y = dy; _pos++; return true; } } } if (_effect == EFFECT_SPIRAL) { x = _x; y = _y; _pos++; // Walk along the edge of the remaining rectangle, then make it smaller switch (_direction) { case 0: // right along the top if (_x < _right) _x++; else { _top++; _direction = 1; _y++; } break; case 1: // down the right side if (_y < _bottom) _y++; else { _right--; _direction = 2; _x--; } break; case 2: // left along the bottom if (_x > _left) _x--; else { _bottom--; _direction = 3; _y--; } break; default: // up the left side if (_y > _top) _y--; else { _left++; _direction = 0; _x++; } break; } return true; } uint16_t p = _pos++; if (_effect == EFFECT_RAIN) { x = p % _w; y = p / _w; return true; } uint8_t column = p / _h; y = p % _h; if (_effect == EFFECT_CENTER) { uint8_t middle = _w / 2; x = (column & 1) ? middle - (column + 1) / 2 : middle + column / 2; } else x = column; return true; } // Number of next() calls that return true uint16_t total() const { return _total; } private: static uint16_t gcd(uint16_t a, uint16_t b) { while (b) { uint16_t t = a % b; a = b; b = t; } return a; } uint8_t _effect = EFFECT_INSTANT; uint8_t _w = 0, _h = 0; uint16_t _total = 0, _pos = 0; uint16_t _lfsr = 1, _mask = 0, _period = 0, _mult = 1; uint16_t _diagonal = 0; // diagonal int16_t _left = 0, _top = 0, _right = 0, _bottom = 0; // spiral int16_t _x = 0, _y = 0; // spiral; _y also diagonal uint8_t _direction = 0; // spiral };