2026-10-12 23:55:15 +02:00
|
|
|
#include "BROSE9323.h"
|
|
|
|
|
|
|
|
|
|
BROSE9323::BROSE9323(uint8_t w, uint8_t h, uint8_t pw, uint16_t ft) : Adafruit_GFX(w, h),
|
|
|
|
|
_flip_time(ft),
|
|
|
|
|
_panel_width(pw),
|
|
|
|
|
_buffer_width((w + 7) / 8),
|
|
|
|
|
_buffer_size(_buffer_width * h)
|
|
|
|
|
{
|
|
|
|
|
// Buffer width should fit at least [width] bits
|
|
|
|
|
//_buffer_width = (w + 7) / 8;
|
|
|
|
|
|
|
|
|
|
//_buffer_size = _buffer_width * h;
|
|
|
|
|
|
|
|
|
|
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
|
|
|
|
_old_buffer = (uint8_t *)calloc(_buffer_size, sizeof(uint8_t));
|
|
|
|
|
#endif
|
|
|
|
|
_new_buffer = (uint8_t *)calloc(_buffer_size, sizeof(uint8_t));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef ESPSERIAL
|
2026-10-13 01:34:04 +02:00
|
|
|
// Only sets the serial port; the first frame is up to the caller
|
2026-10-12 23:55:15 +02:00
|
|
|
void BROSE9323::begin(Stream *s)
|
|
|
|
|
{
|
|
|
|
|
stream = s;
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
void BROSE9323::begin(void)
|
|
|
|
|
{
|
|
|
|
|
digitalWrite(ADDR_0, 1);
|
|
|
|
|
digitalWrite(ADDR_1, 1);
|
|
|
|
|
digitalWrite(ADDR_2, 1);
|
|
|
|
|
digitalWrite(COL_0, 1);
|
|
|
|
|
digitalWrite(COL_1, 1);
|
|
|
|
|
digitalWrite(COL_2, 1);
|
|
|
|
|
digitalWrite(COL_3, 1);
|
|
|
|
|
digitalWrite(COL_4, 1);
|
|
|
|
|
digitalWrite(ROW_0, 1);
|
|
|
|
|
digitalWrite(ROW_1, 1);
|
|
|
|
|
digitalWrite(ROW_2, 1);
|
|
|
|
|
digitalWrite(ROW_3, 1);
|
|
|
|
|
digitalWrite(ROW_4, 1);
|
|
|
|
|
digitalWrite(ROW_RESET, 1);
|
|
|
|
|
digitalWrite(ROW_SET, 1);
|
|
|
|
|
digitalWrite(COL_DATA, 1);
|
|
|
|
|
digitalWrite(ENABLE, 1);
|
|
|
|
|
pinMode(ADDR_0, OUTPUT);
|
|
|
|
|
pinMode(ADDR_1, OUTPUT);
|
|
|
|
|
pinMode(ADDR_2, OUTPUT);
|
|
|
|
|
pinMode(COL_0, OUTPUT);
|
|
|
|
|
pinMode(COL_1, OUTPUT);
|
|
|
|
|
pinMode(COL_2, OUTPUT);
|
|
|
|
|
pinMode(COL_3, OUTPUT);
|
|
|
|
|
pinMode(COL_4, OUTPUT);
|
|
|
|
|
pinMode(ROW_0, OUTPUT);
|
|
|
|
|
pinMode(ROW_1, OUTPUT);
|
|
|
|
|
pinMode(ROW_2, OUTPUT);
|
|
|
|
|
pinMode(ROW_3, OUTPUT);
|
|
|
|
|
pinMode(ROW_4, OUTPUT);
|
|
|
|
|
pinMode(ROW_RESET, OUTPUT);
|
|
|
|
|
pinMode(ROW_SET, OUTPUT);
|
|
|
|
|
pinMode(COL_DATA, OUTPUT);
|
|
|
|
|
pinMode(ENABLE, OUTPUT);
|
|
|
|
|
_setData(1);
|
|
|
|
|
_setData(0);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
void BROSE9323::display(bool force)
|
|
|
|
|
{
|
|
|
|
|
#ifdef ESPSERIAL
|
|
|
|
|
stream->print("D\n");
|
|
|
|
|
stream->flush();
|
2026-10-13 01:52:34 +02:00
|
|
|
// Shadow copy of what the dots show now (for the web UI)
|
|
|
|
|
memcpy(_old_buffer, _new_buffer, _buffer_size);
|
2026-10-12 23:55:15 +02:00
|
|
|
#else
|
|
|
|
|
if (_direct_mode)
|
|
|
|
|
return;
|
2026-10-15 01:29:01 +02:00
|
|
|
bool all = force || _force_next;
|
|
|
|
|
_force_next = false;
|
|
|
|
|
_drawing = false;
|
|
|
|
|
// An explicit full redraw is always instant (the periodic refresh)
|
|
|
|
|
if (_effect == EFFECT_INSTANT || force)
|
2026-10-13 00:12:28 +02:00
|
|
|
{
|
2026-10-15 01:29:01 +02:00
|
|
|
_animating = false;
|
|
|
|
|
_drawAll(all);
|
2026-10-13 00:12:28 +02:00
|
|
|
}
|
2026-10-15 01:29:01 +02:00
|
|
|
else
|
|
|
|
|
_startEffect(all);
|
2026-10-12 23:55:15 +02:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::drawPixel(int16_t x, int16_t y, uint16_t color)
|
|
|
|
|
{
|
|
|
|
|
#ifdef ESPSERIAL
|
|
|
|
|
if (y >= height() || y < 0 || x >= width() || x < 0)
|
|
|
|
|
return;
|
2026-10-13 01:52:34 +02:00
|
|
|
|
|
|
|
|
// Shadow copy for the web UI; S (direct mode) is visible right away
|
|
|
|
|
if (color)
|
|
|
|
|
_new_buffer[y * _buffer_width + x / 8] |= 1 << (x & 7);
|
|
|
|
|
else
|
|
|
|
|
_new_buffer[y * _buffer_width + x / 8] &= ~(1 << (x & 7));
|
|
|
|
|
if (_direct_mode)
|
|
|
|
|
{
|
|
|
|
|
if (color)
|
|
|
|
|
_old_buffer[y * _buffer_width + x / 8] |= 1 << (x & 7);
|
|
|
|
|
else
|
|
|
|
|
_old_buffer[y * _buffer_width + x / 8] &= ~(1 << (x & 7));
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 23:55:15 +02:00
|
|
|
char s[10];
|
|
|
|
|
sprintf(s, "%c%02x%02x%d\n", _direct_mode ? 'S' : 's', x, y, (bool)color);
|
|
|
|
|
stream->print(s);
|
|
|
|
|
stream->flush();
|
|
|
|
|
#else
|
|
|
|
|
if (y >= height() || y < 0 || x >= width() || x < 0)
|
|
|
|
|
return;
|
2026-10-15 01:29:01 +02:00
|
|
|
if (!_direct_mode)
|
|
|
|
|
_drawing = true;
|
2026-10-12 23:55:15 +02:00
|
|
|
if (color)
|
|
|
|
|
{
|
|
|
|
|
if (_new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7)))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_new_buffer[y * _buffer_width + x / 8] |= 1 << (x & 7);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (~_new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7)))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_new_buffer[y * _buffer_width + x / 8] &= ~(1 << (x & 7));
|
|
|
|
|
}
|
|
|
|
|
if (_direct_mode)
|
|
|
|
|
{
|
|
|
|
|
_selectPanel(x / _panel_width);
|
|
|
|
|
|
|
|
|
|
_selectColumn(x % _panel_width);
|
|
|
|
|
|
2026-10-13 00:06:49 +02:00
|
|
|
// The dot is flipped now, so the next display() does not have to flip it again
|
2026-10-15 01:29:01 +02:00
|
|
|
_flipDot(x, y, _pulses);
|
2026-10-12 23:55:15 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::fillScreen(uint16_t color)
|
|
|
|
|
{
|
|
|
|
|
#ifdef ESPSERIAL
|
2026-10-13 01:52:34 +02:00
|
|
|
memset(_new_buffer, color ? 0xFF : 0x00, _buffer_size);
|
2026-10-12 23:55:15 +02:00
|
|
|
stream->print(color ? "F1\n" : "F0\n");
|
|
|
|
|
#else
|
|
|
|
|
memset(_new_buffer, color ? 0xFF : 0x00, _buffer_size);
|
2026-10-15 01:29:01 +02:00
|
|
|
if (!_direct_mode)
|
|
|
|
|
_drawing = true;
|
2026-10-12 23:55:15 +02:00
|
|
|
if (_direct_mode)
|
|
|
|
|
{
|
|
|
|
|
_setData(color);
|
|
|
|
|
for (uint8_t x = 0; x < width(); x++)
|
|
|
|
|
{
|
|
|
|
|
if (x % _panel_width == 0)
|
|
|
|
|
{
|
|
|
|
|
_selectPanel(x / _panel_width);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_selectColumn(x % _panel_width);
|
|
|
|
|
|
|
|
|
|
for (uint8_t y = 0; y < height(); y++)
|
|
|
|
|
{
|
|
|
|
|
_selectRow(y);
|
|
|
|
|
|
|
|
|
|
_strobe();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
|
|
|
|
memset(_old_buffer, color ? 0xFF : 0x00, _buffer_size);
|
2026-10-13 00:12:28 +02:00
|
|
|
_force_next = false;
|
2026-10-12 23:55:15 +02:00
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::setTiming(uint16_t t)
|
|
|
|
|
{
|
|
|
|
|
#ifdef ESPSERIAL
|
|
|
|
|
stream->write('T');
|
|
|
|
|
stream->print(t);
|
|
|
|
|
stream->write('\n');
|
|
|
|
|
#else
|
|
|
|
|
_flip_time = t;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-15 01:29:01 +02:00
|
|
|
void BROSE9323::setGap(uint16_t t)
|
|
|
|
|
{
|
|
|
|
|
#ifdef ESPSERIAL
|
|
|
|
|
stream->write('G');
|
|
|
|
|
stream->print(t);
|
|
|
|
|
stream->write('\n');
|
|
|
|
|
#else
|
|
|
|
|
_flip_gap = t;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::setPulses(uint8_t n)
|
|
|
|
|
{
|
|
|
|
|
#ifdef ESPSERIAL
|
|
|
|
|
stream->write('N');
|
|
|
|
|
stream->print(n);
|
|
|
|
|
stream->write('\n');
|
|
|
|
|
#else
|
|
|
|
|
_pulses = n ? n : 1;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::setEffect(uint8_t effect, uint16_t time_ms, uint8_t flags)
|
|
|
|
|
{
|
|
|
|
|
#ifdef ESPSERIAL
|
|
|
|
|
stream->write('E');
|
|
|
|
|
stream->print(effect);
|
|
|
|
|
stream->write(',');
|
|
|
|
|
stream->print(time_ms);
|
|
|
|
|
stream->write(',');
|
|
|
|
|
stream->print(flags);
|
|
|
|
|
stream->write('\n');
|
|
|
|
|
#else
|
|
|
|
|
_effect = effect < EFFECT_COUNT ? effect : EFFECT_INSTANT;
|
|
|
|
|
_effect_time = time_ms;
|
|
|
|
|
_effect_flags = flags;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-12 23:55:15 +02:00
|
|
|
void BROSE9323::setDirect(bool d)
|
|
|
|
|
{
|
|
|
|
|
_direct_mode = d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifndef ESPSERIAL
|
|
|
|
|
void BROSE9323::printBuffer(void)
|
|
|
|
|
{
|
|
|
|
|
for (uint8_t y = 0; y < height(); y++)
|
|
|
|
|
{
|
|
|
|
|
for (uint8_t x = 0; x < width(); x++)
|
|
|
|
|
{
|
|
|
|
|
Serial.write(_new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7)) ? '*' : ' ');
|
|
|
|
|
}
|
|
|
|
|
Serial.println();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::_selectColumn(uint8_t col)
|
|
|
|
|
{
|
|
|
|
|
if (_active_col == col)
|
|
|
|
|
return;
|
|
|
|
|
_active_col = col;
|
|
|
|
|
col += 1 + col / 7;
|
|
|
|
|
digitalWrite(COL_0, col & 1);
|
|
|
|
|
digitalWrite(COL_1, col & 2);
|
|
|
|
|
digitalWrite(COL_2, col & 4);
|
|
|
|
|
digitalWrite(COL_3, col & 8);
|
|
|
|
|
digitalWrite(COL_4, col & 16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::_selectPanel(uint8_t panel)
|
|
|
|
|
{
|
|
|
|
|
if (_active_panel == panel)
|
|
|
|
|
return;
|
|
|
|
|
_active_panel = panel;
|
|
|
|
|
digitalWrite(ADDR_0, panel & 1);
|
|
|
|
|
digitalWrite(ADDR_1, panel & 2);
|
|
|
|
|
digitalWrite(ADDR_2, panel & 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::_selectRow(uint8_t row)
|
|
|
|
|
{
|
|
|
|
|
if (_active_row == row)
|
|
|
|
|
return;
|
|
|
|
|
_active_row = row;
|
|
|
|
|
row += row / 7;
|
|
|
|
|
digitalWrite(ROW_0, row & 1);
|
|
|
|
|
digitalWrite(ROW_1, row & 2);
|
|
|
|
|
digitalWrite(ROW_2, row & 4);
|
|
|
|
|
digitalWrite(ROW_3, row & 8);
|
|
|
|
|
digitalWrite(ROW_4, row & 16);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::_setData(bool data)
|
|
|
|
|
{
|
|
|
|
|
if (_active_data == data)
|
|
|
|
|
return;
|
|
|
|
|
_active_data = data;
|
|
|
|
|
if (data)
|
|
|
|
|
{
|
|
|
|
|
digitalWrite(ROW_RESET, 1);
|
|
|
|
|
digitalWrite(ROW_SET, 0);
|
|
|
|
|
digitalWrite(COL_DATA, 0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
digitalWrite(ROW_SET, 1);
|
|
|
|
|
digitalWrite(ROW_RESET, 0);
|
|
|
|
|
digitalWrite(COL_DATA, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::_strobe(void)
|
|
|
|
|
{
|
|
|
|
|
digitalWrite(ENABLE, 0);
|
|
|
|
|
delayMicroseconds(_flip_time);
|
|
|
|
|
digitalWrite(ENABLE, 1);
|
2026-10-15 01:29:01 +02:00
|
|
|
if (_flip_gap)
|
|
|
|
|
delayMicroseconds(_flip_gap);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// What the panel shows now (without an old buffer: unknown, so the opposite of the
|
|
|
|
|
// wanted state, which makes every dot count as changed)
|
|
|
|
|
bool BROSE9323::_shown(uint8_t x, uint8_t y) const
|
|
|
|
|
{
|
|
|
|
|
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
|
|
|
|
return _old_buffer[y * _buffer_width + x / 8] & (1 << (x & 7));
|
|
|
|
|
#else
|
|
|
|
|
return !_wanted(x, y);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BROSE9323::_changed(uint8_t x, uint8_t y) const
|
|
|
|
|
{
|
|
|
|
|
return _shown(x, y) != _wanted(x, y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BROSE9323::_wanted(uint8_t x, uint8_t y) const
|
|
|
|
|
{
|
|
|
|
|
return _new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flip one dot and remember that it is shown
|
|
|
|
|
void BROSE9323::_flipTo(uint8_t x, uint8_t y, bool on, uint8_t pulses)
|
|
|
|
|
{
|
|
|
|
|
_selectPanel(x / _panel_width);
|
|
|
|
|
_selectColumn(x % _panel_width);
|
|
|
|
|
_selectRow(y);
|
|
|
|
|
_setData(on);
|
|
|
|
|
for (uint8_t i = 0; i < pulses; i++)
|
|
|
|
|
_strobe();
|
|
|
|
|
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
|
|
|
|
if (on)
|
|
|
|
|
_old_buffer[y * _buffer_width + x / 8] |= 1 << (x & 7);
|
|
|
|
|
else
|
|
|
|
|
_old_buffer[y * _buffer_width + x / 8] &= ~(1 << (x & 7));
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Flip one dot to its state in the new buffer
|
|
|
|
|
void BROSE9323::_flipDot(uint8_t x, uint8_t y, uint8_t pulses)
|
|
|
|
|
{
|
|
|
|
|
_flipTo(x, y, _wanted(x, y), pulses);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Column by column, as fast as possible. all: also the dots that look unchanged (one
|
|
|
|
|
// pulse each, they are only repeated in case they didn't flip before).
|
|
|
|
|
void BROSE9323::_drawAll(bool all)
|
|
|
|
|
{
|
|
|
|
|
for (uint8_t x = 0; x < width(); x++)
|
|
|
|
|
for (uint8_t y = 0; y < height(); y++)
|
|
|
|
|
{
|
|
|
|
|
bool changed = _changed(x, y);
|
|
|
|
|
if (changed || all)
|
|
|
|
|
_flipDot(x, y, changed ? _pulses : 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void BROSE9323::_startEffect(bool all)
|
|
|
|
|
{
|
|
|
|
|
uint16_t total = (uint16_t)width() * height();
|
|
|
|
|
uint16_t changed = 0;
|
|
|
|
|
for (uint8_t y = 0; y < height(); y++)
|
|
|
|
|
for (uint8_t x = 0; x < width(); x++)
|
|
|
|
|
changed += _changed(x, y);
|
|
|
|
|
if (changed == 0 && !all)
|
|
|
|
|
{
|
|
|
|
|
_animating = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// E.g. only the clock's minutes get the effect, a new page appears at once
|
|
|
|
|
if ((_effect_flags & EFFECT_FLAG_SMALL_ONLY) && (all || changed > total / 10))
|
|
|
|
|
{
|
|
|
|
|
_animating = false;
|
|
|
|
|
_drawAll(all);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t effect = _effect;
|
|
|
|
|
if (effect == EFFECT_RANDOM)
|
|
|
|
|
effect = 1 + micros() % (EFFECT_RANDOM - 1);
|
|
|
|
|
// Pushing needs to know what is shown; after boot that is unknown
|
|
|
|
|
if (all && effect == EFFECT_PUSH)
|
|
|
|
|
effect = EFFECT_WIPE;
|
|
|
|
|
_running_effect = effect;
|
|
|
|
|
_animate_all = all;
|
|
|
|
|
_phase = 0;
|
|
|
|
|
|
|
|
|
|
uint32_t steps;
|
|
|
|
|
if (effect == EFFECT_PUSH)
|
|
|
|
|
{
|
|
|
|
|
// One step per column shift; the dots of a shift are flipped as fast as possible
|
|
|
|
|
_push_shift = 1;
|
|
|
|
|
_push_pos = 0;
|
|
|
|
|
steps = width();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_sequence.begin(effect == EFFECT_FLAP ? EFFECT_WIPE : effect, width(), height(), (uint16_t)micros());
|
|
|
|
|
// Sparkle: only the changed dots take time, so a small change sparkles as long as a big one
|
|
|
|
|
steps = (effect == EFFECT_SPARKLE && !all) ? changed : total;
|
|
|
|
|
if (effect == EFFECT_FLAP)
|
|
|
|
|
steps *= 2;
|
|
|
|
|
}
|
|
|
|
|
_step_time = (uint32_t)_effect_time * 1000 / steps;
|
|
|
|
|
_next_step = micros();
|
|
|
|
|
_animating = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// One dot of the current push step. Shift s (1..width) shows old columns s.. on the left
|
|
|
|
|
// and new columns 0..s-1 on the right: each column takes over its right neighbour (not
|
|
|
|
|
// changed yet in this step, as the columns go left to right), the last one a new column.
|
|
|
|
|
// False when the last shift is done.
|
|
|
|
|
bool BROSE9323::_pushDot(void)
|
|
|
|
|
{
|
|
|
|
|
uint8_t w = width(), h = height();
|
|
|
|
|
uint8_t x = _push_pos / h, y = _push_pos % h;
|
|
|
|
|
bool target = x + 1 < w ? _shown(x + 1, y) : _wanted(_push_shift - 1, y);
|
|
|
|
|
if (_shown(x, y) != target)
|
|
|
|
|
_flipTo(x, y, target, _push_shift == w ? _pulses : 1);
|
|
|
|
|
if (++_push_pos < (uint16_t)w * h)
|
|
|
|
|
return true;
|
|
|
|
|
_push_pos = 0;
|
|
|
|
|
_next_step += _step_time;
|
|
|
|
|
return ++_push_shift <= w;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BROSE9323::busy(void) const
|
|
|
|
|
{
|
|
|
|
|
return _animating && !_drawing;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool BROSE9323::update(void)
|
|
|
|
|
{
|
|
|
|
|
// Wait for the D that ends the frame being drawn, it starts the effect again
|
|
|
|
|
if (!_animating || _drawing)
|
|
|
|
|
return busy();
|
|
|
|
|
|
|
|
|
|
// At most about 1 ms per call, so loop() keeps reading the serial port
|
|
|
|
|
uint32_t start = micros();
|
|
|
|
|
while ((int32_t)(micros() - _next_step) >= 0 && micros() - start < 1000)
|
|
|
|
|
{
|
|
|
|
|
if (_running_effect == EFFECT_PUSH)
|
|
|
|
|
{
|
|
|
|
|
if (!_pushDot())
|
|
|
|
|
{
|
|
|
|
|
_animating = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint8_t x, y;
|
|
|
|
|
if (!_sequence.next(x, y))
|
|
|
|
|
{
|
|
|
|
|
if (_running_effect == EFFECT_FLAP && _phase == 0)
|
|
|
|
|
{
|
|
|
|
|
// Everything is yellow now: reveal the new image
|
|
|
|
|
_phase = 1;
|
|
|
|
|
_sequence.begin(EFFECT_WIPE, width(), height(), 0);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
_animating = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// Flap: first every dot to yellow (one pulse), then the new image
|
|
|
|
|
bool yellow_phase = _running_effect == EFFECT_FLAP && _phase == 0;
|
|
|
|
|
bool target = yellow_phase ? true : _wanted(x, y);
|
|
|
|
|
bool changed = _shown(x, y) != target;
|
|
|
|
|
if (!changed && !_animate_all)
|
|
|
|
|
{
|
|
|
|
|
if (_running_effect == EFFECT_SPARKLE)
|
|
|
|
|
continue; // unchanged dots take no time
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
_flipTo(x, y, target, changed && !yellow_phase ? _pulses : 1);
|
|
|
|
|
_next_step += _step_time;
|
|
|
|
|
}
|
|
|
|
|
return busy();
|
2026-10-12 23:55:15 +02:00
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
size_t BROSE9323::print(const char *ifsh)
|
|
|
|
|
{
|
|
|
|
|
// replace these characters
|
|
|
|
|
/*
|
|
|
|
|
UMLAUT:
|
|
|
|
|
Ä \x8E
|
|
|
|
|
ä \x84
|
|
|
|
|
Ö \x99
|
|
|
|
|
ö \x94
|
|
|
|
|
Ü \x9A
|
|
|
|
|
ü \x81
|
|
|
|
|
ß \xE0
|
|
|
|
|
*/
|
|
|
|
|
String temp = ifsh;
|
|
|
|
|
temp.replace("Ä", "\x8E");
|
|
|
|
|
temp.replace("ä", "\x84");
|
|
|
|
|
temp.replace("Ö", "\x99");
|
|
|
|
|
temp.replace("ö", "\x94");
|
|
|
|
|
temp.replace("Ü", "\x9A");
|
|
|
|
|
temp.replace("ü", "\x81");
|
|
|
|
|
temp.replace("ß", "\xE0");
|
|
|
|
|
|
|
|
|
|
ifsh = temp.c_str();
|
|
|
|
|
|
|
|
|
|
return Adafruit_GFX::print(ifsh);
|
2026-10-13 01:52:34 +02:00
|
|
|
}
|
|
|
|
|
bool BROSE9323::shownPixel(int16_t x, int16_t y) const
|
|
|
|
|
{
|
|
|
|
|
if (y >= height() || y < 0 || x >= width() || x < 0)
|
|
|
|
|
return false;
|
|
|
|
|
#if defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PB__) || defined(__AVR_ATmega168__)
|
|
|
|
|
return _new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7));
|
|
|
|
|
#else
|
|
|
|
|
return _old_buffer[y * _buffer_width + x / 8] & (1 << (x & 7));
|
|
|
|
|
#endif
|
|
|
|
|
}
|