Files
Flipdot-display/lib/BROSE9323/dot_sequence.h
T

207 lines
4.9 KiB
C++
Raw Normal View History

#pragma once
#include <stdint.h>
// 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
};