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
+428
-278
No files matched your search
@@ -42,6 +42,8 @@ See README.md for the overview.
|
||||
|
||||
## Nano serial parser (`src/nano/main.cpp`)
|
||||
|
||||
- Files: `main.cpp` (setup/loop, refresh), `config.h` (all constants and debug
|
||||
defines), `command_parser.*` (protocol, one method per command), `settings.*` (EEPROM).
|
||||
- Line based and non-blocking: bytes are collected into a 64-character buffer and a
|
||||
command runs when `\n` arrives. Bitmap (`B`) pixel data is the exception. It is drawn
|
||||
while it arrives, so it can be longer than the buffer. Keep `loop()` free of busy-waits.
|
||||
@@ -53,7 +55,7 @@ See README.md for the overview.
|
||||
- The Nano only replies `READY` (at boot) and `ERR: ...`. There is no per-frame
|
||||
acknowledgement. Whether the Nano's TX is wired to the ESP's RX is unconfirmed, so every
|
||||
wait for a reply needs a timeout.
|
||||
- To test the parser without hardware, compile `main.cpp` with g++ (Strawberry Perl
|
||||
- To test the parser without hardware, compile all `src/nano/*.cpp` files with g++ (Strawberry Perl
|
||||
provides it at `C:\Strawberry\c\bin`) and mock headers for `Arduino.h`, `EEPROM.h` and
|
||||
`BROSE9323.h`.
|
||||
|
||||
|
||||
+12
-1
@@ -6,6 +6,17 @@ The Nano is connected directly to the Brose panel electronics. It keeps an 84 ×
|
||||
buffer and takes drawing commands over serial (115200 baud). It has no clock or network
|
||||
logic.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Contents |
|
||||
|------|----------|
|
||||
| `main.cpp` | `setup()` / `loop()`, feeds serial bytes to the parser, periodic full redraw |
|
||||
| `config.h` | All settings: debug options, display size, flip time limits, timings |
|
||||
| `command_parser.h/.cpp` | `CommandParser`: the serial protocol, one method per command |
|
||||
| `settings.h/.cpp` | Flip time stored in EEPROM |
|
||||
|
||||
The display driver itself is in [lib/BROSE9323](../../lib/BROSE9323/README.md).
|
||||
|
||||
## Behaviour
|
||||
|
||||
- **Boot:** the Nano loads the flip time from EEPROM, sets up the pins and sends `READY`.
|
||||
@@ -24,7 +35,7 @@ logic.
|
||||
|
||||
### Debug options
|
||||
|
||||
Uncomment these at the top of `main.cpp`:
|
||||
Uncomment these in `config.h`:
|
||||
|
||||
| Define | Effect |
|
||||
|--------|--------|
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "BROSE9323.h"
|
||||
#include "config.h"
|
||||
|
||||
// Parses the line based serial protocol from the ESP32 (see README.md) and draws on the
|
||||
// display. Bytes are fed one by one; a command runs when its '\n' arrives, so nothing
|
||||
// here blocks while waiting for data.
|
||||
class CommandParser
|
||||
{
|
||||
public:
|
||||
CommandParser(BROSE9323 &display, Print &reply);
|
||||
|
||||
// Handle one received byte
|
||||
void feed(char c);
|
||||
|
||||
// True when the last command was D (end of a frame) and no new command has started
|
||||
bool frameEnded() const;
|
||||
|
||||
private:
|
||||
BROSE9323 &_display;
|
||||
Print &_reply;
|
||||
|
||||
char _line[LINE_MAX + 1];
|
||||
uint8_t _line_len = 0;
|
||||
bool _discard_line = false; // skip everything until the next '\n'
|
||||
bool _frame_ended = false;
|
||||
|
||||
// Bitmap (B) pixel data is drawn while it arrives instead of being buffered
|
||||
bool _bitmap_active = false;
|
||||
uint8_t _bitmap_x, _bitmap_y, _bitmap_w, _bitmap_h;
|
||||
bool _bitmap_color;
|
||||
uint16_t _bitmap_nibble;
|
||||
|
||||
void _runLine();
|
||||
void _error(const __FlashStringHelper *msg);
|
||||
|
||||
// Commands
|
||||
bool _bitmapStart();
|
||||
void _bitmapData(char c);
|
||||
void _fill();
|
||||
void _print();
|
||||
void _pixel(bool direct);
|
||||
void _timing();
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Debug options (uncomment to enable)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Print unknown command bytes on the serial port
|
||||
//#define DEBUG
|
||||
|
||||
// Time a full redraw at boot and print the duration in ms
|
||||
//#define MEASURE_REFRESH
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Display
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Three Brose panels of 28 x 19 next to each other
|
||||
const uint8_t DISPLAY_WIDTH = 84;
|
||||
const uint8_t DISPLAY_HEIGHT = 19;
|
||||
const uint8_t PANEL_WIDTH = 28;
|
||||
|
||||
// Flip pulse length in us, stored in EEPROM and changed with the T command
|
||||
const uint16_t FLIP_TIME_DEFAULT = 200;
|
||||
const uint16_t FLIP_TIME_MIN = 50;
|
||||
const uint16_t FLIP_TIME_MAX = 2000;
|
||||
|
||||
// Full redraw interval, fixes dots that did not flip
|
||||
const uint32_t REFRESH_TIME = 120000UL; // 2 min
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Serial link to the ESP32
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const uint32_t SERIAL_BAUD = 115200;
|
||||
|
||||
// Serial line quiet for this long: safe to do a due full redraw without waiting for a D
|
||||
const uint32_t IDLE_TIME = 2000UL;
|
||||
|
||||
// Longest command line (bitmap data is streamed and does not count)
|
||||
const uint8_t LINE_MAX = 64;
|
||||
+29
-276
@@ -1,257 +1,35 @@
|
||||
// Arduino Nano firmware: drives the Brose flipdot panels and draws what the ESP32 sends
|
||||
// over serial. See README.md for the protocol and wiring.
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "BROSE9323.h"
|
||||
// fixes
|
||||
#include <SPI.h>
|
||||
#include <Adafruit_I2CDevice.h>
|
||||
#include "command_parser.h"
|
||||
#include "config.h"
|
||||
#include "settings.h"
|
||||
|
||||
#include <EEPROM.h>
|
||||
|
||||
//#define DEBUG
|
||||
// Print how long a full redraw takes (in ms) at startup
|
||||
//#define MEASURE_REFRESH
|
||||
|
||||
// Flip pulse length in us, stored in EEPROM and changed with the T command
|
||||
const uint16_t FLIP_TIME_DEFAULT = 200;
|
||||
const uint16_t FLIP_TIME_MIN = 50;
|
||||
const uint16_t FLIP_TIME_MAX = 2000;
|
||||
|
||||
// EEPROM layout: magic marker (marks the stored value as valid), then the flip time
|
||||
const int EEPROM_MAGIC_ADDR = 0;
|
||||
const int EEPROM_FLIP_TIME_ADDR = 2;
|
||||
const uint16_t EEPROM_MAGIC = 0xF11D;
|
||||
|
||||
// Full redraw interval, fixes dots that did not flip
|
||||
const uint32_t REFRESH_TIME = 120000UL; // 2 min
|
||||
// Serial line quiet for this long: safe to do a due full redraw without waiting for a D
|
||||
const uint32_t IDLE_TIME = 2000UL;
|
||||
|
||||
// Longest command line (bitmap data is streamed and does not count)
|
||||
const uint8_t LINE_MAX = 64;
|
||||
|
||||
BROSE9323 display(84, 19, 28, FLIP_TIME_DEFAULT);
|
||||
|
||||
char line[LINE_MAX + 1];
|
||||
uint8_t line_len = 0;
|
||||
bool discard_line = false; // skip everything until the next '\n'
|
||||
|
||||
// Bitmap (B) pixel data is drawn while it arrives instead of being buffered
|
||||
bool bitmap_active = false;
|
||||
uint8_t bitmap_x, bitmap_y, bitmap_w, bitmap_h;
|
||||
bool bitmap_color;
|
||||
uint16_t bitmap_nibble;
|
||||
BROSE9323 display(DISPLAY_WIDTH, DISPLAY_HEIGHT, PANEL_WIDTH, FLIP_TIME_DEFAULT);
|
||||
CommandParser parser(display, Serial);
|
||||
|
||||
uint32_t last_refresh;
|
||||
uint32_t last_rx;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool isHex(char c)
|
||||
{
|
||||
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
|
||||
}
|
||||
|
||||
uint8_t hex2byte(const char *s)
|
||||
{
|
||||
return (hex2dec(s[0]) << 4) | hex2dec(s[1]);
|
||||
}
|
||||
|
||||
void printError(const __FlashStringHelper *msg)
|
||||
{
|
||||
Serial.print(F("ERR: "));
|
||||
Serial.println(msg);
|
||||
}
|
||||
|
||||
uint16_t loadFlipTime()
|
||||
{
|
||||
uint16_t magic, t;
|
||||
EEPROM.get(EEPROM_MAGIC_ADDR, magic);
|
||||
EEPROM.get(EEPROM_FLIP_TIME_ADDR, t);
|
||||
if (magic != EEPROM_MAGIC || t < FLIP_TIME_MIN || t > FLIP_TIME_MAX)
|
||||
return FLIP_TIME_DEFAULT;
|
||||
return t;
|
||||
}
|
||||
|
||||
void saveFlipTime(uint16_t t)
|
||||
{
|
||||
// put() only writes bytes that changed
|
||||
EEPROM.put(EEPROM_FLIP_TIME_ADDR, t);
|
||||
EEPROM.put(EEPROM_MAGIC_ADDR, EEPROM_MAGIC);
|
||||
}
|
||||
|
||||
// Force a full redraw every REFRESH_TIME. Only called when no data is expected,
|
||||
// because the redraw blocks long enough to overflow the 64 byte serial buffer.
|
||||
// Force a full redraw every REFRESH_TIME. The redraw blocks long enough to overflow the
|
||||
// 64 byte serial buffer, so it only runs when no data is expected: right after the ESP
|
||||
// finished a frame, or when the serial line is idle.
|
||||
void refreshIfDue()
|
||||
{
|
||||
if (millis() - last_refresh >= REFRESH_TIME)
|
||||
bool quiet = parser.frameEnded() || millis() - last_rx >= IDLE_TIME;
|
||||
if (quiet && millis() - last_refresh >= REFRESH_TIME)
|
||||
{
|
||||
display.display(true);
|
||||
last_refresh = millis();
|
||||
}
|
||||
}
|
||||
|
||||
// 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 startBitmap()
|
||||
{
|
||||
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)
|
||||
{
|
||||
printError(F("Invalid bitmap width (not dividable by 4)"));
|
||||
return false;
|
||||
}
|
||||
if (bitmap_h == 0)
|
||||
{
|
||||
printError(F("Invalid bitmap height (0)"));
|
||||
return false;
|
||||
}
|
||||
bitmap_nibble = 0;
|
||||
bitmap_active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void 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++;
|
||||
}
|
||||
|
||||
// Handle one complete command line (without the '\n')
|
||||
void processLine()
|
||||
{
|
||||
if (line_len == 0)
|
||||
return;
|
||||
line[line_len] = '\0';
|
||||
|
||||
char cmd = line[0];
|
||||
switch (cmd)
|
||||
{
|
||||
// Bitmap header shorter than 10 characters (complete headers are handled in loop())
|
||||
case 'b':
|
||||
case 'B':
|
||||
printError(F("Incomplete bitmap header"));
|
||||
break;
|
||||
// Display
|
||||
case 'd':
|
||||
case 'D': // D
|
||||
display.display();
|
||||
// The ESP sends D at the end of a frame, so the line is quiet now
|
||||
refreshIfDue();
|
||||
break;
|
||||
// Fill Screen
|
||||
case 'f':
|
||||
case 'F': // Fc (c = 0/1 color)
|
||||
if (line_len < 2)
|
||||
{
|
||||
printError(F("Missing color"));
|
||||
break;
|
||||
}
|
||||
display.fillScreen(line[1] != '0');
|
||||
break;
|
||||
// Print Text
|
||||
case 'p':
|
||||
case 'P': // 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)
|
||||
{
|
||||
if (line_len < 7)
|
||||
{
|
||||
printError(F("Incomplete print command"));
|
||||
break;
|
||||
}
|
||||
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]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Return buffer (show pixels on console)
|
||||
case 'r':
|
||||
case 'R': // R
|
||||
display.printBuffer();
|
||||
Serial.flush();
|
||||
break;
|
||||
// Set Pixel (S = flip immediately, s = buffer only)
|
||||
case 'S':
|
||||
case 's': // Sxxyyc (xx = 8 bit hex x position; yy = 8 bit hex y position; c = 0/1 color)
|
||||
if (line_len < 6)
|
||||
{
|
||||
printError(F("Incomplete pixel command"));
|
||||
break;
|
||||
}
|
||||
display.setDirect(cmd == 'S');
|
||||
display.drawPixel(hex2byte(line + 1), hex2byte(line + 3), hex2dec(line[5]));
|
||||
display.setDirect(false);
|
||||
break;
|
||||
// Set Timing
|
||||
case 'T':
|
||||
case 't': // Tttt (ttt = decimal flip time in us)
|
||||
{
|
||||
long t = atol(line + 1);
|
||||
if (t < FLIP_TIME_MIN || t > FLIP_TIME_MAX)
|
||||
{
|
||||
printError(F("Flip time out of range (50-2000 us)"));
|
||||
break;
|
||||
}
|
||||
display.setTiming(t);
|
||||
saveFlipTime(t);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
#ifdef DEBUG
|
||||
Serial.print("0x");
|
||||
Serial.print(cmd >> 4, HEX);
|
||||
Serial.print(cmd & 0xF, HEX);
|
||||
Serial.println(" - command not known.");
|
||||
Serial.flush();
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(115200);
|
||||
|
||||
display.setTiming(loadFlipTime());
|
||||
display.begin();
|
||||
|
||||
#ifdef MEASURE_REFRESH
|
||||
void measureRefresh()
|
||||
{
|
||||
delay(1000);
|
||||
|
||||
display.fillScreen(1);
|
||||
@@ -261,6 +39,18 @@ void setup()
|
||||
display.display();
|
||||
uint32_t t2 = millis();
|
||||
Serial.println(t2 - t1);
|
||||
}
|
||||
#endif
|
||||
|
||||
void setup()
|
||||
{
|
||||
Serial.begin(SERIAL_BAUD);
|
||||
|
||||
display.setTiming(loadFlipTime());
|
||||
display.begin();
|
||||
|
||||
#ifdef MEASURE_REFRESH
|
||||
measureRefresh();
|
||||
#endif
|
||||
|
||||
last_refresh = millis();
|
||||
@@ -274,46 +64,9 @@ void loop()
|
||||
{
|
||||
while (Serial.available())
|
||||
{
|
||||
char c = Serial.read();
|
||||
last_rx = millis();
|
||||
|
||||
if (bitmap_active)
|
||||
{
|
||||
if (c == '\n')
|
||||
bitmap_active = false;
|
||||
else
|
||||
bitmapData(c);
|
||||
continue;
|
||||
parser.feed(Serial.read());
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
if (!discard_line)
|
||||
processLine();
|
||||
line_len = 0;
|
||||
discard_line = false;
|
||||
continue;
|
||||
}
|
||||
if (c == '\r' || discard_line)
|
||||
continue;
|
||||
if (line_len >= LINE_MAX)
|
||||
{
|
||||
printError(F("Line too long"));
|
||||
discard_line = true;
|
||||
continue;
|
||||
}
|
||||
line[line_len++] = c;
|
||||
|
||||
// Bitmap header complete: draw the pixel data as it arrives
|
||||
if (line_len == 10 && (line[0] == 'b' || line[0] == 'B'))
|
||||
{
|
||||
line_len = 0;
|
||||
if (!startBitmap())
|
||||
discard_line = true;
|
||||
}
|
||||
}
|
||||
|
||||
// No frames coming in (e.g. ESP idle): do the full redraw now if it is due
|
||||
if (millis() - last_rx >= IDLE_TIME)
|
||||
refreshIfDue();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "settings.h"
|
||||
|
||||
#include <EEPROM.h>
|
||||
|
||||
#include "config.h"
|
||||
|
||||
// EEPROM layout: magic marker (marks the stored value as valid), then the flip time
|
||||
const int EEPROM_MAGIC_ADDR = 0;
|
||||
const int EEPROM_FLIP_TIME_ADDR = 2;
|
||||
const uint16_t EEPROM_MAGIC = 0xF11D;
|
||||
|
||||
uint16_t loadFlipTime()
|
||||
{
|
||||
uint16_t magic, t;
|
||||
EEPROM.get(EEPROM_MAGIC_ADDR, magic);
|
||||
EEPROM.get(EEPROM_FLIP_TIME_ADDR, t);
|
||||
if (magic != EEPROM_MAGIC || t < FLIP_TIME_MIN || t > FLIP_TIME_MAX)
|
||||
return FLIP_TIME_DEFAULT;
|
||||
return t;
|
||||
}
|
||||
|
||||
void saveFlipTime(uint16_t t)
|
||||
{
|
||||
// put() only writes bytes that changed
|
||||
EEPROM.put(EEPROM_FLIP_TIME_ADDR, t);
|
||||
EEPROM.put(EEPROM_MAGIC_ADDR, EEPROM_MAGIC);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Settings that survive a power cycle (stored in EEPROM)
|
||||
|
||||
// Stored flip time, or FLIP_TIME_DEFAULT when nothing valid is stored
|
||||
uint16_t loadFlipTime();
|
||||
|
||||
void saveFlipTime(uint16_t t);
|
||||
Reference in new issue
Block a user