feat: Lua app firmware for the E32R40T display module
Boots to a launcher that lists /apps/<name>/main.lua on the SD card and runs the selected app in a vendored Lua 5.4 with gui, input, fs, sys and log bindings. Settings persist as a Lua table in /settings.lua, covering touch calibration and screen rotation, with a settings app to edit both. Rotation is applied after mapping raw touch into the panel's rotation-0 frame, so turning the UI never invalidates a calibration.
This commit is contained in:
+114
@@ -0,0 +1,114 @@
|
||||
#include <SD.h>
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h>
|
||||
#include <XPT2046_Touchscreen.h>
|
||||
|
||||
#include "lua/lua_app.h"
|
||||
#include "settings.h"
|
||||
|
||||
// SD card is on a separate bus from the display/touch (board schematic)
|
||||
static constexpr int SD_CS = 5;
|
||||
static constexpr int SD_SCK = 18;
|
||||
static constexpr int SD_MOSI = 23;
|
||||
static constexpr int SD_MISO = 19;
|
||||
static constexpr int TOUCH_CS = 33;
|
||||
|
||||
TFT_eSPI tft;
|
||||
SPIClass touchSpi(HSPI);
|
||||
SPIClass& sdSpi = SPI;
|
||||
XPT2046_Touchscreen touch(TOUCH_CS);
|
||||
LuaApp app(tft, touch);
|
||||
|
||||
bool sdOk = false;
|
||||
String appPath;
|
||||
|
||||
void drawLauncher() {
|
||||
tft.fillScreen(TFT_WHITE);
|
||||
tft.setTextColor(TFT_BLACK, TFT_WHITE);
|
||||
tft.drawString("esp32-lcd - tap an app", 10, 10);
|
||||
|
||||
File dir = SD.open("/apps");
|
||||
int y = 40;
|
||||
while (File entry = dir.openNextFile()) {
|
||||
if (entry.isDirectory() && entry.name()[0] != '.') {
|
||||
tft.drawRect(10, y - 4, tft.width() - 20, 28, TFT_BLACK);
|
||||
tft.drawString(entry.name(), 18, y);
|
||||
y += 36;
|
||||
}
|
||||
entry.close();
|
||||
}
|
||||
dir.close();
|
||||
}
|
||||
|
||||
// ponytail: first N rows of 36px = first N app dirs, in SD order. A scrollable
|
||||
// menu widget can come later; this is a launcher, not an OS.
|
||||
void launcherTouch() {
|
||||
if (!touch.touched()) return;
|
||||
TS_Point p = touch.getPoint();
|
||||
int16_t tx, ty;
|
||||
LuaApp::mapTouch(tft, p, tx, ty);
|
||||
int idx = (ty - 40) / 36;
|
||||
if (idx < 0) return;
|
||||
|
||||
File dir = SD.open("/apps");
|
||||
int i = 0;
|
||||
while (File entry = dir.openNextFile()) {
|
||||
if (entry.isDirectory() && entry.name()[0] != '.') {
|
||||
if (i++ == idx) {
|
||||
appPath = String("/apps/") + entry.name() + "/main.lua";
|
||||
entry.close();
|
||||
break;
|
||||
}
|
||||
}
|
||||
entry.close();
|
||||
}
|
||||
dir.close();
|
||||
}
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
tft.begin();
|
||||
tft.setRotation(0);
|
||||
tft.fillScreen(TFT_WHITE);
|
||||
touchSpi.begin(14, 12, 13, TOUCH_CS);
|
||||
touch.begin(touchSpi);
|
||||
|
||||
sdSpi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
|
||||
if (!SD.begin(SD_CS, sdSpi)) {
|
||||
tft.setTextColor(TFT_RED, TFT_WHITE);
|
||||
tft.drawString("SD card mount failed", 10, 10);
|
||||
Serial.println("SD mount failed");
|
||||
return;
|
||||
}
|
||||
sdOk = true;
|
||||
settings.load(); // absent file keeps the built-in touch defaults
|
||||
tft.setRotation(settings.rotationIndex());
|
||||
drawLauncher();
|
||||
Serial.println("launcher ready");
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (!sdOk) return; // SD failed to mount; error already drawn
|
||||
|
||||
if (app.running()) {
|
||||
app.loop();
|
||||
if (!app.running()) {
|
||||
appPath = "";
|
||||
tft.setRotation(settings.rotationIndex()); // apps may have rotated the frame
|
||||
drawLauncher();
|
||||
Serial.println("launcher ready");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (appPath.length() > 0) {
|
||||
String path = appPath;
|
||||
appPath = "";
|
||||
Serial.printf("launching %s\n", path.c_str());
|
||||
app.load(path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
launcherTouch();
|
||||
delay(10);
|
||||
}
|
||||
Reference in New Issue
Block a user