From ffd0acff72e60eb81938416737bbee902acb9887 Mon Sep 17 00:00:00 2001 From: Nic Limper Date: Wed, 9 Aug 2023 23:40:39 +0200 Subject: [PATCH] tft driver for yellow board --- ESP32_AP-Flasher/data/tagtypes/E0.json | 17 ++++ ESP32_AP-Flasher/include/newproto.h | 1 + ESP32_AP-Flasher/platformio.ini | 16 +++- ESP32_AP-Flasher/src/contentmanager.cpp | 28 +++--- ESP32_AP-Flasher/src/ips_display.cpp | 111 ++++++++++++++++-------- ESP32_AP-Flasher/src/makeimage.cpp | 50 ++++++----- ESP32_AP-Flasher/wwwroot/main.js | 51 +++++++---- 7 files changed, 185 insertions(+), 89 deletions(-) create mode 100644 ESP32_AP-Flasher/data/tagtypes/E0.json diff --git a/ESP32_AP-Flasher/data/tagtypes/E0.json b/ESP32_AP-Flasher/data/tagtypes/E0.json new file mode 100644 index 00000000..5632e359 --- /dev/null +++ b/ESP32_AP-Flasher/data/tagtypes/E0.json @@ -0,0 +1,17 @@ +{ + "name": "TFT 320x170", + "width": 320, + "height": 170, + "rotatebuffer": 0, + "bpp": 16, + "colors": 4, + "colortable": { + "white": [ 255, 255, 255 ], + "black": [ 0, 0, 0 ], + "red": [ 255, 0, 0 ], + "gray": [ 150, 150, 150 ] + }, + "capabilities": [ ], + "contentids": [ 0, 1, 2, 3, 4, 8, 16, 9, 7, 19, 10, 11 ], + "usetemplate": 1 +} diff --git a/ESP32_AP-Flasher/include/newproto.h b/ESP32_AP-Flasher/include/newproto.h index a52b30bd..c41bd889 100644 --- a/ESP32_AP-Flasher/include/newproto.h +++ b/ESP32_AP-Flasher/include/newproto.h @@ -4,6 +4,7 @@ extern void addCRC(void* p, uint8_t len); extern bool checkCRC(void* p, uint8_t len); extern void processBlockRequest(struct espBlockRequest* br); +extern void prepareCancelPending(uint8_t dst[8]); extern void prepareIdleReq(uint8_t* dst, uint16_t nextCheckin); extern void prepareDataAvail(uint8_t* data, uint16_t len, uint8_t dataType, uint8_t* dst); extern bool prepareDataAvail(String* filename, uint8_t dataType, uint8_t* dst, uint16_t nextCheckin); diff --git a/ESP32_AP-Flasher/platformio.ini b/ESP32_AP-Flasher/platformio.ini index 80afe48e..71a16e4f 100644 --- a/ESP32_AP-Flasher/platformio.ini +++ b/ESP32_AP-Flasher/platformio.ini @@ -257,12 +257,10 @@ build_src_filter = [env:ESP32_S3_16_8_YELLOW_AP] board = esp32-s3-devkitc-1 board_build.partitions = large_spiffs_16MB.csv -lib_deps = - ${env.lib_deps} - https://github.com/moononournation/Arduino_GFX build_unflags = -D ARDUINO_USB_MODE=1 -D CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y + -D ILI9341_DRIVER build_flags = ${env.build_flags} -D YELLOW_IPS_AP @@ -283,6 +281,18 @@ build_flags = -D FLASHER_AP_TXD=17 -D FLASHER_AP_RXD=18 -D FLASHER_LED=14 + -D ST7789_DRIVER + -D TFT_WIDTH=170 + -D TFT_HEIGHT=320 + -D TFT_MISO=-1 + -D TFT_MOSI=13 + -D TFT_SCLK=12 + -D TFT_CS=10 + -D TFT_DC=11 + -D TFT_RST=1 + -D TFT_RGB_ORDER=TFT_BGR + -D USE_HSPI_PORT + -D LOAD_FONT2 build_src_filter = +<*>-- board_build.flash_mode=qio diff --git a/ESP32_AP-Flasher/src/contentmanager.cpp b/ESP32_AP-Flasher/src/contentmanager.cpp index 3243e5e5..887022dc 100644 --- a/ESP32_AP-Flasher/src/contentmanager.cpp +++ b/ESP32_AP-Flasher/src/contentmanager.cpp @@ -32,8 +32,8 @@ #include "language.h" #include "settings.h" #include "tag_db.h" -#include "web.h" #include "truetype.h" +#include "web.h" #define PAL_BLACK TFT_BLACK #define PAL_WHITE TFT_WHITE @@ -265,12 +265,12 @@ void drawNew(uint8_t mac[8], bool buttonPressed, tagRecord *&taginfo) { case 16: // buienradar - { - uint8_t refresh = drawBuienradar(filename, cfgobj, taginfo, imageParams); - taginfo->nextupdate = now + refresh * 60; - updateTagImage(filename, mac, refresh, taginfo, imageParams); - break; - } + { + uint8_t refresh = drawBuienradar(filename, cfgobj, taginfo, imageParams); + taginfo->nextupdate = now + refresh * 60; + updateTagImage(filename, mac, refresh, taginfo, imageParams); + break; + } case 17: // tag command sendTagCommand(mac, cfgobj["cmd"].as(), (taginfo->isExternal == false)); @@ -328,7 +328,6 @@ bool updateTagImage(String &filename, uint8_t *dst, uint16_t nextCheckin, tagRec void drawString(TFT_eSprite &spr, String content, int16_t posx, int16_t posy, String font, byte align, uint16_t color, uint16_t size) { // drawString(spr,"test",100,10,"bahnschrift30",TC_DATUM,PAL_RED); if (font != "" && font != "null" && !font.startsWith("fonts/") && !font.startsWith("/fonts/")) { - // u8g2 font U8g2_for_TFT_eSPI u8f; u8f.begin(spr); @@ -345,7 +344,6 @@ void drawString(TFT_eSprite &spr, String content, int16_t posx, int16_t posy, St u8f.print(content); } else if (size > 0) { - // truetype time_t t = millis(); truetypeClass truetype = truetypeClass(); @@ -369,17 +367,15 @@ void drawString(TFT_eSprite &spr, String content, int16_t posx, int16_t posy, St truetype.setTextColor(spr.color16to8(color), spr.color16to8(color)); truetype.textDraw(posx, posy, content); truetype.end(); - // Serial.println("text: '" + content + "' " + String(millis() - t) + "ms"); + // Serial.println("text: '" + content + "' " + String(millis() - t) + "ms"); } else { - // vlw bitmap font spr.setTextDatum(align); if (font != "") spr.loadFont(font, *contentFS); spr.setTextColor(color, PAL_WHITE); spr.drawString(content, posx, posy); if (font != "") spr.unloadFont(); - } } @@ -621,7 +617,7 @@ void drawForecast(String &filename, JsonObject &cfgobj, tagRecord *&taginfo, img uint8_t weathercode = doc["daily"]["weathercode"][dag].as(); if (weathercode > 40) weathercode -= 40; - + int iconcolor = PAL_BLACK; if (weathercode == 55 || weathercode == 65 || weathercode == 75 || weathercode == 82 || weathercode == 86 || weathercode == 95 || weathercode == 96 || weathercode == 99) { iconcolor = PAL_RED; @@ -746,7 +742,8 @@ char *epoch_to_display(time_t utc) { if (local_tm.tm_year < now_tm.tm_year || (local_tm.tm_year == now_tm.tm_year && local_tm.tm_mon < now_tm.tm_mon) || (local_tm.tm_year == now_tm.tm_year && local_tm.tm_mon == now_tm.tm_mon && local_tm.tm_mday < now_tm.tm_mday) || - (local_tm.tm_hour == 0 && local_tm.tm_min == 0)) { + (local_tm.tm_hour == 0 && local_tm.tm_min == 0) || + difftime(utc, now) >= 86400) { strftime(display, sizeof(display), "%d-%m", &local_tm); } else { strftime(display, sizeof(display), "%H:%M", &local_tm); @@ -980,7 +977,7 @@ void drawJsonStream(Stream &stream, String &filename, tagRecord *&taginfo, imgPa if (error) { wsErr("json error " + String(error.c_str())); break; - } else { + } else { drawElement(doc.as(), spr); doc.clear(); } @@ -1231,4 +1228,3 @@ void showIpAddress(String dst) { } } } - diff --git a/ESP32_AP-Flasher/src/ips_display.cpp b/ESP32_AP-Flasher/src/ips_display.cpp index add681d2..e37aa41c 100644 --- a/ESP32_AP-Flasher/src/ips_display.cpp +++ b/ESP32_AP-Flasher/src/ips_display.cpp @@ -1,50 +1,93 @@ - #ifdef YELLOW_IPS_AP #include -#include +#include +#include #include +#include "commstructs.h" +#include "newproto.h" +#include "storage.h" #include "tag_db.h" -Arduino_DataBus *bus = new Arduino_ESP32SPI(11 /* DC */, 10 /* CS */, 12 /* SCK */, 13 /* MOSI */, GFX_NOT_DEFINED /* MISO */); -Arduino_GFX *gfx = new Arduino_ST7789(bus, 1 /* RST */, 3 /* rotation */, true /* IPS */, 170 /* width */, 320 /* height */, 35 /* col offset 1 */, 0 /* row offset 1 */, 35 /* col offset 2 */, 0 /* row offset 2 */); +TFT_eSPI tft2 = TFT_eSPI(); +bool first_run = 0; +time_t last_update = 0; +time_t last_checkin = 0; +int32_t tftid = -1; + +int32_t findId(uint8_t mac[8]) { + for (uint32_t c = 0; c < tagDB.size(); c++) { + tagRecord* tag = nullptr; + tag = tagDB.at(c); + if (memcmp(tag->mac, mac, 8) == 0) { + return c; + } + } + return -1; +} + +void sendAvail(uint8_t wakeupReason) { + struct espAvailDataReq eadr = {0}; + uint8_t mac[6]; + WiFi.macAddress(mac); + memcpy(&eadr.src, mac, 6); + eadr.adr.lastPacketRSSI = WiFi.RSSI(); + eadr.adr.currentChannel = WiFi.channel(); + eadr.adr.hwType = 0xE0; + eadr.adr.wakeupReason = wakeupReason; + eadr.adr.capabilities = 0; + eadr.adr.tagSoftwareVersion = 0; + eadr.adr.customMode = 0; + processDataReq(&eadr, true); + if (wakeupReason) tftid = findId(eadr.src); +} void yellow_ap_display_init(void) { - gfx->begin(); - gfx->fillScreen(BLACK); - gfx->setCursor(10, 10); - gfx->setTextColor(RED); - gfx->println("OpenEPaperLink"); - gfx->setCursor(10, 40); - gfx->setTextColor(GREEN); - gfx->println("Also on 16MB Flash 8MB RAM"); - gfx->setCursor(10, 80); - gfx->setTextColor(WHITE); - gfx->println("Booting"); + tft2.init(); + tft2.setRotation(3); + + tft2.fillScreen(TFT_BLACK); + tft2.setCursor(0, 0, 2); + tft2.setTextColor(TFT_WHITE); + tft2.println(" Init\n"); } -bool first_clear = 0; -uint32_t last_update = 0; void yellow_ap_display_loop(void) { - if (millis() - last_update >= 100) { - if (first_clear == 0) { - first_clear = 1; - gfx->fillScreen(BLACK); - gfx->setTextSize(3); + if (millis() - last_checkin >= 60000) { + sendAvail(0); + last_checkin = millis(); + } + if (millis() - last_update >= 500) { + if (first_run == 0) { + sendAvail(0xFC); + first_run = 1; + } + tagRecord* tag = nullptr; + tag = tagDB.at(tftid); + if (tag->pending) { + String filename = tag->filename; + fs::File file = contentFS->open(filename); + if (!file) { + Serial.print("No current file. Canceling request\n"); + prepareCancelPending(tag->mac); + return; + } + + TFT_eSprite spr = TFT_eSprite(&tft2); + if (tag->len == tft2.width() * tft2.height() * 2) spr.setColorDepth(16); + if (tag->len == tft2.width() * tft2.height()) spr.setColorDepth(8); + spr.createSprite(tft2.width(), tft2.height()); + void* spriteData = spr.getPointer(); + size_t bytesRead = file.readBytes((char*)spriteData, spr.width() * spr.height() * 2); + file.close(); + spr.pushSprite(0,0); + + struct espXferComplete xfc = {0}; + memcpy(xfc.src, tag->mac, 8); + processXferComplete(&xfc, true); } last_update = millis(); - gfx->setCursor(0, 0); - gfx->setTextColor(RED, BLACK); - gfx->println("OpenEPaperLink"); - gfx->setTextColor(GREEN, BLACK); - gfx->println("Millis :"); - gfx->println(millis()); - gfx->setTextColor(WHITE, BLACK); - gfx->println("IP Address :"); - gfx->println(WiFi.localIP().toString()); - gfx->setTextColor(WHITE, BLACK); - gfx->println("Tag Count :"); - gfx->println(getTagCount()); } } + #endif \ No newline at end of file diff --git a/ESP32_AP-Flasher/src/makeimage.cpp b/ESP32_AP-Flasher/src/makeimage.cpp index 5ae2b0ef..cede5774 100644 --- a/ESP32_AP-Flasher/src/makeimage.cpp +++ b/ESP32_AP-Flasher/src/makeimage.cpp @@ -1,11 +1,12 @@ #include #include -#include "storage.h" #include #include #include #include +#include "storage.h" + TFT_eSPI tft = TFT_eSPI(); TFT_eSprite spr = TFT_eSprite(&tft); @@ -24,7 +25,7 @@ void jpg2buffer(String filein, String fileout, imgParam &imageParams) { filein = "/" + filein; } TJpgDec.getFsJpgSize(&w, &h, filein, *contentFS); - if (w==0 && h==0) { + if (w == 0 && h == 0) { wsErr("invalid jpg"); return; } @@ -138,12 +139,12 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t // this looks a bit ugly, but it's performing better than shorter notations switch (best_color_index) { case 1: - if(!is_red) + if (!is_red) buffer[byteIndex] |= (1 << bitIndex); break; case 2: imageParams.hasRed = true; - if(is_red) + if (is_red) buffer[byteIndex] |= (1 << bitIndex); break; case 3: @@ -156,7 +157,7 @@ void spr2color(TFT_eSprite &spr, imgParam &imageParams, uint8_t *buffer, size_t Error error = { static_cast(color.r) + error_bufferold[x].r - static_cast(palette[best_color_index].r), static_cast(color.g) + error_bufferold[x].g - static_cast(palette[best_color_index].g), - static_cast(color.b) + error_bufferold[x].b - static_cast(palette[best_color_index].b) }; + static_cast(color.b) + error_bufferold[x].b - static_cast(palette[best_color_index].b)}; // Burkes Dithering error_buffernew[x].r += error.r / 4.0f; @@ -204,27 +205,36 @@ void spr2buffer(TFT_eSprite &spr, String &fileout, imgParam &imageParams) { fs::File f_out = contentFS->open(fileout, "w"); - long bufw = spr.width(), bufh = spr.height(); - size_t buffer_size = (bufw * bufh) / 8; + switch (imageParams.bpp) { + case 1: + case 2: { + long bufw = spr.width(), bufh = spr.height(); + size_t buffer_size = (bufw * bufh) / 8; #ifdef BOARD_HAS_PSRAM - uint8_t *buffer = (uint8_t *)ps_malloc(buffer_size); + uint8_t *buffer = (uint8_t *)ps_malloc(buffer_size); #else - uint8_t *buffer = (uint8_t *)malloc(buffer_size); + uint8_t *buffer = (uint8_t *)malloc(buffer_size); #endif + if (!buffer) { + Serial.println("Failed to allocate buffer"); + Serial.println("Maximum Continuous Heap Space: " + String(heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT))); + return; + } + spr2color(spr, imageParams, buffer, buffer_size, false); + f_out.write(buffer, buffer_size); - if (!buffer) { - Serial.println("Failed to allocate buffer"); - Serial.println("Maximum Continuous Heap Space: " + String(heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT))); - return; - } - spr2color(spr, imageParams, buffer, buffer_size, false); - f_out.write(buffer, buffer_size); + if (imageParams.hasRed && imageParams.bpp > 1) { + spr2color(spr, imageParams, buffer, buffer_size, true); + f_out.write(buffer, buffer_size); + } + free(buffer); + } break; - if (imageParams.hasRed) { - spr2color(spr, imageParams, buffer, buffer_size, true); - f_out.write(buffer, buffer_size); + case 16: { + size_t spriteDataSize = (spr.getColorDepth() == 1) ? (spr.width() * spr.height() / 8) : ((spr.getColorDepth() == 8) ? (spr.width() * spr.height()) : ((spr.getColorDepth() == 16) ? (spr.width() * spr.height() * 2) : 0)); + f_out.write((const uint8_t *)spr.getPointer(), spriteDataSize); + } break; } - free(buffer); f_out.close(); Serial.println("finished writing buffer " + String(millis() - t) + "ms"); diff --git a/ESP32_AP-Flasher/wwwroot/main.js b/ESP32_AP-Flasher/wwwroot/main.js index b897ef23..07ee2f61 100644 --- a/ESP32_AP-Flasher/wwwroot/main.js +++ b/ESP32_AP-Flasher/wwwroot/main.js @@ -626,7 +626,7 @@ function populateSelectTag(hwtype, capabilities) { cardconfig.forEach(item => { var capcheck = item.capabilities ?? 0; var hwtypeArray = item.hwtype; - if (hwtypeArray.includes(hwtype) && (capabilities & capcheck || capcheck == 0)) { + if ((hwtypeArray.includes(hwtype) || tagTypes[hwtype].contentids.includes(item.id)) && (capabilities & capcheck || capcheck == 0)) { option = document.createElement("option"); option.value = item.id; option.text = item.name; @@ -715,21 +715,38 @@ function processQueue() { const ctx = canvas.getContext('2d'); const imageData = ctx.createImageData(canvas.width, canvas.height); const data = new Uint8ClampedArray(buffer); - const offsetRed = (data.length >= (canvas.width * canvas.height / 8) * 2) ? canvas.width * canvas.height / 8 : 0; - var pixelValue = 0; - for (let i = 0; i < data.length; i++) { - for (let j = 0; j < 8; j++) { - const pixelIndex = i * 8 + j; - if (offsetRed) { - pixelValue = ((data[i] & (1 << (7 - j))) ? 1 : 0) | (((data[i + offsetRed] & (1 << (7 - j))) ? 1 : 0) << 1); - } else { - pixelValue = ((data[i] & (1 << (7 - j))) ? 1 : 0); + + if (tagTypes[hwtype].bpp == 16) { + + const is16Bit = data.length == tagTypes[hwtype].width * tagTypes[hwtype].height * 2; + for (let i = 0; i < tagTypes[hwtype].width * tagTypes[hwtype].height; i++) { + const dataIndex = is16Bit ? i * 2 : i; + const rgb = is16Bit ? (data[dataIndex] << 8) | data[dataIndex + 1] : data[dataIndex]; + + imageData.data[i * 4] = is16Bit ? ((rgb >> 11) & 0x1F) << 3 : (((rgb >> 5) & 0x07) << 5) * 1.13; + imageData.data[i * 4 + 1] = is16Bit ? ((rgb >> 5) & 0x3F) << 2 : (((rgb >> 2) & 0x07) << 5) * 1.13; + imageData.data[i * 4 + 2] = is16Bit ? (rgb & 0x1F) << 3 : ((rgb & 0x03) << 6) * 1.3; + imageData.data[i * 4 + 3] = 255; + } + + } else { + + const offsetRed = (data.length >= (canvas.width * canvas.height / 8) * 2) ? canvas.width * canvas.height / 8 : 0; + var pixelValue = 0; + var colorTable = tagTypes[hwtype].colortable; + for (let i = 0; i < data.length; i++) { + for (let j = 0; j < 8; j++) { + const pixelIndex = i * 8 + j; + if (offsetRed) { + pixelValue = ((data[i] & (1 << (7 - j))) ? 1 : 0) | (((data[i + offsetRed] & (1 << (7 - j))) ? 1 : 0) << 1); + } else { + pixelValue = ((data[i] & (1 << (7 - j))) ? 1 : 0); + } + imageData.data[pixelIndex * 4] = colorTable[pixelValue][0]; + imageData.data[pixelIndex * 4 + 1] = colorTable[pixelValue][1]; + imageData.data[pixelIndex * 4 + 2] = colorTable[pixelValue][2]; + imageData.data[pixelIndex * 4 + 3] = 255; } - let colorTable = tagTypes[hwtype].colortable; - imageData.data[pixelIndex * 4] = colorTable[pixelValue][0]; - imageData.data[pixelIndex * 4 + 1] = colorTable[pixelValue][1]; - imageData.data[pixelIndex * 4 + 2] = colorTable[pixelValue][2]; - imageData.data[pixelIndex * 4 + 3] = 255; } } @@ -861,7 +878,7 @@ async function getTagtype(hwtype) { tagTypes[hwtype] = { busy: true }; const response = await fetch('/tagtypes/' + hwtype.toString(16).padStart(2, '0').toUpperCase() + '.json'); if (!response.ok) { - let data = { name: 'unknown id ' + hwtype, width: 0, height: 0, rotatebuffer: 0, colortable: [], busy: false }; + let data = { name: 'unknown id ' + hwtype, width: 0, height: 0, bpp: 0, rotatebuffer: 0, colortable: [], busy: false }; tagTypes[hwtype] = data; return data; } @@ -870,8 +887,10 @@ async function getTagtype(hwtype) { name: jsonData.name, width: parseInt(jsonData.width), height: parseInt(jsonData.height), + bpp: parseInt(jsonData.bpp), rotatebuffer: jsonData.rotatebuffer, colortable: Object.values(jsonData.colortable), + contentids: Object.values(jsonData.contentids ?? []), busy: false }; tagTypes[hwtype] = data;