Public Access
Restructure into a single PlatformIO project with nano and esp32 targets
- Shared BROSE9323 driver in lib/, ESPSERIAL selected via build_flags - Nano firmware in src/nano, ESP32 firmware in src/esp32 - Move WiFi/MQTT credentials to gitignored secrets.h (template provided) - Pin home-assistant-integration to 2.1.0 from the registry - Add esp32_usb environment for first flash / recovery - Add READMEs and CLAUDE.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
0e4d39c47b
18 files changed
+1905
No files matched your search
@@ -0,0 +1,325 @@
|
||||
#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);
|
||||
drawBitmap((width() - 72) / 2, (height() - 16) / 2, _hannio_splash, 72, 16, 1);
|
||||
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__)
|
||||
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));
|
||||
}
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
#ifndef BROSE9323_H
|
||||
#define BROSE9323_H
|
||||
|
||||
#include <Adafruit_GFX.h>
|
||||
|
||||
//#define FLIPDOT_PLCC_ADAPTER
|
||||
// ESPSERIAL is set per target via build_flags in platformio.ini (-D ESPSERIAL for the ESP32)
|
||||
|
||||
const uint8_t _hannio_splash[] PROGMEM = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x70,
|
||||
0x3e, 0x00, 0x39, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x70, 0xff, 0x00, 0x39, 0xff, 0xff, 0xff, 0xff,
|
||||
0xfe, 0x71, 0xf7, 0xc0, 0x39, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x73, 0xc1, 0xc0, 0x38, 0x1f, 0x87,
|
||||
0xf0, 0x7f, 0x06, 0x73, 0x80, 0xe0, 0x38, 0x0f, 0x01, 0xc0, 0x3c, 0x02, 0x77, 0x7f, 0x60, 0x38,
|
||||
0xc7, 0x78, 0xc6, 0x1c, 0x60, 0x77, 0x7f, 0x70, 0x39, 0xe3, 0xfc, 0xcf, 0x9c, 0xf8, 0x76, 0x00,
|
||||
0x30, 0x39, 0xf3, 0xfc, 0xcf, 0x9c, 0xf8, 0x77, 0x7f, 0x70, 0x39, 0xf3, 0x80, 0xcf, 0x9c, 0xf8,
|
||||
0x77, 0x7f, 0x60, 0x39, 0xf3, 0x00, 0xcf, 0x9c, 0xf8, 0x73, 0x80, 0xe0, 0x39, 0xf2, 0x3c, 0xcf,
|
||||
0x9c, 0xf8, 0x73, 0xc1, 0xc0, 0x39, 0xf2, 0x7c, 0xcf, 0x9c, 0xf8, 0x71, 0xf7, 0xc0, 0x39, 0xf2,
|
||||
0x3c, 0xcf, 0x9c, 0xf8, 0x70, 0xff, 0x80, 0x39, 0xf3, 0x00, 0xcf, 0x9c, 0xf8, 0x70, 0x3e, 0x00};
|
||||
|
||||
class BROSE9323 : public Adafruit_GFX
|
||||
{
|
||||
private:
|
||||
uint16_t _flip_time;
|
||||
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;
|
||||
#else
|
||||
uint8_t _active_panel = 255;
|
||||
uint8_t _active_col = 255;
|
||||
uint8_t _active_row = 255;
|
||||
bool _active_data;
|
||||
|
||||
#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);
|
||||
#endif
|
||||
public:
|
||||
BROSE9323(uint8_t, uint8_t, uint8_t, uint16_t ft = 280);
|
||||
#ifdef ESPSERIAL
|
||||
void begin(Stream * = &Serial);
|
||||
#endif
|
||||
void begin(void);
|
||||
void display(bool force = false);
|
||||
void drawPixel(int16_t x, int16_t y, uint16_t color);
|
||||
void fillScreen(uint16_t);
|
||||
void setDirect(bool);
|
||||
void setTiming(uint16_t);
|
||||
#ifndef ESPSERIAL
|
||||
void printBuffer(void);
|
||||
#endif
|
||||
size_t print(const char *ifsh);
|
||||
};
|
||||
#endif //BROSE9323_H
|
||||
@@ -0,0 +1,25 @@
|
||||
# BROSE9323 driver
|
||||
|
||||
An `Adafruit_GFX` driver for Brose flipdot panels, shared by both firmware targets.
|
||||
The constructor is `BROSE9323(width, height, panel_width, flip_time_us = 280)`. This
|
||||
project uses `BROSE9323 display(84, 19, 28)`.
|
||||
|
||||
## Build modes
|
||||
|
||||
| Flag | Used by | Behaviour |
|
||||
|------|---------|-----------|
|
||||
| *(none)* | Nano | Drives the panel through GPIO. Keeps an old and a new frame buffer and flips only the dots that changed. |
|
||||
| `-D ESPSERIAL` | ESP32 | Holds no buffer. `drawPixel`, `fillScreen`, `display` and `setTiming` are sent as serial commands to the Stream passed to `begin(&Serial)`. |
|
||||
| `-D FLIPDOT_PLCC_ADAPTER` | Nano (optional) | Uses the pin mapping of the PLCC adapter board. |
|
||||
|
||||
The flags are set per environment in `platformio.ini` through `build_flags`. Don't
|
||||
`#define` them in the header.
|
||||
|
||||
## Notes
|
||||
|
||||
- In direct mode (`setDirect(true)`) every pixel is flipped right away, without a call to
|
||||
`display()`.
|
||||
- `print()` maps German umlauts (ÄäÖöÜüß) to the code page 437 glyphs used by the GFX
|
||||
font.
|
||||
- On an ATmega168 the old buffer is left out to save RAM. Every `display()` call then
|
||||
flips all dots.
|
||||
Reference in new issue
Block a user