Split Nano firmware into modules

- config.h: all constants and debug defines
- command_parser.h/.cpp: CommandParser class, one method per command
- settings.h/.cpp: EEPROM flip time storage
- main.cpp: setup/loop and the periodic full redraw only
- Full redraw now checks frameEnded() after the serial buffer is drained
- Drop the SPI/Adafruit_I2CDevice include workaround (not needed)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-13 00:19:25 +02:00
1 parent 75f677a2eb
commit 80f3f78152
8 files changed
+429 -279

No files matched your search

+258
View File
@@ -0,0 +1,258 @@
#include "command_parser.h"
#include "settings.h"
// ---------------------------------------------------------------------------
// Hex helpers
// ---------------------------------------------------------------------------
static bool isHex(char c)
{
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
static uint8_t hex2dec(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
return 0;
}
static uint8_t hex2byte(const char *s)
{
return (hex2dec(s[0]) << 4) | hex2dec(s[1]);
}
static bool isBitmapCommand(char c)
{
return c == 'b' || c == 'B';
}
// Length of a bitmap header: BxxyywwhhC
static const uint8_t BITMAP_HEADER_LEN = 10;
// ---------------------------------------------------------------------------
// Input handling
// ---------------------------------------------------------------------------
CommandParser::CommandParser(BROSE9323 &display, Print &reply) : _display(display), _reply(reply)
{
}
void CommandParser::feed(char c)
{
if (_bitmap_active)
{
if (c == '\n')
_bitmap_active = false;
else
_bitmapData(c);
return;
}
if (c == '\n')
{
if (!_discard_line)
_runLine();
_line_len = 0;
_discard_line = false;
return;
}
if (c == '\r' || _discard_line)
return;
if (_line_len >= LINE_MAX)
{
_error(F("Line too long"));
_discard_line = true;
return;
}
_line[_line_len++] = c;
_frame_ended = false;
// Bitmap header complete: draw the pixel data as it arrives
if (_line_len == BITMAP_HEADER_LEN && isBitmapCommand(_line[0]))
{
_line_len = 0;
if (!_bitmapStart())
_discard_line = true;
}
}
bool CommandParser::frameEnded() const
{
return _frame_ended;
}
// Run one complete command line (without the '\n')
void CommandParser::_runLine()
{
if (_line_len == 0)
return;
_line[_line_len] = '\0';
char cmd = _line[0];
switch (cmd)
{
case 'b':
case 'B':
// Complete bitmap headers are handled in feed()
_error(F("Incomplete bitmap header"));
break;
case 'd':
case 'D':
_display.display();
_frame_ended = true;
break;
case 'f':
case 'F':
_fill();
break;
case 'p':
case 'P':
_print();
break;
case 'r':
case 'R':
_display.printBuffer();
_reply.flush();
break;
case 's':
case 'S':
_pixel(cmd == 'S');
break;
case 't':
case 'T':
_timing();
break;
default:
#ifdef DEBUG
_reply.print(F("0x"));
_reply.print(cmd >> 4, HEX);
_reply.print(cmd & 0xF, HEX);
_reply.println(F(" - command not known."));
#endif
break;
}
}
void CommandParser::_error(const __FlashStringHelper *msg)
{
_reply.print(F("ERR: "));
_reply.println(msg);
}
// ---------------------------------------------------------------------------
// Commands
// ---------------------------------------------------------------------------
// BxxyywwhhcBITMAP (xx/ww = 8 bit hex x position/width; yy/hh = 8 bit hex y position/height;
// c = 0/1 color; BITMAP = one hex digit per 4 pixels, row by row, width must be a multiple of 4)
bool CommandParser::_bitmapStart()
{
_bitmap_x = hex2byte(_line + 1);
_bitmap_y = hex2byte(_line + 3);
_bitmap_w = hex2byte(_line + 5);
_bitmap_h = hex2byte(_line + 7);
_bitmap_color = _line[9] != '0';
if (_bitmap_w == 0 || _bitmap_w % 4)
{
_error(F("Invalid bitmap width (not dividable by 4)"));
return false;
}
if (_bitmap_h == 0)
{
_error(F("Invalid bitmap height (0)"));
return false;
}
_bitmap_nibble = 0;
_bitmap_active = true;
return true;
}
void CommandParser::_bitmapData(char c)
{
if (!isHex(c))
return;
uint16_t pixel = _bitmap_nibble * 4;
if (pixel >= (uint16_t)_bitmap_w * _bitmap_h)
return; // more data than the bitmap size, ignore
uint8_t b = hex2dec(c);
for (uint8_t i = 0; i < 4; i++)
{
if (b & (0x8 >> i))
{
uint16_t p = pixel + i;
_display.drawPixel(_bitmap_x + p % _bitmap_w, _bitmap_y + p / _bitmap_w, _bitmap_color);
}
}
_bitmap_nibble++;
}
// Fc (c = 0/1 color)
void CommandParser::_fill()
{
if (_line_len < 2)
{
_error(F("Missing color"));
return;
}
_display.fillScreen(_line[1] != '0');
}
// PxxyycsTEXT (xx = 8 bit hex x position; yy = 8 bit hex y position; c = 0/1 color;
// s = 1-9 size; TEXT = string, newline = escaped \n)
void CommandParser::_print()
{
if (_line_len < 7)
{
_error(F("Incomplete print command"));
return;
}
bool color = _line[5] != '0';
_display.setCursor(hex2byte(_line + 1), hex2byte(_line + 3));
_display.setTextColor(color, !color);
_display.setTextSize(_line[6] - '0');
for (uint8_t i = 7; i < _line_len; i++)
{
if (_line[i] == '\\' && _line[i + 1] == 'n')
{
_display.println();
i++;
}
else
{
_display.write(_line[i]);
}
}
}
// sxxyyc / Sxxyyc (xx = 8 bit hex x position; yy = 8 bit hex y position; c = 0/1 color)
// s only changes the buffer, S flips the dot immediately
void CommandParser::_pixel(bool direct)
{
if (_line_len < 6)
{
_error(F("Incomplete pixel command"));
return;
}
_display.setDirect(direct);
_display.drawPixel(hex2byte(_line + 1), hex2byte(_line + 3), hex2dec(_line[5]));
_display.setDirect(false);
}
// Tttt (ttt = decimal flip time in us), stored in EEPROM
void CommandParser::_timing()
{
long t = atol(_line + 1);
if (t < FLIP_TIME_MIN || t > FLIP_TIME_MAX)
{
_error(F("Flip time out of range (50-2000 us)"));
return;
}
_display.setTiming(t);
saveFlipTime(t);
}