#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 // Only sets the serial port; the first frame is up to the caller 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, bool instant) { #ifdef ESPSERIAL stream->print(instant ? "D1\n" : "D\n"); stream->flush(); // Shadow copy of what the dots show now (for the web UI) memcpy(_old_buffer, _new_buffer, _buffer_size); #else if (_direct_mode) return; 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 || instant) { _animating = false; _drawAll(all); } else _startEffect(all); #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; // 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)); } 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; if (!_direct_mode) _drawing = true; 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); // The dot is flipped now, so the next display() does not have to flip it again _flipDot(x, y, _pulses); } #endif } void BROSE9323::fillScreen(uint16_t color) { #ifdef ESPSERIAL memset(_new_buffer, color ? 0xFF : 0x00, _buffer_size); stream->print(color ? "F1\n" : "F0\n"); #else memset(_new_buffer, color ? 0xFF : 0x00, _buffer_size); if (!_direct_mode) _drawing = true; 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); _force_next = false; #endif } #endif } void BROSE9323::setTiming(uint16_t t) { #ifdef ESPSERIAL stream->write('T'); stream->print(t); stream->write('\n'); #else _flip_time = t; #endif } 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 } 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); 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(); } #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); } 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 }