diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 17cf83652..5d9c75775 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -62,6 +62,8 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- make/find a multithread safe debug logging class (include remote logging and timestamps and levels). make each log event atomic. +- turn on freertos stack size checking - Currently we use Nordic's vendor ID, which is apparently okay: https://devzone.nordicsemi.com/f/nordic-q-a/44014/using-nordic-vid-and-pid-for-nrf52840 and I just picked a PID of 0x4403 - Use NRF logger module (includes flash logging etc...) instead of DEBUG_MSG - Use "LED softblink" library on NRF52 to do nice pretty "breathing" LEDs. Don't whack LED from main thread anymore. diff --git a/platformio.ini b/platformio.ini index e543b06dc..936a8e6c3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,7 +31,7 @@ board_build.partitions = partition-table.csv ; note: we add src to our include search path so that lmic_project_config can override ; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc -build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Isrc/mesh -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map +build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${sysenv.COUNTRY} -DAPP_VERSION=${sysenv.APP_VERSION} diff --git a/src/WorkerThread.cpp b/src/WorkerThread.cpp new file mode 100644 index 000000000..0efe79429 --- /dev/null +++ b/src/WorkerThread.cpp @@ -0,0 +1,44 @@ +#include "WorkerThread.h" +#include + +void Thread::start(const char *name, size_t stackSize, uint32_t priority) +{ + auto r = xTaskCreate(callRun, name, stackSize, this, priority, &taskHandle); + assert(r == pdPASS); +} + +void Thread::callRun(void *_this) +{ + ((Thread *)_this)->doRun(); +} + +void WorkerThread::doRun() +{ + while (!wantExit) { + block(); + loop(); + } +} + +/** + * Notify this thread so it can run + */ +void NotifiedWorkerThread::notify(uint32_t v, eNotifyAction action) +{ + xTaskNotify(taskHandle, v, action); +} + +/** + * Notify from an ISR + */ +void NotifiedWorkerThread::notifyFromISR(BaseType_t *highPriWoken, uint32_t v, eNotifyAction action) +{ + xTaskNotifyFromISR(taskHandle, v, action, highPriWoken); +} + +void NotifiedWorkerThread::block() +{ + xTaskNotifyWait(0, // don't clear notification on entry + 0, // do not reset notification value on read + ¬ification, portMAX_DELAY); // Wait forever +} diff --git a/src/WorkerThread.h b/src/WorkerThread.h new file mode 100644 index 000000000..50d87b965 --- /dev/null +++ b/src/WorkerThread.h @@ -0,0 +1,77 @@ +#include + +class Thread +{ + protected: + TaskHandle_t taskHandle = NULL; + + /** + * set this to true to ask thread to cleanly exit asap + */ + volatile bool wantExit = false; + + public: + void start(const char *name, size_t stackSize = 1024, uint32_t priority = tskIDLE_PRIORITY); + + virtual ~Thread() { vTaskDelete(taskHandle); } + + protected: + /** + * The method that will be called when start is called. + */ + virtual void doRun() = 0; + + private: + static void callRun(void *_this); +}; + +/** + * This wraps threading (FreeRTOS for now) with a blocking API intended for efficiently converting onlyschool arduino loop() code. + * + * Use as a mixin base class for the classes you want to convert. + * + * https://www.freertos.org/RTOS_Task_Notification_As_Mailbox.html + */ +class WorkerThread : public Thread +{ + protected: + /** + * A method that should block execution - either waiting ona queue/mutex or a "task notification" + */ + virtual void block() = 0; + + virtual void loop() = 0; + + /** + * The method that will be called when start is called. + */ + virtual void doRun(); +}; + +/** + * A worker thread that waits on a freertos notification + */ +class NotifiedWorkerThread : public WorkerThread +{ + public: + /** + * Notify this thread so it can run + */ + void notify(uint32_t v = 0, eNotifyAction action = eNoAction); + + /** + * Notify from an ISR + */ + void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction); + + protected: + /** + * The notification that was most recently used to wake the thread. Read from loop() + */ + uint32_t notification = 0; + + /** + * A method that should block execution - either waiting ona queue/mutex or a "task notification" + */ + virtual void block(); +}; \ No newline at end of file diff --git a/src/rf95/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp similarity index 100% rename from src/rf95/FloodingRouter.cpp rename to src/mesh/FloodingRouter.cpp diff --git a/src/rf95/FloodingRouter.h b/src/mesh/FloodingRouter.h similarity index 100% rename from src/rf95/FloodingRouter.h rename to src/mesh/FloodingRouter.h diff --git a/src/rf95/MemoryPool.h b/src/mesh/MemoryPool.h similarity index 100% rename from src/rf95/MemoryPool.h rename to src/mesh/MemoryPool.h diff --git a/src/rf95/PointerQueue.h b/src/mesh/PointerQueue.h similarity index 100% rename from src/rf95/PointerQueue.h rename to src/mesh/PointerQueue.h diff --git a/src/rf95/RF95Interface.cpp b/src/mesh/RF95Interface.cpp similarity index 97% rename from src/rf95/RF95Interface.cpp rename to src/mesh/RF95Interface.cpp index f8f64c9a5..c335205b7 100644 --- a/src/rf95/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -14,6 +14,8 @@ RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOL /// \return true if initialisation succeeded. bool RF95Interface::init() { + RadioLibInterface::init(); + applyModemConfig(); if (power > 20) // This chip has lower power limits than some power = 20; @@ -25,7 +27,7 @@ bool RF95Interface::init() if (res == ERR_NONE) res = lora->setCRC(SX126X_LORA_CRC_ON); - if (res == ERR_NONE) + if (res == ERR_NONE) startReceive(); // start receiving return res == ERR_NONE; diff --git a/src/rf95/RF95Interface.h b/src/mesh/RF95Interface.h similarity index 100% rename from src/rf95/RF95Interface.h rename to src/mesh/RF95Interface.h diff --git a/src/rf95/RadioInterface.cpp b/src/mesh/RadioInterface.cpp similarity index 89% rename from src/rf95/RadioInterface.cpp rename to src/mesh/RadioInterface.cpp index 90f865b58..6e98a3641 100644 --- a/src/rf95/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -7,11 +7,21 @@ #include #include +// 1kb was too small +#define RADIO_STACK_SIZE 4096 + RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE) { assert(sizeof(PacketHeader) == 4); // make sure the compiler did what we expected } +bool RadioInterface::init() +{ + start("radio", RADIO_STACK_SIZE); // Start our worker thread + + return true; +} + ErrorCode SimRadio::send(MeshPacket *p) { DEBUG_MSG("SimRadio.send\n"); diff --git a/src/rf95/RadioInterface.h b/src/mesh/RadioInterface.h similarity index 97% rename from src/rf95/RadioInterface.h rename to src/mesh/RadioInterface.h index 549c280a4..cdfae79d9 100644 --- a/src/rf95/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -3,6 +3,7 @@ #include "MemoryPool.h" #include "MeshTypes.h" #include "PointerQueue.h" +#include "WorkerThread.h" #include "mesh.pb.h" #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission @@ -29,7 +30,7 @@ typedef enum { * * This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations) */ -class RadioInterface +class RadioInterface : protected NotifiedWorkerThread { friend class MeshRadio; // for debugging we let that class touch pool PointerQueue *rxDest = NULL; @@ -64,8 +65,6 @@ class RadioInterface */ void setReceiver(PointerQueue *_rxDest) { rxDest = _rxDest; } - virtual void loop() {} // Idle processing - /** * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) * @@ -88,7 +87,7 @@ class RadioInterface /// Initialise the Driver transport hardware and software. /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. - virtual bool init() = 0; + virtual bool init(); /// Apply any radio provisioning changes /// Make sure the Driver is properly configured before calling init(). @@ -103,6 +102,8 @@ class RadioInterface * Used as the first step of */ size_t beginSending(MeshPacket *p); + + virtual void loop() {} // Idle processing }; class SimRadio : public RadioInterface diff --git a/src/rf95/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp similarity index 86% rename from src/rf95/RadioLibInterface.cpp rename to src/mesh/RadioLibInterface.cpp index 3653e29f9..f6fb2de3d 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -17,16 +17,39 @@ RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq instance = this; } +#ifndef NO_ESP32 +// ESP32 doesn't use that flag +#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR() +#else +#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) +#endif + void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0() { - instance->pending = ISR_RX; instance->disableInterrupt(); + + instance->pending = ISR_RX; + BaseType_t xHigherPriorityTaskWoken; + instance->notifyFromISR(&xHigherPriorityTaskWoken); + + /* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE. + The macro used to do this is dependent on the port and may be called + portEND_SWITCHING_ISR. */ + YIELD_FROM_ISR(xHigherPriorityTaskWoken); } void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0() { - instance->pending = ISR_TX; instance->disableInterrupt(); + + instance->pending = ISR_TX; + BaseType_t xHigherPriorityTaskWoken; + instance->notifyFromISR(&xHigherPriorityTaskWoken); + + /* Force a context switch if xHigherPriorityTaskWoken is now set to pdTRUE. + The macro used to do this is dependent on the port and may be called + portEND_SWITCHING_ISR. */ + YIELD_FROM_ISR(xHigherPriorityTaskWoken); } /** Our ISR code currently needs this to find our active instance @@ -117,19 +140,17 @@ bool RadioLibInterface::canSleep() void RadioLibInterface::loop() { - PendingISR wasPending; - while ((wasPending = pending) != 0) { // atomic read - pending = ISR_NONE; // If the flag was set, it is _guaranteed_ the ISR won't be running, because it masked itself + PendingISR wasPending = pending; + pending = ISR_NONE; - if (wasPending == ISR_TX) - handleTransmitInterrupt(); - else if (wasPending == ISR_RX) - handleReceiveInterrupt(); - else - assert(0); + if (wasPending == ISR_TX) + handleTransmitInterrupt(); + else if (wasPending == ISR_RX) + handleReceiveInterrupt(); + else + assert(0); // We expected to receive a valid notification from the ISR - startNextWork(); - } + startNextWork(); } void RadioLibInterface::startNextWork() diff --git a/src/rf95/RadioLibInterface.h b/src/mesh/RadioLibInterface.h similarity index 96% rename from src/rf95/RadioLibInterface.h rename to src/mesh/RadioLibInterface.h index 1e42aedb0..789df68ab 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -13,10 +13,9 @@ class RadioLibInterface : public RadioInterface { + /// Used as our notification from the ISR enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX }; - /** - * What sort of interrupt do we expect our helper thread to now handle */ volatile PendingISR pending = ISR_NONE; /** Our ISR code currently needs this to find our active instance @@ -73,10 +72,6 @@ class RadioLibInterface : public RadioInterface virtual ErrorCode send(MeshPacket *p); - // methods from radiohead - - virtual void loop(); // Idle processing - /** * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) * @@ -124,4 +119,6 @@ class RadioLibInterface : public RadioInterface * Add SNR data to received messages */ virtual void addReceiveMetadata(MeshPacket *mp) = 0; + + virtual void loop(); // Idle processing }; \ No newline at end of file diff --git a/src/rf95/RadioLibRF95.cpp b/src/mesh/RadioLibRF95.cpp similarity index 100% rename from src/rf95/RadioLibRF95.cpp rename to src/mesh/RadioLibRF95.cpp diff --git a/src/rf95/RadioLibRF95.h b/src/mesh/RadioLibRF95.h similarity index 100% rename from src/rf95/RadioLibRF95.h rename to src/mesh/RadioLibRF95.h diff --git a/src/rf95/Router.cpp b/src/mesh/Router.cpp similarity index 98% rename from src/rf95/Router.cpp rename to src/mesh/Router.cpp index dff4b7795..ecc8c8f40 100644 --- a/src/rf95/Router.cpp +++ b/src/mesh/Router.cpp @@ -35,9 +35,6 @@ Router::Router() : fromRadioQueue(MAX_RX_FROMRADIO) {} */ void Router::loop() { - if (iface) - iface->loop(); - MeshPacket *mp; while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { handleReceived(mp); diff --git a/src/rf95/Router.h b/src/mesh/Router.h similarity index 100% rename from src/rf95/Router.h rename to src/mesh/Router.h diff --git a/src/rf95/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp similarity index 98% rename from src/rf95/SX1262Interface.cpp rename to src/mesh/SX1262Interface.cpp index d2842956c..ad305a4f1 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/mesh/SX1262Interface.cpp @@ -12,6 +12,8 @@ SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RA /// \return true if initialisation succeeded. bool SX1262Interface::init() { + RadioLibInterface::init(); + float tcxoVoltage = 0; // None - we use an XTAL bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC? diff --git a/src/rf95/SX1262Interface.h b/src/mesh/SX1262Interface.h similarity index 100% rename from src/rf95/SX1262Interface.h rename to src/mesh/SX1262Interface.h diff --git a/src/rf95/TypedQueue.h b/src/mesh/TypedQueue.h similarity index 100% rename from src/rf95/TypedQueue.h rename to src/mesh/TypedQueue.h diff --git a/src/rf95/LICENSE b/src/rf95/LICENSE deleted file mode 100644 index da124e128..000000000 --- a/src/rf95/LICENSE +++ /dev/null @@ -1,17 +0,0 @@ -This software is Copyright (C) 2008 Mike McCauley. Use is subject to license -conditions. The main licensing options available are GPL V2 or Commercial: - -Open Source Licensing GPL V2 - -This is the appropriate option if you want to share the source code of your -application with everyone you distribute it to, and you also want to give them -the right to share who uses it. If you wish to use this software under Open -Source Licensing, you must contribute all your source code to the open source -community in accordance with the GPL Version 2 when your application is -distributed. See http://www.gnu.org/copyleft/gpl.html - -Commercial Licensing - -This is the appropriate option if you are creating proprietary applications -and you are not prepared to distribute and share the source code of your -application. Contact info@open.com.au for details. diff --git a/src/rf95/README.md b/src/rf95/README.md deleted file mode 100644 index f6346e4ac..000000000 --- a/src/rf95/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# RF95 - -This is a heavily modified version of the Mike McCauley's RadioHead RF95 driver. We are using it under the GPL V3 License. See the -file LICENSE for Mike's license terms (which listed GPL as acceptible). diff --git a/src/rf95/kh-todo.txt b/src/rf95/kh-todo.txt deleted file mode 100644 index 96664a2c0..000000000 --- a/src/rf95/kh-todo.txt +++ /dev/null @@ -1,22 +0,0 @@ -In old lib code: -* pass header all the way down to device -* have device send header using the same code it uses to send payload -* have device treat received header as identical to payload -* use new MessageHeader in existing app (make sure it is packed properly) - -In the sudomesh code: -* move this rf95 lib into the layer2 project -* make RadioInterface the new layer one API (move over set radio options) -* change meshtastic app to use new layer one API - -Now meshtastic is sharing layer one with disaster radio. -* change mesthastic app to use new layer two API (make sure broadcast still works for max TTL of 1) - -Now meshtastic is sharing layer two with disaster radio. - -* make simulation code work with new API -* make disaster radio app work with new API - -later: -* implement naive flooding in the layer2 lib, use TTL limit max depth of broadcast -* allow packets to be filtered at the device level RX time based on dest addr (to avoid waking main CPU unnecessarily) diff --git a/src/screen.cpp b/src/screen.cpp index 00be30f95..54c57457b 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -315,7 +315,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ const char *username = node->has_user ? node->user.long_name : "Unknown Name"; static char signalStr[20]; - snprintf(signalStr, sizeof(signalStr), "Signal: %ld", node->snr); + snprintf(signalStr, sizeof(signalStr), "Signal: %.0f", node->snr); uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20];