Public Access
- Commands run on '\n' instead of busy-waiting for a byte count, which fixes s/F/T waiting for the next command and P drawing 0xFF garbage - Invalid bitmaps no longer leak their data as commands; bitmap data is streamed so full-screen bitmaps still fit - Store flip time as uint16 with magic marker, validate 50-2000 us - Overflow-safe millis() handling for the periodic refresh - Run the full refresh only after D or when serial is idle, so incoming data is not lost while the redraw blocks - Put the boot refresh measurement behind MEASURE_REFRESH - Fix DEBUG hex output and use F() strings to save RAM - Driver: direct mode now updates the old buffer instead of re-setting the new one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
313 lines
7.0 KiB
C++
313 lines
7.0 KiB
C++
#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
|
|
void BROSE9323::begin(Stream *s)
|
|
{
|
|
stream = s;
|
|
fillScreen(0);
|
|
setCursor(0, 4);
|
|
print("ESP Connected");
|
|
display();
|
|
}
|
|
#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);
|
|
fillScreen(1);
|
|
display();
|
|
fillScreen(0);
|
|
display();
|
|
}
|
|
#endif
|
|
|
|
void BROSE9323::display(bool force)
|
|
{
|
|
#ifdef ESPSERIAL
|
|
stream->print("D\n");
|
|
stream->flush();
|
|
#else
|
|
if (_direct_mode)
|
|
return;
|
|
for (uint8_t x = 0; x < width(); x++)
|
|
{
|
|
_selectPanel(x / _panel_width);
|
|
|
|
_selectColumn(x % _panel_width);
|
|
|
|
for (uint8_t y = 0; y < height(); y++)
|
|
{
|
|
bool b = _new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7));
|
|
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
|
if (!force && (bool)(_old_buffer[y * _buffer_width + x / 8] & (1 << (x & 7))) == b)
|
|
{
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
_selectRow(y);
|
|
|
|
_setData(_new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7)));
|
|
|
|
_strobe();
|
|
}
|
|
}
|
|
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
|
// Store currently displayed content in old buffer
|
|
memcpy(_old_buffer, _new_buffer, _buffer_size);
|
|
#endif
|
|
#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;
|
|
|
|
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 (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);
|
|
|
|
_selectRow(y);
|
|
|
|
_setData(_new_buffer[y * _buffer_width + x / 8] & (1 << (x & 7)));
|
|
|
|
_strobe();
|
|
#if !defined(__AVR_ATmega168P__) && !defined(__AVR_ATmega168PB__) && !defined(__AVR_ATmega168__)
|
|
// The dot is flipped now, so the next display() does not have to flip it again
|
|
if (color)
|
|
_old_buffer[y * _buffer_width + x / 8] |= 1 << (x & 7);
|
|
else
|
|
_old_buffer[y * _buffer_width + x / 8] &= ~(1 << (x & 7));
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void BROSE9323::fillScreen(uint16_t color)
|
|
{
|
|
#ifdef ESPSERIAL
|
|
stream->print(color ? "F1\n" : "F0\n");
|
|
#else
|
|
memset(_new_buffer, color ? 0xFF : 0x00, _buffer_size);
|
|
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);
|
|
#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::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);
|
|
}
|
|
#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);
|
|
} |