Merge pull request #736 from geeksville/eink

Eink
This commit is contained in:
Kevin Hester 2021-03-10 15:59:32 +08:00 committed by GitHub
commit 0b358674ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 243 additions and 78 deletions

View File

@ -1,3 +1,3 @@
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA
JLinkGDBServerCLExe -if SWD -select USB -port 2331 -device NRF52840_XXAA -SuppressInfoUpdateFW -DisableAutoUpdateFW -rtos GDBServer/RTOSPlugin_FreeRTOS

View File

@ -29,6 +29,7 @@ You probably don't care about this section - skip to the next one.
* DONE release protobufs
* DONE release to developers
* DONE fix setch-fast in python tool
* turn off fault 8: https://github.com/meshtastic/Meshtastic-device/issues/734
* age out pendingrequests in the python API
* DONE stress test channel download from python, sometimes it seems like we don't get all replies, bug was due to simultaneous android connection
* DONE combine acks and responses in a single message if possible (do routing plugin LAST and drop ACK if someone else has already replied)
@ -83,17 +84,32 @@ You probably don't care about this section - skip to the next one.
eink:
* DONE check email of reported issues
* DONE turn off vbus driving (in bootloader)
* new battery level sensing
* measure current draw
* current draw no good
* DONE: fix backlight
* USB is busted because of power enable mode?
* DONE - USB is busted because of power enable mode?
* test CPU voltage? something is bad with RAM (removing eink module does not help)
* test that board leaves bootloader always
* test USB - works in bootloader
* test LEDs
* Test BME280
* test gps
* check GPS fast locking
* tested! dlora
* test eink backlight
* tested! eink
* test buttons
* test battery charging
* test serial flash
* send updated app and bootloader image
* OHH BME280! THAT IS GREAT!
* make new screen work, ask for datasheet
* say I think you could ship this
* leds seem busted
* usb doesn't stay connected
* check GPS works
* check GPS fast locking
* fix hw_model: "nrf52unknown"
* use larger icon for meshtastic logo
* send email about variants & faster flash programming - https://github.com/geeksville/Meshtastic-esp32/commit/f110225173a77326aac029321cdb6491bfa640f6
* send PR for bootloader
* fix nrf52 time/date

View File

@ -16,6 +16,7 @@ default_envs = tbeam
;default_envs = tlora-v2
;default_envs = lora-relay-v1 # nrf board
;default_envs = eink
;default_envs = nrf52840dk-geeksville
;default_envs = linux # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
[common]
@ -33,6 +34,8 @@ default_envs = tbeam
extra_scripts = bin/platformio-custom.py
; note: we add src to our include search path so that lmic_project_config can override
; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile
; of code is a heap corruption bug!
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
build_flags = -Wno-missing-field-initializers
-Wno-format
@ -40,7 +43,7 @@ build_flags = -Wno-missing-field-initializers
-DHW_VERSION_${sysenv.COUNTRY}
-DHW_VERSION=${sysenv.HW_VERSION}
-DUSE_THREAD_NAMES
-DTINYGPSPLUS_OPTION_NO_CUSTOM_FIELDS
-DTINYGPS_OPTION_NO_CUSTOM_FIELDS
; leave this commented out to avoid breaking Windows
;upload_port = /dev/ttyUSB0
@ -74,7 +77,7 @@ lib_deps =
https://github.com/meshtastic/arduino-fsm.git#2f106146071fc7bc620e1e8d4b88dc4e0266ce39
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git#31015a55e630a2df77d9d714669c621a5bf355ad
https://github.com/meshtastic/RadioLib.git#07de964e929238949035fb0d5887026a3058df1a
https://github.com/meshtastic/TinyGPSPlus.git#9c1d584d2469523381e077b0b9c1bf868d6c0206
https://github.com/meshtastic/TinyGPSPlus.git#f0f47067ef2f67c856475933188251c1ef615e79
https://github.com/meshtastic/AXP202X_Library.git#8404abb6d4b486748636bc6ad72d2a47baaf5460
Wire ; explicitly needed here because the AXP202 library forgets to add it
SPI
@ -184,7 +187,6 @@ src_filter =
; platform = nordicnrf52
platform = https://github.com/meshtastic/platform-nordicnrf52.git#1a2639a6b0f79b5df66bea3e3089f0d5285fdc63
extends = arduino_base
debug_tool = jlink
build_type = debug ; I'm debugging with ICE a lot now
; note: liboberon provides the AES256 implementation for NRF52 (though not using the hardware acceleration of the NRF52840 - FIXME)
build_flags =
@ -198,6 +200,26 @@ lib_ignore =
BluetoothOTA
monitor_port = /dev/ttyACM1
# we pass in options to jlink so it can understand freertos (note: we don't use "jlink" as the tool)
debug_tool = jlink
debug_port = :2331
# Note: the ARGUMENTS MUST BE on multiple lines. Otherwise platformio/commands/debug/helpers.py misparses everything into the "executable"
# attribute and leaves "arguments" empty
# /home/kevinh/.platformio/packages/tool-jlink/JLinkGDBServerCLExe
debug_server =
/usr/bin/JLinkGDBServerCLExe
-singlerun
-if
SWD
-select
USB
-device
nRF52840_xxAA
-port
2331
-rtos
GDBServer/RTOSPlugin_FreeRTOS
debug_extra_cmds =
source gdbinit

2
proto

@ -1 +1 @@
Subproject commit 7c025b9a4d54bb410ec17ee653122861b413f177
Subproject commit e56f2770c33216ba94f289e2fb7f0b2dfd33aca2

View File

@ -1,9 +1,9 @@
#include "RedirectablePrint.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include <assert.h>
#include <sys/time.h>
#include <time.h>
#include "concurrency/OSThread.h"
/**
* A printer that doesn't go anywhere
@ -20,7 +20,7 @@ size_t RedirectablePrint::write(uint8_t c)
{
// Always send the characters to our segger JTAG debugger
#ifdef SEGGER_STDOUT_CH
SEGGER_RTT_PutCharSkip(SEGGER_STDOUT_CH, c);
SEGGER_RTT_PutChar(SEGGER_STDOUT_CH, c);
#endif
dest->write(c);
@ -38,7 +38,7 @@ size_t RedirectablePrint::vprintf(const char *format, va_list arg)
va_end(arg);
return 0;
};
if (len >= printBufLen) {
if (len >= (int)printBufLen) {
delete[] printBuf;
printBufLen *= 2;
printBuf = new char[printBufLen];
@ -55,45 +55,52 @@ size_t RedirectablePrint::vprintf(const char *format, va_list arg)
size_t RedirectablePrint::logDebug(const char *format, ...)
{
va_list arg;
va_start(arg, format);
// Cope with 0 len format strings, but look for new line terminator
bool hasNewline = *format && format[strlen(format) - 1] == '\n';
size_t r = 0;
// If we are the first message on a report, include the header
if (!isContinuationMessage) {
struct timeval tv;
if (!gettimeofday(&tv, NULL)) {
long hms = tv.tv_sec % SEC_PER_DAY;
//hms += tz.tz_dsttime * SEC_PER_HOUR;
//hms -= tz.tz_minuteswest * SEC_PER_MIN;
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
if (!inDebugPrint) {
inDebugPrint = true;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
va_list arg;
va_start(arg, format);
r += printf("%02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
} else
r += printf("??:??:?? %u ", millis() / 1000);
// Cope with 0 len format strings, but look for new line terminator
bool hasNewline = *format && format[strlen(format) - 1] == '\n';
auto thread = concurrency::OSThread::currentThread;
if(thread) {
print("[");
print(thread->ThreadName);
print("] ");
// If we are the first message on a report, include the header
if (!isContinuationMessage) {
struct timeval tv;
if (!gettimeofday(&tv, NULL)) {
long hms = tv.tv_sec % SEC_PER_DAY;
// hms += tz.tz_dsttime * SEC_PER_HOUR;
// hms -= tz.tz_minuteswest * SEC_PER_MIN;
// mod `hms` to ensure in positive range of [0...SEC_PER_DAY)
hms = (hms + SEC_PER_DAY) % SEC_PER_DAY;
// Tear apart hms into h:m:s
int hour = hms / SEC_PER_HOUR;
int min = (hms % SEC_PER_HOUR) / SEC_PER_MIN;
int sec = (hms % SEC_PER_HOUR) % SEC_PER_MIN; // or hms % SEC_PER_MIN
r += printf("%02d:%02d:%02d %u ", hour, min, sec, millis() / 1000);
} else
r += printf("??:??:?? %u ", millis() / 1000);
auto thread = concurrency::OSThread::currentThread;
if (thread) {
print("[");
// printf("%p ", thread);
// assert(thread->ThreadName.length());
print(thread->ThreadName);
print("] ");
}
}
r += vprintf(format, arg);
va_end(arg);
isContinuationMessage = !hasNewline;
inDebugPrint = false;
}
r += vprintf(format, arg);
va_end(arg);
isContinuationMessage = !hasNewline;
return r;
}

View File

@ -19,6 +19,8 @@ class RedirectablePrint : public Print
/// Used to allow multiple logDebug messages to appear on a single log line
bool isContinuationMessage = false;
volatile bool inDebugPrint = false;
public:
RedirectablePrint(Print *_dest) : dest(_dest) {}

View File

@ -1,13 +1,21 @@
#include "SerialConsole.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "NodeDB.h"
#include <Arduino.h>
#define Port Serial
SerialConsole console;
void consolePrintf(const char *format, ...)
{
va_list arg;
va_start(arg, format);
console.vprintf(format, arg);
va_end(arg);
}
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port)
{
canWrite = false; // We don't send packets to our port until it has talked to us first
@ -29,7 +37,7 @@ void SerialConsole::init()
void SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
{
// Turn off debug serial printing once the API is activated, because other threads could print and corrupt packets
if(!radioConfig.preferences.debug_log_enabled)
if (!radioConfig.preferences.debug_log_enabled)
setDestination(&noopPrint);
canWrite = true;

View File

@ -32,4 +32,7 @@ class SerialConsole : public StreamAPI, public RedirectablePrint
virtual void onConnectionChanged(bool connected);
};
// A simple wrapper to allow non class aware code write to the console
void consolePrintf(const char *format, ...);
extern SerialConsole console;

View File

@ -1,5 +1,6 @@
#include "concurrency/BinarySemaphoreFreeRTOS.h"
#include "configuration.h"
#include <assert.h>
#ifdef HAS_FREE_RTOS
@ -9,6 +10,7 @@ namespace concurrency
BinarySemaphoreFreeRTOS::BinarySemaphoreFreeRTOS()
{
semaphore = xSemaphoreCreateBinary();
assert(semaphore);
}
BinarySemaphoreFreeRTOS::~BinarySemaphoreFreeRTOS()

View File

@ -25,7 +25,7 @@ uint8_t GPS::i2cAddress = 0;
GPS *gps;
/// Multiple GPS instances might use the same serial port (in sequence), but we can
/// Multiple GPS instances might use the same serial port (in sequence), but we can
/// only init that port once.
static bool didSerialInit;
@ -33,7 +33,7 @@ bool GPS::setupGPS()
{
if (_serial_gps && !didSerialInit) {
didSerialInit = true;
#ifdef GPS_RX_PIN
_serial_gps->begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
#else
@ -73,6 +73,13 @@ bool GPS::setup()
return ok;
}
GPS::~GPS()
{
// we really should unregister our sleep observer
notifySleepObserver.unobserve();
notifyDeepSleepObserver.unobserve();
}
// Allow defining the polarity of the WAKE output. default is active high
#ifndef GPS_WAKE_ACTIVE
#define GPS_WAKE_ACTIVE 1
@ -86,8 +93,8 @@ void GPS::wake()
#endif
}
void GPS::sleep() {
void GPS::sleep()
{
#ifdef PIN_GPS_WAKE
digitalWrite(PIN_GPS_WAKE, GPS_WAKE_ACTIVE ? 0 : 1);
pinMode(PIN_GPS_WAKE, OUTPUT);
@ -158,7 +165,8 @@ uint32_t GPS::getWakeTime() const
return t; // already maxint
if (t == 0)
t = radioConfig.preferences.is_router ? 5 * 60 : 15 * 60; // Allow up to 15 mins for each attempt (probably will be much less if we can find sats) or less if a router
t = radioConfig.preferences.is_router ? 5 * 60 : 15 * 60; // Allow up to 15 mins for each attempt (probably will be much
// less if we can find sats) or less if a router
t *= 1000; // msecs
@ -179,8 +187,8 @@ uint32_t GPS::getSleepTime() const
if (t == UINT32_MAX)
return t; // already maxint
if (t == 0) // default - unset in preferences
t = radioConfig.preferences.is_router ? 24 * 60 * 60 : 2 * 60; // 2 mins or once per day for routers
if (t == 0) // default - unset in preferences
t = radioConfig.preferences.is_router ? 24 * 60 * 60 : 2 * 60; // 2 mins or once per day for routers
t *= 1000;

View File

@ -47,7 +47,7 @@ class GPS : private concurrency::OSThread
GPS() : concurrency::OSThread("GPS") {}
virtual ~GPS() {} // FIXME, we really should unregister our sleep observer
virtual ~GPS();
/** We will notify this observable anytime GPS state has changed meaningfully */
Observable<const meshtastic::GPSStatus *> newStatus;

View File

@ -143,11 +143,13 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1
drawIconScreen(region, display, state, x, y);
}
#ifdef HAS_EINK
/// Used on eink displays while in deep sleep
static void drawSleepScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
drawIconScreen("Sleeping...", display, state, x, y);
}
#endif
static void drawPluginFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
{
@ -791,7 +793,8 @@ void Screen::setup()
powerStatusObserver.observe(&powerStatus->onNewStatus);
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
textMessageObserver.observe(textMessagePlugin);
if(textMessagePlugin)
textMessageObserver.observe(textMessagePlugin);
}
void Screen::forceDisplay()
@ -849,7 +852,7 @@ int32_t Screen::runOnce()
free(cmd.print_text);
break;
default:
DEBUG_MSG("BUG: invalid cmd");
DEBUG_MSG("BUG: invalid cmd\n");
}
}

View File

@ -279,7 +279,13 @@ void setup()
concurrency::hasBeenSetup = true;
#ifdef SEGGER_STDOUT_CH
SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, 1024, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
auto mode = true ? SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL : SEGGER_RTT_MODE_NO_BLOCK_TRIM;
#ifdef NRF52840_XXAA
auto buflen = 4096; // this board has a fair amount of ram
#else
auto buflen = 256; // this board has a fair amount of ram
#endif
SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, buflen, mode);
#endif
#ifdef USE_SEGGER
@ -583,6 +589,9 @@ void loop()
#ifndef NO_ESP32
esp32Loop();
#endif
#ifdef NRF52_SERIES
nrf52Loop();
#endif
// For debugging
// if (rIf) ((RadioLibInterface *)rIf)->isActivelyReceiving();

View File

@ -13,7 +13,7 @@ Channels channels;
uint8_t xorHash(const uint8_t *p, size_t len)
{
uint8_t code = 0;
for (int i = 0; i < len; i++)
for (size_t i = 0; i < len; i++)
code ^= p[i];
return code;
}

View File

@ -101,25 +101,30 @@ template <class T> class MemoryPool : public Allocator<T>
/// Return a buffer for use by others
virtual void release(T *p)
{
assert(dead.enqueue(p, 0));
assert(p >= buf &&
(size_t)(p - buf) <
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
assert(dead.enqueue(p, 0));
}
#ifdef HAS_FREE_RTOS
/// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-)
void releaseFromISR(T *p, BaseType_t *higherPriWoken)
{
assert(dead.enqueueFromISR(p, higherPriWoken));
assert(p >= buf &&
(size_t)(p - buf) <
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
assert(dead.enqueueFromISR(p, higherPriWoken));
}
#endif
protected:
/// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you
/// probably don't want this version).
virtual T *alloc(TickType_t maxWait) { return dead.dequeuePtr(maxWait); }
virtual T *alloc(TickType_t maxWait)
{
T *p = dead.dequeuePtr(maxWait);
assert(p);
return p;
}
};

View File

@ -25,7 +25,8 @@ typedef enum _CriticalErrorCode {
CriticalErrorCode_UBloxInitFailed = 5,
CriticalErrorCode_NoAXP192 = 6,
CriticalErrorCode_InvalidRadioSetting = 7,
CriticalErrorCode_TransmitFailed = 8
CriticalErrorCode_TransmitFailed = 8,
CriticalErrorCode_Brownout = 9
} CriticalErrorCode;
typedef enum _Routing_Error {
@ -177,8 +178,8 @@ typedef struct _ToRadio {
#define _Constants_ARRAYSIZE ((Constants)(Constants_DATA_PAYLOAD_LEN+1))
#define _CriticalErrorCode_MIN CriticalErrorCode_None
#define _CriticalErrorCode_MAX CriticalErrorCode_TransmitFailed
#define _CriticalErrorCode_ARRAYSIZE ((CriticalErrorCode)(CriticalErrorCode_TransmitFailed+1))
#define _CriticalErrorCode_MAX CriticalErrorCode_Brownout
#define _CriticalErrorCode_ARRAYSIZE ((CriticalErrorCode)(CriticalErrorCode_Brownout+1))
#define _Routing_Error_MIN Routing_Error_NONE
#define _Routing_Error_MAX Routing_Error_TOO_LARGE

View File

@ -82,8 +82,34 @@ extern "C" void HardFault_Impl(uint32_t stack[])
// while (1) ;
}
#ifndef INC_FREERTOS_H
// This is a generic cortex M entrypoint that doesn't assume freertos
extern "C" void HardFault_Handler(void)
{
asm volatile(" mrs r0,msp\n"
" b HardFault_Impl \n");
}
#else
/* The prototype shows it is a naked function - in effect this is just an
assembly function. */
extern "C" void HardFault_Handler( void ) __attribute__( ( naked ) );
/* The fault handler implementation calls a function called
prvGetRegistersFromStack(). */
extern "C" void HardFault_Handler(void)
{
__asm volatile
(
" tst lr, #4 \n"
" ite eq \n"
" mrseq r0, msp \n"
" mrsne r0, psp \n"
" ldr r1, [r0, #24] \n"
" ldr r2, handler2_address_const \n"
" bx r2 \n"
" handler2_address_const: .word HardFault_Impl \n"
);
}
#endif

View File

@ -1,5 +1,6 @@
#include "NRF52Bluetooth.h"
#include "configuration.h"
#include "error.h"
#include "graphics/TFTDisplay.h"
#include <SPI.h>
#include <Wire.h>
@ -51,14 +52,14 @@ void getMacAddr(uint8_t *dmac)
NRF52Bluetooth *nrf52Bluetooth;
static bool bleOn = false;
static const bool enableBle = false; // Set to false for easier debugging
static const bool useSoftDevice = false; // Set to false for easier debugging
void setBluetoothEnable(bool on)
{
if (on != bleOn) {
if (on) {
if (!nrf52Bluetooth) {
if (!enableBle)
if (!useSoftDevice)
DEBUG_MSG("DISABLING NRF52 BLUETOOTH WHILE DEBUGGING\n");
else {
nrf52Bluetooth = new NRF52Bluetooth();
@ -87,6 +88,49 @@ int printf(const char *fmt, ...)
#include "BQ25713.h"
void initBrownout()
{
auto vccthresh = POWER_POFCON_THRESHOLD_V28;
auto vcchthresh = POWER_POFCON_THRESHOLDVDDH_V27;
if (useSoftDevice) {
auto err_code = sd_power_pof_enable(POWER_POFCON_POF_Enabled);
assert(err_code == NRF_SUCCESS);
err_code = sd_power_pof_threshold_set(vccthresh);
assert(err_code == NRF_SUCCESS);
}
else {
NRF_POWER->POFCON = POWER_POFCON_POF_Msk | (vccthresh << POWER_POFCON_THRESHOLD_Pos) | (vcchthresh << POWER_POFCON_THRESHOLDVDDH_Pos);
}
}
void checkSDEvents()
{
if (useSoftDevice) {
uint32_t evt;
while (NRF_ERROR_NOT_FOUND == sd_evt_get(&evt)) {
switch (evt) {
case NRF_EVT_POWER_FAILURE_WARNING:
recordCriticalError(CriticalErrorCode_Brownout);
break;
default:
DEBUG_MSG("Unexpected SDevt %d\n", evt);
break;
}
}
} else {
if(NRF_POWER->EVENTS_POFWARN)
recordCriticalError(CriticalErrorCode_Brownout);
}
}
void nrf52Loop()
{
checkSDEvents();
}
void nrf52Setup()
{
@ -112,6 +156,8 @@ void nrf52Setup()
// randomSeed(r);
DEBUG_MSG("FIXME, call randomSeed\n");
// ::printf("TESTING PRINTF\n");
initBrownout();
}
void cpuDeepSleep(uint64_t msecToWake)
@ -128,7 +174,7 @@ void cpuDeepSleep(uint64_t msecToWake)
// https://devzone.nordicsemi.com/f/nordic-q-a/48919/ram-retention-settings-with-softdevice-enabled
auto ok = sd_power_system_off();
if(ok != NRF_SUCCESS) {
if (ok != NRF_SUCCESS) {
DEBUG_MSG("FIXME: Ignoring soft device (EasyDMA pending?) and forcing system-off!\n");
NRF_POWER->SYSTEMOFF = 1;
}

View File

@ -114,7 +114,8 @@ int32_t ExternalNotificationPlugin::runOnce()
return (INT32_MAX);
}
#else
return INT32_MAX;
#endif
}

View File

@ -19,9 +19,11 @@ bool NodeInfoPlugin::handleReceivedProtobuf(const MeshPacket &mp, const User *pp
// Show new nodes on LCD screen
if (wasBroadcast) {
String lcd = String("Joined: ") + p.long_name + "\n";
screen->print(lcd.c_str());
if(screen)
screen->print(lcd.c_str());
}
// DEBUG_MSG("did handleReceived\n");
return false; // Let others look at this message also if they want
}

View File

@ -124,7 +124,8 @@ int32_t SerialPlugin::runOnce()
return (INT32_MAX);
}
#else
return INT32_MAX;
#endif
}

View File

@ -99,16 +99,16 @@ extern "C" {
#define NUM_ANALOG_OUTPUTS (0)
// LEDs
#define PIN_LED1 (0 + 13) // red (confirmed on 1.0 board)
#define PIN_LED2 (0 + 14) // blue (seems busted!)
#define PIN_LED3 (0 + 15) // green (seems busted!)
#define PIN_LED1 (0 + 14) // 13 red (confirmed on 1.0 board)
#define PIN_LED2 (0 + 15) // 14 blue
#define PIN_LED3 (0 + 13) // 15 green
#define LED_RED PIN_LED3
#define LED_GREEN PIN_LED1
#define LED_BLUE PIN_LED2
#define LED_BLUE PIN_LED1
#define LED_GREEN PIN_LED2
#define LED_BUILTIN LED_GREEN
#define LED_CONN PIN_BLUE
#define LED_BUILTIN LED_BLUE
#define LED_CONN PIN_GREEN
#define LED_STATE_ON 0 // State when LED is lit
#define LED_INVERTED 1
@ -192,6 +192,9 @@ External serial flash WP25R1635FZUIL0
// #define LORA_DISABLE_SENDING // Define this to disable transmission for testing (power testing etc...)
// #undef SX1262_CS
// #define USE_SIM_RADIO // define to not use the lora radio hardware at all
/*
* eink display pins
*/