From d2e2c619b31f0f088b118552e12122e52f4c69df Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Fri, 27 Jan 2023 20:19:40 +0100 Subject: [PATCH 01/18] small fixes small fixes to be also compatible for compiling in Windows --- ap_fw/cpu/8051/make.mk | 2 +- ap_fw/make.bat | 14 ++++++++++++++ ap_fw/makeit.exe | Bin 0 -> 219662 bytes esp32_fw/src/main.cpp | 2 +- 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 ap_fw/make.bat create mode 100644 ap_fw/makeit.exe diff --git a/ap_fw/cpu/8051/make.mk b/ap_fw/cpu/8051/make.mk index 60d739b0..837d8929 100644 --- a/ap_fw/cpu/8051/make.mk +++ b/ap_fw/cpu/8051/make.mk @@ -2,7 +2,7 @@ FLAGS += -Icpu/8051 FLAGS += -mmcs51 --std-sdcc2x --opt-code-size --peep-file cpu/8051/peep.def --fomit-frame-pointer SOURCES += cpu/8051/asmUtil.c -CC = /usr/local/bin/sdcc +CC = sdcc TARGETS = main.ihx main.bin OBJFILEEXT = rel diff --git a/ap_fw/make.bat b/ap_fw/make.bat new file mode 100644 index 00000000..8f656d19 --- /dev/null +++ b/ap_fw/make.bat @@ -0,0 +1,14 @@ +@echo off +makeit clean +makeit +del /s *.asm +del /s *.lst +del /s *.rst +del /s *.sym +del /s *.map +del /s *.mem +del /s *.ihx +del /s *.adb +del /s *.rel +del /s *.omf + diff --git a/ap_fw/makeit.exe b/ap_fw/makeit.exe new file mode 100644 index 0000000000000000000000000000000000000000..4201fb403ac4884fd5db41f69d4ab4ddc5d7e269 GIT binary patch literal 219662 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 literal 0 HcmV?d00001 diff --git a/esp32_fw/src/main.cpp b/esp32_fw/src/main.cpp index b800454a..efe79a2d 100644 --- a/esp32_fw/src/main.cpp +++ b/esp32_fw/src/main.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include "flasher.h" From 0a97f8a1e1353a1e5c023d2c6c2b1639398d9d07 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Fri, 27 Jan 2023 15:38:59 +0100 Subject: [PATCH 02/18] added button Place a button switch between TEST pin (P1.0) and GND + capacitor in parallel). Pressing button wakes up the tag and sends a request to the AP, including button state. --- ap_fw/main.c | 1 + esp32_fw/include/commstructs.h | 1 + esp32_fw/src/newproto.cpp | 2 +- tag_fw/fw29.bin | Bin 31540 -> 31545 bytes tag_fw/syncedproto.c | 37 +++++++++++++++++++++++++++++++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/ap_fw/main.c b/ap_fw/main.c index 573816c5..d5149570 100644 --- a/ap_fw/main.c +++ b/ap_fw/main.c @@ -60,6 +60,7 @@ struct AvailDataReq { uint8_t softVer; uint8_t hwType; uint8_t protoVer; + //uint8_t buttonState; } __packed; #define DATATYPE_NOUPDATE 0 diff --git a/esp32_fw/include/commstructs.h b/esp32_fw/include/commstructs.h index b9b6a5d6..8afb0a75 100644 --- a/esp32_fw/include/commstructs.h +++ b/esp32_fw/include/commstructs.h @@ -27,6 +27,7 @@ struct AvailDataReq { uint8_t softVer; uint8_t hwType; uint8_t protoVer; + //uint8_t buttonState; } __packed; struct espAvailDataReq { diff --git a/esp32_fw/src/newproto.cpp b/esp32_fw/src/newproto.cpp index 44900621..00f130f0 100644 --- a/esp32_fw/src/newproto.cpp +++ b/esp32_fw/src/newproto.cpp @@ -147,7 +147,7 @@ void processDataReq(struct espAvailDataReq* eadr) { uint8_t src[8]; // *((uint64_t*)src) = swap64(*((uint64_t*)adr->)); sprintf(buffer, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]); - // sprintf(buffer, "softVer, adr->batteryMv); + sprintf(buffer, "adr->protoVer); wsString((String)buffer); Serial.print(buffer); } diff --git a/tag_fw/fw29.bin b/tag_fw/fw29.bin index d90d2501e1c20a0f69cce0de86cdad32738caa62..60dda2ecc08db7bec02ac1ae751475ec272857df 100644 GIT binary patch delta 6918 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 diff --git a/tag_fw/syncedproto.c b/tag_fw/syncedproto.c index 10a43b93..0e571f88 100644 --- a/tag_fw/syncedproto.c +++ b/tag_fw/syncedproto.c @@ -68,6 +68,7 @@ struct AvailDataReq { uint8_t softVer; uint8_t hwType; uint8_t protoVer; + //uint8_t buttonState; } __packed; #define DATATYPE_NOUPDATE 0 @@ -252,8 +253,36 @@ void initAfterWake() { void doSleep(uint32_t __xdata t) { if(t>1000)pr("s=%lu\n ", t / 1000); powerPortsDownForSleep(); + + //Button setup on TEST pin 1.0 (input pullup) + uint8_t tmp_P1FUNC = P1FUNC; + uint8_t tmp_P1DIR = P1DIR; + uint8_t tmp_P1PULL = P1PULL; + uint8_t tmp_P1LVLSEL = P1LVLSEL; + + P1FUNC &=~ (1 << 0); + P1DIR |= (1 << 0); + P1PULL |= (1 << 0); + + P1LVLSEL |= (1 << 0); + P1CHSTA &=~ (1 << 0); + P0INTEN = 0; + P1INTEN = (1 << 0); + P2INTEN = 0; + P1CHSTA &=~ (1 << 0); + // sleepy sleepForMsec(t); + + P0INTEN = 0; + P1INTEN = 0; + P2INTEN = 0; + + P1FUNC = tmp_P1FUNC; + P1DIR = tmp_P1DIR; + P1PULL = tmp_P1PULL; + P1LVLSEL = tmp_P1LVLSEL; + initAfterWake(); } uint16_t getNextSleep() { @@ -295,6 +324,11 @@ void sendAvailDataReq() { txframe->srcPan = 0x4447; // TODO: send some meaningful data availreq->softVer = 1; + if (P1CHSTA && (1 << 0)) { + availreq->protoVer = 1; //buttonState + pr("button pressed\n"); + P1CHSTA &=~ (1 << 0); + } addCRC(availreq, sizeof(struct AvailDataReq)); commsTxNoCpy(outBuffer); } @@ -897,6 +931,9 @@ void mainProtocolLoop(void) { screenSleep(); eepromDeepPowerDown(); initRadio(); + + P1CHSTA &=~ (1 << 0); + // drawPartial(); // i2ctest(); // doSleep(10000); From 7211dcf19e3b2b8f01555607cefaf568916e6358 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Fri, 27 Jan 2023 15:52:55 +0100 Subject: [PATCH 03/18] bugfix on added button --- esp32_fw/src/newproto.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/esp32_fw/src/newproto.cpp b/esp32_fw/src/newproto.cpp index 00f130f0..fb194825 100644 --- a/esp32_fw/src/newproto.cpp +++ b/esp32_fw/src/newproto.cpp @@ -145,9 +145,7 @@ void processXferComplete(struct espXferComplete* xfc) { void processDataReq(struct espAvailDataReq* eadr) { char buffer[64]; uint8_t src[8]; - // *((uint64_t*)src) = swap64(*((uint64_t*)adr->)); - sprintf(buffer, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0]); - sprintf(buffer, "adr->protoVer); + sprintf(buffer, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0], eadr->adr.protoVer); wsString((String)buffer); Serial.print(buffer); } From 5808fdd733908c7b23cb86af2844794507edc9d5 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Fri, 27 Jan 2023 23:19:53 +0100 Subject: [PATCH 04/18] button fixes added buttonState in struct, #define HAS_BUTTON to enable listening to the button state. --- ap_fw/main.c | 2 +- esp32_fw/include/commstructs.h | 2 +- esp32_fw/src/newproto.cpp | 2 +- tag_fw/syncedproto.c | 27 +++++++++------------------ 4 files changed, 12 insertions(+), 21 deletions(-) diff --git a/ap_fw/main.c b/ap_fw/main.c index d5149570..2d3453c6 100644 --- a/ap_fw/main.c +++ b/ap_fw/main.c @@ -60,7 +60,7 @@ struct AvailDataReq { uint8_t softVer; uint8_t hwType; uint8_t protoVer; - //uint8_t buttonState; + uint8_t buttonState; } __packed; #define DATATYPE_NOUPDATE 0 diff --git a/esp32_fw/include/commstructs.h b/esp32_fw/include/commstructs.h index 8afb0a75..bf1f2a24 100644 --- a/esp32_fw/include/commstructs.h +++ b/esp32_fw/include/commstructs.h @@ -27,7 +27,7 @@ struct AvailDataReq { uint8_t softVer; uint8_t hwType; uint8_t protoVer; - //uint8_t buttonState; + uint8_t buttonState; } __packed; struct espAvailDataReq { diff --git a/esp32_fw/src/newproto.cpp b/esp32_fw/src/newproto.cpp index fb194825..a323b133 100644 --- a/esp32_fw/src/newproto.cpp +++ b/esp32_fw/src/newproto.cpp @@ -145,7 +145,7 @@ void processXferComplete(struct espXferComplete* xfc) { void processDataReq(struct espAvailDataReq* eadr) { char buffer[64]; uint8_t src[8]; - sprintf(buffer, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0], eadr->adr.protoVer); + sprintf(buffer, "src[7], eadr->src[6], eadr->src[5], eadr->src[4], eadr->src[3], eadr->src[2], eadr->src[1], eadr->src[0], eadr->adr.buttonState); wsString((String)buffer); Serial.print(buffer); } diff --git a/tag_fw/syncedproto.c b/tag_fw/syncedproto.c index 0e571f88..56e23fd1 100644 --- a/tag_fw/syncedproto.c +++ b/tag_fw/syncedproto.c @@ -68,7 +68,7 @@ struct AvailDataReq { uint8_t softVer; uint8_t hwType; uint8_t protoVer; - //uint8_t buttonState; + uint8_t buttonState; } __packed; #define DATATYPE_NOUPDATE 0 @@ -160,6 +160,9 @@ uint8_t __xdata seq = 0; #define DATA_REQ_MAX_ATTEMPTS 14 // How many attempts (at most) we should do to get something back from the AP #define POWER_SAVING_SMOOTHING 8 // How many samples we should use to smooth the data request interval #define MINIMUM_INTERVAL 45 // IMPORTANT: Minimum interval for check-in; this determines overal battery life! + +#define HAS_BUTTON // uncomment to enable reading a push button (connect between 'TEST' en 'GND' on the tag, along with a 100nF capacitor in parallel). + uint16_t __xdata dataReqAttemptArr[POWER_SAVING_SMOOTHING] = {0}; // Holds the amount of attempts required per data_req/check-in uint8_t __xdata dataReqAttemptArrayIndex = 0; uint8_t __xdata dataReqLastAttempt = 0; @@ -254,34 +257,22 @@ void doSleep(uint32_t __xdata t) { if(t>1000)pr("s=%lu\n ", t / 1000); powerPortsDownForSleep(); +#ifdef HAS_BUTTON //Button setup on TEST pin 1.0 (input pullup) - uint8_t tmp_P1FUNC = P1FUNC; - uint8_t tmp_P1DIR = P1DIR; - uint8_t tmp_P1PULL = P1PULL; - uint8_t tmp_P1LVLSEL = P1LVLSEL; - P1FUNC &=~ (1 << 0); P1DIR |= (1 << 0); P1PULL |= (1 << 0); - P1LVLSEL |= (1 << 0); - P1CHSTA &=~ (1 << 0); - P0INTEN = 0; P1INTEN = (1 << 0); - P2INTEN = 0; P1CHSTA &=~ (1 << 0); +#endif // sleepy sleepForMsec(t); - P0INTEN = 0; +#ifdef HAS_BUTTON P1INTEN = 0; - P2INTEN = 0; - - P1FUNC = tmp_P1FUNC; - P1DIR = tmp_P1DIR; - P1PULL = tmp_P1PULL; - P1LVLSEL = tmp_P1LVLSEL; +#endif initAfterWake(); } @@ -325,7 +316,7 @@ void sendAvailDataReq() { // TODO: send some meaningful data availreq->softVer = 1; if (P1CHSTA && (1 << 0)) { - availreq->protoVer = 1; //buttonState + availreq->buttonState = 1; pr("button pressed\n"); P1CHSTA &=~ (1 << 0); } From 7bb2c6e0e5adff265eb78cfca35752f5e4f74518 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Sat, 28 Jan 2023 21:27:33 +0100 Subject: [PATCH 05/18] preparations for UI --- tag_fw/Makefile | 20 +- tag_fw/barcode.c | 101 ------ tag_fw/barcode.h | 29 -- tag_fw/board/boardZBS29common.c | 6 +- tag_fw/board/zbs154v033/screen.c | 475 --------------------------- tag_fw/board/zbs29v033/screen.c | 397 ----------------------- tag_fw/board/zbs42v033/screen.c | 416 ------------------------ tag_fw/chars.c | 182 ----------- tag_fw/chars.h | 32 -- tag_fw/drawing.c | 152 +++------ tag_fw/drawing.h | 21 -- tag_fw/eeprom.c | 26 +- tag_fw/epd.c | 539 +++++++++++++++++++++++++++++++ tag_fw/epd.h | 54 ++++ tag_fw/font.h | 259 +++++++++++++++ tag_fw/lut.h | 125 +++++++ tag_fw/main.c | 11 - tag_fw/soc/zbs243/spi.c | 10 + tag_fw/soc/zbs243/spi.h | 3 + tag_fw/soc/zbs243/uart.c | 33 +- tag_fw/syncedproto.c | 114 +++---- 21 files changed, 1108 insertions(+), 1897 deletions(-) delete mode 100644 tag_fw/barcode.c delete mode 100644 tag_fw/barcode.h delete mode 100644 tag_fw/board/zbs154v033/screen.c delete mode 100644 tag_fw/board/zbs29v033/screen.c delete mode 100644 tag_fw/board/zbs42v033/screen.c delete mode 100644 tag_fw/chars.c delete mode 100644 tag_fw/chars.h create mode 100644 tag_fw/epd.c create mode 100644 tag_fw/epd.h create mode 100644 tag_fw/font.h create mode 100644 tag_fw/lut.h diff --git a/tag_fw/Makefile b/tag_fw/Makefile index 7dcae7e6..a2bd6bab 100644 --- a/tag_fw/Makefile +++ b/tag_fw/Makefile @@ -3,8 +3,8 @@ BUILD ?= zbs29v033 #file containing main() must be first! SOURCES += main.c eeprom.c drawing.c -SOURCES += comms.c chars.c -SOURCES += syncedproto.c +SOURCES += comms.c +SOURCES += syncedproto.c epd.c all: #make sure it is the first target @@ -18,21 +18,7 @@ FLAGS += -Icpu/$(CPU) SOURCES += cpu/$(CPU)/cpu.c SOURCES += board/$(BUILD)/board.c -SOURCES += board/$(BUILD)/screen.c - - -ifeq ($(BARCODE),barcode) - FLAGS += -DBARCODE - SOURCES += barcode.c -else ifeq ($(BARCODE),datamatrix) - # FLAGS += -DDATAMATRIX - # SOURCES += datamatrix.c - #i might release this later, but for now, just use barcodes :P - FLAGS += -DBARCODE - SOURCES += barcode.c -else - SOURCES += $(warning "no barcode type set") -endif +#SOURCES += board/$(BUILD)/screen.c EEPROMDRV ?= eeprom.c diff --git a/tag_fw/barcode.c b/tag_fw/barcode.c deleted file mode 100644 index c431d148..00000000 --- a/tag_fw/barcode.c +++ /dev/null @@ -1,101 +0,0 @@ -#include -#include "barcode.h" -#include "asmUtil.h" - -//code128 generator (c) 2021 Dmitry Grinberg https://dmitry.gr -//non-commercial use only, contact licensing@dmitry.gr for other options - -#pragma nogcse - -static const uint16_t __code mCode128[] = { - 0b00110011011, 0b00110110011, 0b01100110011, 0b00011001001, 0b00110001001, 0b00110010001, 0b00010011001, 0b00100011001, - 0b00100110001, 0b00010010011, 0b00100010011, 0b00100100011, 0b00111001101, 0b00111011001, 0b01110011001, 0b00110011101, - 0b00110111001, 0b01100111001, 0b01001110011, 0b00111010011, 0b01110010011, 0b00100111011, 0b00101110011, 0b01110110111, - 0b00110010111, 0b00110100111, 0b01100100111, 0b00100110111, 0b00101100111, 0b01001100111, 0b00011011011, 0b01100011011, - 0b01101100011, 0b00011000101, 0b00011010001, 0b01100010001, 0b00010001101, 0b00010110001, 0b01000110001, 0b00010001011, - 0b00010100011, 0b01000100011, 0b00011101101, 0b01110001101, 0b01110110001, 0b00011011101, 0b01100011101, 0b01101110001, - 0b01101110111, 0b01110001011, 0b01110100011, 0b00010111011, 0b01000111011, 0b01110111011, 0b00011010111, 0b01100010111, - 0b01101000111, 0b00010110111, 0b01000110111, 0b01011000111, 0b01011110111, 0b01000010011, 0b01010001111, 0b00001100101, - 0b00110000101, 0b00001101001, 0b01100001001, 0b00110100001, 0b01100100001, 0b00001001101, 0b00100001101, 0b00001011001, - 0b01000011001, 0b00101100001, 0b01001100001, 0b01001000011, 0b00001010011, 0b01011101111, 0b00101000011, 0b01011110001, - 0b00111100101, 0b00111101001, 0b01111001001, 0b00100111101, 0b00101111001, 0b01001111001, 0b00100101111, 0b00101001111, - 0b01001001111, 0b01111011011, 0b01101111011, 0b01101101111, 0b00011110101, 0b01111000101, 0b01111010001, 0b00010111101, - 0b01000111101, 0b00010101111, 0b01000101111, 0b01111011101, 0b01110111101, 0b01111010111, 0b01110101111 -}; - - -#define CODE128_START_B (0b00001001011) -#define CODE128_STOP (0b1101011100011) - -#define CODE128_IDX_START_A (103) -#define CODE128_IDX_START_B (104) -#define CODE128_IDX_START_C (105) -#define CODE128_IDX_CODE_STOP (106) - - -enum BarCodeState { - BarCodeInit, - BarCodeEmittingChar, - BarCodeEmittingChecksum, - BarCodeEmittingStop, - BarCodeDone, -}; - - - - -__bit barcodeIsDone(struct BarcodeInfo __xdata *bci) -{ - return bci->state == BarCodeDone; -} - -__bit barcodeNextBar(struct BarcodeInfo __xdata *bci) -{ - uint8_t t; - __bit ret; - - if (!bci->barsLeft) switch (bci->state) { - case BarCodeInit: - bci->curBars = CODE128_START_B; - bci->barsLeft = 11; - bci->state = BarCodeEmittingChar; - bci->csum = CODE128_IDX_START_B; - break; - - case BarCodeEmittingChar: - t = charsPrvDerefAndIncGenericPtr(&bci->str); - if (t) { - t -= 0x20; - if (t >= 0x60) - t = '?' - 0x20; - bci->csum = mathPrvMod16x8(mathPrvMul8x8(++bci->csumMul, t) + bci->csum, 103); - } - else { - - bci->state = BarCodeEmittingChecksum; - t = bci->csum; - } - bci->curBars = mCode128[t]; - bci->barsLeft = 11; - break; - - case BarCodeEmittingChecksum: - bci->state = BarCodeEmittingStop; - bci->curBars = CODE128_STOP; - bci->barsLeft = 13; - break; - - case BarCodeEmittingStop: - bci->state = BarCodeDone; - //fallthrough - - case BarCodeDone: - default: - return false; - } - - ret = bci->curBars & 1; - bci->curBars >>= 1; - bci->barsLeft--; - return ret; -} diff --git a/tag_fw/barcode.h b/tag_fw/barcode.h deleted file mode 100644 index f6d784b9..00000000 --- a/tag_fw/barcode.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef _BARCODE_H_ -#define _BARCODE_H_ - -#include - -//code128 generator (c) 2021 Dmitry Grinberg https://dmitry.gr -//non-commercial use only, contact licensing@dmitry.gr for other options - - -struct BarcodeInfo { //zero-init this except the string ptr - const char *str; - uint16_t curBars; - uint8_t barsLeft; - uint8_t state; - uint8_t csum; - uint8_t csumMul; -}; - -#define barcodeWidth(nChars) (11 * (nChars) + 11 /*start */+ 11 /*check sum */ + 13 /* stop */) - -#pragma callee_saves barcodeIsDone -__bit barcodeIsDone(struct BarcodeInfo __xdata *bci); - -#pragma callee_saves barcodeNextBar -__bit barcodeNextBar(struct BarcodeInfo __xdata *bci); - - - -#endif diff --git a/tag_fw/board/boardZBS29common.c b/tag_fw/board/boardZBS29common.c index ee41370c..0c9f7e8a 100644 --- a/tag_fw/board/boardZBS29common.c +++ b/tag_fw/board/boardZBS29common.c @@ -10,6 +10,8 @@ #include "adc.h" #include "i2c.h" +//extern uint8_t __xdata* tempBuffer; +uint8_t __xdata tempBuffer[320]; void powerPortsDownForSleep(void) { @@ -148,12 +150,12 @@ void selfUpdate(void) uint32_t updaterInfo = prvUpdateApplierGet(); uint8_t __code *src = (uint8_t __code*)updaterInfo; uint8_t i, len = updaterInfo >> 16; - uint8_t __xdata *dst = mScreenRow; + uint8_t __xdata *dst = tempBuffer; for (i = len; i ; i--) *dst++ = *src++; - if (!flashWrite(0xfc00, mScreenRow, len, true)) + if (!flashWrite(0xfc00, tempBuffer, len, true)) pr("failed to write updater\n"); IEN_EA = 0; //ints off diff --git a/tag_fw/board/zbs154v033/screen.c b/tag_fw/board/zbs154v033/screen.c deleted file mode 100644 index 2bd46f4d..00000000 --- a/tag_fw/board/zbs154v033/screen.c +++ /dev/null @@ -1,475 +0,0 @@ -#include "screen.h" - -#include - -#include "adc.h" -#include "asmUtil.h" -#include "board.h" -#include "cpu.h" -#include "printf.h" -#include "sleep.h" -#include "spi.h" -#include "timer.h" - -uint8_t __xdata mScreenRow[320]; - -static __bit mInited = false, mPartial; -static uint8_t __xdata mPassNo; - -#define SCREEN_CMD_CLOCK_ON 0x80 -#define SCREEN_CMD_CLOCK_OFF 0x01 - -#define SCREEN_CMD_ANALOG_ON 0x40 -#define SCREEN_CMD_ANALOG_OFF 0x02 - -#define SCREEN_CMD_LATCH_TEMPERATURE_VAL 0x20 - -#define SCREEN_CMD_LOAD_LUT 0x10 -#define SCREEN_CMD_USE_MODE_2 0x08 // modified commands 0x10 and 0x04 - -#define SCREEN_CMD_REFRESH 0xC7 - -static const uint8_t __code mColorMap[][6] = - { - // colors are: B, DG, G, LG, W, R - // phase 0 (LUTS: B:W:R:G, purpose: BWR, prepare greys) - { - 1, 1, 1, 1, 0, 0, // lo plane (B) - }, - { - 0, 0, 0, 0, 0, 1, // hi plane (R) - }}; - -static const uint8_t __code partial_lut[] = { - // lut0 (KEEP) voltages - 0x40, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - // lut1 (W2B) voltages - 0x80, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - // lut2 (B2W) voltages - 0x40, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - // lut3 (unused) voltages - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - // lut4 (vcom) voltages - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - - // group0 phase lengths and repeat count - 45, - 0x00, - 0x00, - 0x00, - 0x00, - - // group1 not used - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - - // group2 not used - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - - // group3 phase lengths and repeat count - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - - // group4 phase lengths and repeat count - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - - // group5 phase lengths and repeat count - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - - // group6 phase lengths and repeat count - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, -}; - -#define einkPrvSelect() \ - do { \ - P1_7 = 0; \ - } while (0) - -#define einkPrvDeselect() \ - do { \ - P1_7 = 1; \ - } while (0) -// urx pin -#define einkPrvMarkCommand() \ - do { \ - P2_2 = 0; \ - } while (0) - -#define einkPrvMarkData() \ - do { \ - P2_2 = 1; \ - } while (0) - -#pragma callee_saves einkPrvCmd -static void einkPrvCmd(uint8_t cmd) // sets chip select -{ - einkPrvSelect(); - einkPrvMarkCommand(); - spiByte(cmd); -} - -#pragma callee_saves einkPrvData -static void einkPrvData(uint8_t byte) { - einkPrvMarkData(); - spiByte(byte); -} - -#pragma callee_saves einkPrvCmdWithOneByte -static void einkPrvCmdWithOneByte(uint16_t vals) // passing in one u16 is better than two params cause SDCC sucks -{ - einkPrvCmd(vals >> 8); - einkPrvData(vals); - einkPrvDeselect(); -} - -#pragma callee_saves einkPrvWaitWithTimeout -static void einkPrvWaitWithTimeout(uint32_t timeout) { - uint32_t __xdata start = timerGet(); - - while (timerGet() - start < timeout) { - if (!P2_1) - return; - } - pr("screen timeout %lu ticks\n", timerGet() - start); - while (1) - ; -} - -#pragma callee_saves einkPrvWaitWithTimeout -static void einkPrvWaitWithTimeoutSleep(uint32_t timeout) { - uint8_t tmp_P2FUNC = P2FUNC; - uint8_t tmp_P2DIR = P2DIR; - uint8_t tmp_P2PULL = P2PULL; - uint8_t tmp_P2LVLSEL = P2LVLSEL; - P2FUNC &= 0xfd; - P2DIR |= 2; - P2PULL |= 2; - P2LVLSEL |= 2; - - P2CHSTA &= 0xfd; - P2INTEN |= 2; - P2CHSTA &= 0xfd; - sleepForMsec(timeout); - P2CHSTA &= 0xfd; - P2INTEN &= 0xfd; - - P2FUNC = tmp_P2FUNC; - P2DIR = tmp_P2DIR; - P2PULL = tmp_P2PULL; - P2LVLSEL = tmp_P2LVLSEL; - /*if (!P2_1) - return; - - pr("screen timeout\n"); - while(1);*/ -} - -#pragma callee_saves einkPrvReadByte -static uint8_t einkPrvReadByte(void) { - uint8_t val = 0, i; - - P0DIR = (P0DIR & ~(1 << 0)) | (1 << 1); - P0 &= ~(1 << 0); - P0FUNC &= ~((1 << 0) | (1 << 1)); - - P2_2 = 1; - - for (i = 0; i < 8; i++) { - P0_0 = 1; - __asm__("nop\nnop\nnop\nnop\nnop\n"); - val <<= 1; - if (P0_1) - val++; - P0_0 = 0; - __asm__("nop\nnop\nnop\nnop\nnop\n"); - } - - // set up pins for spi (0.0,0.1,0.2) - P0FUNC |= (1 << 0) | (1 << 1); - - return val; -} - -#pragma callee_saves einkPrvReadStatus -static uint8_t einkPrvReadStatus(void) { - uint8_t sta; - einkPrvCmd(0x2f); - - sta = einkPrvReadByte(); - einkPrvDeselect(); - - return sta; -} - -#pragma callee_saves screenPrvStartSubPhase -static void screenPrvStartSubPhase(__bit redSubphase) { - einkPrvCmd(0x4e); - einkPrvData(0); - einkPrvDeselect(); - - einkPrvCmd(0x4f); - einkPrvData(0x00); - einkPrvData(0x00); - einkPrvDeselect(); - - einkPrvCmd(redSubphase ? 0x26 : 0x24); - - einkPrvDeselect(); -} - -void moveToXY(uint8_t x, uint16_t y, bool color) { - einkPrvCmd(0x4e); - einkPrvData(x); - einkPrvDeselect(); - einkPrvCmd(0x4f); - einkPrvData((uint8_t)(y & 0xFF)); - einkPrvData((uint8_t)(y >> 8)); - einkPrvDeselect(); - einkPrvCmd(color ? 0x26 : 0x24); - einkPrvDeselect(); -} - -#pragma callee_saves screenInitIfNeeded -static void screenInitIfNeeded(__bit forPartial) { - if (mInited) - return; - - mInited = true; - mPartial = forPartial; - - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - P2_0 = 0; - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - P2_0 = 1; - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - - einkPrvCmd(0x12); // software reset - einkPrvDeselect(); - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - - einkPrvCmdWithOneByte(0x7454); - - einkPrvCmdWithOneByte(0x7e3b); - - einkPrvCmd(0x2b); - einkPrvData(0x04); - einkPrvData(0x63); - einkPrvDeselect(); - - einkPrvCmd(0x0c); // they send 8f 8f 8f 3f - einkPrvData(0x8f); - einkPrvData(0x8f); - einkPrvData(0x8f); - einkPrvData(0x3f); - einkPrvDeselect(); - - einkPrvCmd(0x01); - einkPrvData(0x97); - einkPrvData(0x00); - einkPrvData(0x00); - einkPrvDeselect(); - - einkPrvCmdWithOneByte(0x1103); - einkPrvDeselect(); - - einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_CLOCK_ON | SCREEN_CMD_ANALOG_ON); - einkPrvCmd(0x20); - einkPrvDeselect(); - - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND * 10); - - einkPrvCmd(0x44); - einkPrvData(0x00); - einkPrvData(SCREEN_WIDTH / 8 - 1); - einkPrvDeselect(); - - einkPrvCmd(0x45); - einkPrvData(0x00); - einkPrvData(0x00); - einkPrvData(0x97); - einkPrvData(0x00); - einkPrvDeselect(); - - einkPrvCmdWithOneByte(0x3c01); // border will be HiZ - einkPrvDeselect(); - einkPrvCmdWithOneByte(0x1880); // internal temp sensor - - einkPrvCmdWithOneByte(0x2108); - - // turn on clock & analog - einkPrvCmdWithOneByte(0x22B1); - einkPrvCmd(0x20); // do action - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - - if (forPartial) { - einkPrvCmd(0x32); - for (int i = 0; i < sizeof(partial_lut); i++) - einkPrvData(partial_lut[i]); - } - einkPrvDeselect(); -} - -#pragma callee_saves screenPrvDraw -static void screenPrvDraw(void) { - einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_REFRESH); - einkPrvCmd(0x20); // do actions - if (0) { - einkPrvWaitWithTimeoutSleep(1000 * 60UL); - screenSleep(); - } else { - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND * 60UL); - } -} - -__bit screenTxStart(__bit forPartial) { - screenInitIfNeeded(forPartial); - mPassNo = 0; - - screenPrvStartSubPhase(false); - - return true; -} - -void screenEndPass(void) { - switch (mPassNo) { - case 0: - screenPrvStartSubPhase(true); - break; - default: - return; - } - mPassNo++; -} - -void screenTxEnd(void) { - screenPrvDraw(); - screenShutdown(); -} - -void screenShutdown(void) { - if (!mInited) - return; - - mInited = false; - einkPrvCmdWithOneByte(0x1003); // shut down -} - -void screenSleep(void) { - P2_0 = 0; - timerDelay(10); - P2_0 = 1; - timerDelay(50); - - einkPrvCmd(0x12); // software reset - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND / 250); - einkPrvCmdWithOneByte(0x1003); // shut down -} - -#pragma callee_saves screenByteTx -void screenByteTx(uint8_t byte) { - static uint8_t __xdata prev, step = 0; - - prev <<= 2; - prev |= (mColorMap[mPassNo][byte >> 4] << 1) | mColorMap[mPassNo][byte & 0x0f]; - if (++step == 4) { - step = 0; - einkPrvSelect(); - einkPrvData(prev); - einkPrvDeselect(); - } -} - -void screenByteRawTx(uint8_t byte) { - einkPrvSelect(); - einkPrvData(byte); - einkPrvDeselect(); -} - -// yes this is here... -uint16_t adcSampleBattery(void) { - __bit wasInited = mInited; - uint16_t voltage = 2600; - - if (!mInited) - screenInitIfNeeded(false); - - uint8_t val; - - einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_CLOCK_ON | SCREEN_CMD_ANALOG_ON); - einkPrvCmd(0x20); // do action - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - - for (val = 3; val < 8; val++) { - einkPrvCmdWithOneByte(0x1500 + val); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - if (einkPrvReadStatus() & 0x10) { // set if voltage is less than threshold ( == 1.9 + val / 10) - voltage = 1850 + mathPrvMul8x8(val, 100); - break; - } - } - - einkPrvCmdWithOneByte(0x22B1); - einkPrvCmd(0x20); // do action - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - - if (!wasInited) - screenShutdown(); - - return voltage; -} diff --git a/tag_fw/board/zbs29v033/screen.c b/tag_fw/board/zbs29v033/screen.c deleted file mode 100644 index d434b693..00000000 --- a/tag_fw/board/zbs29v033/screen.c +++ /dev/null @@ -1,397 +0,0 @@ -#include -#include "asmUtil.h" -#include "screen.h" -#include "printf.h" -#include "board.h" -#include "timer.h" -#include "sleep.h" -#include "adc.h" -#include "cpu.h" -#include "spi.h" - -uint8_t __xdata mScreenRow[320]; - -static __bit mInited = false, mPartial; -static uint8_t __xdata mPassNo; - -#define SCREEN_CMD_CLOCK_ON 0x80 -#define SCREEN_CMD_CLOCK_OFF 0x01 - -#define SCREEN_CMD_ANALOG_ON 0x40 -#define SCREEN_CMD_ANALOG_OFF 0x02 - -#define SCREEN_CMD_LATCH_TEMPERATURE_VAL 0x20 - -#define SCREEN_CMD_LOAD_LUT 0x10 -#define SCREEN_CMD_USE_MODE_2 0x08 // modified commands 0x10 and 0x04 - -#define SCREEN_CMD_REFRESH 0xC7 - -static const uint8_t __code mColorMap[][6] = - { - // colors are: B, DG, G, LG, W, R - // phase 0 (LUTS: B:W:R:G, purpose: BWR, prepare greys) - { - 1, 1, 1, 1, 0, 0, // lo plane (B) - }, - { - 0, 0, 0, 0, 0, 1, // hi plane (R) - }}; - -#define einkPrvSelect() \ - do \ - { \ - P1_7 = 0; \ - } while (0) - -#define einkPrvDeselect() \ - do \ - { \ - P1_7 = 1; \ - } while (0) -// urx pin -#define einkPrvMarkCommand() \ - do \ - { \ - P2_2 = 0; \ - } while (0) - -#define einkPrvMarkData() \ - do \ - { \ - P2_2 = 1; \ - } while (0) - -#pragma callee_saves einkPrvCmd -static void einkPrvCmd(uint8_t cmd) // sets chip select -{ - einkPrvSelect(); - einkPrvMarkCommand(); - spiByte(cmd); -} - -#pragma callee_saves einkPrvData -static void einkPrvData(uint8_t byte) -{ - einkPrvMarkData(); - spiByte(byte); -} - -#pragma callee_saves einkPrvCmdWithOneByte -static void einkPrvCmdWithOneByte(uint16_t vals) // passing in one u16 is better than two params cause SDCC sucks -{ - einkPrvCmd(vals >> 8); - einkPrvData(vals); - einkPrvDeselect(); -} - -#pragma callee_saves einkPrvWaitWithTimeout -static void einkPrvWaitWithTimeout(uint32_t timeout) -{ - uint32_t __xdata start = timerGet(); - - while (timerGet() - start < timeout) - { - - if (!P2_1) - return; - } - pr("screen timeout %lu ticks\n", timerGet() - start); - while (1) - ; -} - -#pragma callee_saves einkPrvWaitWithTimeout -static void einkPrvWaitWithTimeoutSleep(uint32_t timeout) -{ - uint8_t tmp_P2FUNC = P2FUNC; - uint8_t tmp_P2DIR = P2DIR; - uint8_t tmp_P2PULL = P2PULL; - uint8_t tmp_P2LVLSEL = P2LVLSEL; - P2FUNC &= 0xfd; - P2DIR |= 2; - P2PULL |= 2; - P2LVLSEL |= 2; - - P2CHSTA &= 0xfd; - P2INTEN |= 2; - P2CHSTA &= 0xfd; - sleepForMsec(timeout); - P2CHSTA &= 0xfd; - P2INTEN &= 0xfd; - - P2FUNC = tmp_P2FUNC; - P2DIR = tmp_P2DIR; - P2PULL = tmp_P2PULL; - P2LVLSEL = tmp_P2LVLSEL; - /*if (!P2_1) - return; - - pr("screen timeout\n"); - while(1);*/ -} - -#pragma callee_saves einkPrvReadByte -static uint8_t einkPrvReadByte(void) -{ - uint8_t val = 0, i; - - P0DIR = (P0DIR & ~(1 << 0)) | (1 << 1); - P0 &= ~(1 << 0); - P0FUNC &= ~((1 << 0) | (1 << 1)); - - P2_2 = 1; - - for (i = 0; i < 8; i++) - { - P0_0 = 1; - __asm__("nop\nnop\nnop\nnop\nnop\n"); - val <<= 1; - if (P0_1) - val++; - P0_0 = 0; - __asm__("nop\nnop\nnop\nnop\nnop\n"); - } - - // set up pins for spi (0.0,0.1,0.2) - P0FUNC |= (1 << 0) | (1 << 1); - - return val; -} - -#pragma callee_saves einkPrvReadStatus -static uint8_t einkPrvReadStatus(void) -{ - uint8_t sta; - einkPrvCmd(0x2f); - - sta = einkPrvReadByte(); - einkPrvDeselect(); - - return sta; -} - -#pragma callee_saves screenPrvStartSubPhase -static void screenPrvStartSubPhase(__bit redSubphase) -{ - einkPrvCmd(0x4e); - einkPrvData(0); - einkPrvDeselect(); - - einkPrvCmd(0x4f); - einkPrvData(0x00); - einkPrvData(0x00); - einkPrvDeselect(); - - einkPrvCmd(redSubphase ? 0x26 : 0x24); - - einkPrvDeselect(); -} - -#pragma callee_saves screenInitIfNeeded -static void screenInitIfNeeded(__bit forPartial) -{ - if (mInited) - return; - - mInited = true; - mPartial = forPartial; - - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - P2_0 = 0; - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - P2_0 = 1; - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - - einkPrvCmd(0x12); // software reset - einkPrvDeselect(); - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - - einkPrvCmdWithOneByte(0x7454); - - einkPrvCmdWithOneByte(0x7e3b); - - einkPrvCmd(0x2b); - einkPrvData(0x04); - einkPrvData(0x63); - einkPrvDeselect(); - - einkPrvCmd(0x0c); // they send 8f 8f 8f 3f - einkPrvData(0x8f); - einkPrvData(0x8f); - einkPrvData(0x8f); - einkPrvData(0x3f); - einkPrvDeselect(); - - einkPrvCmd(0x01); - einkPrvData((SCREEN_HEIGHT - 1) & 0xff); - einkPrvData((SCREEN_HEIGHT - 1) >> 8); - einkPrvData(0x00); - einkPrvDeselect(); - - einkPrvCmdWithOneByte(0x1103); - - einkPrvCmd(0x44); - einkPrvData(0x00); - einkPrvData(SCREEN_WIDTH / 8 - 1); - einkPrvDeselect(); - - einkPrvCmd(0x45); - einkPrvData(0x00); - einkPrvData(0x00); - einkPrvData((SCREEN_HEIGHT - 1) & 0xff); - einkPrvData((SCREEN_HEIGHT - 1) >> 8); - einkPrvDeselect(); - - einkPrvCmdWithOneByte(0x3cc0); // border will be HiZ - - einkPrvCmdWithOneByte(0x1880); // internal temp sensor - - einkPrvCmdWithOneByte(0x2108); - // turn on clock & analog - einkPrvCmdWithOneByte(0x22B1); - einkPrvCmd(0x20); // do action - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); -} - -#pragma callee_saves screenPrvDraw -static void screenPrvDraw(void) -{ - /* einkPrvCmd(0x01); - einkPrvData(0x40); - einkPrvData(0x00); - einkPrvData(0x00); - einkPrvDeselect(); - - - einkPrvCmd(0x0f); - einkPrvData(0x64); - einkPrvData(0x00); - einkPrvDeselect(); - */ - - einkPrvCmdWithOneByte(0x3a16); -// einkPrvCmdWithOneByte(0x3b0f); - - - - einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_REFRESH); - einkPrvCmd(0x20); // do actions - if (1) - { - einkPrvWaitWithTimeoutSleep(1000 * 60UL); - screenSleep(); - } - else - { - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND * 60UL); - } -} - -__bit screenTxStart(__bit forPartial) -{ - screenInitIfNeeded(forPartial); - mPassNo = 0; - - screenPrvStartSubPhase(false); - - return true; -} - -void screenEndPass(void) -{ - switch (mPassNo) - { - case 0: - screenPrvStartSubPhase(true); - break; - default: - return; - } - mPassNo++; -} - -void screenTxEnd(void) -{ - screenPrvDraw(); - //screenShutdown(); -} - -void screenShutdown(void) -{ - if (!mInited) - return; - - mInited = false; - einkPrvCmdWithOneByte(0x1003); // shut down -} - -void screenSleep(void) -{ - P2_0 = 0; - timerDelay(TIMER_TICKS_PER_SECOND / 250); - P2_0 = 1; - timerDelay(TIMER_TICKS_PER_SECOND / 250); - - einkPrvCmd(0x12); // software reset - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - - einkPrvCmdWithOneByte(0x1003); // shut down -} - -#pragma callee_saves screenByteTx -void screenByteTx(uint8_t byte) -{ - static uint8_t __xdata prev, step = 0; - - prev <<= 2; - prev |= (mColorMap[mPassNo][byte >> 4] << 1) | mColorMap[mPassNo][byte & 0x0f]; - if (++step == 4) - { - step = 0; - einkPrvSelect(); - einkPrvData(prev); - einkPrvDeselect(); - } -} - -// yes this is here... -uint16_t adcSampleBattery(void) -{ - __bit wasInited = mInited; - uint16_t voltage = 2600; - - if (!mInited) - screenInitIfNeeded(false); - - uint8_t val; - - einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_CLOCK_ON | SCREEN_CMD_ANALOG_ON); - einkPrvCmd(0x20); // do action - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - - for (val = 3; val < 8; val++) - { - - einkPrvCmdWithOneByte(0x1500 + val); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - if (einkPrvReadStatus() & 0x10) - { // set if voltage is less than threshold ( == 1.9 + val / 10) - voltage = 1850 + mathPrvMul8x8(val, 100); - break; - } - } - - einkPrvCmdWithOneByte(0x22B1); - einkPrvCmd(0x20); // do action - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - - if (!wasInited) - screenShutdown(); - - return voltage; -} diff --git a/tag_fw/board/zbs42v033/screen.c b/tag_fw/board/zbs42v033/screen.c deleted file mode 100644 index eb36fdea..00000000 --- a/tag_fw/board/zbs42v033/screen.c +++ /dev/null @@ -1,416 +0,0 @@ -#include -#include "asmUtil.h" -#include "screen.h" -#include "printf.h" -#include "board.h" -#include "timer.h" -#include "sleep.h" -#include "adc.h" -#include "cpu.h" -#include "spi.h" - -uint8_t __xdata mScreenRow[320]; - -static __bit mInited = false, mPartial; -static uint8_t __xdata mPassNo; - - -#define SCREEN_CMD_CLOCK_ON 0x80 -#define SCREEN_CMD_CLOCK_OFF 0x01 - -#define SCREEN_CMD_ANALOG_ON 0x40 -#define SCREEN_CMD_ANALOG_OFF 0x02 - -#define SCREEN_CMD_LATCH_TEMPERATURE_VAL 0x20 - -#define SCREEN_CMD_LOAD_LUT 0x10 -#define SCREEN_CMD_USE_MODE_2 0x08 //modified commands 0x10 and 0x04 - -#define SCREEN_CMD_REFRESH 0xC7 - -static const uint8_t __code mColorMap[][6] = -{ - //colors are: B, DG, G, LG, W, R - //phase 0 (LUTS: B:W:R:G, purpose: BWR, prepare greys) - { - 1, 1, 1, 1, 0, 0, //lo plane (B) - }, - { - 0, 0, 0, 0, 0, 1, //hi plane (R) - } -}; - - - -#define einkPrvSelect() \ - do{ \ - P1_7 = 0; \ - } while (0) - -#define einkPrvDeselect() \ - do{ \ - P1_7 = 1; \ - } while (0) -//urx pin -#define einkPrvMarkCommand() \ - do{ \ - P2_2 = 0; \ - } while (0) - -#define einkPrvMarkData() \ - do{ \ - P2_2 = 1; \ - } while (0) - - - -#pragma callee_saves einkPrvCmd -static void einkPrvCmd(uint8_t cmd) //sets chip select -{ - einkPrvSelect(); - einkPrvMarkCommand(); - spiByte(cmd); -} - -#pragma callee_saves einkPrvData -static void einkPrvData(uint8_t byte) -{ - einkPrvMarkData(); - spiByte(byte); -} - -#pragma callee_saves einkPrvCmdWithOneByte -static void einkPrvCmdWithOneByte(uint16_t vals) //passing in one u16 is better than two params cause SDCC sucks -{ - einkPrvCmd(vals >> 8); - einkPrvData(vals); - einkPrvDeselect(); -} - -#pragma callee_saves einkPrvWaitWithTimeout -static void einkPrvWaitWithTimeout(uint32_t timeout) -{ - uint32_t __xdata start = timerGet(); - - while (timerGet() - start < timeout) { - - if (!P2_1) - return; - } - pr("screen timeout %lu ticks\n", timerGet() - start); - while(1); -} - -#pragma callee_saves einkPrvWaitWithTimeout -static void einkPrvWaitWithTimeoutSleep(uint32_t timeout) -{ - uint8_t tmp_P2FUNC = P2FUNC; - uint8_t tmp_P2DIR = P2DIR; - uint8_t tmp_P2PULL = P2PULL; - uint8_t tmp_P2LVLSEL = P2LVLSEL; - P2FUNC &= 0xfd; - P2DIR |= 2; - P2PULL |= 2; - P2LVLSEL |= 2; - - P2CHSTA &= 0xfd; - P2INTEN |= 2; - P2CHSTA &= 0xfd; - sleepForMsec(timeout); - P2CHSTA &= 0xfd; - P2INTEN &= 0xfd; - - P2FUNC = tmp_P2FUNC; - P2DIR = tmp_P2DIR; - P2PULL = tmp_P2PULL; - P2LVLSEL = tmp_P2LVLSEL; - /*if (!P2_1) - return; - - pr("screen timeout\n"); - while(1);*/ -} - -#pragma callee_saves einkPrvReadByte -static uint8_t einkPrvReadByte(void) -{ - uint8_t val = 0, i; - - P0DIR = (P0DIR &~ (1 << 0)) | (1 << 1); - P0 &=~ (1 << 0); - P0FUNC &=~ ((1 << 0) | (1 << 1)); - - P2_2 = 1; - - for (i = 0; i < 8; i++) { - P0_0 = 1; - __asm__("nop\nnop\nnop\nnop\nnop\n"); - val <<= 1; - if (P0_1) - val++; - P0_0 = 0; - __asm__("nop\nnop\nnop\nnop\nnop\n"); - } - - - //set up pins for spi (0.0,0.1,0.2) - P0FUNC |= (1 << 0) | (1 << 1); - - return val; -} - -#pragma callee_saves einkPrvReadStatus -static uint8_t einkPrvReadStatus(void) -{ - uint8_t sta; - einkPrvCmd(0x2f); - - sta = einkPrvReadByte(); - einkPrvDeselect(); - - return sta; -} - -#pragma callee_saves screenPrvStartSubPhase -static void screenPrvStartSubPhase(__bit redSubphase) -{ - einkPrvCmd(0x4e); - einkPrvData(0x00); - einkPrvDeselect(); - - einkPrvCmd(0x4f); - einkPrvData(0x2b); - einkPrvData(0x01); - einkPrvDeselect(); - - einkPrvCmd(redSubphase ? 0x26 : 0x24); - - einkPrvDeselect(); -} - -#pragma callee_saves screenInitIfNeeded -static void screenInitIfNeeded(__bit forPartial) -{ - if (mInited) - return; - - mInited = true; - mPartial = forPartial; - pr("init 4.2 screen\n"); - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - P2_0 = 0; - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - P2_0 = 1; - timerDelay(TIMER_TICKS_PER_SECOND / 1000); - - einkPrvCmd(0x12); //software reset - einkPrvDeselect(); - timerDelay(TIMER_TICKS_PER_SECOND); - - einkPrvCmdWithOneByte(0x7454); - - einkPrvCmdWithOneByte(0x7e3b); - - einkPrvCmd(0x2b); - einkPrvData(0x04); - einkPrvData(0x63); - einkPrvDeselect(); - - einkPrvCmd(0x0c); //they send 8f 8f 8f 3f - einkPrvData(0x8f); - einkPrvData(0x8f); - einkPrvData(0x8f); - einkPrvData(0x3f); - einkPrvDeselect(); - - einkPrvCmd(0x01); - einkPrvData(0x2b); - einkPrvData(0x01); - einkPrvData(0x01); - einkPrvDeselect(); - - einkPrvCmdWithOneByte(0x1101); - einkPrvDeselect(); - - - einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_CLOCK_ON | SCREEN_CMD_ANALOG_ON); - einkPrvCmd(0x20); - einkPrvDeselect(); - - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND * 10); - - - einkPrvCmd(0x44); - einkPrvData(0x00); - einkPrvData(0x31); - einkPrvDeselect(); - - einkPrvCmd(0x45); - einkPrvData(0x2b); - einkPrvData(0x01); - einkPrvData(0x00); - einkPrvData(0x00); - einkPrvDeselect(); - - //einkPrvCmdWithOneByte(0x3c01); //border will be HiZ - einkPrvCmdWithOneByte(0x3cc0); //border will be HiZ - einkPrvDeselect(); - einkPrvCmdWithOneByte(0x1880); //internal temp sensor - einkPrvDeselect(); - - einkPrvCmdWithOneByte(0x2108); - //turn on clock & analog - einkPrvCmdWithOneByte(0x22B1); - einkPrvCmd(0x20); //do action - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - - /*einkPrvCmd(0x32); - einkPrvData(0x00); einkPrvData(0x90); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x90); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x90); - - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x90); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x27); einkPrvData(0x27); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x01); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x00); - einkPrvData(0x00); einkPrvData(0x00); einkPrvData(0x15); einkPrvData(0x41); - einkPrvData(0xce); einkPrvData(0x32); einkPrvData(0x0f); einkPrvData(0x0c); - einkPrvDeselect(); - */ - - -} - -#pragma callee_saves screenPrvDraw -static void screenPrvDraw(void) -{ - einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_REFRESH); - einkPrvCmd(0x20); //do actions - if (1) - { - einkPrvWaitWithTimeoutSleep(1000 * 60UL); - screenSleep(); - } - else - { - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND * 60UL); - } -} - -__bit screenTxStart(__bit forPartial) -{ - screenInitIfNeeded(forPartial); - mPassNo = 0; - - screenPrvStartSubPhase(false); - - return true; -} - -void screenEndPass(void) -{ - switch (mPassNo) { - case 0: - screenPrvStartSubPhase(true); - break; - default: - return; - } - mPassNo++; -} - -void screenTxEnd(void) -{ - screenPrvDraw(); - screenShutdown(); -} - -void screenShutdown(void) -{ - if (!mInited) - return; - - mInited = false; - einkPrvCmdWithOneByte(0x1003); //shut down -} - -void screenSleep(void) -{ - P2_0 = 0; - timerDelay(20); - P2_0 = 1; - timerDelay(80); - - einkPrvCmd(0x12); // software reset - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND/250); - - einkPrvCmdWithOneByte(0x1003); // shut down -} - - -#pragma callee_saves screenByteTx -void screenByteTx(uint8_t byte) -{ - static uint8_t __xdata prev, step = 0; - - prev <<= 2; - prev |= (mColorMap[mPassNo][byte >> 4] << 1) | mColorMap[mPassNo][byte & 0x0f]; - if (++step == 4) { - step = 0; - einkPrvSelect(); - einkPrvData(prev); - einkPrvDeselect(); - } -} - -//yes this is here... -uint16_t adcSampleBattery(void) -{ - __bit wasInited = mInited; - uint16_t voltage = 2600; - - if (!mInited) - screenInitIfNeeded(false); - - uint8_t val; - - einkPrvCmdWithOneByte(0x2200 | SCREEN_CMD_CLOCK_ON | SCREEN_CMD_ANALOG_ON); - einkPrvCmd(0x20); //do action - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - - for (val = 3; val < 8; val++) { - - einkPrvCmdWithOneByte(0x1500 + val); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - if (einkPrvReadStatus() & 0x10) {//set if voltage is less than threshold ( == 1.9 + val / 10) - voltage = 1850 + mathPrvMul8x8(val, 100); - break; - } - } - - einkPrvCmdWithOneByte(0x22B1); - einkPrvCmd(0x20); //do action - einkPrvDeselect(); - einkPrvWaitWithTimeout(TIMER_TICKS_PER_SECOND); - - if (!wasInited) - screenShutdown(); - - return voltage; -} diff --git a/tag_fw/chars.c b/tag_fw/chars.c deleted file mode 100644 index c7ba9f26..00000000 --- a/tag_fw/chars.c +++ /dev/null @@ -1,182 +0,0 @@ -#include "asmUtil.h" -#include "printf.h" -#include "screen.h" -#include "chars.h" -#include "cpu.h" - -#define CANVAS_FLIP_H 0 -#define CANVAS_MSB_FIRST 1 - -#define NUM_CHARS (0x64) -#define FIRST_CHAR (0x1c) -#define MISSING_CHAR ('?') - -static const uint8_t __code mCharsImgs[]; - -//special RAM area that is not preserved across sleep - -#pragma callee_saves charsPrvDrawCharRow -static void charsPrvDrawCharRow(uint8_t ch, int16_t x, uint8_t imgRow, uint8_t foreColor, uint8_t backColor, uint8_t mag) __reentrant /* save pseg */ -{ - uint8_t c, mc, charRow = imgRow / mag, bitMask = (1 << SCREEN_TX_BPP) - 1; - const uint8_t __code *imgInfoBuf; - - if (ch < FIRST_CHAR || ch - FIRST_CHAR >= NUM_CHARS) - ch = MISSING_CHAR; - - ch -= FIRST_CHAR; - - //get pointer to proper char row - imgInfoBuf = mCharsImgs + (uint16_t)mathPrvMul16x8((CHAR_WIDTH * NUM_CHARS + 7) / 8, charRow) + mathPrvMul8x8(CHAR_WIDTH, ch) / 8; - - for (c = 0; c < CHAR_WIDTH; c++) { //iterate over the char's columns for this row - - uint8_t imgCol = ((uint8_t)((uint8_t)CHAR_WIDTH * (uint8_t)ch) & 7) + c; //sort out where in the row our data begins - uint8_t color = ((imgInfoBuf[imgCol >> 3] >> (7 - (imgCol % 8))) & 1) ? foreColor : backColor; //get the color - - if (color == CHAR_COLOR_TRANSPARENT) - continue; - - for (mc = 0; mc < mag; mc++, x++) { //set the pixel - - uint8_t __xdata *dst = mScreenRow; - uint8_t bitOfst; - uint16_t c = x; - - if (x < 0) - continue; - if (x >= SCREEN_WIDTH) - break; - - #if CANVAS_FLIP_H - c = SCREEN_WIDTH - c - 1; - #endif - - dst += mathPrvMul16x8(c, SCREEN_TX_BPP) / 8; - bitOfst = mathPrvMul16x8(c, SCREEN_TX_BPP) % 8; - - #if CANVAS_MSB_FIRST - bitOfst = 8 - bitOfst - SCREEN_TX_BPP; - #endif - - *dst = ((*dst) & ~(bitMask << bitOfst)) | ((color & bitMask) << bitOfst); - } - } -} - -void charsDrawString(const struct CharDrawingParams __xdata *params) -{ - const char *__xdata s = params->str; - int16_t x = params->x; - uint8_t ch; - - while ((ch = charsPrvDerefAndIncGenericPtr(&s)) != 0) { - - charsPrvDrawCharRow(ch, x, params->imgRow, params->foreColor, params->backColor, params->magnify); - x += (uint16_t)(uint8_t)(CHAR_WIDTH * params->magnify); - } -} - -static const uint8_t __code mCharsImgs[] = { - - 0x00, 0x00, 0x00, 0x02, 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, 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, 0x07, 0x00, 0x00, 0x00, 0x00, 0x18, 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, 0x18, 0x00, 0x38, 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, 0x0e, 0x00, 0x00, 0x00, 0x00, - 0x18, 0x70, 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, 0x3c, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x0f, 0xdc, - 0x00, 0x18, 0x7e, 0x36, 0x3c, 0xd8, 0x38, 0x1c, 0x0c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, - 0x1e, 0x0c, 0x3c, 0x3c, 0x30, 0x7e, 0x1c, 0x7e, 0x3c, 0x3c, 0x00, 0x00, 0x06, 0x00, 0x60, 0x3c, - 0x7e, 0x18, 0x7c, 0x3c, 0x78, 0x7e, 0x7e, 0x3c, 0x66, 0x3c, 0x06, 0x66, 0x60, 0x63, 0x63, 0x3c, - 0x7c, 0x3c, 0x7c, 0x3c, 0x7e, 0x66, 0x66, 0x63, 0x66, 0x66, 0x7e, 0x3c, 0x60, 0x3c, 0x66, 0x00, - 0x0c, 0x00, 0x60, 0x00, 0x06, 0x00, 0x1e, 0x00, 0x60, 0x18, 0x0c, 0x60, 0x78, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x18, 0x30, 0x71, 0x00, - 0x30, 0x0c, 0x30, 0x38, 0x00, 0x3c, 0x7e, 0x36, 0x66, 0xda, 0x6c, 0x1c, 0x18, 0x18, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x06, 0x33, 0x1c, 0x66, 0x66, 0x30, 0x60, 0x18, 0x06, 0x66, 0x66, 0x00, 0x00, - 0x0c, 0x00, 0x30, 0x66, 0xc3, 0x3c, 0x66, 0x66, 0x6c, 0x60, 0x60, 0x66, 0x66, 0x18, 0x06, 0x66, - 0x60, 0x63, 0x63, 0x66, 0x66, 0x66, 0x66, 0x66, 0x18, 0x66, 0x66, 0x63, 0x66, 0x66, 0x06, 0x30, - 0x60, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x06, 0x00, 0x30, 0x00, 0x60, 0x00, 0x00, 0x60, - 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, - 0x18, 0x18, 0xdb, 0x00, 0x40, 0x02, 0x40, 0x72, 0x00, 0x3c, 0x7e, 0x7f, 0x60, 0x76, 0x6c, 0x0c, - 0x18, 0x18, 0x36, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x37, 0x7c, 0x66, 0x66, 0x36, 0x60, 0x30, 0x0c, - 0x66, 0x66, 0x1c, 0x1c, 0x18, 0x00, 0x18, 0x66, 0xc3, 0x66, 0x66, 0x66, 0x66, 0x60, 0x60, 0x66, - 0x66, 0x18, 0x06, 0x6c, 0x60, 0x77, 0x73, 0x66, 0x66, 0x66, 0x66, 0x60, 0x18, 0x66, 0x66, 0x63, - 0x34, 0x66, 0x06, 0x30, 0x30, 0x0c, 0x00, 0x00, 0x00, 0x3c, 0x7c, 0x3c, 0x3e, 0x3c, 0x30, 0x3e, - 0x7c, 0x78, 0x3c, 0x66, 0x18, 0x7e, 0x7c, 0x3c, 0x7c, 0x3e, 0x66, 0x3e, 0x7e, 0x66, 0x66, 0x63, - 0x66, 0x66, 0x7e, 0x18, 0x18, 0x18, 0x8e, 0x00, 0x87, 0xe1, 0x86, 0xe1, 0x00, 0x3c, 0x7e, 0x36, - 0x30, 0x0c, 0x38, 0x18, 0x30, 0x0c, 0x1c, 0x18, 0x00, 0x00, 0x00, 0x0c, 0x37, 0x0c, 0x06, 0x06, - 0x36, 0x60, 0x7c, 0x0c, 0x76, 0x66, 0x1c, 0x1c, 0x30, 0x7e, 0x0c, 0x0c, 0xcf, 0x66, 0x66, 0x60, - 0x66, 0x60, 0x60, 0x60, 0x66, 0x18, 0x06, 0x6c, 0x60, 0x6b, 0x7b, 0x66, 0x66, 0x66, 0x66, 0x30, - 0x18, 0x66, 0x66, 0x6b, 0x18, 0x66, 0x0c, 0x30, 0x30, 0x0c, 0x00, 0x00, 0x00, 0x06, 0x66, 0x66, - 0x66, 0x66, 0x30, 0x66, 0x66, 0x18, 0x0c, 0x66, 0x18, 0x6b, 0x66, 0x66, 0x66, 0x66, 0x6e, 0x60, - 0x30, 0x66, 0x66, 0x6b, 0x66, 0x66, 0x06, 0x18, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x19, 0xd8, - 0x00, 0x18, 0x7e, 0x36, 0x18, 0x18, 0x60, 0x00, 0x30, 0x0c, 0x7f, 0x7e, 0x00, 0x7e, 0x00, 0x18, - 0x33, 0x0c, 0x0c, 0x1c, 0x36, 0x7c, 0x66, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x60, 0x00, 0x06, 0x18, - 0xdb, 0x66, 0x7c, 0x60, 0x66, 0x7c, 0x7c, 0x60, 0x7e, 0x18, 0x06, 0x78, 0x60, 0x6b, 0x6f, 0x66, - 0x7c, 0x66, 0x7c, 0x18, 0x18, 0x66, 0x66, 0x6b, 0x18, 0x3c, 0x18, 0x30, 0x18, 0x0c, 0x00, 0x00, - 0x00, 0x06, 0x66, 0x60, 0x66, 0x66, 0x7e, 0x66, 0x66, 0x18, 0x0c, 0x6c, 0x18, 0x6b, 0x66, 0x66, - 0x66, 0x66, 0x70, 0x60, 0x30, 0x66, 0x66, 0x6b, 0x3c, 0x66, 0x0c, 0x30, 0x18, 0x0c, 0x00, 0x00, - 0x20, 0x04, 0x23, 0x84, 0x00, 0x18, 0x7e, 0x36, 0x0c, 0x30, 0x6f, 0x00, 0x30, 0x0c, 0x1c, 0x18, - 0x00, 0x00, 0x00, 0x18, 0x3b, 0x0c, 0x18, 0x06, 0x66, 0x06, 0x66, 0x18, 0x6e, 0x3e, 0x00, 0x00, - 0x30, 0x7e, 0x0c, 0x18, 0xdb, 0x7e, 0x66, 0x60, 0x66, 0x60, 0x60, 0x6e, 0x66, 0x18, 0x06, 0x6c, - 0x60, 0x6b, 0x67, 0x66, 0x60, 0x66, 0x6c, 0x0c, 0x18, 0x66, 0x66, 0x6b, 0x2c, 0x18, 0x30, 0x30, - 0x18, 0x0c, 0x00, 0x00, 0x00, 0x3e, 0x66, 0x60, 0x66, 0x7e, 0x30, 0x66, 0x66, 0x18, 0x0c, 0x78, - 0x18, 0x6b, 0x66, 0x66, 0x66, 0x66, 0x60, 0x3c, 0x30, 0x66, 0x66, 0x6b, 0x18, 0x66, 0x18, 0x60, - 0x18, 0x06, 0x00, 0x00, 0x41, 0x82, 0x47, 0x02, 0x00, 0x00, 0x7e, 0x7f, 0x06, 0x6e, 0x66, 0x00, - 0x30, 0x0c, 0x36, 0x18, 0x00, 0x00, 0x00, 0x30, 0x3b, 0x0c, 0x30, 0x66, 0x7f, 0x06, 0x66, 0x30, - 0x66, 0x0c, 0x00, 0x00, 0x18, 0x00, 0x18, 0x00, 0xcf, 0x66, 0x66, 0x66, 0x66, 0x60, 0x60, 0x66, - 0x66, 0x18, 0x66, 0x6c, 0x60, 0x63, 0x63, 0x66, 0x60, 0x66, 0x66, 0x06, 0x18, 0x66, 0x66, 0x36, - 0x66, 0x18, 0x60, 0x30, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x60, 0x66, 0x60, 0x30, 0x66, - 0x66, 0x18, 0x0c, 0x6c, 0x18, 0x6b, 0x66, 0x66, 0x66, 0x66, 0x60, 0x06, 0x30, 0x66, 0x66, 0x6b, - 0x3c, 0x66, 0x30, 0x30, 0x18, 0x0c, 0x00, 0x00, 0x86, 0x61, 0x8e, 0x61, 0x00, 0x18, 0x7e, 0x36, - 0x66, 0x5b, 0x66, 0x00, 0x30, 0x0c, 0x00, 0x00, 0x1c, 0x00, 0x1c, 0x30, 0x33, 0x0c, 0x60, 0x66, - 0x06, 0x0c, 0x66, 0x30, 0x66, 0x18, 0x1c, 0x1c, 0x0c, 0x00, 0x30, 0x18, 0xc0, 0x66, 0x66, 0x66, - 0x6c, 0x60, 0x60, 0x66, 0x66, 0x18, 0x66, 0x66, 0x60, 0x63, 0x63, 0x66, 0x60, 0x66, 0x66, 0x66, - 0x18, 0x66, 0x3c, 0x36, 0x66, 0x18, 0x60, 0x30, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, - 0x66, 0x60, 0x30, 0x66, 0x66, 0x18, 0x0c, 0x66, 0x18, 0x6b, 0x66, 0x66, 0x66, 0x66, 0x60, 0x06, - 0x30, 0x66, 0x3c, 0x36, 0x66, 0x66, 0x60, 0x18, 0x18, 0x18, 0x00, 0x00, 0x88, 0x11, 0x9c, 0x11, - 0x00, 0x18, 0x7e, 0x36, 0x3c, 0x1b, 0x3b, 0x00, 0x18, 0x18, 0x00, 0x00, 0x1c, 0x00, 0x1c, 0x60, - 0x1e, 0x0c, 0x7e, 0x3c, 0x06, 0x78, 0x3c, 0x30, 0x3c, 0x38, 0x1c, 0x1c, 0x06, 0x00, 0x60, 0x18, - 0x7f, 0x66, 0x7c, 0x3c, 0x78, 0x7e, 0x60, 0x3e, 0x66, 0x3c, 0x3c, 0x66, 0x7e, 0x63, 0x63, 0x3c, - 0x60, 0x3c, 0x66, 0x3c, 0x18, 0x3c, 0x18, 0x36, 0x66, 0x18, 0x7e, 0x30, 0x06, 0x0c, 0x00, 0x00, - 0x00, 0x3e, 0x7c, 0x3c, 0x3e, 0x3c, 0x30, 0x3e, 0x66, 0x7e, 0x0c, 0x66, 0x7e, 0x63, 0x66, 0x3c, - 0x7c, 0x3e, 0x60, 0x7c, 0x1e, 0x3e, 0x18, 0x36, 0x66, 0x3c, 0x7e, 0x18, 0x18, 0x18, 0x00, 0x00, - 0x10, 0x08, 0x38, 0x08, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0e, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, - 0x0c, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, - 0x06, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x0c, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x18, - 0x18, 0x18, 0x00, 0x00, 0x11, 0x88, 0x71, 0x88, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x0c, 0x30, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, - 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x18, 0x00, 0x0c, 0x18, 0x30, 0x00, 0x00, 0x03, 0xc0, 0xe3, 0xc0, 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, 0x3c, 0x00, 0x3c, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x43, 0xc0, - 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, 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, -}; \ No newline at end of file diff --git a/tag_fw/chars.h b/tag_fw/chars.h deleted file mode 100644 index 176968d6..00000000 --- a/tag_fw/chars.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef _CHARS_H_ -#define _CHARS_H_ - -#include -#include - - -#define CHAR_WIDTH 8 -#define CHAR_HEIGHT 16 - -#define CHAR_COLOR_TRANSPARENT (0xff) - -#define CHAR_SIGNAL_PT1 (0x1c) -#define CHAR_SIGNAL_PT2 (0x1d) - -#define CHAR_NO_SIGNAL_PT1 (0x1e) -#define CHAR_NO_SIGNAL_PT2 (0x1f) - -struct CharDrawingParams { - const char *str; - int16_t x; - uint8_t imgRow; - uint8_t foreColor; - uint8_t backColor; - uint8_t magnify; -}; - -//draws to screen the rows of this string. will draw 16 * magnify rows -void charsDrawString(const struct CharDrawingParams __xdata *params); - - -#endif diff --git a/tag_fw/drawing.c b/tag_fw/drawing.c index ce97dead..d7277562 100644 --- a/tag_fw/drawing.c +++ b/tag_fw/drawing.c @@ -2,12 +2,13 @@ // #include "datamatrix.h" #include "adc.h" #include "asmUtil.h" -#include "barcode.h" +// #include "barcode.h" #include "board.h" -#include "chars.h" +// #include "chars.h" #include "cpu.h" #include "drawing.h" #include "eeprom.h" +#include "epd.h" #include "printf.h" #include "screen.h" #include "timer.h" @@ -57,6 +58,16 @@ struct BitmapDrawInfo { uint8_t bottomUp : 1; }; +uint8_t __xdata mPassNo = 0; + +static const uint8_t __code mColorMap[][6] = { + // colors are: B, DG, G, LG, W, R + // phase 0 (LUTS: B:W:R:G, purpose: BWR, prepare greys) + {1, 1, 1, 1, 0, 0}, // lo plane (B) + + {0, 0, 0, 0, 0, 1} // hi plane (R) +}; + static uint8_t __xdata mClutMap[256]; static struct BitmapDrawInfo __xdata mDrawInfo; @@ -247,7 +258,7 @@ static void drawPrvDecodeImageOnce(void) { if (emit) { emit = false; - screenByteTx(txPrev | val); + ByteDecode(txPrev | val); nBytesOut++; txPrev = 0; } else { @@ -260,7 +271,7 @@ static void drawPrvDecodeImageOnce(void) { bitpoolOut |= val; bitpoolOutUsedUsed += SCREEN_TX_BPP; if (bitpoolOutUsedUsed >= 8) { - screenByteTx(bitpoolOut >> (bitpoolOutUsedUsed -= 8)); + ByteDecode(bitpoolOut >> (bitpoolOutUsedUsed -= 8)); bitpoolOut &= (1 << bitpoolOutUsedUsed) - 1; nBytesOut++; } @@ -272,14 +283,14 @@ static void drawPrvDecodeImageOnce(void) { #if SCREEN_TX_BPP == 4 if (emit) { - screenByteTx(txPrev); + ByteDecode(txPrev); nBytesOut++; } #else if (bitpoolOutUsedUsed) { - screenByteTx(bitpoolOut); + ByteDecode(bitpoolOut); nBytesOut++; } @@ -288,7 +299,7 @@ static void drawPrvDecodeImageOnce(void) { // if we did not produce enough bytes, do so nBytesOut = ((long)SCREEN_WIDTH * SCREEN_TX_BPP + 7) / 8 - nBytesOut; while (nBytesOut--) - screenByteTx(SCREEN_BYTE_FILL); + ByteDecode(SCREEN_BYTE_FILL); // update row if (mDrawInfo.bottomUp) { @@ -306,13 +317,25 @@ static void drawPrvDecodeImageOnce(void) { // fill the rest of the screen for (er = mDrawInfo.effectiveH - SCREEN_HEIGHT; er; er--) { for (c = ((long)SCREEN_WIDTH * SCREEN_TX_BPP + 7) / 8; c; c--) { - screenByteTx(SCREEN_BYTE_FILL); + ByteDecode(SCREEN_BYTE_FILL); } } } extern uint8_t blockXferBuffer[]; +void ByteDecode(uint8_t byte) { + static uint8_t __xdata prev, step = 0; + prev <<= 2; + prev |= (mColorMap[mPassNo][byte >> 4] << 1) | mColorMap[mPassNo][byte & 0x0f]; + if (++step == 4) { + step = 0; + epdSelect(); + epdSend(prev); + epdDeselect(); + } +} + void drawImageAtAddress(uint32_t addr) { uint32_t __xdata clutAddr; uint8_t __xdata iter; @@ -322,105 +345,20 @@ void drawImageAtAddress(uint32_t addr) { return; drawPrvLoadAndMapClut(clutAddr); - screenTxStart(false); - for (iter = 0; iter < SCREEN_DATA_PASSES; iter++) { - pr("."); - drawPrvDecodeImageOnce(); - screenEndPass(); - } + //screenTxStart(false); + epdSetup(); + mPassNo = 0; + beginFullscreenImage(); + beginWriteFramebuffer(EPD_COLOR_BLACK); + drawPrvDecodeImageOnce(); + endWriteFramebuffer(); + mPassNo++; + beginWriteFramebuffer(EPD_COLOR_RED); + drawPrvDecodeImageOnce(); + endWriteFramebuffer(); + pr(" complete.\n"); - screenTxEnd(); - screenShutdown(); -} - -#pragma callee_saves myStrlen -static uint16_t myStrlen(const char *str) { - const char *__xdata strP = str; - - while (charsPrvDerefAndIncGenericPtr(&strP)) - ; - - return strP - str; -} - -void drawFullscreenMsg(const char *str) { - volatile uint16_t PDATA textRow, textRowEnd; // without volatile, compiler ignores "__pdata" - struct CharDrawingParams __xdata cdp; - uint8_t __xdata rowIdx; - uint8_t iteration; - uint16_t i, r; - - getVolt(); - pr("MESSAGE '%s'\n", str); - screenTxStart(false); - - for (iteration = 0; iteration < SCREEN_DATA_PASSES; iteration++) { - __bit inBarcode = false; - rowIdx = 0; - - cdp.magnify = UI_MSG_MAGNIFY1; - cdp.str = str; - cdp.x = mathPrvI16Asr1(SCREEN_WIDTH - mathPrvMul8x8(CHAR_WIDTH * cdp.magnify, myStrlen(cdp.str))); - - cdp.foreColor = UI_MSG_FORE_COLOR_1; - cdp.backColor = UI_MSG_BACK_COLOR; - - textRow = 5; - textRowEnd = textRow + (uint8_t)((uint8_t)CHAR_HEIGHT * (uint8_t)cdp.magnify); - - for (r = 0; r < SCREEN_HEIGHT; r++) { - // clear the row - for (i = 0; i < SCREEN_WIDTH * SCREEN_TX_BPP / 8; i++) - mScreenRow[i] = SCREEN_BYTE_FILL; - - if (r >= textRowEnd) { - switch (rowIdx) { - case 0: - rowIdx = 1; - textRow = textRowEnd + 3; - cdp.magnify = UI_MSG_MAGNIFY2; - cdp.foreColor = UI_MSG_FORE_COLOR_2; - // cdp.str = macSmallString(); - cdp.x = 0; - textRowEnd = textRow + CHAR_HEIGHT * cdp.magnify; - break; - - case 1: - rowIdx = 2; - textRow = SCREEN_HEIGHT - CHAR_HEIGHT - CHAR_HEIGHT; - cdp.magnify = UI_MSG_MAGNIFY3; - cdp.foreColor = UI_MSG_FORE_COLOR_3; - // cdp.str = voltString(); - cdp.x = 1; - inBarcode = false; - - break; - case 2: - rowIdx = 3; - textRow = SCREEN_HEIGHT - CHAR_HEIGHT; - cdp.magnify = UI_MSG_MAGNIFY3; - cdp.foreColor = UI_MSG_FORE_COLOR_3; - // cdp.str = fwVerString(); - cdp.x = 1; - inBarcode = false; - break; - case 3: - cdp.str = ""; - break; - } - } else if (r > textRow) { - inBarcode = false; - cdp.imgRow = r - textRow; - charsDrawString(&cdp); - } - - for (i = 0; i < SCREEN_WIDTH * SCREEN_TX_BPP / 8; i++) - screenByteTx(mScreenRow[i]); - } - - screenEndPass(); - } - - screenTxEnd(); + draw(); + epdEnterSleep(); } diff --git a/tag_fw/drawing.h b/tag_fw/drawing.h index defc621f..4e216e9e 100644 --- a/tag_fw/drawing.h +++ b/tag_fw/drawing.h @@ -9,26 +9,5 @@ void set_offline(__bit state); #pragma callee_saves drawImageAtAddress void drawImageAtAddress(uint32_t addr); -#pragma callee_saves drawImageAtAddress -void drawFullscreenMsg(const char *str); - - - -//expected external funcs -/* -#pragma callee_saves fwVerString -const char __xdata* fwVerString(void); -#pragma callee_saves voltString -const char __xdata* voltString(void); -void getVolt(); -*/ - -#pragma callee_saves macString -//const char __xdata* macString(void); -#pragma callee_saves macSmallString -//const char __xdata* macSmallString(void); - -extern uint8_t __xdata mSelfMac[]; - #endif diff --git a/tag_fw/eeprom.c b/tag_fw/eeprom.c index 7044ed0e..30844c41 100644 --- a/tag_fw/eeprom.c +++ b/tag_fw/eeprom.c @@ -8,6 +8,8 @@ static uint32_t __xdata mEepromSize; static uint8_t __xdata mOpcodeErz4K = 0, mOpcodeErz32K = 0, mOpcodeErz64K = 0; +//extern uint8_t __xdata* tempBuffer; +uint8_t __xdata tempBufferE[320]; uint32_t eepromGetSize(void) { @@ -158,27 +160,27 @@ __bit eepromInit(void) uint8_t j; - eepromPrvSfdpRead(*(uint16_t __xdata*)(buf + 4), mScreenRow, 9 * 4); - if ((mScreenRow[0] & 3) != 1) { + eepromPrvSfdpRead(*(uint16_t __xdata*)(buf + 4), tempBufferE, 9 * 4); + if ((tempBufferE[0] & 3) != 1) { pr("SFDP: no 4K ERZ\n"); break; } - if (!(mScreenRow[0] & 0x04)) { + if (!(tempBufferE[0] & 0x04)) { pr("SFDP: no large write buf\n"); break; } - if ((mScreenRow[2] & 0x06)) { + if ((tempBufferE[2] & 0x06)) { pr("SFDP: addr.len != 3\n"); break; } - if (!mScreenRow[1] || mScreenRow[1] == 0xff) { + if (!tempBufferE[1] || tempBufferE[1] == 0xff) { pr("SFDP: 4K ERZ opcode invalid\n"); break; } - mOpcodeErz4K = mScreenRow[1]; + mOpcodeErz4K = tempBufferE[1]; - if (mScreenRow[7] & 0x80) { + if (tempBufferE[7] & 0x80) { pr("SFDP: device too big\n"); break; @@ -187,11 +189,11 @@ __bit eepromInit(void) uint8_t t; - if (t = mScreenRow[7]) + if (t = tempBufferE[7]) mEepromSize = 0x00200000UL; - else if (t = mScreenRow[6]) + else if (t = tempBufferE[6]) mEepromSize = 0x00002000UL; - else if (t = mScreenRow[5]) + else if (t = tempBufferE[5]) mEepromSize = 0x00000020UL; else { pr("SFDP: device so small?!\n"); @@ -206,12 +208,12 @@ __bit eepromInit(void) //get erase opcodes for (j = 0x1c; j < 0x24; j += 2) { - uint8_t instr = mScreenRow[j + 1]; + uint8_t instr = tempBufferE[j + 1]; if (!instr || instr == 0xff) continue; - switch (mScreenRow[j]) { + switch (tempBufferE[j]) { case 0x0c: if (mOpcodeErz4K != instr) { pr("4K ERZ opcode disagreement\n"); diff --git a/tag_fw/epd.c b/tag_fw/epd.c new file mode 100644 index 00000000..dafd1491 --- /dev/null +++ b/tag_fw/epd.c @@ -0,0 +1,539 @@ +#include "epd.h" + +#include +#include + +#include "adc.h" +#include "asmUtil.h" +#include "board.h" +#include "cpu.h" +#include "font.h" +#include "lut.h" +#include "printf.h" +#include "screen.h" +#include "sleep.h" +#include "spi.h" +#include "timer.h" + +#define CMD_DRV_OUTPUT_CTRL 0x01 +#define CMD_SOFT_START_CTRL 0x0C +#define CMD_ENTER_SLEEP 0x10 +#define CMD_DATA_ENTRY_MODE 0x11 +#define CMD_SOFT_RESET 0x12 +#define CMD_SOFT_RESET2 0x13 +#define CMD_SETUP_VOLT_DETECT 0x15 +#define CMD_TEMP_SENSOR_CONTROL 0x18 +#define CMD_ACTIVATION 0x20 +#define CMD_DISP_UPDATE_CTRL 0x21 +#define CMD_DISP_UPDATE_CTRL2 0x22 +#define CMD_WRITE_FB_BW 0x24 +#define CMD_WRITE_FB_RED 0x26 +#define CMD_UNKNOWN_1 0x2B +#define CMD_LOAD_OTP_LUT 0x31 +#define CMD_WRITE_LUT 0x32 +#define CMD_BORDER_WAVEFORM_CTRL 0x3C +#define CMD_WINDOW_X_SIZE 0x44 +#define CMD_WINDOW_Y_SIZE 0x45 +#define CMD_WRITE_PATTERN_RED 0x46 +#define CMD_WRITE_PATTERN_BW 0x47 +#define CMD_XSTART_POS 0x4E +#define CMD_YSTART_POS 0x4F +#define CMD_ANALOG_BLK_CTRL 0x74 +#define CMD_DIGITAL_BLK_CTRL 0x7E + +#define SCREEN_CMD_CLOCK_ON 0x80 +#define SCREEN_CMD_CLOCK_OFF 0x01 +#define SCREEN_CMD_ANALOG_ON 0x40 +#define SCREEN_CMD_ANALOG_OFF 0x02 +#define SCREEN_CMD_LATCH_TEMPERATURE_VAL 0x20 +#define SCREEN_CMD_LOAD_LUT 0x10 +#define SCREEN_CMD_USE_MODE_2 0x08 // modified commands 0x10 and 0x04 +#define SCREEN_CMD_REFRESH 0xC7 + +#define commandEnd() \ + do { \ + P1_7 = 1; \ + } while (0) + +#define markCommand() \ + do { \ + P2_2 = 0; \ + } while (0) + +#define markData() \ + do { \ + P2_2 = 1; \ + } while (0) + +static uint8_t __xdata currentLUT = 0x00; // Current selected LUT +static bool __idata epdPr = false; // wheter or not we copy the pr("") output to the EPD +static uint8_t __xdata epdCharSize = 1; // character size, 1 or 2 (doubled) +static bool __xdata directionY = true; // print direction, X or Y (true) +static uint8_t __xdata rbuffer[32]; // used to rotate bits around +static uint16_t __xdata fontCurXpos = 0; // current X value where working with +static bool __xdata isInited = false; + +#pragma callee_saves epdBusySleep +#pragma callee_saves epdBusyWait +static void epdBusySleep(uint32_t timeout) { + uint8_t tmp_P2FUNC = P2FUNC; + uint8_t tmp_P2DIR = P2DIR; + uint8_t tmp_P2PULL = P2PULL; + uint8_t tmp_P2LVLSEL = P2LVLSEL; + P2FUNC &= 0xfd; + P2DIR |= 2; + P2PULL |= 2; + P2LVLSEL |= 2; + + P2CHSTA &= 0xfd; + P2INTEN |= 2; + P2CHSTA &= 0xfd; + sleepForMsec(timeout); + P2CHSTA &= 0xfd; + P2INTEN &= 0xfd; + + P2FUNC = tmp_P2FUNC; + P2DIR = tmp_P2DIR; + P2PULL = tmp_P2PULL; + P2LVLSEL = tmp_P2LVLSEL; + eepromPrvDeselect(); +} +static void epdBusyWait(uint32_t timeout) { + uint32_t __xdata start = timerGet(); + + while (timerGet() - start < timeout) { + if (!P2_1) + return; + } + pr("screen timeout %lu ticks :(\n", timerGet() - start); + while (1) + ; +} +static void commandReadBegin(uint8_t cmd) { + epdSelect(); + markCommand(); + spiByte(cmd); // dump LUT + + P0DIR = (P0DIR & ~(1 << 0)) | (1 << 1); + P0 &= ~(1 << 0); + P0FUNC &= ~((1 << 0) | (1 << 1)); + P2_2 = 1; +} +static void commandReadEnd() { + // set up pins for spi (0.0,0.1,0.2) + P0FUNC |= (1 << 0) | (1 << 1); + epdDeselect(); +} +#pragma callee_saves epdReadByte +static uint8_t epdReadByte() { + uint8_t val = 0, i; + + for (i = 0; i < 8; i++) { + P0_0 = 1; + __asm__("nop\nnop\nnop\nnop\nnop\nnop\n"); + val <<= 1; + if (P0_1) + val++; + P0_0 = 0; + __asm__("nop\nnop\nnop\nnop\nnop\nnop\n"); + } + + return val; +} +static void shortCommand(uint8_t cmd) { + epdSelect(); + markCommand(); + spiTXByte(cmd); + epdDeselect(); +} +static void shortCommand1(uint8_t cmd, uint8_t arg) { + epdSelect(); + markCommand(); + spiTXByte(cmd); + markData(); + spiTXByte(arg); + epdDeselect(); +} +static void shortCommand2(uint8_t cmd, uint8_t arg1, uint8_t arg2) { + epdSelect(); + markCommand(); + spiTXByte(cmd); + markData(); + spiTXByte(arg1); + spiTXByte(arg2); + epdDeselect(); +} +static void commandBegin(uint8_t cmd) { + epdSelect(); + markCommand(); + spiTXByte(cmd); + markData(); +} +static void epdReset() { + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + P2_0 = 0; + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + P2_0 = 1; + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + + shortCommand(CMD_SOFT_RESET); // software reset + timerDelay(TIMER_TICKS_PER_SECOND / 1000); + shortCommand(CMD_SOFT_RESET2); + timerDelay(TIMER_TICKS_PER_SECOND / 1000); +} +void epdEnterSleep() { + P2_0 = 0; + timerDelay(10); + P2_0 = 1; + timerDelay(50); + shortCommand(CMD_SOFT_RESET2); + epdBusyWait(133300); + shortCommand1(CMD_ENTER_SLEEP, 0x03); + isInited = false; +} +void epdSetup() { + epdReset(); + shortCommand1(CMD_ANALOG_BLK_CTRL, 0x54); + shortCommand1(CMD_DIGITAL_BLK_CTRL, 0x3B); + shortCommand2(CMD_UNKNOWN_1, 0x04, 0x63); + + commandBegin(CMD_SOFT_START_CTRL); + epdSend(0x8f); + epdSend(0x8f); + epdSend(0x8f); + epdSend(0x3f); + commandEnd(); + + commandBegin(CMD_DRV_OUTPUT_CTRL); + epdSend((SCREEN_HEIGHT - 1) & 0xff); + epdSend((SCREEN_HEIGHT - 1) >> 8); + epdSend(0x00); + commandEnd(); + + // shortCommand1(CMD_DATA_ENTRY_MODE, 0x03); + shortCommand1(CMD_BORDER_WAVEFORM_CTRL, 0xC0); + shortCommand1(CMD_TEMP_SENSOR_CONTROL, 0x80); + shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); // mode 1 (i2C) + // shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB9); // mode 2? + shortCommand(CMD_ACTIVATION); + epdBusyWait(1333000UL); + isInited = true; +} +static uint8_t epdGetStatus() { + uint8_t sta; + commandReadBegin(0x2F); + sta = epdReadByte(); + commandReadEnd(); + return sta; +} +uint16_t epdGetBattery(void) { + uint16_t voltage = 2600; + + if (!isInited) + epdReset(); + uint8_t val; + + shortCommand1(CMD_DISP_UPDATE_CTRL2, SCREEN_CMD_CLOCK_ON | SCREEN_CMD_ANALOG_ON); + shortCommand(CMD_ACTIVATION); + epdBusyWait(133300); + + for (val = 3; val < 8; val++) { + shortCommand1(CMD_SETUP_VOLT_DETECT, val); + epdBusyWait(133300); + if (epdGetStatus() & 0x10) { // set if voltage is less than threshold ( == 1.9 + val / 10) + voltage = 1850 + mathPrvMul8x8(val, 100); + break; + } + } + + shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); + shortCommand(CMD_ACTIVATION); + epdBusyWait(133300); + + if (!isInited) + epdEnterSleep(); + return voltage; +} + +void selectLUT(uint8_t lut) { + if (lut == currentLUT) return; + //lut = 1; + switch (lut) { + case 0: + shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); // mode 1? + shortCommand(CMD_ACTIVATION); + epdBusyWait(1333000UL); + break; + case 1: + commandBegin(CMD_WRITE_LUT); + for (uint8_t i = 0; i < 70; i++) + epdSend(lutorig[i]); + commandEnd(); + break; + } + currentLUT = lut; +} + +void setWindowX(uint16_t start, uint16_t end) { + shortCommand2(CMD_WINDOW_X_SIZE, start / 8, end / 8 - 1); +} +void setWindowY(uint16_t start, uint16_t end) { + commandBegin(CMD_WINDOW_Y_SIZE); + epdSend((start)&0xff); + epdSend((start) >> 8); + epdSend((end - 1) & 0xff); + epdSend((end - 1) >> 8); + commandEnd(); +} +void setPosXY(uint16_t x, uint16_t y) { + shortCommand1(CMD_XSTART_POS, (uint8_t)(x / 8)); + commandBegin(CMD_YSTART_POS); + epdSend((y)&0xff); + epdSend((y) >> 8); + commandEnd(); +} +void setColorMode(uint8_t red, uint8_t bw) { + shortCommand1(CMD_DISP_UPDATE_CTRL, (red << 4) | bw); +} +void fillWindowWithPattern(bool color) { + if (color == EPD_COLOR_RED) { + shortCommand1(CMD_WRITE_PATTERN_RED, 0x00); + } else { + shortCommand1(CMD_WRITE_PATTERN_BW, 0x00); + } +} +void clearWindow(bool color) { + if (color == EPD_COLOR_RED) { + shortCommand1(CMD_WRITE_PATTERN_RED, 0x66); + } else { + shortCommand1(CMD_WRITE_PATTERN_BW, 0x66); + } +} +void clearScreen() { + setWindowX(0, SCREEN_WIDTH); + setWindowY(0, SCREEN_HEIGHT); + setPosXY(0, 0); + shortCommand1(CMD_WRITE_PATTERN_BW, 0x66); + epdBusyWait(133300UL); + shortCommand1(CMD_WRITE_PATTERN_RED, 0x66); + epdBusyWait(133300UL); +} +void draw() { + shortCommand1(0x22, 0xCF); + // shortCommand1(0x22, SCREEN_CMD_REFRESH); + shortCommand(0x20); + epdBusyWait(TIMER_TICKS_PER_SECOND * 120); +} +void drawLineHorizontal(bool red, uint16_t y, uint8_t width) { + setWindowX(0, SCREEN_WIDTH); + setWindowY(y, y + width); + if (red) { + shortCommand1(CMD_WRITE_PATTERN_RED, 0xE6); + } else { + shortCommand1(CMD_WRITE_PATTERN_BW, 0xE6); + } + epdBusyWait(133300UL); +} +void beginFullscreenImage() { + setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); + setWindowX(0, SCREEN_WIDTH); + setWindowY(0, SCREEN_HEIGHT); + shortCommand1(CMD_DATA_ENTRY_MODE, 3); + setPosXY(0, 0); +} +void beginWriteFramebuffer(bool color) { + if (color == EPD_COLOR_RED) { + commandBegin(CMD_WRITE_FB_RED); + } else { + commandBegin(CMD_WRITE_FB_BW); + } + epdDeselect(); +} +void endWriteFramebuffer() { + commandEnd(); +} + +// stuff for printing text +static void pushXFontBytesToEPD(uint8_t byte1, uint8_t byte2) { + if (epdCharSize == 1) { + uint8_t offset = 7 - (fontCurXpos % 8); + for (uint8_t c = 0; c < 8; c++) { + if (byte1 & (1 << c)) rbuffer[c] |= (1 << offset); + } + for (uint8_t c = 0; c < 8; c++) { + if (byte2 & (1 << c)) rbuffer[8 + c] |= (1 << offset); + } + fontCurXpos++; + } else { + uint8_t offset = 6 - (fontCurXpos % 8); + // double font size + for (uint8_t c = 0; c < 8; c++) { + if (byte1 & (1 << c)) { + rbuffer[c * 2] |= (3 << offset); + rbuffer[(c * 2) + 1] |= (3 << offset); + } + } + for (uint8_t c = 0; c < 8; c++) { + if (byte2 & (1 << c)) { + rbuffer[(c * 2) + 16] |= (3 << offset); + rbuffer[(c * 2) + 17] |= (3 << offset); + } + } + fontCurXpos += 2; + } + if (fontCurXpos % 8 == 0) { + // next byte, flush current byte to EPD + + for (uint8_t i = 0; i < (16 * epdCharSize); i++) { + epdSend(rbuffer[i]); + } + memset(rbuffer, 0, 32); + } +} +static void pushYFontBytesToEPD(uint8_t byte1, uint8_t byte2) { + if (epdCharSize == 2) { + for (uint8_t j = 0; j < 2; j++) { + uint8_t c = 0; + for (uint8_t i = 7; i != 255; i--) { + if (byte1 & (1 << i)) c |= (0x03 << ((i % 4) * 2)); + if ((i % 4) == 0) { + epdSend(c); + c = 0; + } + } + for (uint8_t i = 7; i != 255; i--) { + if (byte2 & (1 << i)) c |= (0x03 << ((i % 4) * 2)); + if ((i % 4) == 0) { + epdSend(c); + c = 0; + } + } + } + } else { + epdSend(byte1); + epdSend(byte2); + } +} +void writeCharEPD(uint8_t c) { + if (!epdPr) { + return; + } + // Writes a single character to the framebuffer + bool empty = true; + for (uint8_t i = 0; i < 20; i++) { + if (font[c][i]) empty = false; + } + if (empty) { + for (uint8_t i = 0; i < 8; i++) { + if (directionY) { + pushYFontBytesToEPD(0x00, 0x00); + } else { + pushXFontBytesToEPD(0x00, 0x00); + } + } + return; + } + + uint8_t begin = 0; + while (font[c][begin] == 0x00 && font[c][begin + 1] == 0x00) { + begin += 2; + } + + uint8_t end = 20; + while (font[c][end - 1] == 0x00 && font[c][end - 2] == 0x00) { + end -= 2; + } + + for (uint8_t pos = begin; pos < end; pos += 2) { + if (directionY) { + pushYFontBytesToEPD(font[c][pos + 1], font[c][pos]); + } else { + pushXFontBytesToEPD(font[c][pos], font[c][pos + 1]); + } + } + + // spacing between characters + if (directionY) { + pushYFontBytesToEPD(0x00, 0x00); + } else { + pushXFontBytesToEPD(0x00, 0x00); + } +} +// Print text to the EPD. Origin is top-left +void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool color) { + directionY = direction; + epdCharSize = 1 + fontsize; + if (directionY) { + if (epdCharSize == 2) { + setWindowX(x - 32, x); + setPosXY(x - 32, y); + + } else { + setWindowX(x - 16, x); + setPosXY(x - 16, y); + } + setWindowY(y, SCREEN_HEIGHT); + shortCommand1(CMD_DATA_ENTRY_MODE, 3); + } else { + if (epdCharSize == 2) { + x /= 2; + x *= 2; + setWindowY(y, y + 32); + } else { + setWindowY(y, y + 16); + } + setPosXY(x, y); + fontCurXpos = x; + setWindowX(x, SCREEN_WIDTH); + shortCommand1(CMD_DATA_ENTRY_MODE, 7); + memset(rbuffer, 0, 32); + } + + epdPr = true; + if (color) { + commandBegin(CMD_WRITE_FB_RED); + } else { + commandBegin(CMD_WRITE_FB_BW); + } +} +void epdPrintEnd() { + if (!directionY && ((fontCurXpos % 8) != 0)) { + for (uint8_t i = 0; i < (16 * epdCharSize); i++) { + epdSend(rbuffer[i]); + } + } + commandEnd(); + epdPr = false; +} + +extern uint8_t* __xdata tempBuffer; +extern void dump(uint8_t *__xdata a, uint16_t __xdata l); + +void loadFixedTempLUT() { + shortCommand1(0x18, 0x48); + shortCommand2(0x1A, 0x05, 0x00); // < temp register + shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); // mode 1 (i2C) + shortCommand(CMD_ACTIVATION); + epdBusyWait(1333000UL); +} +static uint8_t readLut() { + uint8_t sta = 0; + + commandReadBegin(0x33); + + uint16_t checksum = 0; + uint16_t ident = 0; + uint16_t shortl = 0; + for (uint16_t c = 0; c < 76; c++) { + sta = epdReadByte(); + checksum += sta; + if (c < 70) ident += sta; + if (c < 14) shortl += sta; + tempBuffer[c] = sta; + } + pr("ident=%04X checksum=%04X shortl=%04X\n", ident, checksum, shortl); + commandReadEnd(); + dump(tempBuffer, 96); + + return sta; +} \ No newline at end of file diff --git a/tag_fw/epd.h b/tag_fw/epd.h new file mode 100644 index 00000000..88f6a2ff --- /dev/null +++ b/tag_fw/epd.h @@ -0,0 +1,54 @@ +#ifndef _JSCREEN_H_ +#define _JSCREEN_H_ + +#include +#include + +#define epdSend spiTXByte +#define EPD_DIRECTION_X false +#define EPD_DIRECTION_Y true +#define EPD_SIZE_SINGLE false +#define EPD_SIZE_DOUBLE true +#define EPD_COLOR_RED true +#define EPD_COLOR_BLACK false + +#define EPD_MODE_NORMAL 0x00 +#define EPD_MODE_INVERT 0x08 +#define EPD_MODE_IGNORE 0x04 + +#define epdSelect() \ + do { \ + P1_7 = 0; \ + } while (0) + +#define epdDeselect() \ + do { \ + P1_7 = 1; \ + } while (0) + +void epdSetup(); +void epdEnterSleep(); + +void setWindowX(uint16_t start, uint16_t end); +void setWindowY(uint16_t start, uint16_t end); +void setPosXY(uint16_t x, uint16_t y); +void setColorMode(uint8_t red, uint8_t bw) ; +void fillWindowWithPattern(bool color); +void clearWindow(bool color); +void clearScreen(); +void draw(); +void drawLineHorizontal(bool red, uint16_t y, uint8_t width); +void beginFullscreenImage(); +void beginWriteFramebuffer(bool color); +void endWriteFramebuffer(); + +void ByteDecode(uint8_t byte); + +void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool red); +void epdPrintEnd(); + + +void beginFullscreenImage(); +void beginWriteFramebuffer(bool color); + +#endif \ No newline at end of file diff --git a/tag_fw/font.h b/tag_fw/font.h new file mode 100644 index 00000000..55e1f604 --- /dev/null +++ b/tag_fw/font.h @@ -0,0 +1,259 @@ +static const uint8_t __code font[256][20]={ +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x00 +{0x80,0x07,0xC0,0x0C,0x40,0x0A,0xA0,0x14,0x20,0x14,0x20,0x14,0xA0,0x14,0x40,0x0A,0xC0,0x0C,0x80,0x07}, // 0x01 +{0x80,0x07,0xC0,0x0F,0xC0,0x0D,0x60,0x1B,0xE0,0x1B,0xE0,0x1B,0x60,0x1B,0xC0,0x0D,0xC0,0x0F,0x80,0x07}, // 0x02 +{0x7C,0x00,0xFE,0x01,0xFE,0x07,0xFE,0x0F,0xFC,0x1F,0xFC,0x1F,0xFE,0x0F,0xFE,0x07,0xFE,0x01,0x7C,0x00}, // 0x03 +{0x80,0x00,0xC0,0x01,0xF0,0x03,0xF8,0x07,0xFE,0x1F,0xF8,0x0F,0xF0,0x07,0xE0,0x03,0xC0,0x01,0x80,0x00}, // 0x04 +{0xC0,0x01,0xE0,0x03,0xE0,0x03,0xFC,0x03,0xFE,0x1F,0xFE,0x1D,0xFC,0x03,0xE0,0x03,0xE0,0x03,0xC0,0x01}, // 0x05 +{0xC0,0x01,0xE0,0x03,0xF0,0x03,0xF8,0x03,0xFC,0x1F,0xFE,0x1D,0xF8,0x03,0xF0,0x03,0xE0,0x03,0xC0,0x01}, // 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 +{0x80,0x07,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x24,0x10,0x74,0x08,0x8E,0x07,0x0E,0x00,0x18,0x00}, // 0x0B +{0x00,0x00,0x78,0x00,0x84,0x04,0x02,0x05,0x02,0x1F,0x02,0x05,0x82,0x05,0x84,0x00,0x78,0x00,0x00,0x00}, // 0x0C +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x0D +{0x00,0x60,0x00,0x70,0x00,0x70,0xF8,0x3F,0x18,0x00,0x08,0x0C,0x0C,0x0E,0x04,0x0E,0xFE,0x07,0x00,0x00}, // 0x0E +{0x80,0x00,0xF0,0x03,0x20,0x03,0x10,0x02,0x18,0x06,0x10,0x02,0x20,0x02,0x30,0x03,0xD0,0x03,0x80,0x00}, // 0x0F +{0xF0,0x1F,0xE0,0x0F,0xE0,0x0F,0xC0,0x07,0xC0,0x07,0xC0,0x07,0x80,0x03,0x80,0x03,0x00,0x01,0x00,0x01}, // 0x10 +{0x00,0x01,0x00,0x01,0x80,0x03,0x80,0x03,0xC0,0x07,0xC0,0x07,0xC0,0x07,0xE0,0x0F,0xE0,0x0F,0xF0,0x1F}, // 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,0xFE,0x1B,0x00,0x00,0x00,0x00,0xFE,0x1B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x13 +{0x00,0x00,0x1C,0x00,0x3E,0x00,0x7E,0x00,0xFE,0x7F,0x02,0x00,0x02,0x00,0xFE,0x7F,0x00,0x00,0x00,0x00}, // 0x14 +{0x00,0x00,0x00,0x00,0xDC,0x61,0x32,0x43,0x22,0x42,0x62,0x46,0x42,0x4C,0x82,0x3B,0x00,0x00,0x00,0x00}, // 0x15 +{0x00,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0x00,0x00}, // 0x16 +{0x00,0x00,0x00,0x00,0x08,0x88,0x04,0x90,0xFE,0xBF,0x04,0x90,0x08,0x88,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x17 +{0x00,0x00,0x00,0x00,0x08,0x00,0x04,0x00,0xFE,0x7F,0x04,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x18 +{0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x20,0xFE,0x7F,0x00,0x20,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x19 +{0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x40,0x05,0x80,0x03,0x00,0x01,0x00,0x00}, // 0x1A +{0x00,0x00,0x00,0x01,0x80,0x03,0x40,0x05,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00}, // 0x1B +{0x00,0x00,0xE0,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x00}, // 0x1C +{0x00,0x00,0x00,0x01,0x80,0x03,0x40,0x05,0x00,0x01,0x00,0x01,0x40,0x05,0x80,0x03,0x00,0x01,0x00,0x00}, // 0x1D +{0x00,0x10,0x00,0x18,0x00,0x1E,0x80,0x1F,0xC0,0x1F,0xF0,0x1F,0xC0,0x1F,0x00,0x1F,0x00,0x1C,0x00,0x10}, // 0x1E +{0x10,0x00,0x30,0x00,0xF0,0x00,0xF0,0x03,0xF0,0x07,0xF0,0x1F,0xF0,0x07,0xF0,0x01,0x70,0x00,0x10,0x00}, // 0x1F +{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,0xFE,0x1B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x21 +{0x00,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x22 +{0x00,0x01,0x20,0x19,0xA0,0x07,0x78,0x01,0x26,0x19,0xA0,0x07,0x78,0x01,0x26,0x01,0x20,0x01,0x00,0x00}, // 0x23 +{0x00,0x00,0x00,0x00,0x1C,0x18,0x26,0x10,0x42,0x10,0xFF,0x3F,0x82,0x11,0x02,0x0F,0x00,0x00,0x00,0x00}, // 0x24 +{0x1C,0x10,0x22,0x08,0x22,0x04,0x22,0x03,0x9C,0x00,0x40,0x0E,0x30,0x11,0x08,0x11,0x04,0x11,0x02,0x0E}, // 0x25 +{0x00,0x07,0x80,0x08,0x5C,0x10,0x62,0x10,0xA2,0x11,0x32,0x13,0x1C,0x1C,0x00,0x18,0x00,0x16,0x80,0x01}, // 0x26 +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x27 +{0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x18,0x18,0x0C,0x30,0x04,0x20,0x02,0x40,0x02,0x40,0x00,0x00}, // 0x28 +{0x00,0x00,0x02,0x40,0x02,0x40,0x04,0x20,0x0C,0x30,0x18,0x18,0xE0,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x29 +{0x00,0x00,0x08,0x00,0x18,0x00,0xF0,0x00,0x4E,0x00,0xF0,0x00,0x18,0x00,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x2A +{0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0xE0,0x1F,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x00}, // 0x2B +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x98,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x2C +{0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00}, // 0x2D +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x2E +{0x00,0x00,0x00,0x40,0x00,0x30,0x00,0x0C,0x00,0x03,0xC0,0x00,0x30,0x00,0x0C,0x00,0x02,0x00,0x00,0x00}, // 0x2F +{0x00,0x00,0xF0,0x03,0x0C,0x0C,0x02,0x10,0x02,0x10,0x02,0x10,0x0C,0x0C,0xF0,0x03,0x00,0x00,0x00,0x00}, // 0x30 +{0x00,0x00,0x04,0x10,0x04,0x10,0x04,0x10,0xFE,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x31 +{0x00,0x00,0x06,0x18,0x02,0x14,0x02,0x12,0x02,0x11,0xC2,0x10,0x3C,0x10,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x32 +{0x00,0x00,0x00,0x00,0x02,0x10,0x42,0x10,0x42,0x10,0x42,0x10,0xBC,0x0F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x33 +{0x00,0x03,0xC0,0x02,0x20,0x02,0x18,0x02,0x04,0x02,0xFE,0x1F,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00}, // 0x34 +{0x00,0x00,0x00,0x00,0x3E,0x10,0x22,0x10,0x22,0x10,0x42,0x08,0x82,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x35 +{0x00,0x00,0xF0,0x07,0x4C,0x08,0x22,0x10,0x22,0x10,0x22,0x10,0x42,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x36 +{0x00,0x00,0x02,0x00,0x02,0x18,0x02,0x07,0xC2,0x00,0x32,0x00,0x0A,0x00,0x06,0x00,0x00,0x00,0x00,0x00}, // 0x37 +{0x00,0x00,0x1C,0x0F,0xA2,0x10,0x42,0x10,0x42,0x10,0xA2,0x10,0xA2,0x09,0x1C,0x06,0x00,0x00,0x00,0x00}, // 0x38 +{0x00,0x00,0x78,0x00,0x84,0x10,0x02,0x11,0x02,0x11,0x02,0x11,0x84,0x0C,0xF8,0x03,0x00,0x00,0x00,0x00}, // 0x39 +{0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x18,0x60,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x3A +{0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x98,0x60,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x3B +{0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x03,0x80,0x04,0x80,0x04,0x40,0x08,0x40,0x08,0x20,0x10,0x00,0x00}, // 0x3C +{0x00,0x00,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x00,0x00}, // 0x3D +{0x00,0x00,0x20,0x10,0x40,0x08,0x40,0x08,0x80,0x04,0x80,0x04,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // 0x3E +{0x00,0x00,0x0E,0x00,0x02,0x00,0x02,0x1B,0x82,0x00,0x42,0x00,0x26,0x00,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x3F +{0xF0,0x03,0x18,0x0C,0x04,0x18,0xE2,0x13,0x12,0x14,0x0A,0x16,0x8A,0x1B,0xFC,0x07,0x00,0x04,0x00,0x04}, // 0x40 +{0x00,0x10,0x00,0x0E,0x80,0x03,0x70,0x02,0x18,0x02,0x30,0x02,0xC0,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0x41 +{0x00,0x00,0xF8,0x1F,0x88,0x10,0x88,0x10,0x88,0x10,0x88,0x10,0x48,0x11,0x30,0x0E,0x00,0x00,0x00,0x00}, // 0x42 +{0xC0,0x03,0x30,0x0C,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x18,0x10,0x00,0x00,0x00,0x00}, // 0x43 +{0x00,0x00,0xF8,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00,0x00,0x00}, // 0x44 +{0x00,0x00,0xF8,0x1F,0x08,0x11,0x08,0x11,0x08,0x11,0x08,0x11,0x08,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0x45 +{0x00,0x00,0xF8,0x1F,0x08,0x01,0x08,0x01,0x08,0x01,0x08,0x01,0x08,0x01,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x46 +{0x00,0x00,0xC0,0x03,0x30,0x0C,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x11,0x08,0x11,0x18,0x1F,0x00,0x00}, // 0x47 +{0x00,0x00,0xF8,0x1F,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0x48 +{0x00,0x00,0x08,0x10,0x08,0x10,0x08,0x10,0xF8,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0x49 +{0x00,0x00,0x00,0x00,0x00,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0xF8,0x0F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x4A +{0x00,0x00,0xF8,0x1F,0x80,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x04,0x08,0x08,0x00,0x10,0x00,0x00}, // 0x4B +{0x00,0x00,0xF8,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x4C +{0xF8,0x1F,0x38,0x00,0xE0,0x01,0x00,0x07,0x00,0x06,0xC0,0x01,0x38,0x00,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0x4D +{0x00,0x00,0xF8,0x1F,0x10,0x00,0x60,0x00,0x80,0x01,0x00,0x06,0x00,0x08,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0x4E +{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0x4F +{0x00,0x00,0xF8,0x1F,0x08,0x01,0x08,0x01,0x08,0x01,0x08,0x01,0x88,0x00,0x70,0x00,0x00,0x00,0x00,0x00}, // 0x50 +{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x30,0x10,0x48,0xE0,0x47,0x00,0x00}, // 0x51 +{0x00,0x00,0xF8,0x1F,0x08,0x01,0x08,0x01,0x08,0x03,0x88,0x04,0x70,0x08,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x52 +{0x00,0x00,0x70,0x18,0x48,0x10,0x88,0x10,0x88,0x10,0x08,0x11,0x08,0x09,0x18,0x0E,0x00,0x00,0x00,0x00}, // 0x53 +{0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0xF8,0x1F,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x00,0x00}, // 0x54 +{0x00,0x00,0xF8,0x07,0x00,0x18,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0x55 +{0x08,0x00,0x30,0x00,0xC0,0x01,0x00,0x06,0x00,0x18,0x00,0x18,0x00,0x07,0xC0,0x00,0x30,0x00,0x08,0x00}, // 0x56 +{0x18,0x00,0xE0,0x03,0x00,0x1C,0x00,0x0F,0xE0,0x00,0xC0,0x01,0x00,0x0E,0x00,0x1C,0xE0,0x03,0x18,0x00}, // 0x57 +{0x08,0x10,0x10,0x08,0x20,0x04,0x40,0x02,0x80,0x01,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x08,0x08,0x10}, // 0x58 +{0x08,0x00,0x10,0x00,0x60,0x00,0x80,0x00,0x00,0x1F,0x80,0x00,0x40,0x00,0x20,0x00,0x10,0x00,0x08,0x00}, // 0x59 +{0x00,0x00,0x08,0x18,0x08,0x14,0x08,0x12,0x08,0x11,0x88,0x10,0x48,0x10,0x28,0x10,0x18,0x10,0x00,0x00}, // 0x5A +{0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x7F,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0x00,0x00,0x00,0x00}, // 0x5B +{0x00,0x00,0x02,0x00,0x0C,0x00,0x30,0x00,0xC0,0x00,0x00,0x03,0x00,0x0C,0x00,0x30,0x00,0x40,0x00,0x00}, // 0x5C +{0x00,0x00,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0xFE,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x5D +{0x00,0x00,0x00,0x04,0x00,0x03,0xE0,0x00,0x38,0x00,0x0E,0x00,0x70,0x00,0x80,0x03,0x00,0x04,0x00,0x00}, // 0x5E +{0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20}, // 0x5F +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x60 +{0x00,0x00,0x00,0x0C,0x20,0x12,0x20,0x11,0x20,0x11,0x20,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x61 +{0x00,0x00,0xFE,0x1F,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x60,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x62 +{0x00,0x00,0x80,0x07,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00}, // 0x63 +{0x00,0x00,0x80,0x07,0x40,0x18,0x20,0x10,0x20,0x10,0x20,0x10,0x40,0x08,0xFE,0x1F,0x00,0x00,0x00,0x00}, // 0x64 +{0x00,0x00,0x80,0x07,0x40,0x09,0x20,0x11,0x20,0x11,0x20,0x11,0x20,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x65 +{0x00,0x00,0x20,0x00,0x20,0x00,0xFC,0x1F,0x24,0x00,0x22,0x00,0x22,0x00,0x22,0x00,0x22,0x00,0x00,0x00}, // 0x66 +{0x00,0x00,0x80,0x07,0x40,0x98,0x20,0x90,0x20,0x90,0x20,0x90,0x40,0x48,0xE0,0x3F,0x00,0x00,0x00,0x00}, // 0x67 +{0x00,0x00,0xFE,0x1F,0x80,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xC0,0x1F,0x00,0x00,0x00,0x00}, // 0x68 +{0x00,0x00,0x20,0x00,0x20,0x00,0x26,0x00,0xE6,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x69 +{0x00,0x00,0x00,0x80,0x20,0x80,0x20,0x80,0x26,0x80,0xE6,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x6A +{0x00,0x00,0xFE,0x1F,0x00,0x01,0x00,0x03,0x80,0x04,0x40,0x04,0x40,0x08,0x20,0x10,0x00,0x00,0x00,0x00}, // 0x6B +{0x00,0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0xFE,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x6C +{0xE0,0x1F,0x40,0x00,0x20,0x00,0x20,0x00,0xC0,0x1F,0x40,0x00,0x20,0x00,0x20,0x00,0xC0,0x1F,0x00,0x00}, // 0x6D +{0x00,0x00,0xE0,0x1F,0xC0,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xC0,0x1F,0x00,0x00,0x00,0x00}, // 0x6E +{0x00,0x00,0x80,0x07,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x6F +{0x00,0x00,0xE0,0xFF,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x60,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x70 +{0x00,0x00,0x80,0x07,0x40,0x18,0x20,0x10,0x20,0x10,0x20,0x10,0x40,0x08,0xE0,0xFF,0x00,0x00,0x00,0x00}, // 0x71 +{0x00,0x00,0x00,0x00,0xE0,0x1F,0x80,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0xE0,0x00,0x00,0x00,0x00,0x00}, // 0x72 +{0x00,0x00,0xC0,0x18,0x20,0x11,0x20,0x11,0x20,0x12,0x20,0x12,0x20,0x0C,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x73 +{0x00,0x00,0x20,0x00,0x20,0x00,0xF8,0x0F,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00}, // 0x74 +{0x00,0x00,0xE0,0x0F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x75 +{0x20,0x00,0xC0,0x01,0x00,0x06,0x00,0x18,0x00,0x10,0x00,0x0C,0x00,0x03,0xC0,0x00,0x20,0x00,0x00,0x00}, // 0x76 +{0x60,0x00,0x80,0x07,0x00,0x18,0x00,0x0E,0xC0,0x01,0x80,0x01,0x00,0x0E,0x00,0x18,0x80,0x07,0x60,0x00}, // 0x77 +{0x00,0x00,0x20,0x10,0x40,0x08,0x80,0x04,0x00,0x03,0x00,0x03,0x80,0x04,0x40,0x08,0x20,0x10,0x00,0x00}, // 0x78 +{0x20,0x80,0xC0,0x80,0x00,0x83,0x00,0x46,0x00,0x38,0x00,0x18,0x00,0x06,0x00,0x01,0xC0,0x00,0x20,0x00}, // 0x79 +{0x00,0x00,0x20,0x10,0x20,0x18,0x20,0x14,0x20,0x12,0x20,0x11,0xA0,0x10,0x60,0x10,0x20,0x10,0x00,0x00}, // 0x7A +{0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0xFC,0x3E,0x02,0x40,0x02,0x40,0x02,0x40,0x00,0x00,0x00,0x00}, // 0x7B +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x7C +{0x00,0x00,0x00,0x00,0x02,0x40,0x02,0x40,0x02,0x40,0xFC,0x3E,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00}, // 0x7D +{0x00,0x03,0x80,0x00,0x80,0x00,0x80,0x00,0x00,0x01,0x00,0x01,0x00,0x02,0x00,0x02,0x00,0x02,0x80,0x01}, // 0x7E +{0x00,0x00,0x80,0x1F,0xC0,0x10,0x20,0x10,0x10,0x10,0x20,0x10,0xC0,0x10,0x80,0x1F,0x00,0x00,0x00,0x00}, // 0x7F +{0xC0,0x03,0x30,0x0C,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x90,0x08,0xB0,0x18,0xD0,0x00,0x00,0x00,0x00}, // 0x80 +{0x00,0x00,0xE0,0x0F,0x04,0x10,0x00,0x10,0x00,0x10,0x04,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x81 +{0x00,0x00,0x80,0x07,0x40,0x09,0x20,0x11,0x24,0x11,0x22,0x11,0x20,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x82 +{0x00,0x00,0x00,0x0C,0x24,0x12,0x22,0x11,0x22,0x11,0x24,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x83 +{0x00,0x00,0x00,0x0C,0x24,0x12,0x20,0x11,0x20,0x11,0x24,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x84 +{0x00,0x00,0x00,0x0C,0x22,0x12,0x24,0x11,0x20,0x11,0x20,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x85 +{0x00,0x00,0x00,0x0C,0x20,0x12,0x22,0x11,0x25,0x11,0x22,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x86 +{0x00,0x00,0x80,0x07,0x40,0x08,0x20,0x10,0x20,0x90,0x20,0xB0,0x20,0xD0,0x20,0x10,0x00,0x00,0x00,0x00}, // 0x87 +{0x00,0x00,0x80,0x07,0x44,0x09,0x22,0x11,0x22,0x11,0x24,0x11,0x20,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x88 +{0x00,0x00,0x80,0x07,0x40,0x09,0x24,0x11,0x20,0x11,0x20,0x11,0x24,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x89 +{0x00,0x00,0x80,0x07,0x40,0x09,0x22,0x11,0x24,0x11,0x20,0x11,0x20,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x8A +{0x00,0x00,0x20,0x00,0x24,0x00,0x20,0x00,0xE0,0x1F,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x8B +{0x00,0x00,0x20,0x00,0x24,0x00,0x22,0x00,0xE2,0x1F,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x8C +{0x00,0x00,0x20,0x00,0x20,0x00,0x22,0x00,0xE4,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x8D +{0x00,0x10,0x00,0x0E,0x81,0x03,0x70,0x02,0x18,0x02,0x30,0x02,0xC1,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0x8E +{0x00,0x10,0x00,0x0E,0x80,0x03,0x72,0x02,0x0D,0x02,0x1D,0x02,0xF2,0x02,0x80,0x03,0x00,0x0E,0x00,0x10}, // 0x8F +{0x00,0x00,0xF8,0x1F,0x08,0x11,0x08,0x11,0x0A,0x11,0x09,0x11,0x08,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0x90 +{0x20,0x0E,0x20,0x13,0x20,0x11,0x20,0x11,0xC0,0x0F,0x20,0x19,0x20,0x11,0x20,0x11,0xC0,0x11,0x00,0x00}, // 0x91 +{0x00,0x10,0x00,0x0C,0x80,0x07,0x60,0x04,0x18,0x04,0xF8,0x1F,0x08,0x11,0x08,0x11,0x08,0x10,0x00,0x00}, // 0x92 +{0x00,0x00,0x80,0x07,0x44,0x08,0x22,0x10,0x22,0x10,0x24,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x93 +{0x00,0x00,0x80,0x07,0x44,0x08,0x20,0x10,0x20,0x10,0x24,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x94 +{0x00,0x00,0x80,0x07,0x42,0x08,0x24,0x10,0x20,0x10,0x20,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x95 +{0x00,0x00,0xE0,0x0F,0x04,0x10,0x02,0x10,0x02,0x10,0x04,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x96 +{0x00,0x00,0xE0,0x0F,0x02,0x10,0x04,0x10,0x00,0x10,0x00,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x97 +{0x20,0x80,0xC0,0x80,0x04,0x83,0x00,0x46,0x00,0x38,0x04,0x18,0x00,0x06,0x00,0x01,0xC0,0x00,0x20,0x00}, // 0x98 +{0x00,0x00,0xE0,0x07,0x11,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x09,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0x99 +{0x00,0x00,0xF8,0x07,0x01,0x18,0x00,0x10,0x00,0x10,0x00,0x10,0x01,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0x9A +{0x00,0x00,0x80,0x17,0x40,0x08,0x20,0x14,0x20,0x13,0xA0,0x10,0x40,0x08,0xA0,0x07,0x00,0x00,0x00,0x00}, // 0x9B +{0x00,0x00,0x00,0x00,0x00,0x10,0x40,0x18,0xFC,0x17,0x42,0x10,0x42,0x10,0x02,0x10,0x00,0x00,0x00,0x00}, // 0x9C +{0x00,0x00,0xE0,0x17,0x10,0x0C,0x08,0x16,0x08,0x11,0x88,0x10,0x68,0x10,0x30,0x08,0xE8,0x07,0x00,0x00}, // 0x9D +{0x00,0x00,0x20,0x10,0x40,0x08,0x80,0x04,0x00,0x03,0x00,0x03,0x80,0x04,0x40,0x08,0x20,0x10,0x00,0x00}, // 0x9E +{0x00,0x00,0x00,0x80,0x20,0x80,0x20,0x80,0xFE,0x7F,0x21,0x00,0x21,0x00,0x01,0x00,0x00,0x00,0x00,0x00}, // 0x9F +{0x00,0x00,0x00,0x0C,0x20,0x12,0x20,0x11,0x24,0x11,0x22,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0xA0 +{0x00,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xE4,0x1F,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xA1 +{0x00,0x00,0x80,0x07,0x40,0x08,0x20,0x10,0x24,0x10,0x22,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0xA2 +{0x00,0x00,0xE0,0x0F,0x00,0x10,0x00,0x10,0x04,0x10,0x02,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xA3 +{0x00,0x00,0xE0,0x1F,0xC4,0x00,0x42,0x00,0x24,0x00,0x24,0x00,0x22,0x00,0xC0,0x1F,0x00,0x00,0x00,0x00}, // 0xA4 +{0x00,0x00,0xF8,0x1F,0x12,0x00,0x61,0x00,0x83,0x01,0x02,0x06,0x01,0x08,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0xA5 +{0x00,0x00,0x00,0x00,0x32,0x00,0x4A,0x00,0x4A,0x00,0x4A,0x00,0x7C,0x00,0x40,0x00,0x00,0x00,0x00,0x00}, // 0xA6 +{0x00,0x00,0x00,0x00,0x3C,0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x3C,0x00,0x00,0x00,0x00,0x00}, // 0xA7 +{0x00,0x00,0x00,0x70,0x00,0xC8,0x00,0x88,0x00,0x84,0x60,0x83,0x00,0x80,0x00,0xE0,0x00,0x00,0x00,0x00}, // 0xA8 +{0x00,0x00,0x38,0x00,0x44,0x00,0xBA,0x00,0xAA,0x00,0xBA,0x00,0x44,0x00,0x38,0x00,0x00,0x00,0x00,0x00}, // 0xA9 +{0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x07,0x00,0x00}, // 0xAA +{0x02,0x10,0x7E,0x0C,0x00,0x02,0x80,0x01,0x60,0x00,0x10,0x00,0x8C,0x10,0x82,0x1C,0x80,0x14,0x00,0x13}, // 0xAB +{0x02,0x10,0x7E,0x0C,0x00,0x02,0x80,0x01,0x60,0x00,0x10,0x06,0x0C,0x05,0x82,0x04,0x80,0x1F,0x00,0x04}, // 0xAC +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xAD +{0x00,0x00,0x00,0x01,0x80,0x02,0x40,0x04,0x20,0x08,0x00,0x01,0x80,0x02,0x40,0x04,0x20,0x08,0x00,0x00}, // 0xAE +{0x00,0x00,0x20,0x08,0x40,0x04,0x80,0x02,0x00,0x01,0x20,0x08,0x40,0x04,0x80,0x02,0x00,0x01,0x00,0x00}, // 0xAF +{0xDB,0x6C,0xDB,0x6C,0x00,0x00,0xDB,0x6C,0xDB,0x6C,0x00,0x00,0xDB,0x6C,0xDB,0x6C,0x00,0x00,0x00,0x00}, // 0xB0 +{0x6C,0xDB,0x6C,0xDB,0xDB,0x6C,0xFF,0xFF,0x6C,0xDB,0xDB,0x6C,0xFF,0xFF,0x6C,0xDB,0xDB,0x6C,0xDB,0x6C}, // 0xB1 +{0xFF,0xFF,0xFF,0xFF,0xDB,0x6C,0xFF,0xFF,0xFF,0xFF,0xDB,0x6C,0xFF,0xFF,0xFF,0xFF,0xDB,0x6C,0xDB,0x6C}, // 0xB2 +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xB3 +{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xB4 +{0x00,0x10,0x00,0x0E,0x80,0x03,0x70,0x02,0x1A,0x02,0x31,0x02,0xC0,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0xB5 +{0x00,0x10,0x00,0x0E,0x80,0x03,0x72,0x02,0x19,0x02,0x31,0x02,0xC2,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0xB6 +{0x00,0x10,0x00,0x0E,0x80,0x03,0x71,0x02,0x1A,0x02,0x30,0x02,0xC0,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0xB7 +{0xF0,0x03,0x0C,0x0C,0xE6,0x19,0x32,0x13,0x12,0x12,0x12,0x12,0x16,0x1A,0x0C,0x0C,0xF0,0x03,0x00,0x00}, // 0xB8 +{0x80,0x02,0x80,0x02,0x80,0x02,0xFF,0xFE,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 +{0x80,0x02,0x80,0x02,0x80,0x02,0x80,0xFE,0x80,0x00,0x80,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBB +{0x80,0x02,0x80,0x02,0x80,0x02,0xFF,0x02,0x00,0x02,0xFF,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBC +{0x00,0x00,0xF0,0x03,0x08,0x04,0x04,0x0C,0x04,0x08,0xFE,0x1F,0x04,0x08,0x04,0x08,0x00,0x00,0x00,0x00}, // 0xBD +{0x02,0x00,0x04,0x00,0x98,0x04,0xA0,0x04,0xC0,0x1F,0xA0,0x04,0x90,0x04,0x08,0x00,0x04,0x00,0x02,0x00}, // 0xBE +{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBF +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC0 +{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xFF,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC1 +{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC2 +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC3 +{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC4 +{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xFF,0xFF,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC5 +{0x00,0x00,0x00,0x0C,0x24,0x12,0x22,0x11,0x24,0x11,0x24,0x09,0xC2,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0xC6 +{0x00,0x10,0x00,0x0E,0x82,0x03,0x71,0x02,0x1B,0x02,0x32,0x02,0xC1,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0xC7 +{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0x00,0x02,0xFF,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xC8 +{0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xFF,0x80,0x00,0x80,0xFE,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xC9 +{0x80,0x02,0x80,0x02,0x80,0x02,0xFF,0x02,0x00,0x02,0xFF,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCA +{0x80,0x02,0x80,0x02,0x80,0x02,0x80,0xFE,0x80,0x00,0x80,0xFE,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCB +{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFE,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCC +{0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCD +{0x80,0x02,0x80,0x02,0x80,0x02,0xFF,0xFE,0x00,0x00,0xFF,0xFE,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCE +{0x00,0x00,0x08,0x04,0xF0,0x03,0x10,0x02,0x10,0x02,0x10,0x02,0x10,0x02,0xF0,0x03,0x08,0x04,0x00,0x00}, // 0xCF +{0x00,0x00,0x82,0x07,0x4A,0x08,0x2E,0x10,0x24,0x10,0x2A,0x10,0x70,0x08,0xC0,0x07,0x00,0x00,0x00,0x00}, // 0xD0 +{0x80,0x00,0xF8,0x1F,0x88,0x10,0x88,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00,0x00,0x00}, // 0xD1 +{0x00,0x00,0xF8,0x1F,0x08,0x11,0x0A,0x11,0x09,0x11,0x09,0x11,0x0A,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD2 +{0x00,0x00,0xF8,0x1F,0x09,0x11,0x08,0x11,0x08,0x11,0x08,0x11,0x09,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD3 +{0x00,0x00,0xF8,0x1F,0x08,0x11,0x08,0x11,0x09,0x11,0x0A,0x11,0x08,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD4 +{0x00,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xD5 +{0x00,0x00,0x08,0x10,0x08,0x10,0x08,0x10,0xFA,0x1F,0x09,0x10,0x08,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD6 +{0x00,0x00,0x08,0x10,0x08,0x10,0x0A,0x10,0xF9,0x1F,0x09,0x10,0x0A,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD7 +{0x00,0x00,0x08,0x10,0x09,0x10,0x08,0x10,0xF8,0x1F,0x08,0x10,0x09,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD8 +{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xD9 +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xDA +{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}, // 0xDB +{0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF}, // 0xDC +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xDD +{0x00,0x00,0x08,0x10,0x08,0x10,0x09,0x10,0xFA,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xDE +{0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00}, // 0xDF +{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x10,0x0A,0x10,0x09,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE0 +{0x00,0x00,0xFC,0x1F,0x02,0x00,0x02,0x00,0xE2,0x10,0x1C,0x11,0x00,0x12,0x00,0x0C,0x00,0x00,0x00,0x00}, // 0xE1 +{0x00,0x00,0xE0,0x07,0x10,0x08,0x0A,0x10,0x09,0x10,0x09,0x10,0x0A,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE2 +{0x00,0x00,0xE0,0x07,0x10,0x08,0x09,0x10,0x0A,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE3 +{0x00,0x00,0x80,0x07,0x44,0x08,0x22,0x10,0x24,0x10,0x24,0x10,0x42,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0xE4 +{0x00,0x00,0xE0,0x07,0x12,0x08,0x09,0x10,0x0B,0x10,0x0A,0x10,0x09,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE5 +{0x00,0x00,0xE0,0xFF,0x00,0x08,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00}, // 0xE6 +{0x00,0x00,0xFE,0xFF,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x60,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0xE7 +{0x00,0x00,0xF8,0x1F,0x20,0x04,0x20,0x04,0x20,0x04,0x20,0x04,0x20,0x02,0xC0,0x01,0x00,0x00,0x00,0x00}, // 0xE8 +{0x00,0x00,0xF8,0x07,0x00,0x18,0x00,0x10,0x02,0x10,0x01,0x10,0x00,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0xE9 +{0x00,0x00,0xF8,0x07,0x00,0x18,0x02,0x10,0x01,0x10,0x01,0x10,0x02,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0xEA +{0x00,0x00,0xF8,0x07,0x00,0x18,0x01,0x10,0x02,0x10,0x00,0x10,0x00,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0xEB +{0x20,0x80,0xC0,0x80,0x00,0x83,0x00,0x46,0x04,0x38,0x02,0x18,0x00,0x06,0x00,0x01,0xC0,0x00,0x20,0x00}, // 0xEC +{0x08,0x00,0x10,0x00,0x60,0x00,0x80,0x00,0x02,0x1F,0x81,0x00,0x40,0x00,0x20,0x00,0x10,0x00,0x08,0x00}, // 0xED +{0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00}, // 0xEE +{0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xEF +{0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00}, // 0xF0 +{0x00,0x00,0x80,0x10,0x80,0x10,0x80,0x10,0x80,0x10,0xE0,0x13,0x80,0x10,0x80,0x10,0x80,0x10,0x00,0x00}, // 0xF1 +{0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0}, // 0xF2 +{0x42,0x10,0x4A,0x08,0x5A,0x04,0x36,0x03,0x80,0x00,0x40,0x06,0x30,0x05,0x88,0x04,0x84,0x1F,0x02,0x04}, // 0xF3 +{0x00,0x00,0x1C,0x00,0x3E,0x00,0x7E,0x00,0xFE,0x7F,0x02,0x00,0x02,0x00,0xFE,0x7F,0x00,0x00,0x00,0x00}, // 0xF4 +{0x00,0x00,0x00,0x00,0xDC,0x61,0x32,0x43,0x22,0x42,0x62,0x46,0x42,0x4C,0x82,0x3B,0x00,0x00,0x00,0x00}, // 0xF5 +{0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x02,0x60,0x1A,0x60,0x1A,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x00}, // 0xF6 +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0xA0,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xF7 +{0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x00,0x12,0x00,0x12,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xF8 +{0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xF9 +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFA +{0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFB +{0x00,0x00,0x00,0x00,0x42,0x00,0x4A,0x00,0x4A,0x00,0x4A,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFC +{0x00,0x00,0x00,0x00,0x42,0x00,0x62,0x00,0x52,0x00,0x52,0x00,0x4C,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFD +{0x00,0x00,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0x00,0x00}, // 0xFE +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} // 0xFF +}; + diff --git a/tag_fw/lut.h b/tag_fw/lut.h new file mode 100644 index 00000000..7cef0509 --- /dev/null +++ b/tag_fw/lut.h @@ -0,0 +1,125 @@ +static const uint8_t __code lut154[] = { + // lut0 (KEEP) voltages + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // lut1 (W2B) voltages + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // lut2 (B2W) voltages + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // lut3 (unused) voltages + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // lut4 (vcom) voltages + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // group0 phase lengths and repeat count + 0x40, 0x0, 0x00, 0x00, 0x00, + // group1 not used + 0x00, 0x00, 0x00, 0x00, 0x00, + // group2 not used + 0x00, 0x00, 0x00, 0x00, 0x00, + // group3 phase lengths and repeat count + 0x00, 0x00, 0x00, 0x00, 0x00, + // group4 phase lengths and repeat count + 0x00, 0x00, 0x00, 0x00, 0x00, + // group5 phase lengths and repeat count + 0x00, 0x00, 0x00, 0x00, 0x00, + // group6 phase lengths and repeat count + 0x00, 0x00, 0x00, 0x00, 0x00, +}; + + +static const uint8_t __code lut29[] = { + // lut0 (KEEP) voltages + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // lut1 (W2B) voltages + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // lut2 (B2W) voltages + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // lut3 (unused) voltages + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // lut4 (vcom) voltages + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + // group0 phase lengths and repeat count +// 0x10, 0x02, 0x00, 0x00, 0x03 - 1, + 0x05, 0x02, 0x00, 0x00, 0x00, + +// 0x40, 0x00, 0x00, 0x00, 0x00, + + // group1 not used + 0x00, 0x00, 0x00, 0x00, 0x00, + // group2 not used + 0x00, 0x00, 0x00, 0x00, 0x00, + // group3 phase lengths and repeat count + 0x00, 0x00, 0x00, 0x00, 0x00, + // group4 phase lengths and repeat count + 0x00, 0x00, 0x00, 0x00, 0x00, + // group5 phase lengths and repeat count + 0x00, 0x00, 0x00, 0x00, 0x00, + // group6 phase lengths and repeat count + 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t __code lutSHA[] = { + // Voltages and other settings? Timing? + 0xA0, 0x90, 0x50, 0x0, 0x0, 0x0, 0x0, + 0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0, + 0xA0, 0x90, 0x50, 0x0, 0x0, 0x0, 0x0, + 0x50, 0x90, 0xA0, 0x0, 0x0, 0x0, 0x0, + 0x00, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0, + + // Update program + // + // Top three lines are the main program (bottom 4 have unknown function) + // Line 1: Negative image + // Line 2: White/Black flashing + // Line 3: Positive image + // + // Line construction + // First two bytes denote Intensity (range 0x00 to 0x0F) + // Second two bytes denote lenght of each 'pulse' (range 0x00 to 0xFF) + // Last byte denotes number of repeats (0 = line runs 1 time, range 0x00 to 0xFF) + // If you don't want a line to do anything, set all bytes to 0x0. + // This way you can make a quick update cycle between two screens. + // Maybe not as pretty/crisp but nice and fast is also awesome! + + // Negative image + // first two bytes negative image, length white pulse (0-FF), length black pulse (0-FF), last byte repeats + + 0x0, 0x0, 0x0, 0x0, 0x0, + + //0xF, 0xF, 0x0, 0x0, 0x0, + + // White or black flash + // white flash intensity, black flash intensity, length white pulse (0-FF), length black pulse (0-FF), repeats + + //0x0, 0x0, 0x0, 0x0, 0x00, + 0xF, 0xF, 0x1, 0x1, 0x00, + //0xF, 0xF, 0x0, 0x0, 0x02, + + + // Positive image + // first byte or second byte positive image (don't know why you need both), rest same as above + + 0xF, 0xF, 0x0, 0x0, 0x0, + + // Unknown what lines below actually do. + // They seem to be programs to, but have no visible effect on dislay. + 0x0F, 0x0F, 0x0, 0x0, 0x0, + 0x0F, 0x0F, 0x0, 0x0, 0x0, + 0x0F, 0x0F, 0x0, 0x0, 0x0, + 0x0F, 0x0F, 0x0, 0x0, 0x0, +}; + +static const uint8_t __code lutorig[] = { + 0x00, 0x66, 0x21, 0x45, 0x40, 0x00, 0x00, + 0x15, 0x66, 0x21, 0xA8, 0x20, 0xA0, 0x00, + 0xA0, 0x66, 0x21, 0x85, 0x2B, 0x2F, 0x00, + 0xA0, 0x66, 0x21, 0x85, 0x2B, 0x2F, 0x00, + 0x00, 0x00, 0x12, 0x48, 0x00, 0x00, 0x00, + //0x04, 0x49, 0x2F, 0x2A, 0x00, + 0x0, 0x0, 0x0, 0x0, 0x0, + 0x02, 0x04, 0x01, 0x03, 0x00, // was 11 repeat + 0x01, 0x14, 0x01, 0x14, 0x00, // was 3 repeat + 0x02, 0x0A, 0x03, 0x0A, 0x00, // was 2 repeat + 0x06, 0x04, 0x04, 0x20, 0x00, // was 3 rpeat + 0x04, 0x04, 0x02, 0x26, 0x00, // was 3 repeat + 0x00, 0x00, 0x00, 0x00, 0x0, +}; \ No newline at end of file diff --git a/tag_fw/main.c b/tag_fw/main.c index 7c8ecb45..91f8a96b 100644 --- a/tag_fw/main.c +++ b/tag_fw/main.c @@ -8,7 +8,6 @@ #include "adc.h" #include "asmUtil.h" #include "board.h" -#include "chars.h" #include "comms.h" #include "cpu.h" #include "drawing.h" @@ -22,16 +21,6 @@ #include "wdt.h" #include "syncedproto.h" - -uint16_t __xdata battery_voltage = 0; -int8_t __xdata mCurTemperature; - - -void getVolt(void) { - if (battery_voltage == 0) - battery_voltage = adcSampleBattery(); -} - void main(void){ mainProtocolLoop(); } diff --git a/tag_fw/soc/zbs243/spi.c b/tag_fw/soc/zbs243/spi.c index c4088d4b..e2a8f7f6 100644 --- a/tag_fw/soc/zbs243/spi.c +++ b/tag_fw/soc/zbs243/spi.c @@ -29,4 +29,14 @@ uint8_t spiByte(uint8_t val) CFGPAGE = bcp; return val; +} + +void spiTXByte(uint8_t val) +{ + uint8_t bcp = CFGPAGE; + CFGPAGE = 4; + SPITX = val; + SPICFG = 0xa0; //spi at 4mhz, mode 0 + while(SPICFG & 0x20); + CFGPAGE = bcp; } \ No newline at end of file diff --git a/tag_fw/soc/zbs243/spi.h b/tag_fw/soc/zbs243/spi.h index b869e4a9..b2aeb4aa 100644 --- a/tag_fw/soc/zbs243/spi.h +++ b/tag_fw/soc/zbs243/spi.h @@ -11,6 +11,9 @@ void spiInit(void); #pragma callee_saves spiByte uint8_t spiByte(uint8_t val); +#pragma callee_saves spiTXByte +void spiTXByte(uint8_t val); + #endif diff --git a/tag_fw/soc/zbs243/uart.c b/tag_fw/soc/zbs243/uart.c index 3bf25bc8..c41514c5 100644 --- a/tag_fw/soc/zbs243/uart.c +++ b/tag_fw/soc/zbs243/uart.c @@ -1,24 +1,23 @@ #include "uart.h" + #include "cpu.h" +void uartInit(void) { + // clock it up + CLKEN |= 0x20; - -void uartInit(void) -{ - //clock it up - CLKEN |= 0x20; - - //configure - UARTBRGH = 0x00; //config for 115200 - UARTBRGL = 0x8A; - UARTSTA = 0x12; //also set the "empty" bit else we wait forever for it to go up + // configure + UARTBRGH = 0x00; // config for 115200 + UARTBRGL = 0x8A; + UARTSTA = 0x12; // also set the "empty" bit else we wait forever for it to go up } -void uartTx(uint8_t val) -{ - while (!(UARTSTA & (1 << 1))); - UARTSTA &=~ (1 << 1); - UARTBUF = val; +extern void writeCharEPD(uint8_t c); + +void uartTx(uint8_t val) { + writeCharEPD(val); + while (!(UARTSTA & (1 << 1))) + ; + UARTSTA &= ~(1 << 1); + UARTBUF = val; } - - diff --git a/tag_fw/syncedproto.c b/tag_fw/syncedproto.c index 56e23fd1..e59df81c 100644 --- a/tag_fw/syncedproto.c +++ b/tag_fw/syncedproto.c @@ -10,7 +10,6 @@ #include "adc.h" #include "asmUtil.h" #include "board.h" -#include "chars.h" #include "comms.h" #include "cpu.h" #include "drawing.h" @@ -19,7 +18,8 @@ #include "printf.h" #include "proto.h" #include "radio.h" -#include "screen.h" + +#include "epd.h" #include "sleep.h" #include "timer.h" #include "wdt.h" @@ -133,6 +133,7 @@ uint16_t __xdata dataRemaining = 0; bool __xdata curXferComplete = false; bool __xdata requestPartialBlock = false; +//uint8_t __xdata *tempBuffer = blockXferBuffer; uint8_t __xdata curImgSlot = 0; uint32_t __xdata curHighSlotId = 0; uint8_t __xdata nextImgSlot = 0; @@ -151,17 +152,17 @@ uint8_t __xdata mSelfMac[8] = {0}; uint8_t __xdata seq = 0; // power saving algorithm -#define INTERVAL_BASE 40 // interval (in seconds) (when 1 packet is sent/received) for target current (7.2µA) -#define INTERVAL_AT_MAX_ATTEMPTS 600 // interval (in seconds) (at max attempts) for target average current -#define INTERVAL_NO_SIGNAL 1800 // interval (in seconds) when no answer for POWER_SAVING_SMOOTHING attempts, - // (INTERVAL_AT_MAX_ATTEMPTS * POWER_SAVING_SMOOTHING) seconds -#define DATA_REQ_RX_WINDOW_SIZE 5UL // How many milliseconds we should wait for a packet during the data_request. - // If the AP holds a long list of data for tags, it may need a little more time to lookup the mac address -#define DATA_REQ_MAX_ATTEMPTS 14 // How many attempts (at most) we should do to get something back from the AP -#define POWER_SAVING_SMOOTHING 8 // How many samples we should use to smooth the data request interval -#define MINIMUM_INTERVAL 45 // IMPORTANT: Minimum interval for check-in; this determines overal battery life! +#define INTERVAL_BASE 40 // interval (in seconds) (when 1 packet is sent/received) for target current (7.2µA) +#define INTERVAL_AT_MAX_ATTEMPTS 600 // interval (in seconds) (at max attempts) for target average current +#define INTERVAL_NO_SIGNAL 1800 // interval (in seconds) when no answer for POWER_SAVING_SMOOTHING attempts, + // (INTERVAL_AT_MAX_ATTEMPTS * POWER_SAVING_SMOOTHING) seconds +#define DATA_REQ_RX_WINDOW_SIZE 5UL // How many milliseconds we should wait for a packet during the data_request. + // If the AP holds a long list of data for tags, it may need a little more time to lookup the mac address +#define DATA_REQ_MAX_ATTEMPTS 14 // How many attempts (at most) we should do to get something back from the AP +#define POWER_SAVING_SMOOTHING 8 // How many samples we should use to smooth the data request interval +#define MINIMUM_INTERVAL 45 // IMPORTANT: Minimum interval for check-in; this determines overal battery life! -#define HAS_BUTTON // uncomment to enable reading a push button (connect between 'TEST' en 'GND' on the tag, along with a 100nF capacitor in parallel). +#define HAS_BUTTON // uncomment to enable reading a push button (connect between 'TEST' en 'GND' on the tag, along with a 100nF capacitor in parallel). uint16_t __xdata dataReqAttemptArr[POWER_SAVING_SMOOTHING] = {0}; // Holds the amount of attempts required per data_req/check-in uint8_t __xdata dataReqAttemptArrayIndex = 0; @@ -248,30 +249,30 @@ void initAfterWake() { timerInit(); // partialInit(); boardInit(); - screenSleep(); + epdEnterSleep(); irqsOn(); boardInitStage2(); initRadio(); } void doSleep(uint32_t __xdata t) { - if(t>1000)pr("s=%lu\n ", t / 1000); + if (t > 1000) pr("s=%lu\n ", t / 1000); powerPortsDownForSleep(); #ifdef HAS_BUTTON - //Button setup on TEST pin 1.0 (input pullup) - P1FUNC &=~ (1 << 0); - P1DIR |= (1 << 0); - P1PULL |= (1 << 0); - P1LVLSEL |= (1 << 0); - P1INTEN = (1 << 0); - P1CHSTA &=~ (1 << 0); + // Button setup on TEST pin 1.0 (input pullup) + P1FUNC &= ~(1 << 0); + P1DIR |= (1 << 0); + P1PULL |= (1 << 0); + P1LVLSEL |= (1 << 0); + P1INTEN = (1 << 0); + P1CHSTA &= ~(1 << 0); #endif // sleepy sleepForMsec(t); #ifdef HAS_BUTTON - P1INTEN = 0; + P1INTEN = 0; #endif initAfterWake(); @@ -315,11 +316,11 @@ void sendAvailDataReq() { txframe->srcPan = 0x4447; // TODO: send some meaningful data availreq->softVer = 1; - if (P1CHSTA && (1 << 0)) { - availreq->buttonState = 1; - pr("button pressed\n"); - P1CHSTA &=~ (1 << 0); - } + if (P1CHSTA && (1 << 0)) { + availreq->buttonState = 1; + pr("button pressed\n"); + P1CHSTA &= ~(1 << 0); + } addCRC(availreq, sizeof(struct AvailDataReq)); commsTxNoCpy(outBuffer); } @@ -504,45 +505,6 @@ bool validateBlockData() { return bd->checksum == t; } -const uint8_t epd_bitmap_ant[] = { - 0xff, 0xbf, 0xfd, 0xdf, 0xfe, 0xdf, 0xee, 0xdf, 0xee, 0xdf, 0xed, 0xdf, 0xd7, 0xbf, 0xd7, 0xff, - 0xd7, 0xff, 0xbb, 0xff, 0xbb, 0xbb, 0xbb, 0xd7, 0xbb, 0xef, 0x7d, 0xd7, 0x7d, 0xbb, 0x01, 0xff}; -void copyImage() { - uint8_t x_begin = 17; - uint16_t y_begin = 0; - uint8_t y_size = 16; - const uint8_t *p = epd_bitmap_ant; - pr("begin copy image to epd buffer"); - for (uint16_t y = 0; y < y_size; y++) { - // moveToXY(x_begin, y_begin + y, false); - pr("d=%02X,", *p); - // screenByteRawTx(*p); - p++; - // screenByteRawTx(*p); - p++; - } -} -void drawPartial() { - screenSleep(); - screenTxStart(true); - pr("sending bytes\n"); - for (uint8_t iteration = 0; iteration < SCREEN_DATA_PASSES; iteration++) { - for (uint16_t y = 0; y < SCREEN_HEIGHT; y++) { - for (uint8_t x = 0; x < SCREEN_WIDTH / 8; x++) { - if (iteration == 0) { - // screenByteRawTx(0xFF); - } else { - // screenByteRawTx(0x00); - } - } - } - screenEndPass(); - pr("pass complete\n"); - } - copyImage(); - screenTxEnd(); -} - // EEprom related stuff uint32_t getAddressForSlot(uint8_t s) { return EEPROM_IMG_START + (EEPROM_IMG_EACH * s); @@ -564,7 +526,7 @@ uint8_t findSlot(uint8_t *__xdata ver) { // 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 uint32_t __xdata markerValid = EEPROM_IMG_VALID; for (uint8_t __xdata c = 0; c < imgSlots; c++) { - struct EepromImageHeader __xdata *eih = (struct EepromImageHeader __xdata *)mScreenRow; + struct EepromImageHeader __xdata *eih = (struct EepromImageHeader __xdata *)blockXferBuffer; eepromRead(getAddressForSlot(c), eih, sizeof(struct EepromImageHeader)); if (xMemEqual4(&eih->validMarker, &markerValid)) { if (xMemEqual(&eih->version, (void *)ver, 8)) { @@ -600,7 +562,7 @@ uint32_t getHighSlotId() { uint32_t temp = 0; uint32_t __xdata markerValid = EEPROM_IMG_VALID; for (uint8_t __xdata c = 0; c < imgSlots; c++) { - struct EepromImageHeader __xdata *eih = (struct EepromImageHeader __xdata *)mScreenRow; + struct EepromImageHeader __xdata *eih = (struct EepromImageHeader __xdata *)blockXferBuffer; eepromRead(getAddressForSlot(c), eih, sizeof(struct EepromImageHeader)); if (xMemEqual4(&eih->validMarker, &markerValid)) { if (temp < eih->id) { @@ -843,7 +805,7 @@ bool doDataDownload(struct AvailDataInfo *__xdata avail) { case DATATYPE_IMG: case DATATYPE_IMGRAW:; // transfer complete. Save data info and mark data in image slot as 'valid' - struct EepromImageHeader __xdata *eih = (struct EepromImageHeader __xdata *)mScreenRow; + struct EepromImageHeader __xdata *eih = (struct EepromImageHeader __xdata *)blockXferBuffer; xMemCopy8(&eih->version, &curDataInfo.dataVer); eih->size = curDataInfo.dataSize; eih->validMarker = EEPROM_IMG_VALID; @@ -902,11 +864,10 @@ void mainProtocolLoop(void) { boardInitStage2(); // i2ctest(); - pr("BOOTED> (new version!)\n\n"); + pr("BOOTED> (new epd driver!!!)\n\n"); if (!eepromInit()) { pr("failed to init eeprom\n"); - drawFullscreenMsg((const __xdata char *)"eeprom failed"); while (1) ; } else { @@ -919,15 +880,12 @@ void mainProtocolLoop(void) { dataReqAttemptArr[c] = INTERVAL_BASE; } - screenSleep(); + epdEnterSleep(); eepromDeepPowerDown(); initRadio(); - - P1CHSTA &=~ (1 << 0); - - // drawPartial(); - // i2ctest(); - // doSleep(10000); + + P1CHSTA &= ~(1 << 0); + while (1) { radioRxEnable(true, true); From 8929afbff26b6480af463e3893a9b6f3c3f6194c Mon Sep 17 00:00:00 2001 From: Jelmer Date: Sat, 28 Jan 2023 23:17:07 +0100 Subject: [PATCH 06/18] first attempt at a UI --- tag_fw/Makefile | 2 +- tag_fw/adc.h | 16 ------------ tag_fw/board/boardZBS29common.c | 3 ++- tag_fw/board/zbs154v033/screen.h | 23 ----------------- tag_fw/drawing.c | 4 --- tag_fw/eeprom.c | 2 +- tag_fw/epd.c | 11 +++++--- tag_fw/epd.h | 3 +++ tag_fw/fw154.bin | Bin 31761 -> 0 bytes tag_fw/fw29.bin | Bin 31545 -> 0 bytes tag_fw/fw42.bin | Bin 31592 -> 0 bytes tag_fw/main.c | 3 +-- tag_fw/soc/zbs243/temperature.c | 1 - tag_fw/syncedproto.c | 15 ++++++----- tag_fw/userinterface.c | 43 +++++++++++++++++++++++++++++++ tag_fw/userinterface.h | 9 +++++++ 16 files changed, 76 insertions(+), 59 deletions(-) delete mode 100644 tag_fw/adc.h delete mode 100644 tag_fw/fw154.bin delete mode 100644 tag_fw/fw29.bin delete mode 100644 tag_fw/fw42.bin create mode 100644 tag_fw/userinterface.c create mode 100644 tag_fw/userinterface.h diff --git a/tag_fw/Makefile b/tag_fw/Makefile index a2bd6bab..5dec4173 100644 --- a/tag_fw/Makefile +++ b/tag_fw/Makefile @@ -4,7 +4,7 @@ BUILD ?= zbs29v033 #file containing main() must be first! SOURCES += main.c eeprom.c drawing.c SOURCES += comms.c -SOURCES += syncedproto.c epd.c +SOURCES += syncedproto.c epd.c userinterface.c all: #make sure it is the first target diff --git a/tag_fw/adc.h b/tag_fw/adc.h deleted file mode 100644 index c495bc07..00000000 --- a/tag_fw/adc.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef _ADC_H_ -#define _ADC_H_ - -#include - -extern uint16_t __xdata mAdcSlope; -extern uint16_t __xdata mAdcIntercept; - - -uint16_t adcSampleBattery(void); //in mV -int8_t adcSampleTemperature(void); //in degrees C - - - - -#endif diff --git a/tag_fw/board/boardZBS29common.c b/tag_fw/board/boardZBS29common.c index 0c9f7e8a..558d961e 100644 --- a/tag_fw/board/boardZBS29common.c +++ b/tag_fw/board/boardZBS29common.c @@ -7,7 +7,6 @@ #include "spi.h" #include "cpu.h" #include "wdt.h" -#include "adc.h" #include "i2c.h" //extern uint8_t __xdata* tempBuffer; @@ -152,6 +151,8 @@ void selfUpdate(void) uint8_t i, len = updaterInfo >> 16; uint8_t __xdata *dst = tempBuffer; + pr("updater len = %d\n", len); + for (i = len; i ; i--) *dst++ = *src++; diff --git a/tag_fw/board/zbs154v033/screen.h b/tag_fw/board/zbs154v033/screen.h index 479da353..2dd805b3 100644 --- a/tag_fw/board/zbs154v033/screen.h +++ b/tag_fw/board/zbs154v033/screen.h @@ -4,12 +4,6 @@ #include #include - -//i hate globals, but for 8051 this makes life a lot easier, sorry :( -extern uint8_t __xdata mScreenVcom; -extern int8_t __xdata mCurTemperature; - - #define SCREEN_WIDTH 152 #define SCREEN_HEIGHT 152 @@ -27,23 +21,6 @@ extern int8_t __xdata mCurTemperature; #define SCREEN_DATA_PASSES 2 -void screenShutdown(void); - -void screenTest(void); - -__bit screenTxStart(__bit forPartial); - -void screenEndPass(void); //at end of each pass -void moveToXY(uint8_t x, uint16_t y, bool red); - -#pragma callee_saves screenByteTx -void screenByteTx(uint8_t byte); -void screenByteRawTx(uint8_t byte); -void screenTxEnd(void); - -void screenSleep(void); - -extern uint8_t __xdata mScreenRow[]; //320 bytes used as temp by many on cc where memory is tight #endif diff --git a/tag_fw/drawing.c b/tag_fw/drawing.c index d7277562..1c91b4bc 100644 --- a/tag_fw/drawing.c +++ b/tag_fw/drawing.c @@ -1,10 +1,6 @@ #include -// #include "datamatrix.h" -#include "adc.h" #include "asmUtil.h" -// #include "barcode.h" #include "board.h" -// #include "chars.h" #include "cpu.h" #include "drawing.h" #include "eeprom.h" diff --git a/tag_fw/eeprom.c b/tag_fw/eeprom.c index 30844c41..ab01bbc7 100644 --- a/tag_fw/eeprom.c +++ b/tag_fw/eeprom.c @@ -9,7 +9,7 @@ static uint32_t __xdata mEepromSize; static uint8_t __xdata mOpcodeErz4K = 0, mOpcodeErz32K = 0, mOpcodeErz64K = 0; //extern uint8_t __xdata* tempBuffer; -uint8_t __xdata tempBufferE[320]; +uint8_t __xdata tempBufferE[320] = {0}; uint32_t eepromGetSize(void) { diff --git a/tag_fw/epd.c b/tag_fw/epd.c index dafd1491..fa7cee44 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -3,7 +3,6 @@ #include #include -#include "adc.h" #include "asmUtil.h" #include "board.h" #include "cpu.h" @@ -193,6 +192,7 @@ void epdEnterSleep() { } void epdSetup() { epdReset(); + currentLUT = 0; shortCommand1(CMD_ANALOG_BLK_CTRL, 0x54); shortCommand1(CMD_DIGITAL_BLK_CTRL, 0x3B); shortCommand2(CMD_UNKNOWN_1, 0x04, 0x63); @@ -257,7 +257,7 @@ uint16_t epdGetBattery(void) { void selectLUT(uint8_t lut) { if (lut == currentLUT) return; - //lut = 1; + // lut = 1; switch (lut) { case 0: shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); // mode 1? @@ -324,6 +324,11 @@ void draw() { shortCommand(0x20); epdBusyWait(TIMER_TICKS_PER_SECOND * 120); } +void drawNoWait() { + shortCommand1(0x22, 0xCF); + // shortCommand1(0x22, SCREEN_CMD_REFRESH); + shortCommand(0x20); +} void drawLineHorizontal(bool red, uint16_t y, uint8_t width) { setWindowX(0, SCREEN_WIDTH); setWindowY(y, y + width); @@ -507,7 +512,7 @@ void epdPrintEnd() { } extern uint8_t* __xdata tempBuffer; -extern void dump(uint8_t *__xdata a, uint16_t __xdata l); +extern void dump(uint8_t* __xdata a, uint16_t __xdata l); void loadFixedTempLUT() { shortCommand1(0x18, 0x48); diff --git a/tag_fw/epd.h b/tag_fw/epd.h index 88f6a2ff..82d4b1fc 100644 --- a/tag_fw/epd.h +++ b/tag_fw/epd.h @@ -37,11 +37,14 @@ void fillWindowWithPattern(bool color); void clearWindow(bool color); void clearScreen(); void draw(); +void drawNoWait(); void drawLineHorizontal(bool red, uint16_t y, uint8_t width); void beginFullscreenImage(); void beginWriteFramebuffer(bool color); void endWriteFramebuffer(); +void selectLUT(uint8_t lut); + void ByteDecode(uint8_t byte); void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool red); diff --git a/tag_fw/fw154.bin b/tag_fw/fw154.bin deleted file mode 100644 index 4aaa15935bf88cf1de69cffc72123df2b388f828..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31761 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 VxXgje9JtJZ%N)4Of&V=Y{7*-~j-3Di diff --git a/tag_fw/fw42.bin b/tag_fw/fw42.bin deleted file mode 100644 index dcf0d475ce4e8d908f64ce2f3642784ff42b7f25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31592 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 #include #include - -#include "adc.h" #include "asmUtil.h" #include "board.h" #include "comms.h" @@ -21,6 +19,7 @@ #include "wdt.h" #include "syncedproto.h" + void main(void){ mainProtocolLoop(); } diff --git a/tag_fw/soc/zbs243/temperature.c b/tag_fw/soc/zbs243/temperature.c index f51b492f..11b20544 100644 --- a/tag_fw/soc/zbs243/temperature.c +++ b/tag_fw/soc/zbs243/temperature.c @@ -1,7 +1,6 @@ #include "asmUtil.h" #include "flash.h" #include "timer.h" -#include "adc.h" #include "cpu.h" static volatile uint8_t __xdata mTempRet[4]; diff --git a/tag_fw/syncedproto.c b/tag_fw/syncedproto.c index e59df81c..5be1e4a2 100644 --- a/tag_fw/syncedproto.c +++ b/tag_fw/syncedproto.c @@ -6,8 +6,6 @@ #include #include #include - -#include "adc.h" #include "asmUtil.h" #include "board.h" #include "comms.h" @@ -18,8 +16,8 @@ #include "printf.h" #include "proto.h" #include "radio.h" - #include "epd.h" +#include "userinterface.h" #include "sleep.h" #include "timer.h" #include "wdt.h" @@ -104,7 +102,7 @@ struct burstMacData { #define BLOCK_PART_DATA_SIZE 99 #define BLOCK_MAX_PARTS 42 -#define BLOCK_DATA_SIZE 4096 +#define BLOCK_DATA_SIZE 4096UL #define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData) #define BLOCK_REQ_PARTS_BYTES 6 @@ -820,6 +818,7 @@ bool doDataDownload(struct AvailDataInfo *__xdata avail) { break; case DATATYPE_UPDATE: pr("firmware download complete, doing update.\n"); + showApplyUpdate(); curXferComplete = true; sendXferComplete(); killRadio(); @@ -861,10 +860,9 @@ void mainProtocolLoop(void) { } irqsOn(); - boardInitStage2(); - // i2ctest(); + boardInitStage2(); - pr("BOOTED> (new epd driver!!!)\n\n"); + pr("BOOTED> (UI 0.03-1)\n\n"); if (!eepromInit()) { pr("failed to init eeprom\n"); @@ -880,6 +878,9 @@ void mainProtocolLoop(void) { dataReqAttemptArr[c] = INTERVAL_BASE; } + // show the splashscreen + showSplashScreen(); + epdEnterSleep(); eepromDeepPowerDown(); initRadio(); diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c new file mode 100644 index 00000000..4d086a63 --- /dev/null +++ b/tag_fw/userinterface.c @@ -0,0 +1,43 @@ +#include "userinterface.h" + +#include +#include + +#include "asmUtil.h" +#include "board.h" +#include "cpu.h" +#include "epd.h" +#include "font.h" +#include "lut.h" +#include "printf.h" +#include "screen.h" +#include "sleep.h" +#include "spi.h" +#include "timer.h" + +void showSplashScreen() { + epdSetup(); +#if (SCREEN_WIDTH == 152) + selectLUT(1); + clearScreen(); + setColorMode(EPD_MODE_IGNORE, EPD_MODE_INVERT); + drawLineHorizontal(EPD_COLOR_BLACK, 33, 1); + epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + pr("Booting!"); + epdPrintEnd(); + draw(); + timerDelay(1333000); +#endif +} + + +void showApplyUpdate(){ + epdSetup(); + selectLUT(1); + clearScreen(); + setColorMode(EPD_MODE_IGNORE, EPD_MODE_NORMAL); + epdPrintBegin(8, 60, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + pr("Updating!"); + epdPrintEnd(); + drawNoWait(); +} \ No newline at end of file diff --git a/tag_fw/userinterface.h b/tag_fw/userinterface.h new file mode 100644 index 00000000..548affd7 --- /dev/null +++ b/tag_fw/userinterface.h @@ -0,0 +1,9 @@ + +#ifndef _UI_H_ +#define _UI_H_ + +void showSplashScreen(); +void showApplyUpdate(); + + +#endif \ No newline at end of file From 0b561d247334562d8f8dd3e734e84ee7bb8e2a1f Mon Sep 17 00:00:00 2001 From: Jelmer Date: Sun, 29 Jan 2023 01:20:06 +0100 Subject: [PATCH 07/18] Fixed stuff.Haven't checked power consumption! --- tag_fw/board/boardZBS29common.c | 2 -- tag_fw/epd.c | 23 +++++++----------- tag_fw/epd.h | 2 ++ tag_fw/lut.h | 28 +++++++++++++++++++++ tag_fw/userinterface.c | 43 ++++++++++++++++++++++++++++++--- 5 files changed, 78 insertions(+), 20 deletions(-) diff --git a/tag_fw/board/boardZBS29common.c b/tag_fw/board/boardZBS29common.c index 558d961e..7a662eed 100644 --- a/tag_fw/board/boardZBS29common.c +++ b/tag_fw/board/boardZBS29common.c @@ -151,8 +151,6 @@ void selfUpdate(void) uint8_t i, len = updaterInfo >> 16; uint8_t __xdata *dst = tempBuffer; - pr("updater len = %d\n", len); - for (i = len; i ; i--) *dst++ = *src++; diff --git a/tag_fw/epd.c b/tag_fw/epd.c index fa7cee44..06bb23ef 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -71,6 +71,7 @@ static bool __xdata directionY = true; // print direction, X or Y (true) static uint8_t __xdata rbuffer[32]; // used to rotate bits around static uint16_t __xdata fontCurXpos = 0; // current X value where working with static bool __xdata isInited = false; +struct waveform __xdata waveform; #pragma callee_saves epdBusySleep #pragma callee_saves epdBusyWait @@ -511,8 +512,6 @@ void epdPrintEnd() { epdPr = false; } -extern uint8_t* __xdata tempBuffer; -extern void dump(uint8_t* __xdata a, uint16_t __xdata l); void loadFixedTempLUT() { shortCommand1(0x18, 0x48); @@ -521,24 +520,20 @@ void loadFixedTempLUT() { shortCommand(CMD_ACTIVATION); epdBusyWait(1333000UL); } -static uint8_t readLut() { - uint8_t sta = 0; - +static void readLut() { commandReadBegin(0x33); - uint16_t checksum = 0; uint16_t ident = 0; uint16_t shortl = 0; for (uint16_t c = 0; c < 76; c++) { - sta = epdReadByte(); - checksum += sta; - if (c < 70) ident += sta; - if (c < 14) shortl += sta; - tempBuffer[c] = sta; + ((uint8_t*)&waveform)[c] = epdReadByte(); } - pr("ident=%04X checksum=%04X shortl=%04X\n", ident, checksum, shortl); commandReadEnd(); - dump(tempBuffer, 96); +} - return sta; +extern void dump(uint8_t* __xdata a, uint16_t __xdata l); // remove me when done + +void lutTest() { + readLut(); + dump((uint8_t*)&waveform, 96); } \ No newline at end of file diff --git a/tag_fw/epd.h b/tag_fw/epd.h index 82d4b1fc..e1251b74 100644 --- a/tag_fw/epd.h +++ b/tag_fw/epd.h @@ -54,4 +54,6 @@ void epdPrintEnd(); void beginFullscreenImage(); void beginWriteFramebuffer(bool color); +void lutTest(); + #endif \ No newline at end of file diff --git a/tag_fw/lut.h b/tag_fw/lut.h index 7cef0509..7e0f8a34 100644 --- a/tag_fw/lut.h +++ b/tag_fw/lut.h @@ -1,3 +1,30 @@ +#define __packed + +struct vgroup { + uint8_t A : 2; + uint8_t B : 2; + uint8_t C : 2; + uint8_t D : 2; +} __packed; + +struct lut { + struct vgroup group[7]; +} __packed; + +struct group { + uint8_t phaselength[4]; + uint8_t repeat; +} __packed; + +struct waveform { + struct lut elut[5]; + struct group egroup[7]; + uint8_t gatelevel; + uint8_t sourcelevel[3]; + uint8_t dummyline; + uint8_t gatewidth; +} __packed; + static const uint8_t __code lut154[] = { // lut0 (KEEP) voltages 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -108,6 +135,7 @@ static const uint8_t __code lutSHA[] = { 0x0F, 0x0F, 0x0, 0x0, 0x0, }; + static const uint8_t __code lutorig[] = { 0x00, 0x66, 0x21, 0x45, 0x40, 0x00, 0x00, 0x15, 0x66, 0x21, 0xA8, 0x20, 0xA0, 0x00, diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index 4d086a63..57f4b89a 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -15,12 +15,15 @@ #include "spi.h" #include "timer.h" +extern uint8_t mSelfMac[]; + void showSplashScreen() { epdSetup(); -#if (SCREEN_WIDTH == 152) + lutTest(); +#if (SCREEN_WIDTH == 152) // 1.54" selectLUT(1); clearScreen(); - setColorMode(EPD_MODE_IGNORE, EPD_MODE_INVERT); + setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); drawLineHorizontal(EPD_COLOR_BLACK, 33, 1); epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); pr("Booting!"); @@ -28,10 +31,42 @@ void showSplashScreen() { draw(); timerDelay(1333000); #endif +#if (SCREEN_WIDTH == 128) // 2.9" + selectLUT(1); + clearScreen(); + setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); + epdPrintBegin(128, 0, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + pr("Booting!"); + epdPrintEnd(); + epdPrintBegin(16, 10, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); + pr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); + pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); + pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); + pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); + epdPrintEnd(); + draw(); + timerDelay(1333000); +#endif +#if (SCREEN_WIDTH == 400) // 2.9" + selectLUT(1); + clearScreen(); + setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); + epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + pr("Booting!"); + epdPrintEnd(); + epdPrintBegin(16, 284, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_RED); + pr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); + pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); + pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); + pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); + epdPrintEnd(); + draw(); + timerDelay(1333000); +#endif + } - -void showApplyUpdate(){ +void showApplyUpdate() { epdSetup(); selectLUT(1); clearScreen(); From d263e3fd0685e3ee6c5f0ff1cdd4a01db2464beb Mon Sep 17 00:00:00 2001 From: Jelmer Date: Sun, 29 Jan 2023 14:11:58 +0100 Subject: [PATCH 08/18] wip --- tag_fw/epd.c | 17 ++- tag_fw/font.h | 264 ++++++++++++++++++++++++++++++++++++++++- tag_fw/userinterface.c | 34 +++++- 3 files changed, 300 insertions(+), 15 deletions(-) diff --git a/tag_fw/epd.c b/tag_fw/epd.c index 06bb23ef..ad01fbac 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -364,23 +364,23 @@ static void pushXFontBytesToEPD(uint8_t byte1, uint8_t byte2) { if (epdCharSize == 1) { uint8_t offset = 7 - (fontCurXpos % 8); for (uint8_t c = 0; c < 8; c++) { - if (byte1 & (1 << c)) rbuffer[c] |= (1 << offset); + if (byte2 & (1 << (7-c))) rbuffer[c] |= (1 << offset); } for (uint8_t c = 0; c < 8; c++) { - if (byte2 & (1 << c)) rbuffer[8 + c] |= (1 << offset); + if (byte1 & (1 << (7-c))) rbuffer[8 + c] |= (1 << offset); } fontCurXpos++; } else { uint8_t offset = 6 - (fontCurXpos % 8); // double font size for (uint8_t c = 0; c < 8; c++) { - if (byte1 & (1 << c)) { + if (byte2 & (1 << (7-c))) { rbuffer[c * 2] |= (3 << offset); rbuffer[(c * 2) + 1] |= (3 << offset); } } for (uint8_t c = 0; c < 8; c++) { - if (byte2 & (1 << c)) { + if (byte1 & (1 << (7-c))) { rbuffer[(c * 2) + 16] |= (3 << offset); rbuffer[(c * 2) + 17] |= (3 << offset); } @@ -389,7 +389,6 @@ static void pushXFontBytesToEPD(uint8_t byte1, uint8_t byte2) { } if (fontCurXpos % 8 == 0) { // next byte, flush current byte to EPD - for (uint8_t i = 0; i < (16 * epdCharSize); i++) { epdSend(rbuffer[i]); } @@ -470,16 +469,16 @@ void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool c directionY = direction; epdCharSize = 1 + fontsize; if (directionY) { + y = SCREEN_HEIGHT - y; if (epdCharSize == 2) { setWindowX(x - 32, x); - setPosXY(x - 32, y); - + setPosXY(x - 32, SCREEN_HEIGHT-y); } else { setWindowX(x - 16, x); - setPosXY(x - 16, y); + setPosXY(x - 16, SCREEN_HEIGHT-y); } setWindowY(y, SCREEN_HEIGHT); - shortCommand1(CMD_DATA_ENTRY_MODE, 3); + shortCommand1(CMD_DATA_ENTRY_MODE, 1); // was 3 } else { if (epdCharSize == 2) { x /= 2; diff --git a/tag_fw/font.h b/tag_fw/font.h index 55e1f604..e3d9906f 100644 --- a/tag_fw/font.h +++ b/tag_fw/font.h @@ -1,4 +1,266 @@ -static const uint8_t __code font[256][20]={ + +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 +{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 +{0x80,0x00,0x98,0x04,0xE0,0x05,0x80,0x1E,0x98,0x64,0xE0,0x05,0x80,0x1E,0x80,0x64,0x80,0x04,0x00,0x00}, // 0x23 +{0x00,0x00,0x00,0x00,0x18,0x38,0x08,0x64,0x08,0x42,0xFC,0xFF,0x88,0x41,0xF0,0x40,0x00,0x00,0x00,0x00}, // 0x24 +{0x08,0x38,0x10,0x44,0x20,0x44,0xC0,0x44,0x00,0x39,0x70,0x02,0x88,0x0C,0x88,0x10,0x88,0x20,0x70,0x40}, // 0x25 +{0xE0,0x00,0x10,0x01,0x08,0x3A,0x08,0x46,0x88,0x45,0xC8,0x4C,0x38,0x38,0x18,0x00,0x68,0x00,0x80,0x01}, // 0x26 +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x27 +{0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x18,0x18,0x0C,0x30,0x04,0x20,0x02,0x40,0x02,0x40,0x00,0x00}, // 0x28 +{0x00,0x00,0x02,0x40,0x02,0x40,0x04,0x20,0x0C,0x30,0x18,0x18,0xE0,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x29 +{0x00,0x00,0x00,0x10,0x00,0x18,0x00,0x0F,0x00,0x72,0x00,0x0F,0x00,0x18,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x2A +{0x40,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0xF8,0x07,0x40,0x00,0x40,0x00,0x40,0x00,0x40,0x00,0x00,0x00}, // 0x2B +{0x00,0x00,0x00,0x00,0x00,0x00,0x19,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x2C +{0x00,0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x00,0x00,0x00,0x00}, // 0x2D +{0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x2E +{0x00,0x00,0x02,0x00,0x0C,0x00,0x30,0x00,0xC0,0x00,0x00,0x03,0x00,0x0C,0x00,0x30,0x00,0x40,0x00,0x00}, // 0x2F +{0x00,0x00,0xC0,0x0F,0x30,0x30,0x08,0x40,0x08,0x40,0x08,0x40,0x30,0x30,0xC0,0x0F,0x00,0x00,0x00,0x00}, // 0x30 +{0x00,0x00,0x08,0x20,0x08,0x20,0x08,0x20,0xF8,0x7F,0x08,0x00,0x08,0x00,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x31 +{0x00,0x00,0x18,0x60,0x28,0x40,0x48,0x40,0x88,0x40,0x08,0x43,0x08,0x3C,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x32 +{0x00,0x00,0x00,0x00,0x08,0x40,0x08,0x42,0x08,0x42,0x08,0x42,0xF0,0x3D,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x33 +{0xC0,0x00,0x40,0x03,0x40,0x04,0x40,0x18,0x40,0x20,0xF8,0x7F,0x40,0x00,0x40,0x00,0x00,0x00,0x00,0x00}, // 0x34 +{0x00,0x00,0x00,0x00,0x08,0x7C,0x08,0x44,0x08,0x44,0x10,0x42,0xE0,0x41,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x35 +{0x00,0x00,0xE0,0x0F,0x10,0x32,0x08,0x44,0x08,0x44,0x08,0x44,0x10,0x42,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0x36 +{0x00,0x00,0x00,0x40,0x18,0x40,0xE0,0x40,0x00,0x43,0x00,0x4C,0x00,0x50,0x00,0x60,0x00,0x00,0x00,0x00}, // 0x37 +{0x00,0x00,0xF0,0x38,0x08,0x45,0x08,0x42,0x08,0x42,0x08,0x45,0x90,0x45,0x60,0x38,0x00,0x00,0x00,0x00}, // 0x38 +{0x00,0x00,0x00,0x1E,0x08,0x21,0x88,0x40,0x88,0x40,0x88,0x40,0x30,0x21,0xC0,0x1F,0x00,0x00,0x00,0x00}, // 0x39 +{0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x06,0x18,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x3A +{0x00,0x00,0x00,0x00,0x00,0x00,0x19,0x06,0x1E,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x3B +{0x00,0x00,0x00,0x00,0xC0,0x00,0xC0,0x00,0x20,0x01,0x20,0x01,0x10,0x02,0x10,0x02,0x08,0x04,0x00,0x00}, // 0x3C +{0x00,0x00,0x20,0x01,0x20,0x01,0x20,0x01,0x20,0x01,0x20,0x01,0x20,0x01,0x20,0x01,0x20,0x01,0x00,0x00}, // 0x3D +{0x00,0x00,0x08,0x04,0x10,0x02,0x10,0x02,0x20,0x01,0x20,0x01,0xC0,0x00,0xC0,0x00,0x00,0x00,0x00,0x00}, // 0x3E +{0x00,0x00,0x00,0x70,0x00,0x40,0xD8,0x40,0x00,0x41,0x00,0x42,0x00,0x64,0x00,0x38,0x00,0x00,0x00,0x00}, // 0x3F +{0xC0,0x0F,0x30,0x18,0x18,0x20,0xC8,0x47,0x28,0x48,0x68,0x50,0xD8,0x51,0xE0,0x3F,0x20,0x00,0x20,0x00}, // 0x40 +{0x08,0x00,0x70,0x00,0xC0,0x01,0x40,0x0E,0x40,0x18,0x40,0x0C,0x40,0x03,0xC0,0x00,0x30,0x00,0x08,0x00}, // 0x41 +{0x00,0x00,0xF8,0x1F,0x08,0x11,0x08,0x11,0x08,0x11,0x08,0x11,0x88,0x12,0x70,0x0C,0x00,0x00,0x00,0x00}, // 0x42 +{0xC0,0x03,0x30,0x0C,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x18,0x00,0x00,0x00,0x00}, // 0x43 +{0x00,0x00,0xF8,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00,0x00,0x00}, // 0x44 +{0x00,0x00,0xF8,0x1F,0x88,0x10,0x88,0x10,0x88,0x10,0x88,0x10,0x88,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0x45 +{0x00,0x00,0xF8,0x1F,0x80,0x10,0x80,0x10,0x80,0x10,0x80,0x10,0x80,0x10,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x46 +{0x00,0x00,0xC0,0x03,0x30,0x0C,0x10,0x08,0x08,0x10,0x08,0x10,0x88,0x10,0x88,0x10,0xF8,0x18,0x00,0x00}, // 0x47 +{0x00,0x00,0xF8,0x1F,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0x48 +{0x00,0x00,0x08,0x10,0x08,0x10,0x08,0x10,0xF8,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0x49 +{0x00,0x00,0x00,0x00,0x08,0x00,0x08,0x10,0x08,0x10,0x08,0x10,0xF0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x4A +{0x00,0x00,0xF8,0x1F,0x00,0x01,0x80,0x01,0x40,0x02,0x20,0x04,0x20,0x08,0x10,0x10,0x08,0x00,0x00,0x00}, // 0x4B +{0x00,0x00,0xF8,0x1F,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x4C +{0xF8,0x1F,0x00,0x1C,0x80,0x07,0xE0,0x00,0x60,0x00,0x80,0x03,0x00,0x1C,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0x4D +{0x00,0x00,0xF8,0x1F,0x00,0x08,0x00,0x06,0x80,0x01,0x60,0x00,0x10,0x00,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0x4E +{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0x4F +{0x00,0x00,0xF8,0x1F,0x80,0x10,0x80,0x10,0x80,0x10,0x80,0x10,0x00,0x11,0x00,0x0E,0x00,0x00,0x00,0x00}, // 0x50 +{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x0C,0x10,0x12,0x08,0xE2,0x07,0x00,0x00}, // 0x51 +{0x00,0x00,0xF8,0x1F,0x80,0x10,0x80,0x10,0xC0,0x10,0x20,0x11,0x10,0x0E,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x52 +{0x00,0x00,0x18,0x0E,0x08,0x12,0x08,0x11,0x08,0x11,0x88,0x10,0x90,0x10,0x70,0x18,0x00,0x00,0x00,0x00}, // 0x53 +{0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0xF8,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x00}, // 0x54 +{0x00,0x00,0xE0,0x1F,0x18,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x10,0x00,0xE0,0x1F,0x00,0x00,0x00,0x00}, // 0x55 +{0x00,0x10,0x00,0x0C,0x80,0x03,0x60,0x00,0x18,0x00,0x18,0x00,0xE0,0x00,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0x56 +{0x00,0x18,0xC0,0x07,0x38,0x00,0xF0,0x00,0x00,0x07,0x80,0x03,0x70,0x00,0x38,0x00,0xC0,0x07,0x00,0x18}, // 0x57 +{0x08,0x10,0x10,0x08,0x20,0x04,0x40,0x02,0x80,0x01,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x08,0x08,0x10}, // 0x58 +{0x00,0x10,0x00,0x08,0x00,0x06,0x00,0x01,0xF8,0x00,0x00,0x01,0x00,0x02,0x00,0x04,0x00,0x08,0x00,0x10}, // 0x59 +{0x00,0x00,0x18,0x10,0x28,0x10,0x48,0x10,0x88,0x10,0x08,0x11,0x08,0x12,0x08,0x14,0x08,0x18,0x00,0x00}, // 0x5A +{0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x7F,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0x00,0x00,0x00,0x00}, // 0x5B +{0x00,0x00,0x00,0x40,0x00,0x30,0x00,0x0C,0x00,0x03,0xC0,0x00,0x30,0x00,0x0C,0x00,0x02,0x00,0x00,0x00}, // 0x5C +{0x00,0x00,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0xFE,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x5D +{0x00,0x00,0x20,0x00,0xC0,0x00,0x00,0x07,0x00,0x1C,0x00,0x70,0x00,0x0E,0xC0,0x01,0x20,0x00,0x00,0x00}, // 0x5E +{0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00,0x04,0x00}, // 0x5F +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x60 +{0x00,0x00,0x30,0x00,0x48,0x04,0x88,0x04,0x88,0x04,0x90,0x04,0xF8,0x03,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x61 +{0x00,0x00,0xF8,0x7F,0x10,0x02,0x08,0x04,0x08,0x04,0x08,0x04,0x10,0x06,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0x62 +{0x00,0x00,0xE0,0x01,0x10,0x02,0x08,0x04,0x08,0x04,0x08,0x04,0x08,0x04,0x08,0x04,0x00,0x00,0x00,0x00}, // 0x63 +{0x00,0x00,0xE0,0x01,0x18,0x02,0x08,0x04,0x08,0x04,0x08,0x04,0x10,0x02,0xF8,0x7F,0x00,0x00,0x00,0x00}, // 0x64 +{0x00,0x00,0xE0,0x01,0x90,0x02,0x88,0x04,0x88,0x04,0x88,0x04,0x88,0x04,0x88,0x03,0x00,0x00,0x00,0x00}, // 0x65 +{0x00,0x00,0x00,0x04,0x00,0x04,0xF8,0x3F,0x00,0x24,0x00,0x44,0x00,0x44,0x00,0x44,0x00,0x44,0x00,0x00}, // 0x66 +{0x00,0x00,0xE0,0x01,0x19,0x02,0x09,0x04,0x09,0x04,0x09,0x04,0x12,0x02,0xFC,0x07,0x00,0x00,0x00,0x00}, // 0x67 +{0x00,0x00,0xF8,0x7F,0x00,0x01,0x00,0x02,0x00,0x04,0x00,0x04,0x00,0x04,0xF8,0x03,0x00,0x00,0x00,0x00}, // 0x68 +{0x00,0x00,0x00,0x04,0x00,0x04,0x00,0x64,0xF8,0x67,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x69 +{0x00,0x00,0x01,0x00,0x01,0x04,0x01,0x04,0x01,0x64,0xFE,0x67,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x6A +{0x00,0x00,0xF8,0x7F,0x80,0x00,0xC0,0x00,0x20,0x01,0x20,0x02,0x10,0x02,0x08,0x04,0x00,0x00,0x00,0x00}, // 0x6B +{0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x40,0x00,0x40,0xF8,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x6C +{0xF8,0x07,0x00,0x02,0x00,0x04,0x00,0x04,0xF8,0x03,0x00,0x02,0x00,0x04,0x00,0x04,0xF8,0x03,0x00,0x00}, // 0x6D +{0x00,0x00,0xF8,0x07,0x00,0x03,0x00,0x02,0x00,0x04,0x00,0x04,0x00,0x04,0xF8,0x03,0x00,0x00,0x00,0x00}, // 0x6E +{0x00,0x00,0xE0,0x01,0x10,0x02,0x08,0x04,0x08,0x04,0x08,0x04,0x10,0x02,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0x6F +{0x00,0x00,0xFF,0x07,0x10,0x02,0x08,0x04,0x08,0x04,0x08,0x04,0x10,0x06,0xE0,0x01,0x00,0x00,0x00,0x00}, // 0x70 +{0x00,0x00,0xE0,0x01,0x18,0x02,0x08,0x04,0x08,0x04,0x08,0x04,0x10,0x02,0xFF,0x07,0x00,0x00,0x00,0x00}, // 0x71 +{0x00,0x00,0x00,0x00,0xF8,0x07,0x00,0x01,0x00,0x02,0x00,0x04,0x00,0x04,0x00,0x07,0x00,0x00,0x00,0x00}, // 0x72 +{0x00,0x00,0x18,0x03,0x88,0x04,0x88,0x04,0x48,0x04,0x48,0x04,0x30,0x04,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x73 +{0x00,0x00,0x00,0x04,0x00,0x04,0xF0,0x1F,0x08,0x04,0x08,0x04,0x08,0x04,0x08,0x04,0x00,0x00,0x00,0x00}, // 0x74 +{0x00,0x00,0xF0,0x07,0x08,0x00,0x08,0x00,0x08,0x00,0x10,0x00,0xF8,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x75 +{0x00,0x04,0x80,0x03,0x60,0x00,0x18,0x00,0x08,0x00,0x30,0x00,0xC0,0x00,0x00,0x03,0x00,0x04,0x00,0x00}, // 0x76 +{0x00,0x06,0xE0,0x01,0x18,0x00,0x70,0x00,0x80,0x03,0x80,0x01,0x70,0x00,0x18,0x00,0xE0,0x01,0x00,0x06}, // 0x77 +{0x00,0x00,0x08,0x04,0x10,0x02,0x20,0x01,0xC0,0x00,0xC0,0x00,0x20,0x01,0x10,0x02,0x08,0x04,0x00,0x00}, // 0x78 +{0x01,0x04,0x01,0x03,0xC1,0x00,0x62,0x00,0x1C,0x00,0x18,0x00,0x60,0x00,0x80,0x00,0x00,0x03,0x00,0x04}, // 0x79 +{0x00,0x00,0x08,0x04,0x18,0x04,0x28,0x04,0x48,0x04,0x88,0x04,0x08,0x05,0x08,0x06,0x08,0x04,0x00,0x00}, // 0x7A +{0x00,0x00,0x00,0x00,0x80,0x00,0x80,0x00,0x7C,0x3F,0x02,0x40,0x02,0x40,0x02,0x40,0x00,0x00,0x00,0x00}, // 0x7B +{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x7C +{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 +}; + + + +static const uint8_t __code font2[256][20]={ //https://github.com/basti79/LCD-fonts/blob/master/10x16_vertikal_LSB_1.h {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x00 {0x80,0x07,0xC0,0x0C,0x40,0x0A,0xA0,0x14,0x20,0x14,0x20,0x14,0xA0,0x14,0x40,0x0A,0xC0,0x0C,0x80,0x07}, // 0x01 {0x80,0x07,0xC0,0x0F,0xC0,0x0D,0x60,0x1B,0xE0,0x1B,0xE0,0x1B,0x60,0x1B,0xC0,0x0D,0xC0,0x0F,0x80,0x07}, // 0x02 diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index 57f4b89a..8909f8f7 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -35,15 +35,26 @@ void showSplashScreen() { selectLUT(1); clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); - epdPrintBegin(128, 0, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + + epdPrintBegin(0, 200, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); pr("Booting!"); epdPrintEnd(); - epdPrintBegin(16, 10, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); - pr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); + + epdPrintBegin(128, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); + pr("MAC5: %02X:%02X", mSelfMac[7], mSelfMac[6]); pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); + + + epdPrintBegin(128, 288, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + pr("MAC8: %02X:%02X", mSelfMac[7], mSelfMac[6]); + pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); + pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); + pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); + epdPrintEnd(); + draw(); timerDelay(1333000); #endif @@ -51,15 +62,28 @@ void showSplashScreen() { selectLUT(1); clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); - epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("Booting!"); + + epdPrintBegin(64, 150, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + pr("TEST"); epdPrintEnd(); + + + epdPrintBegin(300, 296, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_RED); + pr("Booting!Y"); + epdPrintEnd(); + + + epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + pr("BootingX!"); + epdPrintEnd(); + epdPrintBegin(16, 284, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_RED); pr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); + draw(); timerDelay(1333000); #endif From 8313f838ca2adbe5781e028c643bc68bcffc2691 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Sun, 29 Jan 2023 21:13:27 +0100 Subject: [PATCH 09/18] Variable X-shift in Y-axis print --- tag_fw/epd.c | 78 ++++++++---- tag_fw/font.h | 265 +---------------------------------------- tag_fw/userinterface.c | 28 +++-- 3 files changed, 74 insertions(+), 297 deletions(-) diff --git a/tag_fw/epd.c b/tag_fw/epd.c index ad01fbac..83578207 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -69,7 +69,9 @@ static bool __idata epdPr = false; // wheter or not we copy the pr("") o static uint8_t __xdata epdCharSize = 1; // character size, 1 or 2 (doubled) static bool __xdata directionY = true; // print direction, X or Y (true) static uint8_t __xdata rbuffer[32]; // used to rotate bits around -static uint16_t __xdata fontCurXpos = 0; // current X value where working with +static uint16_t __xdata fontCurXpos = 0; // current X value we're working with +static uint16_t __xdata fontCurYpos = 0; // current Y value we're working with + static bool __xdata isInited = false; struct waveform __xdata waveform; @@ -282,8 +284,8 @@ void setWindowY(uint16_t start, uint16_t end) { commandBegin(CMD_WINDOW_Y_SIZE); epdSend((start)&0xff); epdSend((start) >> 8); - epdSend((end - 1) & 0xff); - epdSend((end - 1) >> 8); + epdSend((end)&0xff); // was end-1 + epdSend((end) >> 8); // was end-1 commandEnd(); } void setPosXY(uint16_t x, uint16_t y) { @@ -364,23 +366,23 @@ static void pushXFontBytesToEPD(uint8_t byte1, uint8_t byte2) { if (epdCharSize == 1) { uint8_t offset = 7 - (fontCurXpos % 8); for (uint8_t c = 0; c < 8; c++) { - if (byte2 & (1 << (7-c))) rbuffer[c] |= (1 << offset); + if (byte2 & (1 << (7 - c))) rbuffer[c] |= (1 << offset); } for (uint8_t c = 0; c < 8; c++) { - if (byte1 & (1 << (7-c))) rbuffer[8 + c] |= (1 << offset); + if (byte1 & (1 << (7 - c))) rbuffer[8 + c] |= (1 << offset); } fontCurXpos++; } else { uint8_t offset = 6 - (fontCurXpos % 8); // double font size for (uint8_t c = 0; c < 8; c++) { - if (byte2 & (1 << (7-c))) { + if (byte2 & (1 << (7 - c))) { rbuffer[c * 2] |= (3 << offset); rbuffer[(c * 2) + 1] |= (3 << offset); } } for (uint8_t c = 0; c < 8; c++) { - if (byte1 & (1 << (7-c))) { + if (byte1 & (1 << (7 - c))) { rbuffer[(c * 2) + 16] |= (3 << offset); rbuffer[(c * 2) + 17] |= (3 << offset); } @@ -395,6 +397,30 @@ static void pushXFontBytesToEPD(uint8_t byte1, uint8_t byte2) { memset(rbuffer, 0, 32); } } +static void bufferByteShift(uint8_t byte) { + /* + rbuffer[0] = 0; // previous value + rbuffer[1] = y%8; // offset + rbuffer[2] = 0; // current byte counter; + rbuffer[3] = 1+(epdCharsize*2); + */ + + if (rbuffer[1] == 0) { + epdSend(byte); + } else { + uint8_t offset = rbuffer[1]; + rbuffer[0] |= (byte >> offset); + epdSend(rbuffer[0]); + //epdSend(byte); + rbuffer[0] = (byte << (8-offset)); + rbuffer[2]++; + if (rbuffer[2] == rbuffer[3]) { + epdSend(rbuffer[0]); + rbuffer[0] = 0; + rbuffer[2] = 0; + } + } +} static void pushYFontBytesToEPD(uint8_t byte1, uint8_t byte2) { if (epdCharSize == 2) { for (uint8_t j = 0; j < 2; j++) { @@ -402,21 +428,21 @@ static void pushYFontBytesToEPD(uint8_t byte1, uint8_t byte2) { for (uint8_t i = 7; i != 255; i--) { if (byte1 & (1 << i)) c |= (0x03 << ((i % 4) * 2)); if ((i % 4) == 0) { - epdSend(c); + bufferByteShift(c); c = 0; } } for (uint8_t i = 7; i != 255; i--) { if (byte2 & (1 << i)) c |= (0x03 << ((i % 4) * 2)); if ((i % 4) == 0) { - epdSend(c); + bufferByteShift(c); c = 0; } } } } else { - epdSend(byte1); - epdSend(byte2); + bufferByteShift(byte1); + bufferByteShift(byte2); } } void writeCharEPD(uint8_t c) { @@ -469,16 +495,27 @@ void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool c directionY = direction; epdCharSize = 1 + fontsize; if (directionY) { - y = SCREEN_HEIGHT - y; - if (epdCharSize == 2) { - setWindowX(x - 32, x); - setPosXY(x - 32, SCREEN_HEIGHT-y); + uint8_t extra = 0; + if (x % 8) { + extra = 8; + rbuffer[0] = 0; // previous value + rbuffer[1] = x % 8; // offset + rbuffer[2] = 0; // current byte counter; + rbuffer[3] = (epdCharSize * 2); } else { - setWindowX(x - 16, x); - setPosXY(x - 16, SCREEN_HEIGHT-y); + rbuffer[1] = 0; } - setWindowY(y, SCREEN_HEIGHT); - shortCommand1(CMD_DATA_ENTRY_MODE, 1); // was 3 + + // y = SCREEN_HEIGHT - y; + setWindowY(y, 0); + if (epdCharSize == 2) { + setWindowX(x, x + 32 + extra); + setPosXY(x, y); + } else { + setWindowX(x, x + 16 + extra); + setPosXY(x, y); + } + shortCommand1(CMD_DATA_ENTRY_MODE, 1); // was 3 } else { if (epdCharSize == 2) { x /= 2; @@ -511,7 +548,6 @@ void epdPrintEnd() { epdPr = false; } - void loadFixedTempLUT() { shortCommand1(0x18, 0x48); shortCommand2(0x1A, 0x05, 0x00); // < temp register @@ -530,7 +566,7 @@ static void readLut() { commandReadEnd(); } -extern void dump(uint8_t* __xdata a, uint16_t __xdata l); // remove me when done +extern void dump(uint8_t* __xdata a, uint16_t __xdata l); // remove me when done void lutTest() { readLut(); diff --git a/tag_fw/font.h b/tag_fw/font.h index e3d9906f..3dece553 100644 --- a/tag_fw/font.h +++ b/tag_fw/font.h @@ -1,4 +1,3 @@ - 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 @@ -256,266 +255,4 @@ static const uint8_t __code font[256][20]={ // https://raw.githubusercontent.com {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 -}; - - - -static const uint8_t __code font2[256][20]={ //https://github.com/basti79/LCD-fonts/blob/master/10x16_vertikal_LSB_1.h -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x00 -{0x80,0x07,0xC0,0x0C,0x40,0x0A,0xA0,0x14,0x20,0x14,0x20,0x14,0xA0,0x14,0x40,0x0A,0xC0,0x0C,0x80,0x07}, // 0x01 -{0x80,0x07,0xC0,0x0F,0xC0,0x0D,0x60,0x1B,0xE0,0x1B,0xE0,0x1B,0x60,0x1B,0xC0,0x0D,0xC0,0x0F,0x80,0x07}, // 0x02 -{0x7C,0x00,0xFE,0x01,0xFE,0x07,0xFE,0x0F,0xFC,0x1F,0xFC,0x1F,0xFE,0x0F,0xFE,0x07,0xFE,0x01,0x7C,0x00}, // 0x03 -{0x80,0x00,0xC0,0x01,0xF0,0x03,0xF8,0x07,0xFE,0x1F,0xF8,0x0F,0xF0,0x07,0xE0,0x03,0xC0,0x01,0x80,0x00}, // 0x04 -{0xC0,0x01,0xE0,0x03,0xE0,0x03,0xFC,0x03,0xFE,0x1F,0xFE,0x1D,0xFC,0x03,0xE0,0x03,0xE0,0x03,0xC0,0x01}, // 0x05 -{0xC0,0x01,0xE0,0x03,0xF0,0x03,0xF8,0x03,0xFC,0x1F,0xFE,0x1D,0xF8,0x03,0xF0,0x03,0xE0,0x03,0xC0,0x01}, // 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 -{0x80,0x07,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x24,0x10,0x74,0x08,0x8E,0x07,0x0E,0x00,0x18,0x00}, // 0x0B -{0x00,0x00,0x78,0x00,0x84,0x04,0x02,0x05,0x02,0x1F,0x02,0x05,0x82,0x05,0x84,0x00,0x78,0x00,0x00,0x00}, // 0x0C -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x0D -{0x00,0x60,0x00,0x70,0x00,0x70,0xF8,0x3F,0x18,0x00,0x08,0x0C,0x0C,0x0E,0x04,0x0E,0xFE,0x07,0x00,0x00}, // 0x0E -{0x80,0x00,0xF0,0x03,0x20,0x03,0x10,0x02,0x18,0x06,0x10,0x02,0x20,0x02,0x30,0x03,0xD0,0x03,0x80,0x00}, // 0x0F -{0xF0,0x1F,0xE0,0x0F,0xE0,0x0F,0xC0,0x07,0xC0,0x07,0xC0,0x07,0x80,0x03,0x80,0x03,0x00,0x01,0x00,0x01}, // 0x10 -{0x00,0x01,0x00,0x01,0x80,0x03,0x80,0x03,0xC0,0x07,0xC0,0x07,0xC0,0x07,0xE0,0x0F,0xE0,0x0F,0xF0,0x1F}, // 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,0xFE,0x1B,0x00,0x00,0x00,0x00,0xFE,0x1B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x13 -{0x00,0x00,0x1C,0x00,0x3E,0x00,0x7E,0x00,0xFE,0x7F,0x02,0x00,0x02,0x00,0xFE,0x7F,0x00,0x00,0x00,0x00}, // 0x14 -{0x00,0x00,0x00,0x00,0xDC,0x61,0x32,0x43,0x22,0x42,0x62,0x46,0x42,0x4C,0x82,0x3B,0x00,0x00,0x00,0x00}, // 0x15 -{0x00,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0xE0,0x00,0x00,0x00}, // 0x16 -{0x00,0x00,0x00,0x00,0x08,0x88,0x04,0x90,0xFE,0xBF,0x04,0x90,0x08,0x88,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x17 -{0x00,0x00,0x00,0x00,0x08,0x00,0x04,0x00,0xFE,0x7F,0x04,0x00,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x18 -{0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x20,0xFE,0x7F,0x00,0x20,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x19 -{0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x40,0x05,0x80,0x03,0x00,0x01,0x00,0x00}, // 0x1A -{0x00,0x00,0x00,0x01,0x80,0x03,0x40,0x05,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00}, // 0x1B -{0x00,0x00,0xE0,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x00}, // 0x1C -{0x00,0x00,0x00,0x01,0x80,0x03,0x40,0x05,0x00,0x01,0x00,0x01,0x40,0x05,0x80,0x03,0x00,0x01,0x00,0x00}, // 0x1D -{0x00,0x10,0x00,0x18,0x00,0x1E,0x80,0x1F,0xC0,0x1F,0xF0,0x1F,0xC0,0x1F,0x00,0x1F,0x00,0x1C,0x00,0x10}, // 0x1E -{0x10,0x00,0x30,0x00,0xF0,0x00,0xF0,0x03,0xF0,0x07,0xF0,0x1F,0xF0,0x07,0xF0,0x01,0x70,0x00,0x10,0x00}, // 0x1F -{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,0xFE,0x1B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x21 -{0x00,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x22 -{0x00,0x01,0x20,0x19,0xA0,0x07,0x78,0x01,0x26,0x19,0xA0,0x07,0x78,0x01,0x26,0x01,0x20,0x01,0x00,0x00}, // 0x23 -{0x00,0x00,0x00,0x00,0x1C,0x18,0x26,0x10,0x42,0x10,0xFF,0x3F,0x82,0x11,0x02,0x0F,0x00,0x00,0x00,0x00}, // 0x24 -{0x1C,0x10,0x22,0x08,0x22,0x04,0x22,0x03,0x9C,0x00,0x40,0x0E,0x30,0x11,0x08,0x11,0x04,0x11,0x02,0x0E}, // 0x25 -{0x00,0x07,0x80,0x08,0x5C,0x10,0x62,0x10,0xA2,0x11,0x32,0x13,0x1C,0x1C,0x00,0x18,0x00,0x16,0x80,0x01}, // 0x26 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x27 -{0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x07,0x18,0x18,0x0C,0x30,0x04,0x20,0x02,0x40,0x02,0x40,0x00,0x00}, // 0x28 -{0x00,0x00,0x02,0x40,0x02,0x40,0x04,0x20,0x0C,0x30,0x18,0x18,0xE0,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x29 -{0x00,0x00,0x08,0x00,0x18,0x00,0xF0,0x00,0x4E,0x00,0xF0,0x00,0x18,0x00,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x2A -{0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0xE0,0x1F,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x00}, // 0x2B -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x98,0x00,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x2C -{0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00}, // 0x2D -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x00,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x2E -{0x00,0x00,0x00,0x40,0x00,0x30,0x00,0x0C,0x00,0x03,0xC0,0x00,0x30,0x00,0x0C,0x00,0x02,0x00,0x00,0x00}, // 0x2F -{0x00,0x00,0xF0,0x03,0x0C,0x0C,0x02,0x10,0x02,0x10,0x02,0x10,0x0C,0x0C,0xF0,0x03,0x00,0x00,0x00,0x00}, // 0x30 -{0x00,0x00,0x04,0x10,0x04,0x10,0x04,0x10,0xFE,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x31 -{0x00,0x00,0x06,0x18,0x02,0x14,0x02,0x12,0x02,0x11,0xC2,0x10,0x3C,0x10,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x32 -{0x00,0x00,0x00,0x00,0x02,0x10,0x42,0x10,0x42,0x10,0x42,0x10,0xBC,0x0F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x33 -{0x00,0x03,0xC0,0x02,0x20,0x02,0x18,0x02,0x04,0x02,0xFE,0x1F,0x00,0x02,0x00,0x02,0x00,0x00,0x00,0x00}, // 0x34 -{0x00,0x00,0x00,0x00,0x3E,0x10,0x22,0x10,0x22,0x10,0x42,0x08,0x82,0x07,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x35 -{0x00,0x00,0xF0,0x07,0x4C,0x08,0x22,0x10,0x22,0x10,0x22,0x10,0x42,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x36 -{0x00,0x00,0x02,0x00,0x02,0x18,0x02,0x07,0xC2,0x00,0x32,0x00,0x0A,0x00,0x06,0x00,0x00,0x00,0x00,0x00}, // 0x37 -{0x00,0x00,0x1C,0x0F,0xA2,0x10,0x42,0x10,0x42,0x10,0xA2,0x10,0xA2,0x09,0x1C,0x06,0x00,0x00,0x00,0x00}, // 0x38 -{0x00,0x00,0x78,0x00,0x84,0x10,0x02,0x11,0x02,0x11,0x02,0x11,0x84,0x0C,0xF8,0x03,0x00,0x00,0x00,0x00}, // 0x39 -{0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x18,0x60,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x3A -{0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x98,0x60,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x3B -{0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x03,0x80,0x04,0x80,0x04,0x40,0x08,0x40,0x08,0x20,0x10,0x00,0x00}, // 0x3C -{0x00,0x00,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x80,0x04,0x00,0x00}, // 0x3D -{0x00,0x00,0x20,0x10,0x40,0x08,0x40,0x08,0x80,0x04,0x80,0x04,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00}, // 0x3E -{0x00,0x00,0x0E,0x00,0x02,0x00,0x02,0x1B,0x82,0x00,0x42,0x00,0x26,0x00,0x1C,0x00,0x00,0x00,0x00,0x00}, // 0x3F -{0xF0,0x03,0x18,0x0C,0x04,0x18,0xE2,0x13,0x12,0x14,0x0A,0x16,0x8A,0x1B,0xFC,0x07,0x00,0x04,0x00,0x04}, // 0x40 -{0x00,0x10,0x00,0x0E,0x80,0x03,0x70,0x02,0x18,0x02,0x30,0x02,0xC0,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0x41 -{0x00,0x00,0xF8,0x1F,0x88,0x10,0x88,0x10,0x88,0x10,0x88,0x10,0x48,0x11,0x30,0x0E,0x00,0x00,0x00,0x00}, // 0x42 -{0xC0,0x03,0x30,0x0C,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x18,0x10,0x00,0x00,0x00,0x00}, // 0x43 -{0x00,0x00,0xF8,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00,0x00,0x00}, // 0x44 -{0x00,0x00,0xF8,0x1F,0x08,0x11,0x08,0x11,0x08,0x11,0x08,0x11,0x08,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0x45 -{0x00,0x00,0xF8,0x1F,0x08,0x01,0x08,0x01,0x08,0x01,0x08,0x01,0x08,0x01,0x08,0x00,0x00,0x00,0x00,0x00}, // 0x46 -{0x00,0x00,0xC0,0x03,0x30,0x0C,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x11,0x08,0x11,0x18,0x1F,0x00,0x00}, // 0x47 -{0x00,0x00,0xF8,0x1F,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0x48 -{0x00,0x00,0x08,0x10,0x08,0x10,0x08,0x10,0xF8,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0x49 -{0x00,0x00,0x00,0x00,0x00,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0xF8,0x0F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x4A -{0x00,0x00,0xF8,0x1F,0x80,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x04,0x08,0x08,0x00,0x10,0x00,0x00}, // 0x4B -{0x00,0x00,0xF8,0x1F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x4C -{0xF8,0x1F,0x38,0x00,0xE0,0x01,0x00,0x07,0x00,0x06,0xC0,0x01,0x38,0x00,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0x4D -{0x00,0x00,0xF8,0x1F,0x10,0x00,0x60,0x00,0x80,0x01,0x00,0x06,0x00,0x08,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0x4E -{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0x4F -{0x00,0x00,0xF8,0x1F,0x08,0x01,0x08,0x01,0x08,0x01,0x08,0x01,0x88,0x00,0x70,0x00,0x00,0x00,0x00,0x00}, // 0x50 -{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x08,0x30,0x10,0x48,0xE0,0x47,0x00,0x00}, // 0x51 -{0x00,0x00,0xF8,0x1F,0x08,0x01,0x08,0x01,0x08,0x03,0x88,0x04,0x70,0x08,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x52 -{0x00,0x00,0x70,0x18,0x48,0x10,0x88,0x10,0x88,0x10,0x08,0x11,0x08,0x09,0x18,0x0E,0x00,0x00,0x00,0x00}, // 0x53 -{0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0xF8,0x1F,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x00,0x00}, // 0x54 -{0x00,0x00,0xF8,0x07,0x00,0x18,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0x55 -{0x08,0x00,0x30,0x00,0xC0,0x01,0x00,0x06,0x00,0x18,0x00,0x18,0x00,0x07,0xC0,0x00,0x30,0x00,0x08,0x00}, // 0x56 -{0x18,0x00,0xE0,0x03,0x00,0x1C,0x00,0x0F,0xE0,0x00,0xC0,0x01,0x00,0x0E,0x00,0x1C,0xE0,0x03,0x18,0x00}, // 0x57 -{0x08,0x10,0x10,0x08,0x20,0x04,0x40,0x02,0x80,0x01,0x80,0x01,0x40,0x02,0x20,0x04,0x10,0x08,0x08,0x10}, // 0x58 -{0x08,0x00,0x10,0x00,0x60,0x00,0x80,0x00,0x00,0x1F,0x80,0x00,0x40,0x00,0x20,0x00,0x10,0x00,0x08,0x00}, // 0x59 -{0x00,0x00,0x08,0x18,0x08,0x14,0x08,0x12,0x08,0x11,0x88,0x10,0x48,0x10,0x28,0x10,0x18,0x10,0x00,0x00}, // 0x5A -{0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x7F,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0x00,0x00,0x00,0x00}, // 0x5B -{0x00,0x00,0x02,0x00,0x0C,0x00,0x30,0x00,0xC0,0x00,0x00,0x03,0x00,0x0C,0x00,0x30,0x00,0x40,0x00,0x00}, // 0x5C -{0x00,0x00,0x02,0x40,0x02,0x40,0x02,0x40,0x02,0x40,0xFE,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x5D -{0x00,0x00,0x00,0x04,0x00,0x03,0xE0,0x00,0x38,0x00,0x0E,0x00,0x70,0x00,0x80,0x03,0x00,0x04,0x00,0x00}, // 0x5E -{0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20}, // 0x5F -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x60 -{0x00,0x00,0x00,0x0C,0x20,0x12,0x20,0x11,0x20,0x11,0x20,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x61 -{0x00,0x00,0xFE,0x1F,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x60,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x62 -{0x00,0x00,0x80,0x07,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00}, // 0x63 -{0x00,0x00,0x80,0x07,0x40,0x18,0x20,0x10,0x20,0x10,0x20,0x10,0x40,0x08,0xFE,0x1F,0x00,0x00,0x00,0x00}, // 0x64 -{0x00,0x00,0x80,0x07,0x40,0x09,0x20,0x11,0x20,0x11,0x20,0x11,0x20,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x65 -{0x00,0x00,0x20,0x00,0x20,0x00,0xFC,0x1F,0x24,0x00,0x22,0x00,0x22,0x00,0x22,0x00,0x22,0x00,0x00,0x00}, // 0x66 -{0x00,0x00,0x80,0x07,0x40,0x98,0x20,0x90,0x20,0x90,0x20,0x90,0x40,0x48,0xE0,0x3F,0x00,0x00,0x00,0x00}, // 0x67 -{0x00,0x00,0xFE,0x1F,0x80,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xC0,0x1F,0x00,0x00,0x00,0x00}, // 0x68 -{0x00,0x00,0x20,0x00,0x20,0x00,0x26,0x00,0xE6,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x69 -{0x00,0x00,0x00,0x80,0x20,0x80,0x20,0x80,0x26,0x80,0xE6,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x6A -{0x00,0x00,0xFE,0x1F,0x00,0x01,0x00,0x03,0x80,0x04,0x40,0x04,0x40,0x08,0x20,0x10,0x00,0x00,0x00,0x00}, // 0x6B -{0x00,0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0xFE,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x6C -{0xE0,0x1F,0x40,0x00,0x20,0x00,0x20,0x00,0xC0,0x1F,0x40,0x00,0x20,0x00,0x20,0x00,0xC0,0x1F,0x00,0x00}, // 0x6D -{0x00,0x00,0xE0,0x1F,0xC0,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xC0,0x1F,0x00,0x00,0x00,0x00}, // 0x6E -{0x00,0x00,0x80,0x07,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x6F -{0x00,0x00,0xE0,0xFF,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x60,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x70 -{0x00,0x00,0x80,0x07,0x40,0x18,0x20,0x10,0x20,0x10,0x20,0x10,0x40,0x08,0xE0,0xFF,0x00,0x00,0x00,0x00}, // 0x71 -{0x00,0x00,0x00,0x00,0xE0,0x1F,0x80,0x00,0x40,0x00,0x20,0x00,0x20,0x00,0xE0,0x00,0x00,0x00,0x00,0x00}, // 0x72 -{0x00,0x00,0xC0,0x18,0x20,0x11,0x20,0x11,0x20,0x12,0x20,0x12,0x20,0x0C,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x73 -{0x00,0x00,0x20,0x00,0x20,0x00,0xF8,0x0F,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x00,0x00,0x00,0x00}, // 0x74 -{0x00,0x00,0xE0,0x0F,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x75 -{0x20,0x00,0xC0,0x01,0x00,0x06,0x00,0x18,0x00,0x10,0x00,0x0C,0x00,0x03,0xC0,0x00,0x20,0x00,0x00,0x00}, // 0x76 -{0x60,0x00,0x80,0x07,0x00,0x18,0x00,0x0E,0xC0,0x01,0x80,0x01,0x00,0x0E,0x00,0x18,0x80,0x07,0x60,0x00}, // 0x77 -{0x00,0x00,0x20,0x10,0x40,0x08,0x80,0x04,0x00,0x03,0x00,0x03,0x80,0x04,0x40,0x08,0x20,0x10,0x00,0x00}, // 0x78 -{0x20,0x80,0xC0,0x80,0x00,0x83,0x00,0x46,0x00,0x38,0x00,0x18,0x00,0x06,0x00,0x01,0xC0,0x00,0x20,0x00}, // 0x79 -{0x00,0x00,0x20,0x10,0x20,0x18,0x20,0x14,0x20,0x12,0x20,0x11,0xA0,0x10,0x60,0x10,0x20,0x10,0x00,0x00}, // 0x7A -{0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0xFC,0x3E,0x02,0x40,0x02,0x40,0x02,0x40,0x00,0x00,0x00,0x00}, // 0x7B -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFE,0x7F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x7C -{0x00,0x00,0x00,0x00,0x02,0x40,0x02,0x40,0x02,0x40,0xFC,0x3E,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00}, // 0x7D -{0x00,0x03,0x80,0x00,0x80,0x00,0x80,0x00,0x00,0x01,0x00,0x01,0x00,0x02,0x00,0x02,0x00,0x02,0x80,0x01}, // 0x7E -{0x00,0x00,0x80,0x1F,0xC0,0x10,0x20,0x10,0x10,0x10,0x20,0x10,0xC0,0x10,0x80,0x1F,0x00,0x00,0x00,0x00}, // 0x7F -{0xC0,0x03,0x30,0x0C,0x10,0x08,0x08,0x10,0x08,0x10,0x08,0x90,0x08,0xB0,0x18,0xD0,0x00,0x00,0x00,0x00}, // 0x80 -{0x00,0x00,0xE0,0x0F,0x04,0x10,0x00,0x10,0x00,0x10,0x04,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x81 -{0x00,0x00,0x80,0x07,0x40,0x09,0x20,0x11,0x24,0x11,0x22,0x11,0x20,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x82 -{0x00,0x00,0x00,0x0C,0x24,0x12,0x22,0x11,0x22,0x11,0x24,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x83 -{0x00,0x00,0x00,0x0C,0x24,0x12,0x20,0x11,0x20,0x11,0x24,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x84 -{0x00,0x00,0x00,0x0C,0x22,0x12,0x24,0x11,0x20,0x11,0x20,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x85 -{0x00,0x00,0x00,0x0C,0x20,0x12,0x22,0x11,0x25,0x11,0x22,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0x86 -{0x00,0x00,0x80,0x07,0x40,0x08,0x20,0x10,0x20,0x90,0x20,0xB0,0x20,0xD0,0x20,0x10,0x00,0x00,0x00,0x00}, // 0x87 -{0x00,0x00,0x80,0x07,0x44,0x09,0x22,0x11,0x22,0x11,0x24,0x11,0x20,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x88 -{0x00,0x00,0x80,0x07,0x40,0x09,0x24,0x11,0x20,0x11,0x20,0x11,0x24,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x89 -{0x00,0x00,0x80,0x07,0x40,0x09,0x22,0x11,0x24,0x11,0x20,0x11,0x20,0x11,0xC0,0x11,0x00,0x00,0x00,0x00}, // 0x8A -{0x00,0x00,0x20,0x00,0x24,0x00,0x20,0x00,0xE0,0x1F,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x8B -{0x00,0x00,0x20,0x00,0x24,0x00,0x22,0x00,0xE2,0x1F,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x8C -{0x00,0x00,0x20,0x00,0x20,0x00,0x22,0x00,0xE4,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x8D -{0x00,0x10,0x00,0x0E,0x81,0x03,0x70,0x02,0x18,0x02,0x30,0x02,0xC1,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0x8E -{0x00,0x10,0x00,0x0E,0x80,0x03,0x72,0x02,0x0D,0x02,0x1D,0x02,0xF2,0x02,0x80,0x03,0x00,0x0E,0x00,0x10}, // 0x8F -{0x00,0x00,0xF8,0x1F,0x08,0x11,0x08,0x11,0x0A,0x11,0x09,0x11,0x08,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0x90 -{0x20,0x0E,0x20,0x13,0x20,0x11,0x20,0x11,0xC0,0x0F,0x20,0x19,0x20,0x11,0x20,0x11,0xC0,0x11,0x00,0x00}, // 0x91 -{0x00,0x10,0x00,0x0C,0x80,0x07,0x60,0x04,0x18,0x04,0xF8,0x1F,0x08,0x11,0x08,0x11,0x08,0x10,0x00,0x00}, // 0x92 -{0x00,0x00,0x80,0x07,0x44,0x08,0x22,0x10,0x22,0x10,0x24,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x93 -{0x00,0x00,0x80,0x07,0x44,0x08,0x20,0x10,0x20,0x10,0x24,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x94 -{0x00,0x00,0x80,0x07,0x42,0x08,0x24,0x10,0x20,0x10,0x20,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0x95 -{0x00,0x00,0xE0,0x0F,0x04,0x10,0x02,0x10,0x02,0x10,0x04,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x96 -{0x00,0x00,0xE0,0x0F,0x02,0x10,0x04,0x10,0x00,0x10,0x00,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0x97 -{0x20,0x80,0xC0,0x80,0x04,0x83,0x00,0x46,0x00,0x38,0x04,0x18,0x00,0x06,0x00,0x01,0xC0,0x00,0x20,0x00}, // 0x98 -{0x00,0x00,0xE0,0x07,0x11,0x08,0x08,0x10,0x08,0x10,0x08,0x10,0x09,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0x99 -{0x00,0x00,0xF8,0x07,0x01,0x18,0x00,0x10,0x00,0x10,0x00,0x10,0x01,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0x9A -{0x00,0x00,0x80,0x17,0x40,0x08,0x20,0x14,0x20,0x13,0xA0,0x10,0x40,0x08,0xA0,0x07,0x00,0x00,0x00,0x00}, // 0x9B -{0x00,0x00,0x00,0x00,0x00,0x10,0x40,0x18,0xFC,0x17,0x42,0x10,0x42,0x10,0x02,0x10,0x00,0x00,0x00,0x00}, // 0x9C -{0x00,0x00,0xE0,0x17,0x10,0x0C,0x08,0x16,0x08,0x11,0x88,0x10,0x68,0x10,0x30,0x08,0xE8,0x07,0x00,0x00}, // 0x9D -{0x00,0x00,0x20,0x10,0x40,0x08,0x80,0x04,0x00,0x03,0x00,0x03,0x80,0x04,0x40,0x08,0x20,0x10,0x00,0x00}, // 0x9E -{0x00,0x00,0x00,0x80,0x20,0x80,0x20,0x80,0xFE,0x7F,0x21,0x00,0x21,0x00,0x01,0x00,0x00,0x00,0x00,0x00}, // 0x9F -{0x00,0x00,0x00,0x0C,0x20,0x12,0x20,0x11,0x24,0x11,0x22,0x09,0xC0,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0xA0 -{0x00,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xE4,0x1F,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xA1 -{0x00,0x00,0x80,0x07,0x40,0x08,0x20,0x10,0x24,0x10,0x22,0x10,0x40,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0xA2 -{0x00,0x00,0xE0,0x0F,0x00,0x10,0x00,0x10,0x04,0x10,0x02,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xA3 -{0x00,0x00,0xE0,0x1F,0xC4,0x00,0x42,0x00,0x24,0x00,0x24,0x00,0x22,0x00,0xC0,0x1F,0x00,0x00,0x00,0x00}, // 0xA4 -{0x00,0x00,0xF8,0x1F,0x12,0x00,0x61,0x00,0x83,0x01,0x02,0x06,0x01,0x08,0xF8,0x1F,0x00,0x00,0x00,0x00}, // 0xA5 -{0x00,0x00,0x00,0x00,0x32,0x00,0x4A,0x00,0x4A,0x00,0x4A,0x00,0x7C,0x00,0x40,0x00,0x00,0x00,0x00,0x00}, // 0xA6 -{0x00,0x00,0x00,0x00,0x3C,0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x42,0x00,0x3C,0x00,0x00,0x00,0x00,0x00}, // 0xA7 -{0x00,0x00,0x00,0x70,0x00,0xC8,0x00,0x88,0x00,0x84,0x60,0x83,0x00,0x80,0x00,0xE0,0x00,0x00,0x00,0x00}, // 0xA8 -{0x00,0x00,0x38,0x00,0x44,0x00,0xBA,0x00,0xAA,0x00,0xBA,0x00,0x44,0x00,0x38,0x00,0x00,0x00,0x00,0x00}, // 0xA9 -{0x00,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x07,0x00,0x00}, // 0xAA -{0x02,0x10,0x7E,0x0C,0x00,0x02,0x80,0x01,0x60,0x00,0x10,0x00,0x8C,0x10,0x82,0x1C,0x80,0x14,0x00,0x13}, // 0xAB -{0x02,0x10,0x7E,0x0C,0x00,0x02,0x80,0x01,0x60,0x00,0x10,0x06,0x0C,0x05,0x82,0x04,0x80,0x1F,0x00,0x04}, // 0xAC -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xAD -{0x00,0x00,0x00,0x01,0x80,0x02,0x40,0x04,0x20,0x08,0x00,0x01,0x80,0x02,0x40,0x04,0x20,0x08,0x00,0x00}, // 0xAE -{0x00,0x00,0x20,0x08,0x40,0x04,0x80,0x02,0x00,0x01,0x20,0x08,0x40,0x04,0x80,0x02,0x00,0x01,0x00,0x00}, // 0xAF -{0xDB,0x6C,0xDB,0x6C,0x00,0x00,0xDB,0x6C,0xDB,0x6C,0x00,0x00,0xDB,0x6C,0xDB,0x6C,0x00,0x00,0x00,0x00}, // 0xB0 -{0x6C,0xDB,0x6C,0xDB,0xDB,0x6C,0xFF,0xFF,0x6C,0xDB,0xDB,0x6C,0xFF,0xFF,0x6C,0xDB,0xDB,0x6C,0xDB,0x6C}, // 0xB1 -{0xFF,0xFF,0xFF,0xFF,0xDB,0x6C,0xFF,0xFF,0xFF,0xFF,0xDB,0x6C,0xFF,0xFF,0xFF,0xFF,0xDB,0x6C,0xDB,0x6C}, // 0xB2 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xB3 -{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xB4 -{0x00,0x10,0x00,0x0E,0x80,0x03,0x70,0x02,0x1A,0x02,0x31,0x02,0xC0,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0xB5 -{0x00,0x10,0x00,0x0E,0x80,0x03,0x72,0x02,0x19,0x02,0x31,0x02,0xC2,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0xB6 -{0x00,0x10,0x00,0x0E,0x80,0x03,0x71,0x02,0x1A,0x02,0x30,0x02,0xC0,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0xB7 -{0xF0,0x03,0x0C,0x0C,0xE6,0x19,0x32,0x13,0x12,0x12,0x12,0x12,0x16,0x1A,0x0C,0x0C,0xF0,0x03,0x00,0x00}, // 0xB8 -{0x80,0x02,0x80,0x02,0x80,0x02,0xFF,0xFE,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 -{0x80,0x02,0x80,0x02,0x80,0x02,0x80,0xFE,0x80,0x00,0x80,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBB -{0x80,0x02,0x80,0x02,0x80,0x02,0xFF,0x02,0x00,0x02,0xFF,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBC -{0x00,0x00,0xF0,0x03,0x08,0x04,0x04,0x0C,0x04,0x08,0xFE,0x1F,0x04,0x08,0x04,0x08,0x00,0x00,0x00,0x00}, // 0xBD -{0x02,0x00,0x04,0x00,0x98,0x04,0xA0,0x04,0xC0,0x1F,0xA0,0x04,0x90,0x04,0x08,0x00,0x04,0x00,0x02,0x00}, // 0xBE -{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xBF -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC0 -{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xFF,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC1 -{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0xFF,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC2 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC3 -{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC4 -{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xFF,0xFF,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xC5 -{0x00,0x00,0x00,0x0C,0x24,0x12,0x22,0x11,0x24,0x11,0x24,0x09,0xC2,0x1F,0x00,0x10,0x00,0x00,0x00,0x00}, // 0xC6 -{0x00,0x10,0x00,0x0E,0x82,0x03,0x71,0x02,0x1B,0x02,0x32,0x02,0xC1,0x02,0x00,0x03,0x00,0x0C,0x00,0x10}, // 0xC7 -{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x03,0x00,0x02,0xFF,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xC8 -{0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xFF,0x80,0x00,0x80,0xFE,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xC9 -{0x80,0x02,0x80,0x02,0x80,0x02,0xFF,0x02,0x00,0x02,0xFF,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCA -{0x80,0x02,0x80,0x02,0x80,0x02,0x80,0xFE,0x80,0x00,0x80,0xFE,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCB -{0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0x00,0x00,0xFF,0xFE,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCC -{0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCD -{0x80,0x02,0x80,0x02,0x80,0x02,0xFF,0xFE,0x00,0x00,0xFF,0xFE,0x80,0x02,0x80,0x02,0x80,0x02,0x80,0x02}, // 0xCE -{0x00,0x00,0x08,0x04,0xF0,0x03,0x10,0x02,0x10,0x02,0x10,0x02,0x10,0x02,0xF0,0x03,0x08,0x04,0x00,0x00}, // 0xCF -{0x00,0x00,0x82,0x07,0x4A,0x08,0x2E,0x10,0x24,0x10,0x2A,0x10,0x70,0x08,0xC0,0x07,0x00,0x00,0x00,0x00}, // 0xD0 -{0x80,0x00,0xF8,0x1F,0x88,0x10,0x88,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00,0x00,0x00}, // 0xD1 -{0x00,0x00,0xF8,0x1F,0x08,0x11,0x0A,0x11,0x09,0x11,0x09,0x11,0x0A,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD2 -{0x00,0x00,0xF8,0x1F,0x09,0x11,0x08,0x11,0x08,0x11,0x08,0x11,0x09,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD3 -{0x00,0x00,0xF8,0x1F,0x08,0x11,0x08,0x11,0x09,0x11,0x0A,0x11,0x08,0x11,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD4 -{0x00,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xE0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xD5 -{0x00,0x00,0x08,0x10,0x08,0x10,0x08,0x10,0xFA,0x1F,0x09,0x10,0x08,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD6 -{0x00,0x00,0x08,0x10,0x08,0x10,0x0A,0x10,0xF9,0x1F,0x09,0x10,0x0A,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD7 -{0x00,0x00,0x08,0x10,0x09,0x10,0x08,0x10,0xF8,0x1F,0x08,0x10,0x09,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xD8 -{0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xD9 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01}, // 0xDA -{0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}, // 0xDB -{0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF}, // 0xDC -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xDD -{0x00,0x00,0x08,0x10,0x08,0x10,0x09,0x10,0xFA,0x1F,0x08,0x10,0x08,0x10,0x08,0x10,0x00,0x00,0x00,0x00}, // 0xDE -{0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00}, // 0xDF -{0x00,0x00,0xE0,0x07,0x10,0x08,0x08,0x10,0x0A,0x10,0x09,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE0 -{0x00,0x00,0xFC,0x1F,0x02,0x00,0x02,0x00,0xE2,0x10,0x1C,0x11,0x00,0x12,0x00,0x0C,0x00,0x00,0x00,0x00}, // 0xE1 -{0x00,0x00,0xE0,0x07,0x10,0x08,0x0A,0x10,0x09,0x10,0x09,0x10,0x0A,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE2 -{0x00,0x00,0xE0,0x07,0x10,0x08,0x09,0x10,0x0A,0x10,0x08,0x10,0x08,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE3 -{0x00,0x00,0x80,0x07,0x44,0x08,0x22,0x10,0x24,0x10,0x24,0x10,0x42,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0xE4 -{0x00,0x00,0xE0,0x07,0x12,0x08,0x09,0x10,0x0B,0x10,0x0A,0x10,0x09,0x10,0x10,0x08,0xE0,0x07,0x00,0x00}, // 0xE5 -{0x00,0x00,0xE0,0xFF,0x00,0x08,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x08,0xE0,0x1F,0x00,0x00,0x00,0x00}, // 0xE6 -{0x00,0x00,0xFE,0xFF,0x40,0x08,0x20,0x10,0x20,0x10,0x20,0x10,0x60,0x08,0x80,0x07,0x00,0x00,0x00,0x00}, // 0xE7 -{0x00,0x00,0xF8,0x1F,0x20,0x04,0x20,0x04,0x20,0x04,0x20,0x04,0x20,0x02,0xC0,0x01,0x00,0x00,0x00,0x00}, // 0xE8 -{0x00,0x00,0xF8,0x07,0x00,0x18,0x00,0x10,0x02,0x10,0x01,0x10,0x00,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0xE9 -{0x00,0x00,0xF8,0x07,0x00,0x18,0x02,0x10,0x01,0x10,0x01,0x10,0x02,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0xEA -{0x00,0x00,0xF8,0x07,0x00,0x18,0x01,0x10,0x02,0x10,0x00,0x10,0x00,0x08,0xF8,0x07,0x00,0x00,0x00,0x00}, // 0xEB -{0x20,0x80,0xC0,0x80,0x00,0x83,0x00,0x46,0x04,0x38,0x02,0x18,0x00,0x06,0x00,0x01,0xC0,0x00,0x20,0x00}, // 0xEC -{0x08,0x00,0x10,0x00,0x60,0x00,0x80,0x00,0x02,0x1F,0x81,0x00,0x40,0x00,0x20,0x00,0x10,0x00,0x08,0x00}, // 0xED -{0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00}, // 0xEE -{0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xEF -{0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00,0x00}, // 0xF0 -{0x00,0x00,0x80,0x10,0x80,0x10,0x80,0x10,0x80,0x10,0xE0,0x13,0x80,0x10,0x80,0x10,0x80,0x10,0x00,0x00}, // 0xF1 -{0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0}, // 0xF2 -{0x42,0x10,0x4A,0x08,0x5A,0x04,0x36,0x03,0x80,0x00,0x40,0x06,0x30,0x05,0x88,0x04,0x84,0x1F,0x02,0x04}, // 0xF3 -{0x00,0x00,0x1C,0x00,0x3E,0x00,0x7E,0x00,0xFE,0x7F,0x02,0x00,0x02,0x00,0xFE,0x7F,0x00,0x00,0x00,0x00}, // 0xF4 -{0x00,0x00,0x00,0x00,0xDC,0x61,0x32,0x43,0x22,0x42,0x62,0x46,0x42,0x4C,0x82,0x3B,0x00,0x00,0x00,0x00}, // 0xF5 -{0x00,0x00,0x00,0x02,0x00,0x02,0x00,0x02,0x60,0x1A,0x60,0x1A,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x00}, // 0xF6 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x00,0xA0,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xF7 -{0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x00,0x12,0x00,0x12,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xF8 -{0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xF9 -{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFA -{0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x7E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFB -{0x00,0x00,0x00,0x00,0x42,0x00,0x4A,0x00,0x4A,0x00,0x4A,0x00,0x36,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFC -{0x00,0x00,0x00,0x00,0x42,0x00,0x62,0x00,0x52,0x00,0x52,0x00,0x4C,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 0xFD -{0x00,0x00,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0x1F,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/tag_fw/userinterface.c b/tag_fw/userinterface.c index 8909f8f7..bbcbd42e 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -20,7 +20,7 @@ extern uint8_t mSelfMac[]; void showSplashScreen() { epdSetup(); lutTest(); -#if (SCREEN_WIDTH == 152) // 1.54" +#if (SCREEN_WIDTH == 152) // 1.54" selectLUT(1); clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); @@ -31,7 +31,7 @@ void showSplashScreen() { draw(); timerDelay(1333000); #endif -#if (SCREEN_WIDTH == 128) // 2.9" +#if (SCREEN_WIDTH == 128) // 2.9" selectLUT(1); clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); @@ -40,16 +40,23 @@ void showSplashScreen() { pr("Booting!"); epdPrintEnd(); - epdPrintBegin(128, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); - pr("MAC5: %02X:%02X", mSelfMac[7], mSelfMac[6]); + epdPrintBegin(0, 294, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + pr("="); + epdPrintEnd(); + + epdPrintBegin(32, 150, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + pr("-TESTING-"); + epdPrintEnd(); + + epdPrintBegin(115, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); + pr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); - - epdPrintBegin(128, 288, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - pr("MAC8: %02X:%02X", mSelfMac[7], mSelfMac[6]); + epdPrintBegin(68, 294, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + pr("MAC64: %02X:%02X", mSelfMac[7], mSelfMac[6]); pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); @@ -58,7 +65,7 @@ void showSplashScreen() { draw(); timerDelay(1333000); #endif -#if (SCREEN_WIDTH == 400) // 2.9" +#if (SCREEN_WIDTH == 400) // 2.9" selectLUT(1); clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); @@ -67,12 +74,10 @@ void showSplashScreen() { pr("TEST"); epdPrintEnd(); - epdPrintBegin(300, 296, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_RED); pr("Booting!Y"); epdPrintEnd(); - epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); pr("BootingX!"); epdPrintEnd(); @@ -83,11 +88,10 @@ void showSplashScreen() { pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); - + draw(); timerDelay(1333000); #endif - } void showApplyUpdate() { From 917ad76f66bd25024a91a57781ecbc479fc030da Mon Sep 17 00:00:00 2001 From: Jelmer Date: Mon, 30 Jan 2023 12:25:21 +0100 Subject: [PATCH 10/18] improved line-drawing --- tag_fw/epd.c | 55 ++++++++++++++++++++++++++++++++++++------ tag_fw/epd.h | 4 ++- tag_fw/userinterface.c | 3 +++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/tag_fw/epd.c b/tag_fw/epd.c index 83578207..79d08ab1 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -332,16 +332,34 @@ void drawNoWait() { // shortCommand1(0x22, SCREEN_CMD_REFRESH); shortCommand(0x20); } -void drawLineHorizontal(bool red, uint16_t y, uint8_t width) { - setWindowX(0, SCREEN_WIDTH); - setWindowY(y, y + width); - if (red) { +void drawLineHorizontal(bool color, uint16_t x1, uint16_t x2, uint16_t y) { + setWindowX(x1, x2); + setWindowY(y, y + 1); + if (color) { shortCommand1(CMD_WRITE_PATTERN_RED, 0xE6); } else { shortCommand1(CMD_WRITE_PATTERN_BW, 0xE6); } epdBusyWait(133300UL); } + +void drawLineVertical(bool color, uint16_t x, uint16_t y1, uint16_t y2) { + setWindowY(y1, y2); + setWindowX(x, x + 8); + shortCommand1(CMD_DATA_ENTRY_MODE, 3); + setPosXY(x, y1); + if (color) { + commandBegin(CMD_WRITE_FB_RED); + } else { + commandBegin(CMD_WRITE_FB_BW); + } + uint8_t __xdata c = 0x80; + c>>=(x%8); + for (; y1 < y2; y1++) { + epdSend(c); + } + commandEnd(); +} void beginFullscreenImage() { setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); setWindowX(0, SCREEN_WIDTH); @@ -411,8 +429,8 @@ static void bufferByteShift(uint8_t byte) { uint8_t offset = rbuffer[1]; rbuffer[0] |= (byte >> offset); epdSend(rbuffer[0]); - //epdSend(byte); - rbuffer[0] = (byte << (8-offset)); + // epdSend(byte); + rbuffer[0] = (byte << (8 - offset)); rbuffer[2]++; if (rbuffer[2] == rbuffer[3]) { epdSend(rbuffer[0]); @@ -496,6 +514,8 @@ void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool c epdCharSize = 1 + fontsize; if (directionY) { uint8_t extra = 0; + + // provisions for dealing with font in Y direction, byte-unaligned if (x % 8) { extra = 8; rbuffer[0] = 0; // previous value @@ -506,7 +526,6 @@ void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool c rbuffer[1] = 0; } - // y = SCREEN_HEIGHT - y; setWindowY(y, 0); if (epdCharSize == 2) { setWindowX(x, x + 32 + extra); @@ -568,6 +587,28 @@ static void readLut() { extern void dump(uint8_t* __xdata a, uint16_t __xdata l); // remove me when done +extern uint8_t __xdata blockXferBuffer[]; + +void readRam() { + setWindowY(296, 0); + setWindowX(0, 8); + setPosXY(0, 296); + shortCommand1(CMD_DATA_ENTRY_MODE, 1); // was 3 + shortCommand1(0x41, 0x00); + commandReadBegin(0x27); + epdReadByte(); + + for (uint16_t c = 0; c < 293; c++) { + blockXferBuffer[c] = epdReadByte() | 0x10; + } + commandReadEnd(); + commandBegin(CMD_WRITE_FB_BW); + for (uint16_t c = 0; c < 296; c++) { + epdSend(blockXferBuffer[c]); + } + commandEnd(); +} + void lutTest() { readLut(); dump((uint8_t*)&waveform, 96); diff --git a/tag_fw/epd.h b/tag_fw/epd.h index e1251b74..e9eefd7c 100644 --- a/tag_fw/epd.h +++ b/tag_fw/epd.h @@ -38,7 +38,9 @@ void clearWindow(bool color); void clearScreen(); void draw(); void drawNoWait(); -void drawLineHorizontal(bool red, uint16_t y, uint8_t width); +void drawLineHorizontal(bool color, uint16_t x1, uint16_t x2, uint16_t y); +void drawLineVertical(bool color, uint16_t x, uint16_t y1, uint16_t y2); + void beginFullscreenImage(); void beginWriteFramebuffer(bool color); void endWriteFramebuffer(); diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index bbcbd42e..c9eb1129 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -62,6 +62,9 @@ void showSplashScreen() { pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); + drawLineVertical(EPD_COLOR_RED, 64, 10, 286); + drawLineVertical(EPD_COLOR_BLACK, 65, 10, 286); + draw(); timerDelay(1333000); #endif From 0f0c86d5f88be2d09458ef48f10d9e41f6a4654e Mon Sep 17 00:00:00 2001 From: Jelmer Date: Mon, 30 Jan 2023 15:35:48 +0100 Subject: [PATCH 11/18] added bitmap for splashscreen-fixed red-offset bug --- tag_fw/bitmaps.h | 91 ++++++++++++++++++++++++++++++++++++++++++ tag_fw/drawing.c | 2 +- tag_fw/epd.c | 27 ++++++++++--- tag_fw/epd.h | 1 + tag_fw/userinterface.c | 35 ++++++---------- 5 files changed, 128 insertions(+), 28 deletions(-) create mode 100644 tag_fw/bitmaps.h diff --git a/tag_fw/bitmaps.h b/tag_fw/bitmaps.h new file mode 100644 index 00000000..fc4a7af2 --- /dev/null +++ b/tag_fw/bitmaps.h @@ -0,0 +1,91 @@ +// images generated by https://lvgl.io/tools/imageconverter, prepended with width, height. "CF_INDEXED_1_BIT"-mode, little-endian + +static const uint8_t __code solum[] = { + 128, 26, + 0x00, 0x00, 0x07, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x01, 0xfc, + 0x00, 0x0f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x03, 0xfc, + 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x07, 0xfc, + 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x0f, 0xfc, + 0x00, 0xff, 0x80, 0x00, 0x00, 0x3f, 0xc0, 0x78, 0x03, 0x80, 0x1c, 0x01, 0xff, 0x80, 0x0f, 0xf8, + 0x00, 0xff, 0x00, 0x00, 0x01, 0xff, 0xf0, 0x78, 0x07, 0xc0, 0x3e, 0x01, 0xff, 0x80, 0x1f, 0xf8, + 0x01, 0xfe, 0x00, 0x00, 0x03, 0xff, 0xf8, 0xf8, 0x07, 0xc0, 0x3e, 0x01, 0xff, 0x80, 0x3e, 0xf8, + 0x01, 0xfe, 0x03, 0xfe, 0x0f, 0xe1, 0xfc, 0xf8, 0x0f, 0x80, 0x7c, 0x03, 0xef, 0x80, 0x7d, 0xf8, + 0x01, 0xfe, 0x00, 0x7f, 0x0f, 0x80, 0xfc, 0xf8, 0x0f, 0x80, 0x7c, 0x03, 0xef, 0x80, 0x7d, 0xf0, + 0x01, 0xff, 0x80, 0x3f, 0x9f, 0x00, 0x7c, 0xf0, 0x0f, 0x80, 0x7c, 0x03, 0xe7, 0xc0, 0xf9, 0xf0, + 0x00, 0x7f, 0xf0, 0x3f, 0xbf, 0x00, 0x7d, 0xf0, 0x0f, 0x00, 0x78, 0x03, 0xe7, 0xc1, 0xf1, 0xf0, + 0x00, 0x00, 0x00, 0x3f, 0xbe, 0x00, 0x7d, 0xf0, 0x1f, 0x00, 0xf8, 0x07, 0xc7, 0xc3, 0xf3, 0xe0, + 0x00, 0x00, 0x00, 0x7f, 0xbc, 0x00, 0x7d, 0xe0, 0x1f, 0x00, 0xf8, 0x07, 0xc7, 0xc3, 0xe3, 0xe0, + 0x00, 0x00, 0x01, 0xff, 0x7c, 0x00, 0x7f, 0xe0, 0x1f, 0x00, 0xf0, 0x07, 0xc3, 0xc7, 0xc3, 0xe0, + 0x00, 0x00, 0x03, 0xff, 0x7c, 0x00, 0x7f, 0xe0, 0x1e, 0x01, 0xf0, 0x07, 0xc3, 0xef, 0x87, 0xe0, + 0x00, 0x00, 0x0f, 0xfc, 0x7c, 0x00, 0xfb, 0xe0, 0x3e, 0x01, 0xf0, 0x0f, 0x83, 0xef, 0x87, 0xc0, + 0x00, 0x00, 0x3f, 0xf8, 0x7c, 0x01, 0xfb, 0xc0, 0x3e, 0x01, 0xe0, 0x0f, 0x83, 0xff, 0x07, 0xc0, + 0x00, 0x00, 0xff, 0xf0, 0x7c, 0x01, 0xf7, 0xc0, 0x3e, 0x03, 0xe0, 0x0f, 0x83, 0xfe, 0x07, 0xc0, + 0x00, 0x07, 0xff, 0xc0, 0x7e, 0x07, 0xe7, 0xc0, 0x3e, 0x07, 0xc0, 0x0f, 0x81, 0xfe, 0x0f, 0xc0, + 0x00, 0x3f, 0xff, 0x00, 0x3f, 0x9f, 0xc7, 0xff, 0xbf, 0x9f, 0xc0, 0x1f, 0x01, 0xfc, 0x0f, 0x80, + 0x01, 0xff, 0xfe, 0x00, 0x1f, 0xff, 0x87, 0xff, 0x9f, 0xff, 0x80, 0x1f, 0x01, 0xf8, 0x0f, 0x80, + 0x1f, 0xff, 0xf0, 0x00, 0x0f, 0xfe, 0x07, 0xff, 0x8f, 0xfe, 0x00, 0x1f, 0x01, 0xf0, 0x1f, 0x80, +}; + +static const uint8_t __code hacked[] = { + 112, 56, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x03, 0xff, 0xc0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x87, 0xff, 0xe0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x87, 0xc3, 0xf0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x87, 0x83, 0xf8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x81, 0xfb, 0x8f, 0x03, 0xfc, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x03, 0x81, 0xf3, 0x8f, 0x03, 0xfe, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x07, 0x81, 0xe7, 0x8f, 0x07, 0xbf, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x80, 0x07, 0x81, 0xc7, 0x8f, 0x07, 0x9f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xc0, 0x07, 0x81, 0xc7, 0x87, 0x8f, 0x8f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x07, 0x01, 0xcf, 0x07, 0x9f, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x0f, 0x01, 0xef, 0x07, 0xff, 0x00, + 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0xe1, 0xf8, 0x0f, 0x01, 0xff, 0x03, 0xfe, 0x00, + 0x38, 0x07, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xfc, 0x0f, 0x01, 0xfe, 0x03, 0xfc, 0x00, + 0x38, 0x07, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x7c, 0x1e, 0x00, 0xff, 0xf0, 0x00, 0x00, + 0x3c, 0x07, 0x80, 0x00, 0x00, 0x01, 0xe0, 0x3e, 0x1e, 0x00, 0xff, 0xf0, 0x00, 0x00, + 0x3c, 0x07, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x1f, 0x1e, 0x00, 0x3f, 0xf0, 0x00, 0x00, + 0x3e, 0x07, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x1f, 0x9f, 0xe0, 0x1f, 0xc0, 0x00, 0x00, + 0x1e, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xc0, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, + 0x1f, 0x03, 0xc0, 0x00, 0x00, 0x07, 0x80, 0x07, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, + 0x0f, 0x03, 0xe0, 0x03, 0xc0, 0x07, 0x80, 0x03, 0xfc, 0xff, 0xc0, 0x00, 0x00, 0x00, + 0x0f, 0x01, 0xe0, 0x07, 0xc0, 0x07, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0f, 0x81, 0xfc, 0x07, 0xf8, 0x07, 0x80, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x81, 0xfc, 0x0f, 0xf8, 0x07, 0x80, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0x80, 0xfc, 0x0f, 0x7c, 0x07, 0xc0, 0x78, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x07, 0xc0, 0xfc, 0x0f, 0x7e, 0x03, 0xe0, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xc3, 0xfc, 0x0e, 0x3e, 0x03, 0xf3, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xdf, 0xfc, 0x0e, 0x3f, 0x01, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xff, 0xfe, 0x0e, 0x1f, 0x80, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xff, 0x9e, 0x0f, 0x1f, 0x80, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xfc, 0x1e, 0x0f, 0x1f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0xf0, 0x1f, 0x0f, 0xbf, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0xf0, 0x0f, 0x07, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf8, 0x0f, 0x87, 0xfc, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x78, 0x07, 0x83, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x78, 0x07, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x38, 0x03, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x38, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3c, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3c, 0x01, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3e, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1e, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; diff --git a/tag_fw/drawing.c b/tag_fw/drawing.c index 1c91b4bc..47a11b63 100644 --- a/tag_fw/drawing.c +++ b/tag_fw/drawing.c @@ -318,7 +318,6 @@ static void drawPrvDecodeImageOnce(void) { } } -extern uint8_t blockXferBuffer[]; void ByteDecode(uint8_t byte) { static uint8_t __xdata prev, step = 0; @@ -348,6 +347,7 @@ void drawImageAtAddress(uint32_t addr) { beginWriteFramebuffer(EPD_COLOR_BLACK); drawPrvDecodeImageOnce(); endWriteFramebuffer(); + setPosXY(0, 0); mPassNo++; beginWriteFramebuffer(EPD_COLOR_RED); drawPrvDecodeImageOnce(); diff --git a/tag_fw/epd.c b/tag_fw/epd.c index 79d08ab1..2612992b 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -1,8 +1,6 @@ #include "epd.h" - #include #include - #include "asmUtil.h" #include "board.h" #include "cpu.h" @@ -342,7 +340,6 @@ void drawLineHorizontal(bool color, uint16_t x1, uint16_t x2, uint16_t y) { } epdBusyWait(133300UL); } - void drawLineVertical(bool color, uint16_t x, uint16_t y1, uint16_t y2) { setWindowY(y1, y2); setWindowX(x, x + 8); @@ -354,7 +351,7 @@ void drawLineVertical(bool color, uint16_t x, uint16_t y1, uint16_t y2) { commandBegin(CMD_WRITE_FB_BW); } uint8_t __xdata c = 0x80; - c>>=(x%8); + c >>= (x % 8); for (; y1 < y2; y1++) { epdSend(c); } @@ -378,6 +375,26 @@ void beginWriteFramebuffer(bool color) { void endWriteFramebuffer() { commandEnd(); } +void loadRawBitmap(uint8_t* bmp, uint16_t x, uint16_t y, bool color) { + uint16_t xsize = bmp[0] / 8; + if (bmp[0] % 8) xsize++; + uint16_t size = xsize * bmp[1]; + setWindowX(x, x+(xsize*8)); + setWindowY(y, bmp[1] + y); + setPosXY(x, y); + shortCommand1(CMD_DATA_ENTRY_MODE, 3); + if (color) { + commandBegin(CMD_WRITE_FB_RED); + } else { + commandBegin(CMD_WRITE_FB_BW); + } + bmp += 2; + while (size--) { + epdSend(*(bmp++)); + } + commandEnd(); +} + // stuff for printing text static void pushXFontBytesToEPD(uint8_t byte1, uint8_t byte2) { @@ -612,4 +629,4 @@ void readRam() { void lutTest() { readLut(); dump((uint8_t*)&waveform, 96); -} \ No newline at end of file +} diff --git a/tag_fw/epd.h b/tag_fw/epd.h index e9eefd7c..44300d45 100644 --- a/tag_fw/epd.h +++ b/tag_fw/epd.h @@ -44,6 +44,7 @@ void drawLineVertical(bool color, uint16_t x, uint16_t y1, uint16_t y2); void beginFullscreenImage(); void beginWriteFramebuffer(bool color); void endWriteFramebuffer(); +void loadRawBitmap(uint8_t* bmp, uint16_t x, uint16_t y, bool color); void selectLUT(uint8_t lut); diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index c9eb1129..7ed389b1 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -14,19 +14,22 @@ #include "sleep.h" #include "spi.h" #include "timer.h" +#include "bitmaps.h" extern uint8_t mSelfMac[]; void showSplashScreen() { epdSetup(); - lutTest(); #if (SCREEN_WIDTH == 152) // 1.54" selectLUT(1); clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); - drawLineHorizontal(EPD_COLOR_BLACK, 33, 1); + + loadRawBitmap(solum, 0, 32, EPD_COLOR_BLACK); + loadRawBitmap(hacked, 16, 44, EPD_COLOR_RED); + epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("Booting!"); + pr("Starting!"); epdPrintEnd(); draw(); timerDelay(1333000); @@ -36,16 +39,8 @@ void showSplashScreen() { clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); - epdPrintBegin(0, 200, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("Booting!"); - epdPrintEnd(); - - epdPrintBegin(0, 294, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("="); - epdPrintEnd(); - - epdPrintBegin(32, 150, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("-TESTING-"); + epdPrintBegin(0, 295, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + pr("Starting!"); epdPrintEnd(); epdPrintBegin(115, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); @@ -55,15 +50,11 @@ void showSplashScreen() { pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); - epdPrintBegin(68, 294, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - pr("MAC64: %02X:%02X", mSelfMac[7], mSelfMac[6]); - pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); - pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); - pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); - epdPrintEnd(); - - drawLineVertical(EPD_COLOR_RED, 64, 10, 286); - drawLineVertical(EPD_COLOR_BLACK, 65, 10, 286); + loadRawBitmap(solum, 0, 0, EPD_COLOR_BLACK); + loadRawBitmap(hacked, 16, 12, EPD_COLOR_RED); + lutTest(); + //drawLineVertical(EPD_COLOR_RED, 64, 10, 286); + //drawLineVertical(EPD_COLOR_BLACK, 65, 10, 286); draw(); timerDelay(1333000); From 30abbcaa14e6c21920c7d1f2cfc2ba4209aae752 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Mon, 30 Jan 2023 17:14:00 +0100 Subject: [PATCH 12/18] fixed X font drawing skew --- tag_fw/epd.c | 11 ++++++----- tag_fw/userinterface.c | 7 +++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tag_fw/epd.c b/tag_fw/epd.c index 2612992b..9c93a945 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -1,6 +1,8 @@ #include "epd.h" + #include #include + #include "asmUtil.h" #include "board.h" #include "cpu.h" @@ -282,8 +284,8 @@ void setWindowY(uint16_t start, uint16_t end) { commandBegin(CMD_WINDOW_Y_SIZE); epdSend((start)&0xff); epdSend((start) >> 8); - epdSend((end)&0xff); // was end-1 - epdSend((end) >> 8); // was end-1 + epdSend((end-1)&0xff); + epdSend((end-1) >> 8); commandEnd(); } void setPosXY(uint16_t x, uint16_t y) { @@ -379,7 +381,7 @@ void loadRawBitmap(uint8_t* bmp, uint16_t x, uint16_t y, bool color) { uint16_t xsize = bmp[0] / 8; if (bmp[0] % 8) xsize++; uint16_t size = xsize * bmp[1]; - setWindowX(x, x+(xsize*8)); + setWindowX(x, x + (xsize * 8)); setWindowY(y, bmp[1] + y); setPosXY(x, y); shortCommand1(CMD_DATA_ENTRY_MODE, 3); @@ -395,7 +397,6 @@ void loadRawBitmap(uint8_t* bmp, uint16_t x, uint16_t y, bool color) { commandEnd(); } - // stuff for printing text static void pushXFontBytesToEPD(uint8_t byte1, uint8_t byte2) { if (epdCharSize == 1) { @@ -543,7 +544,7 @@ void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool c rbuffer[1] = 0; } - setWindowY(y, 0); + setWindowY(y, 1); if (epdCharSize == 2) { setWindowX(x, x + 32 + extra); setPosXY(x, y); diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index 7ed389b1..bd7a1cbf 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -27,6 +27,7 @@ void showSplashScreen() { loadRawBitmap(solum, 0, 32, EPD_COLOR_BLACK); loadRawBitmap(hacked, 16, 44, EPD_COLOR_RED); + epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); pr("Starting!"); @@ -83,6 +84,12 @@ void showSplashScreen() { pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); + loadRawBitmap(solum, 256, 10, EPD_COLOR_BLACK); + loadRawBitmap(hacked, 264, 22, EPD_COLOR_RED); + + loadRawBitmap(solum,253, 72, EPD_COLOR_BLACK); + loadRawBitmap(hacked, 261, 82, EPD_COLOR_RED); + draw(); timerDelay(1333000); #endif From 9ca62e638816c8a5fc5eaa6911a9fae8fdc77175 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Mon, 30 Jan 2023 19:26:35 +0100 Subject: [PATCH 13/18] 1.54 splashscreen --- tag_fw/userinterface.c | 44 +++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index bd7a1cbf..2e0ce12f 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -4,6 +4,7 @@ #include #include "asmUtil.h" +#include "bitmaps.h" #include "board.h" #include "cpu.h" #include "epd.h" @@ -14,27 +15,41 @@ #include "sleep.h" #include "spi.h" #include "timer.h" -#include "bitmaps.h" extern uint8_t mSelfMac[]; +static const char __code soft_subversion[] = "-LUTs"; + void showSplashScreen() { epdSetup(); + #if (SCREEN_WIDTH == 152) // 1.54" - selectLUT(1); + // selectLUT(1); clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); - loadRawBitmap(solum, 0, 32, EPD_COLOR_BLACK); - loadRawBitmap(hacked, 16, 44, EPD_COLOR_RED); - - - epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + epdPrintBegin(12, 2, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); pr("Starting!"); epdPrintEnd(); + + loadRawBitmap(solum, 8, 34, EPD_COLOR_BLACK); + loadRawBitmap(hacked, 32, 46, EPD_COLOR_RED); + + epdPrintBegin(5, 136, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_RED); + pr("%02X%02X", mSelfMac[7], mSelfMac[6]); + pr("%02X%02X", mSelfMac[5], mSelfMac[4]); + pr("%02X%02X", mSelfMac[3], mSelfMac[2]); + pr("%02X%02X", mSelfMac[1], mSelfMac[0]); + epdPrintEnd(); + + epdPrintBegin(2, 120, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + pr("zbs29v033 0.1.4"); + pr(soft_subversion); + epdPrintEnd(); draw(); - timerDelay(1333000); #endif + + #if (SCREEN_WIDTH == 128) // 2.9" selectLUT(1); clearScreen(); @@ -51,14 +66,18 @@ void showSplashScreen() { pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); + epdPrintBegin(96, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + pr("zbs29v033 0.1.4"); + pr(soft_subversion); + epdPrintEnd(); + loadRawBitmap(solum, 0, 0, EPD_COLOR_BLACK); loadRawBitmap(hacked, 16, 12, EPD_COLOR_RED); lutTest(); - //drawLineVertical(EPD_COLOR_RED, 64, 10, 286); - //drawLineVertical(EPD_COLOR_BLACK, 65, 10, 286); + // drawLineVertical(EPD_COLOR_RED, 64, 10, 286); + // drawLineVertical(EPD_COLOR_BLACK, 65, 10, 286); draw(); - timerDelay(1333000); #endif #if (SCREEN_WIDTH == 400) // 2.9" selectLUT(1); @@ -87,11 +106,10 @@ void showSplashScreen() { loadRawBitmap(solum, 256, 10, EPD_COLOR_BLACK); loadRawBitmap(hacked, 264, 22, EPD_COLOR_RED); - loadRawBitmap(solum,253, 72, EPD_COLOR_BLACK); + loadRawBitmap(solum, 253, 72, EPD_COLOR_BLACK); loadRawBitmap(hacked, 261, 82, EPD_COLOR_RED); draw(); - timerDelay(1333000); #endif } From 97ced31b12f27d860caea41aca821c2e4eeb7123 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Mon, 30 Jan 2023 21:00:34 +0100 Subject: [PATCH 14/18] beginning refactoring --- tag_fw/Makefile | 2 +- tag_fw/bitmaps.h | 6 + tag_fw/comms.c | 26 ---- tag_fw/main.c | 86 +++++++++++- tag_fw/powermgt.c | 116 ++++++++++++++++ tag_fw/powermgt.h | 26 ++++ tag_fw/proto.h | 241 ++++++++++++++++----------------- tag_fw/soc/radioCommon.h | 14 +- tag_fw/syncedproto.c | 281 ++------------------------------------- tag_fw/syncedproto.h | 6 +- tag_fw/userinterface.c | 4 +- 11 files changed, 363 insertions(+), 445 deletions(-) create mode 100644 tag_fw/powermgt.c create mode 100644 tag_fw/powermgt.h diff --git a/tag_fw/Makefile b/tag_fw/Makefile index 5dec4173..927d382d 100644 --- a/tag_fw/Makefile +++ b/tag_fw/Makefile @@ -5,7 +5,7 @@ BUILD ?= zbs29v033 SOURCES += main.c eeprom.c drawing.c SOURCES += comms.c SOURCES += syncedproto.c epd.c userinterface.c - +SOURCES += powermgt.c all: #make sure it is the first target diff --git a/tag_fw/bitmaps.h b/tag_fw/bitmaps.h index fc4a7af2..d8766259 100644 --- a/tag_fw/bitmaps.h +++ b/tag_fw/bitmaps.h @@ -1,4 +1,8 @@ +#ifndef _BITMAPS_H_ +#define _BITMAPS_H_ + // images generated by https://lvgl.io/tools/imageconverter, prepended with width, height. "CF_INDEXED_1_BIT"-mode, little-endian +#include static const uint8_t __code solum[] = { 128, 26, @@ -89,3 +93,5 @@ static const uint8_t __code hacked[] = { 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; + +#endif \ No newline at end of file diff --git a/tag_fw/comms.c b/tag_fw/comms.c index 273f81a2..4bb915f1 100644 --- a/tag_fw/comms.c +++ b/tag_fw/comms.c @@ -24,32 +24,6 @@ static uint8_t __xdata mSeq = 0; static uint8_t __xdata mLastLqi = 0; static int8_t __xdata mLastRSSI = 0; - -struct MacFrameFromMaster { - struct MacFcs fcs; - uint8_t seq; - uint16_t pan; - uint8_t dst[8]; - uint16_t from; -}; - -struct MacFrameNormal { - struct MacFcs fcs; - uint8_t seq; - uint16_t pan; - uint8_t dst[8]; - uint8_t src[8]; -}; - -struct MacFrameBcast { - struct MacFcs fcs; - uint8_t seq; - uint16_t dstPan; - uint16_t dstAddr; - uint16_t srcPan; - uint8_t src[8]; -}; - uint8_t commsGetLastPacketLQI(void) { return mLastLqi; diff --git a/tag_fw/main.c b/tag_fw/main.c index 997b765a..662bd6a4 100644 --- a/tag_fw/main.c +++ b/tag_fw/main.c @@ -4,6 +4,7 @@ #include #include #include + #include "asmUtil.h" #include "board.h" #include "comms.h" @@ -15,11 +16,92 @@ #include "radio.h" #include "screen.h" #include "sleep.h" +#include "syncedproto.h" #include "timer.h" #include "wdt.h" -#include "syncedproto.h" +#include "powermgt.h" +#include "userinterface.h" +void mainProtocolLoop(void) { + clockingAndIntsInit(); + timerInit(); + boardInit(); -void main(void){ + if (!boardGetOwnMac(mSelfMac)) { + pr("failed to get MAC. Aborting\n"); + while (1) + ; + } else { + /* + for (uint8_t c = 0; c < 8; c++) { + mSelfMac[c] = c + 5; + } + */ + // really... if I do the call below, it'll cost me 8 bytes IRAM. Not the kind of 'optimization' I ever dreamed of doing + // pr("MAC>%02X%02X%02X%02X%02X%02X%02X%02X\n", mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3], mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7]); + 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]); + } + + irqsOn(); + boardInitStage2(); + + pr("BOOTED> (UI 0.03-1)\n\n"); + + if (!eepromInit()) { + pr("failed to init eeprom\n"); + while (1) + ; + } else { + initializeProto(); + } + + // initialize attempt-array with the default value; + initPowerSaving(); + + // show the splashscreen + showSplashScreen(); + + epdEnterSleep(); + eepromDeepPowerDown(); + initRadio(); + + P1CHSTA &= ~(1 << 0); + + while (1) { + radioRxEnable(true, true); + + struct AvailDataInfo *__xdata avail = getAvailDataInfo(); + if (avail == NULL) { + // no data :( + nextCheckInFromAP = 0; // let the power-saving algorithm determine the next sleep period + } else { + nextCheckInFromAP = avail->nextCheckIn; + // got some data from the AP! + if (avail->dataType != DATATYPE_NOUPDATE) { + // data transfer + if (doDataDownload(avail)) { + // succesful transfer, next wake time is determined by the NextCheckin; + } else { + // failed transfer, let the algorithm determine next sleep interval (not the AP) + nextCheckInFromAP = 0; + } + } else { + // no data transfer, just sleep. + } + } + + // if the AP told us to sleep for a specific period, do so. + if (nextCheckInFromAP) { + doSleep(nextCheckInFromAP * 60000UL); + } else { + doSleep(getNextSleep() * 1000UL); + } + } +} + +void main(void) { mainProtocolLoop(); } diff --git a/tag_fw/powermgt.c b/tag_fw/powermgt.c new file mode 100644 index 00000000..1ddb1e2a --- /dev/null +++ b/tag_fw/powermgt.c @@ -0,0 +1,116 @@ +#include "powermgt.h" + +#include +#include +#include +#include +#include + +#include "asmUtil.h" +#include "board.h" +#include "comms.h" +#include "cpu.h" +#include "drawing.h" +#include "eeprom.h" +#include "epd.h" +#include "i2c.h" +#include "printf.h" +#include "proto.h" +#include "radio.h" +#include "sleep.h" +#include "syncedproto.h" +#include "timer.h" +#include "userinterface.h" +#include "wdt.h" + + +#define HAS_BUTTON // uncomment to enable reading a push button (connect between 'TEST' en 'GND' on the tag, along with a 100nF capacitor in parallel). + +uint16_t __xdata dataReqAttemptArr[POWER_SAVING_SMOOTHING] = {0}; // Holds the amount of attempts required per data_req/check-in +uint8_t __xdata dataReqAttemptArrayIndex = 0; +uint8_t __xdata dataReqLastAttempt = 0; +uint16_t __xdata nextCheckInFromAP = 0; + +void initPowerSaving() { + for (uint8_t c = 0; c < POWER_SAVING_SMOOTHING; c++) { + dataReqAttemptArr[c] = INTERVAL_BASE; + } +} + +// init/sleep +void initRadio() { + radioInit(); + radioRxFilterCfg(mSelfMac, 0x10000, PROTO_PAN_ID); + radioSetChannel(RADIO_FIRST_CHANNEL); + radioSetTxPower(10); +} +void killRadio() { + radioRxEnable(false, true); + RADIO_IRQ4_pending = 0; + UNK_C1 &= ~0x81; + TCON &= ~0x20; + uint8_t __xdata cfgPg = CFGPAGE; + CFGPAGE = 4; + RADIO_command = 0xCA; + RADIO_command = 0xC5; + CFGPAGE = cfgPg; +} +void initAfterWake() { + clockingAndIntsInit(); + timerInit(); + // partialInit(); + boardInit(); + epdEnterSleep(); + irqsOn(); + boardInitStage2(); + initRadio(); +} +void doSleep(uint32_t __xdata t) { + if (t > 1000) pr("s=%lu\n ", t / 1000); + powerPortsDownForSleep(); + +#ifdef HAS_BUTTON + // Button setup on TEST pin 1.0 (input pullup) + P1FUNC &= ~(1 << 0); + P1DIR |= (1 << 0); + P1PULL |= (1 << 0); + P1LVLSEL |= (1 << 0); + P1INTEN = (1 << 0); + P1CHSTA &= ~(1 << 0); +#endif + + // sleepy + sleepForMsec(t); + +#ifdef HAS_BUTTON + P1INTEN = 0; +#endif + + initAfterWake(); +} +uint16_t getNextSleep() { + uint16_t __xdata curval = INTERVAL_AT_MAX_ATTEMPTS - INTERVAL_BASE; + curval *= dataReqLastAttempt; + curval /= DATA_REQ_MAX_ATTEMPTS; + curval += INTERVAL_BASE; + dataReqAttemptArr[dataReqAttemptArrayIndex % POWER_SAVING_SMOOTHING] = curval; + dataReqAttemptArrayIndex++; + + uint16_t avg = 0; + bool noNetwork = true; + for (uint8_t c = 0; c < POWER_SAVING_SMOOTHING; c++) { + avg += dataReqAttemptArr[c]; + if (dataReqAttemptArr[c] != INTERVAL_AT_MAX_ATTEMPTS) { + noNetwork = false; + } + } + if (noNetwork == true) return INTERVAL_NO_SIGNAL; + avg /= POWER_SAVING_SMOOTHING; + return avg; +} + +void initRadio(); +void killRadio(); +void initAfterWake(); +void doSleep(uint32_t __xdata t); +uint16_t getNextSleep(); diff --git a/tag_fw/powermgt.h b/tag_fw/powermgt.h new file mode 100644 index 00000000..6135d00d --- /dev/null +++ b/tag_fw/powermgt.h @@ -0,0 +1,26 @@ +#ifndef _POWERMGT_H_ +#define _POWERMGT_H_ +#include + +// power saving algorithm +#define INTERVAL_BASE 40 // interval (in seconds) (when 1 packet is sent/received) for target current (7.2µA) +#define INTERVAL_AT_MAX_ATTEMPTS 600 // interval (in seconds) (at max attempts) for target average current +#define INTERVAL_NO_SIGNAL 1800 // interval (in seconds) when no answer for POWER_SAVING_SMOOTHING attempts, + // (INTERVAL_AT_MAX_ATTEMPTS * POWER_SAVING_SMOOTHING) seconds +#define DATA_REQ_RX_WINDOW_SIZE 5UL // How many milliseconds we should wait for a packet during the data_request. + // If the AP holds a long list of data for tags, it may need a little more time to lookup the mac address +#define DATA_REQ_MAX_ATTEMPTS 14 // How many attempts (at most) we should do to get something back from the AP +#define POWER_SAVING_SMOOTHING 8 // How many samples we should use to smooth the data request interval +#define MINIMUM_INTERVAL 45 // IMPORTANT: Minimum interval for check-in; this determines overal battery life! + +extern void initRadio(); +extern void killRadio(); +extern void initAfterWake(); +extern void doSleep(uint32_t __xdata t); +extern uint16_t getNextSleep(); +extern void initPowerSaving(); + +extern uint16_t __xdata nextCheckInFromAP; +extern uint8_t __xdata dataReqLastAttempt; + +#endif \ No newline at end of file diff --git a/tag_fw/proto.h b/tag_fw/proto.h index 2dd14159..e71224fe 100644 --- a/tag_fw/proto.h +++ b/tag_fw/proto.h @@ -1,47 +1,8 @@ #ifndef _PROTO_H_ #define _PROTO_H_ - +#define __packed #include -/* - All communications are direct from tag to station, EXCEPT association (tag will broadcast). - All comms shall be encrypted and authenticated with AES-CCM. Shared key shall be burned into the firmware. - Master shall provision new key at association. All non-bcast packets shall have pan id compression. - Master may skip "from" field. Tag checking in confirms it got the master's provisioning reply. - - Sadly filtering on MZ100 fails for long addr with no src addr. so short addr for src is used - - T = tag, S = station - - PACKET TYPE USE PAYLOAD STRUCT NOTES - ASSOC_REQ T2bcast TagInfo tag's info and assoc request (encrypted with shared key) - ASSOC_RESP S2T AssocInfo tag's association info (encrypted with shared key) - CHECKIN T2S CheckinInfo tag checking in occasionally - CHECKOUT S2T PendingInfo station's checkin reply telling tag what we have for it - CHUNK_REQ T2S ChunkReqInfo tag requesting a piece of data - CHUNK_RESP S2T ChunkInfo station provides chunk - -*/ - -#define PROTO_PRESHARED_KEY {0x34D906D3, 0xE3E5298E, 0x3429BF58, 0xC1022081} - -#define PROTO_PAN_ID (0x4447) //PAN ID compression shall be used - -#define PKT_ASSOC_REQ (0xF0) -#define PKT_ASSOC_RESP (0xF1) -#define PKT_CHECKIN (0xF2) -#define PKT_CHECKOUT (0xF3) -#define PKT_CHUNK_REQ (0xF4) -#define PKT_CHUNK_RESP (0xF5) - -#define PROTO_VER_0 (0) -#define PROTO_VER_CURRENT (PROTO_VER_0) - -#define PROTO_COMPR_TYPE_LZ (0x0001) -#define PROTO_COMPR_TYPE_BITPACK (0x0002) - -#define PROTO_MAX_DL_LEN (88) - enum TagScreenType { TagScreenEink_BW_1bpp, TagScreenEink_BW_2bpp, @@ -72,111 +33,135 @@ enum TagScreenType { #define __packed __attribute__((packed)) #endif -struct TagState { - uint64_t swVer; - uint16_t hwType; - uint16_t batteryMv; +#define PROTO_PAN_ID (0x4447) //PAN ID compression shall be used + + +#define RADIO_MAX_PACKET_LEN (125) //useful payload, not including the crc + +#define ADDR_MODE_NONE (0) +#define ADDR_MODE_SHORT (2) +#define ADDR_MODE_LONG (3) + +#define FRAME_TYPE_BEACON (0) +#define FRAME_TYPE_DATA (1) +#define FRAME_TYPE_ACK (2) +#define FRAME_TYPE_MAC_CMD (3) + +#define SHORT_MAC_UNUSED (0x10000000UL) //for radioRxFilterCfg's myShortMac + + + +struct MacFcs { + uint8_t frameType : 3; + uint8_t secure : 1; + uint8_t framePending : 1; + uint8_t ackReqd : 1; + uint8_t panIdCompressed : 1; + uint8_t rfu1 : 1; + uint8_t rfu2 : 2; + uint8_t destAddrType : 2; + uint8_t frameVer : 2; + uint8_t srcAddrType : 2; +} __packed ; + +struct MacFrameFromMaster { + struct MacFcs fcs; + uint8_t seq; + uint16_t pan; + uint8_t dst[8]; + uint16_t from; } __packed; -struct TagInfo { - uint8_t protoVer; //PROTO_VER_* - struct TagState state; - uint8_t rfu1[1]; //shall be ignored for now - uint16_t screenPixWidth; - uint16_t screenPixHeight; - uint16_t screenMmWidth; - uint16_t screenMmHeight; - uint16_t compressionsSupported; //COMPR_TYPE_* bitfield - uint16_t maxWaitMsec; //how long tag will wait for packets before going to sleep - uint8_t screenType; //enum TagScreenType - uint8_t rfu[11]; //shall be zero for now +struct MacFrameNormal { + struct MacFcs fcs; + uint8_t seq; + uint16_t pan; + uint8_t dst[8]; + uint8_t src[8]; } __packed; -struct AssocInfo { - uint32_t checkinDelay; //space between checkins, in msec - uint32_t retryDelay; //if download fails mid-way wait thi smany msec to retry (IFF progress was made) - uint16_t failedCheckinsTillBlank; //how many fails till we go blank - uint16_t failedCheckinsTillDissoc; //how many fails till we dissociate - uint32_t newKey[4]; - uint8_t rfu[8]; //shall be zero for now +struct MacFrameBcast { + struct MacFcs fcs; + uint8_t seq; + uint16_t dstPan; + uint16_t dstAddr; + uint16_t srcPan; + uint8_t src[8]; } __packed; -#define CHECKIN_TEMP_OFFSET 0x7f +#define PKT_AVAIL_DATA_REQ 0xE5 +#define PKT_AVAIL_DATA_INFO 0xE6 +#define PKT_BLOCK_PARTIAL_REQUEST 0xE7 +#define PKT_BLOCK_REQUEST_ACK 0xE9 +#define PKT_BLOCK_REQUEST 0xE4 +#define PKT_BLOCK_PART 0xE8 +#define PKT_XFER_COMPLETE 0xEA +#define PKT_XFER_COMPLETE_ACK 0xEB +#define PKT_CANCEL_XFER 0xEC -struct CheckinInfo { - struct TagState state; - uint8_t lastPacketLQI; //zero if not reported/not supported to be reported - int8_t lastPacketRSSI; //zero if not reported/not supported to be reported - uint8_t temperature; //zero if not reported/not supported to be reported. else, this minus CHECKIN_TEMP_OFFSET is temp in degrees C - uint8_t rfu[6]; //shall be zero for now +struct AvailDataReq { + uint8_t checksum; + uint8_t lastPacketLQI; // zero if not reported/not supported to be reported + int8_t lastPacketRSSI; // zero if not reported/not supported to be reported + uint8_t temperature; // zero if not reported/not supported to be reported. else, this minus CHECKIN_TEMP_OFFSET is temp in degrees C + uint16_t batteryMv; + uint8_t softVer; + uint8_t hwType; + uint8_t protoVer; + uint8_t buttonState; } __packed; -struct PendingInfo { - uint64_t imgUpdateVer; - uint32_t imgUpdateSize; - uint64_t osUpdateVer; //version of OS update avail - uint32_t osUpdateSize; - uint8_t rfu[8]; //shall be zero for now +#define DATATYPE_NOUPDATE 0 +#define DATATYPE_IMG 1 +#define DATATYPE_IMGRAW 2 +#define DATATYPE_UPDATE 3 + +struct AvailDataInfo { + uint8_t checksum; + uint64_t dataVer; + uint32_t dataSize; + uint8_t dataType; + uint16_t nextCheckIn; } __packed; -struct ChunkReqInfo { - uint64_t versionRequested; - uint32_t offset; - uint8_t len; - uint8_t osUpdatePlz : 1; - uint8_t rfu[6]; //shall be zero for now +struct blockPart { + uint8_t checksum; + uint8_t blockId; + uint8_t blockPart; + uint8_t data[]; } __packed; -struct ChunkInfo { - uint32_t offset; - uint8_t osUpdatePlz : 1; - uint8_t rfu; //shall be zero for now - uint8_t data[]; //no data means request is out of bounds of this version no longer exists +struct blockData { + uint16_t size; + uint16_t checksum; + uint8_t data[]; } __packed; +struct burstMacData { + uint16_t offset; + uint8_t targetMac[8]; +} __packed; +#define BLOCK_PART_DATA_SIZE 99 +#define BLOCK_MAX_PARTS 42 +#define BLOCK_DATA_SIZE 4096UL +#define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData) +#define BLOCK_REQ_PARTS_BYTES 6 + +struct blockRequest { + uint8_t checksum; + uint64_t ver; + uint8_t blockId; + uint8_t type; + uint8_t requestedParts[BLOCK_REQ_PARTS_BYTES]; +} __packed; + +struct blockRequestAck { + uint8_t checksum; + uint16_t pleaseWaitMs; +} __packed; #define MACFMT "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x" #define MACCVT(x) ((const uint8_t*)(x))[7], ((const uint8_t*)(x))[6], ((const uint8_t*)(x))[5], ((const uint8_t*)(x))[4], ((const uint8_t*)(x))[3], ((const uint8_t*)(x))[2], ((const uint8_t*)(x))[1], ((const uint8_t*)(x))[0] - -#define VERSION_SIGNIFICANT_MASK (0x0000ffffffffffffull) - -#define HW_TYPE_42_INCH_SAMSUNG (1) -#define HW_TYPE_42_INCH_SAMSUNG_ROM_VER_OFST (0xEFF8) - -#define HW_TYPE_74_INCH_DISPDATA (2) -#define HW_TYPE_74_INCH_DISPDATA_FRAME_MODE (3) -#define HW_TYPE_74_INCH_DISPDATA_ROM_VER_OFST (0x008b) - -#define HW_TYPE_ZBD_EPOP50 (4) -#define HW_TYPE_ZBD_EPOP50_ROM_VER_OFST (0x008b) - -#define HW_TYPE_ZBD_EPOP900 (5) -#define HW_TYPE_ZBD_EPOP900_ROM_VER_OFST (0x008b) - -#define HW_TYPE_29_INCH_DISPDATA (6) -#define HW_TYPE_29_INCH_DISPDATA_FRAME_MODE (7) -#define HW_TYPE_29_INCH_DISPDATA_ROM_VER_OFST (0x008b) - -#define HW_TYPE_29_INCH_ZBS_026 (8) -#define HW_TYPE_29_INCH_ZBS_026_FRAME_MODE (9) - -#define HW_TYPE_154_INCH_ZBS_033 (18) -#define HW_TYPE_154_INCH_ZBS_033_FRAME_MODE (19) - -#define HW_TYPE_42_INCH_ZBS_026 (28) -#define HW_TYPE_42_INCH_ZBS_026_FRAME_MODE (29) - -#define HW_TYPE_29_INCH_ZBS_025 (10) -#define HW_TYPE_29_INCH_ZBS_025_FRAME_MODE (11) - -#define HW_TYPE_29_INCH_ZBS_ROM_VER_OFST (0x008b) - - - -#endif - - - - +#endif \ No newline at end of file diff --git a/tag_fw/soc/radioCommon.h b/tag_fw/soc/radioCommon.h index 61c4f8f5..b38f5186 100644 --- a/tag_fw/soc/radioCommon.h +++ b/tag_fw/soc/radioCommon.h @@ -21,19 +21,7 @@ -struct MacFcs { - - uint8_t frameType : 3; - uint8_t secure : 1; - uint8_t framePending : 1; - uint8_t ackReqd : 1; - uint8_t panIdCompressed : 1; - uint8_t rfu1 : 1; - uint8_t rfu2 : 2; - uint8_t destAddrType : 2; - uint8_t frameVer : 2; - uint8_t srcAddrType : 2; -}; + void radioInit(void); diff --git a/tag_fw/syncedproto.c b/tag_fw/syncedproto.c index 5be1e4a2..b9cc6665 100644 --- a/tag_fw/syncedproto.c +++ b/tag_fw/syncedproto.c @@ -6,118 +6,24 @@ #include #include #include + #include "asmUtil.h" #include "board.h" #include "comms.h" #include "cpu.h" #include "drawing.h" #include "eeprom.h" +#include "epd.h" #include "i2c.h" +#include "powermgt.h" #include "printf.h" #include "proto.h" #include "radio.h" -#include "epd.h" -#include "userinterface.h" #include "sleep.h" #include "timer.h" +#include "userinterface.h" #include "wdt.h" - -struct MacFrameFromMaster { - struct MacFcs fcs; - uint8_t seq; - uint16_t pan; - uint8_t dst[8]; - uint16_t from; -} __packed; - -struct MacFrameNormal { - struct MacFcs fcs; - uint8_t seq; - uint16_t pan; - uint8_t dst[8]; - uint8_t src[8]; -} __packed; - -struct MacFrameBcast { - struct MacFcs fcs; - uint8_t seq; - uint16_t dstPan; - uint16_t dstAddr; - uint16_t srcPan; - uint8_t src[8]; -} __packed; - -#define PKT_AVAIL_DATA_REQ 0xE5 -#define PKT_AVAIL_DATA_INFO 0xE6 -#define PKT_BLOCK_PARTIAL_REQUEST 0xE7 -#define PKT_BLOCK_REQUEST_ACK 0xE9 -#define PKT_BLOCK_REQUEST 0xE4 -#define PKT_BLOCK_PART 0xE8 -#define PKT_XFER_COMPLETE 0xEA -#define PKT_XFER_COMPLETE_ACK 0xEB -#define PKT_CANCEL_XFER 0xEC - -struct AvailDataReq { - uint8_t checksum; - uint8_t lastPacketLQI; // zero if not reported/not supported to be reported - int8_t lastPacketRSSI; // zero if not reported/not supported to be reported - uint8_t temperature; // zero if not reported/not supported to be reported. else, this minus CHECKIN_TEMP_OFFSET is temp in degrees C - uint16_t batteryMv; - uint8_t softVer; - uint8_t hwType; - uint8_t protoVer; - uint8_t buttonState; -} __packed; - -#define DATATYPE_NOUPDATE 0 -#define DATATYPE_IMG 1 -#define DATATYPE_IMGRAW 2 -#define DATATYPE_UPDATE 3 - -struct AvailDataInfo { - uint8_t checksum; - uint64_t dataVer; - uint32_t dataSize; - uint8_t dataType; - uint16_t nextCheckIn; -} __packed; - -struct blockPart { - uint8_t checksum; - uint8_t blockId; - uint8_t blockPart; - uint8_t data[]; -} __packed; - -struct blockData { - uint16_t size; - uint16_t checksum; - uint8_t data[]; -} __packed; - -struct burstMacData { - uint16_t offset; - uint8_t targetMac[8]; -} __packed; - -#define BLOCK_PART_DATA_SIZE 99 -#define BLOCK_MAX_PARTS 42 -#define BLOCK_DATA_SIZE 4096UL -#define BLOCK_XFER_BUFFER_SIZE BLOCK_DATA_SIZE + sizeof(struct blockData) -#define BLOCK_REQ_PARTS_BYTES 6 - -struct blockRequest { - uint8_t checksum; - uint64_t ver; - uint8_t blockId; - uint8_t type; - uint8_t requestedParts[BLOCK_REQ_PARTS_BYTES]; -} __packed; - -struct blockRequestAck { - uint8_t checksum; - uint16_t pleaseWaitMs; -} __packed; +#include "powermgt.h" #define TIMER_TICKS_PER_MS 1333UL // #define DEBUGBLOCKS @@ -131,7 +37,7 @@ uint16_t __xdata dataRemaining = 0; bool __xdata curXferComplete = false; bool __xdata requestPartialBlock = false; -//uint8_t __xdata *tempBuffer = blockXferBuffer; +// uint8_t __xdata *tempBuffer = blockXferBuffer; uint8_t __xdata curImgSlot = 0; uint32_t __xdata curHighSlotId = 0; uint8_t __xdata nextImgSlot = 0; @@ -149,24 +55,6 @@ uint16_t __xdata APsrcPan = 0; uint8_t __xdata mSelfMac[8] = {0}; uint8_t __xdata seq = 0; -// power saving algorithm -#define INTERVAL_BASE 40 // interval (in seconds) (when 1 packet is sent/received) for target current (7.2µA) -#define INTERVAL_AT_MAX_ATTEMPTS 600 // interval (in seconds) (at max attempts) for target average current -#define INTERVAL_NO_SIGNAL 1800 // interval (in seconds) when no answer for POWER_SAVING_SMOOTHING attempts, - // (INTERVAL_AT_MAX_ATTEMPTS * POWER_SAVING_SMOOTHING) seconds -#define DATA_REQ_RX_WINDOW_SIZE 5UL // How many milliseconds we should wait for a packet during the data_request. - // If the AP holds a long list of data for tags, it may need a little more time to lookup the mac address -#define DATA_REQ_MAX_ATTEMPTS 14 // How many attempts (at most) we should do to get something back from the AP -#define POWER_SAVING_SMOOTHING 8 // How many samples we should use to smooth the data request interval -#define MINIMUM_INTERVAL 45 // IMPORTANT: Minimum interval for check-in; this determines overal battery life! - -#define HAS_BUTTON // uncomment to enable reading a push button (connect between 'TEST' en 'GND' on the tag, along with a 100nF capacitor in parallel). - -uint16_t __xdata dataReqAttemptArr[POWER_SAVING_SMOOTHING] = {0}; // Holds the amount of attempts required per data_req/check-in -uint8_t __xdata dataReqAttemptArrayIndex = 0; -uint8_t __xdata dataReqLastAttempt = 0; -uint16_t __xdata nextCheckInFromAP = 0; - // buffer we use to prepare/read packets // static uint8_t __xdata mRxBuf[130]; static uint8_t __xdata inBuffer[128] = {0}; @@ -224,78 +112,6 @@ void addCRC(void *p, uint8_t len) { ((uint8_t *)p)[0] = total; } -// init/sleep -void initRadio() { - radioInit(); - radioRxFilterCfg(mSelfMac, 0x10000, PROTO_PAN_ID); - radioSetChannel(RADIO_FIRST_CHANNEL); - radioSetTxPower(10); -} -void killRadio() { - radioRxEnable(false, true); - RADIO_IRQ4_pending = 0; - UNK_C1 &= ~0x81; - TCON &= ~0x20; - uint8_t __xdata cfgPg = CFGPAGE; - CFGPAGE = 4; - RADIO_command = 0xCA; - RADIO_command = 0xC5; - CFGPAGE = cfgPg; -} -void initAfterWake() { - clockingAndIntsInit(); - timerInit(); - // partialInit(); - boardInit(); - epdEnterSleep(); - irqsOn(); - boardInitStage2(); - initRadio(); -} -void doSleep(uint32_t __xdata t) { - if (t > 1000) pr("s=%lu\n ", t / 1000); - powerPortsDownForSleep(); - -#ifdef HAS_BUTTON - // Button setup on TEST pin 1.0 (input pullup) - P1FUNC &= ~(1 << 0); - P1DIR |= (1 << 0); - P1PULL |= (1 << 0); - P1LVLSEL |= (1 << 0); - P1INTEN = (1 << 0); - P1CHSTA &= ~(1 << 0); -#endif - - // sleepy - sleepForMsec(t); - -#ifdef HAS_BUTTON - P1INTEN = 0; -#endif - - initAfterWake(); -} -uint16_t getNextSleep() { - uint16_t __xdata curval = INTERVAL_AT_MAX_ATTEMPTS - INTERVAL_BASE; - curval *= dataReqLastAttempt; - curval /= DATA_REQ_MAX_ATTEMPTS; - curval += INTERVAL_BASE; - dataReqAttemptArr[dataReqAttemptArrayIndex % POWER_SAVING_SMOOTHING] = curval; - dataReqAttemptArrayIndex++; - - uint16_t avg = 0; - bool noNetwork = true; - for (uint8_t c = 0; c < POWER_SAVING_SMOOTHING; c++) { - avg += dataReqAttemptArr[c]; - if (dataReqAttemptArr[c] != INTERVAL_AT_MAX_ATTEMPTS) { - noNetwork = false; - } - } - if (noNetwork == true) return INTERVAL_NO_SIGNAL; - avg /= POWER_SAVING_SMOOTHING; - return avg; -} - // data xfer stuff void sendAvailDataReq() { struct MacFrameBcast __xdata *txframe = (struct MacFrameBcast *)(outBuffer + 1); @@ -835,86 +651,7 @@ bool doDataDownload(struct AvailDataInfo *__xdata avail) { return true; } -// main loop; -void mainProtocolLoop(void) { - clockingAndIntsInit(); - timerInit(); - boardInit(); - - if (!boardGetOwnMac(mSelfMac)) { - pr("failed to get MAC. Aborting\n"); - while (1) - ; - } else { - /* - for (uint8_t c = 0; c < 8; c++) { - mSelfMac[c] = c + 5; - } - */ - // really... if I do the call below, it'll cost me 8 bytes IRAM. Not the kind of 'optimization' I ever dreamed of doing - // pr("MAC>%02X%02X%02X%02X%02X%02X%02X%02X\n", mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3], mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7]); - 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]); - } - - irqsOn(); - boardInitStage2(); - - pr("BOOTED> (UI 0.03-1)\n\n"); - - if (!eepromInit()) { - pr("failed to init eeprom\n"); - while (1) - ; - } else { - getNumSlots(); - curHighSlotId = getHighSlotId(); - } - - // initialize attempt-array with the default value; - for (uint8_t c = 0; c < POWER_SAVING_SMOOTHING; c++) { - dataReqAttemptArr[c] = INTERVAL_BASE; - } - - // show the splashscreen - showSplashScreen(); - - epdEnterSleep(); - eepromDeepPowerDown(); - initRadio(); - - P1CHSTA &= ~(1 << 0); - - while (1) { - radioRxEnable(true, true); - - struct AvailDataInfo *__xdata avail = getAvailDataInfo(); - if (avail == NULL) { - // no data :( - nextCheckInFromAP = 0; // let the power-saving algorithm determine the next sleep period - } else { - nextCheckInFromAP = avail->nextCheckIn; - // got some data from the AP! - if (avail->dataType != DATATYPE_NOUPDATE) { - // data transfer - if (doDataDownload(avail)) { - // succesful transfer, next wake time is determined by the NextCheckin; - } else { - // failed transfer, let the algorithm determine next sleep interval (not the AP) - nextCheckInFromAP = 0; - } - } else { - // no data transfer, just sleep. - } - } - - // if the AP told us to sleep for a specific period, do so. - if (nextCheckInFromAP) { - doSleep(nextCheckInFromAP * 60000UL); - } else { - doSleep(getNextSleep() * 1000UL); - } - } +void initializeProto() { + getNumSlots(); + curHighSlotId = getHighSlotId(); } \ No newline at end of file diff --git a/tag_fw/syncedproto.h b/tag_fw/syncedproto.h index 718babfb..12689232 100644 --- a/tag_fw/syncedproto.h +++ b/tag_fw/syncedproto.h @@ -3,8 +3,12 @@ #include -void mainProtocolLoop(void); + extern uint8_t __xdata mSelfMac[]; +extern struct AvailDataInfo *__xdata getAvailDataInfo(); +extern bool doDataDownload(struct AvailDataInfo *__xdata avail); +extern void initializeProto(); + #endif \ No newline at end of file diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index 2e0ce12f..c5a5afb4 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -18,7 +18,7 @@ extern uint8_t mSelfMac[]; -static const char __code soft_subversion[] = "-LUTs"; +static const char __code soft_subversion[] = "-refactor"; void showSplashScreen() { epdSetup(); @@ -43,7 +43,7 @@ void showSplashScreen() { epdPrintEnd(); epdPrintBegin(2, 120, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - pr("zbs29v033 0.1.4"); + pr("zbs154v033 0.1.4"); pr(soft_subversion); epdPrintEnd(); draw(); From 5089ecf3086d3633f2153c0b4a069026ef71e0a4 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Tue, 31 Jan 2023 00:19:29 +0100 Subject: [PATCH 15/18] WIP on custom LUT's, red offset fixed (maybe?) --- tag_fw/drawing.c | 3 +- tag_fw/epd.c | 133 ++++++++++++++++++++++---------------- tag_fw/epd.h | 3 +- tag_fw/lut.h | 36 ++++++++--- tag_fw/main.c | 9 +-- tag_fw/powermgt.c | 3 +- tag_fw/settings.h | 12 ++++ tag_fw/soc/zbs243/timer.h | 2 +- tag_fw/syncedproto.c | 11 +--- tag_fw/syncedproto.h | 1 - tag_fw/userinterface.c | 18 ++++-- 11 files changed, 139 insertions(+), 92 deletions(-) create mode 100644 tag_fw/settings.h diff --git a/tag_fw/drawing.c b/tag_fw/drawing.c index 47a11b63..641199a1 100644 --- a/tag_fw/drawing.c +++ b/tag_fw/drawing.c @@ -340,15 +340,14 @@ void drawImageAtAddress(uint32_t addr) { return; drawPrvLoadAndMapClut(clutAddr); - //screenTxStart(false); epdSetup(); mPassNo = 0; beginFullscreenImage(); beginWriteFramebuffer(EPD_COLOR_BLACK); drawPrvDecodeImageOnce(); endWriteFramebuffer(); - setPosXY(0, 0); mPassNo++; + beginFullscreenImage(); beginWriteFramebuffer(EPD_COLOR_RED); drawPrvDecodeImageOnce(); endWriteFramebuffer(); diff --git a/tag_fw/epd.c b/tag_fw/epd.c index 9c93a945..f4334dd4 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -64,16 +64,17 @@ P2_2 = 1; \ } while (0) -static uint8_t __xdata currentLUT = 0x00; // Current selected LUT -static bool __idata epdPr = false; // wheter or not we copy the pr("") output to the EPD -static uint8_t __xdata epdCharSize = 1; // character size, 1 or 2 (doubled) -static bool __xdata directionY = true; // print direction, X or Y (true) -static uint8_t __xdata rbuffer[32]; // used to rotate bits around -static uint16_t __xdata fontCurXpos = 0; // current X value we're working with -static uint16_t __xdata fontCurYpos = 0; // current Y value we're working with +extern void dump(uint8_t* __xdata a, uint16_t __xdata l); // remove me when done + +static bool __idata epdPr = false; // wheter or not we copy the pr("") output to the EPD +static uint8_t __xdata epdCharSize = 1; // character size, 1 or 2 (doubled) +static bool __xdata directionY = true; // print direction, X or Y (true) +static uint8_t __xdata rbuffer[32]; // used to rotate bits around +static uint16_t __xdata fontCurXpos = 0; // current X value we're working with +static uint16_t __xdata fontCurYpos = 0; // current Y value we're working with static bool __xdata isInited = false; -struct waveform __xdata waveform; +struct waveform __xdata waveform; // holds the LUT/waveform #pragma callee_saves epdBusySleep #pragma callee_saves epdBusyWait @@ -189,13 +190,12 @@ void epdEnterSleep() { P2_0 = 1; timerDelay(50); shortCommand(CMD_SOFT_RESET2); - epdBusyWait(133300); + epdBusyWait(TIMER_TICKS_PER_MS * 10); shortCommand1(CMD_ENTER_SLEEP, 0x03); isInited = false; } void epdSetup() { epdReset(); - currentLUT = 0; shortCommand1(CMD_ANALOG_BLK_CTRL, 0x54); shortCommand1(CMD_DIGITAL_BLK_CTRL, 0x3B); shortCommand2(CMD_UNKNOWN_1, 0x04, 0x63); @@ -219,7 +219,7 @@ void epdSetup() { shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); // mode 1 (i2C) // shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB9); // mode 2? shortCommand(CMD_ACTIVATION); - epdBusyWait(1333000UL); + epdBusyWait(TIMER_TICKS_PER_SECOND); isInited = true; } static uint8_t epdGetStatus() { @@ -238,11 +238,11 @@ uint16_t epdGetBattery(void) { shortCommand1(CMD_DISP_UPDATE_CTRL2, SCREEN_CMD_CLOCK_ON | SCREEN_CMD_ANALOG_ON); shortCommand(CMD_ACTIVATION); - epdBusyWait(133300); + epdBusyWait(TIMER_TICKS_PER_MS * 100); for (val = 3; val < 8; val++) { shortCommand1(CMD_SETUP_VOLT_DETECT, val); - epdBusyWait(133300); + epdBusyWait(TIMER_TICKS_PER_MS * 100); if (epdGetStatus() & 0x10) { // set if voltage is less than threshold ( == 1.9 + val / 10) voltage = 1850 + mathPrvMul8x8(val, 100); break; @@ -251,30 +251,72 @@ uint16_t epdGetBattery(void) { shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); shortCommand(CMD_ACTIVATION); - epdBusyWait(133300); + epdBusyWait(TIMER_TICKS_PER_MS * 100); if (!isInited) epdEnterSleep(); return voltage; } -void selectLUT(uint8_t lut) { - if (lut == currentLUT) return; - // lut = 1; - switch (lut) { - case 0: - shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); // mode 1? - shortCommand(CMD_ACTIVATION); - epdBusyWait(1333000UL); - break; - case 1: - commandBegin(CMD_WRITE_LUT); - for (uint8_t i = 0; i < 70; i++) - epdSend(lutorig[i]); - commandEnd(); - break; +void loadFixedTempOTPLUT() { + shortCommand1(0x18, 0x48); // external temp sensor + shortCommand2(0x1A, 0x05, 0x00); // < temp register + shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); // mode 1 (i2C) + shortCommand(CMD_ACTIVATION); + epdBusyWait(TIMER_TICKS_PER_SECOND); +} +static void sendCustomLut(uint8_t* lut, uint8_t len) { + commandBegin(CMD_WRITE_LUT); + for (uint8_t i = 0; i < len; i++) + epdSend(((uint8_t*)(lut))[i]); + commandEnd(); +} + +static void writeLut(bool lut) { + if (lut) { + commandBegin(CMD_WRITE_LUT); + for (uint8_t i = 0; i < 70; i++) + epdSend(((uint8_t*)(&waveform))[i]); + commandEnd(); + } else { + shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); // mode 1? + shortCommand(CMD_ACTIVATION); + epdBusyWait(TIMER_TICKS_PER_SECOND); } - currentLUT = lut; +} + +extern uint8_t blockXferBuffer[]; + +static void readLut() { + commandReadBegin(0x33); + uint16_t checksum = 0; + uint16_t ident = 0; + uint16_t shortl = 0; + for (uint16_t c = 0; c < 512; c++) { + //((uint8_t*)&waveform)[c] = epdReadByte(); + blockXferBuffer[c] = epdReadByte(); + } + commandReadEnd(); +} + +void selectLUT(uint8_t lut) { + if (SCREEN_WIDTH == 152) { + sendCustomLut(lut154, 100); + } else { + sendCustomLut(lut154, 70); + } + return; + readLut(); + // dump((uint8_t*)&waveform, 96); + dump(blockXferBuffer, 512); + memset(&(waveform.group[0]), 0x00, 5); + memset(&(waveform.group[1]), 0x00, 5); + memset(&(waveform.group[2]), 0x00, 5); + memset(&(waveform.group[3]), 0x00, 5); + memset(&(waveform.group[4]), 0x00, 5); + memset(&(waveform.group[5]), 0x00, 5); // slow blink + // dump((uint8_t*)&waveform, 96); + // writeLut(EPD_LOAD_CUSTOM_LUT); } void setWindowX(uint16_t start, uint16_t end) { @@ -284,8 +326,8 @@ void setWindowY(uint16_t start, uint16_t end) { commandBegin(CMD_WINDOW_Y_SIZE); epdSend((start)&0xff); epdSend((start) >> 8); - epdSend((end-1)&0xff); - epdSend((end-1) >> 8); + epdSend((end - 1) & 0xff); + epdSend((end - 1) >> 8); commandEnd(); } void setPosXY(uint16_t x, uint16_t y) { @@ -317,9 +359,9 @@ void clearScreen() { setWindowY(0, SCREEN_HEIGHT); setPosXY(0, 0); shortCommand1(CMD_WRITE_PATTERN_BW, 0x66); - epdBusyWait(133300UL); + epdBusyWait(TIMER_TICKS_PER_MS * 100); shortCommand1(CMD_WRITE_PATTERN_RED, 0x66); - epdBusyWait(133300UL); + epdBusyWait(TIMER_TICKS_PER_MS * 100); } void draw() { shortCommand1(0x22, 0xCF); @@ -340,7 +382,7 @@ void drawLineHorizontal(bool color, uint16_t x1, uint16_t x2, uint16_t y) { } else { shortCommand1(CMD_WRITE_PATTERN_BW, 0xE6); } - epdBusyWait(133300UL); + epdBusyWait(TIMER_TICKS_PER_MS * 100); } void drawLineVertical(bool color, uint16_t x, uint16_t y1, uint16_t y2) { setWindowY(y1, y2); @@ -526,6 +568,7 @@ void writeCharEPD(uint8_t c) { pushXFontBytesToEPD(0x00, 0x00); } } + // Print text to the EPD. Origin is top-left void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool color) { directionY = direction; @@ -585,26 +628,6 @@ void epdPrintEnd() { epdPr = false; } -void loadFixedTempLUT() { - shortCommand1(0x18, 0x48); - shortCommand2(0x1A, 0x05, 0x00); // < temp register - shortCommand1(CMD_DISP_UPDATE_CTRL2, 0xB1); // mode 1 (i2C) - shortCommand(CMD_ACTIVATION); - epdBusyWait(1333000UL); -} -static void readLut() { - commandReadBegin(0x33); - uint16_t checksum = 0; - uint16_t ident = 0; - uint16_t shortl = 0; - for (uint16_t c = 0; c < 76; c++) { - ((uint8_t*)&waveform)[c] = epdReadByte(); - } - commandReadEnd(); -} - -extern void dump(uint8_t* __xdata a, uint16_t __xdata l); // remove me when done - extern uint8_t __xdata blockXferBuffer[]; void readRam() { diff --git a/tag_fw/epd.h b/tag_fw/epd.h index 44300d45..5cdb9edf 100644 --- a/tag_fw/epd.h +++ b/tag_fw/epd.h @@ -11,7 +11,8 @@ #define EPD_SIZE_DOUBLE true #define EPD_COLOR_RED true #define EPD_COLOR_BLACK false - +#define EPD_LOAD_CUSTOM_LUT true +#define EPD_LOAD_OTP_LUT false #define EPD_MODE_NORMAL 0x00 #define EPD_MODE_INVERT 0x08 #define EPD_MODE_IGNORE 0x04 diff --git a/tag_fw/lut.h b/tag_fw/lut.h index 7e0f8a34..d164767a 100644 --- a/tag_fw/lut.h +++ b/tag_fw/lut.h @@ -17,15 +17,15 @@ struct group { } __packed; struct waveform { - struct lut elut[5]; - struct group egroup[7]; + struct lut lut[5]; + struct group group[7]; uint8_t gatelevel; uint8_t sourcelevel[3]; uint8_t dummyline; uint8_t gatewidth; } __packed; -static const uint8_t __code lut154[] = { +static const uint8_t __code lut1542[] = { // lut0 (KEEP) voltages 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // lut1 (W2B) voltages @@ -143,11 +143,31 @@ static const uint8_t __code lutorig[] = { 0xA0, 0x66, 0x21, 0x85, 0x2B, 0x2F, 0x00, 0x00, 0x00, 0x12, 0x48, 0x00, 0x00, 0x00, //0x04, 0x49, 0x2F, 0x2A, 0x00, - 0x0, 0x0, 0x0, 0x0, 0x0, - 0x02, 0x04, 0x01, 0x03, 0x00, // was 11 repeat - 0x01, 0x14, 0x01, 0x14, 0x00, // was 3 repeat - 0x02, 0x0A, 0x03, 0x0A, 0x00, // was 2 repeat + 0x0, 0x0, 0x0, 0x0, 0x0, // reverse + 0x02, 0x04, 0x01, 0x03, 0x00, // was 11 repeat // fast blink + 0x01, 0x14, 0x01, 0x14, 0x00, // was 3 repeat // slow pump + 0x02, 0x0A, 0x03, 0x0A, 0x00, // was 2 repeat 0x06, 0x04, 0x04, 0x20, 0x00, // was 3 rpeat 0x04, 0x04, 0x02, 0x26, 0x00, // was 3 repeat 0x00, 0x00, 0x00, 0x00, 0x0, -}; \ No newline at end of file +}; + +static const uint8_t __code lut154[] = { +0x80, 0x66, 0x96, 0x51, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, +0x10, 0x66, 0x96, 0x88, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, +0x8A, 0x66, 0x96, 0x51, 0x0B, 0x2F, 0x00, 0x00, 0x00, 0x00, +0x8A, 0x66, 0x96, 0x51, 0x0B, 0x2F, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x5A, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +0x2D, 0x55, 0x28, 0x25, 0x00, +0x02, 0x03, 0x01, 0x02, 0x00, +0x12, 0x01, 0x12, 0x01, 0x00, +0x05, 0x05, 0x02, 0x05, 0x00, +0x07, 0x01, 0x07, 0x2A, 0x00, +0x04, 0x06, 0x02, 0x2A, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x07, 0x2A, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, +0x00, 0x00, 0x00, 0x00, 0x00, +}; + + diff --git a/tag_fw/main.c b/tag_fw/main.c index 662bd6a4..eaf257b1 100644 --- a/tag_fw/main.c +++ b/tag_fw/main.c @@ -6,21 +6,18 @@ #include #include "asmUtil.h" -#include "board.h" -#include "comms.h" -#include "cpu.h" -#include "drawing.h" #include "eeprom.h" #include "printf.h" #include "proto.h" #include "radio.h" -#include "screen.h" -#include "sleep.h" #include "syncedproto.h" #include "timer.h" #include "wdt.h" #include "powermgt.h" + #include "userinterface.h" +#include "epd.h" + void mainProtocolLoop(void) { clockingAndIntsInit(); diff --git a/tag_fw/powermgt.c b/tag_fw/powermgt.c index 1ddb1e2a..37be9fd8 100644 --- a/tag_fw/powermgt.c +++ b/tag_fw/powermgt.c @@ -22,10 +22,9 @@ #include "timer.h" #include "userinterface.h" #include "wdt.h" +#include "settings.h" -#define HAS_BUTTON // uncomment to enable reading a push button (connect between 'TEST' en 'GND' on the tag, along with a 100nF capacitor in parallel). - uint16_t __xdata dataReqAttemptArr[POWER_SAVING_SMOOTHING] = {0}; // Holds the amount of attempts required per data_req/check-in uint8_t __xdata dataReqAttemptArrayIndex = 0; uint8_t __xdata dataReqLastAttempt = 0; diff --git a/tag_fw/settings.h b/tag_fw/settings.h new file mode 100644 index 00000000..763a26ef --- /dev/null +++ b/tag_fw/settings.h @@ -0,0 +1,12 @@ +#ifndef SYNCED_H +#define SYNCED_H + +#include + +#define FW_VERSION 012 // version number (max 2.5.5 :) ) +#define FW_VERSION_SUFFIX "-rf15" // suffix, like -RC1 or whatever. +#define HAS_BUTTON // uncomment to enable reading a push button (connect between 'TEST' en 'GND' on the tag, along with a 100nF capacitor in parallel). +// #define DEBUGBLOCKS // uncomment to enable extra debug information on the block transfers + + +#endif \ No newline at end of file diff --git a/tag_fw/soc/zbs243/timer.h b/tag_fw/soc/zbs243/timer.h index bce2b700..26aa216b 100644 --- a/tag_fw/soc/zbs243/timer.h +++ b/tag_fw/soc/zbs243/timer.h @@ -4,7 +4,7 @@ #include #define TIMER_TICKS_PER_SECOND (16000000 / 12) //overflows every 53 minutes - +#define TIMER_TICKS_PER_MS 1333UL //this is a requirement by SDCC. is this prototype is missing when compiling main(), we get no irq handler void T0_ISR(void) __interrupt (1); diff --git a/tag_fw/syncedproto.c b/tag_fw/syncedproto.c index b9cc6665..6a3b6900 100644 --- a/tag_fw/syncedproto.c +++ b/tag_fw/syncedproto.c @@ -1,32 +1,26 @@ #define __packed #include "syncedproto.h" - #include #include #include #include #include - #include "asmUtil.h" -#include "board.h" -#include "comms.h" #include "cpu.h" #include "drawing.h" #include "eeprom.h" -#include "epd.h" #include "i2c.h" #include "powermgt.h" #include "printf.h" #include "proto.h" +#include "comms.h" #include "radio.h" #include "sleep.h" #include "timer.h" #include "userinterface.h" #include "wdt.h" #include "powermgt.h" - -#define TIMER_TICKS_PER_MS 1333UL -// #define DEBUGBLOCKS +#include "settings.h" // download-stuff bool __xdata dataPending = true; @@ -389,7 +383,6 @@ uint32_t getHighSlotId() { return temp; } -// #define DEBUGBLOCKS // Main download function bool doDataDownload(struct AvailDataInfo *__xdata avail) { // this is the main function for the download process diff --git a/tag_fw/syncedproto.h b/tag_fw/syncedproto.h index 12689232..dc0278df 100644 --- a/tag_fw/syncedproto.h +++ b/tag_fw/syncedproto.h @@ -5,7 +5,6 @@ extern uint8_t __xdata mSelfMac[]; - extern struct AvailDataInfo *__xdata getAvailDataInfo(); extern bool doDataDownload(struct AvailDataInfo *__xdata avail); extern void initializeProto(); diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index c5a5afb4..4edfcc6b 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -15,19 +15,21 @@ #include "sleep.h" #include "spi.h" #include "timer.h" +#include "settings.h" extern uint8_t mSelfMac[]; -static const char __code soft_subversion[] = "-refactor"; +static const uint8_t __code fwVersion = FW_VERSION; +static const char __code fwVersionSuffix[] = FW_VERSION_SUFFIX; void showSplashScreen() { epdSetup(); #if (SCREEN_WIDTH == 152) // 1.54" - // selectLUT(1); + clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); - + selectLUT(1); epdPrintBegin(12, 2, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); pr("Starting!"); epdPrintEnd(); @@ -43,8 +45,7 @@ void showSplashScreen() { epdPrintEnd(); epdPrintBegin(2, 120, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - pr("zbs154v033 0.1.4"); - pr(soft_subversion); + pr("zbs154v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); epdPrintEnd(); draw(); #endif @@ -67,8 +68,7 @@ void showSplashScreen() { epdPrintEnd(); epdPrintBegin(96, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - pr("zbs29v033 0.1.4"); - pr(soft_subversion); + pr("zbs29v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); epdPrintEnd(); loadRawBitmap(solum, 0, 0, EPD_COLOR_BLACK); @@ -96,6 +96,10 @@ void showSplashScreen() { pr("BootingX!"); epdPrintEnd(); + + epdPrintBegin(16, 252, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + pr("zbs42v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); + epdPrintEnd(); epdPrintBegin(16, 284, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_RED); pr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); From 849fbc44fd4fa24ec15a6f73cf4848927dfabc23 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Tue, 31 Jan 2023 11:46:16 +0100 Subject: [PATCH 16/18] cleanup, rewrote epd-print method --- tag_fw/board/zbs154v033/board.h | 27 +- tag_fw/board/zbs154v033/screen.h | 4 +- tag_fw/board/zbs29v033/board.h | 27 +- tag_fw/board/zbs29v033/screen.h | 25 +- tag_fw/board/zbs42v033/board.h | 27 +- tag_fw/board/zbs42v033/screen.h | 27 +- tag_fw/comms.h | 4 +- tag_fw/cpu/8051/printf.c | 1550 +++++++++++++++--------------- tag_fw/epd.c | 9 +- tag_fw/epd.h | 4 +- tag_fw/printf.h | 3 + tag_fw/soc/radioCommon.h | 8 +- tag_fw/soc/zbs243/uart.c | 3 - tag_fw/userinterface.c | 46 +- 14 files changed, 816 insertions(+), 948 deletions(-) diff --git a/tag_fw/board/zbs154v033/board.h b/tag_fw/board/zbs154v033/board.h index 6e9a87ee..b5683d58 100644 --- a/tag_fw/board/zbs154v033/board.h +++ b/tag_fw/board/zbs154v033/board.h @@ -4,27 +4,11 @@ #include #include "spi.h" -#include "uart.h" - -//colors for ui messages -#define UI_MSG_MAGNIFY1 1 -#define UI_MSG_MAGNIFY2 1 -#define UI_MSG_MAGNIFY3 1 -#define UI_MSG_BACK_COLOR 4 -#define UI_MSG_FORE_COLOR_1 0 -#define UI_MSG_FORE_COLOR_2 5 -#define UI_MSG_FORE_COLOR_3 5 -#define UI_BARCODE_VERTICAL #define eepromByte spiByte #define eepromPrvSelect() do { __asm__("nop\nnop\nnop\n"); P1_1 = 0; __asm__("nop\nnop\nnop\n"); } while(0) #define eepromPrvDeselect() do { __asm__("nop\nnop\nnop\n"); P1_1 = 1; __asm__("nop\nnop\nnop\n"); } while(0) -//debug uart (enable only when needed, on some boards it inhibits eeprom access) -#define dbgUartOn() -#define dbgUartOff() -#define dbgUartByte uartTx - //eeprom map #define EEPROM_SETTINGS_AREA_START (0x01000UL) #define EEPROM_SETTINGS_AREA_LEN (0x03000UL) @@ -35,19 +19,10 @@ //till end of eeprom really. do not put anything after - it will be erased at pairing time!!! #define EEPROM_PROGRESS_BYTES (128) -//radio cfg -#define RADIO_FIRST_CHANNEL (11) //2.4-GHz channels start at 11 -#define RADIO_NUM_CHANNELS (1) - //hw types #define HW_TYPE_NORMAL HW_TYPE_154_INCH_ZBS_033 -#define HW_TYPE_CYCLING HW_TYPE_154_INCH_ZBS_033_FRAME_MODE - - - #include "../boardCommon.h" - -#endif +#endif \ No newline at end of file diff --git a/tag_fw/board/zbs154v033/screen.h b/tag_fw/board/zbs154v033/screen.h index 2dd805b3..034fd430 100644 --- a/tag_fw/board/zbs154v033/screen.h +++ b/tag_fw/board/zbs154v033/screen.h @@ -22,6 +22,4 @@ #define SCREEN_DATA_PASSES 2 -#endif - - +#endif \ No newline at end of file diff --git a/tag_fw/board/zbs29v033/board.h b/tag_fw/board/zbs29v033/board.h index 8951cd17..e63b5e11 100644 --- a/tag_fw/board/zbs29v033/board.h +++ b/tag_fw/board/zbs29v033/board.h @@ -4,27 +4,11 @@ #include #include "spi.h" -#include "uart.h" - -//colors for ui messages -#define UI_MSG_MAGNIFY1 1 -#define UI_MSG_MAGNIFY2 1 -#define UI_MSG_MAGNIFY3 1 -#define UI_MSG_BACK_COLOR 4 -#define UI_MSG_FORE_COLOR_1 0 -#define UI_MSG_FORE_COLOR_2 5 -#define UI_MSG_FORE_COLOR_3 5 -#define UI_BARCODE_VERTICAL #define eepromByte spiByte #define eepromPrvSelect() do { __asm__("nop\nnop\nnop\n"); P1_1 = 0; __asm__("nop\nnop\nnop\n"); } while(0) #define eepromPrvDeselect() do { __asm__("nop\nnop\nnop\n"); P1_1 = 1; __asm__("nop\nnop\nnop\n"); } while(0) -//debug uart (enable only when needed, on some boards it inhibits eeprom access) -#define dbgUartOn() -#define dbgUartOff() -#define dbgUartByte uartTx - //eeprom map #define EEPROM_SETTINGS_AREA_START (0x01000UL) #define EEPROM_SETTINGS_AREA_LEN (0x03000UL) @@ -35,19 +19,10 @@ //till end of eeprom really. do not put anything after - it will be erased at pairing time!!! #define EEPROM_PROGRESS_BYTES (128) -//radio cfg -#define RADIO_FIRST_CHANNEL (11) //2.4-GHz channels start at 11 -#define RADIO_NUM_CHANNELS (1) - //hw types #define HW_TYPE_NORMAL HW_TYPE_29_INCH_ZBS_026 -#define HW_TYPE_CYCLING HW_TYPE_29_INCH_ZBS_026_FRAME_MODE - - - #include "../boardCommon.h" - -#endif +#endif \ No newline at end of file diff --git a/tag_fw/board/zbs29v033/screen.h b/tag_fw/board/zbs29v033/screen.h index 576238d5..26f70666 100644 --- a/tag_fw/board/zbs29v033/screen.h +++ b/tag_fw/board/zbs29v033/screen.h @@ -4,12 +4,6 @@ #include #include - -//i hate globals, but for 8051 this makes life a lot easier, sorry :( -extern uint8_t __xdata mScreenVcom; -extern int8_t __xdata mCurTemperature; - - #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 296 @@ -27,22 +21,5 @@ extern int8_t __xdata mCurTemperature; #define SCREEN_DATA_PASSES 2 -void screenShutdown(void); - -void screenTest(void); - -__bit screenTxStart(__bit forPartial); - -void screenEndPass(void); //at end of each pass - -#pragma callee_saves screenByteTx -void screenByteTx(uint8_t byte); -void screenTxEnd(void); - -void screenSleep(void); - -extern uint8_t __xdata mScreenRow[]; //320 bytes used as temp by many on cc where memory is tight - -#endif - +#endif \ No newline at end of file diff --git a/tag_fw/board/zbs42v033/board.h b/tag_fw/board/zbs42v033/board.h index b23787ee..0cf71379 100644 --- a/tag_fw/board/zbs42v033/board.h +++ b/tag_fw/board/zbs42v033/board.h @@ -4,27 +4,11 @@ #include #include "spi.h" -#include "uart.h" - -//colors for ui messages -#define UI_MSG_MAGNIFY1 1 -#define UI_MSG_MAGNIFY2 1 -#define UI_MSG_MAGNIFY3 1 -#define UI_MSG_BACK_COLOR 4 -#define UI_MSG_FORE_COLOR_1 0 -#define UI_MSG_FORE_COLOR_2 5 -#define UI_MSG_FORE_COLOR_3 5 -#define UI_BARCODE_VERTICAL #define eepromByte spiByte #define eepromPrvSelect() do { __asm__("nop\nnop\nnop\n"); P1_1 = 0; __asm__("nop\nnop\nnop\n"); } while(0) #define eepromPrvDeselect() do { __asm__("nop\nnop\nnop\n"); P1_1 = 1; __asm__("nop\nnop\nnop\n"); } while(0) -//debug uart (enable only when needed, on some boards it inhibits eeprom access) -#define dbgUartOn() -#define dbgUartOff() -#define dbgUartByte uartTx - //eeprom map #define EEPROM_SETTINGS_AREA_START (0x01000UL) #define EEPROM_SETTINGS_AREA_LEN (0x03000UL) @@ -35,19 +19,10 @@ //till end of eeprom really. do not put anything after - it will be erased at pairing time!!! #define EEPROM_PROGRESS_BYTES (128) -//radio cfg -#define RADIO_FIRST_CHANNEL (11) //2.4-GHz channels start at 11 -#define RADIO_NUM_CHANNELS (1) - //hw types #define HW_TYPE_NORMAL HW_TYPE_42_INCH_ZBS_026 -#define HW_TYPE_CYCLING HW_TYPE_42_INCH_ZBS_026_FRAME_MODE - - - #include "../boardCommon.h" - -#endif +#endif \ No newline at end of file diff --git a/tag_fw/board/zbs42v033/screen.h b/tag_fw/board/zbs42v033/screen.h index e02a55d8..96501a0e 100644 --- a/tag_fw/board/zbs42v033/screen.h +++ b/tag_fw/board/zbs42v033/screen.h @@ -4,12 +4,6 @@ #include #include - -//i hate globals, but for 8051 this makes life a lot easier, sorry :( -extern uint8_t __xdata mScreenVcom; -extern int8_t __xdata mCurTemperature; - - #define SCREEN_WIDTH 400 #define SCREEN_HEIGHT 300 @@ -27,24 +21,5 @@ extern int8_t __xdata mCurTemperature; #define SCREEN_DATA_PASSES 2 -void screenShutdown(void); - - -void screenTest(void); - -__bit screenTxStart(__bit forPartial); - -void screenEndPass(void); //at end of each pass - -#pragma callee_saves screenByteTx -void screenByteTx(uint8_t byte); -void screenTxEnd(void); - -void screenSleep(void); - - -extern uint8_t __xdata mScreenRow[]; //320 bytes used as temp by many on cc where memory is tight - -#endif - +#endif \ No newline at end of file diff --git a/tag_fw/comms.h b/tag_fw/comms.h index 7c1c67b9..1753c45a 100644 --- a/tag_fw/comms.h +++ b/tag_fw/comms.h @@ -3,13 +3,11 @@ #include -#define COMMS_MAX_RADIO_WAIT_MSEC 200 -#define COMMS_IV_SIZE (4) //zeroes except these 4 counter bytes +#define COMMS_MAX_RADIO_WAIT_MSEC 200 #define COMMS_RX_ERR_NO_PACKETS (-1) #define COMMS_RX_ERR_INVALID_PACKET (-2) -#define COMMS_RX_ERR_MIC_FAIL (-3) #define COMMS_MAX_PACKET_SZ (127) diff --git a/tag_fw/cpu/8051/printf.c b/tag_fw/cpu/8051/printf.c index 93246c9c..be6dc10c 100644 --- a/tag_fw/cpu/8051/printf.c +++ b/tag_fw/cpu/8051/printf.c @@ -1,795 +1,799 @@ -#include -#include -#include #include "printf.h" -#include "zbs243.h" + +#include +#include +#include + #include "board.h" +#include "epd.h" +#include "uart.h" +#include "zbs243.h" typedef void (*StrFormatOutputFunc)(uint32_t param /* low byte is data, bits 24..31 is char */) __reentrant; -static __idata __at (0x00) unsigned char R0; -static __idata __at (0x01) unsigned char R1; -static __idata __at (0x02) unsigned char R2; -static __idata __at (0x03) unsigned char R3; -static __idata __at (0x04) unsigned char R4; -static __idata __at (0x05) unsigned char R5; -static __idata __at (0x06) unsigned char R6; -static __idata __at (0x07) unsigned char R7; +static __idata __at(0x00) unsigned char R0; +static __idata __at(0x01) unsigned char R1; +static __idata __at(0x02) unsigned char R2; +static __idata __at(0x03) unsigned char R3; +static __idata __at(0x04) unsigned char R4; +static __idata __at(0x05) unsigned char R5; +static __idata __at(0x06) unsigned char R6; +static __idata __at(0x07) unsigned char R7; static uint8_t __xdata mCvtBuf[18]; - -//callback must be reentrant and callee_saves +// callback must be reentrant and callee_saves #pragma callee_saves prvPrintFormat -void prvPrintFormat(StrFormatOutputFunc formatF, uint16_t formatD, const char __code *fmt, va_list vl) __reentrant __naked -{ - //formatF is in DPTR - //sp[0..-1] is return addr - //sp[-2..-3] is formatD - //sp[-4..-5] is fmt - //sp[-6] is vl - - __asm__ ( - " push _R7 \n" - " push DPH \n" //push formatF - " push DPL \n" - " mov _R7, sp \n" //save place on stack where we stashed it so we can call it easily - " push _R4 \n" - " push _R3 \n" - " push _R2 \n" - " push _R1 \n" - " push _R0 \n" - - " mov A, #-12 \n" - " add A, sp \n" - " mov R0, A \n" - //R0 now points to pushed params, for large values, we see high bytes first - // to get next byte, we need to DECEREMENT R0 - - " mov DPH, @R0 \n" - " dec R0 \n" - " mov DPL, @R0 \n" - " dec R0 \n" - " mov _R0, @R0 \n" - " dec R0 \n" - - //now format string is in DPTR, and R0 points to the top byte of whatever was in the first param - - //main loop: get a byte of the format string - "00001$: \n" - " clr A \n" - " movc A, @A + DPTR \n" - " inc DPTR \n" - //if zero, we're done - " jz 00098$ \n" - //if not '%', print it - " cjne A, #'%', 00097$ \n" - - //we got a percent sign - init state for format processing - " mov R4, #0 \n" //bit flags: - // 0x01 = '*' = pointer provided instead of value (integers only) - // 0x02 = '0' = zero-pad (for numbers only) - // 0x04 = have pad-to length - // 0x08 = long - // 0x10 = long long - // 0x20 = signed print requested. also: need to print a negative (used to reuse hex printing for decimal once converted to bcd) - " mov R2, #0 \n" //padLen - - //loop for format string ingestion - "00002$: \n" - " clr A \n" - " movc A, @A + DPTR \n" - " inc DPTR \n" - //if zero, we're done - " jz 00098$ \n" - //check for percent sign - " cjne A, #'%', 00003$ \n" - //fallthrough to print it and go read next non-format byte - //print a char in A, go read next format byte - "00097$: \n" - " lcall 00060$ \n" - " sjmp 00001$ \n" - - //exit label - placed for easy jumping to - "00098$: \n" - " pop _R0 \n" - " pop _R1 \n" - " pop _R2 \n" - " pop _R3 \n" - " pop _R4 \n" - " pop DPL \n" - " pop DPH \n" - " pop _R7 \n" - " ret \n" - - //continue to process format string - handle %c - "00003$: \n" - " cjne A, #'c', 00004$ \n" - " dec R0 \n" //param is pushed as int (16 bits) - " mov A, @R0 \n" - " dec R0 \n" - " sjmp 00097$ \n" //print and go read next non-format byte - - //continue to process format string - handle %m - "00004$: \n" - " mov R3, A \n" - " orl A, #0x20 \n" - " cjne A, #'m', 00008$ \n" - - //sort out which hexch charset to use - " mov A, R3 \n" - " anl A, #0x20 \n" - " rr A \n" - " mov R1, A \n" - - //go, do - " push DPH \n" - " push DPL \n" - " lcall 00090$ \n" //read the short (__xdata) pointer - >DPTR - " mov R4, #8 \n" //byteSel - "00005$: \n" - " push DPH \n" - " push DPL \n" - " mov A, R4 \n" - " dec A \n" - " add A, DPL \n" - " mov DPL, A \n" - " mov A, DPH \n" - " addc A, #0 \n" - " mov DPH, A \n" - " movx A, @DPTR \n" - " mov R2, A \n" - " swap A \n" - " mov R3, #2 \n" - "00006$: \n" - " anl A, #0x0f \n" - " add A, R1 \n" - " mov DPTR, #00099$ \n" - " movc A, @A + DPTR \n" - " lcall 00060$ \n" - " mov A, R2 \n" - " djnz R3, 00006$ \n" - " pop DPL \n" - " pop DPH \n" - " djnz R4, 00007$ \n" - //done with mac addr - - "00055$: \n" - " pop DPL \n" - " pop DPH \n" - " sjmp 00001$ \n" - //print colon and contimue mac addr printing - "00007$: \n" - " mov A, #':' \n" - " lcall 00060$ \n" - " sjmp 00005$ \n" - - //continue to process format string - handle '*' - "00008$: \n" - " mov A, R3 \n" - " cjne A, #'*', 00009$ \n" - " cjne R2, #0, 00097$ \n" //only valid when no length/padding has been specified yet, else invalid specifier - " mov A, #0x01 \n" //"pointer mode" - "00010$: \n" - " orl A, R4 \n" - " mov R4, A \n" - " sjmp 00002$ \n" //get next format specifier now - - //continue to process format string - handle '0' - "00009$: \n" - " cjne A, #'0', 00011$ \n" - " cjne R2, #0, 00011$ \n" //setting "zero pad" is only valid when pad length is zero - " mov A, #0x06 \n" //"have pad length" | "zero-pad" - " sjmp 00010$ \n" //orr A into R4, get next format specifier now - - //continue to process format string - handle '1'...'9' - "00011$: \n" - " mov R3, A \n" - " add A, #-'0' \n" - " jnc 00012$ \n" //now 0..9 are valid - " add A, #-10 \n" - " jc 00012$ \n" - " add A, #10 \n" //get it back into 1..9 range - " mov R3, A \n" - " mov A, #10 \n" - " mov B, R2 \n" - " mul AB \n" - " add A, R3 \n" - " mov R2, A \n" - " mov A, #0x04 \n" //"have pad length" - " sjmp 00010$ \n" //orr A into R4, get next format specifier now - - //continue to process format string - handle 'l' - "00012$: \n" - " cjne R3, #'l', 00014$ \n" - " mov A, R4 \n" - " anl A, #0x08 \n" - " jz 00013$ \n" //no "long" yet? set that - //have long - set long log - " mov A, #0x10 \n" //"long long" - " sjmp 00010$ \n" //orr A into R4, get next format specifier now - //first 'l' - set long - "00013$: \n" - " mov A, #0x08 \n" //"long" - " sjmp 00010$ \n" //orr A into R4, get next format specifier now - - //continue to process format string - handle 's' - "00014$: \n" - " cjne R3, #'s', 00025$ \n" - " mov A, R4 \n" - " anl A, #0x08 \n" - " push DPH \n" - " push DPL \n" - " jnz 00015$ \n" - " lcall 00091$ \n" //get and resolve generic pointer into DPTR - " sjmp 00016$ \n" - "00015$: \n" //get short pointer into DPTR, record that it is to XRAM - " clr PSW.5 \n" - " clr PSW.1 \n" - " lcall 00090$ \n" - "00016$: \n" //pointer to string now in DPTR - //we have the string pointer in {DPTR,PSW}, let's see if we have padding to do - " mov A, R4 \n" - " anl A, #0x04 \n" - " jnz 00018$ \n" - //print string with no length restrictions - "00017$: \n" - " lcall 00095$ \n" - " jz 00055$ \n" - " lcall 00060$ \n" - " sjmp 00017$ \n" - - //print string with length restrictions and/or padding - "00018$: \n" - " cjne R2, #0, 00019$ \n" //verify reqested len was not zero - " sjmp 00055$ \n" - - "00019$: \n" - " lcall 00095$ \n" - " jz 00020$ \n" - " lcall 00060$ \n" - " djnz R2, 00019$ \n" - //we get here if we ran out of allowable bytes - we're done then - " ljmp 00055$ \n" - - //just a trampoline for range issues - "00035$: \n" - " ljmp 00036$ \n" - - //we need to pad with spaces - "00020$: \n" - " mov A, #' ' \n" - " lcall 00060$ \n" - " djnz R2, 00020$ \n" - " ljmp 00055$ \n" - - //continue to process format string - handle 'x'/'X' - "00025$: \n" - " mov A, R3 \n" - " orl A, #0x20 \n" - " cjne A, #'x', 00035$ \n" - " push DPH \n" - " push DPL \n" - " lcall 00080$ \n" //get pointer to the number in DPTR, length in bytes in B - //save it - - "00070$: \n" - " push DPH \n" - " push DPL \n" - - //sort out how long it would be if printed, first get a pointer to the highest - " mov A, B \n" - " rl A \n" - " mov R1, A \n" - " rr A \n" - " add A, #0xff \n" - " add A, DPL \n" - " mov DPL, A \n" - " mov A, DPH \n" - " addc A, #0x00 \n" - " mov DPH, A \n" - "00026$: \n" - " lcall 00079$ \n" - " anl A, #0xf0 \n" - " jnz 00028$ \n" - " dec R1 \n" - " lcall 00079$ \n" - " jnz 00028$ \n" - " dec R1 \n" - //dec DPTR - " dec DPL \n" - " mov A, DPL \n" - " cjne A, #0xff, 00027$ \n" - " dec DPH \n" - "00027$: \n" - " djnz B, 00026$ \n" - - //we now know how many digits the number is (in R1), except that it has "0" if the number if zero, we cannot have that - "00028$: \n" - " cjne R1, #0, 00029$ \n" - " inc R1 \n" - "00029$: \n" //we now finally have the full length of the digits - - //if the number is negative (happens when we're printing decimals) - // the length of it is one more, also in case of zero-padding, we need to print the minus sign here now - " mov A, R4 \n" - " anl A, #0x20 \n" - " jz 00051$ \n" - " inc R1 \n" //the length is one more - " mov A, R4 \n" - " anl A, #02 \n" //if zero-padding, the negative comes now - " jz 00051$ \n" - " mov A, #'-' \n" - " lcall 00060$ \n" - "00051$: \n" - - //sort out if we need padding at all and if there is space - " mov A, R4 \n" - " anl A, #0x04 \n" - " jz 00031$ \n" //no padding requested - //padding was requested len is in R2 - " mov A, R2 \n" - " clr C \n" - " subb A, R1 \n" - " jc 00031$ \n" //pad-to len < number_len -> no padding needed - " jz 00031$ \n" //pad-to len == number_len -> no padding needed - " mov R2, A \n" - - //sort out which character to use -> DPL - " mov A, R4 \n" //fancy way to create space/zero as needed - " anl A, #0x02 \n" - " swap A \n" - " rr A \n" - " add A, #0x20 \n" - " mov DPL, A \n" - - //pad! - "00030$: \n" - " mov A, DPL \n" - " lcall 00060$ \n" - " djnz R2, 00030$ \n" - "00031$: \n" - - //if the number is negative (happens when we're printing decimals) - // we made the length of it is one more, which we need to undo - // also in case of space-padding, we need to print the minus sign here now - " mov A, R4 \n" - " anl A, #0x20 \n" - " jz 00052$ \n" - " dec R1 \n" //the length is one less than we had increased it to - " mov A, R4 \n" - " anl A, #02 \n" //if space-padding, the negative comes now - " jnz 00052$ \n" - " mov A, #'-' \n" - " lcall 00060$ \n" - "00052$: \n" - - //time to print the number itself - //sort out which hexch charset to use -> R2 - " mov A, R3 \n" - " anl A, #0x20 \n" - " rr A \n" - " mov R2, A \n" - //re-get the number pointer - " pop DPL \n" - " pop DPH \n" - //currently DPTR points to the number low byte, R1 is now many digits we expect to print, R2 is the charset selection, R4 and R3 are free - //let's calculate how many bytes we expect to process -> R4 - " mov A, R1 \n" - " inc A \n" - " clr C \n" - " rrc A \n" - " mov R4, A \n" - //let's repoint DPTR to the first byte we'll print in (remember we print 2 digits per byte) - " dec A \n" - " add A, DPL \n" - " mov DPL, A \n" - " mov A, DPH \n" - " addc A, #0x00 \n" - " mov DPH, A \n" - - //decide if we need to print just a nibble of the high byte or the whole thing. Free up R1 - " mov A, R1 \n" - " anl A, #0x01 \n" - " jz 00032$ \n" - - //we're printing just the low nibble of the first byte - set up for it - " lcall 00079$ \n" - " mov R1, #1 \n" - " sjmp 00033$ \n" - - //print loop - "00032$: \n" - " lcall 00079$ \n" - " mov R1, #2 \n" - " mov R3, A \n" - " swap A \n" - "00033$: \n" - " anl A, #0x0f \n" - " add A, R2 \n" - " push DPH \n" - " push DPL \n" - " mov DPTR, #00099$ \n" - " movc A, @A + DPTR \n" - " pop DPL \n" - " pop DPH \n" - " lcall 00060$ \n" - " mov A, R3 \n" - " djnz R1, 00033$ \n" - - //dec DPTR - " dec DPL \n" - " mov A, DPL \n" - " cjne A, #0xff, 00034$ \n" - " dec DPH \n" - "00034$: \n" - " djnz R4, 00032$ \n" - - //done! - " ljmp 00055$ \n" - - //continue to process format string - handle 'd' - "00036$: \n" - " cjne R3, #'d', 00037$ \n" - " mov A, #0x20 \n" - " orl A, R4 \n" - " mov R4, A \n" - " sjmp 00040$ \n" - - //continue to process format string - handle 'u' - "00037$: \n" - " cjne R3, #'u', 00038$ \n" - " sjmp 00040$ \n" - - //no more format strings exist that we can handle - bail - "00038$: \n" - " ljmp 00001$ \n" - - //handle decimal printing - "00040$: \n" - " push DPH \n" - " push DPL \n" - " lcall 00080$ \n" //get pointer to the number in DPTR, length in bytes in B - " push B \n" - - //copy the number to the double-dabble storage at proper offset (0 for u64, 4 for u32, 6 for u16) - //we do this so that the dabble area always starts at the same place... - " mov A, #8 \n" - " clr C \n" - " subb A, B \n" - " add A, #_mCvtBuf \n" - " mov R1, A \n" - " clr A \n" - " addc A, #(_mCvtBuf >> 8) \n" - " mov R3, A \n" - "00041$: \n" - " lcall 00079$ \n" - " inc DPTR \n" - " lcall 00086$ \n" - " movx @DPTR, A \n" - " inc DPTR \n" - " lcall 00086$ \n" - " djnz B, 00041$ \n" - //leave DPTR pointing to dabble storage, past the number - " lcall 00086$ \n" - - //we now have the top byte of the number in A, good time to check for negatives, if needed - " mov B, A \n" - " mov A, R4 \n" - " anl A, #0x20 \n" - " jz 00050$ \n" //unsigned printing requested - " mov A, B \n" - " anl A, #0x80 \n" - " jnz 00043$ \n" //is negative - we need to invert, 0x20 bit in R1 stays - //positive - 0x20 bit in R1 needs to go - " mov A, R4 \n" - " anl A, #~0x20 \n" - " mov R4, A \n" - " sjmp 00050$ \n" - - //we need to negate the number - // but first we need a pointer to it, and its size - "00043$: \n" - " pop B \n" - " push B \n" - " mov A, #8 \n" - " clr C \n" - " subb A, B \n" - " add A, #_mCvtBuf \n" - " mov DPL, A \n" - " clr A \n" - " addc A, #(_mCvtBuf >> 8) \n" - " mov DPH, A \n" - - //ok, now we are ready to negate it - " clr C \n" - "00049$: \n" - " movx A, @DPTR \n" - " mov R1, A \n" - " clr A \n" - " subb A, R1 \n" - " movx @DPTR, A \n" - " inc DPTR \n" - " djnz B, 00049$ \n" - - //zero out the rest of the storage (10 bytes) - "00050$: \n" - " mov B, #10 \n" - " clr A \n" - "00042$: \n" - " movx @DPTR, A \n" - " inc DPTR \n" - " djnz B, 00042$ \n" - - //calculate number of dabble steps - " pop A \n" - " swap A \n" - " rr A \n" - " mov R3, A \n" - - //do the thing - "00044$: \n" - - //dabble (10 iters for simplicity) - " mov DPTR, #(_mCvtBuf + 8) \n" - " mov B, #10 \n" - "00046$: \n" - " movx A, @DPTR \n" - " mov R1, A \n" - " anl A, #0x0f \n" - " add A,#-0x05 \n" - " mov A, R1 \n" - " jnc 00047$ \n" - " add A, #0x03 \n" - "00047$: \n" - " mov R1, A \n" - " anl A, #0xf0 \n" - " add A,#-0x50 \n" - " mov A, R1 \n" - " jnc 00048$ \n" - " add A, #0x30 \n" - "00048$: \n" - " movx @DPTR, A \n" - " inc DPTR \n" - " djnz B, 00046$ \n" - - //double (18 iters for simplicity) - " mov DPTR, #_mCvtBuf \n" - " clr C \n" - " mov B, #18 \n" - "00045$: \n" - " movx A, @DPTR \n" - " rlc A \n" - " movx @DPTR, A \n" - " inc DPTR \n" - " djnz B, 00045$ \n" - - " djnz R3, 00044$ \n" - - //dabbling is done, print it now using hex routine - " mov DPTR, #(_mCvtBuf + 8) \n" - " mov B, #10 \n" - " clr PSW.5 \n" //it is now for sure in XRAM - " ljmp 00070$ \n" - - //read short pointer from param stack - "00090$: \n" - " mov DPH, @R0 \n" - "00093$: \n" - " dec R0 \n" - " mov DPL, @R0 \n" - " dec R0 \n" - " ret \n" - - //read and increment pointer of the type provided by 00091$ (in {DPTR,PSW}) into A. clobber nothing - "00095$: \n" - " jb PSW.5, 00066$ \n" - " jb PSW.1, 00067$ \n" - //XRAM - " movx A, @DPTR \n" - " inc DPTR \n" - " ret \n" - //CODE - "00066$: \n" - " clr A \n" - " movc A, @A+DPTR \n" - " inc DPTR \n" - " ret \n" - //IRAM - "00067$: \n" - " mov DPH, R0 \n" - " mov R0, DPL \n" - " mov A, @R0 \n" - " mov R0, DPH \n" - " inc DPL \n" - " ret \n" - - //resolve generic pointer on param stack to an pointer in DPTR and flags in PSW.5 and PSW.1 - //PSW.5 will be 0 and PSW.1 will be 0 for XRAM (PDATA goes here too) - //PSW.5 will be 1 and PSW.1 will be 0 for CODE - //PSW.5 will be 0 and PSW.1 will be 1 for IRAM - "00091$: \n" - " clr PSW.5 \n" - " clr PSW.1 \n" - " mov A, @R0 \n" - " dec R0 \n" - " jz 00090$ \n" //0x00: pointer type: xdata - " xrl A, #0x80 \n" - " jz 00094$ \n" //0x80: pointer type: code - " xrl A, #0xc0 \n" - " jz 00092$ \n" //0x40: pointer type: idata - //pdata - " mov DPH, _XPAGE \n" - " sjmp 00093$ \n" - //idata - "00092$: \n" - " setb PSW.1 \n" - " sjmp 00093$ \n" - //code - "00094$: \n" - " setb PSW.5 \n" - " sjmp 00090$ \n" - - //read the pointer of the type that 00080$ returns (in DPTR) into A. clobber nothing - "00079$: \n" - " jnb PSW.5, 00078$ \n" - " push _R0 \n" - " mov R0, DPL \n" - " mov A, @R0 \n" - " pop _R0 \n" - " ret \n" - "00078$: \n" - " movx A, @DPTR \n" - " ret \n" - - //get pointer to a number, might be pushed or might be pointed to, size might vary. return pointer to number's LOW byte in DPTR - "00080$: \n" - " mov A, R4 \n" - " anl A, #0x01 \n" - " jnz 00083$ \n" - //param is itself on stack - now we care about size, but either way, PSW.5 will be 1 - " setb PSW.5 \n" - " mov B, #0 \n" - " mov A, R4 \n" - " anl A, #0x18 \n" - " jz 00081$ \n" - " anl A, #0x10 \n" - " jz 00082$ \n" - //long long (8 bytes) \n" - " setb B.2 \n" - " dec R0 \n" - " dec R0 \n" - " dec R0 \n" - " dec R0 \n" - //long (4 bytes) - "00082$: \n" - " setb B.1 \n" - " dec R0 \n" - " dec R0 \n" - //int (2 bytes) \n" - "00081$: \n" - " setb B.0 \n" - " dec R0 \n" - " mov DPL, R0 \n" - " dec R0 \n" - " inc B \n" - " ret \n" - //pointer it on stack itself, number is in xram, but we still need to provide the length - "00083$: \n" - " clr PSW.5 \n" //mark as "in xram" - " mov A, R4 \n" - " anl A, #0x18 \n" - " jz 00084$ \n" - " anl A, #0x10 \n" - " jz 00085$ \n" - //long long - " mov B, #8 \n" - " ljmp 00090$ \n" - //long - "00085$: \n" - " mov B, #4 \n" - " ljmp 00090$ \n" - //int - "00084$: \n" - " mov B, #2 \n" - " ljmp 00090$ \n" +void prvPrintFormat(StrFormatOutputFunc formatF, uint16_t formatD, const char __code *fmt, va_list vl) __reentrant __naked { + // formatF is in DPTR + // sp[0..-1] is return addr + // sp[-2..-3] is formatD + // sp[-4..-5] is fmt + // sp[-6] is vl - //swap R3:R1 <-> DPH:DPL - "00086$: \n" - " xch A, DPH \n" - " xch A, R3 \n" - " xch A, DPH \n" - " xch A, DPL \n" - " xch A, R1 \n" - " xch A, DPL \n" - " ret \n" + __asm__( + " push _R7 \n" + " push DPH \n" // push formatF + " push DPL \n" + " mov _R7, sp \n" // save place on stack where we stashed it so we can call it easily + " push _R4 \n" + " push _R3 \n" + " push _R2 \n" + " push _R1 \n" + " push _R0 \n" - /* putchar func - called via call. char is in A, R7 has pointer to stack as needed - can clobber B, CANNOT clobber DPTR - a mess because...8051 - */ - "00060$: \n" - " push DPH \n" - " push DPL \n" - " push _R1 \n" - " push _R0 \n" - " mov _R0, R7 \n" - " mov DPL, @R0 \n" - " dec R0 \n" - " mov DPH, @R0 \n" //DPTR is now func ptr - " dec R0 \n" - " dec R0 \n" - " dec R0 \n" - " dec R0 \n" - " mov _R1, @R0 \n" - " dec R0 \n" - " mov _R0, @R0 \n" //R1:R0 is now "formatD" - " lcall 00061$ \n" //to set ret addr - " pop _R0 \n" - " pop _R1 \n" - " pop DPL \n" - " pop DPH \n" - " ret \n" - "00061$: \n" - " push DPL \n" - " push DPH \n" - " mov DPL, _R0 \n" - " mov DPH, _R1 \n" - " ret \n" - - "00099$: \n" - " .ascii \"01234567\" \n" - " .ascii \"89ABCDEF\" \n" - " .ascii \"01234567\" \n" - " .ascii \"89abcdef\" \n" - ); - (void)fmt; - (void)vl; - (void)formatF; - (void)formatD; + " mov A, #-12 \n" + " add A, sp \n" + " mov R0, A \n" + // R0 now points to pushed params, for large values, we see high bytes first + // to get next byte, we need to DECEREMENT R0 + + " mov DPH, @R0 \n" + " dec R0 \n" + " mov DPL, @R0 \n" + " dec R0 \n" + " mov _R0, @R0 \n" + " dec R0 \n" + + // now format string is in DPTR, and R0 points to the top byte of whatever was in the first param + + // main loop: get a byte of the format string + "00001$: \n" + " clr A \n" + " movc A, @A + DPTR \n" + " inc DPTR \n" + // if zero, we're done + " jz 00098$ \n" + // if not '%', print it + " cjne A, #'%', 00097$ \n" + + // we got a percent sign - init state for format processing + " mov R4, #0 \n" // bit flags: + // 0x01 = '*' = pointer provided instead of value (integers only) + // 0x02 = '0' = zero-pad (for numbers only) + // 0x04 = have pad-to length + // 0x08 = long + // 0x10 = long long + // 0x20 = signed print requested. also: need to print a negative (used to reuse hex printing for decimal once converted to bcd) + " mov R2, #0 \n" // padLen + + // loop for format string ingestion + "00002$: \n" + " clr A \n" + " movc A, @A + DPTR \n" + " inc DPTR \n" + // if zero, we're done + " jz 00098$ \n" + // check for percent sign + " cjne A, #'%', 00003$ \n" + // fallthrough to print it and go read next non-format byte + // print a char in A, go read next format byte + "00097$: \n" + " lcall 00060$ \n" + " sjmp 00001$ \n" + + // exit label - placed for easy jumping to + "00098$: \n" + " pop _R0 \n" + " pop _R1 \n" + " pop _R2 \n" + " pop _R3 \n" + " pop _R4 \n" + " pop DPL \n" + " pop DPH \n" + " pop _R7 \n" + " ret \n" + + // continue to process format string - handle %c + "00003$: \n" + " cjne A, #'c', 00004$ \n" + " dec R0 \n" // param is pushed as int (16 bits) + " mov A, @R0 \n" + " dec R0 \n" + " sjmp 00097$ \n" // print and go read next non-format byte + + // continue to process format string - handle %m + "00004$: \n" + " mov R3, A \n" + " orl A, #0x20 \n" + " cjne A, #'m', 00008$ \n" + + // sort out which hexch charset to use + " mov A, R3 \n" + " anl A, #0x20 \n" + " rr A \n" + " mov R1, A \n" + + // go, do + " push DPH \n" + " push DPL \n" + " lcall 00090$ \n" // read the short (__xdata) pointer - >DPTR + " mov R4, #8 \n" // byteSel + "00005$: \n" + " push DPH \n" + " push DPL \n" + " mov A, R4 \n" + " dec A \n" + " add A, DPL \n" + " mov DPL, A \n" + " mov A, DPH \n" + " addc A, #0 \n" + " mov DPH, A \n" + " movx A, @DPTR \n" + " mov R2, A \n" + " swap A \n" + " mov R3, #2 \n" + "00006$: \n" + " anl A, #0x0f \n" + " add A, R1 \n" + " mov DPTR, #00099$ \n" + " movc A, @A + DPTR \n" + " lcall 00060$ \n" + " mov A, R2 \n" + " djnz R3, 00006$ \n" + " pop DPL \n" + " pop DPH \n" + " djnz R4, 00007$ \n" + // done with mac addr + + "00055$: \n" + " pop DPL \n" + " pop DPH \n" + " sjmp 00001$ \n" + // print colon and contimue mac addr printing + "00007$: \n" + " mov A, #':' \n" + " lcall 00060$ \n" + " sjmp 00005$ \n" + + // continue to process format string - handle '*' + "00008$: \n" + " mov A, R3 \n" + " cjne A, #'*', 00009$ \n" + " cjne R2, #0, 00097$ \n" // only valid when no length/padding has been specified yet, else invalid specifier + " mov A, #0x01 \n" //"pointer mode" + "00010$: \n" + " orl A, R4 \n" + " mov R4, A \n" + " sjmp 00002$ \n" // get next format specifier now + + // continue to process format string - handle '0' + "00009$: \n" + " cjne A, #'0', 00011$ \n" + " cjne R2, #0, 00011$ \n" // setting "zero pad" is only valid when pad length is zero + " mov A, #0x06 \n" //"have pad length" | "zero-pad" + " sjmp 00010$ \n" // orr A into R4, get next format specifier now + + // continue to process format string - handle '1'...'9' + "00011$: \n" + " mov R3, A \n" + " add A, #-'0' \n" + " jnc 00012$ \n" // now 0..9 are valid + " add A, #-10 \n" + " jc 00012$ \n" + " add A, #10 \n" // get it back into 1..9 range + " mov R3, A \n" + " mov A, #10 \n" + " mov B, R2 \n" + " mul AB \n" + " add A, R3 \n" + " mov R2, A \n" + " mov A, #0x04 \n" //"have pad length" + " sjmp 00010$ \n" // orr A into R4, get next format specifier now + + // continue to process format string - handle 'l' + "00012$: \n" + " cjne R3, #'l', 00014$ \n" + " mov A, R4 \n" + " anl A, #0x08 \n" + " jz 00013$ \n" // no "long" yet? set that + // have long - set long log + " mov A, #0x10 \n" //"long long" + " sjmp 00010$ \n" // orr A into R4, get next format specifier now + // first 'l' - set long + "00013$: \n" + " mov A, #0x08 \n" //"long" + " sjmp 00010$ \n" // orr A into R4, get next format specifier now + + // continue to process format string - handle 's' + "00014$: \n" + " cjne R3, #'s', 00025$ \n" + " mov A, R4 \n" + " anl A, #0x08 \n" + " push DPH \n" + " push DPL \n" + " jnz 00015$ \n" + " lcall 00091$ \n" // get and resolve generic pointer into DPTR + " sjmp 00016$ \n" + "00015$: \n" // get short pointer into DPTR, record that it is to XRAM + " clr PSW.5 \n" + " clr PSW.1 \n" + " lcall 00090$ \n" + "00016$: \n" // pointer to string now in DPTR + // we have the string pointer in {DPTR,PSW}, let's see if we have padding to do + " mov A, R4 \n" + " anl A, #0x04 \n" + " jnz 00018$ \n" + // print string with no length restrictions + "00017$: \n" + " lcall 00095$ \n" + " jz 00055$ \n" + " lcall 00060$ \n" + " sjmp 00017$ \n" + + // print string with length restrictions and/or padding + "00018$: \n" + " cjne R2, #0, 00019$ \n" // verify reqested len was not zero + " sjmp 00055$ \n" + + "00019$: \n" + " lcall 00095$ \n" + " jz 00020$ \n" + " lcall 00060$ \n" + " djnz R2, 00019$ \n" + // we get here if we ran out of allowable bytes - we're done then + " ljmp 00055$ \n" + + // just a trampoline for range issues + "00035$: \n" + " ljmp 00036$ \n" + + // we need to pad with spaces + "00020$: \n" + " mov A, #' ' \n" + " lcall 00060$ \n" + " djnz R2, 00020$ \n" + " ljmp 00055$ \n" + + // continue to process format string - handle 'x'/'X' + "00025$: \n" + " mov A, R3 \n" + " orl A, #0x20 \n" + " cjne A, #'x', 00035$ \n" + " push DPH \n" + " push DPL \n" + " lcall 00080$ \n" // get pointer to the number in DPTR, length in bytes in B + // save it + + "00070$: \n" + " push DPH \n" + " push DPL \n" + + // sort out how long it would be if printed, first get a pointer to the highest + " mov A, B \n" + " rl A \n" + " mov R1, A \n" + " rr A \n" + " add A, #0xff \n" + " add A, DPL \n" + " mov DPL, A \n" + " mov A, DPH \n" + " addc A, #0x00 \n" + " mov DPH, A \n" + "00026$: \n" + " lcall 00079$ \n" + " anl A, #0xf0 \n" + " jnz 00028$ \n" + " dec R1 \n" + " lcall 00079$ \n" + " jnz 00028$ \n" + " dec R1 \n" + // dec DPTR + " dec DPL \n" + " mov A, DPL \n" + " cjne A, #0xff, 00027$ \n" + " dec DPH \n" + "00027$: \n" + " djnz B, 00026$ \n" + + // we now know how many digits the number is (in R1), except that it has "0" if the number if zero, we cannot have that + "00028$: \n" + " cjne R1, #0, 00029$ \n" + " inc R1 \n" + "00029$: \n" // we now finally have the full length of the digits + + // if the number is negative (happens when we're printing decimals) + // the length of it is one more, also in case of zero-padding, we need to print the minus sign here now + " mov A, R4 \n" + " anl A, #0x20 \n" + " jz 00051$ \n" + " inc R1 \n" // the length is one more + " mov A, R4 \n" + " anl A, #02 \n" // if zero-padding, the negative comes now + " jz 00051$ \n" + " mov A, #'-' \n" + " lcall 00060$ \n" + "00051$: \n" + + // sort out if we need padding at all and if there is space + " mov A, R4 \n" + " anl A, #0x04 \n" + " jz 00031$ \n" // no padding requested + // padding was requested len is in R2 + " mov A, R2 \n" + " clr C \n" + " subb A, R1 \n" + " jc 00031$ \n" // pad-to len < number_len -> no padding needed + " jz 00031$ \n" // pad-to len == number_len -> no padding needed + " mov R2, A \n" + + // sort out which character to use -> DPL + " mov A, R4 \n" // fancy way to create space/zero as needed + " anl A, #0x02 \n" + " swap A \n" + " rr A \n" + " add A, #0x20 \n" + " mov DPL, A \n" + + // pad! + "00030$: \n" + " mov A, DPL \n" + " lcall 00060$ \n" + " djnz R2, 00030$ \n" + "00031$: \n" + + // if the number is negative (happens when we're printing decimals) + // we made the length of it is one more, which we need to undo + // also in case of space-padding, we need to print the minus sign here now + " mov A, R4 \n" + " anl A, #0x20 \n" + " jz 00052$ \n" + " dec R1 \n" // the length is one less than we had increased it to + " mov A, R4 \n" + " anl A, #02 \n" // if space-padding, the negative comes now + " jnz 00052$ \n" + " mov A, #'-' \n" + " lcall 00060$ \n" + "00052$: \n" + + // time to print the number itself + // sort out which hexch charset to use -> R2 + " mov A, R3 \n" + " anl A, #0x20 \n" + " rr A \n" + " mov R2, A \n" + // re-get the number pointer + " pop DPL \n" + " pop DPH \n" + // currently DPTR points to the number low byte, R1 is now many digits we expect to print, R2 is the charset selection, R4 and R3 are free + // let's calculate how many bytes we expect to process -> R4 + " mov A, R1 \n" + " inc A \n" + " clr C \n" + " rrc A \n" + " mov R4, A \n" + // let's repoint DPTR to the first byte we'll print in (remember we print 2 digits per byte) + " dec A \n" + " add A, DPL \n" + " mov DPL, A \n" + " mov A, DPH \n" + " addc A, #0x00 \n" + " mov DPH, A \n" + + // decide if we need to print just a nibble of the high byte or the whole thing. Free up R1 + " mov A, R1 \n" + " anl A, #0x01 \n" + " jz 00032$ \n" + + // we're printing just the low nibble of the first byte - set up for it + " lcall 00079$ \n" + " mov R1, #1 \n" + " sjmp 00033$ \n" + + // print loop + "00032$: \n" + " lcall 00079$ \n" + " mov R1, #2 \n" + " mov R3, A \n" + " swap A \n" + "00033$: \n" + " anl A, #0x0f \n" + " add A, R2 \n" + " push DPH \n" + " push DPL \n" + " mov DPTR, #00099$ \n" + " movc A, @A + DPTR \n" + " pop DPL \n" + " pop DPH \n" + " lcall 00060$ \n" + " mov A, R3 \n" + " djnz R1, 00033$ \n" + + // dec DPTR + " dec DPL \n" + " mov A, DPL \n" + " cjne A, #0xff, 00034$ \n" + " dec DPH \n" + "00034$: \n" + " djnz R4, 00032$ \n" + + // done! + " ljmp 00055$ \n" + + // continue to process format string - handle 'd' + "00036$: \n" + " cjne R3, #'d', 00037$ \n" + " mov A, #0x20 \n" + " orl A, R4 \n" + " mov R4, A \n" + " sjmp 00040$ \n" + + // continue to process format string - handle 'u' + "00037$: \n" + " cjne R3, #'u', 00038$ \n" + " sjmp 00040$ \n" + + // no more format strings exist that we can handle - bail + "00038$: \n" + " ljmp 00001$ \n" + + // handle decimal printing + "00040$: \n" + " push DPH \n" + " push DPL \n" + " lcall 00080$ \n" // get pointer to the number in DPTR, length in bytes in B + " push B \n" + + // copy the number to the double-dabble storage at proper offset (0 for u64, 4 for u32, 6 for u16) + // we do this so that the dabble area always starts at the same place... + " mov A, #8 \n" + " clr C \n" + " subb A, B \n" + " add A, #_mCvtBuf \n" + " mov R1, A \n" + " clr A \n" + " addc A, #(_mCvtBuf >> 8) \n" + " mov R3, A \n" + "00041$: \n" + " lcall 00079$ \n" + " inc DPTR \n" + " lcall 00086$ \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " lcall 00086$ \n" + " djnz B, 00041$ \n" + // leave DPTR pointing to dabble storage, past the number + " lcall 00086$ \n" + + // we now have the top byte of the number in A, good time to check for negatives, if needed + " mov B, A \n" + " mov A, R4 \n" + " anl A, #0x20 \n" + " jz 00050$ \n" // unsigned printing requested + " mov A, B \n" + " anl A, #0x80 \n" + " jnz 00043$ \n" // is negative - we need to invert, 0x20 bit in R1 stays + // positive - 0x20 bit in R1 needs to go + " mov A, R4 \n" + " anl A, #~0x20 \n" + " mov R4, A \n" + " sjmp 00050$ \n" + + // we need to negate the number + // but first we need a pointer to it, and its size + "00043$: \n" + " pop B \n" + " push B \n" + " mov A, #8 \n" + " clr C \n" + " subb A, B \n" + " add A, #_mCvtBuf \n" + " mov DPL, A \n" + " clr A \n" + " addc A, #(_mCvtBuf >> 8) \n" + " mov DPH, A \n" + + // ok, now we are ready to negate it + " clr C \n" + "00049$: \n" + " movx A, @DPTR \n" + " mov R1, A \n" + " clr A \n" + " subb A, R1 \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00049$ \n" + + // zero out the rest of the storage (10 bytes) + "00050$: \n" + " mov B, #10 \n" + " clr A \n" + "00042$: \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00042$ \n" + + // calculate number of dabble steps + " pop A \n" + " swap A \n" + " rr A \n" + " mov R3, A \n" + + // do the thing + "00044$: \n" + + // dabble (10 iters for simplicity) + " mov DPTR, #(_mCvtBuf + 8) \n" + " mov B, #10 \n" + "00046$: \n" + " movx A, @DPTR \n" + " mov R1, A \n" + " anl A, #0x0f \n" + " add A,#-0x05 \n" + " mov A, R1 \n" + " jnc 00047$ \n" + " add A, #0x03 \n" + "00047$: \n" + " mov R1, A \n" + " anl A, #0xf0 \n" + " add A,#-0x50 \n" + " mov A, R1 \n" + " jnc 00048$ \n" + " add A, #0x30 \n" + "00048$: \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00046$ \n" + + // double (18 iters for simplicity) + " mov DPTR, #_mCvtBuf \n" + " clr C \n" + " mov B, #18 \n" + "00045$: \n" + " movx A, @DPTR \n" + " rlc A \n" + " movx @DPTR, A \n" + " inc DPTR \n" + " djnz B, 00045$ \n" + + " djnz R3, 00044$ \n" + + // dabbling is done, print it now using hex routine + " mov DPTR, #(_mCvtBuf + 8) \n" + " mov B, #10 \n" + " clr PSW.5 \n" // it is now for sure in XRAM + " ljmp 00070$ \n" + + // read short pointer from param stack + "00090$: \n" + " mov DPH, @R0 \n" + "00093$: \n" + " dec R0 \n" + " mov DPL, @R0 \n" + " dec R0 \n" + " ret \n" + + // read and increment pointer of the type provided by 00091$ (in {DPTR,PSW}) into A. clobber nothing + "00095$: \n" + " jb PSW.5, 00066$ \n" + " jb PSW.1, 00067$ \n" + // XRAM + " movx A, @DPTR \n" + " inc DPTR \n" + " ret \n" + // CODE + "00066$: \n" + " clr A \n" + " movc A, @A+DPTR \n" + " inc DPTR \n" + " ret \n" + // IRAM + "00067$: \n" + " mov DPH, R0 \n" + " mov R0, DPL \n" + " mov A, @R0 \n" + " mov R0, DPH \n" + " inc DPL \n" + " ret \n" + + // resolve generic pointer on param stack to an pointer in DPTR and flags in PSW.5 and PSW.1 + // PSW.5 will be 0 and PSW.1 will be 0 for XRAM (PDATA goes here too) + // PSW.5 will be 1 and PSW.1 will be 0 for CODE + // PSW.5 will be 0 and PSW.1 will be 1 for IRAM + "00091$: \n" + " clr PSW.5 \n" + " clr PSW.1 \n" + " mov A, @R0 \n" + " dec R0 \n" + " jz 00090$ \n" // 0x00: pointer type: xdata + " xrl A, #0x80 \n" + " jz 00094$ \n" // 0x80: pointer type: code + " xrl A, #0xc0 \n" + " jz 00092$ \n" // 0x40: pointer type: idata + // pdata + " mov DPH, _XPAGE \n" + " sjmp 00093$ \n" + // idata + "00092$: \n" + " setb PSW.1 \n" + " sjmp 00093$ \n" + // code + "00094$: \n" + " setb PSW.5 \n" + " sjmp 00090$ \n" + + // read the pointer of the type that 00080$ returns (in DPTR) into A. clobber nothing + "00079$: \n" + " jnb PSW.5, 00078$ \n" + " push _R0 \n" + " mov R0, DPL \n" + " mov A, @R0 \n" + " pop _R0 \n" + " ret \n" + "00078$: \n" + " movx A, @DPTR \n" + " ret \n" + + // get pointer to a number, might be pushed or might be pointed to, size might vary. return pointer to number's LOW byte in DPTR + "00080$: \n" + " mov A, R4 \n" + " anl A, #0x01 \n" + " jnz 00083$ \n" + // param is itself on stack - now we care about size, but either way, PSW.5 will be 1 + " setb PSW.5 \n" + " mov B, #0 \n" + " mov A, R4 \n" + " anl A, #0x18 \n" + " jz 00081$ \n" + " anl A, #0x10 \n" + " jz 00082$ \n" + // long long (8 bytes) \n" + " setb B.2 \n" + " dec R0 \n" + " dec R0 \n" + " dec R0 \n" + " dec R0 \n" + // long (4 bytes) + "00082$: \n" + " setb B.1 \n" + " dec R0 \n" + " dec R0 \n" + // int (2 bytes) \n" + "00081$: \n" + " setb B.0 \n" + " dec R0 \n" + " mov DPL, R0 \n" + " dec R0 \n" + " inc B \n" + " ret \n" + // pointer it on stack itself, number is in xram, but we still need to provide the length + "00083$: \n" + " clr PSW.5 \n" // mark as "in xram" + " mov A, R4 \n" + " anl A, #0x18 \n" + " jz 00084$ \n" + " anl A, #0x10 \n" + " jz 00085$ \n" + // long long + " mov B, #8 \n" + " ljmp 00090$ \n" + // long + "00085$: \n" + " mov B, #4 \n" + " ljmp 00090$ \n" + // int + "00084$: \n" + " mov B, #2 \n" + " ljmp 00090$ \n" + + // swap R3:R1 <-> DPH:DPL + "00086$: \n" + " xch A, DPH \n" + " xch A, R3 \n" + " xch A, DPH \n" + " xch A, DPL \n" + " xch A, R1 \n" + " xch A, DPL \n" + " ret \n" + + /* putchar func + called via call. char is in A, R7 has pointer to stack as needed + can clobber B, CANNOT clobber DPTR + a mess because...8051 + */ + "00060$: \n" + " push DPH \n" + " push DPL \n" + " push _R1 \n" + " push _R0 \n" + " mov _R0, R7 \n" + " mov DPL, @R0 \n" + " dec R0 \n" + " mov DPH, @R0 \n" // DPTR is now func ptr + " dec R0 \n" + " dec R0 \n" + " dec R0 \n" + " dec R0 \n" + " mov _R1, @R0 \n" + " dec R0 \n" + " mov _R0, @R0 \n" // R1:R0 is now "formatD" + " lcall 00061$ \n" // to set ret addr + " pop _R0 \n" + " pop _R1 \n" + " pop DPL \n" + " pop DPH \n" + " ret \n" + "00061$: \n" + " push DPL \n" + " push DPH \n" + " mov DPL, _R0 \n" + " mov DPH, _R1 \n" + " ret \n" + + "00099$: \n" + " .ascii \"01234567\" \n" + " .ascii \"89ABCDEF\" \n" + " .ascii \"01234567\" \n" + " .ascii \"89abcdef\" \n"); + (void)fmt; + (void)vl; + (void)formatF; + (void)formatD; } #pragma callee_saves prPrvPutchar -static void prPrvPutchar(uint32_t data) __reentrant -{ - char ch = data >> 24; - - if (ch == '\n') - dbgUartByte('\r'); - dbgUartByte(ch); +static void prPrvPutchar(uint32_t data) __reentrant { + char ch = data >> 24; + if (ch == '\n') + uartTx('\r'); + uartTx(ch); +} +#pragma callee_saves epdPutchar +static void epdPutchar(uint32_t data) __reentrant { + char ch = data >> 24; + writeCharEPD(ch); } -void pr(const char __code *fmt, ...) __reentrant -{ - va_list vl; - - va_start(vl, fmt); - dbgUartOn(); - prvPrintFormat(prPrvPutchar, 0, fmt, vl); - dbgUartOff(); - va_end(vl); +void pr(const char __code *fmt, ...) __reentrant { + va_list vl; + va_start(vl, fmt); + prvPrintFormat(prPrvPutchar, 0, fmt, vl); + va_end(vl); +} + +void epdpr(const char __code *fmt, ...) __reentrant { + va_list vl; + va_start(vl, fmt); + prvPrintFormat(epdPutchar, 0, fmt, vl); + va_end(vl); } #pragma callee_saves prPrvPutS -static void prPrvPutS(uint32_t data) __reentrant -{ - char __xdata * __idata *strPP = (char __xdata * __idata *)data; - char ch = data >> 24; - - *(*strPP)++ = ch; +static void prPrvPutS(uint32_t data) __reentrant { + char __xdata *__idata *strPP = (char __xdata *__idata *)data; + char ch = data >> 24; + + *(*strPP)++ = ch; } -void spr(char __xdata* out, const char __code *fmt, ...) __reentrant -{ - char __xdata* outStart = out; - - va_list vl; - - va_start(vl, fmt); - prvPrintFormat(prPrvPutS, (uint16_t)&out, fmt, vl); - va_end(vl); - - *out = 0; -} +void spr(char __xdata *out, const char __code *fmt, ...) __reentrant { + char __xdata *outStart = out; + va_list vl; + + va_start(vl, fmt); + prvPrintFormat(prPrvPutS, (uint16_t)&out, fmt, vl); + va_end(vl); + + *out = 0; +} diff --git a/tag_fw/epd.c b/tag_fw/epd.c index f4334dd4..48388629 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -66,7 +66,6 @@ extern void dump(uint8_t* __xdata a, uint16_t __xdata l); // remove me when done -static bool __idata epdPr = false; // wheter or not we copy the pr("") output to the EPD static uint8_t __xdata epdCharSize = 1; // character size, 1 or 2 (doubled) static bool __xdata directionY = true; // print direction, X or Y (true) static uint8_t __xdata rbuffer[32]; // used to rotate bits around @@ -303,9 +302,8 @@ void selectLUT(uint8_t lut) { if (SCREEN_WIDTH == 152) { sendCustomLut(lut154, 100); } else { - sendCustomLut(lut154, 70); + sendCustomLut(lutorig, 70); } - return; readLut(); // dump((uint8_t*)&waveform, 96); dump(blockXferBuffer, 512); @@ -524,9 +522,6 @@ static void pushYFontBytesToEPD(uint8_t byte1, uint8_t byte2) { } } void writeCharEPD(uint8_t c) { - if (!epdPr) { - return; - } // Writes a single character to the framebuffer bool empty = true; for (uint8_t i = 0; i < 20; i++) { @@ -611,7 +606,6 @@ void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool c memset(rbuffer, 0, 32); } - epdPr = true; if (color) { commandBegin(CMD_WRITE_FB_RED); } else { @@ -625,7 +619,6 @@ void epdPrintEnd() { } } commandEnd(); - epdPr = false; } extern uint8_t __xdata blockXferBuffer[]; diff --git a/tag_fw/epd.h b/tag_fw/epd.h index 5cdb9edf..52c62f46 100644 --- a/tag_fw/epd.h +++ b/tag_fw/epd.h @@ -54,10 +54,12 @@ void ByteDecode(uint8_t byte); void epdPrintBegin(uint16_t x, uint16_t y, bool direction, bool fontsize, bool red); void epdPrintEnd(); - void beginFullscreenImage(); void beginWriteFramebuffer(bool color); void lutTest(); +// for printf.c +void writeCharEPD(uint8_t c); + #endif \ No newline at end of file diff --git a/tag_fw/printf.h b/tag_fw/printf.h index b04e0590..8ca2eea5 100644 --- a/tag_fw/printf.h +++ b/tag_fw/printf.h @@ -19,6 +19,9 @@ #pragma callee_saves pr void pr(const char __code *fmt, ...) __reentrant; +#pragma callee_saves epdpr +void epdpr(const char __code *fmt, ...) __reentrant; + #pragma callee_saves spr void spr(char __xdata* out, const char __code *fmt, ...) __reentrant; diff --git a/tag_fw/soc/radioCommon.h b/tag_fw/soc/radioCommon.h index b38f5186..9c931a86 100644 --- a/tag_fw/soc/radioCommon.h +++ b/tag_fw/soc/radioCommon.h @@ -5,6 +5,10 @@ #include +//radio cfg +#define RADIO_FIRST_CHANNEL (11) //2.4-GHz channels start at 11 +#define RADIO_NUM_CHANNELS (1) + #define RADIO_MAX_PACKET_LEN (125) //useful payload, not including the crc @@ -20,10 +24,6 @@ #define SHORT_MAC_UNUSED (0x10000000UL) //for radioRxFilterCfg's myShortMac - - - - void radioInit(void); bool radioTx(const void __xdata* packet); //waits for tx end diff --git a/tag_fw/soc/zbs243/uart.c b/tag_fw/soc/zbs243/uart.c index c41514c5..5b0c8199 100644 --- a/tag_fw/soc/zbs243/uart.c +++ b/tag_fw/soc/zbs243/uart.c @@ -12,10 +12,7 @@ void uartInit(void) { UARTSTA = 0x12; // also set the "empty" bit else we wait forever for it to go up } -extern void writeCharEPD(uint8_t c); - void uartTx(uint8_t val) { - writeCharEPD(val); while (!(UARTSTA & (1 << 1))) ; UARTSTA &= ~(1 << 1); diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index 4edfcc6b..42bb44c0 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -31,21 +31,21 @@ void showSplashScreen() { setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); selectLUT(1); epdPrintBegin(12, 2, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("Starting!"); + epdpr("Starting!"); epdPrintEnd(); loadRawBitmap(solum, 8, 34, EPD_COLOR_BLACK); loadRawBitmap(hacked, 32, 46, EPD_COLOR_RED); epdPrintBegin(5, 136, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_RED); - pr("%02X%02X", mSelfMac[7], mSelfMac[6]); - pr("%02X%02X", mSelfMac[5], mSelfMac[4]); - pr("%02X%02X", mSelfMac[3], mSelfMac[2]); - pr("%02X%02X", mSelfMac[1], mSelfMac[0]); + epdpr("%02X%02X", mSelfMac[7], mSelfMac[6]); + epdpr("%02X%02X", mSelfMac[5], mSelfMac[4]); + epdpr("%02X%02X", mSelfMac[3], mSelfMac[2]); + epdpr("%02X%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); epdPrintBegin(2, 120, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - pr("zbs154v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); + epdpr("zbs154v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); epdPrintEnd(); draw(); #endif @@ -57,18 +57,18 @@ void showSplashScreen() { setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); epdPrintBegin(0, 295, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("Starting!"); + epdpr("Starting!"); epdPrintEnd(); epdPrintBegin(115, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); - pr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); - pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); - pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); - pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); + epdpr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); + epdpr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); + epdpr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); + epdpr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); epdPrintBegin(96, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - pr("zbs29v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); + epdpr("zbs29v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); epdPrintEnd(); loadRawBitmap(solum, 0, 0, EPD_COLOR_BLACK); @@ -84,27 +84,23 @@ void showSplashScreen() { clearScreen(); setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); - epdPrintBegin(64, 150, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("TEST"); - epdPrintEnd(); - epdPrintBegin(300, 296, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_RED); - pr("Booting!Y"); - epdPrintEnd(); + epdpr("Booting!Y"); + epdpr(); epdPrintBegin(0, 0, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("BootingX!"); + epdpr("Starting!"); epdPrintEnd(); epdPrintBegin(16, 252, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - pr("zbs42v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); + epdpr("zbs42v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); epdPrintEnd(); epdPrintBegin(16, 284, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_RED); - pr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); - pr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); - pr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); - pr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); + epdpr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); + epdpr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); + epdpr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); + epdpr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); loadRawBitmap(solum, 256, 10, EPD_COLOR_BLACK); @@ -123,7 +119,7 @@ void showApplyUpdate() { clearScreen(); setColorMode(EPD_MODE_IGNORE, EPD_MODE_NORMAL); epdPrintBegin(8, 60, EPD_DIRECTION_X, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); - pr("Updating!"); + epdpr("Updating!"); epdPrintEnd(); drawNoWait(); } \ No newline at end of file From 85af79fb743f3b82ed95a1ebc96ff7976a063be0 Mon Sep 17 00:00:00 2001 From: Jelmer Date: Tue, 31 Jan 2023 22:29:10 +0100 Subject: [PATCH 17/18] moved some stuff, scanning zigbee channels --- tag_fw/comms.c | 4 ++-- tag_fw/comms.h | 7 ++----- tag_fw/main.c | 19 ++++++++++++------ tag_fw/powermgt.c | 25 +----------------------- tag_fw/powermgt.h | 2 -- tag_fw/soc/radioCommon.h | 2 +- tag_fw/syncedproto.c | 42 ++++++++++++++++++++++++++++++++++++---- tag_fw/syncedproto.h | 7 +++++-- 8 files changed, 62 insertions(+), 46 deletions(-) diff --git a/tag_fw/comms.c b/tag_fw/comms.c index 4bb915f1..10c6557c 100644 --- a/tag_fw/comms.c +++ b/tag_fw/comms.c @@ -21,8 +21,8 @@ static uint8_t __xdata mCommsBuf[127]; static uint8_t __xdata mSeq = 0; -static uint8_t __xdata mLastLqi = 0; -static int8_t __xdata mLastRSSI = 0; +uint8_t __xdata mLastLqi = 0; +int8_t __xdata mLastRSSI = 0; uint8_t commsGetLastPacketLQI(void) { diff --git a/tag_fw/comms.h b/tag_fw/comms.h index 1753c45a..c80f0113 100644 --- a/tag_fw/comms.h +++ b/tag_fw/comms.h @@ -11,11 +11,8 @@ #define COMMS_MAX_PACKET_SZ (127) -#pragma callee_saves commsGetLastPacketLQI -uint8_t commsGetLastPacketLQI(void); - -#pragma callee_saves commsGetLastPacketRSSI -int8_t commsGetLastPacketRSSI(void); +extern uint8_t __xdata mLastLqi; +extern int8_t __xdata mLastRSSI; int8_t commsRxUnencrypted(void __xdata *data); bool commsTxUnencrypted(const void __xdata *packetP, uint8_t len); diff --git a/tag_fw/main.c b/tag_fw/main.c index eaf257b1..b17711d3 100644 --- a/tag_fw/main.c +++ b/tag_fw/main.c @@ -7,17 +7,16 @@ #include "asmUtil.h" #include "eeprom.h" +#include "epd.h" +#include "powermgt.h" #include "printf.h" #include "proto.h" #include "radio.h" +#include "comms.h" // for mLastLqi and mLastRSSI #include "syncedproto.h" #include "timer.h" -#include "wdt.h" -#include "powermgt.h" - #include "userinterface.h" -#include "epd.h" - +#include "wdt.h" void mainProtocolLoop(void) { clockingAndIntsInit(); @@ -55,7 +54,7 @@ void mainProtocolLoop(void) { initializeProto(); } - // initialize attempt-array with the default value; + // initialize Powers-saving-attempt-array with the default value; initPowerSaving(); // show the splashscreen @@ -65,6 +64,14 @@ void mainProtocolLoop(void) { eepromDeepPowerDown(); initRadio(); + for (uint8_t c = 25; c > 10; c--) { + if (probeChannel(c)) { + pr("Channel: %d - LQI: %d RSSI %d\n", c, mLastLqi, mLastRSSI); + } else { + pr("Channel %d - nothing.\n", c); + } + } + P1CHSTA &= ~(1 << 0); while (1) { diff --git a/tag_fw/powermgt.c b/tag_fw/powermgt.c index 37be9fd8..df441448 100644 --- a/tag_fw/powermgt.c +++ b/tag_fw/powermgt.c @@ -37,23 +37,6 @@ void initPowerSaving() { } // init/sleep -void initRadio() { - radioInit(); - radioRxFilterCfg(mSelfMac, 0x10000, PROTO_PAN_ID); - radioSetChannel(RADIO_FIRST_CHANNEL); - radioSetTxPower(10); -} -void killRadio() { - radioRxEnable(false, true); - RADIO_IRQ4_pending = 0; - UNK_C1 &= ~0x81; - TCON &= ~0x20; - uint8_t __xdata cfgPg = CFGPAGE; - CFGPAGE = 4; - RADIO_command = 0xCA; - RADIO_command = 0xC5; - CFGPAGE = cfgPg; -} void initAfterWake() { clockingAndIntsInit(); timerInit(); @@ -106,10 +89,4 @@ uint16_t getNextSleep() { if (noNetwork == true) return INTERVAL_NO_SIGNAL; avg /= POWER_SAVING_SMOOTHING; return avg; -} - -void initRadio(); -void killRadio(); -void initAfterWake(); -void doSleep(uint32_t __xdata t); -uint16_t getNextSleep(); +} \ No newline at end of file diff --git a/tag_fw/powermgt.h b/tag_fw/powermgt.h index 6135d00d..7f641671 100644 --- a/tag_fw/powermgt.h +++ b/tag_fw/powermgt.h @@ -13,8 +13,6 @@ #define POWER_SAVING_SMOOTHING 8 // How many samples we should use to smooth the data request interval #define MINIMUM_INTERVAL 45 // IMPORTANT: Minimum interval for check-in; this determines overal battery life! -extern void initRadio(); -extern void killRadio(); extern void initAfterWake(); extern void doSleep(uint32_t __xdata t); extern uint16_t getNextSleep(); diff --git a/tag_fw/soc/radioCommon.h b/tag_fw/soc/radioCommon.h index 9c931a86..99937234 100644 --- a/tag_fw/soc/radioCommon.h +++ b/tag_fw/soc/radioCommon.h @@ -7,7 +7,7 @@ //radio cfg #define RADIO_FIRST_CHANNEL (11) //2.4-GHz channels start at 11 -#define RADIO_NUM_CHANNELS (1) +#define RADIO_NUM_CHANNELS (15) #define RADIO_MAX_PACKET_LEN (125) //useful payload, not including the crc diff --git a/tag_fw/syncedproto.c b/tag_fw/syncedproto.c index 6a3b6900..f0aa3d85 100644 --- a/tag_fw/syncedproto.c +++ b/tag_fw/syncedproto.c @@ -1,11 +1,14 @@ #define __packed #include "syncedproto.h" + #include #include #include #include #include + #include "asmUtil.h" +#include "comms.h" #include "cpu.h" #include "drawing.h" #include "eeprom.h" @@ -13,14 +16,12 @@ #include "powermgt.h" #include "printf.h" #include "proto.h" -#include "comms.h" #include "radio.h" +#include "settings.h" #include "sleep.h" #include "timer.h" #include "userinterface.h" #include "wdt.h" -#include "powermgt.h" -#include "settings.h" // download-stuff bool __xdata dataPending = true; @@ -48,6 +49,7 @@ uint8_t __xdata APmac[8] = {0}; uint16_t __xdata APsrcPan = 0; uint8_t __xdata mSelfMac[8] = {0}; uint8_t __xdata seq = 0; +uint8_t __xdata currentChannel = 0; // buffer we use to prepare/read packets // static uint8_t __xdata mRxBuf[130]; @@ -106,6 +108,38 @@ void addCRC(void *p, uint8_t len) { ((uint8_t *)p)[0] = total; } +// radio stuff +void initRadio() { + radioInit(); + radioRxFilterCfg(mSelfMac, 0x10000, PROTO_PAN_ID); + if (currentChannel >= 11 && currentChannel <= 25) { + radioSetChannel(currentChannel); + } else { + radioSetChannel(RADIO_FIRST_CHANNEL); + } + radioSetTxPower(10); +} +void killRadio() { + radioRxEnable(false, true); + RADIO_IRQ4_pending = 0; + UNK_C1 &= ~0x81; + TCON &= ~0x20; + uint8_t __xdata cfgPg = CFGPAGE; + CFGPAGE = 4; + RADIO_command = 0xCA; + RADIO_command = 0xC5; + CFGPAGE = cfgPg; +} +bool probeChannel(uint8_t channel) { + radioRxEnable(false, true); + radioRxFlush(); + radioSetChannel(channel); + radioRxEnable(true, true); + getAvailDataInfo(); + return(dataReqLastAttempt != DATA_REQ_MAX_ATTEMPTS); + +} + // data xfer stuff void sendAvailDataReq() { struct MacFrameBcast __xdata *txframe = (struct MacFrameBcast *)(outBuffer + 1); @@ -121,7 +155,7 @@ void sendAvailDataReq() { txframe->seq = seq++; txframe->dstPan = 0xFFFF; txframe->dstAddr = 0xFFFF; - txframe->srcPan = 0x4447; + txframe->srcPan = PROTO_PAN_ID; // TODO: send some meaningful data availreq->softVer = 1; if (P1CHSTA && (1 << 0)) { diff --git a/tag_fw/syncedproto.h b/tag_fw/syncedproto.h index dc0278df..0093d02f 100644 --- a/tag_fw/syncedproto.h +++ b/tag_fw/syncedproto.h @@ -4,10 +4,13 @@ #include +extern void initRadio(); +extern void killRadio(); + extern uint8_t __xdata mSelfMac[]; extern struct AvailDataInfo *__xdata getAvailDataInfo(); extern bool doDataDownload(struct AvailDataInfo *__xdata avail); extern void initializeProto(); - - +extern struct AvailDataInfo *__xdata getAvailDataInfo(); +bool probeChannel(uint8_t channel); #endif \ No newline at end of file From 57895f7c0cff60b789547beea5d0860d0db0638b Mon Sep 17 00:00:00 2001 From: Jelmer Date: Wed, 1 Feb 2023 12:35:52 +0100 Subject: [PATCH 18/18] wip: splashscreen, scanning-menu --- tag_fw/Makefile | 2 +- tag_fw/barcode.c | 101 +++++++++++++++++++++++++++++++++ tag_fw/barcode.h | 29 ++++++++++ tag_fw/bitmaps.h | 113 +++++++++++++++++++++++++++++++++++++ tag_fw/epd.c | 32 ++++++++++- tag_fw/epd.h | 2 + tag_fw/main.c | 56 +++++++++++++------ tag_fw/syncedproto.h | 6 +- tag_fw/userinterface.c | 124 ++++++++++++++++++++++++++++++++++++----- tag_fw/userinterface.h | 8 ++- 10 files changed, 437 insertions(+), 36 deletions(-) create mode 100644 tag_fw/barcode.c create mode 100644 tag_fw/barcode.h diff --git a/tag_fw/Makefile b/tag_fw/Makefile index 927d382d..fae0f459 100644 --- a/tag_fw/Makefile +++ b/tag_fw/Makefile @@ -5,7 +5,7 @@ BUILD ?= zbs29v033 SOURCES += main.c eeprom.c drawing.c SOURCES += comms.c SOURCES += syncedproto.c epd.c userinterface.c -SOURCES += powermgt.c +SOURCES += powermgt.c barcode.c all: #make sure it is the first target diff --git a/tag_fw/barcode.c b/tag_fw/barcode.c new file mode 100644 index 00000000..c431d148 --- /dev/null +++ b/tag_fw/barcode.c @@ -0,0 +1,101 @@ +#include +#include "barcode.h" +#include "asmUtil.h" + +//code128 generator (c) 2021 Dmitry Grinberg https://dmitry.gr +//non-commercial use only, contact licensing@dmitry.gr for other options + +#pragma nogcse + +static const uint16_t __code mCode128[] = { + 0b00110011011, 0b00110110011, 0b01100110011, 0b00011001001, 0b00110001001, 0b00110010001, 0b00010011001, 0b00100011001, + 0b00100110001, 0b00010010011, 0b00100010011, 0b00100100011, 0b00111001101, 0b00111011001, 0b01110011001, 0b00110011101, + 0b00110111001, 0b01100111001, 0b01001110011, 0b00111010011, 0b01110010011, 0b00100111011, 0b00101110011, 0b01110110111, + 0b00110010111, 0b00110100111, 0b01100100111, 0b00100110111, 0b00101100111, 0b01001100111, 0b00011011011, 0b01100011011, + 0b01101100011, 0b00011000101, 0b00011010001, 0b01100010001, 0b00010001101, 0b00010110001, 0b01000110001, 0b00010001011, + 0b00010100011, 0b01000100011, 0b00011101101, 0b01110001101, 0b01110110001, 0b00011011101, 0b01100011101, 0b01101110001, + 0b01101110111, 0b01110001011, 0b01110100011, 0b00010111011, 0b01000111011, 0b01110111011, 0b00011010111, 0b01100010111, + 0b01101000111, 0b00010110111, 0b01000110111, 0b01011000111, 0b01011110111, 0b01000010011, 0b01010001111, 0b00001100101, + 0b00110000101, 0b00001101001, 0b01100001001, 0b00110100001, 0b01100100001, 0b00001001101, 0b00100001101, 0b00001011001, + 0b01000011001, 0b00101100001, 0b01001100001, 0b01001000011, 0b00001010011, 0b01011101111, 0b00101000011, 0b01011110001, + 0b00111100101, 0b00111101001, 0b01111001001, 0b00100111101, 0b00101111001, 0b01001111001, 0b00100101111, 0b00101001111, + 0b01001001111, 0b01111011011, 0b01101111011, 0b01101101111, 0b00011110101, 0b01111000101, 0b01111010001, 0b00010111101, + 0b01000111101, 0b00010101111, 0b01000101111, 0b01111011101, 0b01110111101, 0b01111010111, 0b01110101111 +}; + + +#define CODE128_START_B (0b00001001011) +#define CODE128_STOP (0b1101011100011) + +#define CODE128_IDX_START_A (103) +#define CODE128_IDX_START_B (104) +#define CODE128_IDX_START_C (105) +#define CODE128_IDX_CODE_STOP (106) + + +enum BarCodeState { + BarCodeInit, + BarCodeEmittingChar, + BarCodeEmittingChecksum, + BarCodeEmittingStop, + BarCodeDone, +}; + + + + +__bit barcodeIsDone(struct BarcodeInfo __xdata *bci) +{ + return bci->state == BarCodeDone; +} + +__bit barcodeNextBar(struct BarcodeInfo __xdata *bci) +{ + uint8_t t; + __bit ret; + + if (!bci->barsLeft) switch (bci->state) { + case BarCodeInit: + bci->curBars = CODE128_START_B; + bci->barsLeft = 11; + bci->state = BarCodeEmittingChar; + bci->csum = CODE128_IDX_START_B; + break; + + case BarCodeEmittingChar: + t = charsPrvDerefAndIncGenericPtr(&bci->str); + if (t) { + t -= 0x20; + if (t >= 0x60) + t = '?' - 0x20; + bci->csum = mathPrvMod16x8(mathPrvMul8x8(++bci->csumMul, t) + bci->csum, 103); + } + else { + + bci->state = BarCodeEmittingChecksum; + t = bci->csum; + } + bci->curBars = mCode128[t]; + bci->barsLeft = 11; + break; + + case BarCodeEmittingChecksum: + bci->state = BarCodeEmittingStop; + bci->curBars = CODE128_STOP; + bci->barsLeft = 13; + break; + + case BarCodeEmittingStop: + bci->state = BarCodeDone; + //fallthrough + + case BarCodeDone: + default: + return false; + } + + ret = bci->curBars & 1; + bci->curBars >>= 1; + bci->barsLeft--; + return ret; +} diff --git a/tag_fw/barcode.h b/tag_fw/barcode.h new file mode 100644 index 00000000..f6d784b9 --- /dev/null +++ b/tag_fw/barcode.h @@ -0,0 +1,29 @@ +#ifndef _BARCODE_H_ +#define _BARCODE_H_ + +#include + +//code128 generator (c) 2021 Dmitry Grinberg https://dmitry.gr +//non-commercial use only, contact licensing@dmitry.gr for other options + + +struct BarcodeInfo { //zero-init this except the string ptr + const char *str; + uint16_t curBars; + uint8_t barsLeft; + uint8_t state; + uint8_t csum; + uint8_t csumMul; +}; + +#define barcodeWidth(nChars) (11 * (nChars) + 11 /*start */+ 11 /*check sum */ + 13 /* stop */) + +#pragma callee_saves barcodeIsDone +__bit barcodeIsDone(struct BarcodeInfo __xdata *bci); + +#pragma callee_saves barcodeNextBar +__bit barcodeNextBar(struct BarcodeInfo __xdata *bci); + + + +#endif diff --git a/tag_fw/bitmaps.h b/tag_fw/bitmaps.h index d8766259..d16feecf 100644 --- a/tag_fw/bitmaps.h +++ b/tag_fw/bitmaps.h @@ -94,4 +94,117 @@ static const uint8_t __code hacked[] = { 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; +static const uint8_t __code receive[] = { + 56, 56, + 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, 0x07, 0xc0, + 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xf0, + 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf0, + 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xf0, + 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xf0, + 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, + 0x00, 0x00, 0x00, 0x1f, 0xff, 0x80, 0x00, + 0x00, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xfe, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0f, 0xf8, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1f, 0xe0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3f, 0xc0, 0x00, 0x03, 0xe0, + 0x00, 0x00, 0x7f, 0x80, 0x00, 0x7f, 0xf0, + 0x00, 0x00, 0xfe, 0x00, 0x03, 0xff, 0xf0, + 0x00, 0x01, 0xfc, 0x00, 0x0f, 0xff, 0xf0, + 0x00, 0x01, 0xf8, 0x00, 0x3f, 0xff, 0xf0, + 0x00, 0x03, 0xf8, 0x00, 0xff, 0xff, 0x80, + 0x00, 0x07, 0xf0, 0x01, 0xff, 0xe0, 0x00, + 0x00, 0x0f, 0xe0, 0x03, 0xff, 0x00, 0x00, + 0x00, 0x0f, 0xc0, 0x0f, 0xf8, 0x00, 0x00, + 0x00, 0x1f, 0x80, 0x1f, 0xf0, 0x00, 0x00, + 0x00, 0x1f, 0x80, 0x3f, 0xc0, 0x00, 0x00, + 0x00, 0x3f, 0x00, 0x3f, 0x80, 0x00, 0x00, + 0x00, 0x3f, 0x00, 0x7f, 0x00, 0x00, 0x00, + 0x00, 0x7e, 0x00, 0xfe, 0x00, 0x07, 0xe0, + 0x00, 0x7e, 0x01, 0xfc, 0x00, 0x1f, 0xf0, + 0x00, 0xfc, 0x01, 0xf8, 0x00, 0x7f, 0xf0, + 0x00, 0xfc, 0x03, 0xf0, 0x01, 0xff, 0xf0, + 0x00, 0xf8, 0x03, 0xf0, 0x03, 0xff, 0xf0, + 0x01, 0xf8, 0x07, 0xe0, 0x07, 0xff, 0x00, + 0x01, 0xf8, 0x07, 0xe0, 0x0f, 0xf0, 0x00, + 0x01, 0xf0, 0x0f, 0xc0, 0x1f, 0xe0, 0x00, + 0x01, 0xf0, 0x0f, 0xc0, 0x3f, 0x80, 0x00, + 0x03, 0xf0, 0x0f, 0x80, 0x3f, 0x00, 0x00, + 0x03, 0xf0, 0x1f, 0x80, 0x7e, 0x00, 0x00, + 0x03, 0xe0, 0x1f, 0x80, 0x7e, 0x00, 0x00, + 0x03, 0xe0, 0x1f, 0x00, 0xfc, 0x01, 0xe0, + 0x03, 0xe0, 0x1f, 0x00, 0xfc, 0x07, 0xf8, + 0x03, 0xe0, 0x1f, 0x00, 0xf8, 0x0f, 0xfc, + 0x03, 0xe0, 0x3f, 0x00, 0xf8, 0x0f, 0xfc, + 0x03, 0xe0, 0x3f, 0x01, 0xf8, 0x1f, 0xfe, + 0x03, 0xe0, 0x3f, 0x01, 0xf8, 0x1f, 0xfe, + 0x03, 0xe0, 0x3f, 0x01, 0xf8, 0x1f, 0xfe, + 0x03, 0xc0, 0x3e, 0x01, 0xf0, 0x1f, 0xfe, + 0x01, 0xc0, 0x1e, 0x00, 0xf0, 0x0f, 0xfc, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xfc, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + + +static const uint8_t __code failed[] = { + 48, 48, + 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x03, 0xff, 0xff, 0xe0, 0x00, + 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, + 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x00, + 0x00, 0x7f, 0xf0, 0x0f, 0xfe, 0x00, + 0x00, 0xff, 0x80, 0x01, 0xff, 0x00, + 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, + 0x03, 0xf8, 0x00, 0x00, 0x1f, 0xc0, + 0x07, 0xf0, 0x00, 0x00, 0x3f, 0xe0, + 0x0f, 0xe0, 0x00, 0x00, 0x7f, 0xf0, + 0x0f, 0xc0, 0x00, 0x00, 0xff, 0xf0, + 0x1f, 0x80, 0x00, 0x01, 0xff, 0xf8, + 0x1f, 0x00, 0x00, 0x03, 0xff, 0xf8, + 0x3f, 0x00, 0x00, 0x07, 0xfe, 0xfc, + 0x3e, 0x00, 0x00, 0x0f, 0xfc, 0x7c, + 0x7e, 0x00, 0x00, 0x1f, 0xf8, 0x7e, + 0x7c, 0x00, 0x00, 0x3f, 0xf0, 0x3e, + 0x7c, 0x00, 0x00, 0x7f, 0xe0, 0x3e, + 0xfc, 0x00, 0x00, 0xff, 0xc0, 0x3f, + 0xf8, 0x00, 0x01, 0xff, 0x80, 0x1f, + 0xf8, 0x00, 0x03, 0xff, 0x00, 0x1f, + 0xf8, 0x00, 0x07, 0xfe, 0x00, 0x1f, + 0xf8, 0x00, 0x0f, 0xfc, 0x00, 0x1f, + 0xf8, 0x00, 0x1f, 0xf8, 0x00, 0x1f, + 0xf8, 0x00, 0x3f, 0xf0, 0x00, 0x1f, + 0xf8, 0x00, 0x7f, 0xe0, 0x00, 0x1f, + 0xf8, 0x00, 0xff, 0xc0, 0x00, 0x1f, + 0xfc, 0x01, 0xff, 0x80, 0x00, 0x3f, + 0x7c, 0x03, 0xff, 0x00, 0x00, 0x3e, + 0x7c, 0x07, 0xfe, 0x00, 0x00, 0x3e, + 0x7e, 0x0f, 0xfc, 0x00, 0x00, 0x7e, + 0x3e, 0x1f, 0xf8, 0x00, 0x00, 0x7c, + 0x3f, 0x3f, 0xf0, 0x00, 0x00, 0xfc, + 0x3f, 0x7f, 0xe0, 0x00, 0x00, 0xfc, + 0x1f, 0xff, 0xc0, 0x00, 0x01, 0xf8, + 0x0f, 0xff, 0x80, 0x00, 0x03, 0xf0, + 0x0f, 0xff, 0x00, 0x00, 0x07, 0xf0, + 0x07, 0xfe, 0x00, 0x00, 0x0f, 0xe0, + 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xc0, + 0x01, 0xfe, 0x00, 0x00, 0x7f, 0x80, + 0x00, 0xff, 0x80, 0x01, 0xff, 0x00, + 0x00, 0x7f, 0xf0, 0x0f, 0xfe, 0x00, + 0x00, 0x3f, 0xff, 0xff, 0xfc, 0x00, + 0x00, 0x0f, 0xff, 0xff, 0xf0, 0x00, + 0x00, 0x03, 0xff, 0xff, 0xe0, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, + 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, +}; + #endif \ No newline at end of file diff --git a/tag_fw/epd.c b/tag_fw/epd.c index 48388629..a5baff0a 100644 --- a/tag_fw/epd.c +++ b/tag_fw/epd.c @@ -4,6 +4,7 @@ #include #include "asmUtil.h" +#include "barcode.h" #include "board.h" #include "cpu.h" #include "font.h" @@ -299,11 +300,17 @@ static void readLut() { } void selectLUT(uint8_t lut) { + if (lut == 2) { + sendCustomLut(lut29, 70); + return; + } + if (SCREEN_WIDTH == 152) { sendCustomLut(lut154, 100); } else { sendCustomLut(lutorig, 70); } + return; readLut(); // dump((uint8_t*)&waveform, 96); dump(blockXferBuffer, 512); @@ -356,6 +363,7 @@ void clearScreen() { setWindowX(0, SCREEN_WIDTH); setWindowY(0, SCREEN_HEIGHT); setPosXY(0, 0); + shortCommand1(CMD_DATA_ENTRY_MODE, 3); // was 3 shortCommand1(CMD_WRITE_PATTERN_BW, 0x66); epdBusyWait(TIMER_TICKS_PER_MS * 100); shortCommand1(CMD_WRITE_PATTERN_RED, 0x66); @@ -372,6 +380,9 @@ void drawNoWait() { // shortCommand1(0x22, SCREEN_CMD_REFRESH); shortCommand(0x20); } +void epdWaitRdy() { + epdBusyWait(TIMER_TICKS_PER_SECOND * 120); +} void drawLineHorizontal(bool color, uint16_t x1, uint16_t x2, uint16_t y) { setWindowX(x1, x2); setWindowY(y, y + 1); @@ -436,7 +447,26 @@ void loadRawBitmap(uint8_t* bmp, uint16_t x, uint16_t y, bool color) { } commandEnd(); } - +void printBarcode(const uint8_t* string, uint16_t x, uint16_t y) { + setWindowY(y, 1); + setWindowX(x, x + 8); + setPosXY(x, y); + shortCommand1(CMD_DATA_ENTRY_MODE, 1); + commandBegin(CMD_WRITE_FB_BW); + struct BarcodeInfo __xdata bci = { + .str = string, + }; + while (!barcodeIsDone(&bci)) { + if (barcodeNextBar(&bci)) { + pr("1"); + epdSend(0xFF); + } else { + pr("0"); + epdSend(0x00); + } + } + commandEnd(); +} // stuff for printing text static void pushXFontBytesToEPD(uint8_t byte1, uint8_t byte2) { if (epdCharSize == 1) { diff --git a/tag_fw/epd.h b/tag_fw/epd.h index 52c62f46..94de8eb2 100644 --- a/tag_fw/epd.h +++ b/tag_fw/epd.h @@ -39,6 +39,7 @@ void clearWindow(bool color); void clearScreen(); void draw(); void drawNoWait(); +void epdWaitRdy(); void drawLineHorizontal(bool color, uint16_t x1, uint16_t x2, uint16_t y); void drawLineVertical(bool color, uint16_t x, uint16_t y1, uint16_t y2); @@ -46,6 +47,7 @@ void beginFullscreenImage(); void beginWriteFramebuffer(bool color); void endWriteFramebuffer(); void loadRawBitmap(uint8_t* bmp, uint16_t x, uint16_t y, bool color); +void printBarcode(const uint8_t* string, uint16_t x, uint16_t y); void selectLUT(uint8_t lut); diff --git a/tag_fw/main.c b/tag_fw/main.c index b17711d3..35a967fc 100644 --- a/tag_fw/main.c +++ b/tag_fw/main.c @@ -6,18 +6,47 @@ #include #include "asmUtil.h" +#include "comms.h" // for mLastLqi and mLastRSSI #include "eeprom.h" #include "epd.h" #include "powermgt.h" #include "printf.h" #include "proto.h" #include "radio.h" -#include "comms.h" // for mLastLqi and mLastRSSI #include "syncedproto.h" #include "timer.h" #include "userinterface.h" #include "wdt.h" +uint8_t showChannelSelect() { + uint8_t __xdata result[16]; + memset(result, 0, sizeof(result)); + showScanningWindow(); + for (uint8_t i = 0; i < 3; i++) { + for (uint8_t c = 11; c < 27; c++) { + if (probeChannel(c)) { + if (mLastLqi > result[c - 11]) result[c - 11] = mLastLqi; + pr("Channel: %d - LQI: %d RSSI %d\n", c, mLastLqi, mLastRSSI); + } + } + epdWaitRdy(); + for (uint8_t c = 0; c < 16; c++) { + addScanResult(11 + c, result[c]); + } + drawNoWait(); + } + uint8_t __xdata highestLqi = 0; + uint8_t __xdata highestSlot = 0; + for (uint8_t c = 0; c < sizeof(result); c++) { + if (result[c] > highestLqi) { + highestSlot = c + 11; + highestLqi = result[c]; + } + } + epdWaitRdy(); + return highestSlot; +} + void mainProtocolLoop(void) { clockingAndIntsInit(); timerInit(); @@ -28,13 +57,6 @@ void mainProtocolLoop(void) { while (1) ; } else { - /* - for (uint8_t c = 0; c < 8; c++) { - mSelfMac[c] = c + 5; - } - */ - // really... if I do the call below, it'll cost me 8 bytes IRAM. Not the kind of 'optimization' I ever dreamed of doing - // pr("MAC>%02X%02X%02X%02X%02X%02X%02X%02X\n", mSelfMac[0], mSelfMac[1], mSelfMac[2], mSelfMac[3], mSelfMac[4], mSelfMac[5], mSelfMac[6], mSelfMac[7]); pr("MAC>%02X%02X", mSelfMac[0], mSelfMac[1]); pr("%02X%02X", mSelfMac[2], mSelfMac[3]); pr("%02X%02X", mSelfMac[4], mSelfMac[5]); @@ -53,25 +75,27 @@ void mainProtocolLoop(void) { } else { initializeProto(); } - + eepromDeepPowerDown(); // initialize Powers-saving-attempt-array with the default value; initPowerSaving(); // show the splashscreen showSplashScreen(); - epdEnterSleep(); eepromDeepPowerDown(); initRadio(); - for (uint8_t c = 25; c > 10; c--) { - if (probeChannel(c)) { - pr("Channel: %d - LQI: %d RSSI %d\n", c, mLastLqi, mLastRSSI); - } else { - pr("Channel %d - nothing.\n", c); - } + currentChannel = showChannelSelect(); + if (currentChannel == 0) { + // couldn't find an AP :() + showNoAP(); + } else { + // Found an AP. + showAPFound(); } + epdEnterSleep(); + P1CHSTA &= ~(1 << 0); while (1) { diff --git a/tag_fw/syncedproto.h b/tag_fw/syncedproto.h index 0093d02f..583296dc 100644 --- a/tag_fw/syncedproto.h +++ b/tag_fw/syncedproto.h @@ -4,10 +4,14 @@ #include +extern uint8_t __xdata mSelfMac[8]; +extern uint8_t __xdata currentChannel; +extern uint8_t __xdata APmac[]; + extern void initRadio(); extern void killRadio(); -extern uint8_t __xdata mSelfMac[]; + extern struct AvailDataInfo *__xdata getAvailDataInfo(); extern bool doDataDownload(struct AvailDataInfo *__xdata avail); extern void initializeProto(); diff --git a/tag_fw/userinterface.c b/tag_fw/userinterface.c index 42bb44c0..1b55306b 100644 --- a/tag_fw/userinterface.c +++ b/tag_fw/userinterface.c @@ -1,3 +1,5 @@ + + #include "userinterface.h" #include @@ -6,18 +8,22 @@ #include "asmUtil.h" #include "bitmaps.h" #include "board.h" +#include "comms.h" #include "cpu.h" #include "epd.h" #include "font.h" #include "lut.h" #include "printf.h" #include "screen.h" +#include "settings.h" #include "sleep.h" #include "spi.h" +#include "syncedproto.h" // for APmac / Channel #include "timer.h" -#include "settings.h" -extern uint8_t mSelfMac[]; +extern uint8_t __xdata mSelfMac[8]; +extern uint8_t __xdata currentChannel; +extern uint8_t __xdata APmac[]; static const uint8_t __code fwVersion = FW_VERSION; static const char __code fwVersionSuffix[] = FW_VERSION_SUFFIX; @@ -45,12 +51,11 @@ void showSplashScreen() { epdPrintEnd(); epdPrintBegin(2, 120, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - epdpr("zbs154v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); + epdpr("zbs154v033 %d.%d.%d%s", fwVersion / 100, (fwVersion % 100) / 10, (fwVersion % 10), fwVersionSuffix); epdPrintEnd(); draw(); #endif - #if (SCREEN_WIDTH == 128) // 2.9" selectLUT(1); clearScreen(); @@ -60,24 +65,32 @@ void showSplashScreen() { epdpr("Starting!"); epdPrintEnd(); - epdPrintBegin(115, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); + epdPrintBegin(80, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + epdpr("zbs29v033 %d.%d.%d%s", fwVersion / 100, (fwVersion % 100) / 10, (fwVersion % 10), fwVersionSuffix); + epdPrintEnd(); + + epdPrintBegin(105, 270, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); epdpr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); epdpr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); epdpr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); epdpr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); epdPrintEnd(); - epdPrintBegin(96, 295, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - epdpr("zbs29v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); - epdPrintEnd(); + uint8_t __xdata buffer[17]; + spr(buffer, "%02X%02X", mSelfMac[7], mSelfMac[6]); + spr(buffer + 4, "%02X%02X", mSelfMac[5], mSelfMac[4]); + spr(buffer + 8, "%02X%02X", mSelfMac[3], mSelfMac[2]); + spr(buffer + 12, "%02X%02X", mSelfMac[1], mSelfMac[0]); + printBarcode(buffer, 120, 284); loadRawBitmap(solum, 0, 0, EPD_COLOR_BLACK); loadRawBitmap(hacked, 16, 12, EPD_COLOR_RED); - lutTest(); - // drawLineVertical(EPD_COLOR_RED, 64, 10, 286); - // drawLineVertical(EPD_COLOR_BLACK, 65, 10, 286); + // lutTest(); + // drawLineVertical(EPD_COLOR_RED, 64, 10, 286); + // drawLineVertical(EPD_COLOR_BLACK, 65, 10, 286); draw(); + timerDelay(TIMER_TICKS_PER_SECOND * 10); #endif #if (SCREEN_WIDTH == 400) // 2.9" selectLUT(1); @@ -92,9 +105,8 @@ void showSplashScreen() { epdpr("Starting!"); epdPrintEnd(); - epdPrintBegin(16, 252, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); - epdpr("zbs42v033 %d.%d.%d%s", fwVersion/100, (fwVersion%100)/10, (fwVersion%10),fwVersionSuffix); + epdpr("zbs42v033 %d.%d.%d%s", fwVersion / 100, (fwVersion % 100) / 10, (fwVersion % 10), fwVersionSuffix); epdPrintEnd(); epdPrintBegin(16, 284, EPD_DIRECTION_X, EPD_SIZE_SINGLE, EPD_COLOR_RED); epdpr("MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); @@ -115,6 +127,8 @@ void showSplashScreen() { void showApplyUpdate() { epdSetup(); + setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); + selectLUT(1); clearScreen(); setColorMode(EPD_MODE_IGNORE, EPD_MODE_NORMAL); @@ -122,4 +136,86 @@ void showApplyUpdate() { epdpr("Updating!"); epdPrintEnd(); drawNoWait(); -} \ No newline at end of file +} + +uint8_t __xdata resultcounter = 0; + +void showScanningWindow() { + epdSetup(); + setColorMode(EPD_MODE_NORMAL, EPD_MODE_INVERT); + selectLUT(1); + clearScreen(); + epdPrintBegin(0, 275, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + epdpr("Scanning for APs"); + epdPrintEnd(); + epdPrintBegin(40, 262, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_RED); + epdpr("Channel - Quality"); + epdPrintEnd(); + loadRawBitmap(receive, 36, 24, EPD_COLOR_BLACK); + drawNoWait(); + selectLUT(2); + resultcounter = 0; +} + +void addScanResult(uint8_t channel, uint8_t lqi) { + if (channel == 11) resultcounter = 0; + epdPrintBegin(56 + ((resultcounter % 4) * 16), 282 - (47 * (resultcounter / 4)), EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + epdpr("%d-%d", channel, lqi); + epdPrintEnd(); + resultcounter++; +} + +void showAPFound() { + pr("Showing AP found"); + selectLUT(1); + clearScreen(); + epdPrintBegin(0, 285, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + epdpr("Waiting for data..."); + epdPrintEnd(); + epdPrintBegin(48, 278, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + epdpr("Found the following AP:"); + epdPrintEnd(); + epdPrintBegin(64, 293, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + epdpr("AP MAC: %02X:%02X", APmac[7], APmac[6]); + epdpr(":%02X:%02X", APmac[5], APmac[4]); + epdpr(":%02X:%02X", APmac[3], APmac[2]); + epdpr(":%02X:%02X", APmac[1], APmac[0]); + epdPrintEnd(); + epdPrintBegin(80, 293, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + epdpr("Ch: %d RSSI: %d LQI: %d", currentChannel, mLastRSSI, mLastLqi); + epdPrintEnd(); + + epdPrintBegin(103, 258, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + epdpr("Tag MAC: %02X:%02X", mSelfMac[7], mSelfMac[6]); + epdpr(":%02X:%02X", mSelfMac[5], mSelfMac[4]); + epdpr(":%02X:%02X", mSelfMac[3], mSelfMac[2]); + epdpr(":%02X:%02X", mSelfMac[1], mSelfMac[0]); + epdPrintEnd(); + + + uint8_t __xdata buffer[17]; + spr(buffer, "%02X%02X", mSelfMac[7], mSelfMac[6]); + spr(buffer + 4, "%02X%02X", mSelfMac[5], mSelfMac[4]); + spr(buffer + 8, "%02X%02X", mSelfMac[3], mSelfMac[2]); + spr(buffer + 12, "%02X%02X", mSelfMac[1], mSelfMac[0]); + printBarcode(buffer, 120, 253); + loadRawBitmap(receive, 36, 14, EPD_COLOR_BLACK); + + draw(); +} + +void showNoAP() { + clearScreen(); + epdPrintBegin(0, 285, EPD_DIRECTION_Y, EPD_SIZE_DOUBLE, EPD_COLOR_BLACK); + epdpr("No AP found :("); + epdPrintEnd(); + epdPrintBegin(48, 285, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + epdpr("We'll try again in a"); + epdPrintEnd(); + epdPrintBegin(64, 285, EPD_DIRECTION_Y, EPD_SIZE_SINGLE, EPD_COLOR_BLACK); + epdpr("little while..."); + epdPrintEnd(); + loadRawBitmap(receive, 36, 24, EPD_COLOR_BLACK); + loadRawBitmap(failed, 42, 26, EPD_COLOR_RED); + draw(); +} diff --git a/tag_fw/userinterface.h b/tag_fw/userinterface.h index 548affd7..7b697e72 100644 --- a/tag_fw/userinterface.h +++ b/tag_fw/userinterface.h @@ -1,9 +1,11 @@ - #ifndef _UI_H_ #define _UI_H_ +#include void showSplashScreen(); void showApplyUpdate(); - - +void showScanningWindow(); +void addScanResult(uint8_t channel, uint8_t lqi); +void showAPFound(); +void showNoAP(); #endif \ No newline at end of file