Files
Flipdot-display/lib/BROSE9323/BROSE9323.h
T
mangavalkandClaude Opus 5.5 090c2bf8ae Fix shifted pictures: non-blocking Nano drawing, frame check, refresh settings
The Nano's periodic full redraw blocked for ~0.8 s. A frame that arrived
meanwhile overflowed its 64-byte serial buffer; lost bytes in a bitmap
shift the rest of the image (picture moved up), and diff frames never
repaired it.

- Nano: all drawing (frames, effects, the dot refresh) runs from update()
  in ~1 ms slices; the refresh pauses while a frame is received or shown
- New commands: C (reply with the image checksum) and I (refresh minutes,
  EEPROM)
- ESP32: NanoMonitor asks for the checksum after frames and sends the whole
  image again on a mismatch, missing answer or Nano restart; counts ERR
  replies. Periodic whole-image resync.
- Settings: "Dot refresh" and "Image resync" intervals (settings v5,
  v4 records are migrated)
- System tab: loop timing, free memory, frame check counts
- test_driver: the real driver against a simulated panel

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 15:20:26 +02:00

167 lines
6.6 KiB
C++

#ifndef BROSE9323_H
#define BROSE9323_H
#include <Adafruit_GFX.h>
#include "buffer_crc.h"
#include "dot_sequence.h"
#ifdef ESPSERIAL
#include "frame_commands.h"
#endif
//#define FLIPDOT_PLCC_ADAPTER
// ESPSERIAL is set per target via build_flags in platformio.ini (-D ESPSERIAL for the ESP32)
class BROSE9323 : public Adafruit_GFX
{
private:
uint16_t _flip_time;
uint16_t _flip_gap = 0; // pause after each pulse in us, lets the coil supply recover
uint8_t _pulses = 1; // pulses for each dot that changed
uint8_t _effect = EFFECT_INSTANT;
uint16_t _effect_time = 1500; // ms, how long an effect takes
uint8_t _effect_flags = 0; // EFFECT_FLAG_...
const uint8_t _panel_width;
const uint8_t _buffer_width;
const uint16_t _buffer_size;
uint8_t *_old_buffer = NULL;
uint8_t *_new_buffer = NULL;
bool _direct_mode = false;
#ifdef ESPSERIAL
Stream *stream;
uint32_t _frames = 0; // frames sent (display() and showBitmap() calls that sent something)
#else
uint8_t _active_panel = 255;
uint8_t _active_col = 255;
uint8_t _active_row = 255;
bool _active_data;
// After boot the physical dot state is unknown, so the first display() redraws everything
bool _force_next = true;
// A running effect (display() with an effect other than EFFECT_INSTANT)
DotSequence _sequence;
bool _animating = false;
uint8_t _running_effect = EFFECT_INSTANT; // _effect, or the one chosen for EFFECT_RANDOM
uint8_t _phase = 0; // EFFECT_FLAP: 0 = to yellow, 1 = the new image
uint8_t _push_shift = 0; // EFFECT_PUSH: column shift 1..width
uint16_t _push_pos = 0; // EFFECT_PUSH: dot within the shift
bool _animate_all = false; // flip every dot, not only the changed ones
bool _drawing = false; // the buffer changed since the last display(): pause
uint32_t _step_time = 0; // us per step
uint32_t _next_step = 0; // micros() of the next step
// The full redraw (startRefresh()): one pulse for every dot, column by column, while
// no frame is being received or drawn
bool _refreshing = false;
uint16_t _refresh_pos = 0;
#ifndef FLIPDOT_PLCC_ADAPTER
const uint8_t ENABLE = 7,
ADDR_0 = 3,
ADDR_1 = 5,
ADDR_2 = 6,
COL_DATA = 4,
COL_0 = 8,
COL_1 = 10,
COL_2 = 9,
COL_3 = 11,
COL_4 = 12,
ROW_0 = A3,
ROW_1 = A4,
ROW_2 = A2,
ROW_3 = A0,
ROW_4 = A1,
ROW_RESET = 13,
ROW_SET = A5;
#else
const uint8_t RELAY = 2,
ENABLE = 3,
ADDR_0 = 7,
ADDR_1 = 6,
ADDR_2 = 5,
COL_DATA = 4,
COL_0 = 12,
COL_1 = 11,
COL_2 = 10,
COL_3 = 9,
COL_4 = 8,
ROW_0 = 13, //1 A1 CHECK
ROW_1 = A0, //2 A2
ROW_2 = A1, //4 A3
ROW_3 = A2, //8 13 CHECK
ROW_4 = A3, //F A0
ROW_RESET = A4,
ROW_SET = A5;
#endif
void _selectColumn(uint8_t);
void _selectPanel(uint8_t);
void _selectRow(uint8_t);
void _setData(bool);
void _strobe(void);
bool _shown(uint8_t x, uint8_t y) const;
bool _changed(uint8_t x, uint8_t y) const;
bool _wanted(uint8_t x, uint8_t y) const;
void _flipTo(uint8_t x, uint8_t y, bool on, uint8_t pulses);
void _flipDot(uint8_t x, uint8_t y, uint8_t pulses);
bool _pushDot(void);
void _startEffect(bool all, bool instant);
void _refreshStep(uint32_t start);
#endif
public:
BROSE9323(uint8_t, uint8_t, uint8_t, uint16_t ft = 280);
#ifdef ESPSERIAL
void begin(Stream * = &Serial);
// Shows a full screen bitmap (drawBitmap format) and ends the frame (D, or D1 when
// instant). changes_only: only the dots that differ from the last frame are sent
// (frame_commands.h); otherwise the whole screen. Returns the characters sent (0:
// nothing changed, nothing sent).
size_t showBitmap(const uint8_t *bitmap, bool changes_only, bool instant = false);
// Frames sent so far (display() and showBitmap() calls that sent something)
uint32_t framesSent(void) const { return _frames; }
// How often the Nano flips every dot again in minutes, 0 = never (I command)
void setRefreshInterval(uint16_t minutes);
#endif
void begin(void);
// Shows the buffer: the changed dots, all with force. instant: without the refresh
// effect (frames of moving content, e.g. scrolling text). ESP32: sends D or D1.
// Nano: never blocks, the dots are flipped from update() (also for instant frames,
// as fast as possible), so the serial port is read while they flip.
void display(bool force = false, bool instant = false);
void drawPixel(int16_t x, int16_t y, uint16_t color);
void fillScreen(uint16_t);
void setDirect(bool);
void setTiming(uint16_t);
// Pause after each flip pulse in us
void setGap(uint16_t);
// Pulses for each changed dot (a second pulse catches dots that didn't flip). Full
// redraws use one pulse per dot.
void setPulses(uint8_t);
// How display() shows a new frame (RefreshEffect), how long it takes in ms (pushing
// can take longer: it flips many dots per step) and EFFECT_FLAG_... flags.
// Effects run in steps from update(), so call that often (every loop).
void setEffect(uint8_t effect, uint16_t time_ms, uint8_t flags = 0);
#ifndef ESPSERIAL
// Flips the next dots of a frame or of the full redraw, at most about 1 ms per call
// (plus one dot), so loop() keeps reading the serial port. Returns busy().
bool update(void);
// A frame is being shown (paused while a new frame is being drawn into the buffer)
bool busy(void) const;
// Flip every dot once more (fixes dots that didn't flip), from update() whenever no
// frame is being received or shown. Starts again when one is running.
void startRefresh(void);
bool refreshing(void) const { return _refreshing; }
#endif
#ifndef ESPSERIAL
void printBuffer(void);
#endif
size_t print(const char *ifsh);
// State of a dot as of the last display() call (ESP32: shadow copy for the web UI)
bool shownPixel(int16_t x, int16_t y) const;
// bufferCrc() of the drawn image (Nano: what it received, the C command; ESP32: what
// it sent)
uint16_t crc(void) const { return bufferCrc(_new_buffer, width(), height()); }
};
#endif //BROSE9323_H