780 lines
21 KiB
C++
780 lines
21 KiB
C++
/*
|
|
* SSUP (Super Spiker Ultra Plus)
|
|
* Copyright (C) 2024-2026 Gabriel Weingardt
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License,
|
|
* or any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
* For further information, see <https://weingardt.dev/ssup/>
|
|
* There you'll find documentation and how to update the device.
|
|
*/
|
|
|
|
#include <Wire.h>
|
|
#include <EEPROM.h>
|
|
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
#include "assets.h"
|
|
|
|
/* Screen dimension defines */
|
|
#define IMAGEX 128
|
|
#define IMAGEY 32
|
|
#define TEXTX 21
|
|
#define TEXTY 4
|
|
|
|
Adafruit_SSD1306 display(IMAGEX, IMAGEY, &Wire, -1);
|
|
|
|
const char PROGMEM HELP[] = \
|
|
"--- SSUP Help---\n\
|
|
Please see https://weingardt.dev/ssup for more info\n\
|
|
There you'll also find a manual and instructions\n\n\
|
|
g = Print System Info\n\
|
|
r = Reload Standart Configuration\n\
|
|
s = Enter Data Transfer Mode\n\
|
|
x = Exit Data Transfer Mode\n\
|
|
f = TOC Clear\n\
|
|
c = Clear EEPROM\n\
|
|
p = Print Page Content\n\
|
|
t = Print TOC Content\n\
|
|
h = Help\n\
|
|
u = Unlock Device\n\
|
|
l = Lock Device";
|
|
|
|
|
|
|
|
/* Serial BAUD rate */
|
|
#define BAUD 9600
|
|
|
|
/* EEPROM addresses for settings */
|
|
#define E_PAGE 0
|
|
#define E_CONTRAST 1
|
|
#define E_LOCK 2
|
|
#define E_EEPROM_SIZE 3
|
|
#define E_TOC_SIZE 4
|
|
#define E_SCREEN_ADDR 5
|
|
#define E_EEPROM_ADDR 6
|
|
#define E_THRESHHOLD 7
|
|
|
|
/* Calculator button pins */
|
|
#define ROW 3
|
|
#define D1 A3
|
|
#define D2 A2
|
|
#define D3 A0
|
|
#define D4 A1
|
|
|
|
#define LEFT 0b1000
|
|
#define SUPER 0b0100
|
|
#define TOGGLE 0b0010
|
|
#define RIGHT 0b0001
|
|
|
|
/* Serial flow control values and device info */
|
|
#define WAIT 0xFA
|
|
#define RELEASE 0xFB
|
|
#define MODEL 1
|
|
#define FIRMWARE 4
|
|
|
|
/* Setting variables */
|
|
uint8_t S_EEPROM_Address;
|
|
uint8_t S_SCREEN_Address;
|
|
uint16_t S_EEPROM_Size;
|
|
uint16_t S_TOC_SIZE;
|
|
uint8_t S_THRESHHOLD;
|
|
|
|
/* Variables */
|
|
uint8_t currentIndex = 0;
|
|
uint8_t currentPress = 4;
|
|
uint8_t fetchTrack = 0;
|
|
bool contrast = true;
|
|
bool locked = false;
|
|
bool dataTransferMode = false;
|
|
bool displayOn = true;
|
|
bool nextPage = false;
|
|
|
|
void setup() {
|
|
/* Begin serial and I2C wire for EEPROM */
|
|
Serial.begin(BAUD);
|
|
Wire.begin();
|
|
Wire.setClock(400000);
|
|
|
|
/* Init settings */
|
|
loadSettings();
|
|
|
|
/* Say hello and lock status */
|
|
Serial.println("#SSUP\nType 'h' for help");
|
|
if (locked) Serial.println("!SSUP LOCK\n#This device is locked! Send 'u' to unlock it");
|
|
|
|
/* Init calculator scan pins */
|
|
pinMode(D1, INPUT_PULLUP);
|
|
pinMode(D2, INPUT_PULLUP);
|
|
pinMode(D3, INPUT_PULLUP);
|
|
pinMode(D4, INPUT_PULLUP);
|
|
pinMode(ROW, OUTPUT);
|
|
|
|
/* Init display
|
|
* Note: If less than ~768 bytes of dynamic memory is avaiable,
|
|
* the display will fail to init. The screen library needs 1k
|
|
* of dynamic memory during runtime!
|
|
*/
|
|
if (!display.begin(SSD1306_SWITCHCAPVCC, S_SCREEN_Address)) {
|
|
Serial.print("!DISPLAYBAD: ");
|
|
Serial.println(display.getWriteError());
|
|
Serial.println("#See manual");
|
|
}
|
|
|
|
display.setTextSize(1);
|
|
display.setTextColor(SSD1306_WHITE);
|
|
display.cp437(true);
|
|
|
|
/* Print welcome screen */
|
|
display.clearDisplay();
|
|
display.setCursor(38, 12);
|
|
display.write("@");
|
|
display.print(BAUD);
|
|
display.write(" Baud");
|
|
|
|
scanButtons();
|
|
if ((currentPress & RIGHT) != 0) {
|
|
//display.fillRect(102, 12, 16, 16, SSD1306_BLACK);
|
|
display.drawBitmap(0, 0, LOGO, 128, 32, 1);
|
|
//display.drawBitmap(102, 12, WHAT, 16, 16, 1);
|
|
display.display();
|
|
delay(4000);
|
|
if (currentPress == (RIGHT | LEFT))
|
|
for (;;);
|
|
} else if ((currentPress & LEFT) != 0) {
|
|
|
|
/* Restore settings on left press */
|
|
display.setCursor(0, 0);
|
|
display.print("Loading Default Settings");
|
|
resetSettings();
|
|
}
|
|
|
|
display.display();
|
|
Wire.begin();
|
|
|
|
delay(6000);
|
|
|
|
/* Display first page */
|
|
currentIndex = 0;
|
|
displayPage();
|
|
}
|
|
|
|
void loop() {
|
|
scanButtons();
|
|
|
|
static float datCount = 0;
|
|
static uint8_t recieved = 0;
|
|
|
|
/* Look while data is in seral buffer */
|
|
while (Serial.available()) {
|
|
static bool indexMode = false;
|
|
static bool contentMode = false;
|
|
static bool image = false;
|
|
|
|
static uint8_t newIndex;
|
|
static uint16_t startAddress;
|
|
static uint16_t endAddress;
|
|
|
|
/* Get recieved data */
|
|
recieved = (uint8_t)Serial.read();
|
|
|
|
datCount += (recieved / 100.f);
|
|
|
|
if (contentMode) {
|
|
/* If in content mode (data upload mode) */
|
|
|
|
/* Stop on character ']' */
|
|
if (recieved == ']') {
|
|
Serial.println("!DONE");
|
|
contentMode = false;
|
|
|
|
} else {
|
|
/* Else, interpret the send data */
|
|
static int count = 0;
|
|
count++;
|
|
|
|
/* Send '.' every 8 counts for the master to flow control data */
|
|
if (count > 8) {
|
|
count = 0;
|
|
Serial.print(".");
|
|
}
|
|
|
|
if (image) {
|
|
/* For the image, decode 2 byte ASCII HEX into a byte */
|
|
static uint8_t num = 0;
|
|
/* Stop digging byte on ';' */
|
|
if (recieved == ';') {
|
|
writeEEPROM(startAddress++, num);
|
|
num = 0;
|
|
|
|
} else if (isalnum(recieved)) {
|
|
/* Fetch ASCII hex digit (0..F) into a nibble and construct the byte */
|
|
recieved = tolower(recieved);
|
|
uint8_t fetch = ((recieved - ((recieved >= '0' && recieved <= '9') ? '0' : 'a')) & 0xF);
|
|
|
|
/* First, shift previous fetch and then OR the new nibble */
|
|
num = num << 4;
|
|
num |= fetch;
|
|
}
|
|
|
|
/* Else, just write the ASCII data */
|
|
} else if (recieved >= 0x20) writeEEPROM(startAddress++, recieved);
|
|
|
|
/* On overflow, request temporary pause from master */
|
|
if (startAddress > endAddress) Serial.println("!");
|
|
}
|
|
|
|
|
|
} else if (indexMode) {
|
|
/* Index mode fetches the allocation mode for the next entry */
|
|
if (recieved == ';') {
|
|
/* If ';' was recieved, alloc entry and reset index mode, now comes the data */
|
|
indexMode = false;
|
|
contentMode = true;
|
|
newIndex = allocPage(image);
|
|
startAddress = (getPageAddress(newIndex) & 0x7FFF);
|
|
endAddress = (startAddress + getPageSize(newIndex));
|
|
|
|
} else image = (recieved != 't'); // Determine image or text
|
|
|
|
} else {
|
|
/* Interpret the recieved serial characters */
|
|
|
|
switch (recieved) {
|
|
case 'g':
|
|
/* Print system info to serial */
|
|
printSystemInfo();
|
|
break;
|
|
case 'r':
|
|
/* Load default values and reset */
|
|
Serial.println("!RESETTING");
|
|
resetSettings();
|
|
setup();
|
|
break;
|
|
case 's':
|
|
/* Set data transfer mode */
|
|
dataTransferMode = true;
|
|
break;
|
|
case 'x':
|
|
/* Exit data transfer mode */
|
|
dataTransferMode = false;
|
|
break;
|
|
case 'f':
|
|
/* Clear fast, only TOC */
|
|
clearTOC();
|
|
break;
|
|
case 'c':
|
|
/* Clear entire EEPROM */
|
|
clearEEPROM();
|
|
break;
|
|
case 'p':
|
|
/* Print all page content to serial */
|
|
for (int i = 0; i < 64; i++) {
|
|
uint16_t addr = getPageAddress(i);
|
|
if (addr != 0) printPageData(i);
|
|
}
|
|
break;
|
|
case 't':
|
|
/* Print TOC entries (addresses and size) */
|
|
for (int i = 0; i < 64; i++) {
|
|
printTOC(i);
|
|
}
|
|
break;
|
|
case 'h':
|
|
/* Print help dialog */
|
|
Serial.println(HELP);
|
|
break;
|
|
case 'u':
|
|
/* Unlock device */
|
|
EEPROM.write(E_LOCK, 0);
|
|
Serial.println("!UNLOCKED");
|
|
setup();
|
|
case 'l':
|
|
/* Lock device */
|
|
EEPROM.write(E_LOCK, 1);
|
|
Serial.println("!LOCKED");
|
|
break;
|
|
case '[':
|
|
/* Start index mode (new page entry) */
|
|
indexMode = true;
|
|
break;
|
|
default:
|
|
Serial.println("?");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dataTransferMode) {
|
|
/* If in data transfer mode, show data transfer screen animation */
|
|
static float rotCount = 0;
|
|
|
|
display.clearDisplay();
|
|
|
|
display.setCursor(0, 0);
|
|
display.print("Transfer mode\nHold D2+D3 to \nforcefully abort");
|
|
|
|
/* Nice little animation */
|
|
display.drawLine(108, 16, 108 + (cosf(rotCount) * 10), 16 + (sinf(rotCount) * 10), SSD1306_WHITE);
|
|
display.drawLine(108, 16, 108 + (sinf(datCount + rotCount) * 10), 16 + (cosf(datCount + rotCount) * 10), SSD1306_WHITE);
|
|
|
|
display.display();
|
|
//delay(3);
|
|
|
|
rotCount += 0.125;
|
|
|
|
scanButtons();
|
|
if (currentPress == (SUPER | TOGGLE)) dataTransferMode = false;
|
|
|
|
} else {
|
|
/* If not in data transfer mode, we are in display/normal mode */
|
|
|
|
bool wait = true;
|
|
/* Test the current key press */
|
|
switch (currentPress) {
|
|
case TOGGLE:
|
|
/* Toggle, toggles the display on/off state */
|
|
displayOn = !displayOn;
|
|
display.ssd1306_command(displayOn ? SSD1306_DISPLAYON : SSD1306_DISPLAYOFF);
|
|
delay(200);
|
|
break;
|
|
|
|
case LEFT:
|
|
/* Left, decrements the page */
|
|
incrementPage(false);
|
|
break;
|
|
|
|
case RIGHT:
|
|
/* Right, increments the page */
|
|
incrementPage(true);
|
|
break;
|
|
|
|
case SUPER | TOGGLE:
|
|
/* Super+Toggle toggles the display contrast */
|
|
contrast = !contrast;
|
|
display.ssd1306_command(SSD1306_SETCONTRAST);
|
|
display.ssd1306_command(contrast ? 255 : 0);
|
|
delay(200);
|
|
|
|
break;
|
|
|
|
case SUPER | RIGHT:
|
|
/* Super+Right will lock the calculator */
|
|
bool abort = false;
|
|
for (int i = 5; i >= 0; i--) {
|
|
display.clearDisplay();
|
|
display.setCursor(0, 0);
|
|
display.print("Device will be locked\nHold D1 to abort!\nHold D4 to lock NOW\n -> Locking in: ");
|
|
display.print(i);
|
|
display.display();
|
|
delay(1000);
|
|
|
|
/* On left press abort, on right press skip counter */
|
|
scanButtons();
|
|
if (currentPress == LEFT) {
|
|
abort = true;
|
|
break;
|
|
} else if (currentPress == RIGHT) break;
|
|
}
|
|
|
|
/* Write lock and reset */
|
|
if (!abort) {
|
|
EEPROM.write(E_LOCK, 1);
|
|
setup();
|
|
}
|
|
|
|
case SUPER | LEFT:
|
|
/* Super+Left resets the index to 0 */
|
|
currentIndex = 0;
|
|
displayPage();
|
|
break;
|
|
|
|
default:
|
|
wait = false;
|
|
break;
|
|
}
|
|
if (wait && currentPress != 0) delay(100);
|
|
}
|
|
}
|
|
|
|
void writeEEPROM(uint16_t address, char writebyte) {
|
|
/* Write the given value to EEPROM from desired address */
|
|
|
|
Wire.beginTransmission(S_EEPROM_Address);
|
|
Wire.write((byte)(address >> 8));
|
|
Wire.write((byte)(address & 0xFF));
|
|
Wire.write(writebyte);
|
|
|
|
Wire.endTransmission();
|
|
delay(2); // wait to complete the write cycle
|
|
}
|
|
|
|
int readEEPROM(uint16_t address) {
|
|
/* Read one byte from EEPROM from desired address */
|
|
|
|
Wire.beginTransmission(S_EEPROM_Address);
|
|
Wire.write((byte)(address >> 8));
|
|
Wire.write((byte)(address & 0xFF));
|
|
Wire.endTransmission();
|
|
Wire.requestFrom(S_EEPROM_Address, (uint8_t)1);
|
|
return Wire.read();
|
|
}
|
|
|
|
void clearEEPROM() {
|
|
/* Clearing the entire EEPROM (fill with zeros) */
|
|
|
|
Serial.print("!EEPROM CLEAR ");
|
|
Serial.print((S_EEPROM_Size / 1024) + 1);
|
|
Serial.println("k\n# '.' = 1k");
|
|
|
|
for (uint32_t i = 0; i < S_EEPROM_Size; i++) {
|
|
writeEEPROM(i, 0);
|
|
|
|
/* For every 256 bytes, refresh display */
|
|
if ((i & 0xFF) == 0) {
|
|
display.clearDisplay();
|
|
display.setCursor(0, 0);
|
|
|
|
display.print("Clearing EEPROM ");
|
|
display.print((S_EEPROM_Size / 1024) + 1);
|
|
display.println('k');
|
|
display.print(String(i / 1024));
|
|
display.println("KB cleared");
|
|
display.print((uint8_t)(((float)i / S_EEPROM_Size) * 100));
|
|
display.print("% done ");
|
|
display.println(i < S_TOC_SIZE ? "(TOC)" : "(Data)");
|
|
display.println("Hold D4 to abort");
|
|
|
|
display.display();
|
|
|
|
/* If RIGHT was pressed, abort */
|
|
scanButtons();
|
|
if (currentPress == RIGHT) break;
|
|
|
|
/* Print status */
|
|
Serial.print(".");
|
|
}
|
|
}
|
|
|
|
Serial.println("\n!EEPROM CLEAR DONE");
|
|
displayPage();
|
|
}
|
|
|
|
void displayPage() {
|
|
/* Display page at current index */
|
|
display.clearDisplay();
|
|
display.setCursor(0, 0);
|
|
|
|
/* Get TOC address and size */
|
|
uint16_t start = (getPageAddress(currentIndex) & 0x7FFF);
|
|
uint16_t end = start + getPageSize(currentIndex);
|
|
|
|
/* If text mode, just print the characters */
|
|
if ((getPageAddress(currentIndex) & 0x8000) == 0) {
|
|
for (int16_t i = start; i < end; i++) {
|
|
display.print((char)readEEPROM(i));
|
|
}
|
|
} else {
|
|
/* If it is an image, read bytes and put them into a line buffer and print them out */
|
|
uint8_t buffer[(IMAGEX / 8)];
|
|
uint8_t y = 0;
|
|
uint8_t c = 0;
|
|
|
|
for (uint16_t i = start; i < end; i++) {
|
|
/* Fill buffer with values */
|
|
buffer[c++] = readEEPROM(i);
|
|
|
|
if (c >= (IMAGEX / 8)) {
|
|
/* Draw a 1 line bitmap from the buffer to the S_SCREEN_Address
|
|
* This draws the image line by line, without needing a big buffer
|
|
*/
|
|
display.drawBitmap(0, y++, buffer, IMAGEX - 1, 1, 1);
|
|
c = 0;
|
|
|
|
scanButtons();
|
|
if ((currentPress & (LEFT | RIGHT)) != 0) {
|
|
nextPage = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* For every 8 lines, print the page */
|
|
if ((y % 8) == 0) display.display();
|
|
}
|
|
}
|
|
|
|
display.display();
|
|
}
|
|
|
|
void scanButtons() {
|
|
/* Performs a scan on the calculator button matrix */
|
|
|
|
/* Set the ROW pin to OUTPUT and set it LOW */
|
|
pinMode(ROW, OUTPUT);
|
|
digitalWrite(ROW, LOW);
|
|
|
|
/* Wait a bit */
|
|
delay(3);
|
|
|
|
currentPress = 0;
|
|
|
|
/* Read inputs, if analog read exceeds the threshhold,
|
|
* OR the pressed button to currentPress
|
|
*/
|
|
if (analogRead(D1) < S_THRESHHOLD) currentPress |= LEFT;
|
|
if (analogRead(D2) < S_THRESHHOLD) currentPress |= SUPER;
|
|
if (analogRead(D3) < S_THRESHHOLD) currentPress |= TOGGLE;
|
|
if (analogRead(D4) < S_THRESHHOLD) currentPress |= RIGHT;
|
|
|
|
/* Set ROW to INPUT, this set's the pin in tri state mode */
|
|
pinMode(ROW, INPUT);
|
|
}
|
|
|
|
void incrementPage(bool increment) {
|
|
/* Increments or decrements the page */
|
|
|
|
/* Dont't modify, if index is 0 or 255 or if next TOC entry is empty */
|
|
if (increment && currentIndex == 255) return;
|
|
else if (!increment && currentIndex == 0) return;
|
|
else if (increment && (getPageAddress((currentIndex + 1)) & 0x7FFF) == 0) return;
|
|
|
|
currentIndex += (increment ? 1 : -1);
|
|
|
|
nextPage = false;
|
|
displayPage();
|
|
if (nextPage) incrementPage(increment);
|
|
}
|
|
|
|
void printPageData(uint8_t pageIndex) {
|
|
/* Print the page data at the requested index to serial */
|
|
|
|
int i = getPageAddress(pageIndex) & 0x7FFF;
|
|
int to = i + getPageSize(pageIndex);
|
|
|
|
bool graphicsMode = ((getPageAddress(pageIndex) & 0x8000) != 0);
|
|
|
|
Serial.print("!DATA ");
|
|
Serial.println(graphicsMode ? "IMAGE:" : "TEXT:");
|
|
|
|
for (; i < to; i++) {
|
|
uint8_t val = readEEPROM(i);
|
|
|
|
if (graphicsMode) {
|
|
Serial.print(numToHex(val));
|
|
Serial.print(numToHex(val >> 4));
|
|
Serial.print(';');
|
|
} else {
|
|
Serial.print((char)val);
|
|
}
|
|
}
|
|
|
|
Serial.println("\n!DONE");
|
|
}
|
|
|
|
void printTOC(int index) {
|
|
/* Print TOC index information to serial */
|
|
|
|
uint16_t addr = getPageAddress(index);
|
|
uint16_t size = getPageSize(index);
|
|
|
|
/* Don't print if address is empty */
|
|
if (addr != 0) {
|
|
Serial.print("!TOC INFO: ");
|
|
Serial.print(index);
|
|
Serial.print("\tADDR: ");
|
|
Serial.print((addr & 0x7FFF));
|
|
Serial.print("\tSIZE: ");
|
|
Serial.print(size);
|
|
Serial.print((addr & 0x8000) ? "\tIMAGE" : "\tTEXT");
|
|
Serial.println();
|
|
}
|
|
}
|
|
|
|
char numToHex(uint8_t val) {
|
|
/* Convert the lower section of a byte to a hex char */
|
|
|
|
val &= 0xF;
|
|
return (val < 0xA) ? ('0' + val) : ('A' + (val - 0xA));
|
|
}
|
|
|
|
uint8_t allocPage(bool image) {
|
|
/* Allocate new Memory Block returning the allocated Index
|
|
* Input specifies the allocation size. A Text Entry (image = false)
|
|
* will allocate 84 bytes, while a Image (image = true) will use 512 bytes
|
|
*/
|
|
|
|
/* Get free index */
|
|
uint8_t newIndex = findNewTOCIndex();
|
|
uint16_t resultAddress = 0xFFFF;
|
|
|
|
/* If the index was 0 (first TOC entry), use the address, after the TOC size
|
|
* Addresses after the TOC size is the usable data
|
|
*/
|
|
|
|
Serial.println("!TOC ALLOC");
|
|
if (newIndex == 0) resultAddress = S_TOC_SIZE;
|
|
else {
|
|
/* If not, search for a suitable address based of TOC Entries */
|
|
uint16_t lastAddress = 0;
|
|
uint16_t end = 0;
|
|
|
|
for (int i = 0; i < S_TOC_SIZE / 2; i++) {
|
|
lastAddress = end;
|
|
uint16_t addrs = getPageAddress(i);
|
|
end = (addrs & 0x7FFF) + getPageSize(i);
|
|
|
|
if (i != 0 && (addrs & 0x7FFF) == 0) {
|
|
|
|
/* If we reached end of TOC, break */
|
|
resultAddress = lastAddress;
|
|
Serial.println("!TOC FULL");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update TOC index to found address and set the image/text bit */
|
|
setPageAddress(newIndex, (resultAddress | (image ? 0x8000 : 0)));
|
|
|
|
/* Randomizing of new allocated space (optional) */
|
|
/*
|
|
uint16_t end = (getPageSize(newIndex) + resultAddress);
|
|
for (int i = resultAddress; i < end; i++) {
|
|
if (image) writeEEPROM(i, random(255));
|
|
else writeEEPROM(i, random(20, 127));
|
|
}
|
|
*/
|
|
Serial.print("!TOC INDEX ");
|
|
Serial.print(newIndex);
|
|
Serial.print(" IS ADDR ");
|
|
Serial.println(resultAddress);
|
|
|
|
return newIndex;
|
|
}
|
|
|
|
uint8_t findNewTOCIndex() {
|
|
/* Get next free TOC Index */
|
|
|
|
Serial.println("!TOC SCAN");
|
|
|
|
int foundIndex = 0;
|
|
for (; foundIndex < S_TOC_SIZE / 2; foundIndex++) {
|
|
uint16_t addrs = getPageAddress(foundIndex);
|
|
if (addrs == 0) break;
|
|
}
|
|
|
|
Serial.println("!TOC FOUND INDEX" + String(foundIndex));
|
|
setPageAddress(foundIndex, 0);
|
|
|
|
return foundIndex;
|
|
}
|
|
|
|
uint16_t getPageSize(uint8_t pageIndex) {
|
|
/* Get the TOC page size from TOC index
|
|
* Here, the most significant bit of the address byte
|
|
* is used to determine the allocated space.
|
|
* For text, it's just TEXTX * TEXTY (for 21x4 Text = 84 bytes),
|
|
* for an image it's (IMAGEX / 8) * IMAGEY, because 1 byte can hold 8
|
|
* horizontal pixels (for 128x32 Image = 512 bytes)
|
|
*/
|
|
|
|
return ((getPageAddress(pageIndex) & 0x8000) == 0 ? (TEXTX * TEXTY) : ((IMAGEX / 8) * IMAGEY));
|
|
}
|
|
|
|
uint16_t getPageAddress(uint8_t pageIndex) {
|
|
/* Get the page address from the TOC index */
|
|
|
|
uint16_t retAddrs = readEEPROM(pageIndex * 2);
|
|
retAddrs |= (readEEPROM((pageIndex * 2) + 1) << 8);
|
|
return retAddrs;
|
|
}
|
|
|
|
void setPageAddress(uint8_t pageIndex, uint16_t address) {
|
|
/* Write the given Address to the desired TOC Location */
|
|
|
|
writeEEPROM(pageIndex * 2, (byte)(address & 0xFF));
|
|
writeEEPROM((pageIndex * 2) + 1, (byte)(address >> 8));
|
|
}
|
|
|
|
void clearTOC() {
|
|
/* Clear table of contents (TOC)
|
|
* This is a quick erase, deleting just the address entries
|
|
*/
|
|
|
|
Serial.println("!TOC CLEAR");
|
|
for (int i = 0; i < S_TOC_SIZE; i++) {
|
|
writeEEPROM(i, 0);
|
|
}
|
|
Serial.println("!TOC CLEAR DONE");
|
|
}
|
|
|
|
void printSystemInfo() {
|
|
/* Print system info as JSON to serial */
|
|
|
|
Serial.print("!SYSINFO:{");
|
|
Serial.print("\"FIRMWARE\":" + String(FIRMWARE));
|
|
Serial.print(",\"MODEL\":" + String(MODEL));
|
|
Serial.print(",\"TEXTX\":" + String(TEXTX));
|
|
Serial.print(",\"TEXTY\":" + String(TEXTY));
|
|
Serial.print(",\"IMAGEX\":" + String(IMAGEX));
|
|
Serial.print(",\"IMAGEY\":" + String(IMAGEY));
|
|
|
|
Serial.println("}");
|
|
getSettings();
|
|
}
|
|
|
|
void loadSettings() {
|
|
/* Loads configuration from EEPROM */
|
|
|
|
S_EEPROM_Address = EEPROM.read(E_EEPROM_ADDR);
|
|
S_SCREEN_Address = EEPROM.read(E_SCREEN_ADDR);
|
|
S_EEPROM_Size = (((uint16_t)EEPROM.read(E_EEPROM_SIZE)) * 1024) - 1;
|
|
S_TOC_SIZE = ((uint16_t)EEPROM.read(E_TOC_SIZE)) * 64;
|
|
S_THRESHHOLD = EEPROM.read(E_THRESHHOLD);
|
|
currentIndex = EEPROM.read(E_PAGE);
|
|
contrast = (bool)EEPROM.read(E_CONTRAST);
|
|
locked = (bool)EEPROM.read(E_LOCK);
|
|
}
|
|
|
|
void getSettings() {
|
|
/* Print settings in JSON format to serial */
|
|
|
|
Serial.print("!SETTINGS:{");
|
|
Serial.print("\"EEPROM_ADDR\":" + String(EEPROM.read(E_EEPROM_ADDR)));
|
|
Serial.print(",\"SCREEN_ADDR\":" + String(EEPROM.read(E_SCREEN_ADDR)));
|
|
Serial.print(",\"EEPROM_SIZE\":" + String(EEPROM.read(E_EEPROM_SIZE)));
|
|
Serial.print(",\"TOC_SIZE\":" + String(EEPROM.read(E_TOC_SIZE)));
|
|
Serial.print(",\"THRESHHOLD\":" + String(EEPROM.read(E_THRESHHOLD)));
|
|
Serial.print(",\"PAGE\":" + String(EEPROM.read(E_PAGE)));
|
|
Serial.print(",\"CONTRAST\":" + String(EEPROM.read(E_CONTRAST)));
|
|
Serial.print(",\"LOCKED\":" + String(EEPROM.read(E_LOCK)));
|
|
Serial.println("}");
|
|
}
|
|
|
|
void resetSettings() {
|
|
/* Restore settings to default values */
|
|
|
|
EEPROM.write(E_PAGE, 0);
|
|
EEPROM.write(E_CONTRAST, 255);
|
|
EEPROM.write(E_LOCK, 0);
|
|
EEPROM.write(E_EEPROM_SIZE, 64); // x * 1024 = EEPROM SIZE
|
|
EEPROM.write(E_TOC_SIZE, 8); // x * 64 = TOCSIZE
|
|
EEPROM.write(E_EEPROM_ADDR, 0x50);
|
|
EEPROM.write(E_SCREEN_ADDR, 0x3C);
|
|
EEPROM.write(E_THRESHHOLD, 80);
|
|
}
|
|
|
|
|
|
/* Ein geschlossener Regenschirm ist ebenso elegant wie ein offener hässlich ist */
|