mirror of
https://github.com/meshtastic/firmware.git
synced 2026-09-16 16:41:24 -04:00
feat: Support Elecrow ThinkNode M9 (#10908)
* thinknode-m9 variant * move lora to SPI1 device * enable SDcard * use HSPI * BaseUI tft -> HSPI * buzzer, webdav lib * fix build issues * M9 default to MUI, no BT, short ringtone * add keyboard long-press config * update variant * add ThingNode-M9 GPS string * GPS 115200 baud * Basic BaseUI support * Fixup power detection * Compass and KB fixes for M9 * add timed Lock::lock() * add SD card * point device-ui to thinknode m9 draft branch * trunk fmt * fix FusionCompass * Fix t-deck-tft linker arg list overflow in CI * SDcard/lora fix: SPI1 must not be declared twice in arduino 3.x -> reuse SPI1 defined in FSCommon.cpp * update battery parameters * reinit SD card when updating; fix PSRAM size * update lib versions * fix wakeup on key press (KB_INT) * fix default nag_timeout for TFT/MUI devices with buzzer * increase PSRAM and SD freq * trunk fmt * update lovyanGFX 1.2.26 * update device-ui commit reference * fix screen definition * remove DONE; maybe a keyword or other used identifier * fixed CI error nag_timeout * fix prepareSleep initialization * trunk fmt * reduce SD SPI frequency * update device-ui * fix SDcard issue * stage * fix device-ui commit reference * fix device-ui commit * update device-ui commit (fixed keyboard lag) * fix QMI8658 * trunk fmt * update .ini meta information, align SD freq * fix device-ui reference to target (ready to merge) * device-ui for all other targets * make the rabbit happy * trunk fmt * fixed lock screen * fix compile error * SPI lock timeout * apply device-ui fix * revert bad RadioLib commit hash in platformio.ini Co-authored-by: mverch67 <71137295+mverch67@users.noreply.github.com> * fix wrong commit hash change * fix fix commit fix * I love changing random numbers in random files --------- Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
32 files changed
+1098
-38
No files matched your search
+1
-1
@@ -137,7 +137,7 @@ lib_deps =
|
||||
[device-ui_base]
|
||||
lib_deps =
|
||||
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
|
||||
https://github.com/meshtastic/device-ui/archive/e1de01e0b3c4a6b149c00e95d59cfb0cca7ad49e.zip
|
||||
https://github.com/meshtastic/device-ui/archive/adfbd3811a53b6aed0649c8d8f078118c042a407.zip
|
||||
custom_sdkconfig =
|
||||
# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set
|
||||
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
|
||||
|
||||
+13
-11
@@ -173,23 +173,25 @@ static void lsIdle()
|
||||
powerFSM.trigger(EVENT_SERIAL_CONNECTED);
|
||||
break;
|
||||
|
||||
default:
|
||||
// We woke for some other reason (button press, device IRQ interrupt)
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
bool pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
|
||||
#else
|
||||
case ESP_SLEEP_WAKEUP_GPIO: {
|
||||
bool pressed = false;
|
||||
#if defined(BUTTON_PIN)
|
||||
pressed = !digitalRead(config.device.button_gpio ? config.device.button_gpio : BUTTON_PIN);
|
||||
#elif defined(KB_INT)
|
||||
// keyboard press (probably) triggered GPIO interrupt
|
||||
pressed = true;
|
||||
#endif
|
||||
if (pressed) { // If we woke because of press, instead generate a PRESS event.
|
||||
if (pressed) {
|
||||
powerFSM.trigger(EVENT_PRESS);
|
||||
} else {
|
||||
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
||||
// we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code
|
||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
||||
// we lie and say "wake timer" because the interrupt will be handled by the regular IRQ code
|
||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Someone says we can't sleep now, so just save some power by sleeping the CPU for 100ms or so
|
||||
delay(100);
|
||||
|
||||
@@ -26,6 +26,11 @@ void Lock::lock()
|
||||
}
|
||||
}
|
||||
|
||||
bool Lock::lock(uint32_t timeout)
|
||||
{
|
||||
return xSemaphoreTake(handle, pdMS_TO_TICKS(timeout)) == pdTRUE;
|
||||
}
|
||||
|
||||
void Lock::unlock()
|
||||
{
|
||||
if (xSemaphoreGive(handle) == false) {
|
||||
@@ -39,6 +44,11 @@ Lock::~Lock() {}
|
||||
|
||||
void Lock::lock() {}
|
||||
|
||||
bool Lock::lock(uint32_t)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void Lock::unlock() {}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -22,6 +22,11 @@ class Lock
|
||||
// Must not be called from an ISR.
|
||||
void lock();
|
||||
|
||||
/// Locks the lock with timeout.
|
||||
//
|
||||
// Must not be called from an ISR.
|
||||
bool lock(uint32_t timeout);
|
||||
|
||||
// Unlocks the lock.
|
||||
//
|
||||
// Must not be called from an ISR.
|
||||
|
||||
@@ -253,6 +253,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define BBQ10_KB_ADDR 0x1F
|
||||
#define MPR121_KB_ADDR 0x5A
|
||||
#define TCA8418_KB_ADDR 0x34
|
||||
#define TSTC8_KB_ADDR 0x6C // STC8H companion-MCU keypad on the ThinkNode-M9
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// SENSOR
|
||||
@@ -270,6 +271,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#define QMC5883L_ADDR 0x0D
|
||||
#define HMC5883L_ADDR 0x1E
|
||||
#define MMC5983MA_ADDR 0x30
|
||||
#define QMC6309_ADDR 0x7C
|
||||
#define SHTC3_ADDR 0x70
|
||||
#define LPS22HB_ADDR 0x5C
|
||||
#define LPS22HB_ADDR_ALT 0x5D
|
||||
|
||||
@@ -31,21 +31,21 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, MPR121KB, TCA8418KB};
|
||||
return firstOfOrNONE(6, types);
|
||||
ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, MPR121KB, TCA8418KB, STC8HKB};
|
||||
return firstOfOrNONE(7, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, SC7A20, BMA423, LSM6DS3, BMX160, STK8BAXX,
|
||||
ICM20948, BMM150, BMI270, ICM42607P, ISM330DHCX, QMA6100P};
|
||||
return firstOfOrNONE(13, types);
|
||||
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, SC7A20, BMA423, LSM6DS3, BMX160, STK8BAXX,
|
||||
ICM20948, BMM150, BMI270, ICM42607P, ISM330DHCX, QMA6100P, QMI8658};
|
||||
return firstOfOrNONE(14, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstMagnetometer() const
|
||||
{
|
||||
ScanI2C::DeviceType types[] = {MMC5983MA, IIS2MDCTR};
|
||||
return firstOfOrNONE(2, types);
|
||||
ScanI2C::DeviceType types[] = {MMC5983MA, IIS2MDCTR, QMC6309};
|
||||
return firstOfOrNONE(3, types);
|
||||
}
|
||||
|
||||
ScanI2C::FoundDevice ScanI2C::firstAQI() const
|
||||
|
||||
@@ -42,6 +42,7 @@ class ScanI2C
|
||||
QMC5883L,
|
||||
HMC5883L,
|
||||
MMC5983MA,
|
||||
QMC6309,
|
||||
PMSA003I,
|
||||
QMA6100P,
|
||||
MPU6050,
|
||||
@@ -105,6 +106,7 @@ class ScanI2C
|
||||
IIS2MDCTR,
|
||||
ISM330DHCX,
|
||||
SPA06,
|
||||
STC8HKB, // STC8H companion-MCU keypad (ThinkNode-M9)
|
||||
DS248X,
|
||||
HM330X
|
||||
} DeviceType;
|
||||
|
||||
@@ -444,6 +444,7 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
type = BBQ10KB;
|
||||
logFoundDevice("BB Q10", (uint8_t)addr.address);
|
||||
break;
|
||||
SCAN_SIMPLE_CASE(TSTC8_KB_ADDR, STC8HKB, "STC8H KB", (uint8_t)addr.address);
|
||||
SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address);
|
||||
#ifdef HAS_NCP5623
|
||||
SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623", (uint8_t)addr.address);
|
||||
@@ -1093,6 +1094,20 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
|
||||
foundDevices[addr] = type;
|
||||
}
|
||||
}
|
||||
|
||||
// The QMC6309 magnetometer sits at 0x7C, above the general scan ceiling (the loop above stops at 0x77 to
|
||||
// avoid the reserved 0x78-0x7F block). Probe it explicitly. Gated on the SensorLib driver being present so
|
||||
// only boards that can actually drive the chip poke this reserved address.
|
||||
#if __has_include(<SensorQMC6309.hpp>)
|
||||
addr.address = QMC6309_ADDR;
|
||||
i2cBus->beginTransmission(addr.address);
|
||||
if (i2cBus->endTransmission() == 0 &&
|
||||
getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1) == 0x90 /* QMC6309 chip id */) {
|
||||
deviceAddresses[QMC6309] = addr;
|
||||
foundDevices[addr] = QMC6309;
|
||||
logFoundDevice("QMC6309", (uint8_t)addr.address);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ScanI2CTwoWire::scanPort(I2CPort port)
|
||||
|
||||
+5
-3
@@ -1686,7 +1686,7 @@ GnssModel_t GPS::probe(int serialSpeed)
|
||||
{"AG3335", "$PAIR021,AG3335", GNSS_MODEL_AG3335},
|
||||
{"AG3352", "$PAIR021,AG3352", GNSS_MODEL_AG3352},
|
||||
{"RYS3520", "$PAIR021,REYAX_RYS3520_V2", GNSS_MODEL_AG3352},
|
||||
{"UC6580", "UC6580", GNSS_MODEL_UC6580},
|
||||
{"UC6580", "UC6580", GNSS_MODEL_UC6580}
|
||||
// as L76K is sort of a last ditch effort, we won't attempt to detect it by startup messages for now.
|
||||
/*{"L76K", "SW=URANUS", GNSS_MODEL_MTK}*/};
|
||||
GnssModel_t detectedDriver = getProbeResponse(500, passive_detect, serialSpeed);
|
||||
@@ -1713,8 +1713,10 @@ GnssModel_t GPS::probe(int serialSpeed)
|
||||
case 1: {
|
||||
|
||||
// Unicore UFirebirdII Series: UC6580, UM620, UM621, UM670A, UM680A, or UM681A,or CM121
|
||||
std::vector<ChipInfo> unicore = {
|
||||
{"UC6580", "UC6580", GNSS_MODEL_UC6580}, {"UM600", "UM600", GNSS_MODEL_UC6580}, {"CM121", "CM121", GNSS_MODEL_CM121}};
|
||||
std::vector<ChipInfo> unicore = {{"UC6580", "UC6580", GNSS_MODEL_UC6580},
|
||||
{"UM600", "UM600", GNSS_MODEL_UC6580},
|
||||
{"CM121", "CM121", GNSS_MODEL_CM121},
|
||||
{"CC1167Q", "CC1167Q", GNSS_MODEL_CM121}};
|
||||
PROBE_FAMILY("Unicore Family", "$PDTINFO", unicore, 500);
|
||||
currentDelay = 20;
|
||||
currentStep = 2;
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "target_specific.h"
|
||||
#ifdef COMPASS_SENSOR_DEBUG
|
||||
#include "motion/MotionSensor.h"
|
||||
#endif
|
||||
#include <OLEDDisplay.h>
|
||||
#include <cstring>
|
||||
#include <gps/RTC.h>
|
||||
@@ -1776,6 +1779,26 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
||||
const int *textPos = getTextPositions(display);
|
||||
const bool compactPanel = graphics::isCompactPanel(display);
|
||||
|
||||
#ifdef COMPASS_SENSOR_DEBUG
|
||||
// Optional raw IMU accel + magnetometer x/y/z readout for on-device axis/sign tuning.
|
||||
{
|
||||
char dbg[40];
|
||||
float sx = 0, sy = 0, sz = 0;
|
||||
uint32_t age = 0;
|
||||
if (MotionSensor::getLatestCompassAccelSample(sx, sy, sz, age))
|
||||
snprintf(dbg, sizeof(dbg), "A %.2f %.2f %.2f", sx, sy, sz);
|
||||
else
|
||||
snprintf(dbg, sizeof(dbg), "A ---");
|
||||
display->drawString(x, textPos[line++], dbg);
|
||||
|
||||
if (MotionSensor::getLatestCompassMagSample(sx, sy, sz, age))
|
||||
snprintf(dbg, sizeof(dbg), "M %.2f %.2f %.2f", sx, sy, sz);
|
||||
else
|
||||
snprintf(dbg, sizeof(dbg), "M ---");
|
||||
display->drawString(x, textPos[line++], dbg);
|
||||
}
|
||||
#endif
|
||||
|
||||
// === First Row: My Location ===
|
||||
#if HAS_GPS
|
||||
bool origBold = config.display.heading_bold;
|
||||
|
||||
@@ -235,10 +235,8 @@ DeviceScreen *deviceScreen = nullptr;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Get notified when the system is entering light sleep
|
||||
CallbackObserver<DeviceScreen, void *> tftSleepObserver =
|
||||
CallbackObserver<DeviceScreen, void *>(deviceScreen, &DeviceScreen::prepareSleep);
|
||||
CallbackObserver<DeviceScreen, esp_sleep_wakeup_cause_t> endSleepObserver =
|
||||
CallbackObserver<DeviceScreen, esp_sleep_wakeup_cause_t>(deviceScreen, &DeviceScreen::wakeUp);
|
||||
static CallbackObserver<DeviceScreen, void *> *tftSleepObserver = nullptr;
|
||||
static CallbackObserver<DeviceScreen, esp_sleep_wakeup_cause_t> *endSleepObserver = nullptr;
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -277,6 +275,19 @@ class ReentrantSpiLock : public ISpiLock
|
||||
depth = 1;
|
||||
}
|
||||
|
||||
bool lock(uint32_t timeout) override
|
||||
{
|
||||
ThreadId self = currentThread();
|
||||
if (depth && owner == self) {
|
||||
depth++;
|
||||
return true;
|
||||
}
|
||||
bool result = spiLock->lock(timeout);
|
||||
owner = self;
|
||||
depth = 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
void unlock(void) override
|
||||
{
|
||||
if (--depth == 0) {
|
||||
@@ -413,8 +424,14 @@ void tftSetup(void)
|
||||
|
||||
if (deviceScreen) {
|
||||
#ifdef ARCH_ESP32
|
||||
tftSleepObserver.observe(¬ifyLightSleep);
|
||||
endSleepObserver.observe(¬ifyLightSleepEnd);
|
||||
if (!tftSleepObserver) {
|
||||
tftSleepObserver = new CallbackObserver<DeviceScreen, void *>(deviceScreen, &DeviceScreen::prepareSleep);
|
||||
}
|
||||
if (!endSleepObserver) {
|
||||
endSleepObserver = new CallbackObserver<DeviceScreen, esp_sleep_wakeup_cause_t>(deviceScreen, &DeviceScreen::wakeUp);
|
||||
}
|
||||
tftSleepObserver->observe(¬ifyLightSleep);
|
||||
endSleepObserver->observe(¬ifyLightSleepEnd);
|
||||
xTaskCreatePinnedToCore(tft_task_handler, "tft", TFT_TASK_STACK_SIZE, NULL, 1, NULL, 0);
|
||||
#elif defined(ARCH_PORTDUINO)
|
||||
std::thread *tft_task = new std::thread([] { tft_task_handler(); });
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
#include "STC8HKeyboard.h"
|
||||
|
||||
#if defined(ELECROW_ThinkNode_M9)
|
||||
#include "cardKbI2cImpl.h"
|
||||
|
||||
#include "configuration.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// STC8H companion-MCU keypad driver (ThinkNode-M9).
|
||||
//
|
||||
// The original STC8HKeyboard.cpp was lost from the reference source tree, so
|
||||
// this was recovered from the linked reference firmware.elf (the .o was an LTO
|
||||
// object with no machine code; the final ELF had the real inlined bodies).
|
||||
//
|
||||
// How the hardware works:
|
||||
// - The STC8H raises KB_INT (rising edge, idle-low) when a key is pressed. The ISR
|
||||
// latches key_event; is_key_event() just returns that flag.
|
||||
// - The pressed key code is read over I2C from register 0x05.
|
||||
// - is_key_state() polls KB_INT directly to keep the backlight lit while a
|
||||
// key is held.
|
||||
// - Battery voltage lives in registers 0x01..0x04, little-endian.
|
||||
// - Sleep is requested by writing 0x01 to the STATE register (0x06).
|
||||
// - The keypad backlight (KB_LED) and torch (PIN_LED) are plain host GPIOs,
|
||||
// not I2C commands.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
STC8HKeyboard Stc8HKeyBoard;
|
||||
|
||||
// ISR latched on each KB_INT rising edge (a key was pressed).
|
||||
static void has_key_event()
|
||||
{
|
||||
Stc8HKeyBoard.key_event = true;
|
||||
if (cardKbI2cImpl) {
|
||||
cardKbI2cImpl->setIntervalFromNow(0);
|
||||
// runASAP = true;
|
||||
BaseType_t higherWake = 0;
|
||||
concurrency::mainDelay.interruptFromISR(&higherWake);
|
||||
}
|
||||
}
|
||||
|
||||
void STC8HKeyboard::writeRegister(uint8_t reg, uint8_t val)
|
||||
{
|
||||
_pWire->beginTransmission(_I2C_addr);
|
||||
_pWire->write(reg);
|
||||
_pWire->write(val);
|
||||
_pWire->endTransmission();
|
||||
}
|
||||
|
||||
uint8_t STC8HKeyboard::readRegister(uint8_t reg)
|
||||
{
|
||||
_pWire->beginTransmission(_I2C_addr);
|
||||
_pWire->write(reg);
|
||||
if (_pWire->endTransmission(false) != 0)
|
||||
return 0xFF;
|
||||
if (_pWire->requestFrom(_I2C_addr, (uint8_t)1) != 1)
|
||||
return 0xFF;
|
||||
return _pWire->read();
|
||||
}
|
||||
|
||||
void STC8HKeyboard::begin(uint8_t addr, TwoWire *wire)
|
||||
{
|
||||
LOG_DEBUG("STC8HKeyboard::begin() addr=0x%02x", addr);
|
||||
_I2C_addr = addr;
|
||||
_pWire = wire;
|
||||
pinMode(KB_INT, INPUT);
|
||||
#ifdef KB_LED
|
||||
pinMode(KB_LED, OUTPUT);
|
||||
#endif
|
||||
#ifdef PIN_LED
|
||||
pinMode(PIN_LED, OUTPUT);
|
||||
#endif
|
||||
attachInterrupt(KB_INT, has_key_event, RISING);
|
||||
_pWire->begin();
|
||||
Keyboard_state = true;
|
||||
#ifdef ARCH_ESP32
|
||||
// Detach/reattach the key interrupt around ESP32 light sleep
|
||||
lsObserver.observe(¬ifyLightSleep);
|
||||
lsEndObserver.observe(¬ifyLightSleepEnd);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool STC8HKeyboard::is_Keyboard_begin()
|
||||
{
|
||||
return Keyboard_state;
|
||||
}
|
||||
|
||||
// A key is currently active (KB_INT held); used to wake the keypad backlight.
|
||||
bool STC8HKeyboard::is_key_state()
|
||||
{
|
||||
return digitalRead(KB_INT);
|
||||
}
|
||||
|
||||
// A key-press interrupt has been latched since the flag was last cleared.
|
||||
bool STC8HKeyboard::is_key_event()
|
||||
{
|
||||
return key_event;
|
||||
}
|
||||
|
||||
uint8_t STC8HKeyboard::bsp_get_key_value()
|
||||
{
|
||||
return readRegister(0x01);
|
||||
}
|
||||
|
||||
// Battery millivolts: registers 0x01..0x04 read little-endian, low 16 bits.
|
||||
uint16_t STC8HKeyboard::bsp_get_battery_voltage()
|
||||
{
|
||||
if (!Keyboard_state)
|
||||
return 0;
|
||||
uint32_t voltage = 0;
|
||||
for (uint8_t i = 0; i < 4; i++)
|
||||
voltage |= (uint32_t)readRegister(STC8_REG_ADDR_BATTERY + i) << (i * 8);
|
||||
return voltage > 0xFFFF ? 0xFFFF : (uint16_t)voltage;
|
||||
}
|
||||
|
||||
void STC8HKeyboard::set_keyboard_blight(bool state)
|
||||
{
|
||||
#ifdef KB_LED
|
||||
digitalWrite(KB_LED, state);
|
||||
#else
|
||||
(void)state; // KB_LED pin not defined for this board
|
||||
#endif
|
||||
}
|
||||
|
||||
void STC8HKeyboard::switch_flashlight()
|
||||
{
|
||||
#ifdef PIN_LED
|
||||
digitalWrite(PIN_LED, !digitalRead(PIN_LED));
|
||||
#endif
|
||||
// else: torch pin unresolved on this board (old board used PIN_LED 13,
|
||||
// which the current variant assigns to BATTERY_PIN) -- see variant.h.
|
||||
}
|
||||
|
||||
void STC8HKeyboard::set_sleep_status(void)
|
||||
{
|
||||
writeRegister(STC8_REG_ADDR_STATE, 0x01);
|
||||
_pWire->end();
|
||||
}
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Detach the key interrupt before ESP32 light sleep, so it can't fire while asleep.
|
||||
int STC8HKeyboard::beforeLightSleep(void *unused)
|
||||
{
|
||||
detachInterrupt(KB_INT);
|
||||
return 0; // Indicates success
|
||||
}
|
||||
|
||||
// Reattach the key interrupt after waking from light sleep.
|
||||
int STC8HKeyboard::afterLightSleep(esp_sleep_wakeup_cause_t cause)
|
||||
{
|
||||
attachInterrupt(KB_INT, has_key_event, RISING);
|
||||
return 0; // Indicates success
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ELECROW_ThinkNode_M9
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
#ifndef _STC8H_KEYBOARD_H_
|
||||
#define _STC8H_KEYBOARD_H_
|
||||
|
||||
#include "configuration.h"
|
||||
#include "kbI2cBase.h"
|
||||
#include <Wire.h>
|
||||
#if defined(ELECROW_ThinkNode_M9)
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
#include "sleep.h" // notifyLightSleep / notifyLightSleepEnd + esp_sleep_wakeup_cause_t
|
||||
#endif
|
||||
|
||||
// Registers exposed by the STC8H companion MCU over I2C.
|
||||
#define STC8_REG_ADDR_BATTERY 0x01
|
||||
#define STC8_REG_ADDR_MATRIX_KEY 0x05
|
||||
#define STC8_REG_ADDR_STATE 0x06
|
||||
|
||||
class STC8HKeyboard
|
||||
{
|
||||
public:
|
||||
STC8HKeyboard(){};
|
||||
|
||||
void begin(uint8_t addr, TwoWire *wire);
|
||||
|
||||
void set_sleep_status(void);
|
||||
|
||||
uint16_t bsp_get_battery_voltage();
|
||||
|
||||
bool is_key_event();
|
||||
|
||||
bool is_Keyboard_begin();
|
||||
|
||||
bool is_key_state();
|
||||
|
||||
uint8_t bsp_get_key_value();
|
||||
|
||||
void set_keyboard_blight(bool state);
|
||||
|
||||
void switch_flashlight();
|
||||
|
||||
uint8_t readRegister(uint8_t reg);
|
||||
|
||||
bool key_event;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Detach/reattach the KB_INT interrupt around ESP32 light sleep, so the
|
||||
// companion MCU's key interrupt can't fire spuriously while asleep.
|
||||
int beforeLightSleep(void *unused);
|
||||
int afterLightSleep(esp_sleep_wakeup_cause_t cause);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void writeRegister(uint8_t reg, uint8_t val);
|
||||
|
||||
uint8_t _I2C_addr = TSTC8_KB_ADDR;
|
||||
|
||||
TwoWire *_pWire = &Wire;
|
||||
|
||||
bool Keyboard_state = false;
|
||||
|
||||
#ifdef ARCH_ESP32
|
||||
// Get notified when light sleep begins and ends (mirrors TwoButton / Power)
|
||||
CallbackObserver<STC8HKeyboard, void *> lsObserver =
|
||||
CallbackObserver<STC8HKeyboard, void *>(this, &STC8HKeyboard::beforeLightSleep);
|
||||
CallbackObserver<STC8HKeyboard, esp_sleep_wakeup_cause_t> lsEndObserver =
|
||||
CallbackObserver<STC8HKeyboard, esp_sleep_wakeup_cause_t>(this, &STC8HKeyboard::afterLightSleep);
|
||||
#endif
|
||||
};
|
||||
|
||||
extern STC8HKeyboard Stc8HKeyBoard;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -51,6 +51,10 @@ void CardKbI2cImpl::init()
|
||||
// assign an arbitrary value to distinguish from other models
|
||||
kb_model = 0x84;
|
||||
break;
|
||||
case ScanI2C::DeviceType::STC8HKB:
|
||||
// assign an arbitrary value to distinguish from other models
|
||||
kb_model = 0x12;
|
||||
break;
|
||||
default:
|
||||
// use this as default since it's also just zero
|
||||
LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00", kb_info.type);
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
#include "TCA8418Keyboard.h"
|
||||
#endif
|
||||
|
||||
#if defined(ELECROW_ThinkNode_M9)
|
||||
#include "STC8HKeyboard.h"
|
||||
#include "graphics/Screen.h" // for the global `screen` + FrameFocus
|
||||
#include "graphics/draw/NotificationRenderer.h" // for resetBanner()
|
||||
#endif
|
||||
|
||||
extern ScanI2C::DeviceAddress cardkb_found;
|
||||
extern uint8_t kb_model;
|
||||
|
||||
@@ -64,6 +70,11 @@ int32_t KbI2cBase::runOnce()
|
||||
// resolved via the scanner: WIRE1 may be a bridged bus rather
|
||||
// than the local Wire1 (e.g. SenseCAP Indicator)
|
||||
i2cBus = ScanI2CTwoWire::fetchI2CBus(cardkb_found);
|
||||
#if defined(ELECROW_ThinkNode_M9)
|
||||
if (cardkb_found.address == TSTC8_KB_ADDR) {
|
||||
Stc8HKeyBoard.begin(TSTC8_KB_ADDR, &Wire1);
|
||||
}
|
||||
#endif
|
||||
if (cardkb_found.address == BBQ10_KB_ADDR) {
|
||||
Q10keyboard.begin(BBQ10_KB_ADDR, i2cBus);
|
||||
Q10keyboard.setBacklight(0);
|
||||
@@ -79,6 +90,11 @@ int32_t KbI2cBase::runOnce()
|
||||
case ScanI2C::WIRE:
|
||||
LOG_DEBUG("Use I2C Bus 0 (the first one)");
|
||||
i2cBus = &Wire;
|
||||
#if defined(ELECROW_ThinkNode_M9)
|
||||
if (cardkb_found.address == TSTC8_KB_ADDR) {
|
||||
Stc8HKeyBoard.begin(TSTC8_KB_ADDR, &Wire);
|
||||
}
|
||||
#endif
|
||||
if (cardkb_found.address == BBQ10_KB_ADDR) {
|
||||
Q10keyboard.begin(BBQ10_KB_ADDR, &Wire);
|
||||
Q10keyboard.setBacklight(0);
|
||||
@@ -546,6 +562,104 @@ int32_t KbI2cBase::runOnce()
|
||||
}
|
||||
break;
|
||||
}
|
||||
#if defined(ELECROW_ThinkNode_M9)
|
||||
case 0x12: { // STC8H companion-MCU keypad (ThinkNode-M9)
|
||||
Stc8HKeyBoard.key_event = false;
|
||||
InputEvent e = {};
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.source = this->_originName;
|
||||
uint8_t c = Stc8HKeyBoard.bsp_get_key_value(); // unsigned so the 0x8x/0xbx codes match
|
||||
switch (c) {
|
||||
case 0x81: // Mute
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = INPUT_BROKER_MSG_MUTE_TOGGLE;
|
||||
break;
|
||||
case 0x82: // Home
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
graphics::NotificationRenderer::resetBanner();
|
||||
// TODO(M9): also reset CannedMessage/PresetMessage state once those modules are ported
|
||||
if (screen)
|
||||
screen->setFrames(graphics::Screen::FOCUS_FAULT);
|
||||
break;
|
||||
case 0x83: // Time
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
graphics::NotificationRenderer::resetBanner();
|
||||
// TODO(M9): also reset CannedMessage/PresetMessage state once those modules are ported
|
||||
if (screen)
|
||||
screen->setFrames(graphics::Screen::FOCUS_CLOCK);
|
||||
break;
|
||||
case 0x84:
|
||||
e.inputEvent = INPUT_BROKER_GPS_TOGGLE;
|
||||
Stc8HKeyBoard.switch_flashlight();
|
||||
break;
|
||||
case 0x85: // FM
|
||||
e.inputEvent = INPUT_BROKER_SEND_PING;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x86: // FM (long press)
|
||||
e.inputEvent = INPUT_BROKER_CANCEL;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x87: // Preset
|
||||
graphics::NotificationRenderer::resetBanner();
|
||||
// TODO(M9): also reset CannedMessage state once that module is ported
|
||||
e.inputEvent = INPUT_BROKER_SELECT_LONG;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb5: // Up
|
||||
e.inputEvent = INPUT_BROKER_UP;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb4: // Left
|
||||
e.inputEvent = INPUT_BROKER_LEFT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb6: // Down
|
||||
e.inputEvent = INPUT_BROKER_DOWN;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0xb7: // Right
|
||||
e.inputEvent = INPUT_BROKER_RIGHT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x20: // Space
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0x20;
|
||||
break;
|
||||
case 0x0d: // Enter
|
||||
e.inputEvent = INPUT_BROKER_SELECT;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x08: // Del
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x89: // Del (long press)
|
||||
e.inputEvent = INPUT_BROKER_BACK;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
case 0x88: // Invalid key value
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = 0;
|
||||
break;
|
||||
default: // all other keys (printable ASCII)
|
||||
if ((c >= 0x20) && (c <= 0x7F)) {
|
||||
e.inputEvent = INPUT_BROKER_ANYKEY;
|
||||
e.kbchar = c;
|
||||
} else {
|
||||
e.inputEvent = INPUT_BROKER_NONE;
|
||||
e.kbchar = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (e.inputEvent != INPUT_BROKER_NONE) {
|
||||
// LOG_DEBUG("STC8H companion-MCU keypad key event: 0x%02x", c);
|
||||
this->notifyObservers(&e);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
LOG_WARN("Unknown kb_model 0x%02x", kb_model);
|
||||
}
|
||||
|
||||
@@ -761,6 +761,10 @@ void setup()
|
||||
// assign an arbitrary value to distinguish from other models
|
||||
kb_model = 0x84;
|
||||
break;
|
||||
case ScanI2C::DeviceType::STC8HKB:
|
||||
// assign an arbitrary value to distinguish from other models
|
||||
kb_model = 0x12;
|
||||
break;
|
||||
default:
|
||||
// use this as default since it's also just zero
|
||||
LOG_WARN("kb_info.type unknown(0x%02x), set kb_model=0x00", kb_info.type);
|
||||
|
||||
+6
-8
@@ -1032,7 +1032,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||
strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32);
|
||||
|
||||
#if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \
|
||||
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT) || defined(RAK_WISMESH_TAP_V2)) && \
|
||||
defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT) || defined(RAK_WISMESH_TAP_V2) || \
|
||||
defined(ELECROW_ThinkNode_M9)) && \
|
||||
HAS_TFT
|
||||
// switch BT off by default; use TFT programming mode or hotkey to enable
|
||||
config.bluetooth.enabled = false;
|
||||
@@ -1264,7 +1265,10 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.output_ms = 1000;
|
||||
#endif
|
||||
|
||||
#if defined(PIN_VIBRATION)
|
||||
#if HAS_TFT
|
||||
if (moduleConfig.external_notification.nag_timeout == default_ringtone_nag_secs)
|
||||
moduleConfig.external_notification.nag_timeout = 0;
|
||||
#elif defined(PIN_VIBRATION)
|
||||
moduleConfig.external_notification.nag_timeout = 2;
|
||||
#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) || defined(NEOPIXEL_STATUS_NOTIFICATION_PIN) || \
|
||||
defined(HAS_I2S_SPEAKER_NRF52)
|
||||
@@ -1276,12 +1280,6 @@ void NodeDB::installDefaultModuleConfig()
|
||||
moduleConfig.external_notification.enabled = true;
|
||||
moduleConfig.external_notification.use_i2s_as_buzzer = true;
|
||||
moduleConfig.external_notification.alert_message_buzzer = true;
|
||||
#if HAS_TFT
|
||||
if (moduleConfig.external_notification.nag_timeout == default_ringtone_nag_secs)
|
||||
moduleConfig.external_notification.nag_timeout = 0;
|
||||
#else
|
||||
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
|
||||
#endif // HAS_TFT
|
||||
#endif // HAS_I2S
|
||||
|
||||
#ifdef NANO_G2_ULTRA
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "LSM6DS3Sensor.h"
|
||||
#include "MPU6050Sensor.h"
|
||||
#include "MotionSensor.h"
|
||||
#include "QMI8658Sensor.h"
|
||||
|
||||
#include <memory>
|
||||
#ifdef HAS_QMA6100P
|
||||
@@ -146,6 +147,11 @@ class AccelerometerThread : public concurrency::OSThread
|
||||
case ScanI2C::DeviceType::QMA6100P:
|
||||
sensor.reset(new QMA6100PSensor(device));
|
||||
break;
|
||||
#endif
|
||||
#if __has_include(<SensorQMI8658.hpp>)
|
||||
case ScanI2C::DeviceType::QMI8658:
|
||||
sensor.reset(new QMI8658Sensor(device));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
disable();
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "../concurrency/OSThread.h"
|
||||
#include "MMC5983MASensor.h"
|
||||
#include "MotionSensor.h"
|
||||
#include "QMC6309Sensor.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
@@ -73,6 +74,11 @@ class MagnetometerThread : public concurrency::OSThread
|
||||
case ScanI2C::DeviceType::MMC5983MA:
|
||||
sensor.reset(new MMC5983MASensor(device));
|
||||
break;
|
||||
#endif
|
||||
#if __has_include(<SensorQMC6309.hpp>)
|
||||
case ScanI2C::DeviceType::QMC6309:
|
||||
sensor.reset(new QMC6309Sensor(device));
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
disable();
|
||||
|
||||
@@ -42,6 +42,9 @@ struct CompassAccelSample {
|
||||
|
||||
concurrency::Lock latestCompassAccelLock;
|
||||
CompassAccelSample latestCompassAccelSample;
|
||||
|
||||
concurrency::Lock latestCompassMagLock;
|
||||
CompassAccelSample latestCompassMagSample;
|
||||
} // namespace
|
||||
|
||||
// screen is defined in main.cpp
|
||||
@@ -245,6 +248,35 @@ bool MotionSensor::getLatestCompassAccelSample(float &x, float &y, float &z, uin
|
||||
return true;
|
||||
}
|
||||
|
||||
void MotionSensor::publishCompassMagSample(float x, float y, float z)
|
||||
{
|
||||
concurrency::LockGuard guard(&latestCompassMagLock);
|
||||
latestCompassMagSample.x = x;
|
||||
latestCompassMagSample.y = y;
|
||||
latestCompassMagSample.z = z;
|
||||
latestCompassMagSample.sampledAtMs = millis();
|
||||
latestCompassMagSample.valid = true;
|
||||
}
|
||||
|
||||
bool MotionSensor::getLatestCompassMagSample(float &x, float &y, float &z, uint32_t &ageMs)
|
||||
{
|
||||
uint32_t sampledAtMs = 0;
|
||||
{
|
||||
concurrency::LockGuard guard(&latestCompassMagLock);
|
||||
if (!latestCompassMagSample.valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
x = latestCompassMagSample.x;
|
||||
y = latestCompassMagSample.y;
|
||||
z = latestCompassMagSample.z;
|
||||
sampledAtMs = latestCompassMagSample.sampledAtMs;
|
||||
}
|
||||
|
||||
ageMs = millis() - sampledAtMs;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
|
||||
void MotionSensor::drawFrameCalibration(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
|
||||
@@ -42,6 +42,11 @@ class MotionSensor
|
||||
|
||||
virtual void calibrate(uint16_t forSeconds){};
|
||||
|
||||
// Latest samples published by the compass-fusion drivers (accel from the IMU, mag from the magnetometer).
|
||||
// Public so an optional on-screen sensor debug readout can read them. Return false if nothing published yet.
|
||||
static bool getLatestCompassAccelSample(float &x, float &y, float &z, uint32_t &ageMs);
|
||||
static bool getLatestCompassMagSample(float &x, float &y, float &z, uint32_t &ageMs);
|
||||
|
||||
// True if this sensor produces the compass heading (screen->setHeading()) in runOnce().
|
||||
// Combined accel+magnetometer parts (e.g. BMX160, ICM20948) and standalone magnetometers
|
||||
// handled by the accelerometer thread (e.g. BMM150) override this. Used to avoid halting
|
||||
@@ -74,7 +79,7 @@ class MotionSensor
|
||||
float &lowestY, float &highestZ, float &lowestZ);
|
||||
static float applyCompassOrientation(float heading);
|
||||
static void publishCompassAccelSample(float x, float y, float z);
|
||||
static bool getLatestCompassAccelSample(float &x, float &y, float &z, uint32_t &ageMs);
|
||||
static void publishCompassMagSample(float x, float y, float z);
|
||||
|
||||
ScanI2C::FoundDevice device;
|
||||
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#include "QMC6309Sensor.h"
|
||||
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && __has_include(<SensorQMC6309.hpp>)
|
||||
|
||||
#include "Fusion/Fusion.h"
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include <math.h>
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
extern std::unique_ptr<graphics::Screen> screen;
|
||||
#endif
|
||||
|
||||
static constexpr int32_t QMC6309_UPDATE_INTERVAL_MS = 20;
|
||||
// Heading offset/flip below is a starting point copied from the MMC5983MA path; it is orientation-specific
|
||||
// and must be verified/tuned against known North on real M9 hardware (see plan).
|
||||
static constexpr float QMC6309_HEADING_OFFSET_DEG = 180.0f;
|
||||
static constexpr uint32_t QMC6309_ACCEL_STALE_MS = 300;
|
||||
static constexpr float QMC6309_MIN_AXIS_RADIUS = 1e-4f;
|
||||
|
||||
QMC6309Sensor::QMC6309Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {}
|
||||
|
||||
bool QMC6309Sensor::init()
|
||||
{
|
||||
LOG_DEBUG("QMC6309 begin on addr 0x%02X (port=%d)", device.address.address, device.address.port);
|
||||
TwoWire *wire = ScanI2CTwoWire::fetchI2CBus(device.address);
|
||||
|
||||
if (!sensor.begin(*wire, deviceAddress())) {
|
||||
LOG_DEBUG("QMC6309 init error");
|
||||
return false;
|
||||
}
|
||||
|
||||
sensor.reset();
|
||||
|
||||
// 8 Gauss full-scale easily covers Earth's ~0.5 G field; OSR_8 for low noise. Tunable.
|
||||
if (!sensor.configMagnetometer(OperationMode::CONTINUOUS_MEASUREMENT, MagFullScaleRange::FS_8G, 100.0f,
|
||||
MagOverSampleRatio::OSR_8)) {
|
||||
LOG_DEBUG("QMC6309 config failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
loadMagnetometerCalibration(compassCalibrationFileName, highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
|
||||
LOG_DEBUG("QMC6309 init ok");
|
||||
LOG_DEBUG("QMC6309 calibration extrema: X=(%.3f, %.3f), Y=(%.3f, %.3f), Z=(%.3f, %.3f)", lowestX, highestX, lowestY, highestY,
|
||||
lowestZ, highestZ);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool QMC6309Sensor::readMagnetometer(float &xGauss, float &yGauss, float &zGauss)
|
||||
{
|
||||
MagnetometerData data;
|
||||
if (!sensor.readData(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// magnetic_field is already scaled to Gauss by the driver.
|
||||
xGauss = data.magnetic_field.x;
|
||||
yGauss = data.magnetic_field.y;
|
||||
zGauss = data.magnetic_field.z;
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t QMC6309Sensor::runOnce()
|
||||
{
|
||||
float magX = 0, magY = 0, magZ = 0;
|
||||
if (!readMagnetometer(magX, magY, magZ)) {
|
||||
return QMC6309_UPDATE_INTERVAL_MS;
|
||||
}
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
if (doCalibration) {
|
||||
beginCalibrationDisplay(showingScreen);
|
||||
updateCalibrationExtrema(magX, magY, magZ, highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
|
||||
finishCalibrationIfExpired(showingScreen, compassCalibrationFileName, highestX, lowestX, highestY, lowestY, highestZ,
|
||||
lowestZ);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Hard-iron bias removal.
|
||||
magX -= (highestX + lowestX) * 0.5f;
|
||||
magY -= (highestY + lowestY) * 0.5f;
|
||||
magZ -= (highestZ + lowestZ) * 0.5f;
|
||||
// LOG_WARN("QMC6309 extrema=(%.3f, %.3f, %.3f) to (%.3f, %.3f, %.3f)",
|
||||
// lowestX, lowestY, lowestZ, highestX, highestY, highestZ);
|
||||
|
||||
// Soft-iron diagonal scaling from calibration extrema.
|
||||
const float radiusX = (highestX - lowestX) * 0.5f;
|
||||
const float radiusY = (highestY - lowestY) * 0.5f;
|
||||
const float radiusZ = (highestZ - lowestZ) * 0.5f;
|
||||
const float avgRadius = (radiusX + radiusY + radiusZ) / 3.0f;
|
||||
// magX *= (radiusX > QMC6309_MIN_AXIS_RADIUS) ? (avgRadius / radiusX) : 1.0f;
|
||||
// magY *= (radiusY > QMC6309_MIN_AXIS_RADIUS) ? (avgRadius / radiusY) : 1.0f;
|
||||
// magZ *= (radiusZ > QMC6309_MIN_AXIS_RADIUS) ? (avgRadius / radiusZ) : 1.0f;
|
||||
|
||||
// Publish the calibrated magnetometer values (hard/soft-iron applied) for the optional on-screen debug readout.
|
||||
publishCompassMagSample(magX, magY, magZ);
|
||||
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN) && HAS_SCREEN
|
||||
float heading;
|
||||
float accelX = 0.0f;
|
||||
float accelY = 0.0f;
|
||||
float accelZ = 0.0f;
|
||||
uint32_t accelAgeMs = 0;
|
||||
|
||||
// Fuse with the latest accelerometer sample (published by the QMI8658 driver) for tilt compensation.
|
||||
if (getLatestCompassAccelSample(accelX, accelY, accelZ, accelAgeMs) && accelAgeMs <= QMC6309_ACCEL_STALE_MS) {
|
||||
FusionVector ga = {.axis = {accelX, accelY, accelZ}};
|
||||
FusionVector ma = {.axis = {magX, magY, magZ}};
|
||||
// if (config.display.compass_orientation > meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270) {
|
||||
// ma = FusionAxesSwap(ma, FusionAxesAlignmentNXNYPZ);
|
||||
// ga = FusionAxesSwap(ga, FusionAxesAlignmentNXNYPZ);
|
||||
//}
|
||||
// LOG_WARN("QMC6309 accel age %ums, ga=(%.3f, %.3f, %.3f), ma=(%.3f, %.3f, %.3f)", accelAgeMs, ga.axis.x, ga.axis.y,
|
||||
// ga.axis.z, ma.axis.x, ma.axis.y, ma.axis.z);
|
||||
heading = FusionCompass(ga, ma, FusionConventionNed);
|
||||
if (ga.axis.z > 0.0f)
|
||||
heading = 360.0f - heading;
|
||||
|
||||
} else {
|
||||
heading = atan2f(-magY, magX) * RAD_TO_DEG;
|
||||
}
|
||||
|
||||
if (heading >= 360.0f)
|
||||
heading -= 360.0f;
|
||||
else if (heading < 0.0f)
|
||||
heading += 360.0f;
|
||||
|
||||
heading = applyCompassOrientation(heading);
|
||||
if (screen)
|
||||
screen->setHeading(heading);
|
||||
#endif
|
||||
|
||||
return QMC6309_UPDATE_INTERVAL_MS;
|
||||
}
|
||||
|
||||
void QMC6309Sensor::calibrate(uint16_t forSeconds)
|
||||
{
|
||||
#if !defined(MESHTASTIC_EXCLUDE_SCREEN)
|
||||
float xGauss = 0.0f;
|
||||
float yGauss = 0.0f;
|
||||
float zGauss = 0.0f;
|
||||
|
||||
LOG_DEBUG("QMC6309 calibration started for %is", forSeconds);
|
||||
if (readMagnetometer(xGauss, yGauss, zGauss)) {
|
||||
seedCalibrationExtrema(xGauss, yGauss, zGauss, highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
|
||||
} else {
|
||||
seedCalibrationExtrema(0.0f, 0.0f, 0.0f, highestX, lowestX, highestY, lowestY, highestZ, lowestZ);
|
||||
}
|
||||
startCalibrationWindow(forSeconds);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#ifndef _QMC6309_SENSOR_H_
|
||||
#define _QMC6309_SENSOR_H_
|
||||
|
||||
#include "MotionSensor.h"
|
||||
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && __has_include(<SensorQMC6309.hpp>)
|
||||
|
||||
// SensorQMC6309.hpp (SensorLib 0.4.1) references the isBitSet() macro in an inline method but never includes
|
||||
// SensorLib.h where it is defined. Define it here (guarded) so the header compiles regardless of include order
|
||||
// (pulling in SensorLib.h is unreliable - its #pragma once can already be tripped by an in-progress include).
|
||||
#ifndef isBitSet
|
||||
#define isBitSet(value, bit) (((value) & (1UL << (bit))) == (1UL << (bit)))
|
||||
#endif
|
||||
#include <SensorQMC6309.hpp>
|
||||
|
||||
class QMC6309Sensor : public MotionSensor
|
||||
{
|
||||
private:
|
||||
SensorQMC6309 sensor;
|
||||
bool showingScreen = false;
|
||||
static constexpr const char *compassCalibrationFileName = "/prefs/compass_qmc6309.dat";
|
||||
#ifdef ELECROW_ThinkNode_M9
|
||||
float highestX = -5.548, lowestX = -6.530, highestY = -6.638, lowestY = -7.637, highestZ = -6.676, lowestZ = -7.633;
|
||||
#else
|
||||
float highestX = 0, lowestX = 0, highestY = 0, lowestY = 0, highestZ = 0, lowestZ = 0;
|
||||
#endif
|
||||
|
||||
bool readMagnetometer(float &xGauss, float &yGauss, float &zGauss);
|
||||
|
||||
public:
|
||||
explicit QMC6309Sensor(ScanI2C::FoundDevice foundDevice);
|
||||
virtual bool init() override;
|
||||
virtual int32_t runOnce() override;
|
||||
virtual void calibrate(uint16_t forSeconds) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
#include "QMI8658Sensor.h"
|
||||
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && __has_include(<SensorQMI8658.hpp>)
|
||||
|
||||
#include "NodeDB.h"
|
||||
#include "detect/ScanI2CTwoWire.h"
|
||||
#include <math.h>
|
||||
|
||||
// Accelerometer configuration. 2G full-scale gives the best gravity resolution for the tilt
|
||||
// compensation that the (future) separate compass module will apply to these samples.
|
||||
static constexpr SensorQMI8658::AccelRange QMI8658_ACCEL_RANGE = SensorQMI8658::ACC_RANGE_2G;
|
||||
static constexpr SensorQMI8658::AccelODR QMI8658_ACCEL_ODR = SensorQMI8658::ACC_ODR_125Hz;
|
||||
|
||||
// Any-motion slope threshold (in mg) used to wake the screen. Tunable: raise to reduce false wakes,
|
||||
// lower to make it more sensitive. 200mg (~0.2g) requires a deliberate movement.
|
||||
static constexpr float QMI8658_ANY_MOTION_THRESHOLD_MG = 200.0f;
|
||||
static constexpr uint8_t QMI8658_ANY_MOTION_WINDOW = 1;
|
||||
|
||||
// Optional board-defined rotation (degrees) applied to the accel X/Y before publishing to the compass
|
||||
// fusion path, mirroring the ICM42607P driver. Defaults to no rotation.
|
||||
static constexpr float QMI8658_ACCEL_TO_COMPASS_ROTATION_DEG_VALUE =
|
||||
#ifdef QMI8658_ACCEL_TO_COMPASS_ROTATION_DEG
|
||||
QMI8658_ACCEL_TO_COMPASS_ROTATION_DEG;
|
||||
#else
|
||||
0.0f;
|
||||
#endif
|
||||
|
||||
QMI8658Sensor::QMI8658Sensor(ScanI2C::FoundDevice foundDevice) : MotionSensor::MotionSensor(foundDevice) {}
|
||||
|
||||
bool QMI8658Sensor::init()
|
||||
{
|
||||
LOG_DEBUG("QMI8658 begin on addr 0x%02X (port=%d)", deviceAddress(), devicePort());
|
||||
TwoWire *wire = ScanI2CTwoWire::fetchI2CBus(device.address);
|
||||
|
||||
if (!sensor.begin(*wire, deviceAddress())) {
|
||||
LOG_DEBUG("QMI8658 init failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
sensor.configAccelerometer(QMI8658_ACCEL_RANGE, QMI8658_ACCEL_ODR, SensorQMI8658::LPF_MODE_0);
|
||||
sensor.enableAccelerometer();
|
||||
|
||||
// Configure the on-chip any-motion engine so we can wake the screen without a dedicated interrupt pin.
|
||||
// configMotion() runs alongside normal accel data output (unlike Wake-on-Motion, which halts data), so
|
||||
// we keep publishing samples for compass fusion while still detecting motion.
|
||||
wakeOnMotion = config.display.wake_on_tap_or_motion;
|
||||
if (wakeOnMotion) {
|
||||
const uint8_t modeCtrl = SensorQMI8658::ANY_MOTION_EN_X | SensorQMI8658::ANY_MOTION_EN_Y | SensorQMI8658::ANY_MOTION_EN_Z;
|
||||
// No-motion detection is left disabled (unreliable per the SensorLib example); its thresholds/windows
|
||||
// are still required arguments but are ignored when the mode bits above are clear.
|
||||
sensor.configMotion(modeCtrl, QMI8658_ANY_MOTION_THRESHOLD_MG, QMI8658_ANY_MOTION_THRESHOLD_MG,
|
||||
QMI8658_ANY_MOTION_THRESHOLD_MG, QMI8658_ANY_MOTION_WINDOW, /*NoMotion X/Y/Z*/ 0.1f, 0.1f, 0.1f,
|
||||
/*NoMotionWindow*/ 1, /*SigMotionWaitWindow*/ 1, /*SigMotionConfirmWindow*/ 1);
|
||||
sensor.enableMotionDetect();
|
||||
}
|
||||
|
||||
LOG_DEBUG("QMI8658 init ok");
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t QMI8658Sensor::runOnce()
|
||||
{
|
||||
float ax, ay, az;
|
||||
if (sensor.getAccelerometer(ax, ay, az)) {
|
||||
if (QMI8658_ACCEL_TO_COMPASS_ROTATION_DEG_VALUE != 0.0f) {
|
||||
static const float rotRad = QMI8658_ACCEL_TO_COMPASS_ROTATION_DEG_VALUE * DEG_TO_RAD;
|
||||
static const float cosTheta = cosf(rotRad);
|
||||
static const float sinTheta = sinf(rotRad);
|
||||
const float rotatedX = (ax * cosTheta) - (ay * sinTheta);
|
||||
const float rotatedY = (ax * sinTheta) + (ay * cosTheta);
|
||||
ax = rotatedX;
|
||||
ay = rotatedY;
|
||||
}
|
||||
|
||||
// Match the accel sign convention used by the other FusionCompass sensor paths (e.g. ICM42607P).
|
||||
// The final handedness must be verified against the QMI8658 datasheet and real calibration once the
|
||||
// separate compass module is wired up; do not hand-tune the signs before then.
|
||||
publishCompassAccelSample(ax, ay, az);
|
||||
}
|
||||
|
||||
if (wakeOnMotion && (sensor.getStatusRegister() & SensorQMI8658::EVENT_ANY_MOTION)) {
|
||||
wakeScreen();
|
||||
}
|
||||
|
||||
return MOTION_SENSOR_CHECK_INTERVAL_MS;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#ifndef _QMI8658_SENSOR_H_
|
||||
#define _QMI8658_SENSOR_H_
|
||||
|
||||
#include "MotionSensor.h"
|
||||
|
||||
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && __has_include(<SensorQMI8658.hpp>)
|
||||
|
||||
#include <SensorQMI8658.hpp>
|
||||
|
||||
class QMI8658Sensor : public MotionSensor
|
||||
{
|
||||
private:
|
||||
SensorQMI8658 sensor;
|
||||
bool wakeOnMotion = false;
|
||||
|
||||
public:
|
||||
explicit QMI8658Sensor(ScanI2C::FoundDevice foundDevice);
|
||||
virtual bool init() override;
|
||||
virtual int32_t runOnce() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
+5
-1
@@ -453,8 +453,12 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
|
||||
gpio_wakeup_enable((gpio_num_t)ROTARY_PRESS, GPIO_INTR_LOW_LEVEL);
|
||||
#endif
|
||||
#ifdef KB_INT
|
||||
#if KB_INT_WAKE_ON_HIGH
|
||||
gpio_wakeup_enable((gpio_num_t)KB_INT, GPIO_INTR_HIGH_LEVEL);
|
||||
#else
|
||||
gpio_wakeup_enable((gpio_num_t)KB_INT, GPIO_INTR_LOW_LEVEL);
|
||||
#endif
|
||||
#endif // KB_INT_WAKE_ON_HIGH
|
||||
#endif // KB_INT
|
||||
#ifdef BOARD_PCA9535_INT
|
||||
// Side-key interrupt line from PCA9535 expander (active low).
|
||||
gpio_wakeup_enable((gpio_num_t)BOARD_PCA9535_INT, GPIO_INTR_LOW_LEVEL);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef Pins_Arduino_h
|
||||
#define Pins_Arduino_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define USB_VID 0x303a
|
||||
#define USB_PID 0x1001
|
||||
|
||||
static const uint8_t TX = 43;
|
||||
static const uint8_t RX = 44;
|
||||
|
||||
static const uint8_t SDA = 20;
|
||||
static const uint8_t SCL = 21;
|
||||
|
||||
static const uint8_t SS = 39;
|
||||
static const uint8_t MOSI = 47;
|
||||
static const uint8_t MISO = 38;
|
||||
static const uint8_t SCK = 40;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,99 @@
|
||||
[thinknode_m9_base]
|
||||
custom_meshtastic_hw_model = 131
|
||||
custom_meshtastic_hw_model_slug = ELECROW_ThinkNode_M9
|
||||
custom_meshtastic_architecture = esp32-s3
|
||||
custom_meshtastic_actively_supported = true
|
||||
custom_meshtastic_support_level = 1
|
||||
custom_meshtastic_display_name = ThinkNode M9
|
||||
custom_meshtastic_images = thinknode_m9.svg
|
||||
custom_meshtastic_tags = Elecrow
|
||||
custom_meshtastic_requires_dfu = false
|
||||
custom_meshtastic_partition_scheme = 16MB
|
||||
custom_meshtastic_has_mui = true
|
||||
|
||||
extends = esp32s3_base
|
||||
board = crowpanel
|
||||
board_level = release
|
||||
board_build.partitions = default_16MB.csv
|
||||
upload_protocol = esptool
|
||||
build_src_filter =
|
||||
${esp32s3_base.build_src_filter}
|
||||
+<../variants/esp32s3/ELECROW-ThinkNode-M9>
|
||||
build_flags =
|
||||
${esp32s3_base.build_flags}
|
||||
-I variants/esp32s3/ELECROW-ThinkNode-M9
|
||||
-D ELECROW_ThinkNode_M9
|
||||
-D BOARD_HAS_PSRAM
|
||||
-D HAS_SDCARD=1
|
||||
-D SDCARD_CS=48
|
||||
-D SDCARD_USE_SPI1
|
||||
-D SDCARD_SHARE_SPI
|
||||
-D SDCARD_INIT_SPI
|
||||
-D SD_SPI_FREQUENCY=75000000U
|
||||
-D HAS_SCREEN=1
|
||||
; -D COMPASS_SENSOR_DEBUG=1
|
||||
lib_deps = ${esp32s3_base.lib_deps}
|
||||
# renovate: datasource=custom depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
|
||||
lovyan03/LovyanGFX@1.2.26
|
||||
# renovate: datasource=custom depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||
lewisxhe/SensorLib@0.4.1
|
||||
|
||||
[env:thinknode_m9]
|
||||
extends = thinknode_m9_base
|
||||
build_flags =
|
||||
${thinknode_m9_base.build_flags}
|
||||
|
||||
[env:thinknode_m9-tft]
|
||||
extends = thinknode_m9_base
|
||||
build_flags =
|
||||
${thinknode_m9_base.build_flags}
|
||||
-D RADIOLIB_SPI_PARANOID=0
|
||||
-D CONFIG_DISABLE_HAL_LOCKS=1
|
||||
-D HAS_TFT=1
|
||||
-D USE_PACKET_API
|
||||
-D USE_PIN_BUZZER=PIN_BUZZER
|
||||
-D LV_LVGL_H_INCLUDE_SIMPLE
|
||||
-D LV_CONF_INCLUDE_SIMPLE
|
||||
-D LV_COMP_CONF_INCLUDE_SIMPLE
|
||||
-D LV_USE_SYSMON=0
|
||||
-D LV_USE_PROFILER=0
|
||||
-D LV_USE_PERF_MONITOR=0
|
||||
-D LV_USE_MEM_MONITOR=0
|
||||
-D LV_USE_LOG=0
|
||||
-D LV_BUILD_TEST=0
|
||||
-D USE_LOG_DEBUG
|
||||
-D LOG_DEBUG_INC=\"DebugConfiguration.h\"
|
||||
-D RAM_SIZE=5120
|
||||
-D LGFX_BUFSIZE=153600
|
||||
-D LGFX_DRIVER_TEMPLATE
|
||||
-D DISPLAY_SIZE=320x240
|
||||
-D LGFX_DRIVER=LGFX_GENERIC
|
||||
-D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_GENERIC.h\"
|
||||
-D VIEW_320x240
|
||||
-D LGFX_PANEL=ST7789
|
||||
-D LGFX_ROTATION=3
|
||||
-D LGFX_CFG_HOST=SPI3_HOST
|
||||
-D LGFX_PIN_SCK=40
|
||||
-D LGFX_PIN_MOSI=47
|
||||
-D LGFX_PIN_DC=15
|
||||
-D LGFX_PIN_CS=16
|
||||
-D LGFX_PIN_BL=17
|
||||
-D LGFX_PIN_RST=14
|
||||
-D LGFX_SCREEN_WIDTH=240
|
||||
-D LGFX_SCREEN_HEIGHT=320
|
||||
-D LGFX_INVERT_LIGHT=true
|
||||
-D SPI_FREQUENCY=75000000
|
||||
; -D MAP_FULL_REDRAW
|
||||
-D MUI_WIFI_PS_MIN_MODEM
|
||||
-D DEFAULT_FTP_SERVER_NETWORK_TYPE_ESP32=NETWORK_ESP32
|
||||
-D DEFAULT_STORAGE_TYPE_ESP32=STORAGE_SD
|
||||
-D CHARGING_VOLTAGE=4.25
|
||||
|
||||
lib_deps =
|
||||
${thinknode_m9_base.lib_deps}
|
||||
https://github.com/meshtastic/device-ui/archive/e8a5ff337d1ead20b290307fb2159ad27fe47f86.zip ; PR314 input-policy
|
||||
https://github.com/mverch67/MultiFTPServer/archive/0e854335b9916ed9f2d3bcfe68975ce746992ccd.zip
|
||||
|
||||
custom_sdkconfig =
|
||||
${esp32s3_base.custom_sdkconfig}
|
||||
${device-ui_base.custom_sdkconfig}
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "RadioLib.h"
|
||||
|
||||
static const uint32_t rfswitch_dio_pins[] = {RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
|
||||
|
||||
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
||||
// mode DIO5 DIO6
|
||||
{LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
|
||||
{LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
|
||||
{LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
|
||||
{LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "variant.h"
|
||||
#include "Arduino.h"
|
||||
#include "SPILock.h"
|
||||
#include "Wire.h"
|
||||
|
||||
void earlyInitVariant()
|
||||
{
|
||||
pinMode(LORA_CS, OUTPUT);
|
||||
digitalWrite(LORA_CS, HIGH);
|
||||
pinMode(SDCARD_CS, OUTPUT);
|
||||
digitalWrite(SDCARD_CS, HIGH);
|
||||
pinMode(TFT_CS, OUTPUT);
|
||||
digitalWrite(TFT_CS, HIGH);
|
||||
delay(100);
|
||||
}
|
||||
|
||||
void lateInitVariant()
|
||||
{
|
||||
// configure keyboard long-press time
|
||||
const uint16_t ms = 700;
|
||||
concurrency::LockGuard g(spiLock);
|
||||
Wire.beginTransmission(0x6C);
|
||||
Wire.write(0x03);
|
||||
Wire.write((ms >> 8) & 0xFF);
|
||||
Wire.write(ms & 0xFF);
|
||||
Wire.endTransmission();
|
||||
}
|
||||
|
||||
void variant_shutdown()
|
||||
{
|
||||
uint64_t gpioMask = (1ULL << KB_INT);
|
||||
gpio_pulldown_en((gpio_num_t)KB_INT);
|
||||
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ANY_HIGH);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#define CANNED_MESSAGE_MODULE_ENABLE 1
|
||||
#define PRESET_MESSAGE_MODULE_ENABLE 1
|
||||
|
||||
/*Power*/
|
||||
#define VEXT_ENABLE 18
|
||||
#define VEXT_ON_VALUE LOW
|
||||
#define PIN_GPS_EN 11
|
||||
#define GPS_EN_ACTIVE LOW
|
||||
|
||||
#define USE_POWERSAVE
|
||||
#define SLEEP_TIME 120
|
||||
|
||||
/*Wire Interface*/
|
||||
#define WIRE_INTERFACES_COUNT 2
|
||||
// I2C keyboard
|
||||
#define I2C_SCL 21
|
||||
#define I2C_SDA 20
|
||||
#define KB_INT 12 // STC8H key-press interrupt (idle low, rising edge on press)
|
||||
#define KB_INT_WAKE_ON_HIGH 1 // KB_INT rests low; wake light sleep on its HIGH (active) level
|
||||
#define KB_LED 46 // STC8H keypad backlight LED
|
||||
// I2C peripheral
|
||||
#define I2C_SCL1 6
|
||||
#define I2C_SDA1 7
|
||||
|
||||
/*BUZZER*/
|
||||
#define PIN_BUZZER 9
|
||||
|
||||
/*CHARGE_CHECK*/
|
||||
#define EXT_PWR_DETECT 1
|
||||
// #define EXT_CHRG_DETECT 1
|
||||
#define EXT_PWR_DETECT_VALUE LOW
|
||||
|
||||
/*GPS*/
|
||||
#define HAS_GPS 1
|
||||
#define GPS_BAUDRATE 115200
|
||||
#define PIN_GPS_RESET 5
|
||||
#define PIN_GPS_PPS 4
|
||||
#define GPS_TX_PIN 3
|
||||
#define GPS_RX_PIN 2
|
||||
#define GPS_THREAD_INTERVAL 50
|
||||
|
||||
/*SPI*/
|
||||
#define SPI_MOSI 47
|
||||
#define SPI_SCK 40
|
||||
#define SPI_MISO 38
|
||||
|
||||
/*Screen*/
|
||||
#define ST7789_CS 16
|
||||
#define ST7789_RS 15
|
||||
#define ST7789_TE 19
|
||||
#define ST7789_SDA SPI_MOSI // MOSI
|
||||
#define ST7789_SCK SPI_SCK
|
||||
#define ST7789_RESET 14
|
||||
#define ST7789_MISO SPI_MISO
|
||||
#define ST7789_BUSY -1
|
||||
#define ST7789_BL 17
|
||||
#define ST7789_SPI_HOST SPI3_HOST
|
||||
#define SPI_READ_FREQUENCY 16000000
|
||||
|
||||
#define USE_TFTDISPLAY 1
|
||||
#define HAS_SPI_TFT 1
|
||||
#define TFT_CS ST7789_CS
|
||||
#define TFT_BL ST7789_BL
|
||||
#define TFT_HEIGHT 320
|
||||
#define TFT_WIDTH 240
|
||||
#define TFT_OFFSET_X 0
|
||||
#define TFT_OFFSET_Y 0
|
||||
#define TFT_OFFSET_ROTATION 0
|
||||
#define TFT_PWM_FREQ 44000
|
||||
#define TFT_PWM_CHANNEL 7
|
||||
#define TFT_INVERT_LIGHT true
|
||||
#define TFT_BACKLIGHT_ON LOW
|
||||
#define SCREEN_ROTATE
|
||||
#define SCREEN_TRANSITION_FRAMERATE 10
|
||||
#define BRIGHTNESS_DEFAULT 128
|
||||
|
||||
/*Lora radio*/
|
||||
#define HW_SPI1_DEVICE
|
||||
#define LORA_SCK SPI_SCK
|
||||
#define LORA_MISO SPI_MISO
|
||||
#define LORA_MOSI SPI_MOSI
|
||||
#define LORA_CS 39
|
||||
#define LORA_RESET 45
|
||||
#define LORA_DIO0 41
|
||||
#define LORA_DIO1 42
|
||||
|
||||
#define USE_LR1110
|
||||
#define LR1110_IRQ_PIN LORA_DIO1
|
||||
#define LR1110_NRESET_PIN LORA_RESET
|
||||
#define LR1110_BUSY_PIN LORA_DIO0
|
||||
#define LR1110_SPI_NSS_PIN LORA_CS
|
||||
#define LR1110_SPI_SCK_PIN LORA_SCK
|
||||
#define LR1110_SPI_MOSI_PIN LORA_MOSI
|
||||
#define LR1110_SPI_MISO_PIN LORA_MISO
|
||||
#define LR11X0_DIO3_TCXO_VOLTAGE 3.3
|
||||
#define LR11X0_DIO_AS_RF_SWITCH
|
||||
|
||||
/*RTC*/
|
||||
#define PCF8563_RTC 0x51
|
||||
|
||||
/*BATTERY*/
|
||||
#define BATTERY_PIN 13
|
||||
#define BATTERY_IMMUTABLE
|
||||
#define ADC_MULTIPLIER 2.0f
|
||||
#define BAT_MEASURE_ADC_UNIT ADC_UNIT_2
|
||||
#define ADC_CHANNEL ADC_CHANNEL_2
|
||||
#define OCV_ARRAY 4200, 4080, 3980, 3920, 3870, 3820, 3790, 3750, 3700, 3600, 3100
|
||||
@@ -39,6 +39,10 @@ lib_deps = ${esp32s3_base.lib_deps}
|
||||
extends = env:t-deck
|
||||
board_level = pr
|
||||
|
||||
extra_scripts =
|
||||
${env:t-deck.extra_scripts}
|
||||
extra_scripts/ld_response_file.py
|
||||
|
||||
build_flags =
|
||||
${env:t-deck.build_flags}
|
||||
-D CONFIG_DISABLE_HAL_LOCKS=1 ; "feels" to be a bit more stable without locks
|
||||
|
||||
Reference in new issue
Block a user