From 4dac6ab05c41c313080b815d9885007187062ee3 Mon Sep 17 00:00:00 2001 From: jjwbruijn Date: Mon, 8 Jan 2024 21:59:51 +0100 Subject: [PATCH] M2 v0026 firmware added --- binaries/Tag/SOL_M2_154_SSD_26.bin | Bin 0 -> 56882 bytes binaries/Tag/SOL_M2_29_SSD_26.bin | Bin 0 -> 57256 bytes binaries/Tag/SOL_M2_42_SSD_26.bin | Bin 0 -> 57212 bytes binaries/Tag/tagotaversions.json | 6 +- zbs243_Tag_FW/Makefile | 4 + zbs243_Tag_FW/drawing.c | 17 ++- zbs243_Tag_FW/drawing.h | 7 +- zbs243_Tag_FW/i2cdevices.c | 16 ++- zbs243_Tag_FW/main.c | 150 ++++++++------------- zbs243_Tag_FW/md5.c | 208 +++++++++++++++++++++++++++++ zbs243_Tag_FW/md5.h | 19 +++ zbs243_Tag_FW/powermgt.c | 16 +-- zbs243_Tag_FW/settings.c | 14 ++ zbs243_Tag_FW/settings.h | 31 +++-- zbs243_Tag_FW/syncedproto.c | 208 ++++++++++++++++++++++++----- zbs243_Tag_FW/syncedproto.h | 5 + zbs243_Tag_FW/userinterface.c | 59 ++++++-- zbs243_Tag_FW/userinterface.h | 3 + zbs243_shared/bitmaps.h | 1 + zbs243_shared/board/ssd1619.c | 14 +- zbs243_shared/font.h | 163 +--------------------- zbs243_shared/soc/zbs243/uart.c | 51 ++++--- zbs243_shared/soc/zbs243/uart.h | 9 +- 23 files changed, 646 insertions(+), 355 deletions(-) create mode 100644 binaries/Tag/SOL_M2_154_SSD_26.bin create mode 100644 binaries/Tag/SOL_M2_29_SSD_26.bin create mode 100644 binaries/Tag/SOL_M2_42_SSD_26.bin create mode 100644 zbs243_Tag_FW/md5.c create mode 100644 zbs243_Tag_FW/md5.h diff --git a/binaries/Tag/SOL_M2_154_SSD_26.bin b/binaries/Tag/SOL_M2_154_SSD_26.bin new file mode 100644 index 0000000000000000000000000000000000000000..014de2b9d88f5846423586d93b39b53e4f60f0b2 GIT binary patch literal 56882 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 literal 0 HcmV?d00001 diff --git a/binaries/Tag/SOL_M2_29_SSD_26.bin b/binaries/Tag/SOL_M2_29_SSD_26.bin new file mode 100644 index 0000000000000000000000000000000000000000..b1c55596de7e8f2191411921a2eb6c63d5e48a3c GIT binary patch literal 57256 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 GntuW`YxP?I literal 0 HcmV?d00001 diff --git a/binaries/Tag/SOL_M2_42_SSD_26.bin b/binaries/Tag/SOL_M2_42_SSD_26.bin new file mode 100644 index 0000000000000000000000000000000000000000..a2f78dcbdeb35a499ab61573d0035a76146914c5 GIT binary patch literal 57212 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 literal 0 HcmV?d00001 diff --git a/binaries/Tag/tagotaversions.json b/binaries/Tag/tagotaversions.json index 1e745bea..427ca168 100644 --- a/binaries/Tag/tagotaversions.json +++ b/binaries/Tag/tagotaversions.json @@ -2,17 +2,17 @@ { "00" : { "type": "SOL_M2_154_SSD", - "version": "25", + "version": "26", "md5": "57e02f80c9f2581be0a140979b1124e1" }, "01" : { "type": "SOL_M2_29_SSD", - "version": "25", + "version": "26", "md5": "bed1a20ca6af04e29e5aabce480c1e9d" }, "02" : { "type": "SOL_M2_42_SSD", - "version": "25", + "version": "26", "md5": "80e36c7d4d08beaca8ad2f00f42923d0" }, "05" : { diff --git a/zbs243_Tag_FW/Makefile b/zbs243_Tag_FW/Makefile index 39fa1d6c..a7871ed9 100644 --- a/zbs243_Tag_FW/Makefile +++ b/zbs243_Tag_FW/Makefile @@ -7,6 +7,8 @@ SOURCES += comms.c SOURCES += syncedproto.c userinterface.c SOURCES += powermgt.c barcode.c i2cdevices.c settings.c + + all: #make sure it is the first target include board/$(BUILD)/make.mk @@ -20,6 +22,8 @@ SOURCES += cpu/$(CPU)/cpu.c SOURCES += board/$(BUILD)/board.c SOURCES += board/$(BUILD)/screen.c +SOURCES += md5.c + EEPROMDRV ?= eeprom.c diff --git a/zbs243_Tag_FW/drawing.c b/zbs243_Tag_FW/drawing.c index 5443ddea..7adfd0d2 100755 --- a/zbs243_Tag_FW/drawing.c +++ b/zbs243_Tag_FW/drawing.c @@ -16,17 +16,22 @@ uint8_t __xdata* drawBuffer; -void drawImageAtAddress(uint32_t addr, uint8_t lut) { +void drawImageAtAddress(uint32_t addr, uint8_t lut) __reentrant { drawBuffer = malloc(512); if (!drawBuffer) { +#ifdef DEBUGDRAWING pr("malloc during draw failed..\n"); +#endif return; } - struct EepromImageHeader* __xdata eih = (struct EepromImageHeader*)drawBuffer; + static struct EepromImageHeader* __xdata eih; + eih = (struct EepromImageHeader*)drawBuffer; eepromRead(addr, drawBuffer, sizeof(struct EepromImageHeader)); switch (eih->dataType) { case DATATYPE_IMG_RAW_1BPP: - pr("Doing raw 1bpp\n"); +#ifdef DEBUGDRAWING + pr("Doing raw 1bpp with lut %d\n", lut); +#endif epdSetup(); if (lut) selectLUT(lut); beginFullscreenImage(); @@ -45,7 +50,9 @@ void drawImageAtAddress(uint32_t addr, uint8_t lut) { endWriteFramebuffer(); break; case DATATYPE_IMG_RAW_2BPP: - pr("Doing raw 2bpp\n"); +#ifdef DEBUGDRAWING + pr("Doing raw 2bpp with lut %d\n", lut); +#endif epdSetup(); if (lut) selectLUT(lut); beginFullscreenImage(); @@ -76,7 +83,9 @@ void drawImageAtAddress(uint32_t addr, uint8_t lut) { endWriteFramebuffer(); break; default: // prevent drawing from an unknown file image type +#ifdef DEBUGDRAWING pr("Image with type 0x%02X was requested, but we don't know what to do with that currently...\n", eih->dataType); +#endif free(drawBuffer); return; } diff --git a/zbs243_Tag_FW/drawing.h b/zbs243_Tag_FW/drawing.h index 8ed12e96..131f4e0e 100644 --- a/zbs243_Tag_FW/drawing.h +++ b/zbs243_Tag_FW/drawing.h @@ -3,11 +3,8 @@ #include -#define DRAWING_MIN_BITMAP_SIZE (128) //minimum size we'll consider - -void set_offline(__bit state); +//void set_offline(__bit state); #pragma callee_saves drawImageAtAddress -void drawImageAtAddress(uint32_t addr, uint8_t lut); -void drawImageFromBuffer(uint8_t* buffer, const uint8_t lut); +extern void drawImageAtAddress(uint32_t addr, uint8_t lut) __reentrant; #endif diff --git a/zbs243_Tag_FW/i2cdevices.c b/zbs243_Tag_FW/i2cdevices.c index de978989..7eb9296c 100755 --- a/zbs243_Tag_FW/i2cdevices.c +++ b/zbs243_Tag_FW/i2cdevices.c @@ -24,7 +24,9 @@ extern void dump(uint8_t* __xdata a, uint16_t __xdata l); // remove me when done -__xdata uint8_t i2cbuffer[18]; +#ifndef LEAN_VERSION + +uint8_t __xdata i2cbuffer[18]; extern uint8_t __xdata blockbuffer[]; bool supportsNFCWake() { @@ -106,16 +108,22 @@ void i2cBusScan() { struct I2cTransaction __xdata iictest; iictest.numBytes = 0; iictest.bytes = NULL; +#ifdef DEBUGNFC pr("Starting I2C scan...\n"); +#endif for (uint8_t address = 0x00; address <= 0x7F; address++) { iictest.deviceAddr = address << 1; uint8_t res = i2cTransact(&iictest, 1); if (res == 0) { +#ifdef DEBUGNFC pr(" - Found i2c device at %02X\n", address); +#endif } timerDelay(13330); } +#ifdef DEBUGNFC pr("I2C scan complete\n"); +#endif } bool i2cCheckDevice(uint8_t address) { @@ -124,8 +132,12 @@ bool i2cCheckDevice(uint8_t address) { iictest.deviceAddr = address << 1; uint8_t res = i2cTransact(&iictest, 1); if (res == 0) { +#ifdef DEBUGNFC pr("I2C: Device found at 0x%02X\n", address); +#endif return true; } return false; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/zbs243_Tag_FW/main.c b/zbs243_Tag_FW/main.c index 9ea75dd9..78217398 100755 --- a/zbs243_Tag_FW/main.c +++ b/zbs243_Tag_FW/main.c @@ -30,7 +30,7 @@ // #define DEBUG_MODE -// static const uint64_t __code __at(0x008b) mVersionRom = 0x1000011300000000ull; +static const uint64_t __code __at(0x008b) firmwaremagic = (0xdeadd0d0beefcafeull) + HW_TYPE; #define TAG_MODE_CHANSEARCH 0 #define TAG_MODE_ASSOCIATED 1 @@ -43,74 +43,11 @@ uint8_t __xdata slideShowRefreshCount = 1; extern uint8_t *__idata blockp; extern uint8_t __xdata blockbuffer[]; +static bool __xdata secondLongCheckIn = false; // send another full request if the previous was a special reason + uint8_t *rebootP; -#ifdef ENABLE_EEPROM_LOADER -extern bool __idata serialBypassActive; -bool __xdata serialActive = false; -void processSerial(uint8_t lastchar) { - static uint8_t __xdata cmdbuffer[4]; - // shift characters in - for (uint8_t c = 0; c < 3; c++) { - cmdbuffer[c] = cmdbuffer[c + 1]; - } - cmdbuffer[3] = lastchar; - - if (strncmp(cmdbuffer + 1, ">D>", 3) == 0) { - wdt120s(); - blockp = blockbuffer; - serialBypassActive = true; - pr("ACK>\n"); - while (serialBypassActive) - ; - if (validateBlockData()) { - pr("ACK>\n"); - } else { - pr("NOK>\n"); - } - } - - if (strncmp(cmdbuffer + 1, ""); - for (uint16_t c = 0; c < 4100; c++) { - uartTx(blockbuffer[c]); - timerDelay(TIMER_TICKS_PER_MS / 400); // 30 okay // 50 kinda okay // 80 ook okay? - } - pr("blaat"); - } - - if (strncmp(cmdbuffer, "STE", 3) == 0) { // store block to offset - if (!eepromErase(4096UL * cmdbuffer[3], 4096 / EEPROM_ERZ_SECTOR_SZ)) { - pr("NOK>\n"); - return; - } - if (eepromWrite(4096UL * cmdbuffer[3], blockbuffer + 4, 4096)) - pr("ACK>\n"); - else - pr("NOK>\n"); - } - if (strncmp(cmdbuffer, "LDE", 3) == 0) { // load block from offset - eepromRead(4096UL * cmdbuffer[3], blockbuffer + 4, 4096); - uint16_t *header = blockbuffer; - *header = 4096; - uint16_t *sum = blockbuffer + 2; - *sum = 0; - for (uint16_t c = 4; c < 4100; c++) { - *sum += blockbuffer[c]; - } - pr("ACK>\n"); - } -} -void serialTerminal() { - serialActive = true; - while (serialActive) { - while (uartBytesAvail()) { - processSerial(uartRx()); - } - } -} -#endif +#ifdef DEBUGGUI void displayLoop() { powerUp(INIT_BASE | INIT_UART); @@ -126,6 +63,12 @@ void displayLoop() { wdt30s(); + pr("Failed update screen\n"); + powerUp(INIT_EPD); + showFailedUpdate(); + timerDelay(TIMER_TICKS_PER_SECOND * 4); + wdt30s(); + pr("AP Found\n"); powerUp(INIT_EPD); showAPFound(); @@ -154,13 +97,15 @@ void displayLoop() { wdt30s(); - pr("NO EEPROM\n"); + pr("NO MAC\n"); powerUp(INIT_EPD); showNoMAC(); timerDelay(TIMER_TICKS_PER_SECOND * 4); wdtDeviceReset(); } +#endif + #ifdef WRITE_MAC_FROM_FLASH void writeInfoPageWithMac() { uint8_t *settemp = blockbuffer + 2048; @@ -215,7 +160,9 @@ uint8_t channelSelect(uint8_t rounds) { // returns 0 if no accesspoints were fo for (uint8_t c = 0; c < sizeof(channelList); c++) { if (detectAP(channelList[c])) { if (mLastLqi > result[c]) result[c] = mLastLqi; +#ifdef DEBUGMAIN if (rounds > 2) pr("Channel: %d - LQI: %d RSSI %d\n", channelList[c], mLastLqi, mLastRSSI); +#endif } } } @@ -238,8 +185,10 @@ void validateMacAddress() { for (uint8_t __xdata c = 0; c < 8; c++) { if (mSelfMac[c] != 0xFF) goto macIsValid; } - // invalid mac address. Display warning screen and sleep forever +// invalid mac address. Display warning screen and sleep forever +#ifdef DEBUGMAIN pr("Mac can't be all FF's.\n"); +#endif powerUp(INIT_EPD); showNoMAC(); powerDown(INIT_EPD | INIT_UART | INIT_EEPROM); @@ -250,11 +199,15 @@ macIsValid: } uint8_t getFirstWakeUpReason() { if (RESET & 0x01) { +#ifdef DEBUGMAIN pr("WDT reset!\n"); +#endif return WAKEUP_REASON_WDT_RESET; } return WAKEUP_REASON_FIRSTBOOT; } + +#ifndef LEAN_VERSION void checkI2C() { powerUp(INIT_I2C); // i2cBusScan(); @@ -263,15 +216,20 @@ void checkI2C() { // found something! capabilities |= CAPABILITY_HAS_NFC; if (supportsNFCWake()) { +#ifdef DEBUGNFC pr("NFC: NFC Wake Supported\n"); +#endif capabilities |= CAPABILITY_NFC_WAKE; } } else { +#ifdef DEBUGNFC pr("I2C: No devices found"); +#endif // didn't find a NFC chip on the expected ID powerDown(INIT_I2C); } } +#endif void detectButtonOrJig() { switch (checkButtonOrJig()) { @@ -280,18 +238,11 @@ void detectButtonOrJig() { break; case DETECT_P1_0_JIG: wdt120s(); -#ifdef ENABLE_EEPROM_LOADER - // run the eeprom loader interface - powerUp(INIT_EPD | INIT_EEPROM); - serialActive = true; - serialTerminal(); -#else // show splashscreen powerUp(INIT_EPD); afterFlashScreenSaver(); while (1) ; -#endif break; case DETECT_P1_0_NOTHING: break; @@ -302,10 +253,9 @@ void detectButtonOrJig() { void TagAssociated() { // associated - bool fastNextCheckin = false; struct AvailDataInfo *__xdata avail; // Is there any reason why we should do a long (full) get data request (including reason, status)? - if ((longDataReqCounter > LONG_DATAREQ_INTERVAL) || wakeUpReason != WAKEUP_REASON_TIMED) { + if ((longDataReqCounter > LONG_DATAREQ_INTERVAL) || wakeUpReason != WAKEUP_REASON_TIMED || secondLongCheckIn) { // check if we should do a voltage measurement (those are pretty expensive) if (voltageCheckCounter == VOLTAGE_CHECK_INTERVAL) { doVoltageReading(); @@ -353,16 +303,26 @@ void TagAssociated() { externalWakeHandler(CUSTOM_IMAGE_RF_WAKE); fastNextCheckin = true; break; +#ifndef LEAN_VERSION case WAKEUP_REASON_NFC: externalWakeHandler(CUSTOM_IMAGE_NFC_WAKE); fastNextCheckin = true; break; +#endif } if (avail != NULL) { // we got some data! longDataReqCounter = 0; + + if (secondLongCheckIn == true) { + secondLongCheckIn = false; + } + // since we've had succesful contact, and communicated the wakeup reason succesfully, we can now reset to the 'normal' status + if (wakeUpReason != WAKEUP_REASON_TIMED) { + secondLongCheckIn = true; + } wakeUpReason = WAKEUP_REASON_TIMED; } if (tagSettings.enableTagRoaming) { @@ -431,6 +391,7 @@ void TagAssociated() { doSleep(getNextSleep() * 1000UL); } } + powerUp(INIT_UART); } void TagChanSearch() { @@ -541,12 +502,16 @@ void TagSlideShow() { doSleep(1000UL * SLIDESHOW_INTERVAL_GLACIAL); break; } +#ifdef DEBUGMAIN pr("wake...\n"); +#endif } } void TagShowWaitRFWake() { +#ifdef DEBUGMAIN pr("waiting for RF wake to start slideshow, now showing image\n"); +#endif currentChannel = 11; // suppress the no-rf image thing displayCustomImage(CUSTOM_IMAGE_SLIDESHOW); // powerDown(INIT_EEPROM | INIT_EPD); @@ -657,40 +622,36 @@ void executeCommand(uint8_t cmd) { } void main() { - // displayLoop(); // remove me setupPortsInitial(); powerUp(INIT_BASE | INIT_UART); pr("BOOTED> %d.%d.%d%s\n", fwVersion / 100, (fwVersion % 100) / 10, (fwVersion % 10), fwVersionSuffix); +#ifdef DEBUGGUI + displayLoop(); // remove me +#endif + // Find the reason why we're booting; is this a WDT? wakeUpReason = getFirstWakeUpReason(); // dump(blockbuffer, 1024); // get our own mac address. this is stored in Infopage at offset 0x10-onwards boardGetOwnMac(mSelfMac); + +#ifdef DEBUGMAIN pr("MAC>%02X%02X", mSelfMac[0], mSelfMac[1]); pr("%02X%02X", mSelfMac[2], mSelfMac[3]); pr("%02X%02X", mSelfMac[4], mSelfMac[5]); pr("%02X%02X\n", mSelfMac[6], mSelfMac[7]); - - for (uint16_t c = 0; c < 4096; c++) { - blockbuffer[c] = (c % 256) & 0xFF; - } - +#endif // do a little sleep, this prevents a partial boot during battery insertion - doSleep(2000UL); + doSleep(400UL); powerUp(INIT_EEPROM | INIT_UART); - uint8_t __idata dati; - pr("blockbuffer @%d, idata@%d\n",&blockbuffer[0], &dati); - dump(blockbuffer, 4096); - // load settings from infopage loadSettings(); // invalidate the settings, and write them back in a later state invalidateSettingsEEPROM(); - #ifdef WRITE_MAC_FROM_FLASH if (mSelfMac[7] == 0xFF && mSelfMac[6] == 0xFF) { wdt10s(); @@ -738,8 +699,10 @@ void main() { #endif if (tagSettings.enableFastBoot) { - // Fastboot +// Fastboot +#ifdef DEBUGMAIN pr("Doing fast boot\n"); +#endif capabilities = tagSettings.fastBootCapabilities; if (tagSettings.fixedChannel) { currentChannel = tagSettings.fixedChannel; @@ -748,15 +711,18 @@ void main() { } } else { // Normal boot/startup +#ifdef DEBUGMAIN pr("Normal boot\n"); +#endif // validate the mac address; this will display a warning on the screen if the mac address is invalid validateMacAddress(); +#ifndef LEAN_VERSION #if (NFC_TYPE == 1) // initialize I2C checkI2C(); #endif - +#endif // Get a voltage reading on the tag, loading down the battery with the radio doVoltageReading(); diff --git a/zbs243_Tag_FW/md5.c b/zbs243_Tag_FW/md5.c new file mode 100644 index 00000000..5ffd8930 --- /dev/null +++ b/zbs243_Tag_FW/md5.c @@ -0,0 +1,208 @@ +/* + * Derived from the RSA Data Security, Inc. MD5 Message-Digest Algorithm + * and modified slightly to be functionally identical but condensed into control structures. + */ + +#include "md5.h" + +/* + * Constants defined by the MD5 algorithm + */ +#define A 0x67452301 +#define B 0xefcdab89 +#define C 0x98badcfe +#define D 0x10325476 + +const static uint32_t __code S[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +const static uint32_t __code K[] = {0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391}; + +/* + * Padding used to make the size (in bits) of the input congruent to 448 mod 512 + */ +const static uint8_t __code PADDING[] = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* + * Bit-manipulation functions defined by the MD5 algorithm + */ +#define F(X, Y, Z) ((X & Y) | (~X & Z)) +#define G(X, Y, Z) ((X & Z) | (Y & ~Z)) +#define H(X, Y, Z) (X ^ Y ^ Z) +#define I(X, Y, Z) (Y ^ (X | ~Z)) + +/* + * Rotates a 32-bit word left by n bits + */ +uint32_t rotateLeft(uint32_t x, uint32_t n) { + return (x << n) | (x >> (32 - n)); +} + +/* + * Initialize a context + */ +uint64_t __xdata ctxsize = 0; // Size of input in bytes +uint32_t __xdata ctxbuffer[4] = {0}; // Current accumulation of hash +uint8_t __xdata ctxinput[64] = {0}; // Input to be used in the next step +uint8_t __xdata ctxdigest[16] = {0}; // Result of algorithm + +void md5Init() { + ctxsize = (uint64_t)0; + ctxbuffer[0] = (uint32_t)A; + ctxbuffer[1] = (uint32_t)B; + ctxbuffer[2] = (uint32_t)C; + ctxbuffer[3] = (uint32_t)D; +} + +/* + * Add some amount of input to the context + * + * If the input fills out a block of 512 bits, apply the algorithm (md5Step) + * and save the result in the buffer. Also updates the overall size. + */ + +uint32_t swapEndian(uint8_t j) __reentrant{ + static uint32_t __xdata temp; + temp = (uint32_t)(ctxinput[(j * 4) + 3]) << 24 | + (uint32_t)(ctxinput[(j * 4) + 2]) << 16 | + (uint32_t)(ctxinput[(j * 4) + 1]) << 8 | + (uint32_t)(ctxinput[(j * 4)]); + return temp; +} + + +uint32_t __xdata input[16]; + +void md5Update(uint8_t *input_buffer, size_t input_len) __reentrant{ + static unsigned int __xdata offset; + offset = ctxsize % 64; + ctxsize += (uint64_t)input_len; + + // Copy each byte in input_buffer into the next space in our context input + for (unsigned int i = 0; i < input_len; ++i) { + ctxinput[offset++] = (uint8_t) * (input_buffer + i); + + // If we've filled our context input, copy it into our local array input + // then reset the offset to 0 and fill in a new buffer. + // Every time we fill out a chunk, we run it through the algorithm + // to enable some back and forth between cpu and i/o + if (offset % 64 == 0) { + for (unsigned int j = 0; j < 16; ++j) { + // Convert to little-endian + // The local variable `input` our 512-bit chunk separated into 32-bit words + // we can use in calculations + input[j] = swapEndian(j); + } + md5Step(input); + offset = 0; + } + } +} + +/* + * Pad the current input to get to 448 bytes, append the size in bits to the very end, + * and save the result of the final iteration into digest. + */ +void md5Finalize() __reentrant{ +// uint32_t __xdata input[16]; + static unsigned int __xdata offset; + offset = ctxsize % 64; + static unsigned int __xdata padding_length; + padding_length = offset < 56 ? 56 - offset : (56 + 64) - offset; + + // Fill in the padding and undo the changes to size that resulted from the update + md5Update(PADDING, padding_length); + ctxsize -= (uint64_t)padding_length; + + // Do a final update (internal to this function) + // Last two 32-bit words are the two halves of the size (converted from bytes to bits) + for (unsigned int j = 0; j < 14; ++j) { + input[j] = swapEndian(j); + } + input[14] = (uint32_t)(ctxsize * 8); + input[15] = (uint32_t)((ctxsize * 8) >> 32); + + md5Step(input); + + // Move the result into digest (convert from little-endian) + for (unsigned int i = 0; i < 4; ++i) { + ctxdigest[(i * 4) + 0] = (uint8_t)((ctxbuffer[i] & 0x000000FF)); + ctxdigest[(i * 4) + 1] = (uint8_t)((ctxbuffer[i] & 0x0000FF00) >> 8); + ctxdigest[(i * 4) + 2] = (uint8_t)((ctxbuffer[i] & 0x00FF0000) >> 16); + ctxdigest[(i * 4) + 3] = (uint8_t)((ctxbuffer[i]) >> 24); + } +} + +/* + * Step on 512 bits of input with the main MD5 algorithm. + */ +void md5Step(uint32_t *input) __reentrant { + static uint32_t __xdata AA; + static uint32_t __xdata BB; + static uint32_t __xdata CC; + static uint32_t __xdata DD; + static uint32_t __xdata E; + static unsigned int __xdata j; + static unsigned int __xdata i; + + AA = ctxbuffer[0]; + BB = ctxbuffer[1]; + CC = ctxbuffer[2]; + DD = ctxbuffer[3]; + + for (i = 0; i < 64; ++i) { + switch (i / 16) { + case 0: + E = F(BB, CC, DD); + j = i; + break; + case 1: + E = G(BB, CC, DD); + j = ((i * 5) + 1) % 16; + break; + case 2: + E = H(BB, CC, DD); + j = ((i * 3) + 5) % 16; + break; + default: + E = I(BB, CC, DD); + j = (i * 7) % 16; + break; + } + + uint32_t temp = DD; + DD = CC; + CC = BB; + BB = BB + rotateLeft(AA + E + K[i] + input[j], S[i]); + AA = temp; + } + + ctxbuffer[0] += AA; + ctxbuffer[1] += BB; + ctxbuffer[2] += CC; + ctxbuffer[3] += DD; +} \ No newline at end of file diff --git a/zbs243_Tag_FW/md5.h b/zbs243_Tag_FW/md5.h new file mode 100644 index 00000000..dff4e36b --- /dev/null +++ b/zbs243_Tag_FW/md5.h @@ -0,0 +1,19 @@ +#ifndef MD5_H +#define MD5_H + +#include +#include +//#include +//#include + +extern uint64_t __xdata ctxsize; // Size of input in bytes +extern uint32_t __xdata ctxbuffer[4]; // Current accumulation of hash +extern uint8_t __xdata ctxinput[64]; // Input to be used in the next step +extern uint8_t __xdata ctxdigest[16]; // Result of algorithm + +void md5Init(); +void md5Update(uint8_t *input, size_t input_len) __reentrant; +void md5Finalize(); +void md5Step(uint32_t *input); + +#endif diff --git a/zbs243_Tag_FW/powermgt.c b/zbs243_Tag_FW/powermgt.c index 90372b30..a7b17569 100755 --- a/zbs243_Tag_FW/powermgt.c +++ b/zbs243_Tag_FW/powermgt.c @@ -119,13 +119,12 @@ static void configSPI(const bool setup) { static void configUART(const bool setup) { if (uartActive == setup) return; if (setup) { - P0FUNC |= (1 << 6) | (1 << 7); + P0FUNC |= (1 << 6); P0DIR &= ~(1 << 6); - P0DIR |= (1 << 7); uartInit(); } else { P0DIR |= (1 << 6); - P0FUNC &= ~((1 << 6) | (1 << 7)); + P0FUNC &= ~(1 << 6); CLKEN &= ~(0x20); } uartActive = setup; @@ -268,13 +267,6 @@ void powerDown(const uint8_t parts) { } void doSleep(const uint32_t __xdata t) { - // if (t > 1000) pr("s=%lu\n ", t / 1000); - // powerPortsDownForSleep(); - - // set up pins for spi(0.0,0.1,0.2), UART (0.6) - // setup 1.1(eeprom_nCS), 1.2(eink_BS1), 1.7(eink_nCS) - // setup 2.0(eink_nRST), 2.1(eink_BUSY), 2.2(eink_D/nC) - UartTxWait(); P0FUNC = 0; P1FUNC = 0; P2FUNC = 0; @@ -294,7 +286,9 @@ void doSleep(const uint32_t __xdata t) { uartActive = false; eepromActive = false; - //capabilities |= CAPABILITY_HAS_WAKE_BUTTON; +#ifdef ISDEBUGBUILD + capabilities |= CAPABILITY_HAS_WAKE_BUTTON; +#endif if (capabilities & CAPABILITY_HAS_WAKE_BUTTON) { // Button setup on TEST pin 1.0 (input pullup) diff --git a/zbs243_Tag_FW/settings.c b/zbs243_Tag_FW/settings.c index 0939b733..c2697d62 100755 --- a/zbs243_Tag_FW/settings.c +++ b/zbs243_Tag_FW/settings.c @@ -38,13 +38,17 @@ void loadDefaultSettings() { } void loadSettingsFromBuffer(uint8_t* p) { +#ifdef DEBUGSETTINGS pr("SETTINGS: received settings from AP\n"); +#endif switch (*p) { case SETTINGS_STRUCT_VERSION: // the current tag struct memcpy((void*)tagSettings, (void*)p, sizeof(struct tagsettings)); break; default: +#ifdef DEBUGSETTINGS pr("SETTINGS: received something we couldn't really process, version %d\n"); +#endif break; } tagSettings.fastBootCapabilities = capabilities; @@ -75,29 +79,39 @@ void loadSettings() { if (tagSettings.settingsVer == 0xFF || valid != SETTINGS_MAGIC) { // settings not set. load the defaults loadDefaultSettings(); +#ifdef DEBUGSETTINGS pr("SETTINGS: Loaded default settings\n"); +#endif } else { if (tagSettings.settingsVer < SETTINGS_STRUCT_VERSION) { // upgrade upgradeSettings(); +#ifdef DEBUGSETTINGS pr("SETTINGS: Upgraded from previous version\n"); +#endif } else { // settings are valid +#ifdef DEBUGSETTINGS pr("SETTINGS: Loaded from EEPROM\n"); +#endif } } } void writeSettings() { if (compareSettings()) { +#ifdef DEBUGSETTINGS pr("SETTINGS: Settings matched current settings\n"); +#endif return; } eepromErase(EEPROM_SETTINGS_AREA_START, 1); uint32_t __xdata valid = SETTINGS_MAGIC; eepromWrite(EEPROM_SETTINGS_AREA_START, (void*)&valid, 4); eepromWrite(EEPROM_SETTINGS_AREA_START + 4, (void*)&tagSettings, sizeof(tagSettings)); +#ifdef DEBUGSETTINGS pr("SETTINGS: Updated settings in EEPROM\n"); +#endif } void invalidateSettingsEEPROM() { diff --git a/zbs243_Tag_FW/settings.h b/zbs243_Tag_FW/settings.h index 362d62ab..06c449f2 100755 --- a/zbs243_Tag_FW/settings.h +++ b/zbs243_Tag_FW/settings.h @@ -3,15 +3,28 @@ #include -#define FW_VERSION 0x0025 // version number -#define FW_VERSION_SUFFIX "-FIX3" // suffix, like -RC1 or whatever. -// #define DEBUGBLOCKS // uncomment to enable extra debug information on the block transfers -// #define PRINT_LUT // uncomment if you want the tag to print the LUT for the current temperature bracket -// #define ENABLE_EEPROM_LOADER // uncomment if you want to load eeprom images via the serial interface -// #define ENABLE_GPIO_WAKE // uncomment to enable GPIO wake -// #define ENABLE_RETURN_DATA // enables the tag to send blocks of data back. Enabling this costs about 4 IRAM bytes -// #define LEAN_VERSION // makes a smaller version, leaving extra flash space for other things -// #define WRITE_MAC_FROM_FLASH // takes mac address from flash if none is set in the infopage +#define FW_VERSION 0x0026 // version number +#define FW_VERSION_SUFFIX "-MD5" // suffix, like -RC1 or whatever. +// #define DEBUGBLOCKS // uncomment to enable extra debug information on the block transfers +// #define DEBUGPROTO // debug protocol +// #define DEBUGOTA // debug OTA FW updates +// #define DEBUGDRAWING // debug the drawing part +// #define DEBUGEPD // debug the EPD driver +// #define DEBUGMAIN // parts in the main loop +// #define DEBUGNFC // debug NFC functions +// #define DEBUGGUI // debug GUI drawing (enabled) +// #define DEBUGSETTINGS // debug settings module (preferences/eeprom) + +#define VALIDATE_IMAGE_MD5 // The firmware can validate the image MD5 before displaying it. This costs about 8mAS (milliamp-second) for a 1.54, 16 +// #define PRINT_LUT // uncomment if you want the tag to print the LUT for the current temperature bracket +// #define ENABLE_GPIO_WAKE // uncomment to enable GPIO wake +// #define ENABLE_RETURN_DATA // enables the tag to send blocks of data back. Enabling this costs about 4 IRAM bytes +// #define LEAN_VERSION // makes a smaller version, leaving extra flash space for other things +// #define WRITE_MAC_FROM_FLASH // takes mac address from flash if none is set in the infopage + +#if defined(DEBUGSETTINGS) || defined(DEBUGMSG) || defined(DEBUGBLOCKS) || defined(DEBUGPROTO) || defined(DEBUGOTA) || defined(DEBUGNFC) || defined(DEBUGEPD) || defined(DEBUGMAIN) +#define ISDEBUGBUILD +#endif #define SETTINGS_STRUCT_VERSION 0x01 diff --git a/zbs243_Tag_FW/syncedproto.c b/zbs243_Tag_FW/syncedproto.c index d07b14e3..d3bec98e 100755 --- a/zbs243_Tag_FW/syncedproto.c +++ b/zbs243_Tag_FW/syncedproto.c @@ -26,6 +26,8 @@ #include "userinterface.h" #include "wdt.h" #include "uart.h" +#include "md5.h" +#include "flash.h" // download-stuff uint8_t __xdata blockbuffer[BLOCK_XFER_BUFFER_SIZE] = {0}; @@ -54,6 +56,9 @@ uint8_t __xdata currentChannel = 0; static uint8_t __xdata inBuffer[128] = {0}; static uint8_t __xdata outBuffer[128] = {0}; +// determines if the tagAssociated loop in main.c performs a rapid next checkin +bool __xdata fastNextCheckin = false; + // other stuff we shouldn't have to put here... static uint32_t __xdata markerValid = EEPROM_IMG_VALID; @@ -99,7 +104,6 @@ void dump(const uint8_t *__xdata a, const uint16_t __xdata l) { pr("\n0x%04X | ", c); } pr("%02X ", a[c]); - UartTxWait(); } pr("\n--------"); for (uint8_t c = 0; c < ROWS; c++) { @@ -140,8 +144,8 @@ static void sendPing() { txframe->srcPan = PROTO_PAN_ID; commsTxNoCpy(outBuffer); } -uint8_t detectAP(const uint8_t channel) { - uint32_t __xdata t; +uint8_t detectAP(const uint8_t channel) __reentrant { + static uint32_t __xdata t; radioRxEnable(false, true); radioSetChannel(channel); radioRxFlush(); @@ -150,12 +154,14 @@ uint8_t detectAP(const uint8_t channel) { sendPing(); t = timerGet() + (TIMER_TICKS_PER_MS * PING_REPLY_WINDOW); while (timerGet() < t) { - int8_t __xdata ret = commsRxUnencrypted(inBuffer); + static int8_t __xdata ret; + ret = commsRxUnencrypted(inBuffer); if (ret > 1) { // dump(inBuffer+sizeof(struct MacFrameNormal),32); if ((inBuffer[sizeof(struct MacFrameNormal) + 1] == channel) && (getPacketType(inBuffer) == PKT_PONG)) { if (pktIsUnicast(inBuffer)) { - struct MacFrameNormal *__xdata f = (struct MacFrameNormal *)inBuffer; + static struct MacFrameNormal *__xdata f; + f = (struct MacFrameNormal *)inBuffer; memcpy(APmac, f->src, 8); APsrcPan = f->pan; return c; @@ -317,9 +323,6 @@ static bool processBlockPart(const struct blockPart *bp) { size = sizeof(blockbuffer) - start; } - // check if we already processed this blockpart - if (!(curBlock.requestedParts[bp->blockPart / 8] & (1 << (bp->blockPart % 8)))) return false; - if (checkCRC(bp, sizeof(struct blockPart) + BLOCK_PART_DATA_SIZE)) { // copy block data to buffer xMemCopy((void *)(blockbuffer + start), (const void *)bp->data, size); @@ -381,15 +384,16 @@ static void sendBlockRequest() { addCRC(blockreq, sizeof(struct blockRequest)); commsTxNoCpy(outBuffer); } -static struct blockRequestAck *__xdata performBlockRequest() { - uint32_t __xdata t; +static struct blockRequestAck *__xdata performBlockRequest() __reentrant { + static uint32_t __xdata t; radioRxEnable(true, true); radioRxFlush(); for (uint8_t c = 0; c < 30; c++) { sendBlockRequest(); t = timerGet() + (TIMER_TICKS_PER_MS * (7UL + c / 10)); do { - int8_t __xdata ret = commsRxUnencrypted(inBuffer); + static int8_t __xdata ret; + ret = commsRxUnencrypted(inBuffer); if (ret > 1) { switch (getPacketType(inBuffer)) { case PKT_BLOCK_REQUEST_ACK: @@ -405,7 +409,9 @@ static struct blockRequestAck *__xdata performBlockRequest() { case PKT_CANCEL_XFER: return NULL; default: +#ifdef DEBUGPROTO pr("pkt w/type %02X\n", getPacketType(inBuffer)); +#endif break; } } @@ -444,13 +450,17 @@ static void sendXferComplete() { int8_t __xdata ret = commsRxUnencrypted(inBuffer); if (ret > 1) { if (getPacketType(inBuffer) == PKT_XFER_COMPLETE_ACK) { +#ifdef DEBUGPROTO pr("XFC ACK\n"); +#endif return; } } } } +#ifdef DEBUGPROTO pr("XFC NACK!\n"); +#endif return; } bool validateBlockData() { @@ -471,18 +481,26 @@ static void getNumSlots() { eeSize = eepromGetSize(); uint16_t nSlots = mathPrvDiv32x16(eeSize - EEPROM_IMG_START, EEPROM_IMG_EACH >> 8) >> 8; if (eeSize < EEPROM_IMG_START || !nSlots) { +#ifdef DEBUGPROTO pr("eeprom is too small\n"); +#endif while (1) ; } else if (nSlots >> 8) { +#ifdef DEBUGPROTO pr("eeprom is too big, some will be unused\n"); +#endif imgSlots = 254; } else imgSlots = nSlots; - +#ifdef DEBUGPROTO pr("PROTO: %d image slots total\n", imgSlots); +#endif } static uint8_t findSlotVer(const uint8_t *ver) { +#ifdef DEBUGBLOCKS + return 0xFF; +#endif // return 0xFF; // remove me! This forces the tag to re-download each and every upload without checking if it's already in the eeprom somewhere for (uint8_t c = 0; c < imgSlots; c++) { struct EepromImageHeader __xdata *eih = (struct EepromImageHeader __xdata *)blockbuffer; @@ -538,15 +556,13 @@ static void eraseImageBlock(const uint8_t c) { eepromErase(getAddressForSlot(c), EEPROM_IMG_EACH / EEPROM_ERZ_SECTOR_SZ); } static void saveUpdateBlockData(uint8_t blockId) { - if (!eepromWrite(eeSize - OTA_UPDATE_SIZE + (blockId * BLOCK_DATA_SIZE), blockbuffer + sizeof(struct blockData), BLOCK_DATA_SIZE)) - pr("EEPROM write failed\n"); + eepromWrite(eeSize - OTA_UPDATE_SIZE + (blockId * BLOCK_DATA_SIZE), blockbuffer + sizeof(struct blockData), BLOCK_DATA_SIZE); } static void saveImgBlockData(const uint8_t imgSlot, const uint8_t blockId) { uint16_t length = EEPROM_IMG_EACH - (sizeof(struct EepromImageHeader) + (blockId * BLOCK_DATA_SIZE)); if (length > 4096) length = 4096; - if (!eepromWrite(getAddressForSlot(imgSlot) + sizeof(struct EepromImageHeader) + (blockId * BLOCK_DATA_SIZE), blockbuffer + sizeof(struct blockData), length)) - pr("EEPROM write failed\n"); + eepromWrite(getAddressForSlot(imgSlot) + sizeof(struct EepromImageHeader) + (blockId * BLOCK_DATA_SIZE), blockbuffer + sizeof(struct blockData), length); } void eraseImageBlocks() { for (uint8_t c = 0; c < imgSlots; c++) { @@ -568,7 +584,9 @@ static uint32_t getHighSlotId() { } } } +#ifdef DEBUGPROTO pr("found high id=%lu in slot %d\n", temp, nextImgSlot); +#endif return temp; } @@ -612,7 +630,9 @@ static bool getDataBlock(const uint16_t blockSize) { struct blockRequestAck *__xdata ack = performBlockRequest(); if (ack == NULL) { +#ifdef DEBUGPROTO pr("Cancelled request\n"); +#endif return false; } if (ack->pleaseWaitMs) { // SLEEP - until the AP is ready with the data @@ -674,17 +694,22 @@ static bool getDataBlock(const uint16_t blockSize) { } uint16_t __xdata dataRequestSize = 0; +uint16_t __xdata otaSize = 0; static bool downloadFWUpdate(const struct AvailDataInfo *__xdata avail) { // check if we already started the transfer of this information & haven't completed it if (xMemEqual((const void *__xdata) & avail->dataVer, (const void *__xdata) & xferDataInfo.dataVer, 8) && xferDataInfo.dataSize) { // looks like we did. We'll carry on where we left off. } else { +#if defined(DEBUGOTA) + pr("OTA: Start update!\n"); +#endif // start, or restart the transfer from 0. Copy data from the AvailDataInfo struct, and the struct intself. This forces a new transfer curBlock.blockId = 0; xMemCopy8(&(curBlock.ver), &(avail->dataVer)); curBlock.type = avail->dataType; xMemCopyShort(&xferDataInfo, (void *)avail, sizeof(struct AvailDataInfo)); eraseUpdateBlock(); + otaSize = xferDataInfo.dataSize; } while (xferDataInfo.dataSize) { @@ -708,18 +733,35 @@ static bool downloadFWUpdate(const struct AvailDataInfo *__xdata avail) { return false; } } + wdt60s(); + powerUp(INIT_EEPROM); + if (!validateMD5(eeSize - OTA_UPDATE_SIZE, otaSize)) { +#if defined(DEBUGOTA) + pr("OTA: MD5 verification failed!\n"); +#endif + // if not valid, restart transfer from the beginning + curBlock.blockId = 0; + powerDown(INIT_EEPROM); + return false; + } +#if defined(DEBUGOTA) + pr("OTA: MD5 pass!\n"); +#endif + // no more data, download complete return true; } uint16_t __xdata imageSize = 0; static bool downloadImageDataToEEPROM(const struct AvailDataInfo *__xdata avail) { - // check if we already started the transfer of this information & haven't completed it + // check if we already started the transfer of this information & haven't completed it if (xMemEqual((const void *__xdata) & avail->dataVer, (const void *__xdata) & xferDataInfo.dataVer, 8) && (xferDataInfo.dataTypeArgument == avail->dataTypeArgument) && xferDataInfo.dataSize) { - // looks like we did. We'll carry on where we left off. +// looks like we did. We'll carry on where we left off. +#ifdef DEBUGPROTO pr("restarting image download"); +#endif } else { // new transfer powerUp(INIT_EEPROM); @@ -737,7 +779,9 @@ static bool downloadImageDataToEEPROM(const struct AvailDataInfo *__xdata avail) if (nextImgSlot >= imgSlots) nextImgSlot = 0; if (nextImgSlot == startingSlot) { powerDown(INIT_EEPROM); +#ifdef DEBUGPROTO pr("No slots available. Too many images in the slideshow?\n"); +#endif return true; } struct EepromImageHeader __xdata *eih = (struct EepromImageHeader __xdata *)blockbuffer; @@ -768,8 +812,9 @@ static bool downloadImageDataToEEPROM(const struct AvailDataInfo *__xdata avail) wdtDeviceReset(); eraseSuccess: powerDown(INIT_EEPROM); +#ifdef DEBUGPROTO pr("new download, writing to slot %d\n", xferImgSlot); - +#endif // start, or restart the transfer. Copy data from the AvailDataInfo struct, and the struct intself. This forces a new transfer curBlock.blockId = 0; xMemCopy8(&(curBlock.ver), &(avail->dataVer)); @@ -806,6 +851,16 @@ static bool downloadImageDataToEEPROM(const struct AvailDataInfo *__xdata avail) } // no more data, download complete + // validate MD5 + powerUp(INIT_EEPROM); +#ifdef VALIDATE_IMAGE_MD5 + if (!validateMD5(getAddressForSlot(xferImgSlot) + sizeof(struct EepromImageHeader), imageSize)) { + // if not valid, restart transfer from the beginning + curBlock.blockId = 0; + powerDown(INIT_EEPROM); + return false; + } +#endif // borrow the blockbuffer temporarily struct EepromImageHeader __xdata *eih = (struct EepromImageHeader __xdata *)blockbuffer; xMemCopy8(&eih->version, &xferDataInfo.dataVer); @@ -818,19 +873,19 @@ static bool downloadImageDataToEEPROM(const struct AvailDataInfo *__xdata avail) #ifdef DEBUGBLOCKS pr("Now writing datatype 0x%02X to slot %d\n", xferDataInfo.dataType, xferImgSlot); #endif - powerUp(INIT_EEPROM); eepromWrite(getAddressForSlot(xferImgSlot), eih, sizeof(struct EepromImageHeader)); powerDown(INIT_EEPROM); return true; } +struct imageDataTypeArgStruct __xdata arg = {0}; // this is related to the function below, but if declared -inside- the function, it gets cleared during sleep... inline bool processImageDataAvail(struct AvailDataInfo *__xdata avail) { - struct imageDataTypeArgStruct arg; *((uint8_t *)arg) = avail->dataTypeArgument; - if (arg.preloadImage) { +#ifdef DEBUGPROTO pr("Preloading image with type 0x%02X from arg 0x%02X\n", arg.specialType, avail->dataTypeArgument); +#endif powerUp(INIT_EEPROM); switch (arg.specialType) { // check if a slot with this argument is already set; if so, erase. Only one of each arg type should exist @@ -855,11 +910,14 @@ inline bool processImageDataAvail(struct AvailDataInfo *__xdata avail) { break; } powerDown(INIT_EEPROM); - +#ifdef DEBUGPROTO pr("downloading preload image...\n"); +#endif if (downloadImageDataToEEPROM(avail)) { // sets xferImgSlot to the right slot +#ifdef DEBUGPROTO pr("preload complete!\n"); +#endif powerUp(INIT_RADIO); sendXferComplete(); powerDown(INIT_RADIO); @@ -872,8 +930,9 @@ inline bool processImageDataAvail(struct AvailDataInfo *__xdata avail) { // check if we're currently displaying this data payload if (xMemEqual((const void *__xdata) & avail->dataVer, (const void *__xdata)curDispDataVer, 8)) { // currently displayed, not doing anything except for sending an XFC - +#ifdef DEBUGPROTO pr("currently shown image, send xfc\n"); +#endif powerUp(INIT_RADIO); sendXferComplete(); powerDown(INIT_RADIO); @@ -904,11 +963,15 @@ inline bool processImageDataAvail(struct AvailDataInfo *__xdata avail) { drawImageFromEeprom(findImgSlot, arg.lut); powerDown(INIT_EPD | INIT_EEPROM); } else { - // not found in cache, prepare to download +// not found in cache, prepare to download +#ifdef DEBUGPROTO pr("downloading image...\n"); +#endif if (downloadImageDataToEEPROM(avail)) { - // sets xferImgSlot to the right slot +// sets xferImgSlot to the right slot +#ifdef DEBUGPROTO pr("download complete!\n"); +#endif powerUp(INIT_RADIO); sendXferComplete(); powerDown(INIT_RADIO); @@ -942,23 +1005,42 @@ bool processAvailDataInfo(struct AvailDataInfo *__xdata avail) { case DATATYPE_FW_UPDATE: powerUp(INIT_EEPROM); if (downloadFWUpdate(avail)) { - pr("firmware download complete, doing update.\n"); - - powerUp(INIT_EPD); - showApplyUpdate(); - +#if defined(DEBUGOTA) + pr("OTA: Download complete\n"); +#endif powerUp(INIT_RADIO); sendXferComplete(); powerDown(INIT_RADIO); powerUp(INIT_EEPROM); - wdt60s(); - eepromReadStart(eeSize - OTA_UPDATE_SIZE); - selfUpdate(); + if (validateFWMagic()) { +#if defined(DEBUGOTA) + pr("OTA: Valid magic number\n"); +#endif + powerUp(INIT_EPD); + showApplyUpdate(); + wdt60s(); + eepromReadStart(eeSize - OTA_UPDATE_SIZE); + selfUpdate(); + // ends in WDT reset + } else { +#if defined(DEBUGOTA) + pr("OTA: Invalid magic number!\n"); +#endif + fastNextCheckin = true; + powerDown(INIT_EEPROM); + wakeUpReason = WAKEUP_REASON_FAILED_OTA_FW; + powerUp(INIT_EPD); + showFailedUpdate(); + powerDown(INIT_EPD); + memset(curDispDataVer, 0x00, 8); + } + } else { return false; } break; + case DATATYPE_NFC_URL_DIRECT: case DATATYPE_NFC_RAW_CONTENT: // Handle data for the NFC IC (if we have it) @@ -971,11 +1053,15 @@ bool processAvailDataInfo(struct AvailDataInfo *__xdata avail) { powerDown(INIT_RADIO); return true; } - +#ifndef LEAN_VERSION +#ifdef DEBUGPROTO pr("NFC URL received\n"); +#endif if (xferDataInfo.dataSize == 0 && xMemEqual((const void *__xdata) & avail->dataVer, (const void *__xdata) & xferDataInfo.dataVer, 8)) { // we've already downloaded this NFC data, disregard and send XFC +#ifdef DEBUGPROTO pr("this was the same as the last transfer, disregard\n"); +#endif powerUp(INIT_RADIO); sendXferComplete(); powerDown(INIT_RADIO); @@ -1005,10 +1091,14 @@ bool processAvailDataInfo(struct AvailDataInfo *__xdata avail) { return true; } return false; +#endif break; + case DATATYPE_TAG_CONFIG_DATA: if (xferDataInfo.dataSize == 0 && xMemEqual((const void *__xdata) & avail->dataVer, (const void *__xdata) & xferDataInfo.dataVer, 8)) { +#ifdef DEBUGPROTO pr("this was the same as the last transfer, disregard\n"); +#endif powerUp(INIT_RADIO); sendXferComplete(); powerDown(INIT_RADIO); @@ -1032,7 +1122,9 @@ bool processAvailDataInfo(struct AvailDataInfo *__xdata avail) { return false; break; case DATATYPE_COMMAND_DATA: +#ifdef DEBUGPROTO pr("CMD received\n"); +#endif powerUp(INIT_RADIO); sendXferComplete(); powerDown(INIT_RADIO); @@ -1051,9 +1143,13 @@ bool processAvailDataInfo(struct AvailDataInfo *__xdata avail) { return true; } #ifdef EPD_SSD1619 +#ifdef DEBUGPROTO pr("OTA LUT received\n"); +#endif if (xferDataInfo.dataSize == 0 && xMemEqual((const void *__xdata) & avail->dataVer, (const void *__xdata) & xferDataInfo.dataVer, 8)) { +#ifdef DEBUGPROTO pr("this was the same as the last transfer, disregard\n"); +#endif powerUp(INIT_RADIO); sendXferComplete(); powerDown(INIT_RADIO); @@ -1079,6 +1175,48 @@ bool processAvailDataInfo(struct AvailDataInfo *__xdata avail) { return true; } +bool validateMD5(uint32_t __xdata addr, uint16_t __xdata len) { + md5Init(); + while (len) { + eepromRead(addr, blockbuffer, 256); + if (len >= 256) { + md5Update(blockbuffer, 256); + len -= 256; + addr += 256; + } else { + md5Update(blockbuffer, len); + len = 0; + } + } + md5Finalize(); + if (xMemEqual((const void *__xdata)ctxdigest, (const void *__xdata) & xferDataInfo.dataVer, 8)) { +#ifdef DEBUGPROTO + pr("MD5 pass!"); +#endif + return true; + } else { +#ifdef DEBUGPROTO + pr("MD5 fail..."); +#endif + return false; + } +} + +bool validateFWMagic() { + flashRead(0x8b, (void *)(blockbuffer + 1024), 256); + eepromRead(eeSize - OTA_UPDATE_SIZE, blockbuffer, 256); + if (xMemEqual((const void *__xdata)(blockbuffer + 1024), (const void *__xdata)(blockbuffer + 0x8b), 8)) { +#ifdef DEBUGPROTO + pr("magic number matches! good fw"); +#endif + return true; + } else { +#ifdef DEBUGPROTO + pr("this probably isn't a (recent) firmware file\n"); +#endif + return false; + } +} void initializeProto() { getNumSlots(); curHighSlotId = getHighSlotId(); diff --git a/zbs243_Tag_FW/syncedproto.h b/zbs243_Tag_FW/syncedproto.h index 6e8ca280..bfb6bb0a 100755 --- a/zbs243_Tag_FW/syncedproto.h +++ b/zbs243_Tag_FW/syncedproto.h @@ -9,6 +9,8 @@ extern uint8_t __xdata currentChannel; extern uint8_t __xdata APmac[]; extern uint8_t __xdata curImgSlot; +extern bool __xdata fastNextCheckin; + //extern void setupRadio(void); //extern void killRadio(void); @@ -35,4 +37,7 @@ extern bool processAvailDataInfo(struct AvailDataInfo *__xdata avail); extern void initializeProto(); extern uint8_t detectAP(const uint8_t channel); +extern bool validateMD5(uint32_t __xdata addr, uint16_t __xdata len); +extern bool validateFWMagic(); + #endif \ No newline at end of file diff --git a/zbs243_Tag_FW/userinterface.c b/zbs243_Tag_FW/userinterface.c index 9a4e7882..8984d9e3 100755 --- a/zbs243_Tag_FW/userinterface.c +++ b/zbs243_Tag_FW/userinterface.c @@ -76,6 +76,19 @@ void addOverlay() { } else { lowBatteryShown = false; } + +#ifdef ISDEBUGBUILD +#if (SCREEN_WIDTH == 152) + epdPrintBegin(139, 151, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); +#elif (SCREEN_WIDTH == 400) + epdPrintBegin(87, 0, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); +#elif (SCREEN_WIDTH == 128) + epdPrintBegin(87, 0, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_RED); +#endif + epdpr("DEBUG"); + epdPrintEnd(); +#endif + } void afterFlashScreenSaver() { @@ -203,7 +216,7 @@ void showSplashScreen() { if (displayCustomImage(CUSTOM_IMAGE_SPLASHSCREEN)) return; powerUp(INIT_EPD); - #if (HW_TYPE != SOLUM_M2_BW_29_LOWTEMP) +#if (HW_TYPE != SOLUM_M2_BW_29_LOWTEMP) selectLUT(EPD_LUT_NO_REPEATS); #endif @@ -235,14 +248,27 @@ void showSplashScreen() { epdpr("zbs154 %04X%s", fwVersion, fwVersionSuffix); epdPrintEnd(); +#ifdef ISDEBUGBUILD + epdPrintBegin(5, 78, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_RED); + epdpr("DEBUG"); + epdPrintEnd(); +#endif + + #endif #if (SCREEN_WIDTH == 128) // 2.9" epdPrintBegin(0, 295, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - epdpr("Starting"); + epdpr("OpenEPaperLink"); epdPrintEnd(); +#ifdef ISDEBUGBUILD + epdPrintBegin(35, 280, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + epdpr("DEBUG"); + epdPrintEnd(); +#endif + epdPrintBegin(64, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); addCapabilities(); epdPrintEnd(); @@ -268,11 +294,6 @@ void showSplashScreen() { loadRawBitmap(oepli, 0, 12, EPD_COLOR_BLACK); loadRawBitmap(cloud, 0, 0, EPD_COLOR_RED); #endif - // lutTest(); - // drawLineVertical(EPD_COLOR_RED, 64, 10, 286); - // drawLineVertical(EPD_COLOR_BLACK, 65, 10, 286); - - // timerDelay(TIMER_TICKS_PER_SECOND * 4); #endif #if (SCREEN_WIDTH == 400) // 4.2" epdPrintBegin(3, 3, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); @@ -331,13 +352,33 @@ void showApplyUpdate() { drawNoWait(); } +void showFailedUpdate() { + setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); + selectLUT(1); + clearScreen(); + setColorMode(EPD_MODE_IGNORE, EPD_MODE_NORMAL); +#if (SCREEN_WIDTH == 152) + epdPrintBegin(18, 60, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); +#endif +#if (SCREEN_WIDTH == 128) + epdPrintBegin(48, 270, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); +#endif + +#if (SCREEN_WIDTH == 400) + epdPrintBegin(68, 134, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); +#endif + epdpr("Invalid OTA FW!"); + epdPrintEnd(); + drawWithSleep(); +} + void showAPFound() { if (displayCustomImage(CUSTOM_IMAGE_APFOUND)) return; powerUp(INIT_EPD | INIT_EEPROM); clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); - #if (HW_TYPE != SOLUM_M2_BW_29_LOWTEMP) +#if (HW_TYPE != SOLUM_M2_BW_29_LOWTEMP) selectLUT(1); #endif @@ -451,7 +492,7 @@ void showAPFound() { void showNoAP() { if (displayCustomImage(CUSTOM_IMAGE_NOAPFOUND)) return; powerUp(INIT_EPD | INIT_EEPROM); - #if (HW_TYPE != SOLUM_M2_BW_29_LOWTEMP) +#if (HW_TYPE != SOLUM_M2_BW_29_LOWTEMP) selectLUT(EPD_LUT_NO_REPEATS); #endif diff --git a/zbs243_Tag_FW/userinterface.h b/zbs243_Tag_FW/userinterface.h index a3fbce46..1cd87d97 100755 --- a/zbs243_Tag_FW/userinterface.h +++ b/zbs243_Tag_FW/userinterface.h @@ -8,7 +8,10 @@ bool displayCustomImage(uint8_t imagetype); void afterFlashScreenSaver(); void showSplashScreen(); + void showApplyUpdate(); +void showFailedUpdate(); + void showAPFound(); void showNoAP(); void showLongTermSleep(); diff --git a/zbs243_shared/bitmaps.h b/zbs243_shared/bitmaps.h index 1a4e4b10..80221f03 100644 --- a/zbs243_shared/bitmaps.h +++ b/zbs243_shared/bitmaps.h @@ -8,6 +8,7 @@ #ifndef LEAN_VERSION + static const uint8_t __code oepli[] = { 128, 26, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, diff --git a/zbs243_shared/board/ssd1619.c b/zbs243_shared/board/ssd1619.c index 23e53e1e..054329d3 100755 --- a/zbs243_shared/board/ssd1619.c +++ b/zbs243_shared/board/ssd1619.c @@ -198,9 +198,9 @@ static void epdReset() { timerDelay(TIMER_TICKS_PER_SECOND / 1000); shortCommand(CMD_SOFT_RESET); // software reset - timerDelay(TIMER_TICKS_PER_SECOND / 1000); + timerDelay(TIMER_TICKS_PER_SECOND / 500); shortCommand(CMD_SOFT_RESET2); - timerDelay(TIMER_TICKS_PER_SECOND / 1000); + timerDelay(TIMER_TICKS_PER_SECOND / 500); } void epdConfigGPIO(bool setup) { // data / _command: 2.2 @@ -233,7 +233,7 @@ void epdEnterSleep() { P2_0 = 1; timerDelay(50); shortCommand(CMD_SOFT_RESET2); - epdBusyWait(TIMER_TICKS_PER_MS * 15); + epdBusyWait(TIMER_TICKS_PER_MS * 150); shortCommand1(CMD_ENTER_SLEEP, 0x03); isInited = false; } @@ -291,11 +291,11 @@ uint16_t epdGetBattery(void) { shortCommand1(CMD_DISP_UPDATE_CTRL2, SCREEN_CMD_CLOCK_ON | SCREEN_CMD_ANALOG_ON); shortCommand(CMD_ACTIVATION); - epdBusyWait(TIMER_TICKS_PER_MS * 100); + epdBusyWait(TIMER_TICKS_PER_MS * 150); for (val = 3; val < 8; val++) { shortCommand1(CMD_SETUP_VOLT_DETECT, val); - epdBusyWait(TIMER_TICKS_PER_MS * 100); + epdBusyWait(TIMER_TICKS_PER_MS * 150); if (epdGetStatus() & 0x10) { // set if voltage is less than threshold ( == 1.9 + val / 10) voltage = 1850 + mathPrvMul8x8(val, 100); break; @@ -400,7 +400,9 @@ void selectLUT(uint8_t lut) { if (dispLutSize == 0) { dispLutSize = getLutSize(); dispLutSize /= 10; + #ifdef DEBUGEPD pr("lut size = %d\n", dispLutSize); + #endif #ifdef PRINT_LUT dump(waveformbuffer, LUT_BUFFER_SIZE); #endif @@ -721,6 +723,8 @@ static void pushYFontBytesToEPD(uint8_t byte1, uint8_t byte2) { } } void writeCharEPD(uint8_t c) { + c-=0x20; + // Writes a single character to the framebuffer bool empty = true; for (uint8_t i = 0; i < 20; i++) { diff --git a/zbs243_shared/font.h b/zbs243_shared/font.h index 3dece553..e06b1681 100644 --- a/zbs243_shared/font.h +++ b/zbs243_shared/font.h @@ -1,36 +1,5 @@ -static const uint8_t __code font[256][20]={ // https://raw.githubusercontent.com/basti79/LCD-fonts/master/10x16_vertikal_MSB_1.h -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x00 -{0xE0,0x01,0x30,0x03,0x50,0x02,0x28,0x05,0x28,0x04,0x28,0x04,0x28,0x05,0x50,0x02,0x30,0x03,0xE0,0x01}, // 0x01 -{0xE0,0x01,0xF0,0x03,0xB0,0x03,0xD8,0x06,0xD8,0x07,0xD8,0x07,0xD8,0x06,0xB0,0x03,0xF0,0x03,0xE0,0x01}, // 0x02 -{0x00,0x3E,0x80,0x7F,0xE0,0x7F,0xF0,0x7F,0xF8,0x3F,0xF8,0x3F,0xF0,0x7F,0xE0,0x7F,0x80,0x7F,0x00,0x3E}, // 0x03 -{0x00,0x01,0x80,0x03,0xC0,0x0F,0xE0,0x1F,0xF8,0x7F,0xF0,0x1F,0xE0,0x0F,0xC0,0x07,0x80,0x03,0x00,0x01}, // 0x04 -{0x80,0x03,0xC0,0x07,0xC0,0x07,0xC0,0x3F,0xF8,0x7F,0xB8,0x7F,0xC0,0x3F,0xC0,0x07,0xC0,0x07,0x80,0x03}, // 0x05 -{0x80,0x03,0xC0,0x07,0xC0,0x0F,0xC0,0x1F,0xF8,0x3F,0xB8,0x7F,0xC0,0x1F,0xC0,0x0F,0xC0,0x07,0x80,0x03}, // 0x06 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x07 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x08 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x09 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x0A -{0xE0,0x01,0x10,0x02,0x08,0x04,0x08,0x04,0x08,0x04,0x08,0x24,0x10,0x2E,0xE0,0x71,0x00,0x70,0x00,0x18}, // 0x0B -{0x00,0x00,0x00,0x1E,0x20,0x21,0xA0,0x40,0xF8,0x40,0xA0,0x40,0xA0,0x41,0x00,0x21,0x00,0x1E,0x00,0x00}, // 0x0C -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x0D -{0x06,0x00,0x0E,0x00,0x0E,0x00,0xFC,0x1F,0x00,0x18,0x30,0x10,0x70,0x30,0x70,0x20,0xE0,0x7F,0x00,0x00}, // 0x0E -{0x00,0x01,0xC0,0x0F,0xC0,0x04,0x40,0x08,0x60,0x18,0x40,0x08,0x40,0x04,0xC0,0x0C,0xC0,0x0B,0x00,0x01}, // 0x0F -{0xF8,0x0F,0xF0,0x07,0xF0,0x07,0xE0,0x03,0xE0,0x03,0xE0,0x03,0xC0,0x01,0xC0,0x01,0x80,0x00,0x80,0x00}, // 0x10 -{0x80,0x00,0x80,0x00,0xC0,0x01,0xC0,0x01,0xE0,0x03,0xE0,0x03,0xE0,0x03,0xF0,0x07,0xF0,0x07,0xF8,0x0F}, // 0x11 -{0x00,0x00,0x00,0x00,0x08,0x10,0x04,0x20,0xFE,0x7F,0x04,0x20,0x08,0x10,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x12 -{0x00,0x00,0x00,0x00,0xD8,0x7F,0x00,0x00,0x00,0x00,0xD8,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x13 -{0x00,0x00,0x00,0x38,0x00,0x7C,0x00,0x7E,0xFE,0x7F,0x00,0x40,0x00,0x40,0xFE,0x7F,0x00,0x00,0x00,0x00}, // 0x14 -{0x00,0x00,0x00,0x00,0x86,0x3B,0xC2,0x4C,0x42,0x44,0x62,0x46,0x32,0x42,0xDC,0x41,0x00,0x00,0x00,0x00}, // 0x15 -{0x00,0x00,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x07,0x00,0x00}, // 0x16 -{0x00,0x00,0x00,0x00,0x11,0x10,0x09,0x20,0xFD,0x7F,0x09,0x20,0x11,0x10,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x17 -{0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x20,0xFE,0x7F,0x00,0x20,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x18 -{0x00,0x00,0x00,0x00,0x08,0x00,0x04,0x00,0xFE,0x7F,0x04,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x19 -{0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xA0,0x02,0xC0,0x01,0x80,0x00,0x00,0x00}, // 0x1A -{0x00,0x00,0x80,0x00,0xC0,0x01,0xA0,0x02,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x00,0x00}, // 0x1B -{0x00,0x00,0xF8,0x07,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x00,0x00}, // 0x1C -{0x00,0x00,0x80,0x00,0xC0,0x01,0xA0,0x02,0x80,0x00,0x80,0x00,0xA0,0x02,0xC0,0x01,0x80,0x00,0x00,0x00}, // 0x1D -{0x08,0x00,0x18,0x00,0x78,0x00,0xF8,0x01,0xF8,0x03,0xF8,0x0F,0xF8,0x03,0xF8,0x00,0x38,0x00,0x08,0x00}, // 0x1E -{0x00,0x08,0x00,0x0C,0x00,0x0F,0xC0,0x0F,0xE0,0x0F,0xF8,0x0F,0xE0,0x0F,0x80,0x0F,0x00,0x0E,0x00,0x08}, // 0x1F +static const uint8_t __code font[96][20]={ // https://raw.githubusercontent.com/basti79/LCD-fonts/master/10x16_vertikal_MSB_1.h + {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x20 {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xD8,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x21 {0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x22 @@ -127,132 +96,4 @@ static const uint8_t __code font[256][20]={ // https://raw.githubusercontent.com {0x00,0x00,0x00,0x00,0x02,0x40,0x02,0x40,0x02,0x40,0x7C,0x3F,0x80,0x00,0x80,0x00,0x00,0x00,0x00,0x00}, // 0x7D {0xC0,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x80,0x00,0x80,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0x80,0x01}, // 0x7E {0x00,0x00,0xF8,0x01,0x08,0x03,0x08,0x04,0x08,0x08,0x08,0x04,0x08,0x03,0xF8,0x01,0x00,0x00,0x00,0x00}, // 0x7F -{0xC0,0x03,0x30,0x0C,0x10,0x08,0x08,0x10,0x08,0x10,0x09,0x10,0x0D,0x10,0x0B,0x18,0x00,0x00,0x00,0x00}, // 0x80 -{0x00,0x00,0xF0,0x07,0x08,0x20,0x08,0x00,0x08,0x00,0x10,0x20,0xF8,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x81 -{0x00,0x00,0xE0,0x01,0x90,0x02,0x88,0x04,0x88,0x24,0x88,0x44,0x88,0x04,0x88,0x03,0x00,0x00,0x00,0x00}, // 0x82 -{0x00,0x00,0x30,0x00,0x48,0x24,0x88,0x44,0x88,0x44,0x90,0x24,0xF8,0x03,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x83 -{0x00,0x00,0x30,0x00,0x48,0x24,0x88,0x04,0x88,0x04,0x90,0x24,0xF8,0x03,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x84 -{0x00,0x00,0x30,0x00,0x48,0x44,0x88,0x24,0x88,0x04,0x90,0x04,0xF8,0x03,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x85 -{0x00,0x00,0x30,0x00,0x48,0x04,0x88,0x44,0x88,0xA4,0x90,0x44,0xF8,0x03,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x86 -{0x00,0x00,0xE0,0x01,0x10,0x02,0x08,0x04,0x09,0x04,0x0D,0x04,0x0B,0x04,0x08,0x04,0x00,0x00,0x00,0x00}, // 0x87 -{0x00,0x00,0xE0,0x01,0x90,0x22,0x88,0x44,0x88,0x44,0x88,0x24,0x88,0x04,0x88,0x03,0x00,0x00,0x00,0x00}, // 0x88 -{0x00,0x00,0xE0,0x01,0x90,0x02,0x88,0x24,0x88,0x04,0x88,0x04,0x88,0x24,0x88,0x03,0x00,0x00,0x00,0x00}, // 0x89 -{0x00,0x00,0xE0,0x01,0x90,0x02,0x88,0x44,0x88,0x24,0x88,0x04,0x88,0x04,0x88,0x03,0x00,0x00,0x00,0x00}, // 0x8A -{0x00,0x00,0x00,0x04,0x00,0x24,0x00,0x04,0xF8,0x07,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x8B -{0x00,0x00,0x00,0x04,0x00,0x24,0x00,0x44,0xF8,0x47,0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x8C -{0x00,0x00,0x00,0x04,0x00,0x04,0x00,0x44,0xF8,0x27,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x8D -{0x08,0x00,0x70,0x00,0xC0,0x81,0x40,0x0E,0x40,0x18,0x40,0x0C,0x40,0x83,0xC0,0x00,0x30,0x00,0x08,0x00}, // 0x8E -{0x08,0x00,0x70,0x00,0xC0,0x01,0x40,0x4E,0x40,0xB0,0x40,0xB8,0x40,0x4F,0xC0,0x01,0x70,0x00,0x08,0x00}, // 0x8F -{0x00,0x00,0xF8,0x1F,0x88,0x10,0x88,0x10,0x88,0x50,0x88,0x90,0x88,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0x90 -{0x70,0x04,0xC8,0x04,0x88,0x04,0x88,0x04,0xF0,0x03,0x98,0x04,0x88,0x04,0x88,0x04,0x88,0x03,0x00,0x00}, // 0x91 -{0x08,0x00,0x30,0x00,0xE0,0x01,0x20,0x06,0x20,0x18,0xF8,0x1F,0x88,0x10,0x88,0x10,0x08,0x10,0x00,0x00}, // 0x92 -{0x00,0x00,0xE0,0x01,0x10,0x22,0x08,0x44,0x08,0x44,0x08,0x24,0x10,0x02,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0x93 -{0x00,0x00,0xE0,0x01,0x10,0x22,0x08,0x04,0x08,0x04,0x08,0x24,0x10,0x02,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0x94 -{0x00,0x00,0xE0,0x01,0x10,0x42,0x08,0x24,0x08,0x04,0x08,0x04,0x10,0x02,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0x95 -{0x00,0x00,0xF0,0x07,0x08,0x20,0x08,0x40,0x08,0x40,0x10,0x20,0xF8,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x96 -{0x00,0x00,0xF0,0x07,0x08,0x40,0x08,0x20,0x08,0x00,0x10,0x00,0xF8,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x97 -{0x01,0x04,0x01,0x03,0xC1,0x20,0x62,0x00,0x1C,0x00,0x18,0x20,0x60,0x00,0x80,0x00,0x00,0x03,0x00,0x04}, // 0x98 -{0x00,0x00,0xE0,0x07,0x10,0x88,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x90,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0x99 -{0x00,0x00,0xE0,0x1F,0x18,0x80,0x08,0x00,0x08,0x00,0x08,0x00,0x10,0x80,0xE0,0x1F,0x00,0x00,0x00,0x00}, // 0x9A -{0x00,0x00,0xE8,0x01,0x10,0x02,0x28,0x04,0xC8,0x04,0x08,0x05,0x10,0x02,0xE0,0x05,0x00,0x00,0x00,0x00}, // 0x9B -{0x00,0x00,0x00,0x00,0x08,0x00,0x18,0x02,0xE8,0x3F,0x08,0x42,0x08,0x42,0x08,0x40,0x00,0x00,0x00,0x00}, // 0x9C -{0x00,0x00,0xE8,0x07,0x30,0x08,0x68,0x10,0x88,0x10,0x08,0x11,0x08,0x16,0x10,0x0C,0xE0,0x17,0x00,0x00}, // 0x9D -{0x00,0x00,0x08,0x04,0x10,0x02,0x20,0x01,0xC0,0x00,0xC0,0x00,0x20,0x01,0x10,0x02,0x08,0x04,0x00,0x00}, // 0x9E -{0x00,0x00,0x01,0x00,0x01,0x04,0x01,0x04,0xFE,0x7F,0x00,0x84,0x00,0x84,0x00,0x80,0x00,0x00,0x00,0x00}, // 0x9F -{0x00,0x00,0x30,0x00,0x48,0x04,0x88,0x04,0x88,0x24,0x90,0x44,0xF8,0x03,0x08,0x00,0x00,0x00,0x00,0x00}, // 0xA0 -{0x00,0x00,0x00,0x04,0x00,0x04,0x00,0x04,0xF8,0x27,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xA1 -{0x00,0x00,0xE0,0x01,0x10,0x02,0x08,0x04,0x08,0x24,0x08,0x44,0x10,0x02,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0xA2 -{0x00,0x00,0xF0,0x07,0x08,0x00,0x08,0x00,0x08,0x20,0x10,0x40,0xF8,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xA3 -{0x00,0x00,0xF8,0x07,0x00,0x23,0x00,0x42,0x00,0x24,0x00,0x24,0x00,0x44,0xF8,0x03,0x00,0x00,0x00,0x00}, // 0xA4 -{0x00,0x00,0xF8,0x1F,0x00,0x48,0x00,0x86,0x80,0xC1,0x60,0x40,0x10,0x80,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0xA5 -{0x00,0x00,0x00,0x00,0x00,0x4C,0x00,0x52,0x00,0x52,0x00,0x52,0x00,0x3E,0x00,0x02,0x00,0x00,0x00,0x00}, // 0xA6 -{0x00,0x00,0x00,0x00,0x00,0x3C,0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x3C,0x00,0x00,0x00,0x00}, // 0xA7 -{0x00,0x00,0x0E,0x00,0x13,0x00,0x11,0x00,0x21,0x00,0xC1,0x06,0x01,0x00,0x07,0x00,0x00,0x00,0x00,0x00}, // 0xA8 -{0x00,0x00,0x00,0x1C,0x00,0x22,0x00,0x5D,0x00,0x55,0x00,0x5D,0x00,0x22,0x00,0x1C,0x00,0x00,0x00,0x00}, // 0xA9 -{0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xE0,0x01,0x00,0x00}, // 0xAA -{0x08,0x40,0x30,0x7E,0x40,0x00,0x80,0x01,0x00,0x06,0x00,0x08,0x08,0x31,0x38,0x41,0x28,0x01,0xC8,0x00}, // 0xAB -{0x08,0x40,0x30,0x7E,0x40,0x00,0x80,0x01,0x00,0x06,0x60,0x08,0xA0,0x30,0x20,0x41,0xF8,0x01,0x20,0x00}, // 0xAC -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xAD -{0x00,0x00,0x80,0x00,0x40,0x01,0x20,0x02,0x10,0x04,0x80,0x00,0x40,0x01,0x20,0x02,0x10,0x04,0x00,0x00}, // 0xAE -{0x00,0x00,0x10,0x04,0x20,0x02,0x40,0x01,0x80,0x00,0x10,0x04,0x20,0x02,0x40,0x01,0x80,0x00,0x00,0x00}, // 0xAF -{0x36,0xDB,0x36,0xDB,0x00,0x00,0x36,0xDB,0x36,0xDB,0x00,0x00,0x36,0xDB,0x36,0xDB,0x00,0x00,0x00,0x00}, // 0xB0 -{0xDB,0x36,0xDB,0x36,0x36,0xDB,0xFF,0xFF,0xDB,0x36,0x36,0xDB,0xFF,0xFF,0xDB,0x36,0x36,0xDB,0x36,0xDB}, // 0xB1 -{0xFF,0xFF,0xFF,0xFF,0x36,0xDB,0xFF,0xFF,0xFF,0xFF,0x36,0xDB,0xFF,0xFF,0xFF,0xFF,0x36,0xDB,0x36,0xDB}, // 0xB2 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xB3 -{0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xB4 -{0x08,0x00,0x70,0x00,0xC0,0x01,0x40,0x0E,0x40,0x58,0x40,0x8C,0x40,0x03,0xC0,0x00,0x30,0x00,0x08,0x00}, // 0xB5 -{0x08,0x00,0x70,0x00,0xC0,0x01,0x40,0x4E,0x40,0x98,0x40,0x8C,0x40,0x43,0xC0,0x00,0x30,0x00,0x08,0x00}, // 0xB6 -{0x08,0x00,0x70,0x00,0xC0,0x01,0x40,0x8E,0x40,0x58,0x40,0x0C,0x40,0x03,0xC0,0x00,0x30,0x00,0x08,0x00}, // 0xB7 -{0xC0,0x0F,0x30,0x30,0x98,0x67,0xC8,0x4C,0x48,0x48,0x48,0x48,0x58,0x68,0x30,0x30,0xC0,0x0F,0x00,0x00}, // 0xB8 -{0x40,0x01,0x40,0x01,0x40,0x01,0x7F,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xB9 -{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBA -{0x40,0x01,0x40,0x01,0x40,0x01,0x7F,0x01,0x00,0x01,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBB -{0x40,0x01,0x40,0x01,0x40,0x01,0x40,0xFF,0x40,0x00,0xC0,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBC -{0x00,0x00,0xC0,0x0F,0x20,0x10,0x30,0x20,0x10,0x20,0xF8,0x7F,0x10,0x20,0x10,0x20,0x00,0x00,0x00,0x00}, // 0xBD -{0x00,0x40,0x00,0x20,0x20,0x19,0x20,0x05,0xF8,0x03,0x20,0x05,0x20,0x09,0x00,0x10,0x00,0x20,0x00,0x40}, // 0xBE -{0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBF -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xFF,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00}, // 0xC0 -{0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0xFF,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00}, // 0xC1 -{0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFF,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00}, // 0xC2 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00}, // 0xC3 -{0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00}, // 0xC4 -{0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xFF,0xFF,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00}, // 0xC5 -{0x00,0x00,0x30,0x00,0x48,0x24,0x88,0x44,0x88,0x24,0x90,0x24,0xF8,0x43,0x08,0x00,0x00,0x00,0x00,0x00}, // 0xC6 -{0x08,0x00,0x70,0x00,0xC0,0x41,0x40,0x8E,0x40,0xD8,0x40,0x4C,0x40,0x83,0xC0,0x00,0x30,0x00,0x08,0x00}, // 0xC7 -{0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xFF,0x40,0x00,0x40,0xFF,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01}, // 0xC8 -{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x01,0x00,0x01,0x7F,0x01,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01}, // 0xC9 -{0x40,0x01,0x40,0x01,0x40,0x01,0x40,0xFF,0x40,0x00,0x40,0xFF,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01}, // 0xCA -{0x40,0x01,0x40,0x01,0x40,0x01,0x7F,0x01,0x00,0x01,0x7F,0x01,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01}, // 0xCB -{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x7F,0xFF,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01}, // 0xCC -{0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01}, // 0xCD -{0x40,0x01,0x40,0x01,0x40,0x01,0x7F,0xFF,0x00,0x00,0x7F,0xFF,0x40,0x01,0x40,0x01,0x40,0x01,0x40,0x01}, // 0xCE -{0x00,0x00,0x20,0x10,0xC0,0x0F,0x40,0x08,0x40,0x08,0x40,0x08,0x40,0x08,0xC0,0x0F,0x20,0x10,0x00,0x00}, // 0xCF -{0x00,0x00,0xE0,0x41,0x10,0x52,0x08,0x74,0x08,0x24,0x08,0x54,0x10,0x0E,0xE0,0x03,0x00,0x00,0x00,0x00}, // 0xD0 -{0x00,0x01,0xF8,0x1F,0x08,0x11,0x08,0x11,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00,0x00,0x00}, // 0xD1 -{0x00,0x00,0xF8,0x1F,0x88,0x10,0x88,0x50,0x88,0x90,0x88,0x90,0x88,0x50,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD2 -{0x00,0x00,0xF8,0x1F,0x88,0x90,0x88,0x10,0x88,0x10,0x88,0x10,0x88,0x90,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD3 -{0x00,0x00,0xF8,0x1F,0x88,0x10,0x88,0x10,0x88,0x90,0x88,0x50,0x88,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD4 -{0x00,0x00,0x00,0x04,0x00,0x04,0x00,0x04,0xF8,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xD5 -{0x00,0x00,0x08,0x10,0x08,0x10,0x08,0x10,0xF8,0x5F,0x08,0x90,0x08,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD6 -{0x00,0x00,0x08,0x10,0x08,0x10,0x08,0x50,0xF8,0x9F,0x08,0x90,0x08,0x50,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD7 -{0x00,0x00,0x08,0x10,0x08,0x90,0x08,0x10,0xF8,0x1F,0x08,0x10,0x08,0x90,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD8 -{0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xD9 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00}, // 0xDA -{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}, // 0xDB -{0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00}, // 0xDC -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xDD -{0x00,0x00,0x08,0x10,0x08,0x10,0x08,0x90,0xF8,0x5F,0x08,0x10,0x08,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xDE -{0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF}, // 0xDF -{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x10,0x08,0x50,0x08,0x90,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE0 -{0x00,0x00,0xF8,0x3F,0x00,0x40,0x00,0x40,0x08,0x47,0x88,0x38,0x48,0x00,0x30,0x00,0x00,0x00,0x00,0x00}, // 0xE1 -{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x50,0x08,0x90,0x08,0x90,0x08,0x50,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE2 -{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x90,0x08,0x50,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE3 -{0x00,0x00,0xE0,0x01,0x10,0x22,0x08,0x44,0x08,0x24,0x08,0x24,0x10,0x42,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0xE4 -{0x00,0x00,0xE0,0x07,0x10,0x48,0x08,0x90,0x08,0xD0,0x08,0x50,0x08,0x90,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE5 -{0x00,0x00,0xFF,0x07,0x10,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x10,0x00,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0xE6 -{0x00,0x00,0xFF,0x7F,0x10,0x02,0x08,0x04,0x08,0x04,0x08,0x04,0x10,0x06,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0xE7 -{0x00,0x00,0xF8,0x1F,0x20,0x04,0x20,0x04,0x20,0x04,0x20,0x04,0x40,0x04,0x80,0x03,0x00,0x00,0x00,0x00}, // 0xE8 -{0x00,0x00,0xE0,0x1F,0x18,0x00,0x08,0x00,0x08,0x40,0x08,0x80,0x10,0x00,0xE0,0x1F,0x00,0x00,0x00,0x00}, // 0xE9 -{0x00,0x00,0xE0,0x1F,0x18,0x00,0x08,0x40,0x08,0x80,0x08,0x80,0x10,0x40,0xE0,0x1F,0x00,0x00,0x00,0x00}, // 0xEA -{0x00,0x00,0xE0,0x1F,0x18,0x00,0x08,0x80,0x08,0x40,0x08,0x00,0x10,0x00,0xE0,0x1F,0x00,0x00,0x00,0x00}, // 0xEB -{0x01,0x04,0x01,0x03,0xC1,0x00,0x62,0x00,0x1C,0x20,0x18,0x40,0x60,0x00,0x80,0x00,0x00,0x03,0x00,0x04}, // 0xEC -{0x00,0x10,0x00,0x08,0x00,0x06,0x00,0x01,0xF8,0x40,0x00,0x81,0x00,0x02,0x00,0x04,0x00,0x08,0x00,0x10}, // 0xED -{0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80}, // 0xEE -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xEF -{0x00,0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x00,0x00,0x00,0x00}, // 0xF0 -{0x00,0x00,0x08,0x01,0x08,0x01,0x08,0x01,0x08,0x01,0xC8,0x07,0x08,0x01,0x08,0x01,0x08,0x01,0x00,0x00}, // 0xF1 -{0x05,0x00,0x05,0x00,0x05,0x00,0x05,0x00,0x05,0x00,0x05,0x00,0x05,0x00,0x05,0x00,0x05,0x00,0x05,0x00}, // 0xF2 -{0x08,0x42,0x10,0x52,0x20,0x5A,0xC0,0x6C,0x00,0x01,0x60,0x02,0xA0,0x0C,0x20,0x11,0xF8,0x21,0x20,0x40}, // 0xF3 -{0x00,0x00,0x00,0x38,0x00,0x7C,0x00,0x7E,0xFE,0x7F,0x00,0x40,0x00,0x40,0xFE,0x7F,0x00,0x00,0x00,0x00}, // 0xF4 -{0x00,0x00,0x00,0x00,0x86,0x3B,0xC2,0x4C,0x42,0x44,0x62,0x46,0x32,0x42,0xDC,0x41,0x00,0x00,0x00,0x00}, // 0xF5 -{0x00,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0x58,0x06,0x58,0x06,0x40,0x00,0x40,0x00,0x40,0x00,0x00,0x00}, // 0xF6 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x05,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xF7 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x00,0x48,0x00,0x48,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xF8 -{0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xF9 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFA -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFB -{0x00,0x00,0x00,0x00,0x00,0x42,0x00,0x52,0x00,0x52,0x00,0x52,0x00,0x6C,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFC -{0x00,0x00,0x00,0x00,0x00,0x42,0x00,0x46,0x00,0x4A,0x00,0x4A,0x00,0x32,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFD -{0x00,0x00,0xF8,0x07,0xF8,0x07,0xF8,0x07,0xF8,0x07,0xF8,0x07,0xF8,0x07,0xF8,0x07,0xF8,0x07,0x00,0x00}, // 0xFE -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} // 0xFF }; \ No newline at end of file diff --git a/zbs243_shared/soc/zbs243/uart.c b/zbs243_shared/soc/zbs243/uart.c index f02cb53e..49618145 100755 --- a/zbs243_shared/soc/zbs243/uart.c +++ b/zbs243_shared/soc/zbs243/uart.c @@ -2,42 +2,47 @@ #include "cpu.h" +#ifdef AP_FW #include "stdbool.h" -// #include "string.h" +#include "string.h" +#endif void uartInit(void) { // clock it up CLKEN |= 0x20; // configure baud rate UARTBRGH = 0x00; - +#ifdef AP_FW // UARTBRGL = 69; // nice. 230400 baud - // UARTBRGL = 70; // 79 == 200k - // UARTSTA = 0x12; // also set the "empty" bit else we wait forever for it to go up - UARTBRGL = 0x8A; // config for 115200 - UARTSTA = 0x10; // clear the register, don't trigger the interrupt just yet + //UARTBRGL = 70; // 79 == 200k IEN_UART0 = 1; + UARTBRGL = 0x8A; // config for 115200 +#else + UARTBRGL = 0x8A; // config for 115200 +#endif + UARTSTA = 0x12; // also set the "empty" bit else we wait forever for it to go up } +#ifndef AP_FW +void uartTx(uint8_t val) { + while (!(UARTSTA & (1 << 1))) + ; + UARTSTA &= ~(1 << 1); + UARTBUF = val; +} +#else + extern uint8_t __xdata blockbuffer[]; -volatile uint8_t __xdata txtail = 0; -volatile uint8_t __xdata txhead = 0; +volatile uint8_t txtail = 0; +volatile uint8_t txhead = 0; uint8_t __xdata txbuf[256] = {0}; volatile uint8_t __idata rxtail = 0; volatile uint8_t __idata rxhead = 0; uint8_t __xdata rxbuf[256] = {0}; -void UartTxWait() { - while (txhead != txtail) { - } -} - void uartTx(uint8_t val) { - while(txhead+1 == txtail){ - - } __critical { txbuf[txhead] = val; if (txhead == txtail) { @@ -62,8 +67,20 @@ uint8_t uartBytesAvail() { } uint8_t* __idata blockp; +uint8_t __idata cmd[3]; volatile bool __idata serialBypassActive = false; +void checkcommand(uint8_t rx) { + for (uint8_t c = 0; c < 2; c++) { + cmd[c] = cmd[c + 1]; + } + cmd[2] = rx; + if (strncmp(cmd, ">D>", 3) == 0) { + blockp = blockbuffer; + serialBypassActive = true; + } +} + void UART_IRQ1(void) __interrupt(0) { if (UARTSTA & 1) { // RXC UARTSTA &= 0xfe; @@ -76,6 +93,7 @@ void UART_IRQ1(void) __interrupt(0) { } else { rxbuf[rxhead] = UARTBUF; rxhead++; + // checkcommand(UARTBUF); } } if (UARTSTA & 2) { // TXC @@ -86,3 +104,4 @@ void UART_IRQ1(void) __interrupt(0) { } } } +#endif \ No newline at end of file diff --git a/zbs243_shared/soc/zbs243/uart.h b/zbs243_shared/soc/zbs243/uart.h index e699a73e..97385358 100644 --- a/zbs243_shared/soc/zbs243/uart.h +++ b/zbs243_shared/soc/zbs243/uart.h @@ -3,12 +3,17 @@ #include +//pre-configured for 115200 8n1 +//RX can be done but i do not need it + #pragma callee_saves uartInit void uartInit(void); #pragma callee_saves uartTx void uartTx(uint8_t val); + +#ifdef AP_FW void UART_IRQ1(void) __interrupt (0); #pragma callee_saves uartBytesAvail @@ -16,8 +21,6 @@ uint8_t uartBytesAvail(void); #pragma callee_saves uartRX uint8_t uartRx(); - -void UartTxWait(); - #endif +#endif \ No newline at end of file