Add Meshnology W10 AIOT Dev Kit (SX1262 via MCP23017 I2C expander) (#10911)
* Add software LoRa IRQ polling and an MCP23017 expander HAL for radios behind an I2C expander
Some boards route the SX126x control lines (NRESET/DIO1/BUSY) through an I2C
GPIO expander instead of native GPIOs, and don't wire the expander's /INT to
the MCU, so RadioLib cannot take a hardware DIO1 interrupt.
Two reusable pieces, both gated behind USE_MCP23017 / LORA_DIO1_SOFTWARE_POLL
so builds that don't define them are unaffected:
- ExtensionIOMCP23017 + MCP23017LockingArduinoHal: a RadioLib HAL that maps
virtual pins (MCP23017_VPIN_BASE .. +15) onto an MCP23017's GPIO, so
RESET/BUSY/DIO1 reads and writes become I2C transactions while the real SPI
GPIOs (SCK/MOSI/MISO/CS) pass straight through. A failed I2C read skips the
read-modify-write rather than clobbering the rest of the bank.
- LORA_DIO1_SOFTWARE_POLL: with no DIO1 interrupt available, the SX126x
interface polls the radio IRQ status register from the radio thread and
synthesizes the ISR_TX/ISR_RX events, filtering noisy preamble/header IRQs so
they can't starve TX. A 1 ms ISR_POLL_TICK drives the poll; TX timers may
overwrite the pending tick (pollMissedIrqs() is the backup that bounds the
latency of the busy-Rx contention window). Behaviour is unchanged on all
boards that keep the hardware interrupt.
* Add Meshnology W10 AIOT Dev Kit variant (SX1262 via MCP23017 expander)
ESP32-S3R8 + EBYTE E22-900MM22S (SX1262) + AXP2101 PMIC + Quectel L76KB GPS +
SPI TFT, with the radio's RESET/DIO1/BUSY and the LCD reset routed through an
MCP23017 I2C expander at 0x20. The expander's /INT is not wired to the ESP32,
so DIO1 uses the software poll. Pins come from the board schematic and the
vendor firmware; the expander is an MCP23017 despite the V1.1 schematic's stale
'TCA9555' label (the V1.2 placement diagram and all vendor code use the MCP23017
register map).
A local pins_arduino.h shadows the generic esp32s3 variant to drop RGB_BUILTIN,
which otherwise pulls the Arduino RMT HAL into the link and fails against this
build's trimmed FreeRTOS config.
Reports HardwareModel 140 (MESHNOLOGY_W10, already present in the protobufs).
Hardware-verified on a real W10 AIOT Dev Kit: AXP2101 PMU, MCP23017, SX1262
init + a full over-the-air TX/RX round trip through the expander, PCF85063 RTC,
SHT41 and QMI8658 auto-detected, and an L76KB GPS fix.
* meshnology-w10: enable ES8311 speaker for notification tones
Bring up the ES8311 codec (I2C 0x18) -> NS4150 amp -> speaker so the board can
play notification tones / ringtones over the I2S buzzer path (the
use_i2s_as_buzzer external-notification option). Codec2 voice stays out of
scope: it is SX1280-only and this is a sub-GHz board.
- variant.h: HAS_I2S + DAC_I2S pins (MCLK=1, BCK=2, WS=4, DOUT=5, DIN=3)
- platformio.ini: arduino-audio-driver + ESP8266Audio + ESP8266SAM
- extra_variants/meshnology_w10/variant.cpp: lateInitVariant() configures the
ES8311, mirroring the other Meshtastic ES8311 boards. Kept codec-only (no
main.h) so the audio driver's 'using namespace audio_driver' does not pull a
conflicting GpioPin into scope alongside Meshtastic's class GpioPin.
- AudioThread.h: toggle the NS4150 amp (MCP23017 EXIO_PA_CTRL) around playback.
Verified on hardware: a test tune played audibly through the speaker.
* meshnology-w10: address review feedback on the MCP23017 driver
- ExtensionIOMCP23017: serialize register access with a mutex, since the radio
HAL (BUSY/DIO1/RESET) and AudioThread (amp enable) now reach the expander from
different threads and the read-modify-write paths are not atomic.
- digitalRead: return a fail-safe HIGH on a failed read instead of LOW, so a
transient I2C error on the LoRa BUSY line can't look like 'ready' and let
RadioLib start an SPI transaction early. (DIO1 is polled via the radio IRQ
register, not this pin.)
- enablePinChangeInterrupt: use the checked readReg() overload and skip on a
failed read, matching the other read-modify-write helpers.
- meshnology_w10 variant.cpp: log a warning if an ES8311 register write NACKs
instead of silently leaving the codec half-configured.
- RadioInterface.cpp: guard the MCP23017 HAL branch with ARCH_ESP32 to match the
include and driver-file guards.
* Replace board-model audio/sleep ifdefs with reusable variant capability macros
Two shared-code spots keyed off specific hardware models; move the board-specific
detail into opt-in macros the variants define, so the core code stays generic and
new boards can opt into the behavior class without touching shared files.
- AudioThread amp control: drop the T_LORA_PAGER / MESHNOLOGY_W10 ifdefs. A board
with an I2S amp now defines AUDIO_AMP_ENABLE(on) in its variant.h (T-LoRa Pager
and Meshnology W10 do), and AudioThread just calls it around playback. The
expander includes are keyed on USE_XL9555 / USE_MCP23017 (capabilities) instead
of the model name.
- Light-sleep / DIO1 wakeup: drop the SENSECAP_INDICATOR check. Boards whose
LORA_DIO1 is an expander pin (not an ESP32 GPIO) define LORA_DIO1_EXTENDED_IO
(SenseCAP Indicator and Meshnology W10), and sleep.cpp keys off that for both
the light-sleep skip and the DIO1 GPIO-wakeup skip. This also fixes a latent
issue on the SenseCAP: it previously reached the GPIO-wakeup path and called
gpio_pulldown_en() on a virtual expander pin; it now skips that cleanly.
Builds verified on meshnology_w10, tlora-pager, and seeed-sensecap-indicator.
* meshnology-w10: silence cppcheck constParameterPointer on the ISR-callback helper
isIsrTxCallback() takes a function pointer it only compares; cppcheck's
constParameterPointer wants it 'pointer to const', which is meaningless for a
function pointer (the codebase already globally suppresses the sibling
constParameterCallback). Inline-suppress it to keep the check green.
* HopScaling: qualify the member assignment as this->count
cppcheck (CI's version) flags 'count = newCount;' at the end of trimIfNeeded()
as uselessAssignmentArg ('assignment of function parameter has no effect') - a
false positive, since count is a member that outlives the call. Write it as
this->count (matching the Step-1 assignment above) so the analyzer sees a member
write. This finding comes in via the develop merge, not this board; fixing it
here to unblock the PR's cppcheck check.
This commit is contained in:
+15
-8
@@ -12,11 +12,18 @@
|
|||||||
#include <AudioOutputI2S.h>
|
#include <AudioOutputI2S.h>
|
||||||
#include <ESP8266SAM.h>
|
#include <ESP8266SAM.h>
|
||||||
|
|
||||||
|
// A board with an I2S amplifier opts in by defining AUDIO_AMP_ENABLE(on) in its variant.h to power the
|
||||||
|
// amp on/off around playback (e.g. an enable pin on an I/O expander). The includes below expose the
|
||||||
|
// expander instances (io / mcpIoExpander) those macros typically reference.
|
||||||
#ifdef USE_XL9555
|
#ifdef USE_XL9555
|
||||||
#include "ExtensionIOXL9555.hpp"
|
#include "ExtensionIOXL9555.hpp"
|
||||||
extern ExtensionIOXL9555 io;
|
extern ExtensionIOXL9555 io;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_MCP23017
|
||||||
|
#include "platform/esp32/ExtensionIOMCP23017.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define AUDIO_THREAD_INTERVAL_MS 100
|
#define AUDIO_THREAD_INTERVAL_MS 100
|
||||||
|
|
||||||
class AudioThread : public concurrency::OSThread
|
class AudioThread : public concurrency::OSThread
|
||||||
@@ -26,8 +33,8 @@ class AudioThread : public concurrency::OSThread
|
|||||||
|
|
||||||
void beginRttl(const void *data, uint32_t len)
|
void beginRttl(const void *data, uint32_t len)
|
||||||
{
|
{
|
||||||
#ifdef T_LORA_PAGER
|
#ifdef AUDIO_AMP_ENABLE
|
||||||
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
AUDIO_AMP_ENABLE(true);
|
||||||
#endif
|
#endif
|
||||||
setCPUFast(true);
|
setCPUFast(true);
|
||||||
rtttlFile = std::unique_ptr<AudioFileSourcePROGMEM>(new AudioFileSourcePROGMEM(data, len));
|
rtttlFile = std::unique_ptr<AudioFileSourcePROGMEM>(new AudioFileSourcePROGMEM(data, len));
|
||||||
@@ -54,8 +61,8 @@ class AudioThread : public concurrency::OSThread
|
|||||||
rtttlFile = nullptr;
|
rtttlFile = nullptr;
|
||||||
|
|
||||||
setCPUFast(false);
|
setCPUFast(false);
|
||||||
#ifdef T_LORA_PAGER
|
#ifdef AUDIO_AMP_ENABLE
|
||||||
io.digitalWrite(EXPANDS_AMP_EN, LOW);
|
AUDIO_AMP_ENABLE(false);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,14 +73,14 @@ class AudioThread : public concurrency::OSThread
|
|||||||
i2sRtttl = nullptr;
|
i2sRtttl = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef T_LORA_PAGER
|
#ifdef AUDIO_AMP_ENABLE
|
||||||
io.digitalWrite(EXPANDS_AMP_EN, HIGH);
|
AUDIO_AMP_ENABLE(true);
|
||||||
#endif
|
#endif
|
||||||
auto sam = std::unique_ptr<ESP8266SAM>(new ESP8266SAM);
|
auto sam = std::unique_ptr<ESP8266SAM>(new ESP8266SAM);
|
||||||
sam->Say(audioOut.get(), text);
|
sam->Say(audioOut.get(), text);
|
||||||
setCPUFast(false);
|
setCPUFast(false);
|
||||||
#ifdef T_LORA_PAGER
|
#ifdef AUDIO_AMP_ENABLE
|
||||||
io.digitalWrite(EXPANDS_AMP_EN, LOW);
|
AUDIO_AMP_ENABLE(false);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,6 +173,10 @@ AudioThread *audioThread = nullptr;
|
|||||||
ExtensionIOXL9555 io;
|
ExtensionIOXL9555 io;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef USE_MCP23017
|
||||||
|
#include "platform/esp32/ExtensionIOMCP23017.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HAS_TFT
|
#if HAS_TFT
|
||||||
extern void tftSetup(void);
|
extern void tftSetup(void);
|
||||||
#endif
|
#endif
|
||||||
@@ -606,6 +610,12 @@ void setup()
|
|||||||
powerStatus->observe(&power->newStatus);
|
powerStatus->observe(&power->newStatus);
|
||||||
power->setup(); // Must be after status handler is installed, so that handler gets notified of the initial configuration
|
power->setup(); // Must be after status handler is installed, so that handler gets notified of the initial configuration
|
||||||
|
|
||||||
|
#ifdef USE_MCP23017
|
||||||
|
// Bring up the I2C IO expander (LoRa reset, LCD reset, GPS wake) now that the PMU rails are up,
|
||||||
|
// before the I2C scan and radio/display init
|
||||||
|
mcp23017EarlyInit();
|
||||||
|
#endif
|
||||||
|
|
||||||
#if !MESHTASTIC_EXCLUDE_I2C
|
#if !MESHTASTIC_EXCLUDE_I2C
|
||||||
// We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to
|
// We need to scan here to decide if we have a screen for nodeDB.init() and because power has been applied to
|
||||||
// accessories
|
// accessories
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
#include "platform/portduino/USBHal.h"
|
#include "platform/portduino/USBHal.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if defined(ARCH_ESP32) && defined(USE_MCP23017)
|
||||||
|
#include "platform/esp32/MCP23017LockingArduinoHal.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ARCH_STM32WL
|
#ifdef ARCH_STM32WL
|
||||||
#include "STM32WLE5JCInterface.h"
|
#include "STM32WLE5JCInterface.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -401,6 +405,10 @@ std::unique_ptr<RadioInterface> initLoRa()
|
|||||||
#elif defined(HW_SPI1_DEVICE)
|
#elif defined(HW_SPI1_DEVICE)
|
||||||
LockingArduinoHal *loraHal = new LockingArduinoHal(SPI1, loraSpiSettings);
|
LockingArduinoHal *loraHal = new LockingArduinoHal(SPI1, loraSpiSettings);
|
||||||
RadioLibHAL = loraHal;
|
RadioLibHAL = loraHal;
|
||||||
|
#elif defined(ARCH_ESP32) && defined(USE_MCP23017)
|
||||||
|
// Radio control lines (RESET/DIO1/BUSY) are virtual pins on an MCP23017 I2C expander
|
||||||
|
LockingArduinoHal *loraHal = new MCP23017LockingArduinoHal(SPI, loraSpiSettings, mcpIoExpander);
|
||||||
|
RadioLibHAL = loraHal;
|
||||||
#else // HW_SPI1_DEVICE
|
#else // HW_SPI1_DEVICE
|
||||||
LockingArduinoHal *loraHal = new LockingArduinoHal(SPI, loraSpiSettings);
|
LockingArduinoHal *loraHal = new LockingArduinoHal(SPI, loraSpiSettings);
|
||||||
RadioLibHAL = loraHal;
|
RadioLibHAL = loraHal;
|
||||||
|
|||||||
@@ -363,6 +363,40 @@ The CW size is determined by setTransmitDelay() and depends either on the curren
|
|||||||
of a flooding message. After this, we perform channel activity detection (CAD) and reset the transmit delay if it is
|
of a flooding message. After this, we perform channel activity detection (CAD) and reset the transmit delay if it is
|
||||||
currently active.
|
currently active.
|
||||||
*/
|
*/
|
||||||
|
// In software-IRQ-poll mode (LORA_DIO1_SOFTWARE_POLL) a 1ms poll tick is almost always pending, so
|
||||||
|
// TX timers must be allowed to overwrite the pending notification or TX scheduling starves. On all
|
||||||
|
// other targets keep the historical non-overwriting behavior.
|
||||||
|
#ifdef LORA_DIO1_SOFTWARE_POLL
|
||||||
|
static constexpr bool txTimerOverwrite = true;
|
||||||
|
#else
|
||||||
|
static constexpr bool txTimerOverwrite = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// cppcheck-suppress constParameterPointer ; a function pointer can't meaningfully point to const
|
||||||
|
bool RadioLibInterface::isIsrTxCallback(void (*callback)())
|
||||||
|
{
|
||||||
|
return callback == isrTxLevel0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RadioLibInterface::scheduleIrqPollTick()
|
||||||
|
{
|
||||||
|
// Never overwrite a pending notification (especially TRANSMIT_DELAY_COMPLETED),
|
||||||
|
// otherwise poll ticks would starve TX scheduling.
|
||||||
|
//
|
||||||
|
// There is a single notification slot, so while a TX is queued and the radio is busy receiving,
|
||||||
|
// the self-rescheduling TRANSMIT_DELAY_COMPLETED timer (which does overwrite, see txTimerOverwrite)
|
||||||
|
// can keep the slot and prevent a poll tick from being scheduled. In that window a completing
|
||||||
|
// RX/TX is not seen by the poll; RadioInterface's pollMissedIrqs() (~1s) is the backup that
|
||||||
|
// recovers it, so the effect is bounded added latency under heavy contention, not a lost event.
|
||||||
|
notifyLater(1, ISR_POLL_TICK, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RadioLibInterface::deliverPendingIrqFromPoll(PendingISR cause)
|
||||||
|
{
|
||||||
|
disableInterrupt(); // stop polling; this is the poll-path equivalent of isrLevel0Common()
|
||||||
|
notify(cause, true);
|
||||||
|
}
|
||||||
|
|
||||||
void RadioLibInterface::onNotify(uint32_t notification)
|
void RadioLibInterface::onNotify(uint32_t notification)
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -385,6 +419,9 @@ void RadioLibInterface::onNotify(uint32_t notification)
|
|||||||
startReceive();
|
startReceive();
|
||||||
setTransmitDelay();
|
setTransmitDelay();
|
||||||
break;
|
break;
|
||||||
|
case ISR_POLL_TICK:
|
||||||
|
handleSoftwareLoraIrqPoll();
|
||||||
|
break;
|
||||||
case TRANSMIT_DELAY_COMPLETED:
|
case TRANSMIT_DELAY_COMPLETED:
|
||||||
|
|
||||||
// If we are not currently in receive mode, then restart the random delay (this can happen if the main thread
|
// If we are not currently in receive mode, then restart the random delay (this can happen if the main thread
|
||||||
@@ -398,7 +435,7 @@ void RadioLibInterface::onNotify(uint32_t notification)
|
|||||||
long delay_remaining = txp->tx_after ? txp->tx_after - millis() : 0;
|
long delay_remaining = txp->tx_after ? txp->tx_after - millis() : 0;
|
||||||
if (delay_remaining > 0) {
|
if (delay_remaining > 0) {
|
||||||
// There's still some delay pending on this packet, so resume waiting for it to elapse
|
// There's still some delay pending on this packet, so resume waiting for it to elapse
|
||||||
notifyLater(delay_remaining, TRANSMIT_DELAY_COMPLETED, false);
|
notifyLater(delay_remaining, TRANSMIT_DELAY_COMPLETED, txTimerOverwrite);
|
||||||
#if !MESHTASTIC_EXCLUDE_BEACON
|
#if !MESHTASTIC_EXCLUDE_BEACON
|
||||||
} else if (MeshBeaconModule::beaconTxConfigInvalid(txp)) {
|
} else if (MeshBeaconModule::beaconTxConfigInvalid(txp)) {
|
||||||
// The beacon's target radio config is invalid (bad preset/region, or an
|
// The beacon's target radio config is invalid (bad preset/region, or an
|
||||||
@@ -455,7 +492,7 @@ void RadioLibInterface::setTransmitDelay()
|
|||||||
unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p) : getTxDelayMsec();
|
unsigned long add_delay = p->rx_rssi ? getTxDelayMsecWeighted(p) : getTxDelayMsec();
|
||||||
unsigned long now = millis();
|
unsigned long now = millis();
|
||||||
p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr));
|
p->tx_after = min(max(p->tx_after + add_delay, now + add_delay), now + 2 * getTxDelayMsecWeightedWorst(p->rx_snr));
|
||||||
notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, false);
|
notifyLater(p->tx_after - now, TRANSMIT_DELAY_COMPLETED, txTimerOverwrite);
|
||||||
} else if (p->rx_snr == 0 && p->rx_rssi == 0) {
|
} else if (p->rx_snr == 0 && p->rx_rssi == 0) {
|
||||||
/* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally.
|
/* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally.
|
||||||
* This assumption is valid because of the offset generated by the radio to account for the noise
|
* This assumption is valid because of the offset generated by the radio to account for the noise
|
||||||
@@ -474,7 +511,7 @@ void RadioLibInterface::startTransmitTimer(bool withDelay)
|
|||||||
// If we have work to do and the timer wasn't already scheduled, schedule it now
|
// If we have work to do and the timer wasn't already scheduled, schedule it now
|
||||||
if (!txQueue.empty()) {
|
if (!txQueue.empty()) {
|
||||||
uint32_t delay = !withDelay ? 1 : getTxDelayMsec();
|
uint32_t delay = !withDelay ? 1 : getTxDelayMsec();
|
||||||
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
|
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, txTimerOverwrite); // This will implicitly enable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -483,7 +520,7 @@ void RadioLibInterface::startTransmitTimerRebroadcast(meshtastic_MeshPacket *p)
|
|||||||
// If we have work to do and the timer wasn't already scheduled, schedule it now
|
// If we have work to do and the timer wasn't already scheduled, schedule it now
|
||||||
if (!txQueue.empty()) {
|
if (!txQueue.empty()) {
|
||||||
uint32_t delay = getTxDelayMsecWeighted(p);
|
uint32_t delay = getTxDelayMsecWeighted(p);
|
||||||
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, false); // This will implicitly enable
|
notifyLater(delay, TRANSMIT_DELAY_COMPLETED, txTimerOverwrite); // This will implicitly enable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,17 +52,17 @@ class STM32WLx_ModuleWrapper : public STM32WLx_Module
|
|||||||
|
|
||||||
class RadioLibInterface : public RadioInterface, protected concurrency::NotifiedWorkerThread
|
class RadioLibInterface : public RadioInterface, protected concurrency::NotifiedWorkerThread
|
||||||
{
|
{
|
||||||
|
MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE);
|
||||||
|
|
||||||
|
protected:
|
||||||
/// Used as our notification from the ISR
|
/// Used as our notification from the ISR
|
||||||
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED };
|
enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED, ISR_POLL_TICK };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Raw ISR handler that just calls our polymorphic method
|
* Raw ISR handler that just calls our polymorphic method
|
||||||
*/
|
*/
|
||||||
static void isrTxLevel0(), isrLevel0Common(PendingISR code);
|
static void isrTxLevel0(), isrLevel0Common(PendingISR code);
|
||||||
|
|
||||||
MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
ModemType_t modemType = RADIOLIB_MODEM_LORA;
|
ModemType_t modemType = RADIOLIB_MODEM_LORA;
|
||||||
DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; }
|
DataRate_t getDataRate() const { return {.lora = {.spreadingFactor = sf, .bandwidth = bw, .codingRate = cr}}; }
|
||||||
PacketConfig_t getPacketConfig() const
|
PacketConfig_t getPacketConfig() const
|
||||||
@@ -350,4 +350,13 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
|
|||||||
|
|
||||||
void checkRxDoneIrqFlag();
|
void checkRxDoneIrqFlag();
|
||||||
void checkTxDoneIrqFlag();
|
void checkTxDoneIrqFlag();
|
||||||
|
|
||||||
|
/** Software-poll substitute for a hardware DIO interrupt, for radios whose IRQ line sits behind
|
||||||
|
* an I2C IO expander with no INT routed to the MCU (e.g. Meshnology W10, LORA_DIO1_SOFTWARE_POLL).
|
||||||
|
* The chip-specific subclass polls the radio's IRQ status register from the radio thread and
|
||||||
|
* synthesizes ISR_TX/ISR_RX events equivalent to the hardware DIO1 interrupt. */
|
||||||
|
void deliverPendingIrqFromPoll(PendingISR cause);
|
||||||
|
void scheduleIrqPollTick();
|
||||||
|
static bool isIsrTxCallback(void (*callback)());
|
||||||
|
virtual void handleSoftwareLoraIrqPoll() {}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -274,11 +274,62 @@ template <typename T> int16_t SX126xInterface<T>::getCurrentRSSI()
|
|||||||
return (int16_t)round(rssi);
|
return (int16_t)round(rssi);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> void SX126xInterface<T>::enableInterrupt(void (*callback)())
|
||||||
|
{
|
||||||
|
#ifdef LORA_DIO1_SOFTWARE_POLL
|
||||||
|
irqPollingActive = true;
|
||||||
|
pollTxMode = isIsrTxCallback(callback);
|
||||||
|
scheduleIrqPollTick();
|
||||||
|
#else
|
||||||
|
lora.setDio1Action(callback);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T> void SX126xInterface<T>::disableInterrupt()
|
template <typename T> void SX126xInterface<T>::disableInterrupt()
|
||||||
{
|
{
|
||||||
|
#ifdef LORA_DIO1_SOFTWARE_POLL
|
||||||
|
irqPollingActive = false;
|
||||||
|
#else
|
||||||
lora.clearDio1Action();
|
lora.clearDio1Action();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef LORA_DIO1_SOFTWARE_POLL
|
||||||
|
template <typename T> void SX126xInterface<T>::handleSoftwareLoraIrqPoll()
|
||||||
|
{
|
||||||
|
if (!irqPollingActive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// getIrqFlags()/clearIrqFlags() both operate on the raw SX126x IRQ register, so use the
|
||||||
|
// chip-specific RADIOLIB_SX126X_IRQ_* masks on both the read and the clear.
|
||||||
|
uint16_t irq = lora.getIrqFlags();
|
||||||
|
const uint16_t rxEventMask =
|
||||||
|
RADIOLIB_SX126X_IRQ_RX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT | RADIOLIB_SX126X_IRQ_CRC_ERR | RADIOLIB_SX126X_IRQ_HEADER_ERR;
|
||||||
|
const uint16_t noisyRxMask = RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID;
|
||||||
|
|
||||||
|
// Do NOT treat a preamble/header-only IRQ as a full RX event: noisy preamble detections would
|
||||||
|
// repeatedly trigger readData() and starve TX scheduling. Clear these non-terminal bits, or the
|
||||||
|
// poll loop spins at high rate while they stay latched.
|
||||||
|
if (!pollTxMode && (irq & noisyRxMask) && ((irq & ~noisyRxMask) == 0U)) {
|
||||||
|
lora.clearIrqFlags(noisyRxMask);
|
||||||
|
scheduleIrqPollTick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pollTxMode) {
|
||||||
|
if (irq & (RADIOLIB_SX126X_IRQ_TX_DONE | RADIOLIB_SX126X_IRQ_TIMEOUT)) {
|
||||||
|
deliverPendingIrqFromPoll(ISR_TX);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (irq & rxEventMask) {
|
||||||
|
deliverPendingIrqFromPoll(ISR_RX);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleIrqPollTick();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
template <typename T> void SX126xInterface<T>::setStandby()
|
template <typename T> void SX126xInterface<T>::setStandby()
|
||||||
{
|
{
|
||||||
checkNotification(); // handle any pending interrupts before we force standby
|
checkNotification(); // handle any pending interrupts before we force standby
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#if RADIOLIB_EXCLUDE_SX126X != 1
|
#if RADIOLIB_EXCLUDE_SX126X != 1
|
||||||
|
|
||||||
#include "RadioLibInterface.h"
|
#include "RadioLibInterface.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Adapter for SX126x radio family. Implements common logic for child classes.
|
* \brief Adapter for SX126x radio family. Implements common logic for child classes.
|
||||||
@@ -51,7 +52,11 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
|||||||
/**
|
/**
|
||||||
* Enable a particular ISR callback glue function
|
* Enable a particular ISR callback glue function
|
||||||
*/
|
*/
|
||||||
virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); }
|
virtual void enableInterrupt(void (*callback)()) override;
|
||||||
|
|
||||||
|
#ifdef LORA_DIO1_SOFTWARE_POLL
|
||||||
|
void handleSoftwareLoraIrqPoll() override;
|
||||||
|
#endif
|
||||||
|
|
||||||
/** can we detect a LoRa preamble on the current channel? */
|
/** can we detect a LoRa preamble on the current channel? */
|
||||||
virtual bool isChannelActive() override;
|
virtual bool isChannelActive() override;
|
||||||
@@ -79,6 +84,10 @@ template <class T> class SX126xInterface : public RadioLibInterface
|
|||||||
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
|
uint32_t getPacketTime(uint32_t pl, bool received) override { return computePacketTime(lora, pl, received); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
#ifdef LORA_DIO1_SOFTWARE_POLL
|
||||||
|
bool irqPollingActive = false;
|
||||||
|
bool pollTxMode = false;
|
||||||
|
#endif
|
||||||
/** Some boards require GPIO control of tx vs rx paths */
|
/** Some boards require GPIO control of tx vs rx paths */
|
||||||
void setTransmitEnable(bool txon);
|
void setTransmitEnable(bool txon);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ void HopScalingModule::trimIfNeeded()
|
|||||||
newCount++;
|
newCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
count = newCount;
|
this->count = newCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#if defined(ARCH_ESP32) && defined(USE_MCP23017)
|
||||||
|
|
||||||
|
#include "ExtensionIOMCP23017.h"
|
||||||
|
#include "concurrency/LockGuard.h"
|
||||||
|
|
||||||
|
ExtensionIOMCP23017 mcpIoExpander;
|
||||||
|
|
||||||
|
void ExtensionIOMCP23017::begin(TwoWire &wire, uint8_t addr, int sda, int scl)
|
||||||
|
{
|
||||||
|
if (_begun)
|
||||||
|
return;
|
||||||
|
_wire = &wire;
|
||||||
|
_addr = addr;
|
||||||
|
wire.begin(sda, scl);
|
||||||
|
_begun = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExtensionIOMCP23017::readReg(uint8_t reg, uint8_t &val)
|
||||||
|
{
|
||||||
|
if (!_wire || !_begun)
|
||||||
|
return false;
|
||||||
|
_wire->beginTransmission(_addr);
|
||||||
|
_wire->write(reg);
|
||||||
|
if (_wire->endTransmission() != 0)
|
||||||
|
return false;
|
||||||
|
if (_wire->requestFrom((int)_addr, 1) != 1 || !_wire->available())
|
||||||
|
return false;
|
||||||
|
val = (uint8_t)_wire->read();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ExtensionIOMCP23017::readReg(uint8_t reg)
|
||||||
|
{
|
||||||
|
uint8_t val = 0;
|
||||||
|
readReg(reg, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionIOMCP23017::writeReg(uint8_t reg, uint8_t val)
|
||||||
|
{
|
||||||
|
if (!_wire || !_begun)
|
||||||
|
return;
|
||||||
|
_wire->beginTransmission(_addr);
|
||||||
|
_wire->write(reg);
|
||||||
|
_wire->write(val);
|
||||||
|
_wire->endTransmission();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionIOMCP23017::updateDirectionBit(uint8_t pin, bool asOutput)
|
||||||
|
{
|
||||||
|
uint8_t reg = iodirRegForPin(pin);
|
||||||
|
uint8_t v;
|
||||||
|
if (!readReg(reg, v)) // skip the write rather than clobber the other 7 pins on a failed read
|
||||||
|
return;
|
||||||
|
uint8_t mask = bitForPin(pin);
|
||||||
|
if (asOutput)
|
||||||
|
v &= ~mask; // output: bit 0
|
||||||
|
else
|
||||||
|
v |= mask; // input: bit 1
|
||||||
|
writeReg(reg, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionIOMCP23017::pinMode(uint8_t pin, uint8_t mode)
|
||||||
|
{
|
||||||
|
if (pin > 15)
|
||||||
|
return;
|
||||||
|
concurrency::LockGuard guard(&_lock);
|
||||||
|
if (mode == OUTPUT) {
|
||||||
|
updateDirectionBit(pin, true);
|
||||||
|
} else {
|
||||||
|
updateDirectionBit(pin, false);
|
||||||
|
uint8_t reg = gppuRegForPin(pin);
|
||||||
|
uint8_t v;
|
||||||
|
if (!readReg(reg, v)) // skip rather than clobber the bank on a failed read
|
||||||
|
return;
|
||||||
|
if (mode == INPUT_PULLUP)
|
||||||
|
v |= bitForPin(pin);
|
||||||
|
else
|
||||||
|
v &= ~bitForPin(pin);
|
||||||
|
writeReg(reg, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionIOMCP23017::digitalWrite(uint8_t pin, uint8_t value)
|
||||||
|
{
|
||||||
|
if (pin > 15)
|
||||||
|
return;
|
||||||
|
concurrency::LockGuard guard(&_lock);
|
||||||
|
uint8_t reg = olatRegForPin(pin);
|
||||||
|
uint8_t v;
|
||||||
|
if (!readReg(reg, v)) // skip rather than clobber the bank (e.g. drop LORA_NRST) on a failed read
|
||||||
|
return;
|
||||||
|
uint8_t mask = bitForPin(pin);
|
||||||
|
if (value == LOW)
|
||||||
|
v &= ~mask;
|
||||||
|
else
|
||||||
|
v |= mask;
|
||||||
|
writeReg(reg, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ExtensionIOMCP23017::digitalRead(uint8_t pin)
|
||||||
|
{
|
||||||
|
if (pin > 15)
|
||||||
|
return LOW;
|
||||||
|
concurrency::LockGuard guard(&_lock);
|
||||||
|
uint8_t v;
|
||||||
|
if (!readReg(gpioRegForPin(pin), v))
|
||||||
|
return HIGH; // fail-safe: a failed read must not look like "LoRa BUSY released" and let RadioLib
|
||||||
|
// start an SPI transaction too early. DIO1 is polled via the radio IRQ register, not
|
||||||
|
// this pin, so reading it high on error is harmless there.
|
||||||
|
return (v & bitForPin(pin)) ? HIGH : LOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionIOMCP23017::enablePinChangeInterrupt(uint8_t pin, bool enable)
|
||||||
|
{
|
||||||
|
if (pin > 15)
|
||||||
|
return;
|
||||||
|
concurrency::LockGuard guard(&_lock);
|
||||||
|
uint8_t regG = gpintenRegForPin(pin);
|
||||||
|
uint8_t v;
|
||||||
|
if (!readReg(regG, v)) // skip rather than clobber the bank on a failed read
|
||||||
|
return;
|
||||||
|
if (enable)
|
||||||
|
v |= bitForPin(pin);
|
||||||
|
else
|
||||||
|
v &= ~bitForPin(pin);
|
||||||
|
writeReg(regG, v);
|
||||||
|
// INTCON: 0 = interrupt on pin change from previous value
|
||||||
|
uint8_t regI = intconRegForPin(pin);
|
||||||
|
if (!readReg(regI, v))
|
||||||
|
return;
|
||||||
|
v &= ~bitForPin(pin);
|
||||||
|
writeReg(regI, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionIOMCP23017::clearInterruptLatches()
|
||||||
|
{
|
||||||
|
if (!_begun)
|
||||||
|
return;
|
||||||
|
concurrency::LockGuard guard(&_lock);
|
||||||
|
(void)readReg(0x10); // INTCAPA - clears INTA condition
|
||||||
|
(void)readReg(0x11); // INTCAPB
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t ExtensionIOMCP23017::readRegister(uint8_t reg)
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&_lock);
|
||||||
|
return readReg(reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mcp23017EarlyInit()
|
||||||
|
{
|
||||||
|
mcpIoExpander.begin(Wire, MCP23017_ADDR, I2C_SDA, I2C_SCL);
|
||||||
|
|
||||||
|
#ifdef EXIO_LCD_RST
|
||||||
|
mcpIoExpander.pinMode(EXIO_LCD_RST, OUTPUT);
|
||||||
|
mcpIoExpander.digitalWrite(EXIO_LCD_RST, LOW);
|
||||||
|
delay(10);
|
||||||
|
mcpIoExpander.digitalWrite(EXIO_LCD_RST, HIGH);
|
||||||
|
delay(20);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef EXIO_LORA_NRST
|
||||||
|
mcpIoExpander.pinMode(EXIO_LORA_NRST, OUTPUT);
|
||||||
|
mcpIoExpander.digitalWrite(EXIO_LORA_NRST, HIGH);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef EXIO_LORA_BUSY
|
||||||
|
mcpIoExpander.pinMode(EXIO_LORA_BUSY, INPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef EXIO_LORA_DIO1
|
||||||
|
mcpIoExpander.pinMode(EXIO_LORA_DIO1, INPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef EXIO_GPS_WAKE
|
||||||
|
mcpIoExpander.pinMode(EXIO_GPS_WAKE, OUTPUT);
|
||||||
|
mcpIoExpander.digitalWrite(EXIO_GPS_WAKE, HIGH);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef EXIO_IMU_INT1
|
||||||
|
mcpIoExpander.pinMode(EXIO_IMU_INT1, INPUT);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LOG_INFO("MCP23017 0x%02x: IODIRA=0x%02x IODIRB=0x%02x GPIOA=0x%02x GPIOB=0x%02x", MCP23017_ADDR,
|
||||||
|
mcpIoExpander.readRegister(0x00), mcpIoExpander.readRegister(0x01), mcpIoExpander.readRegister(0x12),
|
||||||
|
mcpIoExpander.readRegister(0x13));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ARCH_ESP32 && USE_MCP23017
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "concurrency/Lock.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MCP23017 16-bit I2C GPIO expander (pins 0-15 = GPA0-7, GPB0-7).
|
||||||
|
*
|
||||||
|
* Used by boards that route radio/display control lines through the expander
|
||||||
|
* (e.g. Meshnology W10). RadioLib access goes through MCP23017LockingArduinoHal,
|
||||||
|
* which maps virtual pins MCP23017_VPIN_BASE..+15 onto local pins 0-15.
|
||||||
|
*/
|
||||||
|
class ExtensionIOMCP23017
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ExtensionIOMCP23017() : _wire(nullptr), _addr(0), _begun(false) {}
|
||||||
|
|
||||||
|
void begin(TwoWire &wire, uint8_t addr, int sda, int scl);
|
||||||
|
|
||||||
|
/** Local pin index 0-15 (GPA0=0 ... GPB7=15), not the virtual RadioLib pin. */
|
||||||
|
void pinMode(uint8_t pin, uint8_t mode);
|
||||||
|
void digitalWrite(uint8_t pin, uint8_t value);
|
||||||
|
int digitalRead(uint8_t pin);
|
||||||
|
|
||||||
|
/** Enable the MCP23017 pin-change interrupt so /INT asserts (only useful if /INT is wired to the MCU). */
|
||||||
|
void enablePinChangeInterrupt(uint8_t pin, bool enable);
|
||||||
|
|
||||||
|
/** Read INTCAPx to clear a latched interrupt condition after MCP /INT asserts. */
|
||||||
|
void clearInterruptLatches();
|
||||||
|
|
||||||
|
/** Raw register read (0x00-0x1A), for bring-up / debug. */
|
||||||
|
uint8_t readRegister(uint8_t reg);
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t readReg(uint8_t reg);
|
||||||
|
// Checked read: returns false (and leaves val untouched) on any I2C error, so a glitched read
|
||||||
|
// can't drive a read-modify-write that clobbers the rest of the bank.
|
||||||
|
bool readReg(uint8_t reg, uint8_t &val);
|
||||||
|
void writeReg(uint8_t reg, uint8_t val);
|
||||||
|
void updateDirectionBit(uint8_t pin, bool asOutput);
|
||||||
|
uint8_t iodirRegForPin(uint8_t pin) const { return pin < 8 ? 0x00 : 0x01; }
|
||||||
|
uint8_t gpioRegForPin(uint8_t pin) const { return pin < 8 ? 0x12 : 0x13; }
|
||||||
|
uint8_t olatRegForPin(uint8_t pin) const { return pin < 8 ? 0x14 : 0x15; }
|
||||||
|
uint8_t gppuRegForPin(uint8_t pin) const { return pin < 8 ? 0x0C : 0x0D; }
|
||||||
|
uint8_t gpintenRegForPin(uint8_t pin) const { return pin < 8 ? 0x04 : 0x05; }
|
||||||
|
uint8_t intconRegForPin(uint8_t pin) const { return pin < 8 ? 0x08 : 0x09; }
|
||||||
|
uint8_t bitForPin(uint8_t pin) const { return 1u << (pin & 7); }
|
||||||
|
|
||||||
|
TwoWire *_wire;
|
||||||
|
uint8_t _addr;
|
||||||
|
bool _begun;
|
||||||
|
// Serializes register access: the radio HAL (BUSY/DIO1/RESET) and AudioThread (amp enable) both
|
||||||
|
// reach this expander from different threads, and the read-modify-write paths are not atomic.
|
||||||
|
concurrency::Lock _lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Global instance shared by the early-init hook and the RadioLib HAL. */
|
||||||
|
extern ExtensionIOMCP23017 mcpIoExpander;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bring up the expander and set board-specific pin directions/levels (LoRa reset,
|
||||||
|
* LCD reset, GPS wake, ...). Must run after power->setup() so the PMU rails are up,
|
||||||
|
* and before the I2C scan / radio / display init.
|
||||||
|
*/
|
||||||
|
void mcp23017EarlyInit();
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#if defined(ARCH_ESP32) && defined(USE_MCP23017)
|
||||||
|
|
||||||
|
#include "MCP23017LockingArduinoHal.h"
|
||||||
|
#include "SPILock.h"
|
||||||
|
|
||||||
|
MCP23017LockingArduinoHal::MCP23017LockingArduinoHal(SPIClass &spi, SPISettings spiSettings, ExtensionIOMCP23017 &expander)
|
||||||
|
: LockingArduinoHal(spi, spiSettings), mcp(expander)
|
||||||
|
{
|
||||||
|
#if MCP23017_INT_ESP32_PIN < 0
|
||||||
|
#if defined(LORA_DIO1_SOFTWARE_POLL)
|
||||||
|
LOG_INFO("MCP23017 /INT not wired: LoRa DIO1 IRQ simulated by polling the radio IRQ status register");
|
||||||
|
#else
|
||||||
|
LOG_WARN("MCP23017_INT_ESP32_PIN unset and no LORA_DIO1_SOFTWARE_POLL: LoRa DIO1 interrupts will not work");
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MCP23017LockingArduinoHal::isMcpPin(uint32_t pin)
|
||||||
|
{
|
||||||
|
return pin >= MCP23017_VPIN_BASE && pin <= MCP23017_VPIN_BASE + 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCP23017LockingArduinoHal::pinMode(uint32_t pin, uint32_t mode)
|
||||||
|
{
|
||||||
|
if (isMcpPin(pin)) {
|
||||||
|
uint8_t local = (uint8_t)(pin - MCP23017_VPIN_BASE);
|
||||||
|
mcp.pinMode(local, mode == GpioModeOutput ? OUTPUT : INPUT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArduinoHal::pinMode(pin, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCP23017LockingArduinoHal::digitalWrite(uint32_t pin, uint32_t value)
|
||||||
|
{
|
||||||
|
if (isMcpPin(pin)) {
|
||||||
|
uint8_t local = (uint8_t)(pin - MCP23017_VPIN_BASE);
|
||||||
|
mcp.digitalWrite(local, value == GpioLevelHigh ? HIGH : LOW);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ArduinoHal::digitalWrite(pin, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t MCP23017LockingArduinoHal::digitalRead(uint32_t pin)
|
||||||
|
{
|
||||||
|
if (isMcpPin(pin)) {
|
||||||
|
uint8_t local = (uint8_t)(pin - MCP23017_VPIN_BASE);
|
||||||
|
return (uint32_t)mcp.digitalRead(local);
|
||||||
|
}
|
||||||
|
return ArduinoHal::digitalRead(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCP23017LockingArduinoHal::attachInterrupt(uint32_t interruptNum, void (*cb)(void), uint32_t mode)
|
||||||
|
{
|
||||||
|
#if MCP23017_INT_ESP32_PIN >= 0
|
||||||
|
uint8_t idx = (uint8_t)(SX126X_DIO1 - MCP23017_VPIN_BASE);
|
||||||
|
if (idx <= 15)
|
||||||
|
mcp.enablePinChangeInterrupt(idx, true);
|
||||||
|
// MCP23017 /INT is open-drain active-low: trigger on the falling edge of the INT line
|
||||||
|
// (RadioLib passes the DIO rising-edge mode, which applies to the DIO pin, not /INT).
|
||||||
|
ArduinoHal::attachInterrupt(interruptNum, cb, GpioInterruptFalling);
|
||||||
|
#else
|
||||||
|
ArduinoHal::attachInterrupt(interruptNum, cb, mode);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCP23017LockingArduinoHal::detachInterrupt(uint32_t interruptNum)
|
||||||
|
{
|
||||||
|
ArduinoHal::detachInterrupt(interruptNum);
|
||||||
|
#if MCP23017_INT_ESP32_PIN >= 0
|
||||||
|
uint8_t idx = (uint8_t)(SX126X_DIO1 - MCP23017_VPIN_BASE);
|
||||||
|
if (idx <= 15)
|
||||||
|
mcp.enablePinChangeInterrupt(idx, false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t MCP23017LockingArduinoHal::pinToInterrupt(uint32_t pin)
|
||||||
|
{
|
||||||
|
if (isMcpPin(pin)) {
|
||||||
|
#if MCP23017_INT_ESP32_PIN >= 0
|
||||||
|
return ::digitalPinToInterrupt((unsigned int)MCP23017_INT_ESP32_PIN);
|
||||||
|
#else
|
||||||
|
return RADIOLIB_NC;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return ArduinoHal::pinToInterrupt(pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if MCP23017_INT_ESP32_PIN >= 0
|
||||||
|
void MCP23017LockingArduinoHal::spiBeginTransaction()
|
||||||
|
{
|
||||||
|
spiLock->lock();
|
||||||
|
// Clear any latched expander interrupt before talking to the radio, so a stale /INT
|
||||||
|
// level doesn't mask the next DIO1 edge.
|
||||||
|
mcp.clearInterruptLatches();
|
||||||
|
ArduinoHal::spiBeginTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCP23017LockingArduinoHal::spiEndTransaction()
|
||||||
|
{
|
||||||
|
ArduinoHal::spiEndTransaction();
|
||||||
|
spiLock->unlock();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // ARCH_ESP32 && USE_MCP23017
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#if defined(ARCH_ESP32) && defined(USE_MCP23017)
|
||||||
|
|
||||||
|
#include "mesh/RadioLibInterface.h"
|
||||||
|
#include "platform/esp32/ExtensionIOMCP23017.h"
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
|
#ifndef MCP23017_VPIN_BASE
|
||||||
|
#define MCP23017_VPIN_BASE 100
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MCP23017_INT_ESP32_PIN
|
||||||
|
#define MCP23017_INT_ESP32_PIN (-1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Routes RadioLib virtual GPIO MCP23017_VPIN_BASE..+15 to an MCP23017 I2C expander (GPA0-GPB7);
|
||||||
|
* all other pins fall through to the regular Arduino GPIO HAL.
|
||||||
|
*
|
||||||
|
* DIO1 interrupts: if the expander /INT line is wired to an ESP32 GPIO, set MCP23017_INT_ESP32_PIN
|
||||||
|
* and a real edge interrupt is used. If not (MCP23017_INT_ESP32_PIN < 0), define
|
||||||
|
* LORA_DIO1_SOFTWARE_POLL so the radio thread polls the radio's IRQ status register instead.
|
||||||
|
*/
|
||||||
|
class MCP23017LockingArduinoHal : public LockingArduinoHal
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MCP23017LockingArduinoHal(SPIClass &spi, SPISettings spiSettings, ExtensionIOMCP23017 &expander);
|
||||||
|
|
||||||
|
void pinMode(uint32_t pin, uint32_t mode) override;
|
||||||
|
void digitalWrite(uint32_t pin, uint32_t value) override;
|
||||||
|
uint32_t digitalRead(uint32_t pin) override;
|
||||||
|
void attachInterrupt(uint32_t interruptNum, void (*cb)(void), uint32_t mode) override;
|
||||||
|
void detachInterrupt(uint32_t interruptNum) override;
|
||||||
|
uint32_t pinToInterrupt(uint32_t pin) override;
|
||||||
|
#if MCP23017_INT_ESP32_PIN >= 0
|
||||||
|
void spiBeginTransaction() override;
|
||||||
|
void spiEndTransaction() override;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
private:
|
||||||
|
ExtensionIOMCP23017 &mcp;
|
||||||
|
static bool isMcpPin(uint32_t pin);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // ARCH_ESP32 && USE_MCP23017
|
||||||
@@ -208,6 +208,8 @@
|
|||||||
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2
|
#define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2
|
||||||
#elif defined(M5STACK_CARDPUTER_ADV)
|
#elif defined(M5STACK_CARDPUTER_ADV)
|
||||||
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV
|
#define HW_VENDOR meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV
|
||||||
|
#elif defined(MESHNOLOGY_W10)
|
||||||
|
#define HW_VENDOR meshtastic_HardwareModel_MESHNOLOGY_W10
|
||||||
#else
|
#else
|
||||||
#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
|
#define HW_VENDOR meshtastic_HardwareModel_PRIVATE_HW
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
#ifdef MESHNOLOGY_W10
|
||||||
|
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
#ifdef HAS_I2S
|
||||||
|
// NOTE: do not include main.h / AudioThread.h here. AudioBoard.h does `using namespace audio_driver`,
|
||||||
|
// which pulls audio_driver::GpioPin into global scope and collides with Meshtastic's class GpioPin if
|
||||||
|
// GpioLogic.h is also visible in this TU. Keeping this file codec-only (like the other ES8311 boards)
|
||||||
|
// avoids that.
|
||||||
|
#include "AudioBoard.h"
|
||||||
|
#include "platform/esp32/ExtensionIOMCP23017.h" // mcpIoExpander (NS4150 amp enable on EXIO_PA_CTRL)
|
||||||
|
|
||||||
|
DriverPins PinsAudioBoardES8311;
|
||||||
|
AudioBoard audioCodecBoard(AudioDriverES8311, PinsAudioBoardES8311);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Meshnology W10 late init: bring up the ES8311 codec so the NS4150 -> speaker path can play
|
||||||
|
// notification tones over I2S. Called after power->setup() and the radio init, so the I2C bus and
|
||||||
|
// the MCP23017 are already up. The amp itself is toggled by AudioThread around playback.
|
||||||
|
void lateInitVariant()
|
||||||
|
{
|
||||||
|
#ifdef HAS_I2S
|
||||||
|
// Keep the NS4150 amp muted until AudioThread turns it on for playback (avoids idle hiss).
|
||||||
|
mcpIoExpander.pinMode(EXIO_PA_CTRL, OUTPUT);
|
||||||
|
mcpIoExpander.digitalWrite(EXIO_PA_CTRL, LOW);
|
||||||
|
|
||||||
|
// I2C: function, Wire (shared bus, ES8311 at 0x18); I2S: function, mclk, bck, ws, dout, din
|
||||||
|
PinsAudioBoardES8311.addI2C(PinFunction::CODEC, Wire);
|
||||||
|
PinsAudioBoardES8311.addI2S(PinFunction::CODEC, DAC_I2S_MCLK, DAC_I2S_BCK, DAC_I2S_WS, DAC_I2S_DOUT, DAC_I2S_DIN);
|
||||||
|
|
||||||
|
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;
|
||||||
|
audioCodecBoard.begin(cfg);
|
||||||
|
|
||||||
|
// ES8311 register setup (matches the vendor demo / other Meshtastic ES8311 boards)
|
||||||
|
auto es8311_write_reg = [](uint8_t reg, uint8_t val) {
|
||||||
|
Wire.beginTransmission(0x18); // ES8311 I2C address
|
||||||
|
Wire.write(reg);
|
||||||
|
Wire.write(val);
|
||||||
|
uint8_t err = Wire.endTransmission();
|
||||||
|
if (err != 0)
|
||||||
|
LOG_WARN("ES8311 reg 0x%02x write failed (err=%d)", reg, err);
|
||||||
|
};
|
||||||
|
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 (0 dB)
|
||||||
|
es8311_write_reg(0x37, 0x08); // EQ bypass
|
||||||
|
LOG_INFO("Meshnology W10: ES8311 audio codec initialized");
|
||||||
|
#endif // HAS_I2S
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // MESHNOLOGY_W10
|
||||||
+6
-4
@@ -404,9 +404,9 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r
|
|||||||
{
|
{
|
||||||
// LOG_DEBUG("Enter light sleep");
|
// LOG_DEBUG("Enter light sleep");
|
||||||
|
|
||||||
// LORA_DIO1 is an extended IO pin. Setting it as a wake-up pin will cause problems, such as the indicator device not entering
|
// LORA_DIO1 is an extended IO pin (on an I/O expander). Setting it as a wake-up pin will cause problems,
|
||||||
// LightSleep.
|
// such as the device not entering light sleep. Boards opt in with LORA_DIO1_EXTENDED_IO in their variant.
|
||||||
#if defined(SENSECAP_INDICATOR)
|
#if defined(LORA_DIO1_EXTENDED_IO)
|
||||||
return ESP_SLEEP_WAKEUP_TIMER;
|
return ESP_SLEEP_WAKEUP_TIMER;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -586,7 +586,9 @@ bool shouldLoraWake(uint32_t msecToWake)
|
|||||||
|
|
||||||
void enableLoraInterrupt()
|
void enableLoraInterrupt()
|
||||||
{
|
{
|
||||||
#if SOC_PM_SUPPORT_EXT_WAKEUP && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC)
|
#if defined(LORA_DIO1_EXTENDED_IO)
|
||||||
|
// DIO1 is a virtual pin on an I/O expander - it cannot be a GPIO wakeup source
|
||||||
|
#elif SOC_PM_SUPPORT_EXT_WAKEUP && defined(LORA_DIO1) && (LORA_DIO1 != RADIOLIB_NC)
|
||||||
esp_err_t res;
|
esp_err_t res;
|
||||||
res = gpio_pulldown_en((gpio_num_t)LORA_DIO1);
|
res = gpio_pulldown_en((gpio_num_t)LORA_DIO1);
|
||||||
if (res != ESP_OK) {
|
if (res != ESP_OK) {
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
// Meshnology W10 - shadows the generic esp32s3 variant pins.
|
||||||
|
// Deliberately omits PIN_RGB_LED / LED_BUILTIN / RGB_BUILTIN: the generic definitions make the
|
||||||
|
// core's digitalWrite() reference the RMT-backed RGB LED HAL, which fails to link against this
|
||||||
|
// build's trimmed FreeRTOS config (no xEventGroupSetBitsFromISR).
|
||||||
|
#ifndef Pins_Arduino_h
|
||||||
|
#define Pins_Arduino_h
|
||||||
|
|
||||||
|
#include "soc/soc_caps.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 = 8;
|
||||||
|
static const uint8_t SCL = 7;
|
||||||
|
|
||||||
|
// Shared LoRa/LCD SPI bus (E22_NSS is the default SS)
|
||||||
|
static const uint8_t SS = 14;
|
||||||
|
static const uint8_t MOSI = 13;
|
||||||
|
static const uint8_t MISO = 11;
|
||||||
|
static const uint8_t SCK = 12;
|
||||||
|
|
||||||
|
static const uint8_t A0 = 1;
|
||||||
|
static const uint8_t A1 = 2;
|
||||||
|
static const uint8_t A2 = 3;
|
||||||
|
static const uint8_t A3 = 4;
|
||||||
|
static const uint8_t A4 = 5;
|
||||||
|
static const uint8_t A5 = 6;
|
||||||
|
static const uint8_t A6 = 7;
|
||||||
|
static const uint8_t A7 = 8;
|
||||||
|
static const uint8_t A8 = 9;
|
||||||
|
static const uint8_t A9 = 10;
|
||||||
|
static const uint8_t A10 = 11;
|
||||||
|
static const uint8_t A11 = 12;
|
||||||
|
static const uint8_t A12 = 13;
|
||||||
|
static const uint8_t A13 = 14;
|
||||||
|
static const uint8_t A14 = 15;
|
||||||
|
static const uint8_t A15 = 16;
|
||||||
|
static const uint8_t A16 = 17;
|
||||||
|
static const uint8_t A17 = 18;
|
||||||
|
static const uint8_t A18 = 19;
|
||||||
|
static const uint8_t A19 = 20;
|
||||||
|
|
||||||
|
static const uint8_t T1 = 1;
|
||||||
|
static const uint8_t T2 = 2;
|
||||||
|
static const uint8_t T3 = 3;
|
||||||
|
static const uint8_t T4 = 4;
|
||||||
|
static const uint8_t T5 = 5;
|
||||||
|
static const uint8_t T6 = 6;
|
||||||
|
static const uint8_t T7 = 7;
|
||||||
|
static const uint8_t T8 = 8;
|
||||||
|
static const uint8_t T9 = 9;
|
||||||
|
static const uint8_t T10 = 10;
|
||||||
|
static const uint8_t T11 = 11;
|
||||||
|
static const uint8_t T12 = 12;
|
||||||
|
static const uint8_t T13 = 13;
|
||||||
|
static const uint8_t T14 = 14;
|
||||||
|
|
||||||
|
#endif /* Pins_Arduino_h */
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
[env:meshnology_w10]
|
||||||
|
custom_meshtastic_hw_model = 140
|
||||||
|
custom_meshtastic_hw_model_slug = MESHNOLOGY_W10
|
||||||
|
custom_meshtastic_architecture = esp32-s3
|
||||||
|
custom_meshtastic_actively_supported = true
|
||||||
|
custom_meshtastic_support_level = 1
|
||||||
|
custom_meshtastic_display_name = Meshnology W10
|
||||||
|
custom_meshtastic_requires_dfu = true
|
||||||
|
custom_meshtastic_partition_scheme = 16MB
|
||||||
|
|
||||||
|
; ESP32-S3R8: 8 MB OPI PSRAM, W25Q128JVSIQ = 16 MB QIO flash (pg1)
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
board_level = pr
|
||||||
|
board_build.partitions = default_16MB.csv
|
||||||
|
board_upload.flash_size = 16MB
|
||||||
|
board_build.flash_mode = qio
|
||||||
|
board_build.psram_type = opi
|
||||||
|
board_build.arduino.memory_type = qio_opi
|
||||||
|
|
||||||
|
extends = esp32s3_base
|
||||||
|
build_flags =
|
||||||
|
${esp32s3_base.build_flags}
|
||||||
|
-D MESHNOLOGY_W10
|
||||||
|
-D ARDUINO_USB_CDC_ON_BOOT=1
|
||||||
|
-I variants/esp32s3/meshnology-w10
|
||||||
|
|
||||||
|
lib_deps =
|
||||||
|
${esp32s3_base.lib_deps}
|
||||||
|
; ST7789/ST7796 TFT (USE_TFTDISPLAY)
|
||||||
|
; renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX
|
||||||
|
lovyan03/LovyanGFX@1.2.21
|
||||||
|
; PCF85063 RTC driver (PCF85063_RTC)
|
||||||
|
; renovate: datasource=custom.pio depName=SensorLib packageName=lewisxhe/library/SensorLib
|
||||||
|
lewisxhe/SensorLib@0.3.4
|
||||||
|
; ES8311 audio codec + I2S notification tones (HAS_I2S)
|
||||||
|
; renovate: datasource=github-tags depName=pschatzmann_arduino-audio-driver packageName=pschatzmann/arduino-audio-driver
|
||||||
|
https://github.com/pschatzmann/arduino-audio-driver/archive/v0.3.0.zip
|
||||||
|
# renovate: datasource=git-refs depName=ESP8266Audio packageName=https://github.com/meshtastic/ESP8266Audio gitBranch=meshtastic-2.0.0-dacfix
|
||||||
|
https://github.com/meshtastic/ESP8266Audio/archive/343024632ee78d6216907b2353fc943a62422d80.zip
|
||||||
|
# renovate: datasource=custom.pio depName=ESP8266SAM packageName=earlephilhower/library/ESP8266SAM
|
||||||
|
earlephilhower/ESP8266SAM@1.1.0
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Meshnology W10 "LoRa AIOT Dev Kit" - ESP32-S3R8 + EBYTE E22-900MM22S (SX1262) + AXP2101 PMIC +
|
||||||
|
// Quectel L76KB-A58 GPS + SPI TFT. The SX1262 RESET/DIO1/BUSY and the LCD reset are wired to an
|
||||||
|
// MCP23017 I2C expander (0x20) whose /INT is not routed to the MCU, so DIO1 uses a software poll.
|
||||||
|
|
||||||
|
// ─── I2C bus ──────────────────────────────────────────────────────────────────
|
||||||
|
// Shared by: AXP2101 PMIC, MCP23017 I/O expander, PCF85063ATL RTC, QMI8658 IMU,
|
||||||
|
// ES8311 codec, SHT41 temp/humidity sensor (pg1: ESP_SDA=GPIO8, ESP_SCL=GPIO7)
|
||||||
|
#define I2C_SDA 8
|
||||||
|
#define I2C_SCL 7
|
||||||
|
|
||||||
|
// ─── Power management ─────────────────────────────────────────────────────────
|
||||||
|
// AXP2101 PMIC on I2C (pg3). AXP_IRQ → EXIO5 (expander, whose /INT is not routed to the
|
||||||
|
// ESP32), so no PMU_IRQ is possible; battery state is polled via the AXP2101 fuel gauge.
|
||||||
|
// There is no direct battery ADC - the PMIC is the only voltage source.
|
||||||
|
#define HAS_AXP2101
|
||||||
|
|
||||||
|
// ─── RTC ──────────────────────────────────────────────────────────────────────
|
||||||
|
// PCF85063ATL on I2C (pg3); RTC_INT → EXIO4 (unused)
|
||||||
|
#define PCF85063_RTC 0x51
|
||||||
|
|
||||||
|
// ─── I/O expander ─────────────────────────────────────────────────────────────
|
||||||
|
// U7 is drawn as "TCA9555PWR(UMW)" on schematic V1.1, but production boards use the MCP23017
|
||||||
|
// register map (V1.2 placement labels the part MCP23017T-E, and all vendor firmware/demo code
|
||||||
|
// drives IODIR 0x00/01, GPIO 0x12/13, OLAT 0x14/15). A0/A1/A2 = 0 → I2C address 0x20.
|
||||||
|
// The expander /INT output is NOT routed to the ESP32.
|
||||||
|
#define USE_MCP23017
|
||||||
|
#define MCP23017_ADDR 0x20
|
||||||
|
#define MCP23017_VPIN_BASE 100 // RadioLib virtual pins 100..115 = expander GPA0..GPB7
|
||||||
|
#define MCP23017_INT_ESP32_PIN (-1) // /INT not wired to any ESP32 GPIO
|
||||||
|
#if MCP23017_INT_ESP32_PIN < 0
|
||||||
|
// No hardware DIO1 interrupt possible: SX126x IRQ status register is polled from the radio thread
|
||||||
|
#define LORA_DIO1_SOFTWARE_POLL 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// LORA_DIO1 is an expander pin, not an ESP32 GPIO, so it can't be used as a sleep/GPIO wakeup source
|
||||||
|
// (shared capability; see its use in sleep.cpp).
|
||||||
|
#define LORA_DIO1_EXTENDED_IO
|
||||||
|
|
||||||
|
// Expander pin map (pg2 "I/O Extensions"; EXIO0..7 = GPA0..7 / P00..P07, EXIO8..15 = GPB0..7 / P10..P17)
|
||||||
|
// Not wired up here: EXIO0 CAM_PWDN, EXIO2 TP_INT, EXIO5 AXP_IRQ, EXIO6 SYS_OUT,
|
||||||
|
// EXIO11 GPS RESET_N (driver FET Q3 unpopulated), EXIO13 TP_RST
|
||||||
|
#define EXIO_LCD_RST 1 // GPA1: LCD reset
|
||||||
|
#define EXIO_LORA_NRST 3 // GPA3: E22 NRST
|
||||||
|
#define EXIO_RTC_INT 4 // GPA4: PCF85063 INT (unused)
|
||||||
|
#define EXIO_PA_CTRL 7 // GPA7: NS4150 speaker amp enable (driven by AudioThread during playback)
|
||||||
|
#define EXIO_IMU_INT1 8 // GPB0: QMI8658 INT1 (input only, no MCU interrupt)
|
||||||
|
#define EXIO_LORA_DIO1 9 // GPB1: E22 DIO1
|
||||||
|
#define EXIO_LORA_BUSY 10 // GPB2: E22 BUSY
|
||||||
|
#define EXIO_GPS_WAKE 12 // GPB4: L76KB WAKE_UP (driven high at boot)
|
||||||
|
|
||||||
|
// ─── LoRa radio ───────────────────────────────────────────────────────────────
|
||||||
|
// EBYTE E22-900MM22S (SX1262) - pg4 U10. SPI shared with the LCD, separate chip selects.
|
||||||
|
// Bus pins via 0R links: E22_SCK=GPIO12, E22_MOSI=GPIO13, E22_MISO=GPIO11, E22_NSS=GPIO14 (pg4)
|
||||||
|
#define USE_SX1262
|
||||||
|
#define LORA_SCK 12
|
||||||
|
#define LORA_MOSI 13
|
||||||
|
#define LORA_MISO 11
|
||||||
|
#define LORA_CS 14
|
||||||
|
|
||||||
|
// Control lines route through the MCP23017 (pg4: E22_NRST=EXIO3, E22_DIO1=EXIO9, E22_BUSY=EXIO10),
|
||||||
|
// handled as RadioLib virtual pins by MCP23017LockingArduinoHal
|
||||||
|
#define LORA_DIO1 (MCP23017_VPIN_BASE + EXIO_LORA_DIO1) // 109
|
||||||
|
#define LORA_BUSY (MCP23017_VPIN_BASE + EXIO_LORA_BUSY) // 110
|
||||||
|
#define LORA_RESET (MCP23017_VPIN_BASE + EXIO_LORA_NRST) // 103
|
||||||
|
|
||||||
|
#define SX126X_CS LORA_CS
|
||||||
|
#define SX126X_DIO1 LORA_DIO1
|
||||||
|
#define SX126X_BUSY LORA_BUSY
|
||||||
|
#define SX126X_RESET LORA_RESET
|
||||||
|
|
||||||
|
// RF switch is fully hardware-automatic on this board: DIO2 drives TXEN directly and RXEN through
|
||||||
|
// inverting FET Q4 (pg4). Do not assign TXEN/RXEN GPIOs.
|
||||||
|
#define SX126X_DIO2_AS_RF_SWITCH
|
||||||
|
#define SX126X_TXEN RADIOLIB_NC
|
||||||
|
#define SX126X_RXEN RADIOLIB_NC
|
||||||
|
|
||||||
|
// E22-900MM22S uses a plain 32 MHz crystal (no TCXO) - SX126X_DIO3_TCXO_VOLTAGE deliberately not defined
|
||||||
|
#define SX126X_MAX_POWER 22
|
||||||
|
|
||||||
|
// ─── Display ──────────────────────────────────────────────────────────────────
|
||||||
|
// SPI TFT on the "OLED"-silkscreened header / LCD FPC, sharing the LoRa SPI bus (pg1).
|
||||||
|
// Vendor ships two panels on identical pins; the default kit has the ST7789 1.54" IPS 240x240.
|
||||||
|
// Build with -D MESHNOLOGY_W10_LCD_ST7796_35 for the 3.5" ST7796 320x480 panel instead.
|
||||||
|
#define TFT_CS 10 // pg1: OLED_CS=GPIO10
|
||||||
|
#define TFT_DC 16 // pg1: OLED_DC=GPIO16
|
||||||
|
#define TFT_BL 6 // pg1: OLED_BL=GPIO6 (backlight PWM)
|
||||||
|
#define TFT_RST -1 // panel reset is EXIO1 on the expander, toggled in mcp23017EarlyInit()
|
||||||
|
#define USE_TFTDISPLAY 1
|
||||||
|
|
||||||
|
#define SPI_FREQUENCY 75000000
|
||||||
|
#define SPI_READ_FREQUENCY 16000000
|
||||||
|
|
||||||
|
#ifdef MESHNOLOGY_W10_LCD_ST7796_35
|
||||||
|
// ST7796 3.5" 320x480 (capacitive touch on I2C - not enabled yet)
|
||||||
|
#define ST7796_SPI_HOST SPI2_HOST
|
||||||
|
#define ST7796_CS TFT_CS
|
||||||
|
#define ST7796_RS TFT_DC
|
||||||
|
#define ST7796_SDA LORA_MOSI
|
||||||
|
#define ST7796_SCK LORA_SCK
|
||||||
|
#define ST7796_MISO LORA_MISO
|
||||||
|
#define ST7796_RESET TFT_RST
|
||||||
|
#define ST7796_BL TFT_BL
|
||||||
|
#define ST7796_BUSY -1
|
||||||
|
#define TFT_WIDTH 320
|
||||||
|
#define TFT_HEIGHT 480
|
||||||
|
#define TFT_OFFSET_ROTATION 3
|
||||||
|
#else
|
||||||
|
// ST7789 1.54" IPS 240x240 (default kit panel)
|
||||||
|
#define ST7789_SPI_HOST SPI2_HOST
|
||||||
|
#define ST7789_CS TFT_CS
|
||||||
|
#define ST7789_RS TFT_DC
|
||||||
|
#define ST7789_SDA LORA_MOSI
|
||||||
|
#define ST7789_SCK LORA_SCK
|
||||||
|
#define ST7789_MISO LORA_MISO
|
||||||
|
#define ST7789_RESET TFT_RST
|
||||||
|
#define ST7789_BL TFT_BL
|
||||||
|
#define ST7789_BUSY -1
|
||||||
|
#define TFT_WIDTH 240
|
||||||
|
#define TFT_HEIGHT 240
|
||||||
|
#define TFT_OFFSET_ROTATION 1
|
||||||
|
#endif
|
||||||
|
#define TFT_OFFSET_X 0
|
||||||
|
#define TFT_OFFSET_Y 0
|
||||||
|
|
||||||
|
// ─── GPS ──────────────────────────────────────────────────────────────────────
|
||||||
|
// Quectel L76KB-A58 on UART0 pins (pg4: GPS_TXD→U0RXD=GPIO44, GPS_RXD→U0TXD=GPIO43; console is USB)
|
||||||
|
// 1PPS only drives LED3 (pg3); RESET_N driver FET is unpopulated; WAKE_UP = EXIO12, driven high at boot
|
||||||
|
#define HAS_GPS 1
|
||||||
|
#define GPS_RX_PIN 44
|
||||||
|
#define GPS_TX_PIN 43
|
||||||
|
#define GPS_BAUDRATE 9600
|
||||||
|
|
||||||
|
// ─── User input ───────────────────────────────────────────────────────────────
|
||||||
|
// pg1: SW2 pulls GPIO0 to GND (BOOT doubles as user button). SW3 is the AXP2101 power button.
|
||||||
|
#define BUTTON_PIN 0
|
||||||
|
#define BUTTON_NEED_PULLUP
|
||||||
|
|
||||||
|
// ─── LED ──────────────────────────────────────────────────────────────────────
|
||||||
|
// TX1812 (WS2812-compatible) RGB LED on GPIO48 via 33R (pg1, U35). The other LEDs are
|
||||||
|
// hardware-driven: AXP2101 CHGLED, VSYS power LED, GPS 1PPS LED, UART0 TX/RX activity LEDs.
|
||||||
|
// TODO: enabling HAS_NEOPIXEL currently fails to link (Adafruit NeoPixel pulls in the Arduino RMT
|
||||||
|
// HAL, which needs xEventGroupSetBitsFromISR - not present in this build's FreeRTOS config)
|
||||||
|
// #define HAS_NEOPIXEL
|
||||||
|
// #define NEOPIXEL_COUNT 1
|
||||||
|
// #define NEOPIXEL_DATA 48
|
||||||
|
// #define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800)
|
||||||
|
|
||||||
|
// ─── Audio ────────────────────────────────────────────────────────────────────
|
||||||
|
// ES8311 codec (I2C 0x18) -> NS4150 amp -> speaker, initialized in the variant's lateInitVariant().
|
||||||
|
// Used for notification tones / ringtones over the I2S "buzzer" path (turn on the
|
||||||
|
// use_i2s_as_buzzer external-notification option). Codec2 voice is SX1280-only, so it does not
|
||||||
|
// apply to this sub-GHz board. The NS4150 amp enable is on the MCP23017 (EXIO_PA_CTRL / GPA7) and
|
||||||
|
// is toggled by AudioThread around playback. pg3 I2S wiring below.
|
||||||
|
#define HAS_I2S
|
||||||
|
#define DAC_I2S_MCLK 1 // pg3: ES8311 MCLK
|
||||||
|
#define DAC_I2S_BCK 2 // pg3: ES8311 BCLK/SCLK
|
||||||
|
#define DAC_I2S_WS 4 // pg3: ES8311 LRCK
|
||||||
|
#define DAC_I2S_DOUT 5 // pg3: playback data (ESP32 -> ES8311)
|
||||||
|
#define DAC_I2S_DIN 3 // pg3: record data (ES8311 -> ESP32)
|
||||||
|
// AudioThread powers the NS4150 amp on/off around playback via this (opt-in) hook.
|
||||||
|
#define AUDIO_AMP_ENABLE(on) mcpIoExpander.digitalWrite(EXIO_PA_CTRL, (on) ? HIGH : LOW)
|
||||||
|
|
||||||
|
// ─── On-board peripherals not wired up yet ────────────────────────────────────
|
||||||
|
// SHT41 temp/humidity (0x44) and QMI8658 IMU (0x6B): auto-detected on the I2C scan
|
||||||
|
// microSD: CS on GPIO9, shares the LCD/LoRa SPI bus - not enabled
|
||||||
|
// Camera interface: GPIO38-42/45-48 (pg1) - not enabled
|
||||||
|
|
||||||
|
// ─── Board identity ───────────────────────────────────────────────────────────
|
||||||
|
#define MESHNOLOGY_W10 1
|
||||||
@@ -68,6 +68,9 @@
|
|||||||
#define LORA_DIO1 (3 | IO_EXPANDER) // SX1262 IRQ
|
#define LORA_DIO1 (3 | IO_EXPANDER) // SX1262 IRQ
|
||||||
#define LORA_DIO2 (2 | IO_EXPANDER) // SX1262 BUSY
|
#define LORA_DIO2 (2 | IO_EXPANDER) // SX1262 BUSY
|
||||||
#define LORA_DIO3
|
#define LORA_DIO3
|
||||||
|
// LORA_DIO1 is an expander pin, not an ESP32 GPIO, so it can't be used as a sleep/GPIO wakeup source
|
||||||
|
// (shared capability; see its use in sleep.cpp).
|
||||||
|
#define LORA_DIO1_EXTENDED_IO
|
||||||
|
|
||||||
#define SX126X_CS LORA_CS
|
#define SX126X_CS LORA_CS
|
||||||
#define SX126X_DIO1 LORA_DIO1
|
#define SX126X_DIO1 LORA_DIO1
|
||||||
|
|||||||
@@ -92,6 +92,8 @@
|
|||||||
#define EXPANDS_DRV_EN (0)
|
#define EXPANDS_DRV_EN (0)
|
||||||
#define EXPANDS_AMP_EN (1)
|
#define EXPANDS_AMP_EN (1)
|
||||||
#define EXPANDS_KB_RST (2)
|
#define EXPANDS_KB_RST (2)
|
||||||
|
// AudioThread powers the amp on/off around playback via this (opt-in) hook.
|
||||||
|
#define AUDIO_AMP_ENABLE(on) io.digitalWrite(EXPANDS_AMP_EN, (on) ? HIGH : LOW)
|
||||||
#define EXPANDS_LORA_EN (3)
|
#define EXPANDS_LORA_EN (3)
|
||||||
#define EXPANDS_GPS_EN (4)
|
#define EXPANDS_GPS_EN (4)
|
||||||
#define EXPANDS_NFC_EN (5)
|
#define EXPANDS_NFC_EN (5)
|
||||||
|
|||||||
Reference in New Issue
Block a user