Merge branch 'pioarduino' into crowpanel-p4

This commit is contained in:
Manuel
2026-04-22 22:28:41 +02:00
committed by GitHub
240 changed files with 22980 additions and 1048 deletions
@@ -0,0 +1,45 @@
#include "configuration.h"
#ifdef M5STACK_CARDPUTER_ADV
#include "AudioBoard.h"
DriverPins PinsAudioBoardES8311;
AudioBoard board(AudioDriverES8311, PinsAudioBoardES8311);
// M5stack Cardputer ADV specific init
void lateInitVariant()
{
// AudioDriverLogger.begin(Serial, AudioDriverLogLevel::Debug);
// I2C: function, scl, sda
PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire);
// I2S: function, mclk, bck, ws, data_out, data_in
PinsAudioBoardES8311.addI2S(PinFunction::CODEC, DAC_I2S_MCLK, DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_DIN);
// configure codec
CodecConfig cfg;
cfg.input_device = ADC_INPUT_LINE1;
cfg.output_device = DAC_OUTPUT_ALL;
cfg.i2s.bits = BIT_LENGTH_16BITS;
cfg.i2s.rate = RATE_44K;
board.begin(cfg);
// extra ES8311 init
auto es8311_write_reg = [](uint8_t reg, uint8_t val) {
Wire.beginTransmission(0x18); // ES8311 i2c address
Wire.write(reg);
Wire.write(val);
Wire.endTransmission();
};
es8311_write_reg(0x00, 0x80); // reset, power on
es8311_write_reg(0x01, 0xB5); // MCLK = BCLK
es8311_write_reg(0x02, 0x18); // CLOCK_MANAGER/ MULT_PRE=3
es8311_write_reg(0x0D, 0x01); // analog power up
es8311_write_reg(0x12, 0x00); // DAC power up
es8311_write_reg(0x13, 0x10); // enable HP drive
es8311_write_reg(0x32, 0xBF); // DAC volume (0dB)
es8311_write_reg(0x37, 0x08); // EQ bypass
}
#endif
@@ -0,0 +1,144 @@
#include "configuration.h"
#ifdef T5_S3_EPAPER_PRO
#include "Observer.h"
#include "TouchDrvGT911.hpp"
#include "Wire.h"
#include "input/InputBroker.h"
#include "input/TouchScreenImpl1.h"
#include "sleep.h"
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
#include "graphics/niche/InkHUD/InkHUD.h"
#include "graphics/niche/InkHUD/SystemApplet.h"
// Bridges touch events from TouchScreenImpl1 directly into InkHUD,
// bypassing the InputBroker (which is excluded in InkHUD builds).
// Routing mirrors the mini-epaper-s3 two-way rocker pattern:
// - Nav left/right: prevApplet/nextApplet when idle, navUp/Down when a system applet has focus (e.g. menu)
// - Nav up/down: navUp/navDown always (menu scroll)
// - Tap: shortpress (cycle applets / confirm in menu)
// - Long press: longpress (open menu / back)
class TouchInkHUDBridge : public Observer<const InputEvent *>
{
int onNotify(const InputEvent *e) override
{
auto *inkhud = NicheGraphics::InkHUD::InkHUD::getInstance();
// Keep alignment in sync with the current rotation so that visual-frame gestures
// always pass through nav functions without remapping: (rotation + alignment) % 4 == 0.
inkhud->persistence->settings.joystick.alignment = (4 - inkhud->persistence->settings.rotation) % 4;
// Check whether a system applet (e.g. menu) is currently handling input
bool systemHandlingInput = false;
for (NicheGraphics::InkHUD::SystemApplet *sa : inkhud->systemApplets) {
if (sa->handleInput) {
systemHandlingInput = true;
break;
}
}
switch (e->inputEvent) {
case INPUT_BROKER_USER_PRESS:
inkhud->shortpress();
break;
case INPUT_BROKER_SELECT:
inkhud->longpress();
break;
case INPUT_BROKER_LEFT:
if (systemHandlingInput)
inkhud->navUp();
else
inkhud->prevApplet();
break;
case INPUT_BROKER_RIGHT:
if (systemHandlingInput)
inkhud->navDown();
else
inkhud->nextApplet();
break;
case INPUT_BROKER_UP:
inkhud->navUp();
break;
case INPUT_BROKER_DOWN:
inkhud->navDown();
break;
default:
break;
}
return 0;
}
};
static TouchInkHUDBridge touchBridge;
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
TouchDrvGT911 touch;
// Commands the GT911 into standby before the Wire bus is torn down.
// notifyDeepSleep fires before Wire.end() in doDeepSleep(), so I2C is still available here.
struct TouchDeepSleepObserver {
int onDeepSleep(void *)
{
touch.sleep();
return 0;
}
CallbackObserver<TouchDeepSleepObserver, void *> observer{this, &TouchDeepSleepObserver::onDeepSleep};
} static touchDeepSleepObserver;
bool readTouch(int16_t *x, int16_t *y)
{
if (!digitalRead(GT911_PIN_INT)) {
int16_t raw_x;
int16_t raw_y;
if (touch.getPoint(&raw_x, &raw_y)) {
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
// Transform raw GT911 axes to visual-frame coordinates for the current display rotation.
// rotation=3 is the physical identity (device's default orientation).
switch (NicheGraphics::InkHUD::InkHUD::getInstance()->persistence->settings.rotation) {
default:
case 3:
*x = raw_x;
*y = raw_y;
break; // identity
case 2:
*x = (EPD_WIDTH - 1) - raw_y;
*y = raw_x;
break; // 90° CW tilt
case 1:
*x = (EPD_HEIGHT - 1) - raw_x;
*y = (EPD_WIDTH - 1) - raw_y;
break; // 180° flip
case 0:
*x = raw_y;
*y = (EPD_HEIGHT - 1) - raw_x;
break; // 90° CCW tilt
}
#else
*x = raw_x;
*y = raw_y;
#endif
LOG_DEBUG("touched(%d/%d)", *x, *y);
return true;
}
}
return false;
}
// T5-S3-ePaper Pro specific (late-) init
void lateInitVariant(void)
{
touch.setPins(GT911_PIN_RST, GT911_PIN_INT);
if (touch.begin(Wire, GT911_SLAVE_ADDRESS_H, GT911_PIN_SDA, GT911_PIN_SCL)) {
touchDeepSleepObserver.observer.observe(&notifyDeepSleep);
touchScreenImpl1 = new TouchScreenImpl1(EPD_WIDTH, EPD_HEIGHT, readTouch);
touchScreenImpl1->init();
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
touchBridge.observe(touchScreenImpl1);
#endif
} else {
LOG_ERROR("Failed to find touch controller!");
}
}
#endif
@@ -8,20 +8,127 @@
CSE_CST328 tsPanel = CSE_CST328(EINK_WIDTH, EINK_HEIGHT, &Wire, CST328_PIN_RST, CST328_PIN_INT);
static bool is_cst3530 = false;
volatile bool touch_isr = false;
#define CST3530_ADDR 0x1A
bool read_cst3530_touch(int16_t *x, int16_t *y)
{
uint8_t buffer[9] = {0};
uint8_t r_cmd[] = {0xD0, 0x07, 0x00, 0x00};
uint8_t clear_cmd[] = {0xD0, 0x00, 0x02, 0xAB};
Wire.beginTransmission(CST3530_ADDR);
Wire.write(r_cmd, sizeof(r_cmd));
if (Wire.endTransmission() != 0) {
LOG_DEBUG("CST3530 I2C send addr failed");
return false;
}
int read_len = Wire.requestFrom((int)CST3530_ADDR, sizeof(buffer));
if (read_len != sizeof(buffer)) {
LOG_DEBUG("CST3530 read len error: %d (expect 9)", read_len);
return false;
}
int actual_read = Wire.readBytes(buffer, sizeof(buffer));
if (actual_read != sizeof(buffer)) {
LOG_DEBUG("CST3530 read bytes error: %d (expect 9)", actual_read);
return false;
}
uint8_t report_typ = buffer[2];
if (report_typ != 0xFF) {
return false;
}
uint8_t touch_points = buffer[3] & 0x0F;
if (touch_points == 0 || touch_points > 1) {
LOG_DEBUG("CST3530 touch points invalid: %d", touch_points);
return false;
}
*x = buffer[4] + ((uint16_t)(buffer[7] & 0x0F) << 8);
*y = buffer[5] + ((uint16_t)(buffer[7] & 0xF0) << 4);
// LOG_DEBUG("CST3530 touch: num:%d x=%d,y=%d", touch_points, *x, *y);
Wire.beginTransmission(CST3530_ADDR);
Wire.write(clear_cmd, sizeof(clear_cmd));
if (Wire.endTransmission() != 0) {
LOG_DEBUG("CST3530 clear cmd failed");
}
return true;
}
bool readTouch(int16_t *x, int16_t *y)
{
if (tsPanel.getTouches()) {
*x = tsPanel.getPoint(0).x;
*y = tsPanel.getPoint(0).y;
return true;
if (is_cst3530) {
if (touch_isr) {
touch_isr = false;
return read_cst3530_touch(x, y);
}
return false;
} else {
if (tsPanel.getTouches()) {
*x = tsPanel.getPoint(0).x;
*y = tsPanel.getPoint(0).y;
return true;
}
}
return false;
}
static void IRAM_ATTR touchInterruptHandler()
{
touch_isr = true;
}
// T-Deck Pro specific init
void lateInitVariant()
{
tsPanel.begin();
// Reset touch
pinMode(CST328_PIN_RST, OUTPUT);
digitalWrite(CST328_PIN_RST, HIGH);
delay(20);
digitalWrite(CST328_PIN_RST, LOW);
delay(80);
digitalWrite(CST328_PIN_RST, HIGH);
delay(20);
int retry = 5;
uint8_t buffer[7];
uint8_t r_cmd[] = {0x0d0, 0x03, 0x00, 0x00};
// Probe touch chip
while (retry--) {
Wire.beginTransmission(CST3530_ADDR);
Wire.write(r_cmd, sizeof(r_cmd));
if (Wire.endTransmission() == 0) {
Wire.requestFrom((int)CST3530_ADDR, 7);
Wire.readBytes(buffer, 7);
if (buffer[2] == 0xCA && buffer[3] == 0xCA) {
LOG_DEBUG("CST3530 detected");
is_cst3530 = true;
// The CST3530 will automatically enter sleep mode;
// polling should not be used, but rather an interrupt method should be employed.
pinMode(CST328_PIN_INT, INPUT);
attachInterrupt(digitalPinToInterrupt(CST328_PIN_INT), touchInterruptHandler, FALLING);
break;
} else {
LOG_DEBUG("CST3530 not response ~!");
}
}
uint8_t cmd1[] = {0xD0, 0x00, 0x04, 0x00};
Wire.beginTransmission(CST3530_ADDR);
Wire.write(cmd1, sizeof(cmd1));
Wire.endTransmission();
delay(50);
}
touchScreenImpl1 = new TouchScreenImpl1(EINK_WIDTH, EINK_HEIGHT, readTouch);
touchScreenImpl1->init();
}
+7 -1
View File
@@ -182,6 +182,10 @@ void portduinoSetup()
// Force stdout to be line buffered
setvbuf(stdout, stdoutBuffer, _IOLBF, sizeof(stdoutBuffer));
// We do this super early so that we can log from the rest of the init code
concurrency::hasBeenSetup = true;
consoleInit();
if (portduino_config.force_simradio == true) {
portduino_config.lora_module = use_simradio;
} else if (configPath != nullptr) {
@@ -650,7 +654,9 @@ void portduinoSetup()
if (verboseEnabled && portduino_config.logoutputlevel != level_trace) {
portduino_config.logoutputlevel = level_debug;
}
if (portduino_config.lora_spi_dev != "") {
portduinoSetOptions({.realHardware = true});
}
return;
}
+51
View File
@@ -4,6 +4,57 @@
#include <stm32wle5xx.h>
#include <stm32wlxx_hal.h>
// ─── Bootloader redirect ──────────────────────────────────────────────────────
//
// Why .noinit + constructor instead of TAMP backup registers:
//
// The STM32duino startup sequence initialises clocks which may call
// __HAL_RCC_BACKUPRESET_FORCE/RELEASE when configuring the LSE oscillator,
// wiping the entire backup domain (including TAMP->BKP0R) before setup()
// ever runs. The backup-register approach therefore cannot reliably survive
// a soft reset in this toolchain.
//
// Solution: store the magic in a .noinit SRAM variable.
// - NVIC_SystemReset() does NOT clear SRAM.
// - The linker script skips zero-init for .noinit sections.
// - __attribute__((constructor)) fires before main()/HAL_Init(), so we can
// intercept and jump before anything disturbs peripheral state.
#define BOOTLOADER_MAGIC 0xD00DB007UL
#define SYS_MEM_BASE 0x1FFF0000UL
// Placed in .noinit — not zeroed at startup, survives NVIC_SystemReset().
__attribute__((section(".noinit"), used)) volatile uint32_t g_bootloaderMagic;
// Fires before main() / HAL_Init(). Must use only core Cortex-M registers.
__attribute__((constructor(101), used)) static void earlyBootCheck(void)
{
if (g_bootloaderMagic != BOOTLOADER_MAGIC)
return;
g_bootloaderMagic = 0;
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
for (int i = 0; i < 8; i++) {
NVIC->ICER[i] = 0xFFFFFFFF;
NVIC->ICPR[i] = 0xFFFFFFFF;
}
__DSB();
__ISB();
SCB->VTOR = SYS_MEM_BASE;
__set_MSP(*(volatile uint32_t *)SYS_MEM_BASE);
((void (*)(void))(*(volatile uint32_t *)(SYS_MEM_BASE + 4)))();
while (1)
;
}
void enterDfuMode()
{
g_bootloaderMagic = BOOTLOADER_MAGIC;
HAL_NVIC_SystemReset();
}
void setBluetoothEnable(bool enable) {}
void playStartMelody() {}