tft driver for yellow board

This commit is contained in:
Nic Limper
2023-08-09 23:40:39 +02:00
parent a9c9812525
commit ffd0acff72
7 changed files with 185 additions and 89 deletions

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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 =
+<*>-<usbflasher.cpp>-<swd.cpp>
board_build.flash_mode=qio

View File

@@ -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<int>(), (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<int>();
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<JsonObject>(), spr);
doc.clear();
}
@@ -1231,4 +1228,3 @@ void showIpAddress(String dst) {
}
}
}

View File

@@ -1,50 +1,93 @@
#ifdef YELLOW_IPS_AP
#include <Arduino.h>
#include <Arduino_GFX_Library.h>
#include <FS.h>
#include <TFT_eSPI.h>
#include <WiFi.h>
#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

View File

@@ -1,11 +1,12 @@
#include <Arduino.h>
#include <FS.h>
#include "storage.h"
#include <TFT_eSPI.h>
#include <TJpg_Decoder.h>
#include <makeimage.h>
#include <web.h>
#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<float>(color.r) + error_bufferold[x].r - static_cast<float>(palette[best_color_index].r),
static_cast<float>(color.g) + error_bufferold[x].g - static_cast<float>(palette[best_color_index].g),
static_cast<float>(color.b) + error_bufferold[x].b - static_cast<float>(palette[best_color_index].b) };
static_cast<float>(color.b) + error_bufferold[x].b - static_cast<float>(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");

View File

@@ -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;