Public Access
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:
1 parent
75f677a2eb
commit
80f3f78152
8 files changed
+429
-279
No files matched your search
@@ -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);
|
||||
}
|
||||
Reference in new issue
Block a user