From 93a06906cb8cb15d99434bb09f1e73fe8416c29e Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 10 Apr 2020 12:15:00 -0700 Subject: [PATCH 001/197] begin splitting up source files, so we can have a tree of sources... unique to each architecture. For now, we have "esp32" and "bare" esp32 is the old esp stuff bare is an target suitable for emulation that doesn't require any particular hardware to run (no bluetooth, no i2c devices, no spi devices) --- platformio.ini | 20 ++++++++++++--- src/bare/main-bare.cpp | 6 +++++ src/configuration.h | 2 ++ src/esp32/main-esp32.cpp | 53 ++++++++++++++++++++++++++++++++++++++++ src/target_specific.h | 6 +++++ 5 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 src/bare/main-bare.cpp create mode 100644 src/esp32/main-esp32.cpp create mode 100644 src/target_specific.h diff --git a/platformio.ini b/platformio.ini index 093a8d477..cd1755adf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,6 +15,11 @@ default_envs = tbeam ; default to a US frequency range, change it as needed for your region and hardware (CN, JP, EU433, EU865) hw_version = US +; Common settings for ESP targes, mixin with extends = esp32_base +[esp32_base] +src_filter = + ${env.src_filter} - + [env] platform = espressif32 framework = arduino @@ -70,7 +75,8 @@ lib_deps = https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git ; The 1.0 release of the TBEAM board -[env:tbeam] +[env:tbeam] +extends = esp32_base board = ttgo-t-beam lib_deps = ${env.lib_deps} @@ -79,22 +85,26 @@ build_flags = ${env.build_flags} -D TBEAM_V10 ; The original TBEAM board without the AXP power chip and a few other changes -[env:tbeam0.7] +[env:tbeam0.7] +extends = esp32_base board = ttgo-t-beam build_flags = ${env.build_flags} -D TBEAM_V07 [env:heltec] ;build_type = debug ; to make it possible to step through our jtag debugger +extends = esp32_base board = heltec_wifi_lora_32_V2 [env:ttgo-lora32-v1] +extends = esp32_base board = ttgo-lora32-v1 build_flags = ${env.build_flags} -D TTGO_LORA_V1 ; note: the platformio definition for lora32-v2 seems stale, it is missing a pins_arduino.h file, therefore I don't think it works [env:ttgo-lora32-v2] +extends = esp32_base board = ttgo-lora32-v1 build_flags = ${env.build_flags} -D TTGO_LORA_V2 @@ -104,4 +114,8 @@ build_flags = [env:bare] board = ttgo-lora32-v1 build_flags = - ${env.build_flags} -D BARE_BOARD \ No newline at end of file + ${env.build_flags} -D BARE_BOARD +src_filter = + ${env.src_filter} - +lib_ignore = + BluetoothOTA \ No newline at end of file diff --git a/src/bare/main-bare.cpp b/src/bare/main-bare.cpp new file mode 100644 index 000000000..29532a639 --- /dev/null +++ b/src/bare/main-bare.cpp @@ -0,0 +1,6 @@ +#include "target_specific.h" + +void setBluetoothEnable(bool on) +{ + // Do nothing +} \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index a8128882d..862aa0f12 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -202,6 +202,8 @@ along with this program. If not, see . // This string must exactly match the case used in release file names or the android updater won't work #define HW_VENDOR "bare" +#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) + #endif // ----------------------------------------------------------------------------- diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp new file mode 100644 index 000000000..baf0d85b7 --- /dev/null +++ b/src/esp32/main-esp32.cpp @@ -0,0 +1,53 @@ +#include "BluetoothUtil.h" +#include "MeshBluetoothService.h" +#include "PowerFSM.h" +#include "configuration.h" +#include "main.h" +#include "target_specific.h" + +bool bluetoothOn; + +// This routine is called multiple times, once each time we come back from sleep +void reinitBluetooth() +{ + DEBUG_MSG("Starting bluetooth\n"); + + // FIXME - we are leaking like crazy + // AllocatorScope scope(btPool); + + // Note: these callbacks might be coming in from a different thread. + BLEServer *serve = initBLE( + [](uint32_t pin) { + powerFSM.trigger(EVENT_BLUETOOTH_PAIR); + screen.startBluetoothPinScreen(pin); + }, + []() { screen.stopBluetoothPinScreen(); }, getDeviceName(), HW_VENDOR, xstr(APP_VERSION), + xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr + createMeshBluetoothService(serve); + + // Start advertising - this must be done _after_ creating all services + serve->getAdvertising()->start(); +} + +// Enable/disable bluetooth. +void setBluetoothEnable(bool on) +{ + if (on != bluetoothOn) { + DEBUG_MSG("Setting bluetooth enable=%d\n", on); + + bluetoothOn = on; + if (on) { + Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap()); + // ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) ); + reinitBluetooth(); + } else { + // We have to totally teardown our bluetooth objects to prevent leaks + stopMeshBluetoothService(); // Must do before shutting down bluetooth + deinitBLE(); + destroyMeshBluetoothService(); // must do after deinit, because it frees our service + Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap()); + // ESP_ERROR_CHECK( heap_trace_stop() ); + // heap_trace_dump(); + } + } +} \ No newline at end of file diff --git a/src/target_specific.h b/src/target_specific.h new file mode 100644 index 000000000..6acca364a --- /dev/null +++ b/src/target_specific.h @@ -0,0 +1,6 @@ +#pragma once + +// Functions that are unique to particular target types (esp32, bare, nrf52 etc...) + +// Enable/disable bluetooth. +void setBluetoothEnable(bool on); \ No newline at end of file From 6ad451eb5f9387c77740691601f04a8a4bf2e9a7 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 10 Apr 2020 12:18:48 -0700 Subject: [PATCH 002/197] move bluetooth code into something that is architecture specific... because the ESP32 implementation will be different from NRF52 to make this possible I needed to decouple knowlege about bluetooth from our mesh service. Instead mesh service now uses the Obserable pattern to let any interested consumer get notified of important mesh changes (currently that is only bluetooth, but really we should do the same thing for decoupling the GUI 'app' from the mesh service) @girtsf would you mind reviewing my Observer changes? I haven't written C++ code in a long time ;-) --- src/GPS.cpp | 2 +- src/GPS.h | 2 +- src/MeshService.cpp | 21 +++---- src/MeshService.h | 15 +++-- src/Observer.cpp | 11 ---- src/Observer.h | 71 +++++++++++++++++++----- src/PowerFSM.cpp | 1 + src/{ => esp32}/MeshBluetoothService.cpp | 22 +++----- src/{ => esp32}/MeshBluetoothService.h | 0 src/main.cpp | 54 +++--------------- src/main.h | 3 + src/sleep.cpp | 9 ++- src/sleep.h | 1 - 13 files changed, 104 insertions(+), 108 deletions(-) rename src/{ => esp32}/MeshBluetoothService.cpp (96%) rename src/{ => esp32}/MeshBluetoothService.h (100%) diff --git a/src/GPS.cpp b/src/GPS.cpp index 8ca8dd36b..ea04b09f9 100644 --- a/src/GPS.cpp +++ b/src/GPS.cpp @@ -192,7 +192,7 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 if (hasValidLocation) { wantNewLocation = false; - notifyObservers(); + notifyObservers(NULL); // ublox.powerOff(); } } else // we didn't get a location update, go back to sleep and hope the characters show up diff --git a/src/GPS.h b/src/GPS.h index 5d0d4b876..caf3fc249 100644 --- a/src/GPS.h +++ b/src/GPS.h @@ -10,7 +10,7 @@ * * When new data is available it will notify observers. */ -class GPS : public PeriodicTask, public Observable +class GPS : public PeriodicTask, public Observable { SFE_UBLOX_GPS ublox; diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 85f4856d2..6b9dabdc6 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -3,7 +3,7 @@ #include #include "GPS.h" -#include "MeshBluetoothService.h" +//#include "MeshBluetoothService.h" #include "MeshService.h" #include "NodeDB.h" #include "Periodic.h" @@ -52,8 +52,7 @@ MeshService service; 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big MeshService::MeshService() - : packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE), fromRadioQueue(MAX_RX_FROMRADIO), fromNum(0), - radio(packetPool, fromRadioQueue) + : packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE), fromRadioQueue(MAX_RX_FROMRADIO), radio(packetPool, fromRadioQueue) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro } @@ -65,7 +64,7 @@ void MeshService::init() if (!radio.init()) DEBUG_MSG("radio init failed\n"); - gps.addObserver(this); + gpsObserver.observe(&gps); // No need to call this here, our periodic task will fire quite soon // sendOwnerPeriod(); @@ -191,7 +190,7 @@ void MeshService::handleFromRadio() handleFromRadio(mp); } if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets - bluetoothNotifyFromNum(fromNum); + fromNumChanged.notifyObservers(fromNum); } uint32_t sendOwnerCb() @@ -244,7 +243,7 @@ void MeshService::handleToRadio(std::string s) if (loopback) { MeshPacket *mp = packetPool.allocCopy(r.variant.packet); handleFromRadio(mp); - bluetoothNotifyFromNum(fromNum); // tell the phone a new packet arrived + // handleFromRadio will tell the phone a new packet arrived } break; } @@ -323,8 +322,10 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) sendToMesh(p); } -void MeshService::onGPSChanged() +void MeshService::onGPSChanged(void *unused) { + DEBUG_MSG("got gps notify\n"); + // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); p->payload.which_variant = SubPacket_position_tag; @@ -354,9 +355,3 @@ void MeshService::onGPSChanged() releaseToPool(p); } } - -void MeshService::onNotify(Observable *o) -{ - DEBUG_MSG("got gps notify\n"); - onGPSChanged(); -} diff --git a/src/MeshService.h b/src/MeshService.h index 884529237..30e03017c 100644 --- a/src/MeshService.h +++ b/src/MeshService.h @@ -13,8 +13,10 @@ * Top level app for this service. keeps the mesh, the radio config and the queue of received packets. * */ -class MeshService : private Observer +class MeshService { + CallbackObserver gpsObserver = CallbackObserver(this, &MeshService::onGPSChanged); + MemoryPool packetPool; /// received packets waiting for the phone to process them @@ -28,11 +30,13 @@ class MeshService : private Observer PointerQueue fromRadioQueue; /// The current nonce for the newest packet which has been queued for the phone - uint32_t fromNum; + uint32_t fromNum = 0; public: MeshRadio radio; + Observable fromNumChanged; + MeshService(); void init(); @@ -76,14 +80,13 @@ class MeshService : private Observer void sendToMesh(MeshPacket *p); /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh - void onGPSChanged(); - - virtual void onNotify(Observable *o); + void onGPSChanged(void *arg); /// handle all the packets that just arrived from the mesh radio void handleFromRadio(); - /// Handle a packet that just arrived from the radio. We will either eventually enqueue the message to the phone or return it to the free pool + /// Handle a packet that just arrived from the radio. We will either eventually enqueue the message to the phone or return it + /// to the free pool void handleFromRadio(MeshPacket *p); /// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all diff --git a/src/Observer.cpp b/src/Observer.cpp index 468f5ade9..1025f8bc0 100644 --- a/src/Observer.cpp +++ b/src/Observer.cpp @@ -1,13 +1,2 @@ #include "Observer.h" -Observer::~Observer() -{ - if (observed) - observed->removeObserver(this); - observed = NULL; -} - -void Observer::observe(Observable *o) -{ - o->addObserver(this); -} \ No newline at end of file diff --git a/src/Observer.h b/src/Observer.h index b4de9d83b..b20fe818d 100644 --- a/src/Observer.h +++ b/src/Observer.h @@ -4,38 +4,83 @@ #include -class Observable; +template class Observable; -class Observer +/** + * An observer which can be mixed in as a baseclass. Implement onNotify as a method in your class. + */ +template class Observer { - Observable *observed; + Observable *observed; public: Observer() : observed(NULL) {} virtual ~Observer(); - void observe(Observable *o); + void observe(Observable *o); private: - friend class Observable; + friend class Observable; - virtual void onNotify(Observable *o) = 0; + protected: + virtual void onNotify(T arg) = 0; }; -class Observable +/** + * An observer that calls an arbitrary method + */ +template class CallbackObserver : public Observer { - std::list observers; + typedef void (Callback::*ObserverCallback)(T arg); + + Callback *objPtr; + ObserverCallback method; public: - void notifyObservers() + CallbackObserver(Callback *_objPtr, ObserverCallback _method) : objPtr(_objPtr), method(_method) {} + + protected: + virtual void onNotify(T arg) { (objPtr->*method)(arg); } +}; + +/** + * An observable class that will notify observers anytime notifyObservers is called. Argument type T can be any type, but for + * performance reasons a pointer or word sized object is recommended. + */ +template class Observable +{ + std::list *> observers; + + public: + /** + * Tell all observers about a change, observers can process arg as they wish + */ + void notifyObservers(T arg) { - for (std::list::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator) { - (*iterator)->onNotify(this); + for (typename std::list *>::const_iterator iterator = observers.begin(); iterator != observers.end(); + ++iterator) { + (*iterator)->onNotify(arg); } } - void addObserver(Observer *o) { observers.push_back(o); } + private: + friend class Observer; - void removeObserver(Observer *o) { observers.remove(o); } + // Not called directly, instead call observer.observe + void addObserver(Observer *o) { observers.push_back(o); } + + void removeObserver(Observer *o) { observers.remove(o); } }; + +template Observer::~Observer() +{ + if (observed) + observed->removeObserver(this); + observed = NULL; +} + +template void Observer::observe(Observable *o) +{ + o->addObserver(this); +} \ No newline at end of file diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index ce7005ffa..d484efe62 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -7,6 +7,7 @@ #include "main.h" #include "screen.h" #include "sleep.h" +#include "target_specific.h" static void sdsEnter() { diff --git a/src/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp similarity index 96% rename from src/MeshBluetoothService.cpp rename to src/esp32/MeshBluetoothService.cpp index 16af14921..d1bd273b6 100644 --- a/src/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -204,7 +204,7 @@ class FromRadioCharacteristic : public CallbackCharacteristic } }; -class FromNumCharacteristic : public CallbackCharacteristic +class FromNumCharacteristic : public CallbackCharacteristic, public Observer { public: FromNumCharacteristic() @@ -212,6 +212,7 @@ class FromNumCharacteristic : public CallbackCharacteristic BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY) { + observe(&service.fromNumChanged); } void onRead(BLECharacteristic *c) @@ -219,6 +220,13 @@ class FromNumCharacteristic : public CallbackCharacteristic BLEKeepAliveCallbacks::onRead(c); DEBUG_MSG("FIXME implement fromnum read\n"); } + + /// If the mesh service tells us fromNum has changed, tell the phone + virtual void onNotify(uint32_t newValue) + { + setValue(newValue); + notify(); + } }; class MyNodeInfoCharacteristic : public ProtobufCharacteristic @@ -244,18 +252,6 @@ class MyNodeInfoCharacteristic : public ProtobufCharacteristic FromNumCharacteristic *meshFromNumCharacteristic; -/** - * Tell any bluetooth clients that the number of rx packets has changed - */ -void bluetoothNotifyFromNum(uint32_t newValue) -{ - if (meshFromNumCharacteristic) { - // if bt not running ignore - meshFromNumCharacteristic->setValue(newValue); - meshFromNumCharacteristic->notify(); - } -} - BLEService *meshService; /* diff --git a/src/MeshBluetoothService.h b/src/esp32/MeshBluetoothService.h similarity index 100% rename from src/MeshBluetoothService.h rename to src/esp32/MeshBluetoothService.h diff --git a/src/main.cpp b/src/main.cpp index 910d45cfe..0c0958a63 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,9 +21,7 @@ */ -#include "BluetoothUtil.h" #include "GPS.h" -#include "MeshBluetoothService.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -39,6 +37,10 @@ #include #include +#ifndef NO_ESP32 +#include "BluetoothUtil.h" +#endif + #ifdef TBEAM_V10 #include "axp20x.h" AXP20X_Class axp; @@ -59,8 +61,6 @@ static meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; -bool bluetoothOn; - // ----------------------------------------------------------------------------- // Application // ----------------------------------------------------------------------------- @@ -266,49 +266,6 @@ void setup() setCPUFast(false); // 80MHz is fine for our slow peripherals } -void initBluetooth() -{ - DEBUG_MSG("Starting bluetooth\n"); - - // FIXME - we are leaking like crazy - // AllocatorScope scope(btPool); - - // Note: these callbacks might be coming in from a different thread. - BLEServer *serve = initBLE( - [](uint32_t pin) { - powerFSM.trigger(EVENT_BLUETOOTH_PAIR); - screen.startBluetoothPinScreen(pin); - }, - []() { screen.stopBluetoothPinScreen(); }, getDeviceName(), HW_VENDOR, xstr(APP_VERSION), - xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr - createMeshBluetoothService(serve); - - // Start advertising - this must be done _after_ creating all services - serve->getAdvertising()->start(); -} - -void setBluetoothEnable(bool on) -{ - if (on != bluetoothOn) { - DEBUG_MSG("Setting bluetooth enable=%d\n", on); - - bluetoothOn = on; - if (on) { - Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap()); - // ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) ); - initBluetooth(); - } else { - // We have to totally teardown our bluetooth objects to prevent leaks - stopMeshBluetoothService(); // Must do before shutting down bluetooth - deinitBLE(); - destroyMeshBluetoothService(); // must do after deinit, because it frees our service - Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap()); - // ESP_ERROR_CHECK( heap_trace_stop() ); - // heap_trace_dump(); - } - } -} - uint32_t ledBlinker() { static bool ledOn; @@ -352,7 +309,10 @@ void loop() ledPeriodic.loop(); // axpDebugOutput.loop(); + +#ifndef NO_ESP32 loopBLE(); +#endif // for debug printing // service.radio.radioIf.canSleep(); diff --git a/src/main.h b/src/main.h index 9842ea96f..c8c379715 100644 --- a/src/main.h +++ b/src/main.h @@ -9,3 +9,6 @@ extern bool isUSBPowered; // Global Screen singleton. extern meshtastic::Screen screen; + +// Return a human readable string of the form "Meshtastic_ab13" +const char *getDeviceName(); diff --git a/src/sleep.cpp b/src/sleep.cpp index e204ea5f5..2a3dcda24 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -1,7 +1,5 @@ #include "sleep.h" -#include "BluetoothUtil.h" #include "GPS.h" -#include "MeshBluetoothService.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" @@ -11,9 +9,14 @@ #include "esp_pm.h" #include "main.h" #include "rom/rtc.h" +#include "target_specific.h" #include #include +#ifndef NO_ESP32 +#include "BluetoothUtil.h" +#endif + #ifdef TBEAM_V10 #include "axp20x.h" extern AXP20X_Class axp; @@ -105,7 +108,9 @@ void doDeepSleep(uint64_t msecToWake) // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); +#ifndef NO_ESP32 BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep +#endif screen.setOn(false); // datasheet says this will draw only 10ua diff --git a/src/sleep.h b/src/sleep.h index 49976516b..129728f50 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -5,7 +5,6 @@ void doDeepSleep(uint64_t msecToWake); esp_sleep_wakeup_cause_t doLightSleep(uint64_t msecToWake); -void setBluetoothEnable(bool on); void setGPSPower(bool on); // Perform power on init that we do on each wake from deep sleep From 640cb3bf7f551bf40caee034d022c78187d6cf5c Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 10 Apr 2020 12:40:44 -0700 Subject: [PATCH 003/197] allow observers to return an error code to abort further processing Will allow me to use observers to generalize the various hooks that need to run to preflight sleep entry. --- src/MeshService.cpp | 4 +++- src/MeshService.h | 3 ++- src/Observer.h | 20 +++++++++++++++----- src/esp32/MeshBluetoothService.cpp | 3 ++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 6b9dabdc6..38fdc3473 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -322,7 +322,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) sendToMesh(p); } -void MeshService::onGPSChanged(void *unused) +int MeshService::onGPSChanged(void *unused) { DEBUG_MSG("got gps notify\n"); @@ -354,4 +354,6 @@ void MeshService::onGPSChanged(void *unused) releaseToPool(p); } + + return 0; } diff --git a/src/MeshService.h b/src/MeshService.h index 30e03017c..e8f8721eb 100644 --- a/src/MeshService.h +++ b/src/MeshService.h @@ -80,7 +80,8 @@ class MeshService void sendToMesh(MeshPacket *p); /// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh - void onGPSChanged(void *arg); + /// returns 0 to allow futher processing + int onGPSChanged(void *arg); /// handle all the packets that just arrived from the mesh radio void handleFromRadio(); diff --git a/src/Observer.h b/src/Observer.h index b20fe818d..eab1a4a30 100644 --- a/src/Observer.h +++ b/src/Observer.h @@ -24,7 +24,11 @@ template class Observer friend class Observable; protected: - virtual void onNotify(T arg) = 0; + /** + * returns 0 if other observers should continue to be called + * returns !0 if the observe calls should be aborted and this result code returned for notifyObservers + **/ + virtual int onNotify(T arg) = 0; }; /** @@ -32,7 +36,7 @@ template class Observer */ template class CallbackObserver : public Observer { - typedef void (Callback::*ObserverCallback)(T arg); + typedef int (Callback::*ObserverCallback)(T arg); Callback *objPtr; ObserverCallback method; @@ -41,7 +45,7 @@ template class CallbackObserver : public Observer CallbackObserver(Callback *_objPtr, ObserverCallback _method) : objPtr(_objPtr), method(_method) {} protected: - virtual void onNotify(T arg) { (objPtr->*method)(arg); } + virtual int onNotify(T arg) { return (objPtr->*method)(arg); } }; /** @@ -55,13 +59,19 @@ template class Observable public: /** * Tell all observers about a change, observers can process arg as they wish + * + * returns !0 if an observer chose to abort processing by returning this code */ - void notifyObservers(T arg) + int notifyObservers(T arg) { for (typename std::list *>::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator) { - (*iterator)->onNotify(arg); + int result = (*iterator)->onNotify(arg); + if (result != 0) + return result; } + + return 0; } private: diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index d1bd273b6..086f0afe6 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -222,10 +222,11 @@ class FromNumCharacteristic : public CallbackCharacteristic, public Observer Date: Sun, 12 Apr 2020 10:54:27 -0700 Subject: [PATCH 004/197] fix bin paths --- bin/program-release-heltec.sh | 2 +- bin/program-release-tbeam.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/program-release-heltec.sh b/bin/program-release-heltec.sh index d16f6c360..90506473b 100755 --- a/bin/program-release-heltec.sh +++ b/bin/program-release-heltec.sh @@ -3,4 +3,4 @@ set -e source bin/version.sh -esptool.py --baud 921600 write_flash 0x10000 release/latest/firmware-HELTEC-US-$VERSION.bin +esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/firmware-heltec-US-$VERSION.bin diff --git a/bin/program-release-tbeam.sh b/bin/program-release-tbeam.sh index 2c5f030c6..d83b35622 100755 --- a/bin/program-release-tbeam.sh +++ b/bin/program-release-tbeam.sh @@ -3,4 +3,4 @@ set -e source bin/version.sh -esptool.py --baud 921600 write_flash 0x10000 release/latest/firmware-TBEAM-US-$VERSION.bin +esptool.py --baud 921600 write_flash 0x10000 release/latest/bins/firmware-tbeam-US-$VERSION.bin From 4757b6807ec1c1dcd95bce0d50e7f50ec3bb7e98 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 14 Apr 2020 11:40:49 -0700 Subject: [PATCH 005/197] lots of changes: * preflightSleep, notifySleep, notifyDeepSleep now allow arbitrary drivers/devices/software to register for sleep notification. * Use the proceeding to clean up MeshRadio - now the mesh radio is more like an independent driver that doesn't care so much about other systems * clean up MeshService so that it can work with zero MeshRadios added. This is a prelude to supporting boards with multiple interfaces (wifi, extra LORA radios etc) and allows development/testing in sim with a bare ESP32 board * Remove remaining ESP32 dependencies from the bare simulation target this allows running on anything that implements the arduino API --- src/MeshRadio.cpp | 27 ++++++++++++++------- src/MeshRadio.h | 43 ++++++++++++++++++++++++--------- src/MeshService.cpp | 18 +++++++------- src/MeshService.h | 20 ++++++++++------ src/PowerFSM.cpp | 16 ------------- src/error.h | 2 +- src/main.cpp | 16 ++++++++++++- src/sleep.cpp | 58 +++++++++++++++++++++++++++++++++++++-------- src/sleep.h | 12 +++++++++- 9 files changed, 147 insertions(+), 65 deletions(-) diff --git a/src/MeshRadio.cpp b/src/MeshRadio.cpp index 1f7d20d43..7950760bf 100644 --- a/src/MeshRadio.cpp +++ b/src/MeshRadio.cpp @@ -5,8 +5,10 @@ #include #include "MeshRadio.h" +#include "MeshService.h" #include "NodeDB.h" #include "configuration.h" +#include "sleep.h" #include #include @@ -25,7 +27,7 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts bool useHardware = true; MeshRadio::MeshRadio(MemoryPool &_pool, PointerQueue &_rxDest) - : radioIf(_pool, _rxDest) // , manager(radioIf) + : radioIf(_pool, _rxDest), sendPacketObserver(this, &MeshRadio::send) // , manager(radioIf) { myNodeInfo.num_channels = NUM_CHANNELS; @@ -40,6 +42,11 @@ bool MeshRadio::init() DEBUG_MSG("Starting meshradio init...\n"); + configChangedObserver.observe(&service.configChanged); + sendPacketObserver.observe(&service.sendViaRadio); + preflightSleepObserver.observe(&preflightSleep); + notifyDeepSleepObserver.observe(¬ifyDeepSleep); + #ifdef RESET_GPIO pinMode(RESET_GPIO, OUTPUT); // Deassert reset digitalWrite(RESET_GPIO, HIGH); @@ -84,7 +91,7 @@ unsigned long hash(char *str) return hash; } -void MeshRadio::reloadConfig() +int MeshRadio::reloadConfig(void *unused) { radioIf.setModeIdle(); // Need to be idle before doing init @@ -116,17 +123,21 @@ void MeshRadio::reloadConfig() // Done with init tell radio to start receiving radioIf.setModeRx(); + + return 0; } -ErrorCode MeshRadio::send(MeshPacket *p) +int MeshRadio::send(MeshPacket *p) { lastTxStart = millis(); - if (useHardware) - return radioIf.send(p); - else { - radioIf.pool.release(p); - return ERRNO_OK; + if (useHardware) { + radioIf.send(p); + // Note: we ignore the error code, because no matter what the interface has already freed the packet. + return 1; // Indicate success - stop offering this packet to radios + } else { + // fail + return 0; } } diff --git a/src/MeshRadio.h b/src/MeshRadio.h index efb9e4faa..4b4d3c7b6 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -3,6 +3,7 @@ #include "CustomRF95.h" #include "MemoryPool.h" #include "MeshTypes.h" +#include "Observer.h" #include "PointerQueue.h" #include "configuration.h" #include "mesh.pb.h" @@ -80,22 +81,42 @@ class MeshRadio bool init(); - /// Send a packet (possibly by enquing in a private fifo). This routine will - /// later free() the packet to pool. This routine is not allowed to stall because it is called from - /// bluetooth comms code. If the txmit queue is empty it might return an error - ErrorCode send(MeshPacket *p); - /// Do loop callback operations (we currently FIXME poll the receive mailbox here) /// for received packets it will call the rx handler void loop(); - /// The radioConfig object just changed, call this to force the hw to change to the new settings - void reloadConfig(); - private: - // RHReliableDatagram manager; // don't use mesh yet - // RHMesh manager; - /// Used for the tx timer watchdog, to check for bugs in our transmit code, msec of last time we did a send uint32_t lastTxStart = 0; + + CallbackObserver configChangedObserver = + CallbackObserver(this, &MeshRadio::reloadConfig); + + CallbackObserver preflightSleepObserver = + CallbackObserver(this, &MeshRadio::preflightSleepCb); + + CallbackObserver notifyDeepSleepObserver = + CallbackObserver(this, &MeshRadio::notifyDeepSleepDb); + + CallbackObserver sendPacketObserver; /* = + CallbackObserver(this, &MeshRadio::send); */ + + /// Send a packet (possibly by enquing in a private fifo). This routine will + /// later free() the packet to pool. This routine is not allowed to stall because it is called from + /// bluetooth comms code. If the txmit queue is empty it might return an error. + /// + /// Returns 1 for success or 0 for failure (and if we fail it is the _callers_ responsibility to free the packet) + int send(MeshPacket *p); + + /// The radioConfig object just changed, call this to force the hw to change to the new settings + int reloadConfig(void *unused = NULL); + + /// Return 0 if sleep is okay + int preflightSleepCb(void *unused = NULL) { return radioIf.canSleep() ? 0 : 1; } + + int notifyDeepSleepDb(void *unused = NULL) + { + radioIf.sleep(); + return 0; + } }; diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 38fdc3473..2af86b4c3 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -51,8 +51,7 @@ MeshService service; #define MAX_RX_FROMRADIO \ 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big -MeshService::MeshService() - : packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE), fromRadioQueue(MAX_RX_FROMRADIO), radio(packetPool, fromRadioQueue) +MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE), packetPool(MAX_PACKETS), fromRadioQueue(MAX_RX_FROMRADIO) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro } @@ -61,9 +60,6 @@ void MeshService::init() { nodeDB.init(); - if (!radio.init()) - DEBUG_MSG("radio init failed\n"); - gpsObserver.observe(&gps); // No need to call this here, our periodic task will fire quite soon @@ -205,8 +201,6 @@ Periodic sendOwnerPeriod(sendOwnerCb); /// Do idle processing (mostly processing messages which have been queued from the radio) void MeshService::loop() { - radio.loop(); // FIXME, possibly move radio interaction to own thread - handleFromRadio(); // occasionally send our owner info @@ -218,7 +212,7 @@ void MeshService::reloadConfig() { // If we can successfully set this radio to these settings, save them to disk nodeDB.resetRadioConfig(); // Don't let the phone send us fatally bad settings - radio.reloadConfig(); + configChanged.notifyObservers(NULL); nodeDB.saveToDisk(); } @@ -276,8 +270,12 @@ void MeshService::sendToMesh(MeshPacket *p) DEBUG_MSG("Dropping locally processed message\n"); else { // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - if (radio.send(p) != ERRNO_OK) - DEBUG_MSG("Dropped packet because send queue was full!\n"); + int didSend = sendViaRadio.notifyObservers(p); + if (!didSend) { + DEBUG_MSG("No radio was able to send packet, discarding..."); + releaseToPool(p); + } + } } diff --git a/src/MeshService.h b/src/MeshService.h index e8f8721eb..e9d4fe488 100644 --- a/src/MeshService.h +++ b/src/MeshService.h @@ -17,26 +17,32 @@ class MeshService { CallbackObserver gpsObserver = CallbackObserver(this, &MeshService::onGPSChanged); - MemoryPool packetPool; - /// received packets waiting for the phone to process them /// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure /// we never hang because android hasn't been there in a while /// FIXME - save this to flash on deep sleep PointerQueue toPhoneQueue; - /// Packets which have just arrived from the radio, ready to be processed by this service and possibly - /// forwarded to the phone. - PointerQueue fromRadioQueue; - /// The current nonce for the newest packet which has been queued for the phone uint32_t fromNum = 0; public: - MeshRadio radio; + MemoryPool packetPool; + /// Packets which have just arrived from the radio, ready to be processed by this service and possibly + /// forwarded to the phone. + PointerQueue fromRadioQueue; + + /// Called when some new packets have arrived from one of the radios Observable fromNumChanged; + /// Called when radio config has changed (radios should observe this and set their hardware as required) + Observable configChanged; + + /// Radios should observe this and return 0 if they were unable to process the packet or 1 if they were (and therefore it + /// should not be offered to other radios) + Observable sendViaRadio; + MeshService(); void init(); diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index d484efe62..716e4e2c3 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -31,22 +31,6 @@ static void lsEnter() DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs); screen.setOn(false); - uint32_t now = millis(); - while (!service.radio.radioIf.canSleep()) { - delay(10); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) - - if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep - recordCriticalError(ErrSleepEnterWait); - break; - } - } - - gps.prepareSleep(); // abandon in-process parsing - - // if (!isUSBPowered) // FIXME - temp hack until we can put gps in sleep mode, if we have AC when we go to sleep then - // leave GPS on - // setGPSPower(false); // kill GPS power - DEBUG_MSG("lsEnter end\n"); } diff --git a/src/error.h b/src/error.h index cc3328c3b..3fa3f4709 100644 --- a/src/error.h +++ b/src/error.h @@ -1,7 +1,7 @@ #pragma once /// Error codes for critical error -enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait }; +enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio }; /// Record an error that should be reported via analytics void recordCriticalError(CriticalErrorCode code, uint32_t address = 0); diff --git a/src/main.cpp b/src/main.cpp index 0c0958a63..a10de160b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,7 @@ #include "Periodic.h" #include "PowerFSM.h" #include "configuration.h" +#include "error.h" #include "esp32/pm.h" #include "esp_pm.h" #include "power.h" @@ -205,6 +206,8 @@ const char *getDeviceName() return name; } +static MeshRadio *radio = NULL; + void setup() { // Debug @@ -259,6 +262,14 @@ void setup() service.init(); +#ifndef NO_ESP32 + // MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) + radio = new MeshRadio(service.packetPool, service.fromRadioQueue); +#endif + + if (radio && !radio->init()) + recordCriticalError(ErrNoRadio); + // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS @@ -307,6 +318,9 @@ void loop() gps.loop(); service.loop(); + if (radio) + radio->loop(); + ledPeriodic.loop(); // axpDebugOutput.loop(); @@ -315,7 +329,7 @@ void loop() #endif // for debug printing - // service.radio.radioIf.canSleep(); + // radio.radioIf.canSleep(); #ifdef PMU_IRQ if (pmu_irq) { diff --git a/src/sleep.cpp b/src/sleep.cpp index 2a3dcda24..0ae911b04 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -5,6 +5,7 @@ #include "NodeDB.h" #include "Periodic.h" #include "configuration.h" +#include "error.h" #include "esp32/pm.h" #include "esp_pm.h" #include "main.h" @@ -22,6 +23,12 @@ extern AXP20X_Class axp; #endif +/// Called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen +Observable preflightSleep; + +/// Called to tell observers we are now entering sleep and you should prepare. Must return 0 +Observable notifySleep, notifyDeepSleep; + // deep sleep support RTC_DATA_ATTR int bootCount = 0; esp_sleep_source_t wakeCause; // the reason we booted this time @@ -101,22 +108,53 @@ void initDeepSleep() DEBUG_MSG("booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason); } +/// return true if sleep is allowed +static bool doPreflightSleep() +{ + if (preflightSleep.notifyObservers(NULL) != 0) + return false; // vetoed + else + return true; +} + +/// Tell devices we are going to sleep and wait for them to handle things +static void waitEnterSleep() +{ + /* + former hardwired code - now moved into notifySleep callbacks: + // Put radio in sleep mode (will still draw power but only 0.2uA) + service.radio.radioIf.sleep(); + */ + + uint32_t now = millis(); + while (!doPreflightSleep()) { + delay(10); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) + + if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep + recordCriticalError(ErrSleepEnterWait); + break; + } + } + + // Code that still needs to be moved into notifyObservers + Serial.flush(); // send all our characters before we stop cpu clock + setBluetoothEnable(false); // has to be off before calling light sleep + gps.prepareSleep(); // abandon in-process parsing + + notifySleep.notifyObservers(NULL); +} + void doDeepSleep(uint64_t msecToWake) { DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000); // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); - -#ifndef NO_ESP32 - BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep -#endif + waitEnterSleep(); + notifyDeepSleep.notifyObservers(NULL); screen.setOn(false); // datasheet says this will draw only 10ua - // Put radio in sleep mode (will still draw power but only 0.2uA) - service.radio.radioIf.sleep(); - nodeDB.saveToDisk(); #ifdef RESET_OLED @@ -204,10 +242,10 @@ void doDeepSleep(uint64_t msecToWake) esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default { // DEBUG_MSG("Enter light sleep\n"); - uint64_t sleepUsec = sleepMsec * 1000LL; - Serial.flush(); // send all our characters before we stop cpu clock - setBluetoothEnable(false); // has to be off before calling light sleep + waitEnterSleep(); + + uint64_t sleepUsec = sleepMsec * 1000LL; // NOTE! ESP docs say we must disable bluetooth and wifi before light sleep diff --git a/src/sleep.h b/src/sleep.h index 129728f50..e63fa70a6 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -1,6 +1,7 @@ #pragma once #include "Arduino.h" +#include "Observer.h" #include "esp_sleep.h" void doDeepSleep(uint64_t msecToWake); @@ -17,4 +18,13 @@ extern int bootCount; extern esp_sleep_source_t wakeCause; // is bluetooth sw currently running? -extern bool bluetoothOn; \ No newline at end of file +extern bool bluetoothOn; + +/// Called to ask any observers if they want to veto sleep. Return 1 to veto or 0 to allow sleep to happen +extern Observable preflightSleep; + +/// Called to tell observers we are now entering (light or deep) sleep and you should prepare. Must return 0 +extern Observable notifySleep; + +/// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 +extern Observable notifyDeepSleep; \ No newline at end of file From 5c379c4a989296dde1d07fca6ab5511b6f5f194c Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 14 Apr 2020 11:44:35 -0700 Subject: [PATCH 006/197] missing newline --- src/MeshService.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 2af86b4c3..c8e947acf 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -272,10 +272,9 @@ void MeshService::sendToMesh(MeshPacket *p) // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it int didSend = sendViaRadio.notifyObservers(p); if (!didSend) { - DEBUG_MSG("No radio was able to send packet, discarding..."); + DEBUG_MSG("No radio was able to send packet, discarding...\n"); releaseToPool(p); } - } } From fd17193d5ef1d90b344381f147be41199cd4f061 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 14 Apr 2020 12:31:29 -0700 Subject: [PATCH 007/197] Strip out all the parts of Radiohead (most of it) that we don't need --- platformio.ini | 3 +- src/CustomRF95.h | 1 - src/MeshRadio.cpp | 2 - src/MeshRadio.h | 1 - src/error.h | 2 + src/rf95/LICENSE | 17 + src/rf95/README.md | 4 + src/rf95/RHGenericDriver.cpp | 221 +++++ src/rf95/RHGenericDriver.h | 307 +++++++ src/rf95/RHGenericSPI.cpp | 31 + src/rf95/RHGenericSPI.h | 183 ++++ src/rf95/RHHardwareSPI.cpp | 499 +++++++++++ src/rf95/RHHardwareSPI.h | 116 +++ src/rf95/RHNRFSPIDriver.cpp | 137 +++ src/rf95/RHNRFSPIDriver.h | 101 +++ src/rf95/RHSPIDriver.cpp | 95 ++ src/rf95/RHSPIDriver.h | 100 +++ src/rf95/RHSoftwareSPI.cpp | 166 ++++ src/rf95/RHSoftwareSPI.h | 90 ++ src/rf95/RH_RF95.cpp | 668 ++++++++++++++ src/rf95/RH_RF95.h | 894 +++++++++++++++++++ src/rf95/RadioHead.h | 1595 ++++++++++++++++++++++++++++++++++ 22 files changed, 5227 insertions(+), 6 deletions(-) create mode 100644 src/rf95/LICENSE create mode 100644 src/rf95/README.md create mode 100644 src/rf95/RHGenericDriver.cpp create mode 100644 src/rf95/RHGenericDriver.h create mode 100644 src/rf95/RHGenericSPI.cpp create mode 100644 src/rf95/RHGenericSPI.h create mode 100644 src/rf95/RHHardwareSPI.cpp create mode 100644 src/rf95/RHHardwareSPI.h create mode 100644 src/rf95/RHNRFSPIDriver.cpp create mode 100644 src/rf95/RHNRFSPIDriver.h create mode 100644 src/rf95/RHSPIDriver.cpp create mode 100644 src/rf95/RHSPIDriver.h create mode 100644 src/rf95/RHSoftwareSPI.cpp create mode 100644 src/rf95/RHSoftwareSPI.h create mode 100644 src/rf95/RH_RF95.cpp create mode 100644 src/rf95/RH_RF95.h create mode 100644 src/rf95/RadioHead.h diff --git a/platformio.ini b/platformio.ini index cd1755adf..077e825dc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,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 = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${common.hw_version} +build_flags = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${common.hw_version} ; not needed included in ttgo-t-beam board file ; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram @@ -65,7 +65,6 @@ debug_tool = jlink debug_init_break = tbreak setup lib_deps = - https://github.com/meshtastic/RadioHead.git#d32df52f8c80eb2525d3774adc1fe36bb4c32952 https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306 SPI ; 1260 ; OneButton - not used yet diff --git a/src/CustomRF95.h b/src/CustomRF95.h index 77b1ee8e1..9f4e6b65c 100644 --- a/src/CustomRF95.h +++ b/src/CustomRF95.h @@ -2,7 +2,6 @@ #include "RadioInterface.h" #include "mesh.pb.h" -#include #include #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission diff --git a/src/MeshRadio.cpp b/src/MeshRadio.cpp index 7950760bf..7bf2ce45d 100644 --- a/src/MeshRadio.cpp +++ b/src/MeshRadio.cpp @@ -1,6 +1,4 @@ -#include "RH_RF95.h" #include "error.h" -#include #include #include diff --git a/src/MeshRadio.h b/src/MeshRadio.h index 4b4d3c7b6..7e39e0c32 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -7,7 +7,6 @@ #include "PointerQueue.h" #include "configuration.h" #include "mesh.pb.h" -#include // US channel settings #define CH0_US 903.08f // MHz diff --git a/src/error.h b/src/error.h index 3fa3f4709..4fae3825b 100644 --- a/src/error.h +++ b/src/error.h @@ -1,5 +1,7 @@ #pragma once +#include + /// Error codes for critical error enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio }; diff --git a/src/rf95/LICENSE b/src/rf95/LICENSE new file mode 100644 index 000000000..da124e128 --- /dev/null +++ b/src/rf95/LICENSE @@ -0,0 +1,17 @@ +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 new file mode 100644 index 000000000..f6346e4ac --- /dev/null +++ b/src/rf95/README.md @@ -0,0 +1,4 @@ +# 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/RHGenericDriver.cpp b/src/rf95/RHGenericDriver.cpp new file mode 100644 index 000000000..332596fda --- /dev/null +++ b/src/rf95/RHGenericDriver.cpp @@ -0,0 +1,221 @@ +// RHGenericDriver.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RHGenericDriver.cpp,v 1.23 2018/02/11 23:57:18 mikem Exp $ + +#include + +RHGenericDriver::RHGenericDriver() + : + _mode(RHModeInitialising), + _thisAddress(RH_BROADCAST_ADDRESS), + _txHeaderTo(RH_BROADCAST_ADDRESS), + _txHeaderFrom(RH_BROADCAST_ADDRESS), + _txHeaderId(0), + _txHeaderFlags(0), + _rxBad(0), + _rxGood(0), + _txGood(0), + _cad_timeout(0) +{ +} + +bool RHGenericDriver::init() +{ + return true; +} + +// Blocks until a valid message is received +void RHGenericDriver::waitAvailable() +{ + while (!available()) + YIELD; +} + +// Blocks until a valid message is received or timeout expires +// Return true if there is a message available +// Works correctly even on millis() rollover +bool RHGenericDriver::waitAvailableTimeout(uint16_t timeout) +{ + unsigned long starttime = millis(); + while ((millis() - starttime) < timeout) + { + if (available()) + { + return true; + } + YIELD; + } + return false; +} + +bool RHGenericDriver::waitPacketSent() +{ + while (_mode == RHModeTx) + YIELD; // Wait for any previous transmit to finish + return true; +} + +bool RHGenericDriver::waitPacketSent(uint16_t timeout) +{ + unsigned long starttime = millis(); + while ((millis() - starttime) < timeout) + { + if (_mode != RHModeTx) // Any previous transmit finished? + return true; + YIELD; + } + return false; +} + +// Wait until no channel activity detected or timeout +bool RHGenericDriver::waitCAD() +{ + if (!_cad_timeout) + return true; + + // Wait for any channel activity to finish or timeout + // Sophisticated DCF function... + // DCF : BackoffTime = random() x aSlotTime + // 100 - 1000 ms + // 10 sec timeout + unsigned long t = millis(); + while (isChannelActive()) + { + if (millis() - t > _cad_timeout) + return false; +#if (RH_PLATFORM == RH_PLATFORM_STM32) // stdlib on STMF103 gets confused if random is redefined + delay(_random(1, 10) * 100); +#else + delay(random(1, 10) * 100); // Should these values be configurable? Macros? +#endif + } + + return true; +} + +// subclasses are expected to override if CAD is available for that radio +bool RHGenericDriver::isChannelActive() +{ + return false; +} + +void RHGenericDriver::setPromiscuous(bool promiscuous) +{ + _promiscuous = promiscuous; +} + +void RHGenericDriver::setThisAddress(uint8_t address) +{ + _thisAddress = address; +} + +void RHGenericDriver::setHeaderTo(uint8_t to) +{ + _txHeaderTo = to; +} + +void RHGenericDriver::setHeaderFrom(uint8_t from) +{ + _txHeaderFrom = from; +} + +void RHGenericDriver::setHeaderId(uint8_t id) +{ + _txHeaderId = id; +} + +void RHGenericDriver::setHeaderFlags(uint8_t set, uint8_t clear) +{ + _txHeaderFlags &= ~clear; + _txHeaderFlags |= set; +} + +uint8_t RHGenericDriver::headerTo() +{ + return _rxHeaderTo; +} + +uint8_t RHGenericDriver::headerFrom() +{ + return _rxHeaderFrom; +} + +uint8_t RHGenericDriver::headerId() +{ + return _rxHeaderId; +} + +uint8_t RHGenericDriver::headerFlags() +{ + return _rxHeaderFlags; +} + +int16_t RHGenericDriver::lastRssi() +{ + return _lastRssi; +} + +RHGenericDriver::RHMode RHGenericDriver::mode() +{ + return _mode; +} + +void RHGenericDriver::setMode(RHMode mode) +{ + _mode = mode; +} + +bool RHGenericDriver::sleep() +{ + return false; +} + +// Diagnostic help +void RHGenericDriver::printBuffer(const char* prompt, const uint8_t* buf, uint8_t len) +{ +#ifdef RH_HAVE_SERIAL + Serial.println(prompt); + uint8_t i; + for (i = 0; i < len; i++) + { + if (i % 16 == 15) + Serial.println(buf[i], HEX); + else + { + Serial.print(buf[i], HEX); + Serial.print(' '); + } + } + Serial.println(""); +#endif +} + +uint16_t RHGenericDriver::rxBad() +{ + return _rxBad; +} + +uint16_t RHGenericDriver::rxGood() +{ + return _rxGood; +} + +uint16_t RHGenericDriver::txGood() +{ + return _txGood; +} + +void RHGenericDriver::setCADTimeout(unsigned long cad_timeout) +{ + _cad_timeout = cad_timeout; +} + +#if (RH_PLATFORM == RH_PLATFORM_ATTINY) +// Tinycore does not have __cxa_pure_virtual, so without this we +// get linking complaints from the default code generated for pure virtual functions +extern "C" void __cxa_pure_virtual() +{ + while (1); +} +#endif diff --git a/src/rf95/RHGenericDriver.h b/src/rf95/RHGenericDriver.h new file mode 100644 index 000000000..ae523d966 --- /dev/null +++ b/src/rf95/RHGenericDriver.h @@ -0,0 +1,307 @@ +// RHGenericDriver.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RHGenericDriver.h,v 1.23 2018/09/23 23:54:01 mikem Exp $ + +#ifndef RHGenericDriver_h +#define RHGenericDriver_h + +#include + +// Defines bits of the FLAGS header reserved for use by the RadioHead library and +// the flags available for use by applications +#define RH_FLAGS_RESERVED 0xf0 +#define RH_FLAGS_APPLICATION_SPECIFIC 0x0f +#define RH_FLAGS_NONE 0 + +// Default timeout for waitCAD() in ms +#define RH_CAD_DEFAULT_TIMEOUT 10000 + +///////////////////////////////////////////////////////////////////// +/// \class RHGenericDriver RHGenericDriver.h +/// \brief Abstract base class for a RadioHead driver. +/// +/// This class defines the functions that must be provided by any RadioHead driver. +/// Different types of driver will implement all the abstract functions, and will perhaps override +/// other functions in this subclass, or perhaps add new functions specifically required by that driver. +/// Do not directly instantiate this class: it is only to be subclassed by driver classes. +/// +/// Subclasses are expected to implement a half-duplex, unreliable, error checked, unaddressed packet transport. +/// They are expected to carry a message payload with an appropriate maximum length for the transport hardware +/// and to also carry unaltered 4 message headers: TO, FROM, ID, FLAGS +/// +/// \par Headers +/// +/// Each message sent and received by a RadioHead driver includes 4 headers: +/// -TO The node address that the message is being sent to (broadcast RH_BROADCAST_ADDRESS (255) is permitted) +/// -FROM The node address of the sending node +/// -ID A message ID, distinct (over short time scales) for each message sent by a particilar node +/// -FLAGS A bitmask of flags. The most significant 4 bits are reserved for use by RadioHead. The least +/// significant 4 bits are reserved for applications. +class RHGenericDriver +{ +public: + /// \brief Defines different operating modes for the transport hardware + /// + /// These are the different values that can be adopted by the _mode variable and + /// returned by the mode() member function, + typedef enum + { + RHModeInitialising = 0, ///< Transport is initialising. Initial default value until init() is called.. + RHModeSleep, ///< Transport hardware is in low power sleep mode (if supported) + RHModeIdle, ///< Transport is idle. + RHModeTx, ///< Transport is in the process of transmitting a message. + RHModeRx, ///< Transport is in the process of receiving a message. + RHModeCad ///< Transport is in the process of detecting channel activity (if supported) + } RHMode; + + /// Constructor + RHGenericDriver(); + + /// 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(); + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, if there is an uncollected received message, and there is no message + /// currently bing transmitted, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it will be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop. + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv(). + virtual bool available() = 0; + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len) = 0; + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then optionally waits for Channel Activity Detection (CAD) + /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is NOT permitted. If the message is too long for the underlying radio technology, send() will + /// return false and will not send the message. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send (> 0) + /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. + /// \return true if the message length was valid and it was correctly queued for transmit. Return false + /// if CAD was requested and the CAD timeout timed out before clear channel was detected. + virtual bool send(const uint8_t* data, uint8_t len) = 0; + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength() = 0; + + /// Starts the receiver and blocks until a valid received + /// message is available. + virtual void waitAvailable(); + + /// Blocks until the transmitter + /// is no longer transmitting. + virtual bool waitPacketSent(); + + /// Blocks until the transmitter is no longer transmitting. + /// or until the timeout occuers, whichever happens first + /// \param[in] timeout Maximum time to wait in milliseconds. + /// \return true if the radio completed transmission within the timeout period. False if it timed out. + virtual bool waitPacketSent(uint16_t timeout); + + /// Starts the receiver and blocks until a received message is available or a timeout + /// \param[in] timeout Maximum time to wait in milliseconds. + /// \return true if a message is available + virtual bool waitAvailableTimeout(uint16_t timeout); + + // Bent G Christensen (bentor@gmail.com), 08/15/2016 + /// Channel Activity Detection (CAD). + /// Blocks until channel activity is finished or CAD timeout occurs. + /// Uses the radio's CAD function (if supported) to detect channel activity. + /// Implements random delays of 100 to 1000ms while activity is detected and until timeout. + /// Caution: the random() function is not seeded. If you want non-deterministic behaviour, consider + /// using something like randomSeed(analogRead(A0)); in your sketch. + /// Permits the implementation of listen-before-talk mechanism (Collision Avoidance). + /// Calls the isChannelActive() member function for the radio (if supported) + /// to determine if the channel is active. If the radio does not support isChannelActive(), + /// always returns true immediately + /// \return true if the radio-specific CAD (as returned by isChannelActive()) + /// shows the channel is clear within the timeout period (or the timeout period is 0), else returns false. + virtual bool waitCAD(); + + /// Sets the Channel Activity Detection timeout in milliseconds to be used by waitCAD(). + /// The default is 0, which means do not wait for CAD detection. + /// CAD detection depends on support for isChannelActive() by your particular radio. + void setCADTimeout(unsigned long cad_timeout); + + /// Determine if the currently selected radio channel is active. + /// This is expected to be subclassed by specific radios to implement their Channel Activity Detection + /// if supported. If the radio does not support CAD, returns true immediately. If a RadioHead radio + /// supports isChannelActive() it will be documented in the radio specific documentation. + /// This is called automatically by waitCAD(). + /// \return true if the radio-specific CAD (as returned by override of isChannelActive()) shows the + /// current radio channel as active, else false. If there is no radio-specific CAD, returns false. + virtual bool isChannelActive(); + + /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. + /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, + /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. + /// In promiscuous mode, all messages will be accepted regardless of the TO header. + /// In a conventional multinode system, all nodes will have a unique address + /// (which you could store in EEPROM). + /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, + /// allowing the possibilty of address spoofing). + /// \param[in] thisAddress The address of this node. + virtual void setThisAddress(uint8_t thisAddress); + + /// Sets the TO header to be sent in all subsequent messages + /// \param[in] to The new TO header value + virtual void setHeaderTo(uint8_t to); + + /// Sets the FROM header to be sent in all subsequent messages + /// \param[in] from The new FROM header value + virtual void setHeaderFrom(uint8_t from); + + /// Sets the ID header to be sent in all subsequent messages + /// \param[in] id The new ID header value + virtual void setHeaderId(uint8_t id); + + /// Sets and clears bits in the FLAGS header to be sent in all subsequent messages + /// First it clears he FLAGS according to the clear argument, then sets the flags according to the + /// set argument. The default for clear always clears the application specific flags. + /// \param[in] set bitmask of bits to be set. Flags are cleared with the clear mask before being set. + /// \param[in] clear bitmask of flags to clear. Defaults to RH_FLAGS_APPLICATION_SPECIFIC + /// which clears the application specific flags, resulting in new application specific flags + /// identical to the set. + virtual void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_APPLICATION_SPECIFIC); + + /// Tells the receiver to accept messages with any TO address, not just messages + /// addressed to thisAddress or the broadcast address + /// \param[in] promiscuous true if you wish to receive messages with any TO address + virtual void setPromiscuous(bool promiscuous); + + /// Returns the TO header of the last received message + /// \return The TO header + virtual uint8_t headerTo(); + + /// Returns the FROM header of the last received message + /// \return The FROM header + virtual uint8_t headerFrom(); + + /// Returns the ID header of the last received message + /// \return The ID header + virtual uint8_t headerId(); + + /// Returns the FLAGS header of the last received message + /// \return The FLAGS header + virtual uint8_t headerFlags(); + + /// Returns the most recent RSSI (Receiver Signal Strength Indicator). + /// Usually it is the RSSI of the last received message, which is measured when the preamble is received. + /// If you called readRssi() more recently, it will return that more recent value. + /// \return The most recent RSSI measurement in dBm. + virtual int16_t lastRssi(); + + /// Returns the operating mode of the library. + /// \return the current mode, one of RF69_MODE_* + virtual RHMode mode(); + + /// Sets the operating mode of the transport. + virtual void setMode(RHMode mode); + + /// Sets the transport hardware into low-power sleep mode + /// (if supported). May be overridden by specific drivers to initialte sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// \return true if sleep mode is supported by transport hardware and the RadioHead driver, and if sleep mode + /// was successfully entered. If sleep mode is not suported, return false. + virtual bool sleep(); + + /// Prints a data buffer in HEX. + /// For diagnostic use + /// \param[in] prompt string to preface the print + /// \param[in] buf Location of the buffer to print + /// \param[in] len Length of the buffer in octets. + static void printBuffer(const char* prompt, const uint8_t* buf, uint8_t len); + + /// Returns the count of the number of bad received packets (ie packets with bad lengths, checksum etc) + /// which were rejected and not delivered to the application. + /// Caution: not all drivers can correctly report this count. Some underlying hardware only report + /// good packets. + /// \return The number of bad packets received. + virtual uint16_t rxBad(); + + /// Returns the count of the number of + /// good received packets + /// \return The number of good packets received. + virtual uint16_t rxGood(); + + /// Returns the count of the number of + /// packets successfully transmitted (though not necessarily received by the destination) + /// \return The number of packets successfully transmitted + virtual uint16_t txGood(); + +protected: + + /// The current transport operating mode + volatile RHMode _mode; + + /// This node id + uint8_t _thisAddress; + + /// Whether the transport is in promiscuous mode + bool _promiscuous; + + /// TO header in the last received mesasge + volatile uint8_t _rxHeaderTo; + + /// FROM header in the last received mesasge + volatile uint8_t _rxHeaderFrom; + + /// ID header in the last received mesasge + volatile uint8_t _rxHeaderId; + + /// FLAGS header in the last received mesasge + volatile uint8_t _rxHeaderFlags; + + /// TO header to send in all messages + uint8_t _txHeaderTo; + + /// FROM header to send in all messages + uint8_t _txHeaderFrom; + + /// ID header to send in all messages + uint8_t _txHeaderId; + + /// FLAGS header to send in all messages + uint8_t _txHeaderFlags; + + /// The value of the last received RSSI value, in some transport specific units + volatile int16_t _lastRssi; + + /// Count of the number of bad messages (eg bad checksum etc) received + volatile uint16_t _rxBad; + + /// Count of the number of successfully transmitted messaged + volatile uint16_t _rxGood; + + /// Count of the number of bad messages (correct checksum etc) received + volatile uint16_t _txGood; + + /// Channel activity detected + volatile bool _cad; + + /// Channel activity timeout in ms + unsigned int _cad_timeout; + +private: + +}; + +#endif diff --git a/src/rf95/RHGenericSPI.cpp b/src/rf95/RHGenericSPI.cpp new file mode 100644 index 000000000..ec43cd403 --- /dev/null +++ b/src/rf95/RHGenericSPI.cpp @@ -0,0 +1,31 @@ +// RHGenericSPI.cpp +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHGenericSPI.cpp,v 1.2 2014/04/12 05:26:05 mikem Exp $ + +#include + +RHGenericSPI::RHGenericSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode) + : + _frequency(frequency), + _bitOrder(bitOrder), + _dataMode(dataMode) +{ +} + +void RHGenericSPI::setBitOrder(BitOrder bitOrder) +{ + _bitOrder = bitOrder; +} + +void RHGenericSPI::setDataMode(DataMode dataMode) +{ + _dataMode = dataMode; +} + +void RHGenericSPI::setFrequency(Frequency frequency) +{ + _frequency = frequency; +} + diff --git a/src/rf95/RHGenericSPI.h b/src/rf95/RHGenericSPI.h new file mode 100644 index 000000000..4b961b310 --- /dev/null +++ b/src/rf95/RHGenericSPI.h @@ -0,0 +1,183 @@ +// RHGenericSPI.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHGenericSPI.h,v 1.9 2020/01/05 07:02:23 mikem Exp mikem $ + +#ifndef RHGenericSPI_h +#define RHGenericSPI_h + +#include + +///////////////////////////////////////////////////////////////////// +/// \class RHGenericSPI RHGenericSPI.h +/// \brief Base class for SPI interfaces +/// +/// This generic abstract class is used to encapsulate hardware or software SPI interfaces for +/// a variety of platforms. +/// The intention is so that driver classes can be configured to use hardware or software SPI +/// without changing the main code. +/// +/// You must provide a subclass of this class to driver constructors that require SPI. +/// A concrete subclass that encapsualates the standard Arduino hardware SPI and a bit-banged +/// software implementation is included. +/// +/// Do not directly use this class: it must be subclassed and the following abstract functions at least +/// must be implmented: +/// - begin() +/// - end() +/// - transfer() +class RHGenericSPI +{ +public: + + /// \brief Defines constants for different SPI modes + /// + /// Defines constants for different SPI modes + /// that can be passed to the constructor or setMode() + /// We need to define these in a device and platform independent way, because the + /// SPI implementation is different on each platform. + typedef enum + { + DataMode0 = 0, ///< SPI Mode 0: CPOL = 0, CPHA = 0 + DataMode1, ///< SPI Mode 1: CPOL = 0, CPHA = 1 + DataMode2, ///< SPI Mode 2: CPOL = 1, CPHA = 0 + DataMode3, ///< SPI Mode 3: CPOL = 1, CPHA = 1 + } DataMode; + + /// \brief Defines constants for different SPI bus frequencies + /// + /// Defines constants for different SPI bus frequencies + /// that can be passed to setFrequency(). + /// The frequency you get may not be exactly the one according to the name. + /// We need to define these in a device and platform independent way, because the + /// SPI implementation is different on each platform. + typedef enum + { + Frequency1MHz = 0, ///< SPI bus frequency close to 1MHz + Frequency2MHz, ///< SPI bus frequency close to 2MHz + Frequency4MHz, ///< SPI bus frequency close to 4MHz + Frequency8MHz, ///< SPI bus frequency close to 8MHz + Frequency16MHz ///< SPI bus frequency close to 16MHz + } Frequency; + + /// \brief Defines constants for different SPI endianness + /// + /// Defines constants for different SPI endianness + /// that can be passed to setBitOrder() + /// We need to define these in a device and platform independent way, because the + /// SPI implementation is different on each platform. + typedef enum + { + BitOrderMSBFirst = 0, ///< SPI MSB first + BitOrderLSBFirst, ///< SPI LSB first + } BitOrder; + + /// Constructor + /// Creates an instance of an abstract SPI interface. + /// Do not use this contructor directly: you must instead use on of the concrete subclasses provided + /// such as RHHardwareSPI or RHSoftwareSPI + /// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency + /// is mapped to the closest available bus frequency on the platform. + /// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or + /// RHGenericSPI::BitOrderLSBFirst. + /// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode + RHGenericSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0); + + /// Transfer a single octet to and from the SPI interface + /// \param[in] data The octet to send + /// \return The octet read from SPI while the data octet was sent + virtual uint8_t transfer(uint8_t data) = 0; + +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + /// Transfer up to 2 bytes on the SPI interface + /// \param[in] byte0 The first byte to be sent on the SPI interface + /// \param[in] byte1 The second byte to be sent on the SPI interface + /// \return The second byte clocked in as the second byte is sent. + virtual uint8_t transfer2B(uint8_t byte0, uint8_t byte1) = 0; + + /// Read a number of bytes on the SPI interface from an NRF device + /// \param[in] reg The NRF device register to read + /// \param[out] dest The buffer to hold the bytes read + /// \param[in] len The number of bytes to read + /// \return The NRF status byte + virtual uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) = 0; + + /// Wrte a number of bytes on the SPI interface to an NRF device + /// \param[in] reg The NRF device register to read + /// \param[out] src The buffer to hold the bytes write + /// \param[in] len The number of bytes to write + /// \return The NRF status byte + virtual uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) = 0; + +#endif + + /// SPI Configuration methods + /// Enable SPI interrupts (if supported) + /// This can be used in an SPI slave to indicate when an SPI message has been received + virtual void attachInterrupt() {}; + + /// Disable SPI interrupts (if supported) + /// This can be used to diable the SPI interrupt in slaves where that is supported. + virtual void detachInterrupt() {}; + + /// Initialise the SPI library. + /// Call this after configuring and before using the SPI library + virtual void begin() = 0; + + /// Disables the SPI bus (leaving pin modes unchanged). + /// Call this after you have finished using the SPI interface + virtual void end() = 0; + + /// Sets the bit order the SPI interface will use + /// Sets the order of the bits shifted out of and into the SPI bus, either + /// LSBFIRST (least-significant bit first) or MSBFIRST (most-significant bit first). + /// \param[in] bitOrder Bit order to be used: one of RHGenericSPI::BitOrder + virtual void setBitOrder(BitOrder bitOrder); + + /// Sets the SPI data mode: that is, clock polarity and phase. + /// See the Wikipedia article on SPI for details. + /// \param[in] dataMode The mode to use: one of RHGenericSPI::DataMode + virtual void setDataMode(DataMode dataMode); + + /// Sets the SPI clock divider relative to the system clock. + /// On AVR based boards, the dividers available are 2, 4, 8, 16, 32, 64 or 128. + /// The default setting is SPI_CLOCK_DIV4, which sets the SPI clock to one-quarter + /// the frequency of the system clock (4 Mhz for the boards at 16 MHz). + /// \param[in] frequency The data rate to use: one of RHGenericSPI::Frequency + virtual void setFrequency(Frequency frequency); + + /// Signal the start of an SPI transaction that must not be interrupted by other SPI actions + /// In subclasses that support transactions this will ensure that other SPI transactions + /// are blocked until this one is completed by endTransaction(). + /// Base does nothing + /// Might be overridden in subclass + virtual void beginTransaction(){} + + /// Signal the end of an SPI transaction + /// Base does nothing + /// Might be overridden in subclass + virtual void endTransaction(){} + + /// Specify the interrupt number of the interrupt that will use SPI transactions + /// Tells the SPI support software that SPI transactions will occur with the interrupt + /// handler assocated with interruptNumber + /// Base does nothing + /// Might be overridden in subclass + /// \param[in] interruptNumber The number of the interrupt + virtual void usingInterrupt(uint8_t interruptNumber){ + (void)interruptNumber; + } + +protected: + + /// The configure SPI Bus frequency, one of RHGenericSPI::Frequency + Frequency _frequency; // Bus frequency, one of RHGenericSPI::Frequency + + /// Bit order, one of RHGenericSPI::BitOrder + BitOrder _bitOrder; + + /// SPI bus mode, one of RHGenericSPI::DataMode + DataMode _dataMode; +}; +#endif diff --git a/src/rf95/RHHardwareSPI.cpp b/src/rf95/RHHardwareSPI.cpp new file mode 100644 index 000000000..b4b1f6bd6 --- /dev/null +++ b/src/rf95/RHHardwareSPI.cpp @@ -0,0 +1,499 @@ +// RHHardwareSPI.cpp +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHHardwareSPI.cpp,v 1.25 2020/01/05 07:02:23 mikem Exp mikem $ + +#include + +#ifdef RH_HAVE_HARDWARE_SPI + +// Declare a single default instance of the hardware SPI interface class +RHHardwareSPI hardware_spi; + + +#if (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc +// Declare an SPI interface to use +HardwareSPI SPI(1); +#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 Discovery +// Declare an SPI interface to use +HardwareSPI SPI(1); +#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) // Mongoose OS platform +HardwareSPI SPI(1); +#endif + +// Arduino Due has default SPI pins on central SPI headers, and not on 10, 11, 12, 13 +// as per other Arduinos +// http://21stdigitalhome.blogspot.com.au/2013/02/arduino-due-hardware-spi.html +#if defined (__arm__) && !defined(CORE_TEENSY) && !defined(SPI_CLOCK_DIV16) && !defined(RH_PLATFORM_NRF52) + // Arduino Due in 1.5.5 has no definitions for SPI dividers + // SPI clock divider is based on MCK of 84MHz + #define SPI_CLOCK_DIV16 (VARIANT_MCK/84000000) // 1MHz + #define SPI_CLOCK_DIV8 (VARIANT_MCK/42000000) // 2MHz + #define SPI_CLOCK_DIV4 (VARIANT_MCK/21000000) // 4MHz + #define SPI_CLOCK_DIV2 (VARIANT_MCK/10500000) // 8MHz + #define SPI_CLOCK_DIV1 (VARIANT_MCK/5250000) // 16MHz +#endif + +RHHardwareSPI::RHHardwareSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode) + : + RHGenericSPI(frequency, bitOrder, dataMode) +{ +} + +uint8_t RHHardwareSPI::transfer(uint8_t data) +{ + return SPI.transfer(data); +} + +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) +uint8_t RHHardwareSPI::transfer2B(uint8_t byte0, uint8_t byte1) +{ + return SPI.transfer2B(byte0, byte1); +} + +uint8_t RHHardwareSPI::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) +{ + return SPI.spiBurstRead(reg, dest, len); +} + +uint8_t RHHardwareSPI::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) +{ + uint8_t status = SPI.spiBurstWrite(reg, src, len); + return status; +} +#endif + +void RHHardwareSPI::attachInterrupt() +{ +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO || RH_PLATFORM == RH_PLATFORM_NRF52) + SPI.attachInterrupt(); +#endif +} + +void RHHardwareSPI::detachInterrupt() +{ +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO || RH_PLATFORM == RH_PLATFORM_NRF52) + SPI.detachInterrupt(); +#endif +} + +void RHHardwareSPI::begin() +{ +#if defined(SPI_HAS_TRANSACTION) + // Perhaps this is a uniform interface for SPI? + // Currently Teensy and ESP32 only + uint32_t frequency; + if (_frequency == Frequency16MHz) + frequency = 16000000; + else if (_frequency == Frequency8MHz) + frequency = 8000000; + else if (_frequency == Frequency4MHz) + frequency = 4000000; + else if (_frequency == Frequency2MHz) + frequency = 2000000; + else + frequency = 1000000; + +#if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))) || defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32) || defined(NRF52) + // Arduino Due in 1.5.5 has its own BitOrder :-( + // So too does Arduino Zero + // So too does rogerclarkmelbourne/Arduino_STM32 + ::BitOrder bitOrder; +#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA) + ::BitOrder bitOrder; +#else + uint8_t bitOrder; +#endif + + if (_bitOrder == BitOrderLSBFirst) + bitOrder = LSBFIRST; + else + bitOrder = MSBFIRST; + + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE3; + else + dataMode = SPI_MODE0; + + // Save the settings for use in transactions + _settings = SPISettings(frequency, bitOrder, dataMode); + SPI.begin(); + +#else // SPI_HAS_TRANSACTION + + // Sigh: there are no common symbols for some of these SPI options across all platforms +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE || RH_PLATFORM == RH_PLATFORM_NRF52) + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE3; + else + dataMode = SPI_MODE0; +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY) + // Temporary work-around due to problem where avr_emulation.h does not work properly for the setDataMode() cal + SPCR &= ~SPI_MODE_MASK; +#else + #if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && defined(ARDUINO_ARCH_SAMD)) || defined(ARDUINO_ARCH_NRF52) + // Zero requires begin() before anything else :-) + SPI.begin(); + #endif + + SPI.setDataMode(dataMode); +#endif + +#if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))) || defined(ARDUINO_ARCH_NRF52) || defined (ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32) + // Arduino Due in 1.5.5 has its own BitOrder :-( + // So too does Arduino Zero + // So too does rogerclarkmelbourne/Arduino_STM32 + ::BitOrder bitOrder; +#else + uint8_t bitOrder; +#endif + if (_bitOrder == BitOrderLSBFirst) + bitOrder = LSBFIRST; + else + bitOrder = MSBFIRST; + SPI.setBitOrder(bitOrder); + uint8_t divider; + switch (_frequency) + { + case Frequency1MHz: + default: +#if F_CPU == 8000000 + divider = SPI_CLOCK_DIV8; +#else + divider = SPI_CLOCK_DIV16; +#endif + break; + + case Frequency2MHz: +#if F_CPU == 8000000 + divider = SPI_CLOCK_DIV4; +#else + divider = SPI_CLOCK_DIV8; +#endif + break; + + case Frequency4MHz: +#if F_CPU == 8000000 + divider = SPI_CLOCK_DIV2; +#else + divider = SPI_CLOCK_DIV4; +#endif + break; + + case Frequency8MHz: + divider = SPI_CLOCK_DIV2; // 4MHz on an 8MHz Arduino + break; + + case Frequency16MHz: + divider = SPI_CLOCK_DIV2; // Not really 16MHz, only 8MHz. 4MHz on an 8MHz Arduino + break; + + } + SPI.setClockDivider(divider); + SPI.begin(); + // Teensy requires it to be set _after_ begin() + SPI.setClockDivider(divider); + +#elif (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc + spi_mode dataMode; + // Hmmm, if we do this as a switch, GCC on maple gets v confused! + if (_dataMode == DataMode0) + dataMode = SPI_MODE_0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE_1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE_2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE_3; + else + dataMode = SPI_MODE_0; + + uint32 bitOrder; + if (_bitOrder == BitOrderLSBFirst) + bitOrder = LSBFIRST; + else + bitOrder = MSBFIRST; + + SPIFrequency frequency; // Yes, I know these are not exact equivalents. + switch (_frequency) + { + case Frequency1MHz: + default: + frequency = SPI_1_125MHZ; + break; + + case Frequency2MHz: + frequency = SPI_2_25MHZ; + break; + + case Frequency4MHz: + frequency = SPI_4_5MHZ; + break; + + case Frequency8MHz: + frequency = SPI_9MHZ; + break; + + case Frequency16MHz: + frequency = SPI_18MHZ; + break; + + } + SPI.begin(frequency, bitOrder, dataMode); + +#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 discovery + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE3; + else + dataMode = SPI_MODE0; + + uint32_t bitOrder; + if (_bitOrder == BitOrderLSBFirst) + bitOrder = LSBFIRST; + else + bitOrder = MSBFIRST; + + SPIFrequency frequency; // Yes, I know these are not exact equivalents. + switch (_frequency) + { + case Frequency1MHz: + default: + frequency = SPI_1_3125MHZ; + break; + + case Frequency2MHz: + frequency = SPI_2_625MHZ; + break; + + case Frequency4MHz: + frequency = SPI_5_25MHZ; + break; + + case Frequency8MHz: + frequency = SPI_10_5MHZ; + break; + + case Frequency16MHz: + frequency = SPI_21_0MHZ; + break; + + } + SPI.begin(frequency, bitOrder, dataMode); + +#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Photon + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = SPI_MODE3; + else + dataMode = SPI_MODE0; + SPI.setDataMode(dataMode); + if (_bitOrder == BitOrderLSBFirst) + SPI.setBitOrder(LSBFIRST); + else + SPI.setBitOrder(MSBFIRST); + + switch (_frequency) + { + case Frequency1MHz: + default: + SPI.setClockSpeed(1, MHZ); + break; + + case Frequency2MHz: + SPI.setClockSpeed(2, MHZ); + break; + + case Frequency4MHz: + SPI.setClockSpeed(4, MHZ); + break; + + case Frequency8MHz: + SPI.setClockSpeed(8, MHZ); + break; + + case Frequency16MHz: + SPI.setClockSpeed(16, MHZ); + break; + } + +// SPI.setClockDivider(SPI_CLOCK_DIV4); // 72MHz / 4MHz = 18MHz +// SPI.setClockSpeed(1, MHZ); + SPI.begin(); + +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) + // Requires SPI driver for ESP8266 from https://github.com/esp8266/Arduino/tree/master/libraries/SPI + // Which ppears to be in Arduino Board Manager ESP8266 Community version 2.1.0 + // Contributed by David Skinner + // begin comes first + SPI.begin(); + + // datamode + switch ( _dataMode ) + { + case DataMode1: + SPI.setDataMode ( SPI_MODE1 ); + break; + case DataMode2: + SPI.setDataMode ( SPI_MODE2 ); + break; + case DataMode3: + SPI.setDataMode ( SPI_MODE3 ); + break; + case DataMode0: + default: + SPI.setDataMode ( SPI_MODE0 ); + break; + } + + // bitorder + SPI.setBitOrder(_bitOrder == BitOrderLSBFirst ? LSBFIRST : MSBFIRST); + + // frequency (this sets the divider) + switch (_frequency) + { + case Frequency1MHz: + default: + SPI.setFrequency(1000000); + break; + case Frequency2MHz: + SPI.setFrequency(2000000); + break; + case Frequency4MHz: + SPI.setFrequency(4000000); + break; + case Frequency8MHz: + SPI.setFrequency(8000000); + break; + case Frequency16MHz: + SPI.setFrequency(16000000); + break; + } + +#elif (RH_PLATFORM == RH_PLATFORM_RASPI) // Raspberry PI + uint8_t dataMode; + if (_dataMode == DataMode0) + dataMode = BCM2835_SPI_MODE0; + else if (_dataMode == DataMode1) + dataMode = BCM2835_SPI_MODE1; + else if (_dataMode == DataMode2) + dataMode = BCM2835_SPI_MODE2; + else if (_dataMode == DataMode3) + dataMode = BCM2835_SPI_MODE3; + + uint8_t bitOrder; + if (_bitOrder == BitOrderLSBFirst) + bitOrder = BCM2835_SPI_BIT_ORDER_LSBFIRST; + else + bitOrder = BCM2835_SPI_BIT_ORDER_MSBFIRST; + + uint32_t divider; + switch (_frequency) + { + case Frequency1MHz: + default: + divider = BCM2835_SPI_CLOCK_DIVIDER_256; + break; + case Frequency2MHz: + divider = BCM2835_SPI_CLOCK_DIVIDER_128; + break; + case Frequency4MHz: + divider = BCM2835_SPI_CLOCK_DIVIDER_64; + break; + case Frequency8MHz: + divider = BCM2835_SPI_CLOCK_DIVIDER_32; + break; + case Frequency16MHz: + divider = BCM2835_SPI_CLOCK_DIVIDER_16; + break; + } + SPI.begin(divider, bitOrder, dataMode); +#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + uint8_t dataMode = SPI_MODE0; + uint32_t frequency = 4000000; //!!! ESP32/NRF902 works ok at 4MHz but not at 8MHz SPI clock. + uint32_t bitOrder = MSBFIRST; + + if (_dataMode == DataMode0) { + dataMode = SPI_MODE0; + } else if (_dataMode == DataMode1) { + dataMode = SPI_MODE1; + } else if (_dataMode == DataMode2) { + dataMode = SPI_MODE2; + } else if (_dataMode == DataMode3) { + dataMode = SPI_MODE3; + } + + if (_bitOrder == BitOrderLSBFirst) { + bitOrder = LSBFIRST; + } + + if (_frequency == Frequency4MHz) + frequency = 4000000; + else if (_frequency == Frequency2MHz) + frequency = 2000000; + else + frequency = 1000000; + + SPI.begin(frequency, bitOrder, dataMode); +#else + #warning RHHardwareSPI does not support this platform yet. Consider adding it and contributing a patch. +#endif + +#endif // SPI_HAS_TRANSACTION +} + +void RHHardwareSPI::end() +{ + return SPI.end(); +} + +void RHHardwareSPI::beginTransaction() +{ +#if defined(SPI_HAS_TRANSACTION) + SPI.beginTransaction(_settings); +#endif +} + +void RHHardwareSPI::endTransaction() +{ +#if defined(SPI_HAS_TRANSACTION) + SPI.endTransaction(); +#endif +} + +void RHHardwareSPI::usingInterrupt(uint8_t interrupt) +{ +#if defined(SPI_HAS_TRANSACTION) && !defined(RH_MISSING_SPIUSINGINTERRUPT) + SPI.usingInterrupt(interrupt); +#endif + (void)interrupt; +} + +#endif diff --git a/src/rf95/RHHardwareSPI.h b/src/rf95/RHHardwareSPI.h new file mode 100644 index 000000000..3238ff378 --- /dev/null +++ b/src/rf95/RHHardwareSPI.h @@ -0,0 +1,116 @@ +// RHHardwareSPI.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHHardwareSPI.h,v 1.12 2020/01/05 07:02:23 mikem Exp mikem $ + +#ifndef RHHardwareSPI_h +#define RHHardwareSPI_h + +#include + +///////////////////////////////////////////////////////////////////// +/// \class RHHardwareSPI RHHardwareSPI.h +/// \brief Encapsulate a hardware SPI bus interface +/// +/// This concrete subclass of GenericSPIClass encapsulates the standard Arduino hardware and other +/// hardware SPI interfaces. +/// +/// SPI transactions are supported in development environments that support it with SPI_HAS_TRANSACTION. +class RHHardwareSPI : public RHGenericSPI +{ +#ifdef RH_HAVE_HARDWARE_SPI +public: + /// Constructor + /// Creates an instance of a hardware SPI interface, using whatever SPI hardware is available on + /// your processor platform. On Arduino and Uno32, uses SPI. On Maple, uses HardwareSPI. + /// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency + /// is mapped to the closest available bus frequency on the platform. + /// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or + /// RHGenericSPI::BitOrderLSBFirst. + /// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode + RHHardwareSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0); + + /// Transfer a single octet to and from the SPI interface + /// \param[in] data The octet to send + /// \return The octet read from SPI while the data octet was sent + uint8_t transfer(uint8_t data); + +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + /// Transfer (write) 2 bytes on the SPI interface to an NRF device + /// \param[in] byte0 The first byte to be sent on the SPI interface + /// \param[in] byte1 The second byte to be sent on the SPI interface + /// \return The second byte clocked in as the second byte is sent. + uint8_t transfer2B(uint8_t byte0, uint8_t byte1); + + /// Read a number of bytes on the SPI interface from an NRF device + /// \param[in] reg The NRF device register to read + /// \param[out] dest The buffer to hold the bytes read + /// \param[in] len The number of bytes to read + /// \return The NRF status byte + uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); + + /// Wrte a number of bytes on the SPI interface to an NRF device + /// \param[in] reg The NRF device register to read + /// \param[out] src The buffer to hold the bytes write + /// \param[in] len The number of bytes to write + /// \return The NRF status byte + uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); + +#endif + + // SPI Configuration methods + /// Enable SPI interrupts + /// This can be used in an SPI slave to indicate when an SPI message has been received + /// It will cause the SPI_STC_vect interrupt vectr to be executed + void attachInterrupt(); + + /// Disable SPI interrupts + /// This can be used to diable the SPI interrupt in slaves where that is supported. + void detachInterrupt(); + + /// Initialise the SPI library + /// Call this after configuring the SPI interface and before using it to transfer data. + /// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high. + void begin(); + + /// Disables the SPI bus (leaving pin modes unchanged). + /// Call this after you have finished using the SPI interface. + void end(); +#else + // not supported on ATTiny etc + uint8_t transfer(uint8_t /*data*/) {return 0;} + void begin(){} + void end(){} + +#endif + + /// Signal the start of an SPI transaction that must not be interrupted by other SPI actions + /// In subclasses that support transactions this will ensure that other SPI transactions + /// are blocked until this one is completed by endTransaction(). + /// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION. + virtual void beginTransaction(); + + /// Signal the end of an SPI transaction + /// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION. + virtual void endTransaction(); + + /// Specify the interrupt number of the interrupt that will use SPI transactions + /// Tells the SPI support software that SPI transactions will occur with the interrupt + /// handler assocated with interruptNumber + /// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION. + /// \param[in] interruptNumber The number of the interrupt + virtual void usingInterrupt(uint8_t interruptNumber); + +protected: + +#if defined(SPI_HAS_TRANSACTION) + // Storage for SPI settings used in SPI transactions + SPISettings _settings; +#endif +}; + +// Built in default instance +extern RHHardwareSPI hardware_spi; + +#endif diff --git a/src/rf95/RHNRFSPIDriver.cpp b/src/rf95/RHNRFSPIDriver.cpp new file mode 100644 index 000000000..a1e8f0da7 --- /dev/null +++ b/src/rf95/RHNRFSPIDriver.cpp @@ -0,0 +1,137 @@ +// RHNRFSPIDriver.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RHNRFSPIDriver.cpp,v 1.5 2020/01/05 07:02:23 mikem Exp mikem $ + +#include + +RHNRFSPIDriver::RHNRFSPIDriver(uint8_t slaveSelectPin, RHGenericSPI& spi) + : + _spi(spi), + _slaveSelectPin(slaveSelectPin) +{ +} + +bool RHNRFSPIDriver::init() +{ + // start the SPI library with the default speeds etc: + // On Arduino Due this defaults to SPI1 on the central group of 6 SPI pins + _spi.begin(); + + // Initialise the slave select pin + // On Maple, this must be _after_ spi.begin + pinMode(_slaveSelectPin, OUTPUT); + digitalWrite(_slaveSelectPin, HIGH); + + delay(100); + return true; +} + +// Low level commands for interfacing with the device +uint8_t RHNRFSPIDriver::spiCommand(uint8_t command) +{ + uint8_t status; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + status = _spi.transfer(command); +#else + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(command); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); +#endif + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RHNRFSPIDriver::spiRead(uint8_t reg) +{ + uint8_t val; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + val = _spi.transfer2B(reg, 0); // Send the address, discard the status, The written value is ignored, reg value is read +#else + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + _spi.transfer(reg); // Send the address, discard the status + val = _spi.transfer(0); // The written value is ignored, reg value is read + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); +#endif + ATOMIC_BLOCK_END; + return val; +} + +uint8_t RHNRFSPIDriver::spiWrite(uint8_t reg, uint8_t val) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + status = _spi.transfer2B(reg, val); +#else + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(reg); // Send the address + _spi.transfer(val); // New value follows +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY) + // Sigh: some devices, such as MRF89XA dont work properly on Teensy 3.1: + // At 1MHz, the clock returns low _after_ slave select goes high, which prevents SPI + // write working. This delay gixes time for the clock to return low. +delayMicroseconds(5); +#endif + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); +#endif + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RHNRFSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + status = _spi.spiBurstRead(reg, dest, len); +#else + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(reg); // Send the start address + while (len--) + *dest++ = _spi.transfer(0); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); +#endif + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RHNRFSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; +#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + status = _spi.spiBurstWrite(reg, src, len); +#else + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(reg); // Send the start address + while (len--) + _spi.transfer(*src++); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); +#endif + ATOMIC_BLOCK_END; + return status; +} + +void RHNRFSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin) +{ + _slaveSelectPin = slaveSelectPin; +} + +void RHNRFSPIDriver::spiUsingInterrupt(uint8_t interruptNumber) +{ + _spi.usingInterrupt(interruptNumber); +} + diff --git a/src/rf95/RHNRFSPIDriver.h b/src/rf95/RHNRFSPIDriver.h new file mode 100644 index 000000000..b93557d78 --- /dev/null +++ b/src/rf95/RHNRFSPIDriver.h @@ -0,0 +1,101 @@ +// RHNRFSPIDriver.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RHNRFSPIDriver.h,v 1.5 2017/11/06 00:04:08 mikem Exp $ + +#ifndef RHNRFSPIDriver_h +#define RHNRFSPIDriver_h + +#include +#include + +class RHGenericSPI; + +///////////////////////////////////////////////////////////////////// +/// \class RHNRFSPIDriver RHNRFSPIDriver.h +/// \brief Base class for RadioHead drivers that use the SPI bus +/// to communicate with its NRF family transport hardware. +/// +/// This class can be subclassed by Drivers that require to use the SPI bus. +/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform) +/// of the bitbanged RHSoftwareSPI class. The dfault behaviour is to use a pre-instantiated built-in RHHardwareSPI +/// interface. +/// +/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts +/// are disabled during access. +/// +/// The read and write routines use SPI conventions as used by Nordic NRF radios and otehr devices, +/// but these can be overriden +/// in subclasses if necessary. +/// +/// Application developers are not expected to instantiate this class directly: +/// it is for the use of Driver developers. +class RHNRFSPIDriver : public RHGenericDriver +{ +public: + /// Constructor + /// \param[in] slaveSelectPin The controller pin to use to select the desired SPI device. This pin will be driven LOW + /// during SPI communications with the SPI device that uis iused by this Driver. + /// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface. + RHNRFSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + bool init(); + + /// Sends a single command to the device + /// \param[in] command The command code to send to the device. + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiCommand(uint8_t command); + + /// Reads a single register from the SPI device + /// \param[in] reg Register number + /// \return The value of the register + uint8_t spiRead(uint8_t reg); + + /// Writes a single byte to the SPI device + /// \param[in] reg Register number + /// \param[in] val The value to write + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiWrite(uint8_t reg, uint8_t val); + + /// Reads a number of consecutive registers from the SPI device using burst read mode + /// \param[in] reg Register number of the first register + /// \param[in] dest Array to write the register values to. Must be at least len bytes + /// \param[in] len Number of bytes to read + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); + + /// Write a number of consecutive registers using burst write mode + /// \param[in] reg Register number of the first register + /// \param[in] src Array of new register values to write. Must be at least len bytes + /// \param[in] len Number of bytes to write + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); + + /// Set or change the pin to be used for SPI slave select. + /// This can be called at any time to change the + /// pin that will be used for slave select in subsquent SPI operations. + /// \param[in] slaveSelectPin The pin to use + void setSlaveSelectPin(uint8_t slaveSelectPin); + + /// Set the SPI interrupt number + /// If SPI transactions can occur within an interrupt, tell the low level SPI + /// interface which interrupt is used + /// \param[in] interruptNumber the interrupt number + void spiUsingInterrupt(uint8_t interruptNumber); + +protected: + /// Reference to the RHGenericSPI instance to use to trasnfer data with teh SPI device + RHGenericSPI& _spi; + + /// The pin number of the Slave Select pin that is used to select the desired device. + uint8_t _slaveSelectPin; +}; + +#endif diff --git a/src/rf95/RHSPIDriver.cpp b/src/rf95/RHSPIDriver.cpp new file mode 100644 index 000000000..25bae8a08 --- /dev/null +++ b/src/rf95/RHSPIDriver.cpp @@ -0,0 +1,95 @@ +// RHSPIDriver.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RHSPIDriver.cpp,v 1.11 2017/11/06 00:04:08 mikem Exp $ + +#include + +RHSPIDriver::RHSPIDriver(uint8_t slaveSelectPin, RHGenericSPI& spi) + : + _spi(spi), + _slaveSelectPin(slaveSelectPin) +{ +} + +bool RHSPIDriver::init() +{ + // start the SPI library with the default speeds etc: + // On Arduino Due this defaults to SPI1 on the central group of 6 SPI pins + _spi.begin(); + + // Initialise the slave select pin + // On Maple, this must be _after_ spi.begin + pinMode(_slaveSelectPin, OUTPUT); + digitalWrite(_slaveSelectPin, HIGH); + + delay(100); + return true; +} + +uint8_t RHSPIDriver::spiRead(uint8_t reg) +{ + uint8_t val; + ATOMIC_BLOCK_START; + digitalWrite(_slaveSelectPin, LOW); + _spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the address with the write mask off + val = _spi.transfer(0); // The written value is ignored, reg value is read + digitalWrite(_slaveSelectPin, HIGH); + ATOMIC_BLOCK_END; + return val; +} + +uint8_t RHSPIDriver::spiWrite(uint8_t reg, uint8_t val) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the address with the write mask on + _spi.transfer(val); // New value follows + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RHSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the start address with the write mask off + while (len--) + *dest++ = _spi.transfer(0); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RHSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the start address with the write mask on + while (len--) + _spi.transfer(*src++); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + return status; +} + +void RHSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin) +{ + _slaveSelectPin = slaveSelectPin; +} + +void RHSPIDriver::spiUsingInterrupt(uint8_t interruptNumber) +{ + _spi.usingInterrupt(interruptNumber); +} + diff --git a/src/rf95/RHSPIDriver.h b/src/rf95/RHSPIDriver.h new file mode 100644 index 000000000..fdd5de410 --- /dev/null +++ b/src/rf95/RHSPIDriver.h @@ -0,0 +1,100 @@ +// RHSPIDriver.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RHSPIDriver.h,v 1.14 2019/09/06 04:40:40 mikem Exp $ + +#ifndef RHSPIDriver_h +#define RHSPIDriver_h + +#include +#include + +// This is the bit in the SPI address that marks it as a write +#define RH_SPI_WRITE_MASK 0x80 + +class RHGenericSPI; + +///////////////////////////////////////////////////////////////////// +/// \class RHSPIDriver RHSPIDriver.h +/// \brief Base class for RadioHead drivers that use the SPI bus +/// to communicate with its transport hardware. +/// +/// This class can be subclassed by Drivers that require to use the SPI bus. +/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform) +/// of the bitbanged RHSoftwareSPI class. The default behaviour is to use a pre-instantiated built-in RHHardwareSPI +/// interface. +/// +/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts +/// are disabled during access. +/// +/// The read and write routines implement commonly used SPI conventions: specifically that the MSB +/// of the first byte transmitted indicates that it is a write and the remaining bits indicate the rehgister to access) +/// This can be overriden +/// in subclasses if necessaryor an alternative class, RHNRFSPIDriver can be used to access devices like +/// Nordic NRF series radios, which have different requirements. +/// +/// Application developers are not expected to instantiate this class directly: +/// it is for the use of Driver developers. +class RHSPIDriver : public RHGenericDriver +{ +public: + /// Constructor + /// \param[in] slaveSelectPin The controler pin to use to select the desired SPI device. This pin will be driven LOW + /// during SPI communications with the SPI device that uis iused by this Driver. + /// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface. + RHSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi); + + /// Initialise the Driver transport hardware and software. + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + bool init(); + + /// Reads a single register from the SPI device + /// \param[in] reg Register number + /// \return The value of the register + uint8_t spiRead(uint8_t reg); + + /// Writes a single byte to the SPI device + /// \param[in] reg Register number + /// \param[in] val The value to write + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiWrite(uint8_t reg, uint8_t val); + + /// Reads a number of consecutive registers from the SPI device using burst read mode + /// \param[in] reg Register number of the first register + /// \param[in] dest Array to write the register values to. Must be at least len bytes + /// \param[in] len Number of bytes to read + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); + + /// Write a number of consecutive registers using burst write mode + /// \param[in] reg Register number of the first register + /// \param[in] src Array of new register values to write. Must be at least len bytes + /// \param[in] len Number of bytes to write + /// \return Some devices return a status byte during the first data transfer. This byte is returned. + /// it may or may not be meaningfule depending on the the type of device being accessed. + uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); + + /// Set or change the pin to be used for SPI slave select. + /// This can be called at any time to change the + /// pin that will be used for slave select in subsquent SPI operations. + /// \param[in] slaveSelectPin The pin to use + void setSlaveSelectPin(uint8_t slaveSelectPin); + + /// Set the SPI interrupt number + /// If SPI transactions can occur within an interrupt, tell the low level SPI + /// interface which interrupt is used + /// \param[in] interruptNumber the interrupt number + void spiUsingInterrupt(uint8_t interruptNumber); + + protected: + /// Reference to the RHGenericSPI instance to use to transfer data with the SPI device + RHGenericSPI& _spi; + + /// The pin number of the Slave Select pin that is used to select the desired device. + uint8_t _slaveSelectPin; +}; + +#endif diff --git a/src/rf95/RHSoftwareSPI.cpp b/src/rf95/RHSoftwareSPI.cpp new file mode 100644 index 000000000..f1959cb06 --- /dev/null +++ b/src/rf95/RHSoftwareSPI.cpp @@ -0,0 +1,166 @@ +// SoftwareSPI.cpp +// Author: Chris Lapa (chris@lapa.com.au) +// Copyright (C) 2014 Chris Lapa +// Contributed by Chris Lapa + +#include + +RHSoftwareSPI::RHSoftwareSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode) + : + RHGenericSPI(frequency, bitOrder, dataMode) +{ + setPins(12, 11, 13); +} + +// Caution: on Arduino Uno and many other CPUs, digitalWrite is quite slow, taking about 4us +// digitalWrite is also slow, taking about 3.5us +// resulting in very slow SPI bus speeds using this technique, up to about 120us per octet of transfer +uint8_t RHSoftwareSPI::transfer(uint8_t data) +{ + uint8_t readData; + uint8_t writeData; + uint8_t builtReturn; + uint8_t mask; + + if (_bitOrder == BitOrderMSBFirst) + { + mask = 0x80; + } + else + { + mask = 0x01; + } + builtReturn = 0; + readData = 0; + + for (uint8_t count=0; count<8; count++) + { + if (data & mask) + { + writeData = HIGH; + } + else + { + writeData = LOW; + } + + if (_clockPhase == 1) + { + // CPHA=1, miso/mosi changing state now + digitalWrite(_mosi, writeData); + digitalWrite(_sck, ~_clockPolarity); + delayPeriod(); + + // CPHA=1, miso/mosi stable now + readData = digitalRead(_miso); + digitalWrite(_sck, _clockPolarity); + delayPeriod(); + } + else + { + // CPHA=0, miso/mosi changing state now + digitalWrite(_mosi, writeData); + digitalWrite(_sck, _clockPolarity); + delayPeriod(); + + // CPHA=0, miso/mosi stable now + readData = digitalRead(_miso); + digitalWrite(_sck, ~_clockPolarity); + delayPeriod(); + } + + if (_bitOrder == BitOrderMSBFirst) + { + mask >>= 1; + builtReturn |= (readData << (7 - count)); + } + else + { + mask <<= 1; + builtReturn |= (readData << count); + } + } + + digitalWrite(_sck, _clockPolarity); + + return builtReturn; +} + +/// Initialise the SPI library +void RHSoftwareSPI::begin() +{ + if (_dataMode == DataMode0 || + _dataMode == DataMode1) + { + _clockPolarity = LOW; + } + else + { + _clockPolarity = HIGH; + } + + if (_dataMode == DataMode0 || + _dataMode == DataMode2) + { + _clockPhase = 0; + } + else + { + _clockPhase = 1; + } + digitalWrite(_sck, _clockPolarity); + + // Caution: these counts assume that digitalWrite is very fast, which is usually not true + switch (_frequency) + { + case Frequency1MHz: + _delayCounts = 8; + break; + + case Frequency2MHz: + _delayCounts = 4; + break; + + case Frequency4MHz: + _delayCounts = 2; + break; + + case Frequency8MHz: + _delayCounts = 1; + break; + + case Frequency16MHz: + _delayCounts = 0; + break; + } +} + +/// Disables the SPI bus usually, in this case +/// there is no hardware controller to disable. +void RHSoftwareSPI::end() { } + +/// Sets the pins used by this SoftwareSPIClass instance. +/// \param[in] miso master in slave out pin used +/// \param[in] mosi master out slave in pin used +/// \param[in] sck clock pin used +void RHSoftwareSPI::setPins(uint8_t miso, uint8_t mosi, uint8_t sck) +{ + _miso = miso; + _mosi = mosi; + _sck = sck; + + pinMode(_miso, INPUT); + pinMode(_mosi, OUTPUT); + pinMode(_sck, OUTPUT); + digitalWrite(_sck, _clockPolarity); +} + + +void RHSoftwareSPI::delayPeriod() +{ + for (uint8_t count = 0; count < _delayCounts; count++) + { + __asm__ __volatile__ ("nop"); + } +} + diff --git a/src/rf95/RHSoftwareSPI.h b/src/rf95/RHSoftwareSPI.h new file mode 100644 index 000000000..5e7e1a5dd --- /dev/null +++ b/src/rf95/RHSoftwareSPI.h @@ -0,0 +1,90 @@ +// SoftwareSPI.h +// Author: Chris Lapa (chris@lapa.com.au) +// Copyright (C) 2014 Chris Lapa +// Contributed by Chris Lapa + +#ifndef RHSoftwareSPI_h +#define RHSoftwareSPI_h + +#include + +///////////////////////////////////////////////////////////////////// +/// \class RHSoftwareSPI RHSoftwareSPI.h +/// \brief Encapsulate a software SPI interface +/// +/// This concrete subclass of RHGenericSPI enapsulates a bit-banged software SPI interface. +/// Caution: this software SPI interface will be much slower than hardware SPI on most +/// platforms. +/// +/// SPI transactions are not supported, and associated functions do nothing. +/// +/// \par Usage +/// +/// Usage varies slightly depending on what driver you are using. +/// +/// For RF22, for example: +/// \code +/// #include +/// RHSoftwareSPI spi; +/// RH_RF22 driver(SS, 2, spi); +/// RHReliableDatagram(driver, CLIENT_ADDRESS); +/// void setup() +/// { +/// spi.setPins(6, 5, 7); // Or whatever SPI pins you need +/// .... +/// } +/// \endcode +class RHSoftwareSPI : public RHGenericSPI +{ +public: + + /// Constructor + /// Creates an instance of a bit-banged software SPI interface. + /// Sets the SPI pins to the defaults of + /// MISO = 12, MOSI = 11, SCK = 13. If you need other assigments, call setPins() before + /// calling manager.init() or driver.init(). + /// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency + /// is mapped to the closest available bus frequency on the platform. CAUTION: the achieved + /// frequency will almost certainly be very much slower on most platforms. eg on Arduino Uno, the + /// the clock rate is likely to be at best around 46kHz. + /// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or + /// RHGenericSPI::BitOrderLSBFirst. + /// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode + RHSoftwareSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0); + + /// Transfer a single octet to and from the SPI interface + /// \param[in] data The octet to send + /// \return The octet read from SPI while the data octet was sent. + uint8_t transfer(uint8_t data); + + /// Initialise the software SPI library + /// Call this after configuring the SPI interface and before using it to transfer data. + /// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high. + void begin(); + + /// Disables the SPI bus usually, in this case + /// there is no hardware controller to disable. + void end(); + + /// Sets the pins used by this SoftwareSPIClass instance. + /// The defaults are: MISO = 12, MOSI = 11, SCK = 13. + /// \param[in] miso master in slave out pin used + /// \param[in] mosi master out slave in pin used + /// \param[in] sck clock pin used + void setPins(uint8_t miso = 12, uint8_t mosi = 11, uint8_t sck = 13); + +private: + + /// Delay routine for bus timing. + void delayPeriod(); + +private: + uint8_t _miso; + uint8_t _mosi; + uint8_t _sck; + uint8_t _delayCounts; + uint8_t _clockPolarity; + uint8_t _clockPhase; +}; + +#endif diff --git a/src/rf95/RH_RF95.cpp b/src/rf95/RH_RF95.cpp new file mode 100644 index 000000000..7d7bf0fd9 --- /dev/null +++ b/src/rf95/RH_RF95.cpp @@ -0,0 +1,668 @@ +// RH_RF95.cpp +// +// Copyright (C) 2011 Mike McCauley +// $Id: RH_RF95.cpp,v 1.22 2020/01/05 07:02:23 mikem Exp mikem $ + +#include + +// Interrupt vectors for the 3 Arduino interrupt pins +// Each interrupt can be handled by a different instance of RH_RF95, allowing you to have +// 2 or more LORAs per Arduino +RH_RF95 *RH_RF95::_deviceForInterrupt[RH_RF95_NUM_INTERRUPTS] = {0, 0, 0}; +uint8_t RH_RF95::_interruptCount = 0; // Index into _deviceForInterrupt for next device + +// These are indexed by the values of ModemConfigChoice +// Stored in flash (program) memory to save SRAM +PROGMEM static const RH_RF95::ModemConfig MODEM_CONFIG_TABLE[] = + { + // 1d, 1e, 26 + {0x72, 0x74, 0x04}, // Bw125Cr45Sf128 (the chip default), AGC enabled + {0x92, 0x74, 0x04}, // Bw500Cr45Sf128, AGC enabled + {0x48, 0x94, 0x04}, // Bw31_25Cr48Sf512, AGC enabled + {0x78, 0xc4, 0x0c}, // Bw125Cr48Sf4096, AGC enabled + +}; + +RH_RF95::RH_RF95(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI &spi) + : RHSPIDriver(slaveSelectPin, spi), + _rxBufValid(0) +{ + _interruptPin = interruptPin; + _myInterruptIndex = 0xff; // Not allocated yet +} + +bool RH_RF95::init() +{ + if (!RHSPIDriver::init()) + return false; + + // Determine the interrupt number that corresponds to the interruptPin + int interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; +#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER + interruptNumber = _interruptPin; +#endif + + // Tell the low level SPI interface we will use SPI within this interrupt + spiUsingInterrupt(interruptNumber); + + // No way to check the device type :-( + + // Add by Adrien van den Bossche for Teensy + // ARM M4 requires the below. else pin interrupt doesn't work properly. + // On all other platforms, its innocuous, belt and braces + pinMode(_interruptPin, INPUT); + + bool isWakeFromDeepSleep = false; // true if we think we are waking from deep sleep AND the rf95 seems to have a valid configuration + + if (!isWakeFromDeepSleep) + { + // Set sleep mode, so we can also set LORA mode: + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE); + delay(10); // Wait for sleep mode to take over from say, CAD + // Check we are in sleep mode, with LORA set + if (spiRead(RH_RF95_REG_01_OP_MODE) != (RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE)) + { + // Serial.println(spiRead(RH_RF95_REG_01_OP_MODE), HEX); + return false; // No device present? + } + + // Set up FIFO + // We configure so that we can use the entire 256 byte FIFO for either receive + // or transmit, but not both at the same time + spiWrite(RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0); + spiWrite(RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0); + + // Packet format is preamble + explicit-header + payload + crc + // Explicit Header Mode + // payload is TO + FROM + ID + FLAGS + message data + // RX mode is implmented with RXCONTINUOUS + // max message data length is 255 - 4 = 251 octets + + setModeIdle(); + + // Set up default configuration + // No Sync Words in LORA mode. + setModemConfig(Bw125Cr45Sf128); // Radio default + // setModemConfig(Bw125Cr48Sf4096); // slow and reliable? + setPreambleLength(8); // Default is 8 + // An innocuous ISM frequency, same as RF22's + setFrequency(434.0); + // Lowish power + setTxPower(13); + + Serial.printf("IRQ flag mask 0x%x\n", spiRead(RH_RF95_REG_11_IRQ_FLAGS_MASK)); + } + else + { + // FIXME + // restore mode base off reading RS95 registers + + // Only let CPU enter deep sleep if RF95 is sitting waiting on a receive or is in idle or sleep. + } + + // geeksville: we do this last, because if there is an interrupt pending from during the deep sleep, this attach will cause it to be taken. + + // Set up interrupt handler + // Since there are a limited number of interrupt glue functions isr*() available, + // we can only support a limited number of devices simultaneously + // ON some devices, notably most Arduinos, the interrupt pin passed in is actuallt the + // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping + // yourself based on knwledge of what Arduino board you are running on. + if (_myInterruptIndex == 0xff) + { + // First run, no interrupt allocated yet + if (_interruptCount <= RH_RF95_NUM_INTERRUPTS) + _myInterruptIndex = _interruptCount++; + else + return false; // Too many devices, not enough interrupt vectors + } + _deviceForInterrupt[_myInterruptIndex] = this; + if (_myInterruptIndex == 0) + attachInterrupt(interruptNumber, isr0, RISING); + else if (_myInterruptIndex == 1) + attachInterrupt(interruptNumber, isr1, RISING); + else if (_myInterruptIndex == 2) + attachInterrupt(interruptNumber, isr2, RISING); + else + return false; // Too many devices, not enough interrupt vectors + + return true; +} + +void RH_RF95::prepareDeepSleep() +{ + // Determine the interrupt number that corresponds to the interruptPin + int interruptNumber = digitalPinToInterrupt(_interruptPin); + + detachInterrupt(interruptNumber); +} + +bool RH_RF95::isReceiving() +{ + // 0x0b == Look for header info valid, signal synchronized or signal detected + uint8_t reg = spiRead(RH_RF95_REG_18_MODEM_STAT) & 0x1f; + // Serial.printf("reg %x\n", reg); + return _mode == RHModeRx && (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | + RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED | + RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0; +} + + +// C++ level interrupt handler for this instance +// LORA is unusual in that it has several interrupt lines, and not a single, combined one. +// On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly +// connnected to the processor. +// We use this to get RxDone and TxDone interrupts +void RH_RF95::handleInterrupt() +{ + // Read the interrupt register + uint8_t irq_flags = spiRead(RH_RF95_REG_12_IRQ_FLAGS); + + // Note: there can be substantial latency between ISR assertion and this function being run, therefore + // multiple flags might be set. Handle them all + + // Note: we are running the chip in continuous receive mode (currently, so RX_TIMEOUT shouldn't ever occur) + bool haveRxError = irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR); + if (haveRxError) + // if (_mode == RHModeRx && irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR)) + { + _rxBad++; + clearRxBuf(); + } + + if ((irq_flags & RH_RF95_RX_DONE) && !haveRxError) + { + // Read the RegHopChannel register to check if CRC presence is signalled + // in the header. If not it might be a stray (noise) packet.* + uint8_t crc_present = spiRead(RH_RF95_REG_1C_HOP_CHANNEL) & RH_RF95_RX_PAYLOAD_CRC_IS_ON; + + spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags, required before reading fifo (according to datasheet) + + if (!crc_present) + { + _rxBad++; + clearRxBuf(); + } + else + { + // Have received a packet + uint8_t len = spiRead(RH_RF95_REG_13_RX_NB_BYTES); + + // Reset the fifo read ptr to the beginning of the packet + spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, spiRead(RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR)); + spiBurstRead(RH_RF95_REG_00_FIFO, _buf, len); + _bufLen = len; + + // Remember the last signal to noise ratio, LORA mode + // Per page 111, SX1276/77/78/79 datasheet + _lastSNR = (int8_t)spiRead(RH_RF95_REG_19_PKT_SNR_VALUE) / 4; + + // Remember the RSSI of this packet, LORA mode + // this is according to the doc, but is it really correct? + // weakest receiveable signals are reported RSSI at about -66 + _lastRssi = spiRead(RH_RF95_REG_1A_PKT_RSSI_VALUE); + // Adjust the RSSI, datasheet page 87 + if (_lastSNR < 0) + _lastRssi = _lastRssi + _lastSNR; + else + _lastRssi = (int)_lastRssi * 16 / 15; + if (_usingHFport) + _lastRssi -= 157; + else + _lastRssi -= 164; + + // We have received a message. + validateRxBuf(); + if (_rxBufValid) + setModeIdle(); // Got one + } + } + + if (irq_flags & RH_RF95_TX_DONE) + { + _txGood++; + setModeIdle(); + } + + if (_mode == RHModeCad && (irq_flags & RH_RF95_CAD_DONE)) + { + _cad = irq_flags & RH_RF95_CAD_DETECTED; + setModeIdle(); + } + + // ack all interrupts, note - we did this already in the RX_DONE case above, and we don't want to do it twice + if (!(irq_flags & RH_RF95_RX_DONE)) + { + // Sigh: on some processors, for some unknown reason, doing this only once does not actually + // clear the radio's interrupt flag. So we do it twice. Why? + // kevinh: turn this off until root cause is known, because it can cause missed interrupts! + // spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags + spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags + } +} + +// These are low level functions that call the interrupt handler for the correct +// instance of RH_RF95. +// 3 interrupts allows us to have 3 different devices +void RH_INTERRUPT_ATTR RH_RF95::isr0() +{ + if (_deviceForInterrupt[0]) + _deviceForInterrupt[0]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_RF95::isr1() +{ + if (_deviceForInterrupt[1]) + _deviceForInterrupt[1]->handleInterrupt(); +} +void RH_INTERRUPT_ATTR RH_RF95::isr2() +{ + if (_deviceForInterrupt[2]) + _deviceForInterrupt[2]->handleInterrupt(); +} + +// Check whether the latest received message is complete and uncorrupted +void RH_RF95::validateRxBuf() +{ + if (_bufLen < 4) + return; // Too short to be a real message + // Extract the 4 headers + _rxHeaderTo = _buf[0]; + _rxHeaderFrom = _buf[1]; + _rxHeaderId = _buf[2]; + _rxHeaderFlags = _buf[3]; + if (_promiscuous || + _rxHeaderTo == _thisAddress || + _rxHeaderTo == RH_BROADCAST_ADDRESS) + { + _rxGood++; + _rxBufValid = true; + } +} + +bool RH_RF95::available() +{ + if (_mode == RHModeTx) + return false; + setModeRx(); + return _rxBufValid; // Will be set by the interrupt handler when a good message is received +} + +void RH_RF95::clearRxBuf() +{ + ATOMIC_BLOCK_START; + _rxBufValid = false; + _bufLen = 0; + ATOMIC_BLOCK_END; +} + +bool RH_RF95::recv(uint8_t *buf, uint8_t *len) +{ + if (!available()) + return false; + if (buf && len) + { + ATOMIC_BLOCK_START; + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen - RH_RF95_HEADER_LEN) + *len = _bufLen - RH_RF95_HEADER_LEN; + memcpy(buf, _buf + RH_RF95_HEADER_LEN, *len); + ATOMIC_BLOCK_END; + } + clearRxBuf(); // This message accepted and cleared + return true; +} + +bool RH_RF95::send(const uint8_t *data, uint8_t len) +{ + if (len > RH_RF95_MAX_MESSAGE_LEN) + return false; + + waitPacketSent(); // Make sure we dont interrupt an outgoing message + setModeIdle(); + + if (!waitCAD()) + return false; // Check channel activity + + // Position at the beginning of the FIFO + spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, 0); + // The headers + spiWrite(RH_RF95_REG_00_FIFO, _txHeaderTo); + spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFrom); + spiWrite(RH_RF95_REG_00_FIFO, _txHeaderId); + spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFlags); + // The message data + spiBurstWrite(RH_RF95_REG_00_FIFO, data, len); + spiWrite(RH_RF95_REG_22_PAYLOAD_LENGTH, len + RH_RF95_HEADER_LEN); + + setModeTx(); // Start the transmitter + // when Tx is done, interruptHandler will fire and radio mode will return to STANDBY + return true; +} + +bool RH_RF95::printRegisters() +{ +#ifdef RH_HAVE_SERIAL + uint8_t registers[] = {0x01, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x014, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}; + + uint8_t i; + for (i = 0; i < sizeof(registers); i++) + { + Serial.print(registers[i], HEX); + Serial.print(": "); + Serial.println(spiRead(registers[i]), HEX); + } +#endif + return true; +} + +uint8_t RH_RF95::maxMessageLength() +{ + return RH_RF95_MAX_MESSAGE_LEN; +} + +bool RH_RF95::setFrequency(float centre) +{ + // Frf = FRF / FSTEP + uint32_t frf = (centre * 1000000.0) / RH_RF95_FSTEP; + spiWrite(RH_RF95_REG_06_FRF_MSB, (frf >> 16) & 0xff); + spiWrite(RH_RF95_REG_07_FRF_MID, (frf >> 8) & 0xff); + spiWrite(RH_RF95_REG_08_FRF_LSB, frf & 0xff); + _usingHFport = (centre >= 779.0); + + return true; +} + +void RH_RF95::setModeIdle() +{ + if (_mode != RHModeIdle) + { + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_STDBY); + _mode = RHModeIdle; + } +} + +bool RH_RF95::sleep() +{ + if (_mode != RHModeSleep) + { + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP); + _mode = RHModeSleep; + } + return true; +} + +void RH_RF95::setModeRx() +{ + if (_mode != RHModeRx) + { + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_RXCONTINUOUS); + spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x00); // Interrupt on RxDone + _mode = RHModeRx; + } +} + +void RH_RF95::setModeTx() +{ + if (_mode != RHModeTx) + { + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_TX); + spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone + _mode = RHModeTx; + } +} + +void RH_RF95::setTxPower(int8_t power, bool useRFO) +{ + // Sigh, different behaviours depending on whther the module use PA_BOOST or the RFO pin + // for the transmitter output + if (useRFO) + { + if (power > 14) + power = 14; + if (power < -1) + power = -1; + spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_MAX_POWER | (power + 1)); + } + else + { + if (power > 23) + power = 23; + if (power < 5) + power = 5; + + // For RH_RF95_PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf' + // RH_RF95_PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will us it + // for 21, 22 and 23dBm + if (power > 20) + { + spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_ENABLE); + power -= 3; + } + else + { + spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_DISABLE); + } + + // RFM95/96/97/98 does not have RFO pins connected to anything. Only PA_BOOST + // pin is connected, so must use PA_BOOST + // Pout = 2 + OutputPower. + // The documentation is pretty confusing on this topic: PaSelect says the max power is 20dBm, + // but OutputPower claims it would be 17dBm. + // My measurements show 20dBm is correct + spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_PA_SELECT | (power - 5)); + } +} + +// Sets registers from a canned modem configuration structure +void RH_RF95::setModemRegisters(const ModemConfig *config) +{ + spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, config->reg_1d); + spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, config->reg_1e); + spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, config->reg_26); +} + +// Set one of the canned FSK Modem configs +// Returns true if its a valid choice +bool RH_RF95::setModemConfig(ModemConfigChoice index) +{ + if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) + return false; + + ModemConfig cfg; + memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(RH_RF95::ModemConfig)); + setModemRegisters(&cfg); + + return true; +} + +void RH_RF95::setPreambleLength(uint16_t bytes) +{ + spiWrite(RH_RF95_REG_20_PREAMBLE_MSB, bytes >> 8); + spiWrite(RH_RF95_REG_21_PREAMBLE_LSB, bytes & 0xff); +} + +bool RH_RF95::isChannelActive() +{ + // Set mode RHModeCad + if (_mode != RHModeCad) + { + spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_CAD); + spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x80); // Interrupt on CadDone + _mode = RHModeCad; + } + + while (_mode == RHModeCad) + YIELD; + + return _cad; +} + +void RH_RF95::enableTCXO() +{ + while ((spiRead(RH_RF95_REG_4B_TCXO) & RH_RF95_TCXO_TCXO_INPUT_ON) != RH_RF95_TCXO_TCXO_INPUT_ON) + { + sleep(); + spiWrite(RH_RF95_REG_4B_TCXO, (spiRead(RH_RF95_REG_4B_TCXO) | RH_RF95_TCXO_TCXO_INPUT_ON)); + } +} + +// From section 4.1.5 of SX1276/77/78/79 +// Ferror = FreqError * 2**24 * BW / Fxtal / 500 +int RH_RF95::frequencyError() +{ + int32_t freqerror = 0; + + // Convert 2.5 bytes (5 nibbles, 20 bits) to 32 bit signed int + // Caution: some C compilers make errors with eg: + // freqerror = spiRead(RH_RF95_REG_28_FEI_MSB) << 16 + // so we go more carefully. + freqerror = spiRead(RH_RF95_REG_28_FEI_MSB); + freqerror <<= 8; + freqerror |= spiRead(RH_RF95_REG_29_FEI_MID); + freqerror <<= 8; + freqerror |= spiRead(RH_RF95_REG_2A_FEI_LSB); + // Sign extension into top 3 nibbles + if (freqerror & 0x80000) + freqerror |= 0xfff00000; + + int error = 0; // In hertz + float bw_tab[] = {7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250, 500}; + uint8_t bwindex = spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) >> 4; + if (bwindex < (sizeof(bw_tab) / sizeof(float))) + error = (float)freqerror * bw_tab[bwindex] * ((float)(1L << 24) / (float)RH_RF95_FXOSC / 500.0); + // else not defined + + return error; +} + +int RH_RF95::lastSNR() +{ + return _lastSNR; +} + +/////////////////////////////////////////////////// +// +// additions below by Brian Norman 9th Nov 2018 +// brian.n.norman@gmail.com +// +// Routines intended to make changing BW, SF and CR +// a bit more intuitive +// +/////////////////////////////////////////////////// + +void RH_RF95::setSpreadingFactor(uint8_t sf) +{ + if (sf <= 6) + sf = RH_RF95_SPREADING_FACTOR_64CPS; + else if (sf == 7) + sf = RH_RF95_SPREADING_FACTOR_128CPS; + else if (sf == 8) + sf = RH_RF95_SPREADING_FACTOR_256CPS; + else if (sf == 9) + sf = RH_RF95_SPREADING_FACTOR_512CPS; + else if (sf == 10) + sf = RH_RF95_SPREADING_FACTOR_1024CPS; + else if (sf == 11) + sf = RH_RF95_SPREADING_FACTOR_2048CPS; + else if (sf >= 12) + sf = RH_RF95_SPREADING_FACTOR_4096CPS; + + // set the new spreading factor + spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, (spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) & ~RH_RF95_SPREADING_FACTOR) | sf); + // check if Low data Rate bit should be set or cleared + setLowDatarate(); +} + +void RH_RF95::setSignalBandwidth(long sbw) +{ + uint8_t bw; //register bit pattern + + if (sbw <= 7800) + bw = RH_RF95_BW_7_8KHZ; + else if (sbw <= 10400) + bw = RH_RF95_BW_10_4KHZ; + else if (sbw <= 15600) + bw = RH_RF95_BW_15_6KHZ; + else if (sbw <= 20800) + bw = RH_RF95_BW_20_8KHZ; + else if (sbw <= 31250) + bw = RH_RF95_BW_31_25KHZ; + else if (sbw <= 41700) + bw = RH_RF95_BW_41_7KHZ; + else if (sbw <= 62500) + bw = RH_RF95_BW_62_5KHZ; + else if (sbw <= 125000) + bw = RH_RF95_BW_125KHZ; + else if (sbw <= 250000) + bw = RH_RF95_BW_250KHZ; + else + bw = RH_RF95_BW_500KHZ; + + // top 4 bits of reg 1D control bandwidth + spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, (spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) & ~RH_RF95_BW) | bw); + // check if low data rate bit should be set or cleared + setLowDatarate(); +} + +void RH_RF95::setCodingRate4(uint8_t denominator) +{ + int cr = RH_RF95_CODING_RATE_4_5; + + // if (denominator <= 5) + // cr = RH_RF95_CODING_RATE_4_5; + if (denominator == 6) + cr = RH_RF95_CODING_RATE_4_6; + else if (denominator == 7) + cr = RH_RF95_CODING_RATE_4_7; + else if (denominator >= 8) + cr = RH_RF95_CODING_RATE_4_8; + + // CR is bits 3..1 of RH_RF95_REG_1D_MODEM_CONFIG1 + spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, (spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) & ~RH_RF95_CODING_RATE) | cr); +} + +void RH_RF95::setLowDatarate() +{ + // called after changing bandwidth and/or spreading factor + // Semtech modem design guide AN1200.13 says + // "To avoid issues surrounding drift of the crystal reference oscillator due to either temperature change + // or motion,the low data rate optimization bit is used. Specifically for 125 kHz bandwidth and SF = 11 and 12, + // this adds a small overhead to increase robustness to reference frequency variations over the timescale of the LoRa packet." + + // read current value for BW and SF + uint8_t BW = spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) >> 4; // bw is in bits 7..4 + uint8_t SF = spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) >> 4; // sf is in bits 7..4 + + // calculate symbol time (see Semtech AN1200.22 section 4) + float bw_tab[] = {7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000}; + + float bandwidth = bw_tab[BW]; + + float symbolTime = 1000.0 * pow(2, SF) / bandwidth; // ms + + // the symbolTime for SF 11 BW 125 is 16.384ms. + // and, according to this :- + // https://www.thethingsnetwork.org/forum/t/a-point-to-note-lora-low-data-rate-optimisation-flag/12007 + // the LDR bit should be set if the Symbol Time is > 16ms + // So the threshold used here is 16.0ms + + // the LDR is bit 3 of RH_RF95_REG_26_MODEM_CONFIG3 + uint8_t current = spiRead(RH_RF95_REG_26_MODEM_CONFIG3) & ~RH_RF95_LOW_DATA_RATE_OPTIMIZE; // mask off the LDR bit + if (symbolTime > 16.0) + spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, current | RH_RF95_LOW_DATA_RATE_OPTIMIZE); + else + spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, current); +} + +void RH_RF95::setPayloadCRC(bool on) +{ + // Payload CRC is bit 2 of register 1E + uint8_t current = spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) & ~RH_RF95_PAYLOAD_CRC_ON; // mask off the CRC + + if (on) + spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, current | RH_RF95_PAYLOAD_CRC_ON); + else + spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, current); +} diff --git a/src/rf95/RH_RF95.h b/src/rf95/RH_RF95.h new file mode 100644 index 000000000..43a5bc573 --- /dev/null +++ b/src/rf95/RH_RF95.h @@ -0,0 +1,894 @@ +// RH_RF95.h +// +// Definitions for HopeRF LoRa radios per: +// http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf +// http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RH_RF95.h,v 1.23 2019/11/02 02:34:22 mikem Exp $ +// + +#ifndef RH_RF95_h +#define RH_RF95_h + +#include + +// This is the maximum number of interrupts the driver can support +// Most Arduinos can handle 2, Megas can handle more +#define RH_RF95_NUM_INTERRUPTS 3 + +// Max number of octets the LORA Rx/Tx FIFO can hold +#define RH_RF95_FIFO_SIZE 255 + +// This is the maximum number of bytes that can be carried by the LORA. +// We use some for headers, keeping fewer for RadioHead messages +#define RH_RF95_MAX_PAYLOAD_LEN RH_RF95_FIFO_SIZE + +// The length of the headers we add. +// The headers are inside the LORA's payload +#define RH_RF95_HEADER_LEN 4 + +// This is the maximum message length that can be supported by this driver. +// Can be pre-defined to a smaller size (to save SRAM) prior to including this header +// Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS +#ifndef RH_RF95_MAX_MESSAGE_LEN + #define RH_RF95_MAX_MESSAGE_LEN (RH_RF95_MAX_PAYLOAD_LEN - RH_RF95_HEADER_LEN) +#endif + +// The crystal oscillator frequency of the module +#define RH_RF95_FXOSC 32000000.0 + +// The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19 +#define RH_RF95_FSTEP (RH_RF95_FXOSC / 524288) + + +// Register names (LoRa Mode, from table 85) +#define RH_RF95_REG_00_FIFO 0x00 +#define RH_RF95_REG_01_OP_MODE 0x01 +#define RH_RF95_REG_02_RESERVED 0x02 +#define RH_RF95_REG_03_RESERVED 0x03 +#define RH_RF95_REG_04_RESERVED 0x04 +#define RH_RF95_REG_05_RESERVED 0x05 +#define RH_RF95_REG_06_FRF_MSB 0x06 +#define RH_RF95_REG_07_FRF_MID 0x07 +#define RH_RF95_REG_08_FRF_LSB 0x08 +#define RH_RF95_REG_09_PA_CONFIG 0x09 +#define RH_RF95_REG_0A_PA_RAMP 0x0a +#define RH_RF95_REG_0B_OCP 0x0b +#define RH_RF95_REG_0C_LNA 0x0c +#define RH_RF95_REG_0D_FIFO_ADDR_PTR 0x0d +#define RH_RF95_REG_0E_FIFO_TX_BASE_ADDR 0x0e +#define RH_RF95_REG_0F_FIFO_RX_BASE_ADDR 0x0f +#define RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR 0x10 +#define RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 +#define RH_RF95_REG_12_IRQ_FLAGS 0x12 +#define RH_RF95_REG_13_RX_NB_BYTES 0x13 +#define RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB 0x14 +#define RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB 0x15 +#define RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB 0x16 +#define RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB 0x17 +#define RH_RF95_REG_18_MODEM_STAT 0x18 +#define RH_RF95_REG_19_PKT_SNR_VALUE 0x19 +#define RH_RF95_REG_1A_PKT_RSSI_VALUE 0x1a +#define RH_RF95_REG_1B_RSSI_VALUE 0x1b +#define RH_RF95_REG_1C_HOP_CHANNEL 0x1c +#define RH_RF95_REG_1D_MODEM_CONFIG1 0x1d +#define RH_RF95_REG_1E_MODEM_CONFIG2 0x1e +#define RH_RF95_REG_1F_SYMB_TIMEOUT_LSB 0x1f +#define RH_RF95_REG_20_PREAMBLE_MSB 0x20 +#define RH_RF95_REG_21_PREAMBLE_LSB 0x21 +#define RH_RF95_REG_22_PAYLOAD_LENGTH 0x22 +#define RH_RF95_REG_23_MAX_PAYLOAD_LENGTH 0x23 +#define RH_RF95_REG_24_HOP_PERIOD 0x24 +#define RH_RF95_REG_25_FIFO_RX_BYTE_ADDR 0x25 +#define RH_RF95_REG_26_MODEM_CONFIG3 0x26 + +#define RH_RF95_REG_27_PPM_CORRECTION 0x27 +#define RH_RF95_REG_28_FEI_MSB 0x28 +#define RH_RF95_REG_29_FEI_MID 0x29 +#define RH_RF95_REG_2A_FEI_LSB 0x2a +#define RH_RF95_REG_2C_RSSI_WIDEBAND 0x2c +#define RH_RF95_REG_31_DETECT_OPTIMIZE 0x31 +#define RH_RF95_REG_33_INVERT_IQ 0x33 +#define RH_RF95_REG_37_DETECTION_THRESHOLD 0x37 +#define RH_RF95_REG_39_SYNC_WORD 0x39 + +#define RH_RF95_REG_40_DIO_MAPPING1 0x40 +#define RH_RF95_REG_41_DIO_MAPPING2 0x41 +#define RH_RF95_REG_42_VERSION 0x42 + +#define RH_RF95_REG_4B_TCXO 0x4b +#define RH_RF95_REG_4D_PA_DAC 0x4d +#define RH_RF95_REG_5B_FORMER_TEMP 0x5b +#define RH_RF95_REG_61_AGC_REF 0x61 +#define RH_RF95_REG_62_AGC_THRESH1 0x62 +#define RH_RF95_REG_63_AGC_THRESH2 0x63 +#define RH_RF95_REG_64_AGC_THRESH3 0x64 + +// RH_RF95_REG_01_OP_MODE 0x01 +#define RH_RF95_LONG_RANGE_MODE 0x80 +#define RH_RF95_ACCESS_SHARED_REG 0x40 +#define RH_RF95_LOW_FREQUENCY_MODE 0x08 +#define RH_RF95_MODE 0x07 +#define RH_RF95_MODE_SLEEP 0x00 +#define RH_RF95_MODE_STDBY 0x01 +#define RH_RF95_MODE_FSTX 0x02 +#define RH_RF95_MODE_TX 0x03 +#define RH_RF95_MODE_FSRX 0x04 +#define RH_RF95_MODE_RXCONTINUOUS 0x05 +#define RH_RF95_MODE_RXSINGLE 0x06 +#define RH_RF95_MODE_CAD 0x07 + +// RH_RF95_REG_09_PA_CONFIG 0x09 +#define RH_RF95_PA_SELECT 0x80 +#define RH_RF95_MAX_POWER 0x70 +#define RH_RF95_OUTPUT_POWER 0x0f + +// RH_RF95_REG_0A_PA_RAMP 0x0a +#define RH_RF95_LOW_PN_TX_PLL_OFF 0x10 +#define RH_RF95_PA_RAMP 0x0f +#define RH_RF95_PA_RAMP_3_4MS 0x00 +#define RH_RF95_PA_RAMP_2MS 0x01 +#define RH_RF95_PA_RAMP_1MS 0x02 +#define RH_RF95_PA_RAMP_500US 0x03 +#define RH_RF95_PA_RAMP_250US 0x04 +#define RH_RF95_PA_RAMP_125US 0x05 +#define RH_RF95_PA_RAMP_100US 0x06 +#define RH_RF95_PA_RAMP_62US 0x07 +#define RH_RF95_PA_RAMP_50US 0x08 +#define RH_RF95_PA_RAMP_40US 0x09 +#define RH_RF95_PA_RAMP_31US 0x0a +#define RH_RF95_PA_RAMP_25US 0x0b +#define RH_RF95_PA_RAMP_20US 0x0c +#define RH_RF95_PA_RAMP_15US 0x0d +#define RH_RF95_PA_RAMP_12US 0x0e +#define RH_RF95_PA_RAMP_10US 0x0f + +// RH_RF95_REG_0B_OCP 0x0b +#define RH_RF95_OCP_ON 0x20 +#define RH_RF95_OCP_TRIM 0x1f + +// RH_RF95_REG_0C_LNA 0x0c +#define RH_RF95_LNA_GAIN 0xe0 +#define RH_RF95_LNA_GAIN_G1 0x20 +#define RH_RF95_LNA_GAIN_G2 0x40 +#define RH_RF95_LNA_GAIN_G3 0x60 +#define RH_RF95_LNA_GAIN_G4 0x80 +#define RH_RF95_LNA_GAIN_G5 0xa0 +#define RH_RF95_LNA_GAIN_G6 0xc0 +#define RH_RF95_LNA_BOOST_LF 0x18 +#define RH_RF95_LNA_BOOST_LF_DEFAULT 0x00 +#define RH_RF95_LNA_BOOST_HF 0x03 +#define RH_RF95_LNA_BOOST_HF_DEFAULT 0x00 +#define RH_RF95_LNA_BOOST_HF_150PC 0x03 + +// RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 +#define RH_RF95_RX_TIMEOUT_MASK 0x80 +#define RH_RF95_RX_DONE_MASK 0x40 +#define RH_RF95_PAYLOAD_CRC_ERROR_MASK 0x20 +#define RH_RF95_VALID_HEADER_MASK 0x10 +#define RH_RF95_TX_DONE_MASK 0x08 +#define RH_RF95_CAD_DONE_MASK 0x04 +#define RH_RF95_FHSS_CHANGE_CHANNEL_MASK 0x02 +#define RH_RF95_CAD_DETECTED_MASK 0x01 + +// RH_RF95_REG_12_IRQ_FLAGS 0x12 +#define RH_RF95_RX_TIMEOUT 0x80 +#define RH_RF95_RX_DONE 0x40 +#define RH_RF95_PAYLOAD_CRC_ERROR 0x20 +#define RH_RF95_VALID_HEADER 0x10 +#define RH_RF95_TX_DONE 0x08 +#define RH_RF95_CAD_DONE 0x04 +#define RH_RF95_FHSS_CHANGE_CHANNEL 0x02 +#define RH_RF95_CAD_DETECTED 0x01 + +// RH_RF95_REG_18_MODEM_STAT 0x18 +#define RH_RF95_RX_CODING_RATE 0xe0 +#define RH_RF95_MODEM_STATUS_CLEAR 0x10 +#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08 +#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04 +#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02 +#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01 + +// RH_RF95_REG_1C_HOP_CHANNEL 0x1c +#define RH_RF95_PLL_TIMEOUT 0x80 +#define RH_RF95_RX_PAYLOAD_CRC_IS_ON 0x40 +#define RH_RF95_FHSS_PRESENT_CHANNEL 0x3f + +// RH_RF95_REG_1D_MODEM_CONFIG1 0x1d +#define RH_RF95_BW 0xf0 + +#define RH_RF95_BW_7_8KHZ 0x00 +#define RH_RF95_BW_10_4KHZ 0x10 +#define RH_RF95_BW_15_6KHZ 0x20 +#define RH_RF95_BW_20_8KHZ 0x30 +#define RH_RF95_BW_31_25KHZ 0x40 +#define RH_RF95_BW_41_7KHZ 0x50 +#define RH_RF95_BW_62_5KHZ 0x60 +#define RH_RF95_BW_125KHZ 0x70 +#define RH_RF95_BW_250KHZ 0x80 +#define RH_RF95_BW_500KHZ 0x90 +#define RH_RF95_CODING_RATE 0x0e +#define RH_RF95_CODING_RATE_4_5 0x02 +#define RH_RF95_CODING_RATE_4_6 0x04 +#define RH_RF95_CODING_RATE_4_7 0x06 +#define RH_RF95_CODING_RATE_4_8 0x08 +#define RH_RF95_IMPLICIT_HEADER_MODE_ON 0x01 + +// RH_RF95_REG_1E_MODEM_CONFIG2 0x1e +#define RH_RF95_SPREADING_FACTOR 0xf0 +#define RH_RF95_SPREADING_FACTOR_64CPS 0x60 +#define RH_RF95_SPREADING_FACTOR_128CPS 0x70 +#define RH_RF95_SPREADING_FACTOR_256CPS 0x80 +#define RH_RF95_SPREADING_FACTOR_512CPS 0x90 +#define RH_RF95_SPREADING_FACTOR_1024CPS 0xa0 +#define RH_RF95_SPREADING_FACTOR_2048CPS 0xb0 +#define RH_RF95_SPREADING_FACTOR_4096CPS 0xc0 +#define RH_RF95_TX_CONTINUOUS_MODE 0x08 + +#define RH_RF95_PAYLOAD_CRC_ON 0x04 +#define RH_RF95_SYM_TIMEOUT_MSB 0x03 + +// RH_RF95_REG_26_MODEM_CONFIG3 +#define RH_RF95_MOBILE_NODE 0x08 // HopeRF term +#define RH_RF95_LOW_DATA_RATE_OPTIMIZE 0x08 // Semtechs term +#define RH_RF95_AGC_AUTO_ON 0x04 + +// RH_RF95_REG_4B_TCXO 0x4b +#define RH_RF95_TCXO_TCXO_INPUT_ON 0x10 + +// RH_RF95_REG_4D_PA_DAC 0x4d +#define RH_RF95_PA_DAC_DISABLE 0x04 +#define RH_RF95_PA_DAC_ENABLE 0x07 + +///////////////////////////////////////////////////////////////////// +/// \class RH_RF95 RH_RF95.h +/// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa +/// capable radio transceiver. +/// +/// For Semtech SX1276/77/78/79 and HopeRF RF95/96/97/98 and other similar LoRa capable radios. +/// Based on http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf +/// and http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf +/// and http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf +/// and http://www.semtech.com/images/datasheet/sx1276.pdf +/// and http://www.semtech.com/images/datasheet/sx1276_77_78_79.pdf +/// FSK/GFSK/OOK modes are not (yet) supported. +/// +/// Works with +/// - the excellent MiniWirelessLoRa from Anarduino http://www.anarduino.com/miniwireless +/// - The excellent Modtronix inAir4 http://modtronix.com/inair4.html +/// and inAir9 modules http://modtronix.com/inair9.html. +/// - the excellent Rocket Scream Mini Ultra Pro with the RFM95W +/// http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ +/// - Lora1276 module from NiceRF http://www.nicerf.com/product_view.aspx?id=99 +/// - Adafruit Feather M0 with RFM95 +/// - The very fine Talk2 Whisper Node LoRa boards https://wisen.com.au/store/products/whisper-node-lora +/// an Arduino compatible board, which include an on-board RFM95/96 LoRa Radio (Semtech SX1276), external antenna, +/// run on 2xAAA batteries and support low power operations. RF95 examples work without modification. +/// Use Arduino Board Manager to install the Talk2 code support. Upload the code with an FTDI adapter set to 5V. +/// - heltec / TTGO ESP32 LoRa OLED https://www.aliexpress.com/item/Internet-Development-Board-SX1278-ESP32-WIFI-chip-0-96-inch-OLED-Bluetooth-WIFI-Lora-Kit-32/32824535649.html +/// +/// \par Overview +/// +/// This class provides basic functions for sending and receiving unaddressed, +/// unreliable datagrams of arbitrary length to 251 octets per packet. +/// +/// Manager classes may use this class to implement reliable, addressed datagrams and streams, +/// mesh routers, repeaters, translators etc. +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// modulation scheme. +/// +/// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF +/// RFM95/96/97/98(W), Semtech SX1276/77/78/79 and compatible radio modules in LoRa mode. +/// +/// The Hope-RF (http://www.hoperf.com) RFM95/96/97/98(W) and Semtech SX1276/77/78/79 is a low-cost ISM transceiver +/// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and +/// programmable data rates, and it also supports the proprietary LoRA (Long Range) mode, which +/// is the only mode supported in this RadioHead driver. +/// +/// This Driver provides functions for sending and receiving messages of up +/// to 251 octets on any frequency supported by the radio, in a range of +/// predefined Bandwidths, Spreading Factors and Coding Rates. Frequency can be set with +/// 61Hz precision to any frequency from 240.0MHz to 960.0MHz. Caution: most modules only support a more limited +/// range of frequencies due to antenna tuning. +/// +/// Up to 2 modules can be connected to an Arduino (3 on a Mega), +/// permitting the construction of translators and frequency changers, etc. +/// +/// Support for other features such as transmitter power control etc is +/// also provided. +/// +/// Tested on MinWirelessLoRa with arduino-1.0.5 +/// on OpenSuSE 13.1. +/// Also tested with Teensy3.1, Modtronix inAir4 and Arduino 1.6.5 on OpenSuSE 13.1 +/// +/// \par Packet Format +/// +/// All messages sent and received by this RH_RF95 Driver conform to this packet format: +/// +/// - LoRa mode: +/// - 8 symbol PREAMBLE +/// - Explicit header with header CRC (handled internally by the radio) +/// - 4 octets HEADER: (TO, FROM, ID, FLAGS) +/// - 0 to 251 octets DATA +/// - CRC (handled internally by the radio) +/// +/// \par Connecting RFM95/96/97/98 and Semtech SX1276/77/78/79 to Arduino +/// +/// We tested with Anarduino MiniWirelessLoRA, which is an Arduino Duemilanove compatible with a RFM96W +/// module on-board. Therefore it needs no connections other than the USB +/// programming connection and an antenna to make it work. +/// +/// If you have a bare RFM95/96/97/98 that you want to connect to an Arduino, you +/// might use these connections (untested): CAUTION: you must use a 3.3V type +/// Arduino, otherwise you will also need voltage level shifters between the +/// Arduino and the RFM95. CAUTION, you must also ensure you connect an +/// antenna. +/// +/// \code +/// Arduino RFM95/96/97/98 +/// GND----------GND (ground in) +/// 3V3----------3.3V (3.3V in) +/// interrupt 0 pin D2-----------DIO0 (interrupt request out) +/// SS pin D10----------NSS (CS chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------MOSI (SPI Data in) +/// MISO pin D12----------MISO (SPI Data out) +/// \endcode +/// With these connections, you can then use the default constructor RH_RF95(). +/// You can override the default settings for the SS pin and the interrupt in +/// the RH_RF95 constructor if you wish to connect the slave select SS to other +/// than the normal one for your Arduino (D10 for Diecimila, Uno etc and D53 +/// for Mega) or the interrupt request to other than pin D2 (Caution, +/// different processors have different constraints as to the pins available +/// for interrupts). +/// +/// You can connect a Modtronix inAir4 or inAir9 directly to a 3.3V part such as a Teensy 3.1 like +/// this (tested). +/// \code +/// Teensy inAir4 inAir9 +/// GND----------0V (ground in) +/// 3V3----------3.3V (3.3V in) +/// interrupt 0 pin D2-----------D0 (interrupt request out) +/// SS pin D10----------CS (CS chip select in) +/// SCK pin D13----------CK (SPI clock in) +/// MOSI pin D11----------SI (SPI Data in) +/// MISO pin D12----------SO (SPI Data out) +/// \endcode +/// With these connections, you can then use the default constructor RH_RF95(). +/// you must also set the transmitter power with useRFO: +/// driver.setTxPower(13, true); +/// +/// Note that if you are using Modtronix inAir4 or inAir9,or any other module which uses the +/// transmitter RFO pins and not the PA_BOOST pins +/// that you must configure the power transmitter power for -1 to 14 dBm and with useRFO true. +/// Failure to do that will result in extremely low transmit powers. +/// +/// If you have an Arduino M0 Pro from arduino.org, +/// you should note that you cannot use Pin 2 for the interrupt line +/// (Pin 2 is for the NMI only). The same comments apply to Pin 4 on Arduino Zero from arduino.cc. +/// Instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this: +/// \code +/// // Slave Select is pin 10, interrupt is Pin 3 +/// RH_RF95 driver(10, 3); +/// \endcode +/// +/// If you have a Rocket Scream Mini Ultra Pro with the RFM95W: +/// - Ensure you have Arduino SAMD board support 1.6.5 or later in Arduino IDE 1.6.8 or later. +/// - The radio SS is hardwired to pin D5 and the DIO0 interrupt to pin D2, +/// so you need to initialise the radio like this: +/// \code +/// RH_RF95 driver(5, 2); +/// \endcode +/// - The name of the serial port on that board is 'SerialUSB', not 'Serial', so this may be helpful at the top of our +/// sample sketches: +/// \code +/// #define Serial SerialUSB +/// \endcode +/// - You also need this in setup before radio initialisation +/// \code +/// // Ensure serial flash is not interfering with radio communication on SPI bus +/// pinMode(4, OUTPUT); +/// digitalWrite(4, HIGH); +/// \endcode +/// - and if you have a 915MHz part, you need this after driver/manager intitalisation: +/// \code +/// rf95.setFrequency(915.0); +/// \endcode +/// which adds up to modifying sample sketches something like: +/// \code +/// #include +/// #include +/// RH_RF95 rf95(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W +/// #define Serial SerialUSB +/// +/// void setup() +/// { +/// // Ensure serial flash is not interfering with radio communication on SPI bus +/// pinMode(4, OUTPUT); +/// digitalWrite(4, HIGH); +/// +/// Serial.begin(9600); +/// while (!Serial) ; // Wait for serial port to be available +/// if (!rf95.init()) +/// Serial.println("init failed"); +/// rf95.setFrequency(915.0); +/// } +/// ... +/// \endcode +/// +/// For Adafruit Feather M0 with RFM95, construct the driver like this: +/// \code +/// RH_RF95 rf95(8, 3); +/// \endcode +/// +/// If you have a talk2 Whisper Node LoRa board with on-board RF95 radio, +/// the example rf95_* sketches work without modification. Initialise the radio like +/// with the default constructor: +/// \code +/// RH_RF95 driver; +/// \endcode +/// +/// It is possible to have 2 or more radios connected to one Arduino, provided +/// each radio has its own SS and interrupt line (SCK, SDI and SDO are common +/// to all radios) +/// +/// Caution: on some Arduinos such as the Mega 2560, if you set the slave +/// select pin to be other than the usual SS pin (D53 on Mega 2560), you may +/// need to set the usual SS pin to be an output to force the Arduino into SPI +/// master mode. +/// +/// Caution: Power supply requirements of the RFM module may be relevant in some circumstances: +/// RFM95/96/97/98 modules are capable of pulling 120mA+ at full power, where Arduino's 3.3V line can +/// give 50mA. You may need to make provision for alternate power supply for +/// the RFM module, especially if you wish to use full transmit power, and/or you have +/// other shields demanding power. Inadequate power for the RFM is likely to cause symptoms such as: +/// - reset's/bootups terminate with "init failed" messages +/// - random termination of communication after 5-30 packets sent/received +/// - "fake ok" state, where initialization passes fluently, but communication doesn't happen +/// - shields hang Arduino boards, especially during the flashing +/// +/// \par Interrupts +/// +/// The RH_RF95 driver uses interrupts to react to events in the RFM module, +/// such as the reception of a new packet, or the completion of transmission +/// of a packet. The RH_RF95 driver interrupt service routine reads status from +/// and writes data to the the RFM module via the SPI interface. It is very +/// important therefore, that if you are using the RH_RF95 driver with another +/// SPI based deviced, that you disable interrupts while you transfer data to +/// and from that other device. Use cli() to disable interrupts and sei() to +/// reenable them. +/// +/// \par Memory +/// +/// The RH_RF95 driver requires non-trivial amounts of memory. The sample +/// programs all compile to about 8kbytes each, which will fit in the +/// flash proram memory of most Arduinos. However, the RAM requirements are +/// more critical. Therefore, you should be vary sparing with RAM use in +/// programs that use the RH_RF95 driver. +/// +/// It is often hard to accurately identify when you are hitting RAM limits on Arduino. +/// The symptoms can include: +/// - Mysterious crashes and restarts +/// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements) +/// - Hanging +/// - Output from Serial.print() not appearing +/// +/// \par Range +/// +/// We have made some simple range tests under the following conditions: +/// - rf95_client base station connected to a VHF discone antenna at 8m height above ground +/// - rf95_server mobile connected to 17.3cm 1/4 wavelength antenna at 1m height, no ground plane. +/// - Both configured for 13dBm, 434MHz, Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range +/// - Minimum reported RSSI seen for successful comms was about -91 +/// - Range over flat ground through heavy trees and vegetation approx 2km. +/// - At 20dBm (100mW) otherwise identical conditions approx 3km. +/// - At 20dBm, along salt water flat sandy beach, 3.2km. +/// +/// It should be noted that at this data rate, a 12 octet message takes 2 seconds to transmit. +/// +/// At 20dBm (100mW) with Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. +/// (Default medium range) in the conditions described above. +/// - Range over flat ground through heavy trees and vegetation approx 2km. +/// +/// Caution: the performance of this radio, especially with narrow bandwidths is strongly dependent on the +/// accuracy and stability of the chip clock. HopeRF and Semtech do not appear to +/// recommend bandwidths of less than 62.5 kHz +/// unless you have the optional Temperature Compensated Crystal Oscillator (TCXO) installed and +/// enabled on your radio module. See the refernece manual for more data. +/// Also https://lowpowerlab.com/forum/rf-range-antennas-rfm69-library/lora-library-experiences-range/15/ +/// and http://www.semtech.com/images/datasheet/an120014-xo-guidance-lora-modulation.pdf +/// +/// \par Transmitter Power +/// +/// You can control the transmitter power on the RF transceiver +/// with the RH_RF95::setTxPower() function. The argument can be any of +/// +5 to +23 (for modules that use PA_BOOST) +/// -1 to +14 (for modules that use RFO transmitter pin) +/// The default is 13. Eg: +/// \code +/// driver.setTxPower(10); // use PA_BOOST transmitter pin +/// driver.setTxPower(10, true); // use PA_RFO pin transmitter pin +/// \endcode +/// +/// We have made some actual power measurements against +/// programmed power for Anarduino MiniWirelessLoRa (which has RFM96W-433Mhz installed) +/// - MiniWirelessLoRa RFM96W-433Mhz, USB power +/// - 30cm RG316 soldered direct to RFM96W module ANT and GND +/// - SMA connector +/// - 12db attenuator +/// - SMA connector +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// 5 5 +/// 7 7 +/// 9 8 +/// 11 11 +/// 13 13 +/// 15 15 +/// 17 16 +/// 19 18 +/// 20 20 +/// 21 21 +/// 22 22 +/// 23 23 +/// \endcode +/// +/// We have also measured the actual power output from a Modtronix inAir4 http://modtronix.com/inair4.html +/// connected to a Teensy 3.1: +/// Teensy 3.1 this is a 3.3V part, connected directly to: +/// Modtronix inAir4 with SMA antenna connector, connected as above: +/// 10cm SMA-SMA cable +/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) +/// - Tektronix TDS220 scope to measure the Vout from power head +/// \code +/// Program power Measured Power +/// dBm dBm +/// -1 0 +/// 1 2 +/// 3 4 +/// 5 7 +/// 7 10 +/// 9 13 +/// 11 14.2 +/// 13 15 +/// 14 16 +/// \endcode +/// (Caution: we dont claim laboratory accuracy for these power measurements) +/// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna. +class RH_RF95 : public RHSPIDriver +{ +public: + /// \brief Defines register values for a set of modem configuration registers + /// + /// Defines register values for a set of modem configuration registers + /// that can be passed to setModemRegisters() if none of the choices in + /// ModemConfigChoice suit your need setModemRegisters() writes the + /// register values from this structure to the appropriate registers + /// to set the desired spreading factor, coding rate and bandwidth + typedef struct + { + uint8_t reg_1d; ///< Value for register RH_RF95_REG_1D_MODEM_CONFIG1 + uint8_t reg_1e; ///< Value for register RH_RF95_REG_1E_MODEM_CONFIG2 + uint8_t reg_26; ///< Value for register RH_RF95_REG_26_MODEM_CONFIG3 + } ModemConfig; + + /// Choices for setModemConfig() for a selected subset of common + /// data rates. If you need another configuration, + /// determine the necessary settings and call setModemRegisters() with your + /// desired settings. It might be helpful to use the LoRa calculator mentioned in + /// http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf + /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic + /// definitions and not their integer equivalents: its possible that new values will be + /// introduced in later versions (though we will try to avoid it). + /// Caution: if you are using slow packet rates and long packets with RHReliableDatagram or subclasses + /// you may need to change the RHReliableDatagram timeout for reliable operations. + /// Caution: for some slow rates nad with ReliableDatagrams youi may need to increase the reply timeout + /// with manager.setTimeout() to + /// deal with the long transmission times. + typedef enum + { + Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range + } ModemConfigChoice; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface + /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient + /// distinct interrupt lines, one for each instance. + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RH_RF22 before + /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) + /// \param[in] interruptPin The interrupt Pin number that is connected to the RFM DIO0 interrupt line. + /// Defaults to pin 2, as required by Anarduino MinWirelessLoRa module. + /// Caution: You must specify an interrupt capable pin. + /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. + /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. + /// On Arduino Zero from arduino.cc, any digital pin other than 4. + /// On Arduino M0 Pro from arduino.org, any digital pin other than 2. + /// On other Arduinos pins 2 or 3. + /// See http://arduino.cc/en/Reference/attachInterrupt for more details. + /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. + /// On other boards, any digital pin may be used. + /// \param[in] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_RF95(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI& spi = hardware_spi); + + /// 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(); + + /// The main CPU is about to enter deep sleep, prepare the RF95 so it will be able to wake properly after we reboot + /// i.e. confirm we are in idle or rx mode, set a rtcram flag with state we need to restore after boot. Later in boot + /// we'll need to be careful not to wipe registers and be ready to handle any pending interrupts that occurred while + /// the main CPU was powered down. + void prepareDeepSleep(); + + /// Prints the value of all chip registers + /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform + /// For debugging purposes only. + /// \return true on success + bool printRegisters(); + + /// Sets all the registered required to configure the data modem in the RF95/96/97/98, including the bandwidth, + /// spreading factor etc. You can use this to configure the modem with custom configurations if none of the + /// canned configurations in ModemConfigChoice suit you. + /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. + void setModemRegisters(const ModemConfig* config); + + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. + /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator + /// for reliable operation. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + bool setModemConfig(ModemConfigChoice index); + + /// Tests whether a new message is available + /// from the Driver. + /// On most drivers, this will also put the Driver into RHModeRx mode until + /// a message is actually received by the transport, when it wil be returned to RHModeIdle. + /// This can be called multiple times in a timeout loop + /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() + virtual bool available(); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available, copy it to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). + /// You should be sure to call this function frequently enough to not miss any messages + /// It is recommended that you call it in your main loop. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied. + /// \return true if a valid message was copied to buf + virtual bool recv(uint8_t* buf, uint8_t* len); + + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then optionally waits for Channel Activity Detection (CAD) + /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. + /// \return true if the message length was valid and it was correctly queued for transmit. Return false + /// if CAD was requested and the CAD timeout timed out before clear channel was detected. + virtual bool send(const uint8_t* data, uint8_t len); + + /// Sets the length of the preamble + /// in bytes. + /// Caution: this should be set to the same + /// value on all nodes in your network. Default is 8. + /// Sets the message preamble length in RH_RF95_REG_??_PREAMBLE_?SB + /// \param[in] bytes Preamble length in bytes. + void setPreambleLength(uint16_t bytes); + + /// Returns the maximum message length + /// available in this Driver. + /// \return The maximum legal message length + virtual uint8_t maxMessageLength(); + + /// Sets the transmitter and receiver + /// centre frequency. + /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several + /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work + /// \return true if the selected frquency centre is within range + bool setFrequency(float centre); + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + void setModeIdle(); + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the RF95/96/97/98. + void setModeRx(); + + /// If current mode is Rx or Idle, changes it to Rx. F + /// Starts the transmitter in the RF95/96/97/98. + void setModeTx(); + + /// Sets the transmitter power output level, and configures the transmitter pin. + /// Be a good neighbour and set the lowest power level you need. + /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) + /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) + /// while some (such as the Modtronix inAir4 and inAir9) + /// use the RFO transmitter pin for lower power but higher efficiency. + /// You must set the appropriate power level and useRFO argument for your module. + /// Check with your module manufacturer which transmtter pin is used on your module + /// to ensure you are setting useRFO correctly. + /// Failure to do so will result in very low + /// transmitter power output. + /// Caution: legal power limits may apply in certain countries. + /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). + /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, + /// valid values are from +5 to +23. + /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), + /// valid values are from -1 to 14. + /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of + /// the PA_BOOST pin (false). Choose the correct setting for your module. + void setTxPower(int8_t power, bool useRFO = false); + + /// Sets the radio into low-power sleep mode. + /// If successful, the transport will stay in sleep mode until woken by + /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) + /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. + /// \return true if sleep mode was successfully entered. + virtual bool sleep(); + + // Bent G Christensen (bentor@gmail.com), 08/15/2016 + /// Use the radio's Channel Activity Detect (CAD) function to detect channel activity. + /// Sets the RF95 radio into CAD mode and waits until CAD detection is complete. + /// To be used in a listen-before-talk mechanism (Collision Avoidance) + /// with a reasonable time backoff algorithm. + /// This is called automatically by waitCAD(). + /// \return true if channel is in use. + virtual bool isChannelActive(); + + /// Enable TCXO mode + /// Call this immediately after init(), to force your radio to use an external + /// frequency source, such as a Temperature Compensated Crystal Oscillator (TCXO), if available. + /// See the comments in the main documentation about the sensitivity of this radio to + /// clock frequency especially when using narrow bandwidths. + /// Leaves the module in sleep mode. + /// Caution, this function has not been tested by us. + /// Caution, the TCXO model radios are not low power when in sleep (consuming + /// about ~600 uA, reported by Phang Moh Lim.
+ void enableTCXO(); + + /// Returns the last measured frequency error. + /// The LoRa receiver estimates the frequency offset between the receiver centre frequency + /// and that of the received LoRa signal. This function returns the estimates offset (in Hz) + /// of the last received message. Caution: this measurement is not absolute, but is measured + /// relative to the local receiver's oscillator. + /// Apparent errors may be due to the transmitter, the receiver or both. + /// \return The estimated centre frequency offset in Hz of the last received message. + /// If the modem bandwidth selector in + /// register RH_RF95_REG_1D_MODEM_CONFIG1 is invalid, returns 0. + int frequencyError(); + + /// Returns the Signal-to-noise ratio (SNR) of the last received message, as measured + /// by the receiver. + /// \return SNR of the last received message in dB + int lastSNR(); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// Sets the radio spreading factor. + /// valid values are 6 through 12. + /// Out of range values below 6 are clamped to 6 + /// Out of range values above 12 are clamped to 12 + /// See Semtech DS SX1276/77/78/79 page 27 regarding SF6 configuration. + /// + /// \param[in] uint8_t sf (spreading factor 6..12) + /// \return nothing + void setSpreadingFactor(uint8_t sf); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// Sets the radio signal bandwidth + /// sbw ranges and resultant settings are as follows:- + /// sbw range actual bw (kHz) + /// 0-7800 7.8 + /// 7801-10400 10.4 + /// 10401-15600 15.6 + /// 15601-20800 20.8 + /// 20801-31250 31.25 + /// 31251-41700 41.7 + /// 41701-62500 62.5 + /// 62501-12500 125.0 + /// 12501-250000 250.0 + /// >250000 500.0 + /// NOTE caution Earlier - Semtech do not recommend BW below 62.5 although, in testing + /// I managed 31.25 with two devices in close proximity. + /// \param[in] sbw long, signal bandwidth e.g. 125000 + void setSignalBandwidth(long sbw); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// Sets the coding rate to 4/5, 4/6, 4/7 or 4/8. + /// Valid denominator values are 5, 6, 7 or 8. A value of 5 sets the coding rate to 4/5 etc. + /// Values below 5 are clamped at 5 + /// values above 8 are clamped at 8 + /// \param[in] denominator uint8_t range 5..8 + void setCodingRate4(uint8_t denominator); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// sets the low data rate flag if symbol time exceeds 16ms + /// ref: https://www.thethingsnetwork.org/forum/t/a-point-to-note-lora-low-data-rate-optimisation-flag/12007 + /// called by setBandwidth() and setSpreadingfactor() since these affect the symbol time. + void setLowDatarate(); + + /// brian.n.norman@gmail.com 9th Nov 2018 + /// allows the payload CRC bit to be turned on/off. Normally this should be left on + /// so that packets with a bad CRC are rejected + /// \patam[in] on bool, true turns the payload CRC on, false turns it off + void setPayloadCRC(bool on); + + /// Return true if we are currently receiving a packet + bool isReceiving(); + +protected: + /// This is a low level function to handle the interrupts for one instance of RH_RF95. + /// Called automatically by isr*() + /// Should not need to be called by user code. + virtual void handleInterrupt(); + + /// Examine the revceive buffer to determine whether the message is for this node + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + +private: + /// Low level interrupt service routine for device connected to interrupt 0 + static void isr0(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr1(); + + /// Low level interrupt service routine for device connected to interrupt 1 + static void isr2(); + + /// Array of instances connected to interrupts 0 and 1 + static RH_RF95* _deviceForInterrupt[]; + + /// Index of next interrupt number to use in _deviceForInterrupt + static uint8_t _interruptCount; + + /// The configured interrupt pin connected to this instance + uint8_t _interruptPin; + + /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) + /// else 0xff + uint8_t _myInterruptIndex; + + // True if we are using the HF port (779.0 MHz and above) + bool _usingHFport; + + // Last measured SNR, dB + int8_t _lastSNR; + +protected: + /// Number of octets in the buffer + volatile uint8_t _bufLen; + + /// The receiver/transmitter buffer + uint8_t _buf[RH_RF95_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the buffer + volatile bool _rxBufValid; +}; + +/// @example rf95_client.pde +/// @example rf95_server.pde +/// @example rf95_encrypted_client.pde +/// @example rf95_encrypted_server.pde +/// @example rf95_reliable_datagram_client.pde +/// @example rf95_reliable_datagram_server.pde + +#endif + diff --git a/src/rf95/RadioHead.h b/src/rf95/RadioHead.h new file mode 100644 index 000000000..ed1551ff4 --- /dev/null +++ b/src/rf95/RadioHead.h @@ -0,0 +1,1595 @@ +// RadioHead.h +// Author: Mike McCauley (mikem@airspayce.com) DO NOT CONTACT THE AUTHOR DIRECTLY +// Copyright (C) 2014 Mike McCauley +// $Id: RadioHead.h,v 1.80 2020/01/05 07:02:23 mikem Exp mikem $ + +/*! \mainpage RadioHead Packet Radio library for embedded microprocessors + +This is the RadioHead Packet Radio library for embedded microprocessors. +It provides a complete object-oriented library for sending and receiving packetized messages +via a variety of common data radios and other transports on a range of embedded microprocessors. + +The version of the package that this documentation refers to can be downloaded +from http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.98.zip +You can find the latest version of the documentation at http://www.airspayce.com/mikem/arduino/RadioHead + +You can also find online help and discussion at +http://groups.google.com/group/radiohead-arduino +Please use that group for all questions and discussions on this topic. +Do not contact the author directly, unless it is to discuss commercial licensing. +Before asking a question or reporting a bug, please read +- http://en.wikipedia.org/wiki/Wikipedia:Reference_desk/How_to_ask_a_software_question +- http://www.catb.org/esr/faqs/smart-questions.html +- http://www.chiark.greenend.org.uk/~shgtatham/bugs.html + +Caution: Developing this type of software and using data radios +successfully is challenging and requires a substantial knowledge +base in software and radio and data transmission technologies and +theory. It may not be an appropriate project for beginners. If +you are a beginner, you will need to spend some time gaining +knowledge in these areas first. + +\par Overview + +RadioHead consists of 2 main sets of classes: Drivers and Managers. + +- Drivers provide low level access to a range of different packet radios and other packetized message transports. +- Managers provide high level message sending and receiving facilities for a range of different requirements. + +Every RadioHead program will have an instance of a Driver to +provide access to the data radio or transport, and usually a +Manager that uses that driver to send and receive messages for the +application. The programmer is required to instantiate a Driver +and a Manager, and to initialise the Manager. Thereafter the +facilities of the Manager can be used to send and receive +messages. + +It is also possible to use a Driver on its own, without a Manager, although this only allows unaddressed, +unreliable transport via the Driver's facilities. + +In some specialised use cases, it is possible to instantiate more than one Driver and more than one Manager. + +A range of different common embedded microprocessor platforms are supported, allowing your project to run +on your choice of processor. + +Example programs are included to show the main modes of use. + +\par Drivers + +The following Drivers are provided: + +- RH_RF22 +Works with Hope-RF +RF22B and RF23B based transceivers, and compatible chips and modules, +including the RFM22B transceiver module such as +hthis bare module: http://www.sparkfun.com/products/10153 +and this shield: http://www.sparkfun.com/products/11018 +and this board: http://www.anarduino.com/miniwireless +and RF23BP modules such as: http://www.anarduino.com/details.jsp?pid=130 +Supports GFSK, FSK and OOK. Access to other chip +features such as on-chip temperature measurement, analog-digital +converter, transmitter power control etc is also provided. + +- RH_RF24 +Works with Silicon Labs Si4460/4461/4463/4464 family of transceivers chip, and the equivalent +HopeRF RF24/26/27 family of chips and the HopeRF RFM24W/26W/27W modules. +Supports GFSK, FSK and OOK. Access to other chip +features such as on-chip temperature measurement, analog-digital +converter, transmitter power control etc is also provided. + +- RH_RF69 +Works with Hope-RF +RF69B based radio modules, such as the RFM69 module, (as used on the excellent Moteino and Moteino-USB +boards from LowPowerLab http://lowpowerlab.com/moteino/ ) +and compatible chips and modules such as RFM69W, RFM69HW, RFM69CW, RFM69HCW (Semtech SX1231, SX1231H). +Also works with Anarduino MiniWireless -CW and -HW boards http://www.anarduino.com/miniwireless/ including +the marvellous high powered MinWireless-HW (with 20dBm output for excellent range). +Supports GFSK, FSK. + +- RH_NRF24 +Works with Nordic nRF24 based 2.4GHz radio modules, such as nRF24L01 and others. +Also works with Hope-RF RFM73 +and compatible devices (such as BK2423). nRF24L01 and RFM73 can interoperate +with each other. + +- RH_NRF905 +Works with Nordic nRF905 based 433/868/915 MHz radio modules. + +- RH_NRF51 +Works with Nordic nRF51 compatible 2.4 GHz SoC/devices such as the nRF51822. +Also works with Sparkfun nRF52832 breakout board, with Arduino 1.6.13 and +Sparkfun nRF52 boards manager 0.2.3. Caution: although RadioHead compiles with nRF52832 as at 2019-06-06 +there appears to be a problem with the support of interupt handlers in the Sparkfun support libraries, +and drivers (ie most of the SPI based radio drivers) that require interrupts do not work correctly. + +- RH_RF95 +Works with Semtech SX1276/77/78/79, Modtronix inAir4 and inAir9, +and HopeRF RFM95/96/97/98 and other similar LoRa capable radios. +Supports Long Range (LoRa) with spread spectrum frequency hopping, large payloads etc. +FSK/GFSK/OOK modes are not (yet) supported. + +- RH_MRF89 +Works with Microchip MRF89XA and compatible transceivers. +and modules such as MRF89XAM9A. + +- RH_CC110 +Works with Texas Instruments CC110L transceivers and compatible modules such as +Anaren AIR BoosterPack 430BOOST-CC110L + +- RH_E32 +Works with EBYTE E32-TTL-1W serial radio transceivers (and possibly other transceivers in the same family) + +- RH_ASK +Works with a range of inexpensive ASK (amplitude shift keying) RF transceivers such as RX-B1 +(also known as ST-RX04-ASK) receiver; TX-C1 transmitter and DR3100 transceiver; FS1000A/XY-MK-5V transceiver; +HopeRF RFM83C / RFM85. Supports ASK (OOK). + +- RH_Serial +Works with RS232, RS422, RS485, RS488 and other point-to-point and multidropped serial connections, +or with TTL serial UARTs such as those on Arduino and many other processors, +or with data radios with a +serial port interface. RH_Serial provides packetization and error detection over any hardware or +virtual serial connection. Also builds and runs on Linux and OSX. + +- RH_TCP +For use with simulated sketches compiled and running on Linux. +Works with tools/etherSimulator.pl to pass messages between simulated sketches, allowing +testing of Manager classes on Linux and without need for real radios or other transport hardware. + +- RHEncryptedDriver +Adds encryption and decryption to any RadioHead transport driver, using any encrpytion cipher +supported by ArduinoLibs Cryptographic Library http://rweather.github.io/arduinolibs/crypto.html + +Drivers can be used on their own to provide unaddressed, unreliable datagrams. +All drivers have the same identical API. +Or you can use any Driver with any of the Managers described below. + +We welcome contributions of well tested and well documented code to support other transports. + +If your radio or transciever is not on the list above, there is a good chance it +wont work without modifying RadioHead to suit it. If you wish for +support for another radio or transciever, and you send 2 of them to +AirSpayce Pty Ltd, we will consider adding support for it. + +\par Managers + +The drivers above all provide for unaddressed, unreliable, variable +length messages, but if you need more than that, the following +Managers are provided: + +- RHDatagram +Addressed, unreliable variable length messages, with optional broadcast facilities. + +- RHReliableDatagram +Addressed, reliable, retransmitted, acknowledged variable length messages. + +- RHRouter +Multi-hop delivery of RHReliableDatagrams from source node to destination node via 0 or more +intermediate nodes, with manual, pre-programmed routing. + +- RHMesh +Multi-hop delivery of RHReliableDatagrams with automatic route discovery and rediscovery. + +Any Manager may be used with any Driver. + +\par Platforms + +A range of processors and platforms are supported: + +- Arduino and the Arduino IDE (version 1.0 to 1.8.1 and later) +Including Diecimila, Uno, Mega, Leonardo, Yun, Due, Zero etc. http://arduino.cc/, Also similar boards such as + - Moteino http://lowpowerlab.com/moteino/ + - Anarduino Mini http://www.anarduino.com/mini/ + - RedBearLab Blend V1.0 http://redbearlab.com/blend/ (with Arduino 1.0.5 and RedBearLab Blend Add-On version 20140701) + - MoteinoMEGA https://lowpowerlab.com/shop/moteinomega + (with Arduino 1.0.5 and the MoteinoMEGA Arduino Core + https://github.com/LowPowerLab/Moteino/tree/master/MEGA/Core) + - ESP8266 on Arduino IDE and Boards Manager per https://github.com/esp8266/Arduino + Tested using Arduino 1.6.8 with esp8266 by ESP8266 Community version 2.1.0 + Also Arduino 1.8.1 with esp8266 by SparkFun Electronics 2.5.2 + Examples serial_reliable_datagram_* and ask_* are shown to work. + CAUTION: The GHz radio included in the ESP8266 is + not yet supported. + CAUTION: tests here show that when powered by an FTDI USB-Serial converter, + the ESP8266 can draw so much power when transmitting on its GHz WiFi that VCC will sag + causing random crashes. We strongly recommend a large cap, say 1000uF 10V on VCC if you are also using the WiFi. + - Various Talk2 Whisper boards eg https://wisen.com.au/store/products/whisper-node-lora. + Use Arduino Board Manager to install the Talk2 code support. + - etc. + +- STM32 F4 Discover board, using Arduino 1.8.2 or later and + Roger Clarkes Arduino_STM from https://github.com/rogerclarkmelbourne/Arduino_STM32 + Caution: with this library and board, sending text to Serial causes the board to hang in mysterious ways. + Serial2 emits to PA2. The default SPI pins are SCK: PB3, MOSI PB5, MISO PB4. + We tested with PB0 as slave select and PB1 as interrupt pin for various radios. RH_ASK and RH_Serial also work. + +- ChipKIT Core with Arduino IDE on any ChipKIT Core supported Digilent processor (tested on Uno32) + http://chipkit.net/wiki/index.php?title=ChipKIT_core + +- Maple and Flymaple boards with libmaple and the Maple-IDE development environment + http://leaflabs.com/devices/maple/ and http://www.open-drone.org/flymaple + +- Teensy including Teensy 3.1 and earlier built using Arduino IDE 1.0.5 to 1.6.4 and later with + teensyduino addon 1.18 to 1.23 and later. + http://www.pjrc.com/teensy + +- Particle Photon https://store.particle.io/collections/photon and ARM3 based CPU with built-in + Wi-Fi transceiver and extensive IoT software suport. RadioHead does not support the built-in transceiver + but can be used to control other SPI based radios, Serial ports etc. + See below for details on how to build RadioHead for Photon + +- ATTiny built using Arduino IDE 1.8 and the ATTiny core from + https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json + using the instructions at + https://medium.com/jungletronics/attiny85-easy-flashing-through-arduino-b5f896c48189 + (Caution: these are very small processors and not all RadioHead features may be available, depending on memory requirements) + (Caution: we have not had good success building RH_ASK sketches for ATTiny 85 with SpenceKonde ATTinyCore) + +- AtTiny Mega chips supported by Spencer Konde's megaTinyCore (https://github.com/SpenceKonde/megaTinyCore) + (on Arduino 1.8.9 or later) such as AtTiny 3216, AtTiny 1616 etc. These chips can be easily programmed through their + UPDI pin, using an ordinary Arduino board programmed as a jtag2updi programmer as described in + https://github.com/SpenceKonde/megaTinyCore/blob/master/MakeUPDIProgrammer.md. + Make sure you set the programmer type to jtag2updi in the Arduino Tools->Programmer menu. + See https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/ImportantInfo.md for links to pinouts + and pin numbering information for all the suported chips. + +- nRF51 compatible Arm chips such as nRF51822 with Arduino 1.6.4 and later using the procedures + in http://redbearlab.com/getting-started-nrf51822/ + +- nRF52 compatible Arm chips such as as Adafruit BLE Feather board + https://www.adafruit.com/product/3406 + +- Adafruit Feather. These are excellent boards that are available with a variety of radios. We tested with the + Feather 32u4 with RFM69HCW radio, with Arduino IDE 1.6.8 and the Adafruit AVR Boards board manager version 1.6.10. + https://www.adafruit.com/products/3076 + +- Adafruit Feather M0 boards with Arduino 1.8.1 and later, using the Arduino and Adafruit SAMD board support. + https://learn.adafruit.com/adafruit-feather-m0-basic-proto/using-with-arduino-ide + +- ESP32 built using Arduino IDE 1.8.9 or later using the ESP32 toolchain installed per + https://github.com/espressif/arduino-esp32 + The internal 2.4GHz radio is not yet supported. Tested with RFM22 using SPI interface + +- Raspberry Pi + Uses BCM2835 library for GPIO http://www.airspayce.com/mikem/bcm2835/ + Currently works only with RH_NRF24 driver or other drivers that do not require interrupt support. + Contributed by Mike Poublon. + +- Linux and OSX + Using the RHutil/HardwareSerial class, the RH_Serial driver and any manager will + build and run on Linux and OSX. These can be used to build programs that talk securely and reliably to + Arduino and other processors or to other Linux or OSX hosts on a reliable, error detected (and possibly encrypted) datagram + protocol over various types of serial line. + +- Mongoose OS, courtesy Paul Austen. Mongoose OSis an Internet of Things Firmware Development Framework + available under Apache License Version 2.0. It supports low power, connected microcontrollers such as: + ESP32, ESP8266, TI CC3200, TI CC3220, STM32. + https://mongoose-os.com/ + +Other platforms are partially supported, such as Generic AVR 8 bit processors, MSP430. +We welcome contributions that will expand the range of supported platforms. + +If your processor is not on the list above, there is a good chance it +wont work without modifying RadioHead to suit it. If you wish for +support for another processor, and you send 2 of them to +AirSpayce Pty Ltd, we will consider adding support for it. + +RadioHead is available (through the efforts of others) +for PlatformIO. PlatformIO is a cross-platform code builder and the missing library manager. +http://platformio.org/#!/lib/show/124/RadioHead + +\par History + +RadioHead was created in April 2014, substantially based on code from some of our other earlier Radio libraries: + +- RHMesh, RHRouter, RHReliableDatagram and RHDatagram are derived from the RF22 library version 1.39. +- RH_RF22 is derived from the RF22 library version 1.39. +- RH_RF69 is derived from the RF69 library version 1.2. +- RH_ASK is based on the VirtualWire library version 1.26, after significant conversion to C++. +- RH_Serial was new. +- RH_NRF24 is based on the NRF24 library version 1.12, with some significant changes. + +During this combination and redevelopment, we have tried to retain all the processor dependencies and support from +the libraries that were contributed by other people. However not all platforms can be tested by us, so if you +find that support from some platform has not been successfully migrated, please feel free to fix it and send us a +patch. + +Users of RHMesh, RHRouter, RHReliableDatagram and RHDatagram in the previous RF22 library will find that their +existing code will run mostly without modification. See the RH_RF22 documentation for more details. + +\par Installation + +Install in the usual way: unzip the distribution zip file to the libraries +sub-folder of your sketchbook. +The example sketches will be visible in in your Arduino, mpide, maple-ide or whatever. +http://arduino.cc/en/Guide/Libraries + +\par Building for Particle Photon + +The Photon is not supported by the Arduino IDE, so it takes a little effort to set up a build environment. +Heres what we did to enable building of RadioHead example sketches on Linux, +but there are other ways to skin this cat. +Basic reference for getting started is: http://particle-firmware.readthedocs.org/en/develop/build/ +- Download the ARM gcc cross compiler binaries and unpack it in a suitable place: +\code +cd /tmp +wget https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 +tar xvf gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 +\endcode +- If dfu-util and friends not installed on your platform, download dfu-util and friends to somewhere in your path +\code +cd ~/bin +wget http://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-util +wget http://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-suffix +wget http://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-prefix +\endcode +- Download the Particle firmware (contains headers and libraries require to compile Photon sketches) + to a suitable place: +\code +cd /tmp +wget https://github.com/spark/firmware/archive/develop.zip +unzip develop.zip +\endcode +- Make a working area containing the RadioHead library source code and your RadioHead sketch. You must + rename the sketch from .pde or .ino to application.cpp +\code +cd /tmp +mkdir RadioHead +cd RadioHead +cp /usr/local/projects/arduino/libraries/RadioHead/ *.h . +cp /usr/local/projects/arduino/libraries/RadioHead/ *.cpp . +cp /usr/local/projects/arduino/libraries/RadioHead/examples/cc110/cc110_client/cc110_client.pde application.cpp +\endcode +- Edit application.cpp and comment out any \#include so it looks like: +\code + // #include +\endcode +- Connect your Photon by USB. Put it in DFU mode as descibed in Photon documentation. Light should be flashing yellow +- Compile the RadioHead sketch and install it as the user program (this does not update the rest of the + Photon firmware, just the user part: +\code +cd /tmp/firmware-develop/main +PATH=$PATH:/tmp/gcc-arm-none-eabi-5_2-2015q4/bin make APPDIR=/tmp/RadioHead all PLATFORM=photon program-dfu +\endcode +- You should see RadioHead compile without errors and download the finished sketch into the Photon. + +\par Compatible Hardware Suppliers + +We have had good experiences with the following suppliers of RadioHead compatible hardware: + +- LittleBird http://littlebirdelectronics.com.au in Australia for all manner of Arduinos and radios. +- LowPowerLab http://lowpowerlab.com/moteino in USA for the excellent Moteino and Moteino-USB + boards which include Hope-RF RF69B radios on-board. +- Anarduino and HopeRF USA (http://www.hoperfusa.com and http://www.anarduino.com) who have a wide range + of HopeRF radios and Arduino integrated modules. +- SparkFun https://www.sparkfun.com/ in USA who design and sell a wide range of Arduinos and radio modules. +- Wisen http://wisen.com.au who design and sell a wide range of integrated radio/processor modules including the + excellent Talk2 range. + +\par Coding Style + +RadioHead is designed so it can run on small processors with very +limited resources and strict timing contraints. As a result, we +tend only to use the simplest and least demanding (in terms of memory and CPU) C++ +facilities. In particular we avoid as much as possible dynamic +memory allocation, and the use of complex objects like C++ +strings, IO and buffers. We are happy with this, but we are aware +that some people may think we are leaving useful tools on the +table. You should not use this code as an example of how to do +generalised C++ programming on well resourced processors. + +\par Donations + +This library is offered under a free GPL license for those who want to use it that way. +We try hard to keep it up to date, fix bugs +and to provide free support. If this library has helped you save time or money, please consider donating at +http://www.airspayce.com or here: + +\htmlonly
\endhtmlonly + +\subpage packingdata "Passing Sensor Data Between RadioHead nodes" + +\par Trademarks + +RadioHead is a trademark of AirSpayce Pty Ltd. The RadioHead mark was first used on April 12 2014 for +international trade, and is used only in relation to data communications hardware and software and related services. +It is not to be confused with any other similar marks covering other goods and services. + +\par Copyright + +This software is Copyright (C) 2011-2018 Mike McCauley. Use is subject to license +conditions. The main licensing options available are GPL V2 or Commercial: + +\par 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 https://www.gnu.org/licenses/gpl-2.0.html + +\par 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. To purchase a commercial license, contact info@airspayce.com + +\par Revision History +\version 1.1 2014-04-14
+ Initial public release +\version 1.2 2014-04-23
+ Fixed various typos.
+ Added links to compatible Anarduino products.
+ Added RHNRFSPIDriver, RH_NRF24 classes to support Nordic NRF24 based radios. +\version 1.3 2014-04-28
+ Various documentation fixups.
+ RHDatagram::setThisAddress() did not set the local copy of thisAddress. Reported by Steve Childress.
+ Fixed a problem on Teensy with RF22 and RF69, where the interrupt pin needs to be set for input,
+ else pin interrupt doesn't work properly. Reported by Steve Childress and patched by + Adrien van den Bossche. Thanks.
+ Fixed a problem that prevented RF22 honouring setPromiscuous(true). Reported by Steve Childress.
+ Updated documentation to clarify some issues to do with maximum message lengths + reported by Steve Childress.
+ Added support for yield() on systems that support it (currently Arduino 1.5.5 and later) + so that spin-loops can suport multitasking. Suggested by Steve Childress.
+ Added RH_RF22::setGpioReversed() so the reversal it can be configured at run-time after + radio initialisation. It must now be called _after_ init(). Suggested by Steve Childress.
+\version 1.4 2014-04-29
+ Fixed further problems with Teensy compatibility for RH_RF22. Tested on Teensy 3.1. + The example/rf22_* examples now run out of the box with the wiring connections as documented for Teensy + in RH_RF22.
+ Added YIELDs to spin-loops in RHRouter, RHMesh and RHReliableDatagram, RH_NRF24.
+ Tested RH_Serial examples with Teensy 3.1: they now run out of the box.
+ Tested RH_ASK examples with Teensy 3.1: they now run out of the box.
+ Reduced default SPI speed for NRF24 from 8MHz to 1MHz on Teensy, to improve reliability when + poor wiring is in use.
+ on some devices such as Teensy.
+ Tested RH_NRF24 examples with Teensy 3.1: they now run out of the box.
+\version 1.5 2014-04-29
+ Added support for Nordic Semiconductor nRF905 transceiver with RH_NRF905 driver. Also + added examples for nRF905 and tested on Teensy 3.1 +\version 1.6 2014-04-30
+ NRF905 examples were missing +\version 1.7 2014-05-03
+ Added support for Arduino Due. Tested with RH_NRF905, RH_Serial, RH_ASK. + IMPORTANT CHANGE to interrupt pins on Arduino with RH_RF22 and RH_RF69 constructors: + previously, you had to specify the interrupt _number_ not the interrupt _pin_. Arduinos and Uno32 + are now consistent with all other platforms: you must specify the interrupt pin number. Default + changed to pin 2 (a common choice with RF22 shields). + Removed examples/maple/maple_rf22_reliable_datagram_client and + examples/maple/maple_rf22_reliable_datagram_client since the rf22 examples now work out + of the box with Flymaple. + Removed examples/uno32/uno32_rf22_reliable_datagram_client and + examples/uno32/uno32_rf22_reliable_datagram_client since the rf22 examples now work out + of the box with ChipKit Uno32. +\version 1.8 2014-05-08
+ Added support for YIELD in Teensy 2 and 3, suggested by Steve Childress.
+ Documentation updates. Clarify use of headers and Flags
+ Fixed misalignment in RH_RF69 between ModemConfigChoice definitions and the implemented choices + which meant you didnt get the choice you thought and GFSK_Rb55555Fd50 hung the transmitter.
+ Preliminary work on Linux simulator. +\version 1.9 2014-05-14
+ Added support for using Timer 2 instead of Timer 1 on Arduino in RH_ASK when + RH_ASK_ARDUINO_USE_TIMER2 is defined. With the kind assistance of + Luc Small. Thanks!
+ Updated comments in RHReliableDatagram concerning servers, retries, timeouts and delays. + Fixed an error in RHReliableDatagram where recvfrom return value was not checked. + Reported by Steve Childress.
+ Added Linux simulator support so simple RadioHead sketches can be compiled and run on Linux.
+ Added RH_TCP driver to permit message passing between simulated sketches on Linux.
+ Added example simulator sketches.
+ Added tools/etherSimulator.pl, a simulator of the 'Luminiferous Ether' that passes + messages between simulated sketches and can simulate random message loss etc.
+ Fixed a number of typos and improved some documentation.
+\version 1.10 2014-05-15
+ Added support for RFM73 modules to RH_NRF24. These 2 radios are very similar, and can interoperate + with each other. Added new RH_NRF24::TransmitPower enums for the RFM73, which has a different + range of available powers
+ reduced the default SPI bus speed for RH_NRF24 to 1MHz, since so many modules and CPU have problems + with 8MHz.
+\version 1.11 2014-05-18
+ Testing RH_RF22 with RFM23BP and 3.3V Teensy 3.1 and 5V Arduinos. + Updated documentation with respect to GPIO and antenna + control pins for RFM23. Updated documentation with respect to transmitter power control for RFM23
+ Fixed a problem with RH_RF22 driver, where GPIO TX and RX pins were not configured during + initialisation, causing poor transmit power and sensitivity on those RF22/RF23 devices where GPIO controls + the antenna selection pins. +\version 1.12 2014-05-20
+ Testing with RF69HW and the RH_RF69 driver. Works well with the Anarduino MiniWireless -CW and -HW + boards http://www.anarduino.com/miniwireless/ including + the marvellous high powered MinWireless-HW (with 20dBm output for excellent range).
+ Clarified documentation of RH_RF69::setTxPower values for different models of RF69.
+ Added RHReliableDatagram::resetRetransmissions().
+ Retransmission count precision increased to uin32_t.
+ Added data about actual power measurements from RFM22 module.
+\version 1.13 2014-05-23
+ setHeaderFlags(flags) changed to setHeaderFlags(set, clear), enabling any flags to be + individually set and cleared by either RadioHead or application code. Requested by Steve Childress.
+ Fixed power output setting for boost power on RF69HW for 18, 19 and 20dBm.
+ Added data about actual power measurements from RFM69W and RFM69HW modules.
+\version 1.14 2014-05-26
+ RH_RF69::init() now always sets the PA boost back to the default settings, else can get invalid + PA power modes after uploading new sketches without a power cycle. Reported by Bryan.
+ Added new macros RH_VERSION_MAJOR RH_VERSION_MINOR, with automatic maintenance in Makefile.
+ Improvements to RH_TCP: constructor now honours the server argument in the form "servername:port".
+ Added YIELD to RHReliableDatagram::recvfromAckTimeout. Requested by Steve Childress.
+ Fixed a problem with RH_RF22 reliable datagram acknowledgements that was introduced in version 1.13. + Reported by Steve Childress.
+\version 1.15 2014-05-27
+ Fixed a problem with the RadioHead .zip link. +\version 1.16 2014-05-30
+ Fixed RH_RF22 so that lastRssi() returns the signal strength in dBm. Suggested by Steve Childress.
+ Added support for getLastPreambleTime() to RH_RF69. Requested by Steve Childress.
+ RH_NRF24::init() now checks if there is a device connected and responding, else init() will fail. + Suggested by Steve Brown.
+ RHSoftwareSPI now initialises default values for SPI pins MOSI = 12, MISO = 11 and SCK = 13.
+ Fixed some problems that prevented RH_NRF24 working with mixed software and hardware SPI + on different devices: a race condition + due to slow SPI transfers and fast acknowledgement.
+\version 1.17 2014-06-02
+ Fixed a debug typo in RHReliableDatagram that was introduced in 1.16.
+ RH_NRF24 now sets default power, data rate and channel in init(), in case another + app has previously set different values without powerdown.
+ Caution: there are still problems with RH_NRF24 and Software SPI. Do not use.
+\version 1.18 2014-06-02
+ Improvements to performance of RH_NRF24 statusRead, allowing RH_NRF24 and Software SPI + to operate on slow devices like Arduino Uno.
+\version 1.19 2014-06-19
+ Added examples ask_transmitter.pde and ask_receiver.pde.
+ Fixed an error in the RH_RF22 doc for connection of Teensy to RF22.
+ Improved documentation of start symbol bit patterns in RH_ASK.cpp +\version 1.20 2014-06-24
+ Fixed a problem with compiling on platforms such as ATTiny where SS is not defined.
+ Added YIELD to RHMesh::recvfromAckTimeout().
+\version 1.21 2014-06-24
+ Fixed an issue in RH_Serial where characters might be lost with back-to-back frames. + Suggested by Steve Childress.
+ Brought previous RHutil/crc16.h code into mainline RHCRC.cpp to prevent name collisions + with other similarly named code in other libraries. Suggested by Steve Childress.
+ Fix SPI bus speed errors on 8MHz Arduinos. +\version 1.22 2014-07-01
+ Update RH_ASK documentation for common wiring connections.
+ Testing RH_ASK with HopeRF RFM83C/RFM85 courtesy Anarduino http://www.anarduino.com/
+ Testing RH_NRF24 with Itead Studio IBoard Pro http://imall.iteadstudio.com/iboard-pro.html + using both hardware SPI on the ITDB02 Parallel LCD Module Interface pins and software SPI + on the nRF24L01+ Module Interface pins. Documented wiring required.
+ Added support for AVR 1284 and 1284p, contributed by Peter Scargill. + Added support for Semtech SX1276/77/78 and HopeRF RFM95/96/97/98 and other similar LoRa capable radios + in LoRa mode only. Tested with the excellent MiniWirelessLoRa from + Anarduino http://www.anarduino.com/miniwireless
+\version 1.23 2014-07-03
+ Changed the default modulation for RH_RF69 to GFSK_Rb250Fd250, since the previous default + was not very reliable.
+ Documented RH_RF95 range tests.
+ Improvements to RH_RF22 RSSI readings so that lastRssi correctly returns the last message in dBm.
+\version 1.24 2014-07-18 + Added support for building RadioHead for STM32F4 Discovery boards, using the native STM Firmware libraries, + in order to support Codec2WalkieTalkie (http://www.airspayce.com/mikem/Codec2WalkieTalkie) + and other projects. See STM32ArduinoCompat.
+ Default modulation for RH_RF95 was incorrectly set to a very slow Bw125Cr48Sf4096 +\version 1.25 2014-07-25 + The available() function will longer terminate any current transmission, and force receive mode. + Now, if there is no unprocessed incoming message and an outgoing message is currently being transmitted, + available() will return false.
+ RHRouter::sendtoWait(uint8_t*, uint8_t, uint8_t, uint8_t) renamed to sendtoFromSourceWait due to conflicts + with new sendtoWait() with optional flags.
+ RHMEsh and RHRouter already supported end-to-end application layer flags, but RHMesh::sendtoWait() + and RHRouter::sendToWait have now been extended to expose a way to send optional application layer flags. +\version 1.26 2014-08-12 + Fixed a Teensy 2.0 compile problem due yield() not available on Teensy < 3.0.
+ Adjusted the algorithm of RH_RF69::temperatureRead() to more closely reflect reality.
+ Added functions to RHGenericDriver to get driver packet statistics: rxBad(), rxGood(), txGood().
+ Added RH_RF69::printRegisters().
+ RH_RF95::printRegisters() was incorrectly printing the register index instead of the address. + Reported by Phang Moh Lim.
+ RH_RF95, added definitions for some more registers that are usable in LoRa mode.
+ RH_RF95::setTxPower now uses RH_RF95_PA_DAC_ENABLE to achieve 21, 22 and 23dBm.
+ RH_RF95, updated power output measurements.
+ Testing RH_RF69 on Teensy 3.1 with RF69 on PJRC breakout board. OK.
+ Improvements so RadioHead will build under Arduino where SPI is not supported, such as + ATTiny.
+ Improvements so RadioHead will build for ATTiny using Arduino IDE and tinycore arduino-tiny-0100-0018.zip.
+ Testing RH_ASK on ATTiny85. Reduced RAM footprint. + Added helpful documentation. Caution: RAM memory is *very* tight on this platform.
+ RH_RF22 and RH_RF69, added setIdleMode() function to allow the idle mode radio operating state + to be controlled for lower idle power consumption at the expense of slower transitions to TX and RX.
+\version 1.27 2014-08-13 + All RH_RF69 modulation schemes now have data whitening enabled by default.
+ Tested and added a number of OOK modulation schemes to RH_RF69 Modem config table.
+ Minor improvements to a number of the faster RH_RF69 modulation schemes, but some slower ones + are still not working correctly.
+\version 1.28 2014-08-20 + Added new RH_RF24 driver to support Si446x, RF24/26/26, RFM24/26/27 family of transceivers. + Tested with the excellent + Anarduino Mini and RFM24W and RFM26W with the generous assistance of the good people at + Anarduino http://www.anarduino.com. +\version 1.29 2014-08-21 + Fixed a compile error in RH_RF24 introduced at the last minute in hte previous release.
+ Improvements to RH_RF69 modulation schemes: now include the AFCBW in teh ModemConfig.
+ ModemConfig RH_RF69::FSK_Rb2Fd5 and RH_RF69::GFSK_Rb2Fd5 are now working.
+\version 1.30 2014-08-25 + Fixed some compile problems with ATtiny84 on Arduino 1.5.5 reported by Glen Cook.
+\version 1.31 2014-08-27 + Changed RH_RF69 FSK and GFSK modulations from Rb2_4Fd2_4 to Rb2_4Fd4_8 and FSK_Rb4_8Fd4_8 to FSK_Rb4_8Fd9_6 + since the previous ones were unreliable (they had modulation indexes of 1).
+\version 1.32 2014-08-28 + Testing with RedBearLab Blend board http://redbearlab.com/blend/. OK.
+ Changed more RH_RF69 FSK and GFSK slowish modulations to have modulation index of 2 instead of 1. + This required chnaging the symbolic names.
+\version 1.33 2014-09-01 + Added support for sleep mode in RHGeneric driver, with new mode + RHModeSleep and new virtual function sleep().
+ Added support for sleep to RH_RF69, RH_RF22, RH_NRF24, RH_RF24, RH_RF95 drivers.
+\version 1.34 2014-09-19 + Fixed compile errors in example rf22_router_test.
+ Fixed a problem with RH_NRF24::setNetworkAddress, also improvements to RH_NRF24 register printing. + Patched by Yveaux.
+ Improvements to RH_NRF24 initialisation for version 2.0 silicon.
+ Fixed problem with ambigiguous print call in RH_RFM69 when compiling for Codec2.
+ Fixed a problem with RH_NRF24 on RFM73 where the LNA gain was not set properly, reducing the sensitivity + of the receiver. +\version 1.35 2014-09-19 + Fixed a problem with interrupt setup on RH_RF95 with Teensy3.1. Reported by AD.
+\version 1.36 2014-09-22 + Improvements to interrupt pin assignments for __AVR_ATmega1284__ and__AVR_ATmega1284P__, provided by + Peter Scargill.
+ Work around a bug in Arduino 1.0.6 where digitalPinToInterrupt is defined but NOT_AN_INTERRUPT is not.
+ \version 1.37 2014-10-19 + Updated doc for connecting RH_NRF24 to Arduino Mega.
+ Changes to RHGenericDriver::setHeaderFlags(), so that the default for the clear argument + is now RH_FLAGS_APPLICATION_SPECIFIC, which is less surprising to users. + Testing with the excellent MoteinoMEGA from LowPowerLab + https://lowpowerlab.com/shop/moteinomega with on-board RFM69W. + \version 1.38 2014-12-29 + Fixed compile warning on some platforms where RH_RF24::send and RH_RF24::writeTxFifo + did not return a value.
+ Fixed some more compiler warnings in RH_RF24 on some platforms.
+ Refactored printRegisters for some radios. Printing to Serial + is now controlled by the definition of RH_HAVE_SERIAL.
+ Added partial support for ARM M4 w/CMSIS with STM's Hardware Abstraction lib for + Steve Childress.
+ \version 1.39 2014-12-30 + Fix some compiler warnings under IAR.
+ RH_HAVE_SERIAL and Serial.print calls removed for ATTiny platforms.
+ \version 1.40 2015-03-09 + Added notice about availability on PlatformIO, thanks to Ivan Kravets.
+ Fixed a problem with RH_NRF24 where short packet lengths would occasionally not be trasmitted + due to a race condition with RH_NRF24_TX_DS. Reported by Mark Fox.
+ \version 1.41 2015-03-29 + RH_RF22, RH_RF24, RH_RF69 and RH_RF95 improved to allow driver.init() to be called multiple + times without reallocating a new interrupt, allowing the driver to be reinitialised + after sleeping or powering down. + \version 1.42 2015-05-17 + Added support for RH_NRF24 driver on Raspberry Pi, using BCM2835 + library for GPIO pin IO. Contributed by Mike Poublon.
+ Tested RH_NRF24 module with NRF24L01+PA+LNA SMA Antenna Wireless Transceiver modules + similar to: http://www.elecfreaks.com/wiki/index.php?title=2.4G_Wireless_nRF24L01p_with_PA_and_LNA + works with no software changes. Measured max power output 18dBm.
+ \version 1.43 2015-08-02 + Added RH_NRF51 driver to support Nordic nRF51 family processor with 2.4GHz radio such + as nRF51822, to be built on Arduino 1.6.4 and later. Tested with RedBearLabs nRF51822 board + and BLE Nano kit
+ \version 1.44 2015-08-08 + Fixed errors with compiling on some platforms without serial, such as ATTiny. + Reported by Friedrich Müller.
+ \version 1.45 2015-08-13 + Added support for using RH_Serial on Linux and OSX (new class RHutil/HardwareSerial + encapsulates serial ports on those platforms). Example examples/serial upgraded + to build and run on Linux and OSX using the tools/simBuild builder. + RHMesh, RHRouter and RHReliableDatagram updated so they can use RH_Serial without + polling loops on Linux and OSX for CPU efficiency.
+ \version 1.46 2015-08-14 + Amplified some doc concerning Linux and OSX RH_Serial. Added support for 230400 + baud rate in HardwareSerial.
+ Added sample sketches nrf51_audio_tx and nrf51_audio_rx which show how to + build an audio TX/RX pair with RedBear nRF51822 boards and a SparkFun MCP4725 DAC board. + Uses the built-in ADC of the nRF51822 to sample audio at 5kHz and transmit packets + to the receiver which plays them via the DAC.
+\version 1.47 2015-09-18 + Removed top level Makefile from distribution: its only used by the developer and + its presence confuses some people.
+ Fixed a problem with RHReliableDatagram with some versions of Raspberry Pi random() that causes + problems: random(min, max) sometimes exceeds its max limit. +\version 1.48 2015-09-30 + Added support for Arduino Zero. Tested on Arduino Zero Pro. +\version 1.49 2015-10-01 + Fixed problems that prevented interrupts working correctly on Arduino Zero and Due. + Builds and runs with 1.6.5 (with 'Arduino SAMD Boards' for Zero version 1.6.1) from arduino.cc. + Arduino version 1.7.7 from arduino.org is not currently supported. +\version 1.50 2015-10-25 + Verified correct building and operation with Arduino 1.7.7 from arduino.org. + Caution: You must burn the bootloader from 1.7.7 to the Arduino Zero before it will + work with Arduino 1.7.7 from arduino.org. Conversely, you must burn the bootloader from 1.6.5 + to the Arduino Zero before it will + work with Arduino 1.6.5 from arduino.cc. Sigh. + Fixed a problem with RH_NRF905 that prevented the power and frequency ranges being set + properly. Reported by Alan Webber. +\version 1.51 2015-12-11 + Changes to RH_RF6::setTxPower() to be compatible with SX1276/77/78/79 modules that + use RFO transmitter pins instead of PA_BOOST, such as the excellent + Modtronix inAir4 http://modtronix.com/inair4.html + and inAir9 modules http://modtronix.com/inair9.html. With the kind assistance of + David from Modtronix. +\version 1.52 2015-12-17 + Added RH_MRF89 module to suport Microchip MRF89XA and compatible transceivers. + and modules.
+\version 1.53 2016-01-02 + Added RH_CC110 module to support Texas Instruments CC110L and compatible transceivers and modules.
+\version 1.54 2016-01-29 + Added support for ESP8266 processor on Arduino IDE. Examples serial_reliable_datagram_* are shown to work. + CAUTION: SPI not supported yet. Timers used by RH_ASK are not tested. + The GHz radio included in the ESP8266 is not yet supported. +\version 1.55 2016-02-12 + Added macros for htons() and friends to RadioHead.h. + Added example sketch serial_gateway.pde. Acts as a transparent gateway between RH_RF22 and RH_Serial, + and with minor mods acts as a universal gateway between any 2 RadioHead driver networks. + Initial work on supporting STM32 F2 on Particle Photon: new platform type defined. + Fixed many warnings exposed by test building for Photon. + Particle Photon tested support for RH_Serial, RH_ASK, SPI, RH_CC110 etc. + Added notes on how to build RadioHead sketches for Photon. +\version 1.56 2016-02-18 + Implemented timers for RH_ASK on ESP8266, added some doc on IO pin selection. +\version 1.57 2016-02-23 + Fixed an issue reported by S3B, where RH_RF22 would sometimes not clear the rxbufvalid flag. +\version 1.58 2-16-04-04 + Tested RH_RF69 with Arduino Due. OK. Updated doc.
+ Added support for all ChipKIT Core supported boards + http://chipkit.net/wiki/index.php?title=ChipKIT_core + Tested on ChipKIT Uno32.
+ Digilent Uno32 under the old MPIDE is no longer formally + supported but may continue to work for some time.
+\version 1.59 2016-04-12 + Testing with the excellent Rocket Scream Mini Ultra Pro with the RFM95W and RFM69HCW modules from + http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ (915MHz versions). Updated + documentation with hints to suit. Caution: requires Arduino 1.6.8 and Arduino SAMD Boards 1.6.5. + See also http://www.rocketscream.com/blog/2016/03/10/radio-range-test-with-rfm69hcw/ + for the vendors tests and range with the RFM69HCW version. They also have an RF95 version equipped with + TCXO temperature controllled oscillator for extra frequency stability and support of very slow and + long range protocols. + These boards are highly recommended. They also include battery charging support. +\version 1.60 2016-06-25 + Tested with the excellent talk2 Whisper Node boards + (https://talk2.wisen.com.au/ and https://bitbucket.org/talk2/), + an Arduino Nano compatible board, which include an on-board RF69 radio, external antenna, + run on 2xAA batteries and support low power operations. RF69 examples work without modification. + Added support for ESP8266 SPI, provided by David Skinner. +\version 1.61 2016-07-07 + Patch to RH_ASK.cpp for ESP8266, to prevent crashes in interrupt handlers. Patch from Alexander Mamchits. +\version 1.62 2016-08-17 + Fixed a problem in RH_ASK where _rxInverted was not properly initialised. Reported by "gno.sun.sop". + Added support for waitCAD() and isChannelActive() and setCADTimeout() to RHGeneric. + Implementation of RH_RF95::isChannelActive() allows the RF95 module to support + Channel Activity Detection (CAD). Based on code contributed by Bent Guldbjerg Christensen. + Implmentations of isChannelActive() plus documentation for other radio modules wil be welcomed. +\version 1.63 2016-10-20 + Testing with Adafruit Feather 32u4 with RFM69HCW. Updated documentation to reflect.
+\version 1.64 2016-12-10 + RHReliableDatagram now initialises _seenids. Fix from Ben Lim.
+ In RH_NRF51, added get_temperature().
+ In RH_NRF51, added support for AES packet encryption, which required a slight change + to the on-air message format.
+\version 1.65 2017-01-11 + Fixed a race condition with RH_NRF51 that prevented ACKs being reliably received.
+ Removed code in RH_NRF51 that enabled the DC-DC converter. This seems not to be a necessary condition + for the radio to work and is now left to the application if that is required.
+ Proven interoperation between nRF51822 and nRF52832.
+ Modification and testing of RH_NRF51 so it works with nRF52 family processors, + such Sparkfun nRF52832 breakout board, with Arduino 1.6.13 and + Sparkfun nRF52 boards manager 0.2.3 using the procedures outlined in + https://learn.sparkfun.com/tutorials/nrf52832-breakout-board-hookup-guide
+ Caution, the Sparkfun development system for Arduino is still immature. We had to + rebuild the nrfutil program since the supplied one was not suitable for + the Linux host we were developing on. See https://forum.sparkfun.com/viewtopic.php?f=32&t=45071 + Also, after downloading a sketch in the nRF52832, the program does not start executing cleanly: + you have to reset the processor again by pressing the reset button. + This appears to be a problem with nrfutil, rather than a bug in RadioHead. +\version 1.66 2017-01-15 + Fixed some errors in (unused) register definitions in RH_RF95.h.
+ Fixed a problem that caused compilation errors in RH_NRF51 if the appropriate board + support was not installed. +\version 1.67 2017-01-24 + Added RH_RF95::frequencyError() to return the estimated centre frequency offset in Hz + of the last received message +\version 1.68 2017-01-25 + Fixed arithmetic error in RH_RF95::frequencyError() for some platforms. +\version 1.69 2017-02-02 + Added RH_RF95::lastSNR() and improved lastRssi() calculations per the manual. +\version 1.70 2017-02-03 + Added link to Binpress commercial license purchasing. +\version 1.71 2017-02-07 + Improved support for STM32. Patch from Bent Guldbjerg Christensen. +\version 1.72 2017-03-02 + In RH_RF24, fixed a problem where some important properties were not set by the ModemConfig. + Added properties 2007, 2008, 2009. Also properties 200a was not being set in the chip. + Reported by Shannon Bailey and Alan Adamson. + Fixed corresponding convert.pl and added it to the distribution. +\version 1.73 2017-03-04 + Significant changes to RH_RF24 and its API. It is no longer possible to change the modulation scheme + programatically: it proved impossible to cater for all the possible crystal frequencies, + base frequency and modulation schemes. Instead you can use one of a small set of supplied radio + configuration header files, or generate your own with Silicon Labs WDS application. Changing + modulation scheme required editing RH_RF24.cpp to specify the appropriate header and recompiling. + convert.pl is now redundant and removed from the distribution. +\version 1.74 2017-03-08 + Changed RHReliableDatagram so it would not ACK messages heard addressed to other nodes + in promiscuous mode.
+ Added RH_RF24::deviceType() to return the integer value of the connected device.
+ Added documentation about how to connect RFM69 to an ESP8266. Tested OK.
+ RH_RF24 was not correctly changing state in sleep() and setModeIdle().
+ Added example rf24_lowpower_client.pde showing how to put an arduino and radio into a low power + mode between transmissions to save battery power.
+ Improvements to RH_RF69::setTxPower so it now takes an optional ishighpowermodule + flag to indicate if the connected module is a high power RFM69HW, and so set the power level + correctly. Based on code contributed by bob. +\version 1.75 2017-06-22 + Fixed broken compiler issues with RH_RF95::frequencyError() reported by Steve Rogerson.
+ Testing with the very excellent Rocket Scream boards equipped with RF95 TCXO modules. The + temperature controlled oscillator stabilises the chip enough to be able to use even the slowest + protocol Bw125Cr48Sf4096. Caution, the TCXO model radios are not low power when in sleep (consuming + about ~600 uA, reported by Phang Moh Lim).
+ Added support for EBYTE E32-TTL-1W and family serial radio transceivers. These RF95 LoRa based radios + can deliver reliable messages at up to 7km measured. +\version 1.76 2017-06-23 + Fixed a problem with RH_RF95 hanging on transmit under some mysterious circumstances. + Reported by several people at https://forum.pjrc.com/threads/41878-Probable-race-condition-in-Radiohead-library?p=146601#post146601
+ Increased the size of rssi variables to 16 bits to permit RSSI less than -128 as reported by RF95. +\version 1.77 2017-06-25 + Fixed a compilation error with lastRssi().
+\version 1.78 2017-07-19 + Fixed a number of unused variable warnings from g++.
+ Added new module RHEncryptedDriver and examples, contributed by Philippe Rochat, which + adds encryption and decryption to any RadioHead transport driver, using any encryption cipher + supported by ArduinoLibs Cryptographic Library http://rweather.github.io/arduinolibs/crypto.html + Includes several examples.
+\version 1.79 2017-07-25 + Added documentation about 'Passing Sensor Data Between RadioHead nodes'.
+ Changes to RH_CC110 driver to calculate RSSI in dBm, based on a patch from Jurie Pieterse.
+ Added missing passthroughmethoids to RHEncryptedDriver, allowing it to be used with RHDatagram, + RHReliableDatagram etc. Tested with RH_Serial. Added examples +\version 1.80 2017-10-04 + Testing with the very fine Talk2 Whisper Node LoRa boards https://wisen.com.au/store/products/whisper-node-lora + an Arduino compatible board, which include an on-board RFM95/96 LoRa Radio (Semtech SX1276), external antenna, + run on 2xAAA batteries and support low power operations. RF95 examples work without modification. + Use Arduino Board Manager to install the Talk2 code support. Upload the code with an FTDI adapter set to 5V.
+ Added support for SPI transactions in development environments that support it with SPI_HAS_TRANSACTION. + Tested on ESP32 with RFM-22 and Teensy 3.1 with RF69 + Added support for ESP32, tested with RFM-22 connected by SPI.
+\version 1.81 2017-11-15 + RH_CC110, moved setPaTable() from protected to public.
+ RH_RF95 modem config Bw125Cr48Sf4096 altered to enable slow daat rate in register 26 + as suggested by Dieter Kneffel. + Added support for nRF52 compatible Arm chips such as as Adafruit BLE Feather board + https://www.adafruit.com/product/3406, with a patch from Mike Bell.
+ Fixed a problem where rev 1.80 broke Adafruit M0 LoRa support by declaring + bitOrder variable always as a unsigned char. Reported by Guilherme Jardim.
+ In RH_RF95, all modes now have AGC enabled, as suggested by Dieter Kneffel.
+\version 1.82 2018-01-07 + Added guard code to RH_NRF24::waitPacketSent() so that if the transmit never completes for some + reason, the code will eventually return with FALSE. + Added the low-datarate-optimization bit to config for RH_RF95::Bw125Cr48Sf4096. + Fix from Jurie Pieterse to ensure RH_CC110::sleep always enters sleep mode. + Update ESP32 support to include ASK timers. RH_ASK module is now working on ESP32. +\version 1.83 2018-02-12 + Testing adafruit M0 Feather with E32. Updated RH_E32 documentation to show suggested connections + and contructor initialisation.
+ Fixed a problem with RHEncryptedDriver that could cause a crash on some platforms when used + with RHReliableDatagram. Reported by Joachim Baumann.
+ Improvments to doxygen doc layout in RadioHead.h +\version 1.84 2018-05-07 + Compiles with Roger Clarkes Arduino_STM32 https://github.com/rogerclarkmelbourne/Arduino_STM32, + to support STM32F103C etc, and STM32 F4 Discovery etc.
+ Tested STM32 F4 Discovery board with RH_RF22, RH_ASK and RH_Serial. + +\version 1.85 2018-07-09 + RHGenericDriver methods changed to virtual, to allow overriding by RHEncrypredDriver: + lastRssi(), mode(), setMode(). Reported by Eyal Gal.
+ Fixed a problem with compiling RH_E32 on some older IDEs, contributed by Philippe Rochat.
+ Improvements to RH_RF95 to improve detection of bad packets, contributed by PiNi.
+ Fixed an error in RHEncryptedDriver that caused incorrect message lengths for messages multiples of 16 bytes + when STRICT_CONTENT_LEN is defined.
+ Fixed a bug in RHMesh which causes the creation of a route to the address which is the byte + behind the end of the route array. Reported by Pascal Gillès de Pélichy.
+\version 1.86 2018-08-28 + Update commercial licensing, remove binpress. +\version 1.87 2018-10-06 + RH_RF22 now resets all registers to default state before initialisation commences. Suggested by Wothke.
+ Added RH_ENABLE_EXPLICIT_RETRY_DEDUP which improves the handling of duplicate detection especiually + in the case where a transmitter periodically wakes up and start tranmitting from the first sequence number. + Patch courtesy Justin Newitter. Thanks. +\version 1.88 2018-11-13 + Updated to support ATTiny using instructions in + https://medium.com/jungletronics/attiny85-easy-flashing-through-arduino-b5f896c48189 + Updated examples ask_transmitter and ask_receiver to compile cleanly on ATTiny. + Tested using ATTiny85 and Arduino 1.8.1.
+\version 1.89 2018-11-15 + Testing with ATTiny core from https://github.com/SpenceKonde/ATTinyCore and RH_ASK, + using example ask_transmitter. This resulted in 'Low Memory, instability may occur', + and the resulting sketch would transmit only one packet. Suggest ATTiny users do not use this core, but use + the one from https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json + as described in https://medium.com/jungletronics/attiny85-easy-flashing-through-arduino-b5f896c48189
+ Added support for RH_RF95::setSpreadingFactor(), RH_RF95::setSignalBandwidth(), RH_RF95::setLowDatarate() and + RH_RF95::setPayloadCRC(). Patch from Brian Norman. Thanks.
+ +\version 1.90 2019-05-21 + Fixed a block size error in RhEncryptedDriver for the case when + using STRICT_CONTENT_LEN and sending messages of exactly _blockcipher.blockSize() bytes in length. + Reported and patched by Philippe Rochat. + Patch from Samuel Archibald to prevent compile errors with RH_AAK.cpp fo ATSAMD51. + Fixed a probem in RH_RF69::setSyncWords that prevented setSyncWords(NULL, 0) correctly + disabling sync detection and generation. Reported by Federico Maggi. + RHHardwareSPI::usingInterrupt() was a noop. Fixed to call SPI.usingInterrupt(interrupt);. + +\version 1.91 2019-06-01 + Fixed a problem with new RHHardwareSPI::usingInterrupt() that prevented compilation on ESP8266 + which does not have that call. + +\version 1.92 2019-07-14 + Retested serial_reliable_datagram_client.pde and serial_reliable_datagram_server.pde built on Linux + as described in their headers, and with USB-RS485 adapters. No changes, working correctly. + Testing of nRF5232 with Sparkfun nRF52 board support 0.2.3 shows that there appears to be a problem with + interrupt handlers on this board, and none of the interrupt based radio drivers can be expected to work + with this chip. + Ensured all interrupt routines are flagged with ICACHE_RAM_ATTR when compiled for ESP8266, to prevent crashes. + +\version 1.94 2019-09-02 + Fixed a bug in RHSoftwareSPI where RHGenericSPI::setBitOrder() has no effect for + on RHSoftwareSPI. Reported by Peter.
+ Added support in RHRouter for a node to optionally be leaf node, and not participate as a router in the + network. See RHRouter::setNodeTypePatch from Alex Evans.
+ Fixed a problem with ESP32 causing compile errors over missing SPI.usingInterrupt().
+ +\version 1.95 2019-10-14 + Fixed some typos in RH_RF05.h macro definitions reported by Clayton Smith.
+ Patch from Michael Cain from RH_ASK on ESP32, untested by me.
+ Added support for RPi Zero and Zero W for the RF95, contributed by Brody Mahoney. + Not tested by me.
+ +\version 1.96 2019-10-14 + Added examples for RPi Zero and Zero W to examples/raspi/rf95, contributed by Brody Mahoney + not tested by me.
+ +\version 1.97 2019-11-02 + Added support for Mongoose OS, contributed by Paul Austen. + +\version 1.98 2020-01-06 + Rationalised use of RH_PLATFORM_ATTINY to be consistent with other platforms.
+ Added support for RH_PLATFORM_ATTINY_MEGA, for use with Spencer Konde's megaTinyCore + https://github.com/SpenceKonde/megaTinyCore on Atmel megaAVR AtTiny chips. + Tested with AtTiny 3217, 3216 and 1614, using + RH_Serial, RH_ASK, and RH_RF22 drivers.
+ + +\author Mike McCauley. DO NOT CONTACT THE AUTHOR DIRECTLY. USE THE GOOGLE LIST GIVEN ABOVE +*/ + +/*! \page packingdata +\par Passing Sensor Data Between RadioHead nodes + +People often ask about how to send data (such as numbers, sensor +readings etc) from one RadioHead node to another. Although this issue +is not specific to RadioHead, and more properly lies in the area of +programming for networks, we will try to give some guidance here. + +One reason for the uncertainty and confusion in this area, especially +amongst beginners, is that there is no *best* way to do it. The best +solution for your project may depend on the range of processors and +data that you have to deal with. Also, it gets more difficult if you +need to send several numbers in one packet, and/or deal with floating +point numbers and/or different types of processors. + +The principal cause of difficulty is that different microprocessors of +the kind that run RadioHead may have different ways of representing +binary data such as integers. Some processors are little-endian and +some are big-endian in the way they represent multi-byte integers +(https://en.wikipedia.org/wiki/Endianness). And different processors +and maths libraries may represent floating point numbers in radically +different ways: +(https://en.wikipedia.org/wiki/Floating-point_arithmetic) + +All the RadioHead examples show how to send and receive simple ASCII +strings, and if thats all you want, refer to the examples folder in +your RadioHead distribution. But your needs may be more complicated +than that. + +The essence of all engineering is compromise so it will be up to you to +decide whats best for your particular needs. The main choices are: +- Raw Binary +- Network Order Binary +- ASCII + +\par Raw Binary + +With this technique you just pack the raw binary numbers into the packet: + +\code +// Sending a single 16 bit unsigned integer +// in the transmitter: +... +uint16_t data = getsomevalue(); +if (!driver.send((uint8_t*)&data, sizeof(data))) +{ + ... +\endcode + +\code +// and in the receiver: +... +uint16_t data; +uint8_t datalen = sizeof(data); +if ( driver.recv((uint8_t*)&data, &datalen) + && datalen == sizeof(data)) +{ + // Have the data, so do something with it + uint16_t xyz = data; + ... +\endcode + +If you need to send more than one number at a time, its best to pack +them into a structure + +\code +// Sending several 16 bit unsigned integers in a structure +// in a common header for your project: +typedef struct +{ + uint16_t dataitem1; + uint16_t dataitem2; +} MyDataStruct; +... +\endcode + +\code +// In the transmitter +... +MyDataStruct data; +data.dataitem1 = getsomevalue(); +data.dataitem2 = getsomeothervalue(); +if (!driver.send((uint8_t*)&data, sizeof(data))) +{ + ... +\endcode + +\code +// in the receiver +MyDataStruct data; +uint8_t datalen = sizeof(data); +if ( driver.recv((uint8_t*)&data, &datalen) + && datalen == sizeof(data)) +{ + // Have the data, so do something with it + uint16_t pqr = data.dataitem1; + uint16_t xyz = data.dataitem2; + .... +\endcode + + +The disadvantage with this simple technique becomes apparent if your +transmitter and receiver have different endianness: the integers you +receive will not be the same as the ones you sent (actually they are, +but with the internal bytes swapped around, so they probably wont make +sense to you). Endianness is not a problem if *every* data item you +send is a just single byte (uint8_t or int8_t or char), or if the +transmitter and receiver have the same endianness. + +So you should only adopt this technique if: +- You only send data items of a single byte each, or +- You are absolutely sure (now and forever into the future) that you +will only ever use the same processor endianness in the transmitter and receiver. + +\par Network Order Binary + +One solution to the issue of endianness in your processors is to +always convert your data from the processor's native byte order to +'network byte order' before transmission and then convert it back to +the receiver's native byte order on reception. You do this with the +htons (host to network short) macro and friends. These functions may +be a no-op on big-endian processors. + +With this technique you convert every multi-byte number to and from +network byte order (note that in most Arduino processors an integer is +in fact a short, and is the same as int16_t. We prefer to use types +that explicitly specify their size so we can be sure of applying the +right conversions): + +\code +// Sending a single 16 bit unsigned integer +// in the transmitter: +... +uint16_t data = htons(getsomevalue()); +if (!driver.send((uint8_t*)&data, sizeof(data))) +{ + ... +\endcode +\code +// and in the receiver: +... +uint16_t data; +uint8_t datalen = sizeof(data); +if ( driver.recv((uint8_t*)&data, &datalen) + && datalen == sizeof(data)) +{ + // Have the data, so do something with it + uint16_t xyz = ntohs(data); + ... +\endcode + +If you need to send more than one number at a time, its best to pack +them into a structure + +\code +// Sending several 16 bit unsigned integers in a structure +// in a common header for your project: +typedef struct +{ + uint16_t dataitem1; + uint16_t dataitem2; +} MyDataStruct; +... +\endcode +\code +// In the transmitter +... +MyDataStruct data; +data.dataitem1 = htons(getsomevalue()); +data.dataitem2 = htons(getsomeothervalue()); +if (!driver.send((uint8_t*)&data, sizeof(data))) +{ + ... +\endcode +\code +// in the receiver +MyDataStruct data; +uint8_t datalen = sizeof(data); +if ( driver.recv((uint8_t*)&data, &datalen) + && datalen == sizeof(data)) +{ + // Have the data, so do something with it + uint16_t pqr = ntohs(data.dataitem1); + uint16_t xyz = ntohs(data.dataitem2); + .... +\endcode + +This technique is quite general for integers but may not work if you +want to send floating point number between transmitters and receivers +that have different floating point number representations. + + +\par ASCII + +In this technique, you transmit the printable ASCII equivalent of +each floating point and then convert it back to a float in the receiver: + +\code +// In the transmitter +... +float data = getsomevalue(); +uint8_t buf[15]; // Bigger than the biggest possible ASCII +snprintf(buf, sizeof(buf), "%f", data); +if (!driver.send(buf, strlen(buf) + 1)) // Include the trailing NUL +{ + ... +\endcode +\code + +// In the receiver +... +float data; +uint8_t buf[15]; // Bigger than the biggest possible ASCII +uint8_t buflen = sizeof(buf); +if (driver.recv(buf, &buflen)) +{ + // Have the data, so do something with it + float data = atof(buf); // String to float + ... +\endcode + +\par Conclusion: + +- This is just a basic introduction to the issues. You may need to +extend your study into related C/C++ programming techniques. + +- You can extend these ideas to signed 16 bit (int16_t) and 32 bit +(uint32_t, int32_t) numbers. + +- Things can be simple or complicated depending on the needs of your +project. + +- We are not going to write your code for you: its up to you to take +these examples and explanations and extend them to suit your needs. + +*/ + + + +#ifndef RadioHead_h +#define RadioHead_h + +// Official version numbers are maintained automatically by Makefile: +#define RH_VERSION_MAJOR 1 +#define RH_VERSION_MINOR 98 + +// Symbolic names for currently supported platform types +#define RH_PLATFORM_ARDUINO 1 +#define RH_PLATFORM_MSP430 2 +#define RH_PLATFORM_STM32 3 +#define RH_PLATFORM_GENERIC_AVR8 4 +#define RH_PLATFORM_UNO32 5 +#define RH_PLATFORM_UNIX 6 +#define RH_PLATFORM_STM32STD 7 +#define RH_PLATFORM_STM32F4_HAL 8 +#define RH_PLATFORM_RASPI 9 +#define RH_PLATFORM_NRF51 10 +#define RH_PLATFORM_ESP8266 11 +#define RH_PLATFORM_STM32F2 12 +#define RH_PLATFORM_CHIPKIT_CORE 13 +#define RH_PLATFORM_ESP32 14 +#define RH_PLATFORM_NRF52 15 +#define RH_PLATFORM_MONGOOSE_OS 16 +#define RH_PLATFORM_ATTINY 17 +// Spencer Kondes megaTinyCore: +#define RH_PLATFORM_ATTINY_MEGA 18 + +//////////////////////////////////////////////////// +// Select platform automatically, if possible +#ifndef RH_PLATFORM + #if (defined(MPIDE) && MPIDE>=150 && defined(ARDUINO)) + // Using ChipKIT Core on Arduino IDE + #define RH_PLATFORM RH_PLATFORM_CHIPKIT_CORE + #elif defined(MPIDE) + // Uno32 under old MPIDE, which has been discontinued: + #define RH_PLATFORM RH_PLATFORM_UNO32 + #elif defined(NRF51) + #define RH_PLATFORM RH_PLATFORM_NRF51 + #elif defined(NRF52) + #define RH_PLATFORM RH_PLATFORM_NRF52 + #elif defined(ESP8266) + #define RH_PLATFORM RH_PLATFORM_ESP8266 + #elif defined(ESP32) + #define RH_PLATFORM RH_PLATFORM_ESP32 + #elif defined(MGOS) + #define RH_PLATFORM RH_PLATFORM_MONGOOSE_OS + #elif defined(ARDUINO_attinyxy2) || defined(ARDUINO_attinyxy4) || defined(ARDUINO_attinyxy6) || defined(ARDUINO_attinyxy7) + #define RH_PLATFORM RH_PLATFORM_ATTINY_MEGA + #elif defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtinyX4__) || defined(__AVR_ATtinyX5__) || defined(__AVR_ATtiny2313__) || defined(__AVR_ATtiny4313__) || defined(__AVR_ATtinyX313__) || defined(ARDUINO_attiny) + #define RH_PLATFORM RH_PLATFORM_ATTINY + #elif defined(ARDUINO) + #define RH_PLATFORM RH_PLATFORM_ARDUINO + #elif defined(__MSP430G2452__) || defined(__MSP430G2553__) + #define RH_PLATFORM RH_PLATFORM_MSP430 + #elif defined(MCU_STM32F103RE) + #define RH_PLATFORM RH_PLATFORM_STM32 + #elif defined(STM32F2XX) + #define RH_PLATFORM RH_PLATFORM_STM32F2 + #elif defined(USE_STDPERIPH_DRIVER) + #define RH_PLATFORM RH_PLATFORM_STM32STD + #elif defined(RASPBERRY_PI) + #define RH_PLATFORM RH_PLATFORM_RASPI + #elif defined(__unix__) // Linux + #define RH_PLATFORM RH_PLATFORM_UNIX + #elif defined(__APPLE__) // OSX + #define RH_PLATFORM RH_PLATFORM_UNIX + #else + #error Platform not defined! + #endif +#endif + +//////////////////////////////////////////////////// +// Platform specific headers: +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) + #if (ARDUINO >= 100) + #include + #else + #include + #endif + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #if defined(ARDUINO_ARCH_STM32F4) + // output to Serial causes hangs on STM32 F4 Discovery board + // There seems to be no way to output text to the USB connection + #define Serial Serial2 + #endif +#elif (RH_PLATFORM == RH_PLATFORM_ATTINY) + #warning Arduino TinyCore does not support hardware SPI. Use software SPI instead. +#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA) + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) // ESP8266 processor on Arduino IDE + #include + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #define RH_MISSING_SPIUSINGINTERRUPT + +#elif (RH_PLATFORM == RH_PLATFORM_ESP32) // ESP32 processor on Arduino IDE + #include + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #define RH_MISSING_SPIUSINGINTERRUPT + + #elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) // Mongoose OS platform + #include + #include + #include + #include + #include + #include + #include // We use the floor() math function. + #define RH_HAVE_HARDWARE_SPI + //If a Radio is connected via a serial port then this defines the serial + //port the radio is connected to. + #if defined(RH_SERIAL_PORT) + #if RH_SERIAL_PORT == 0 + #define Serial Serial0 + #elif RH_SERIAL_PORT == 1 + #define Serial Serial1 + #elif RH_SERIAL_PORT == 2 + #define Serial Serial2 + #endif + #else + #warning "RH_SERIAL_PORT not defined. Therefore serial port 0 selected" + #define Serial Serial0 + #endif + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_MSP430) // LaunchPad specific + #include "legacymsp430.h" + #include "Energia.h" + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIALg + +#elif (RH_PLATFORM == RH_PLATFORM_UNO32 || RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI + #define memcpy_P memcpy + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_STM32) // Maple, Flymaple etc + #include + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI + // Defines which timer to use on Maple + #define MAPLE_TIMER 1 + #define PROGMEM + #define memcpy_P memcpy + #define Serial SerialUSB + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Particle Photon with firmware-develop + #include + #include + #include // floor + #define RH_HAVE_SERIAL + #define RH_HAVE_HARDWARE_SPI + +#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32 with STM32F4xx_StdPeriph_Driver + #include + #include + #include + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI + #define Serial SerialUSB + #define RH_HAVE_SERIAL + +#elif (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8) + #include + #include + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI + #include + +// For Steve Childress port to ARM M4 w/CMSIS with STM's Hardware Abstraction lib. +// See ArduinoWorkarounds.h (not supplied) +#elif (RH_PLATFORM == RH_PLATFORM_STM32F4_HAL) + #include + #include // Also using ST's CubeMX to generate I/O and CPU setup source code for IAR/EWARM, not GCC ARM. + #include + #include + #include + #define RH_HAVE_HARDWARE_SPI // using HAL (Hardware Abstraction Libraries from ST along with CMSIS, not arduino libs or pins concept. + +#elif (RH_PLATFORM == RH_PLATFORM_RASPI) + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #define PROGMEM + #if (__has_include ()) + #include + #else + #include + #endif + #include + //Define SS for CS0 or pin 24 + #define SS 8 + +#elif (RH_PLATFORM == RH_PLATFORM_NRF51) + #define RH_HAVE_SERIAL + #define PROGMEM + #include + +#elif (RH_PLATFORM == RH_PLATFORM_NRF52) + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #define PROGMEM + #include + +#elif (RH_PLATFORM == RH_PLATFORM_UNIX) + // Simulate the sketch on Linux and OSX + #include + #define RH_HAVE_SERIAL +#include // For htons and friends + +#else + #error Platform unknown! +#endif + +//////////////////////////////////////////////////// +// This is an attempt to make a portable atomic block +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) +#if defined(__arm__) + #include + #else + #include + #endif + #define ATOMIC_BLOCK_START ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { + #define ATOMIC_BLOCK_END } +#elif (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) + // UsingChipKIT Core on Arduino IDE + #define ATOMIC_BLOCK_START unsigned int __status = disableInterrupts(); { + #define ATOMIC_BLOCK_END } restoreInterrupts(__status); +#elif (RH_PLATFORM == RH_PLATFORM_UNO32) + // Under old MPIDE, which has been discontinued: + #include + #define ATOMIC_BLOCK_START unsigned int __status = INTDisableInterrupts(); { + #define ATOMIC_BLOCK_END } INTRestoreInterrupts(__status); +#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Particle Photon with firmware-develop + #define ATOMIC_BLOCK_START { int __prev = HAL_disable_irq(); + #define ATOMIC_BLOCK_END HAL_enable_irq(__prev); } +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) +// See hardware/esp8266/2.0.0/cores/esp8266/Arduino.h + #define ATOMIC_BLOCK_START { uint32_t __savedPS = xt_rsil(15); + #define ATOMIC_BLOCK_END xt_wsr_ps(__savedPS);} +#else + // TO BE DONE: + #define ATOMIC_BLOCK_START + #define ATOMIC_BLOCK_END +#endif + +//////////////////////////////////////////////////// +// Try to be compatible with systems that support yield() and multitasking +// instead of spin-loops +// Recent Arduino IDE or Teensy 3 has yield() +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO && ARDUINO >= 155) || (defined(TEENSYDUINO) && defined(__MK20DX128__)) + #define YIELD yield(); +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) +// ESP8266 also has it + #define YIELD yield(); +#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) + //ESP32 and ESP8266 use freertos so we include calls + //that we would normall exit a function and return to + //the rtos in mgosYield() (E.G flush TX uart buffer + extern "C" { + void mgosYield(void); + } + #define YIELD mgosYield() +#else + #define YIELD +#endif + +//////////////////////////////////////////////////// +// digitalPinToInterrupt is not available prior to Arduino 1.5.6 and 1.0.6 +// See http://arduino.cc/en/Reference/attachInterrupt +#ifndef NOT_AN_INTERRUPT + #define NOT_AN_INTERRUPT -1 +#endif +#ifndef digitalPinToInterrupt + #if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && !defined(__arm__) + + #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + // Arduino Mega, Mega ADK, Mega Pro + // 2->0, 3->1, 21->2, 20->3, 19->4, 18->5 + #define digitalPinToInterrupt(p) ((p) == 2 ? 0 : ((p) == 3 ? 1 : ((p) >= 18 && (p) <= 21 ? 23 - (p) : NOT_AN_INTERRUPT))) + + #elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) + // Arduino 1284 and 1284P - See Manicbug and Optiboot + // 10->0, 11->1, 2->2 + #define digitalPinToInterrupt(p) ((p) == 10 ? 0 : ((p) == 11 ? 1 : ((p) == 2 ? 2 : NOT_AN_INTERRUPT))) + + #elif defined(__AVR_ATmega32U4__) + // Leonardo, Yun, Micro, Pro Micro, Flora, Esplora + // 3->0, 2->1, 0->2, 1->3, 7->4 + #define digitalPinToInterrupt(p) ((p) == 0 ? 2 : ((p) == 1 ? 3 : ((p) == 2 ? 1 : ((p) == 3 ? 0 : ((p) == 7 ? 4 : NOT_AN_INTERRUPT))))) + + #else + // All other arduino except Due: + // Serial Arduino, Extreme, NG, BT, Uno, Diecimila, Duemilanove, Nano, Menta, Pro, Mini 04, Fio, LilyPad, Ethernet etc + // 2->0, 3->1 + #define digitalPinToInterrupt(p) ((p) == 2 ? 0 : ((p) == 3 ? 1 : NOT_AN_INTERRUPT)) + + #endif + + #elif (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) + // Hmmm, this is correct for Uno32, but what about other boards on ChipKIT Core? + #define digitalPinToInterrupt(p) ((p) == 38 ? 0 : ((p) == 2 ? 1 : ((p) == 7 ? 2 : ((p) == 8 ? 3 : ((p) == 735 ? 4 : NOT_AN_INTERRUPT))))) + + #else + // Everything else (including Due and Teensy) interrupt number the same as the interrupt pin number + #define digitalPinToInterrupt(p) (p) + #endif +#endif + +// On some platforms, attachInterrupt() takes a pin number, not an interrupt number +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_SAM_DUE)) + #define RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER +#endif + +// Slave select pin, some platforms such as ATTiny do not define it. +#ifndef SS + #define SS 10 +#endif + +// Some platforms require specail attributes for interrupt routines +#if (RH_PLATFORM == RH_PLATFORM_ESP8266) + // interrupt handler and related code must be in RAM on ESP8266, + // according to issue #46. + #define RH_INTERRUPT_ATTR ICACHE_RAM_ATTR + +#elif (RH_PLATFORM == RH_PLATFORM_ESP32) + #define RH_INTERRUPT_ATTR IRAM_ATTR +#else + #define RH_INTERRUPT_ATTR +#endif + +// These defs cause trouble on some versions of Arduino +#undef abs +#undef round +#undef double + +// Sigh: there is no widespread adoption of htons and friends in the base code, only in some WiFi headers etc +// that have a lot of excess baggage +#if RH_PLATFORM != RH_PLATFORM_UNIX && !defined(htons) +// #ifndef htons +// These predefined macros available on modern GCC compilers + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + // Atmel processors + #define htons(x) ( ((x)<<8) | (((x)>>8)&0xFF) ) + #define ntohs(x) htons(x) + #define htonl(x) ( ((x)<<24 & 0xFF000000UL) | \ + ((x)<< 8 & 0x00FF0000UL) | \ + ((x)>> 8 & 0x0000FF00UL) | \ + ((x)>>24 & 0x000000FFUL) ) + #define ntohl(x) htonl(x) + + #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // Others + #define htons(x) (x) + #define ntohs(x) (x) + #define htonl(x) (x) + #define ntohl(x) (x) + + #else + #error "Dont know how to define htons and friends for this processor" + #endif +#endif + +// This is the address that indicates a broadcast +#define RH_BROADCAST_ADDRESS 0xff + +// Uncomment this is to enable Encryption (see RHEncryptedDriver): +// But ensure you have installed the Crypto directory from arduinolibs first: +// http://rweather.github.io/arduinolibs/index.html +//#define RH_ENABLE_ENCRYPTION_MODULE + +#endif From 5904d66111f7331e89e4583fc427549c14ab1b16 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 14 Apr 2020 12:38:42 -0700 Subject: [PATCH 008/197] Move Custom95 in with the rest of the RH code, to be ready to refactor --- src/{ => rf95}/CustomRF95.cpp | 0 src/{ => rf95}/CustomRF95.h | 0 src/rf95/RHGenericDriver.h | 171 ++++------ src/rf95/RH_RF95.cpp | 130 +++----- src/rf95/RH_RF95.h | 602 +++++++++++++++++----------------- 5 files changed, 409 insertions(+), 494 deletions(-) rename src/{ => rf95}/CustomRF95.cpp (100%) rename src/{ => rf95}/CustomRF95.h (100%) diff --git a/src/CustomRF95.cpp b/src/rf95/CustomRF95.cpp similarity index 100% rename from src/CustomRF95.cpp rename to src/rf95/CustomRF95.cpp diff --git a/src/CustomRF95.h b/src/rf95/CustomRF95.h similarity index 100% rename from src/CustomRF95.h rename to src/rf95/CustomRF95.h diff --git a/src/rf95/RHGenericDriver.h b/src/rf95/RHGenericDriver.h index ae523d966..d06c9e4c0 100644 --- a/src/rf95/RHGenericDriver.h +++ b/src/rf95/RHGenericDriver.h @@ -8,21 +8,21 @@ #include -// Defines bits of the FLAGS header reserved for use by the RadioHead library and +// Defines bits of the FLAGS header reserved for use by the RadioHead library and // the flags available for use by applications -#define RH_FLAGS_RESERVED 0xf0 -#define RH_FLAGS_APPLICATION_SPECIFIC 0x0f -#define RH_FLAGS_NONE 0 +#define RH_FLAGS_RESERVED 0xf0 +#define RH_FLAGS_APPLICATION_SPECIFIC 0x0f +#define RH_FLAGS_NONE 0 // Default timeout for waitCAD() in ms -#define RH_CAD_DEFAULT_TIMEOUT 10000 +#define RH_CAD_DEFAULT_TIMEOUT 10000 ///////////////////////////////////////////////////////////////////// /// \class RHGenericDriver RHGenericDriver.h /// \brief Abstract base class for a RadioHead driver. /// /// This class defines the functions that must be provided by any RadioHead driver. -/// Different types of driver will implement all the abstract functions, and will perhaps override +/// Different types of driver will implement all the abstract functions, and will perhaps override /// other functions in this subclass, or perhaps add new functions specifically required by that driver. /// Do not directly instantiate this class: it is only to be subclassed by driver classes. /// @@ -40,19 +40,18 @@ /// significant 4 bits are reserved for applications. class RHGenericDriver { -public: + public: /// \brief Defines different operating modes for the transport hardware /// - /// These are the different values that can be adopted by the _mode variable and + /// These are the different values that can be adopted by the _mode variable and /// returned by the mode() member function, - typedef enum - { - RHModeInitialising = 0, ///< Transport is initialising. Initial default value until init() is called.. - RHModeSleep, ///< Transport hardware is in low power sleep mode (if supported) - RHModeIdle, ///< Transport is idle. - RHModeTx, ///< Transport is in the process of transmitting a message. - RHModeRx, ///< Transport is in the process of receiving a message. - RHModeCad ///< Transport is in the process of detecting channel activity (if supported) + typedef enum { + RHModeInitialising = 0, ///< Transport is initialising. Initial default value until init() is called.. + RHModeSleep, ///< Transport hardware is in low power sleep mode (if supported) + RHModeIdle, ///< Transport is idle. + RHModeTx, ///< Transport is in the process of transmitting a message. + RHModeRx, ///< Transport is in the process of receiving a message. + RHModeCad ///< Transport is in the process of detecting channel activity (if supported) } RHMode; /// Constructor @@ -64,7 +63,7 @@ public: virtual bool init(); /// Tests whether a new message is available - /// from the Driver. + /// from the Driver. /// On most drivers, if there is an uncollected received message, and there is no message /// currently bing transmitted, this will also put the Driver into RHModeRx mode until /// a message is actually received by the transport, when it will be returned to RHModeIdle. @@ -72,53 +71,29 @@ public: /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv(). virtual bool available() = 0; - /// Turns the receiver on if it not already on. - /// If there is a valid message available, copy it to buf and return true - /// else return false. - /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). - /// You should be sure to call this function frequently enough to not miss any messages - /// It is recommended that you call it in your main loop. - /// \param[in] buf Location to copy the received message - /// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied. - /// \return true if a valid message was copied to buf - virtual bool recv(uint8_t* buf, uint8_t* len) = 0; - - /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). - /// Then optionally waits for Channel Activity Detection (CAD) - /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). - /// Then loads a message into the transmitter and starts the transmitter. Note that a message length - /// of 0 is NOT permitted. If the message is too long for the underlying radio technology, send() will - /// return false and will not send the message. - /// \param[in] data Array of data to be sent - /// \param[in] len Number of bytes of data to send (> 0) - /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. - /// \return true if the message length was valid and it was correctly queued for transmit. Return false - /// if CAD was requested and the CAD timeout timed out before clear channel was detected. - virtual bool send(const uint8_t* data, uint8_t len) = 0; - - /// Returns the maximum message length + /// Returns the maximum message length /// available in this Driver. /// \return The maximum legal message length virtual uint8_t maxMessageLength() = 0; - /// Starts the receiver and blocks until a valid received + /// Starts the receiver and blocks until a valid received /// message is available. - virtual void waitAvailable(); + virtual void waitAvailable(); - /// Blocks until the transmitter + /// Blocks until the transmitter /// is no longer transmitting. - virtual bool waitPacketSent(); + virtual bool waitPacketSent(); /// Blocks until the transmitter is no longer transmitting. /// or until the timeout occuers, whichever happens first /// \param[in] timeout Maximum time to wait in milliseconds. /// \return true if the radio completed transmission within the timeout period. False if it timed out. - virtual bool waitPacketSent(uint16_t timeout); + virtual bool waitPacketSent(uint16_t timeout); /// Starts the receiver and blocks until a received message is available or a timeout /// \param[in] timeout Maximum time to wait in milliseconds. /// \return true if a message is available - virtual bool waitAvailableTimeout(uint16_t timeout); + virtual bool waitAvailableTimeout(uint16_t timeout); // Bent G Christensen (bentor@gmail.com), 08/15/2016 /// Channel Activity Detection (CAD). @@ -128,12 +103,12 @@ public: /// Caution: the random() function is not seeded. If you want non-deterministic behaviour, consider /// using something like randomSeed(analogRead(A0)); in your sketch. /// Permits the implementation of listen-before-talk mechanism (Collision Avoidance). - /// Calls the isChannelActive() member function for the radio (if supported) + /// Calls the isChannelActive() member function for the radio (if supported) /// to determine if the channel is active. If the radio does not support isChannelActive(), /// always returns true immediately /// \return true if the radio-specific CAD (as returned by isChannelActive()) /// shows the channel is clear within the timeout period (or the timeout period is 0), else returns false. - virtual bool waitCAD(); + virtual bool waitCAD(); /// Sets the Channel Activity Detection timeout in milliseconds to be used by waitCAD(). /// The default is 0, which means do not wait for CAD detection. @@ -142,166 +117,164 @@ public: /// Determine if the currently selected radio channel is active. /// This is expected to be subclassed by specific radios to implement their Channel Activity Detection - /// if supported. If the radio does not support CAD, returns true immediately. If a RadioHead radio + /// if supported. If the radio does not support CAD, returns true immediately. If a RadioHead radio /// supports isChannelActive() it will be documented in the radio specific documentation. /// This is called automatically by waitCAD(). /// \return true if the radio-specific CAD (as returned by override of isChannelActive()) shows the /// current radio channel as active, else false. If there is no radio-specific CAD, returns false. - virtual bool isChannelActive(); + virtual bool isChannelActive(); /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. /// In promiscuous mode, all messages will be accepted regardless of the TO header. - /// In a conventional multinode system, all nodes will have a unique address + /// In a conventional multinode system, all nodes will have a unique address /// (which you could store in EEPROM). - /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, + /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, /// allowing the possibilty of address spoofing). /// \param[in] thisAddress The address of this node. virtual void setThisAddress(uint8_t thisAddress); /// Sets the TO header to be sent in all subsequent messages /// \param[in] to The new TO header value - virtual void setHeaderTo(uint8_t to); + virtual void setHeaderTo(uint8_t to); /// Sets the FROM header to be sent in all subsequent messages /// \param[in] from The new FROM header value - virtual void setHeaderFrom(uint8_t from); + virtual void setHeaderFrom(uint8_t from); /// Sets the ID header to be sent in all subsequent messages /// \param[in] id The new ID header value - virtual void setHeaderId(uint8_t id); + virtual void setHeaderId(uint8_t id); /// Sets and clears bits in the FLAGS header to be sent in all subsequent messages - /// First it clears he FLAGS according to the clear argument, then sets the flags according to the + /// First it clears he FLAGS according to the clear argument, then sets the flags according to the /// set argument. The default for clear always clears the application specific flags. /// \param[in] set bitmask of bits to be set. Flags are cleared with the clear mask before being set. /// \param[in] clear bitmask of flags to clear. Defaults to RH_FLAGS_APPLICATION_SPECIFIC /// which clears the application specific flags, resulting in new application specific flags /// identical to the set. - virtual void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_APPLICATION_SPECIFIC); + virtual void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_APPLICATION_SPECIFIC); /// Tells the receiver to accept messages with any TO address, not just messages /// addressed to thisAddress or the broadcast address /// \param[in] promiscuous true if you wish to receive messages with any TO address - virtual void setPromiscuous(bool promiscuous); + virtual void setPromiscuous(bool promiscuous); /// Returns the TO header of the last received message /// \return The TO header - virtual uint8_t headerTo(); + virtual uint8_t headerTo(); /// Returns the FROM header of the last received message /// \return The FROM header - virtual uint8_t headerFrom(); + virtual uint8_t headerFrom(); /// Returns the ID header of the last received message /// \return The ID header - virtual uint8_t headerId(); + virtual uint8_t headerId(); /// Returns the FLAGS header of the last received message /// \return The FLAGS header - virtual uint8_t headerFlags(); + virtual uint8_t headerFlags(); /// Returns the most recent RSSI (Receiver Signal Strength Indicator). /// Usually it is the RSSI of the last received message, which is measured when the preamble is received. /// If you called readRssi() more recently, it will return that more recent value. /// \return The most recent RSSI measurement in dBm. - virtual int16_t lastRssi(); + virtual int16_t lastRssi(); /// Returns the operating mode of the library. /// \return the current mode, one of RF69_MODE_* - virtual RHMode mode(); + virtual RHMode mode(); /// Sets the operating mode of the transport. - virtual void setMode(RHMode mode); + virtual void setMode(RHMode mode); /// Sets the transport hardware into low-power sleep mode /// (if supported). May be overridden by specific drivers to initialte sleep mode. - /// If successful, the transport will stay in sleep mode until woken by + /// If successful, the transport will stay in sleep mode until woken by /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) /// \return true if sleep mode is supported by transport hardware and the RadioHead driver, and if sleep mode /// was successfully entered. If sleep mode is not suported, return false. - virtual bool sleep(); + virtual bool sleep(); /// Prints a data buffer in HEX. /// For diagnostic use /// \param[in] prompt string to preface the print /// \param[in] buf Location of the buffer to print /// \param[in] len Length of the buffer in octets. - static void printBuffer(const char* prompt, const uint8_t* buf, uint8_t len); + static void printBuffer(const char *prompt, const uint8_t *buf, uint8_t len); /// Returns the count of the number of bad received packets (ie packets with bad lengths, checksum etc) /// which were rejected and not delivered to the application. /// Caution: not all drivers can correctly report this count. Some underlying hardware only report /// good packets. /// \return The number of bad packets received. - virtual uint16_t rxBad(); + virtual uint16_t rxBad(); - /// Returns the count of the number of + /// Returns the count of the number of /// good received packets /// \return The number of good packets received. - virtual uint16_t rxGood(); + virtual uint16_t rxGood(); - /// Returns the count of the number of + /// Returns the count of the number of /// packets successfully transmitted (though not necessarily received by the destination) /// \return The number of packets successfully transmitted - virtual uint16_t txGood(); - -protected: + virtual uint16_t txGood(); + protected: /// The current transport operating mode - volatile RHMode _mode; + volatile RHMode _mode; /// This node id - uint8_t _thisAddress; - + uint8_t _thisAddress; + /// Whether the transport is in promiscuous mode - bool _promiscuous; + bool _promiscuous; /// TO header in the last received mesasge - volatile uint8_t _rxHeaderTo; + volatile uint8_t _rxHeaderTo; /// FROM header in the last received mesasge - volatile uint8_t _rxHeaderFrom; + volatile uint8_t _rxHeaderFrom; /// ID header in the last received mesasge - volatile uint8_t _rxHeaderId; + volatile uint8_t _rxHeaderId; /// FLAGS header in the last received mesasge - volatile uint8_t _rxHeaderFlags; + volatile uint8_t _rxHeaderFlags; /// TO header to send in all messages - uint8_t _txHeaderTo; + uint8_t _txHeaderTo; /// FROM header to send in all messages - uint8_t _txHeaderFrom; + uint8_t _txHeaderFrom; /// ID header to send in all messages - uint8_t _txHeaderId; + uint8_t _txHeaderId; /// FLAGS header to send in all messages - uint8_t _txHeaderFlags; + uint8_t _txHeaderFlags; /// The value of the last received RSSI value, in some transport specific units - volatile int16_t _lastRssi; + volatile int16_t _lastRssi; /// Count of the number of bad messages (eg bad checksum etc) received - volatile uint16_t _rxBad; + volatile uint16_t _rxBad; /// Count of the number of successfully transmitted messaged - volatile uint16_t _rxGood; + volatile uint16_t _rxGood; /// Count of the number of bad messages (correct checksum etc) received - volatile uint16_t _txGood; - + volatile uint16_t _txGood; + /// Channel activity detected - volatile bool _cad; + volatile bool _cad; /// Channel activity timeout in ms - unsigned int _cad_timeout; - -private: + unsigned int _cad_timeout; + private: }; -#endif +#endif diff --git a/src/rf95/RH_RF95.cpp b/src/rf95/RH_RF95.cpp index 7d7bf0fd9..d08de8b1d 100644 --- a/src/rf95/RH_RF95.cpp +++ b/src/rf95/RH_RF95.cpp @@ -13,19 +13,17 @@ uint8_t RH_RF95::_interruptCount = 0; // Index into _deviceForInterrupt for next // These are indexed by the values of ModemConfigChoice // Stored in flash (program) memory to save SRAM -PROGMEM static const RH_RF95::ModemConfig MODEM_CONFIG_TABLE[] = - { - // 1d, 1e, 26 - {0x72, 0x74, 0x04}, // Bw125Cr45Sf128 (the chip default), AGC enabled - {0x92, 0x74, 0x04}, // Bw500Cr45Sf128, AGC enabled - {0x48, 0x94, 0x04}, // Bw31_25Cr48Sf512, AGC enabled - {0x78, 0xc4, 0x0c}, // Bw125Cr48Sf4096, AGC enabled +PROGMEM static const RH_RF95::ModemConfig MODEM_CONFIG_TABLE[] = { + // 1d, 1e, 26 + {0x72, 0x74, 0x04}, // Bw125Cr45Sf128 (the chip default), AGC enabled + {0x92, 0x74, 0x04}, // Bw500Cr45Sf128, AGC enabled + {0x48, 0x94, 0x04}, // Bw31_25Cr48Sf512, AGC enabled + {0x78, 0xc4, 0x0c}, // Bw125Cr48Sf4096, AGC enabled }; RH_RF95::RH_RF95(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI &spi) - : RHSPIDriver(slaveSelectPin, spi), - _rxBufValid(0) + : RHSPIDriver(slaveSelectPin, spi), _rxBufValid(0) { _interruptPin = interruptPin; _myInterruptIndex = 0xff; // Not allocated yet @@ -54,16 +52,15 @@ bool RH_RF95::init() // On all other platforms, its innocuous, belt and braces pinMode(_interruptPin, INPUT); - bool isWakeFromDeepSleep = false; // true if we think we are waking from deep sleep AND the rf95 seems to have a valid configuration + bool isWakeFromDeepSleep = + false; // true if we think we are waking from deep sleep AND the rf95 seems to have a valid configuration - if (!isWakeFromDeepSleep) - { + if (!isWakeFromDeepSleep) { // Set sleep mode, so we can also set LORA mode: spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE); delay(10); // Wait for sleep mode to take over from say, CAD // Check we are in sleep mode, with LORA set - if (spiRead(RH_RF95_REG_01_OP_MODE) != (RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE)) - { + if (spiRead(RH_RF95_REG_01_OP_MODE) != (RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE)) { // Serial.println(spiRead(RH_RF95_REG_01_OP_MODE), HEX); return false; // No device present? } @@ -93,16 +90,15 @@ bool RH_RF95::init() setTxPower(13); Serial.printf("IRQ flag mask 0x%x\n", spiRead(RH_RF95_REG_11_IRQ_FLAGS_MASK)); - } - else - { + } else { // FIXME // restore mode base off reading RS95 registers // Only let CPU enter deep sleep if RF95 is sitting waiting on a receive or is in idle or sleep. } - // geeksville: we do this last, because if there is an interrupt pending from during the deep sleep, this attach will cause it to be taken. + // geeksville: we do this last, because if there is an interrupt pending from during the deep sleep, this attach will cause it + // to be taken. // Set up interrupt handler // Since there are a limited number of interrupt glue functions isr*() available, @@ -110,8 +106,7 @@ bool RH_RF95::init() // ON some devices, notably most Arduinos, the interrupt pin passed in is actuallt the // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping // yourself based on knwledge of what Arduino board you are running on. - if (_myInterruptIndex == 0xff) - { + if (_myInterruptIndex == 0xff) { // First run, no interrupt allocated yet if (_interruptCount <= RH_RF95_NUM_INTERRUPTS) _myInterruptIndex = _interruptCount++; @@ -139,17 +134,15 @@ void RH_RF95::prepareDeepSleep() detachInterrupt(interruptNumber); } -bool RH_RF95::isReceiving() +bool RH_RF95::isReceiving() { // 0x0b == Look for header info valid, signal synchronized or signal detected uint8_t reg = spiRead(RH_RF95_REG_18_MODEM_STAT) & 0x1f; // Serial.printf("reg %x\n", reg); - return _mode == RHModeRx && (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | - RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED | - RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0; + return _mode == RHModeRx && (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED | + RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0; } - // C++ level interrupt handler for this instance // LORA is unusual in that it has several interrupt lines, and not a single, combined one. // On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly @@ -172,21 +165,17 @@ void RH_RF95::handleInterrupt() clearRxBuf(); } - if ((irq_flags & RH_RF95_RX_DONE) && !haveRxError) - { + if ((irq_flags & RH_RF95_RX_DONE) && !haveRxError) { // Read the RegHopChannel register to check if CRC presence is signalled // in the header. If not it might be a stray (noise) packet.* uint8_t crc_present = spiRead(RH_RF95_REG_1C_HOP_CHANNEL) & RH_RF95_RX_PAYLOAD_CRC_IS_ON; spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags, required before reading fifo (according to datasheet) - if (!crc_present) - { + if (!crc_present) { _rxBad++; clearRxBuf(); - } - else - { + } else { // Have received a packet uint8_t len = spiRead(RH_RF95_REG_13_RX_NB_BYTES); @@ -220,21 +209,18 @@ void RH_RF95::handleInterrupt() } } - if (irq_flags & RH_RF95_TX_DONE) - { + if (irq_flags & RH_RF95_TX_DONE) { _txGood++; setModeIdle(); } - - if (_mode == RHModeCad && (irq_flags & RH_RF95_CAD_DONE)) - { + + if (_mode == RHModeCad && (irq_flags & RH_RF95_CAD_DONE)) { _cad = irq_flags & RH_RF95_CAD_DETECTED; setModeIdle(); } // ack all interrupts, note - we did this already in the RX_DONE case above, and we don't want to do it twice - if (!(irq_flags & RH_RF95_RX_DONE)) - { + if (!(irq_flags & RH_RF95_RX_DONE)) { // Sigh: on some processors, for some unknown reason, doing this only once does not actually // clear the radio's interrupt flag. So we do it twice. Why? // kevinh: turn this off until root cause is known, because it can cause missed interrupts! @@ -272,10 +258,7 @@ void RH_RF95::validateRxBuf() _rxHeaderFrom = _buf[1]; _rxHeaderId = _buf[2]; _rxHeaderFlags = _buf[3]; - if (_promiscuous || - _rxHeaderTo == _thisAddress || - _rxHeaderTo == RH_BROADCAST_ADDRESS) - { + if (_promiscuous || _rxHeaderTo == _thisAddress || _rxHeaderTo == RH_BROADCAST_ADDRESS) { _rxGood++; _rxBufValid = true; } @@ -297,23 +280,6 @@ void RH_RF95::clearRxBuf() ATOMIC_BLOCK_END; } -bool RH_RF95::recv(uint8_t *buf, uint8_t *len) -{ - if (!available()) - return false; - if (buf && len) - { - ATOMIC_BLOCK_START; - // Skip the 4 headers that are at the beginning of the rxBuf - if (*len > _bufLen - RH_RF95_HEADER_LEN) - *len = _bufLen - RH_RF95_HEADER_LEN; - memcpy(buf, _buf + RH_RF95_HEADER_LEN, *len); - ATOMIC_BLOCK_END; - } - clearRxBuf(); // This message accepted and cleared - return true; -} - bool RH_RF95::send(const uint8_t *data, uint8_t len) { if (len > RH_RF95_MAX_MESSAGE_LEN) @@ -344,11 +310,12 @@ bool RH_RF95::send(const uint8_t *data, uint8_t len) bool RH_RF95::printRegisters() { #ifdef RH_HAVE_SERIAL - uint8_t registers[] = {0x01, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x014, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}; + uint8_t registers[] = {0x01, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x014, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, + 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}; uint8_t i; - for (i = 0; i < sizeof(registers); i++) - { + for (i = 0; i < sizeof(registers); i++) { Serial.print(registers[i], HEX); Serial.print(": "); Serial.println(spiRead(registers[i]), HEX); @@ -376,8 +343,7 @@ bool RH_RF95::setFrequency(float centre) void RH_RF95::setModeIdle() { - if (_mode != RHModeIdle) - { + if (_mode != RHModeIdle) { spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_STDBY); _mode = RHModeIdle; } @@ -385,8 +351,7 @@ void RH_RF95::setModeIdle() bool RH_RF95::sleep() { - if (_mode != RHModeSleep) - { + if (_mode != RHModeSleep) { spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP); _mode = RHModeSleep; } @@ -395,8 +360,7 @@ bool RH_RF95::sleep() void RH_RF95::setModeRx() { - if (_mode != RHModeRx) - { + if (_mode != RHModeRx) { spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_RXCONTINUOUS); spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x00); // Interrupt on RxDone _mode = RHModeRx; @@ -405,8 +369,7 @@ void RH_RF95::setModeRx() void RH_RF95::setModeTx() { - if (_mode != RHModeTx) - { + if (_mode != RHModeTx) { spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_TX); spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone _mode = RHModeTx; @@ -417,16 +380,13 @@ void RH_RF95::setTxPower(int8_t power, bool useRFO) { // Sigh, different behaviours depending on whther the module use PA_BOOST or the RFO pin // for the transmitter output - if (useRFO) - { + if (useRFO) { if (power > 14) power = 14; if (power < -1) power = -1; spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_MAX_POWER | (power + 1)); - } - else - { + } else { if (power > 23) power = 23; if (power < 5) @@ -435,13 +395,10 @@ void RH_RF95::setTxPower(int8_t power, bool useRFO) // For RH_RF95_PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf' // RH_RF95_PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will us it // for 21, 22 and 23dBm - if (power > 20) - { + if (power > 20) { spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_ENABLE); power -= 3; - } - else - { + } else { spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_DISABLE); } @@ -486,8 +443,7 @@ void RH_RF95::setPreambleLength(uint16_t bytes) bool RH_RF95::isChannelActive() { // Set mode RHModeCad - if (_mode != RHModeCad) - { + if (_mode != RHModeCad) { spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_CAD); spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x80); // Interrupt on CadDone _mode = RHModeCad; @@ -501,8 +457,7 @@ bool RH_RF95::isChannelActive() void RH_RF95::enableTCXO() { - while ((spiRead(RH_RF95_REG_4B_TCXO) & RH_RF95_TCXO_TCXO_INPUT_ON) != RH_RF95_TCXO_TCXO_INPUT_ON) - { + while ((spiRead(RH_RF95_REG_4B_TCXO) & RH_RF95_TCXO_TCXO_INPUT_ON) != RH_RF95_TCXO_TCXO_INPUT_ON) { sleep(); spiWrite(RH_RF95_REG_4B_TCXO, (spiRead(RH_RF95_REG_4B_TCXO) | RH_RF95_TCXO_TCXO_INPUT_ON)); } @@ -577,7 +532,7 @@ void RH_RF95::setSpreadingFactor(uint8_t sf) void RH_RF95::setSignalBandwidth(long sbw) { - uint8_t bw; //register bit pattern + uint8_t bw; // register bit pattern if (sbw <= 7800) bw = RH_RF95_BW_7_8KHZ; @@ -629,7 +584,8 @@ void RH_RF95::setLowDatarate() // Semtech modem design guide AN1200.13 says // "To avoid issues surrounding drift of the crystal reference oscillator due to either temperature change // or motion,the low data rate optimization bit is used. Specifically for 125 kHz bandwidth and SF = 11 and 12, - // this adds a small overhead to increase robustness to reference frequency variations over the timescale of the LoRa packet." + // this adds a small overhead to increase robustness to reference frequency variations over the timescale of the LoRa + // packet." // read current value for BW and SF uint8_t BW = spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) >> 4; // bw is in bits 7..4 diff --git a/src/rf95/RH_RF95.h b/src/rf95/RH_RF95.h index 43a5bc573..7a4a34a64 100644 --- a/src/rf95/RH_RF95.h +++ b/src/rf95/RH_RF95.h @@ -7,7 +7,7 @@ // Author: Mike McCauley (mikem@airspayce.com) // Copyright (C) 2014 Mike McCauley // $Id: RH_RF95.h,v 1.23 2019/11/02 02:34:22 mikem Exp $ -// +// #ifndef RH_RF95_h #define RH_RF95_h @@ -29,222 +29,221 @@ // The headers are inside the LORA's payload #define RH_RF95_HEADER_LEN 4 -// This is the maximum message length that can be supported by this driver. +// This is the maximum message length that can be supported by this driver. // Can be pre-defined to a smaller size (to save SRAM) prior to including this header // Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS #ifndef RH_RF95_MAX_MESSAGE_LEN - #define RH_RF95_MAX_MESSAGE_LEN (RH_RF95_MAX_PAYLOAD_LEN - RH_RF95_HEADER_LEN) +#define RH_RF95_MAX_MESSAGE_LEN (RH_RF95_MAX_PAYLOAD_LEN - RH_RF95_HEADER_LEN) #endif // The crystal oscillator frequency of the module #define RH_RF95_FXOSC 32000000.0 // The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19 -#define RH_RF95_FSTEP (RH_RF95_FXOSC / 524288) - +#define RH_RF95_FSTEP (RH_RF95_FXOSC / 524288) // Register names (LoRa Mode, from table 85) -#define RH_RF95_REG_00_FIFO 0x00 -#define RH_RF95_REG_01_OP_MODE 0x01 -#define RH_RF95_REG_02_RESERVED 0x02 -#define RH_RF95_REG_03_RESERVED 0x03 -#define RH_RF95_REG_04_RESERVED 0x04 -#define RH_RF95_REG_05_RESERVED 0x05 -#define RH_RF95_REG_06_FRF_MSB 0x06 -#define RH_RF95_REG_07_FRF_MID 0x07 -#define RH_RF95_REG_08_FRF_LSB 0x08 -#define RH_RF95_REG_09_PA_CONFIG 0x09 -#define RH_RF95_REG_0A_PA_RAMP 0x0a -#define RH_RF95_REG_0B_OCP 0x0b -#define RH_RF95_REG_0C_LNA 0x0c -#define RH_RF95_REG_0D_FIFO_ADDR_PTR 0x0d -#define RH_RF95_REG_0E_FIFO_TX_BASE_ADDR 0x0e -#define RH_RF95_REG_0F_FIFO_RX_BASE_ADDR 0x0f -#define RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR 0x10 -#define RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 -#define RH_RF95_REG_12_IRQ_FLAGS 0x12 -#define RH_RF95_REG_13_RX_NB_BYTES 0x13 -#define RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB 0x14 -#define RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB 0x15 -#define RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB 0x16 -#define RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB 0x17 -#define RH_RF95_REG_18_MODEM_STAT 0x18 -#define RH_RF95_REG_19_PKT_SNR_VALUE 0x19 -#define RH_RF95_REG_1A_PKT_RSSI_VALUE 0x1a -#define RH_RF95_REG_1B_RSSI_VALUE 0x1b -#define RH_RF95_REG_1C_HOP_CHANNEL 0x1c -#define RH_RF95_REG_1D_MODEM_CONFIG1 0x1d -#define RH_RF95_REG_1E_MODEM_CONFIG2 0x1e -#define RH_RF95_REG_1F_SYMB_TIMEOUT_LSB 0x1f -#define RH_RF95_REG_20_PREAMBLE_MSB 0x20 -#define RH_RF95_REG_21_PREAMBLE_LSB 0x21 -#define RH_RF95_REG_22_PAYLOAD_LENGTH 0x22 -#define RH_RF95_REG_23_MAX_PAYLOAD_LENGTH 0x23 -#define RH_RF95_REG_24_HOP_PERIOD 0x24 -#define RH_RF95_REG_25_FIFO_RX_BYTE_ADDR 0x25 -#define RH_RF95_REG_26_MODEM_CONFIG3 0x26 +#define RH_RF95_REG_00_FIFO 0x00 +#define RH_RF95_REG_01_OP_MODE 0x01 +#define RH_RF95_REG_02_RESERVED 0x02 +#define RH_RF95_REG_03_RESERVED 0x03 +#define RH_RF95_REG_04_RESERVED 0x04 +#define RH_RF95_REG_05_RESERVED 0x05 +#define RH_RF95_REG_06_FRF_MSB 0x06 +#define RH_RF95_REG_07_FRF_MID 0x07 +#define RH_RF95_REG_08_FRF_LSB 0x08 +#define RH_RF95_REG_09_PA_CONFIG 0x09 +#define RH_RF95_REG_0A_PA_RAMP 0x0a +#define RH_RF95_REG_0B_OCP 0x0b +#define RH_RF95_REG_0C_LNA 0x0c +#define RH_RF95_REG_0D_FIFO_ADDR_PTR 0x0d +#define RH_RF95_REG_0E_FIFO_TX_BASE_ADDR 0x0e +#define RH_RF95_REG_0F_FIFO_RX_BASE_ADDR 0x0f +#define RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR 0x10 +#define RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 +#define RH_RF95_REG_12_IRQ_FLAGS 0x12 +#define RH_RF95_REG_13_RX_NB_BYTES 0x13 +#define RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB 0x14 +#define RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB 0x15 +#define RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB 0x16 +#define RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB 0x17 +#define RH_RF95_REG_18_MODEM_STAT 0x18 +#define RH_RF95_REG_19_PKT_SNR_VALUE 0x19 +#define RH_RF95_REG_1A_PKT_RSSI_VALUE 0x1a +#define RH_RF95_REG_1B_RSSI_VALUE 0x1b +#define RH_RF95_REG_1C_HOP_CHANNEL 0x1c +#define RH_RF95_REG_1D_MODEM_CONFIG1 0x1d +#define RH_RF95_REG_1E_MODEM_CONFIG2 0x1e +#define RH_RF95_REG_1F_SYMB_TIMEOUT_LSB 0x1f +#define RH_RF95_REG_20_PREAMBLE_MSB 0x20 +#define RH_RF95_REG_21_PREAMBLE_LSB 0x21 +#define RH_RF95_REG_22_PAYLOAD_LENGTH 0x22 +#define RH_RF95_REG_23_MAX_PAYLOAD_LENGTH 0x23 +#define RH_RF95_REG_24_HOP_PERIOD 0x24 +#define RH_RF95_REG_25_FIFO_RX_BYTE_ADDR 0x25 +#define RH_RF95_REG_26_MODEM_CONFIG3 0x26 -#define RH_RF95_REG_27_PPM_CORRECTION 0x27 -#define RH_RF95_REG_28_FEI_MSB 0x28 -#define RH_RF95_REG_29_FEI_MID 0x29 -#define RH_RF95_REG_2A_FEI_LSB 0x2a -#define RH_RF95_REG_2C_RSSI_WIDEBAND 0x2c -#define RH_RF95_REG_31_DETECT_OPTIMIZE 0x31 -#define RH_RF95_REG_33_INVERT_IQ 0x33 -#define RH_RF95_REG_37_DETECTION_THRESHOLD 0x37 -#define RH_RF95_REG_39_SYNC_WORD 0x39 +#define RH_RF95_REG_27_PPM_CORRECTION 0x27 +#define RH_RF95_REG_28_FEI_MSB 0x28 +#define RH_RF95_REG_29_FEI_MID 0x29 +#define RH_RF95_REG_2A_FEI_LSB 0x2a +#define RH_RF95_REG_2C_RSSI_WIDEBAND 0x2c +#define RH_RF95_REG_31_DETECT_OPTIMIZE 0x31 +#define RH_RF95_REG_33_INVERT_IQ 0x33 +#define RH_RF95_REG_37_DETECTION_THRESHOLD 0x37 +#define RH_RF95_REG_39_SYNC_WORD 0x39 -#define RH_RF95_REG_40_DIO_MAPPING1 0x40 -#define RH_RF95_REG_41_DIO_MAPPING2 0x41 -#define RH_RF95_REG_42_VERSION 0x42 +#define RH_RF95_REG_40_DIO_MAPPING1 0x40 +#define RH_RF95_REG_41_DIO_MAPPING2 0x41 +#define RH_RF95_REG_42_VERSION 0x42 -#define RH_RF95_REG_4B_TCXO 0x4b -#define RH_RF95_REG_4D_PA_DAC 0x4d -#define RH_RF95_REG_5B_FORMER_TEMP 0x5b -#define RH_RF95_REG_61_AGC_REF 0x61 -#define RH_RF95_REG_62_AGC_THRESH1 0x62 -#define RH_RF95_REG_63_AGC_THRESH2 0x63 -#define RH_RF95_REG_64_AGC_THRESH3 0x64 +#define RH_RF95_REG_4B_TCXO 0x4b +#define RH_RF95_REG_4D_PA_DAC 0x4d +#define RH_RF95_REG_5B_FORMER_TEMP 0x5b +#define RH_RF95_REG_61_AGC_REF 0x61 +#define RH_RF95_REG_62_AGC_THRESH1 0x62 +#define RH_RF95_REG_63_AGC_THRESH2 0x63 +#define RH_RF95_REG_64_AGC_THRESH3 0x64 // RH_RF95_REG_01_OP_MODE 0x01 -#define RH_RF95_LONG_RANGE_MODE 0x80 -#define RH_RF95_ACCESS_SHARED_REG 0x40 -#define RH_RF95_LOW_FREQUENCY_MODE 0x08 -#define RH_RF95_MODE 0x07 -#define RH_RF95_MODE_SLEEP 0x00 -#define RH_RF95_MODE_STDBY 0x01 -#define RH_RF95_MODE_FSTX 0x02 -#define RH_RF95_MODE_TX 0x03 -#define RH_RF95_MODE_FSRX 0x04 -#define RH_RF95_MODE_RXCONTINUOUS 0x05 -#define RH_RF95_MODE_RXSINGLE 0x06 -#define RH_RF95_MODE_CAD 0x07 +#define RH_RF95_LONG_RANGE_MODE 0x80 +#define RH_RF95_ACCESS_SHARED_REG 0x40 +#define RH_RF95_LOW_FREQUENCY_MODE 0x08 +#define RH_RF95_MODE 0x07 +#define RH_RF95_MODE_SLEEP 0x00 +#define RH_RF95_MODE_STDBY 0x01 +#define RH_RF95_MODE_FSTX 0x02 +#define RH_RF95_MODE_TX 0x03 +#define RH_RF95_MODE_FSRX 0x04 +#define RH_RF95_MODE_RXCONTINUOUS 0x05 +#define RH_RF95_MODE_RXSINGLE 0x06 +#define RH_RF95_MODE_CAD 0x07 // RH_RF95_REG_09_PA_CONFIG 0x09 -#define RH_RF95_PA_SELECT 0x80 -#define RH_RF95_MAX_POWER 0x70 -#define RH_RF95_OUTPUT_POWER 0x0f +#define RH_RF95_PA_SELECT 0x80 +#define RH_RF95_MAX_POWER 0x70 +#define RH_RF95_OUTPUT_POWER 0x0f // RH_RF95_REG_0A_PA_RAMP 0x0a -#define RH_RF95_LOW_PN_TX_PLL_OFF 0x10 -#define RH_RF95_PA_RAMP 0x0f -#define RH_RF95_PA_RAMP_3_4MS 0x00 -#define RH_RF95_PA_RAMP_2MS 0x01 -#define RH_RF95_PA_RAMP_1MS 0x02 -#define RH_RF95_PA_RAMP_500US 0x03 -#define RH_RF95_PA_RAMP_250US 0x04 -#define RH_RF95_PA_RAMP_125US 0x05 -#define RH_RF95_PA_RAMP_100US 0x06 -#define RH_RF95_PA_RAMP_62US 0x07 -#define RH_RF95_PA_RAMP_50US 0x08 -#define RH_RF95_PA_RAMP_40US 0x09 -#define RH_RF95_PA_RAMP_31US 0x0a -#define RH_RF95_PA_RAMP_25US 0x0b -#define RH_RF95_PA_RAMP_20US 0x0c -#define RH_RF95_PA_RAMP_15US 0x0d -#define RH_RF95_PA_RAMP_12US 0x0e -#define RH_RF95_PA_RAMP_10US 0x0f +#define RH_RF95_LOW_PN_TX_PLL_OFF 0x10 +#define RH_RF95_PA_RAMP 0x0f +#define RH_RF95_PA_RAMP_3_4MS 0x00 +#define RH_RF95_PA_RAMP_2MS 0x01 +#define RH_RF95_PA_RAMP_1MS 0x02 +#define RH_RF95_PA_RAMP_500US 0x03 +#define RH_RF95_PA_RAMP_250US 0x04 +#define RH_RF95_PA_RAMP_125US 0x05 +#define RH_RF95_PA_RAMP_100US 0x06 +#define RH_RF95_PA_RAMP_62US 0x07 +#define RH_RF95_PA_RAMP_50US 0x08 +#define RH_RF95_PA_RAMP_40US 0x09 +#define RH_RF95_PA_RAMP_31US 0x0a +#define RH_RF95_PA_RAMP_25US 0x0b +#define RH_RF95_PA_RAMP_20US 0x0c +#define RH_RF95_PA_RAMP_15US 0x0d +#define RH_RF95_PA_RAMP_12US 0x0e +#define RH_RF95_PA_RAMP_10US 0x0f // RH_RF95_REG_0B_OCP 0x0b -#define RH_RF95_OCP_ON 0x20 -#define RH_RF95_OCP_TRIM 0x1f +#define RH_RF95_OCP_ON 0x20 +#define RH_RF95_OCP_TRIM 0x1f // RH_RF95_REG_0C_LNA 0x0c -#define RH_RF95_LNA_GAIN 0xe0 -#define RH_RF95_LNA_GAIN_G1 0x20 -#define RH_RF95_LNA_GAIN_G2 0x40 -#define RH_RF95_LNA_GAIN_G3 0x60 -#define RH_RF95_LNA_GAIN_G4 0x80 -#define RH_RF95_LNA_GAIN_G5 0xa0 -#define RH_RF95_LNA_GAIN_G6 0xc0 -#define RH_RF95_LNA_BOOST_LF 0x18 -#define RH_RF95_LNA_BOOST_LF_DEFAULT 0x00 -#define RH_RF95_LNA_BOOST_HF 0x03 -#define RH_RF95_LNA_BOOST_HF_DEFAULT 0x00 -#define RH_RF95_LNA_BOOST_HF_150PC 0x03 +#define RH_RF95_LNA_GAIN 0xe0 +#define RH_RF95_LNA_GAIN_G1 0x20 +#define RH_RF95_LNA_GAIN_G2 0x40 +#define RH_RF95_LNA_GAIN_G3 0x60 +#define RH_RF95_LNA_GAIN_G4 0x80 +#define RH_RF95_LNA_GAIN_G5 0xa0 +#define RH_RF95_LNA_GAIN_G6 0xc0 +#define RH_RF95_LNA_BOOST_LF 0x18 +#define RH_RF95_LNA_BOOST_LF_DEFAULT 0x00 +#define RH_RF95_LNA_BOOST_HF 0x03 +#define RH_RF95_LNA_BOOST_HF_DEFAULT 0x00 +#define RH_RF95_LNA_BOOST_HF_150PC 0x03 // RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 -#define RH_RF95_RX_TIMEOUT_MASK 0x80 -#define RH_RF95_RX_DONE_MASK 0x40 -#define RH_RF95_PAYLOAD_CRC_ERROR_MASK 0x20 -#define RH_RF95_VALID_HEADER_MASK 0x10 -#define RH_RF95_TX_DONE_MASK 0x08 -#define RH_RF95_CAD_DONE_MASK 0x04 -#define RH_RF95_FHSS_CHANGE_CHANNEL_MASK 0x02 -#define RH_RF95_CAD_DETECTED_MASK 0x01 +#define RH_RF95_RX_TIMEOUT_MASK 0x80 +#define RH_RF95_RX_DONE_MASK 0x40 +#define RH_RF95_PAYLOAD_CRC_ERROR_MASK 0x20 +#define RH_RF95_VALID_HEADER_MASK 0x10 +#define RH_RF95_TX_DONE_MASK 0x08 +#define RH_RF95_CAD_DONE_MASK 0x04 +#define RH_RF95_FHSS_CHANGE_CHANNEL_MASK 0x02 +#define RH_RF95_CAD_DETECTED_MASK 0x01 // RH_RF95_REG_12_IRQ_FLAGS 0x12 -#define RH_RF95_RX_TIMEOUT 0x80 -#define RH_RF95_RX_DONE 0x40 -#define RH_RF95_PAYLOAD_CRC_ERROR 0x20 -#define RH_RF95_VALID_HEADER 0x10 -#define RH_RF95_TX_DONE 0x08 -#define RH_RF95_CAD_DONE 0x04 -#define RH_RF95_FHSS_CHANGE_CHANNEL 0x02 -#define RH_RF95_CAD_DETECTED 0x01 +#define RH_RF95_RX_TIMEOUT 0x80 +#define RH_RF95_RX_DONE 0x40 +#define RH_RF95_PAYLOAD_CRC_ERROR 0x20 +#define RH_RF95_VALID_HEADER 0x10 +#define RH_RF95_TX_DONE 0x08 +#define RH_RF95_CAD_DONE 0x04 +#define RH_RF95_FHSS_CHANGE_CHANNEL 0x02 +#define RH_RF95_CAD_DETECTED 0x01 // RH_RF95_REG_18_MODEM_STAT 0x18 -#define RH_RF95_RX_CODING_RATE 0xe0 -#define RH_RF95_MODEM_STATUS_CLEAR 0x10 -#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08 -#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04 -#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02 -#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01 +#define RH_RF95_RX_CODING_RATE 0xe0 +#define RH_RF95_MODEM_STATUS_CLEAR 0x10 +#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08 +#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04 +#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02 +#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01 // RH_RF95_REG_1C_HOP_CHANNEL 0x1c -#define RH_RF95_PLL_TIMEOUT 0x80 -#define RH_RF95_RX_PAYLOAD_CRC_IS_ON 0x40 -#define RH_RF95_FHSS_PRESENT_CHANNEL 0x3f +#define RH_RF95_PLL_TIMEOUT 0x80 +#define RH_RF95_RX_PAYLOAD_CRC_IS_ON 0x40 +#define RH_RF95_FHSS_PRESENT_CHANNEL 0x3f // RH_RF95_REG_1D_MODEM_CONFIG1 0x1d -#define RH_RF95_BW 0xf0 +#define RH_RF95_BW 0xf0 -#define RH_RF95_BW_7_8KHZ 0x00 -#define RH_RF95_BW_10_4KHZ 0x10 -#define RH_RF95_BW_15_6KHZ 0x20 -#define RH_RF95_BW_20_8KHZ 0x30 -#define RH_RF95_BW_31_25KHZ 0x40 -#define RH_RF95_BW_41_7KHZ 0x50 -#define RH_RF95_BW_62_5KHZ 0x60 -#define RH_RF95_BW_125KHZ 0x70 -#define RH_RF95_BW_250KHZ 0x80 -#define RH_RF95_BW_500KHZ 0x90 -#define RH_RF95_CODING_RATE 0x0e -#define RH_RF95_CODING_RATE_4_5 0x02 -#define RH_RF95_CODING_RATE_4_6 0x04 -#define RH_RF95_CODING_RATE_4_7 0x06 -#define RH_RF95_CODING_RATE_4_8 0x08 -#define RH_RF95_IMPLICIT_HEADER_MODE_ON 0x01 +#define RH_RF95_BW_7_8KHZ 0x00 +#define RH_RF95_BW_10_4KHZ 0x10 +#define RH_RF95_BW_15_6KHZ 0x20 +#define RH_RF95_BW_20_8KHZ 0x30 +#define RH_RF95_BW_31_25KHZ 0x40 +#define RH_RF95_BW_41_7KHZ 0x50 +#define RH_RF95_BW_62_5KHZ 0x60 +#define RH_RF95_BW_125KHZ 0x70 +#define RH_RF95_BW_250KHZ 0x80 +#define RH_RF95_BW_500KHZ 0x90 +#define RH_RF95_CODING_RATE 0x0e +#define RH_RF95_CODING_RATE_4_5 0x02 +#define RH_RF95_CODING_RATE_4_6 0x04 +#define RH_RF95_CODING_RATE_4_7 0x06 +#define RH_RF95_CODING_RATE_4_8 0x08 +#define RH_RF95_IMPLICIT_HEADER_MODE_ON 0x01 // RH_RF95_REG_1E_MODEM_CONFIG2 0x1e -#define RH_RF95_SPREADING_FACTOR 0xf0 -#define RH_RF95_SPREADING_FACTOR_64CPS 0x60 -#define RH_RF95_SPREADING_FACTOR_128CPS 0x70 -#define RH_RF95_SPREADING_FACTOR_256CPS 0x80 -#define RH_RF95_SPREADING_FACTOR_512CPS 0x90 -#define RH_RF95_SPREADING_FACTOR_1024CPS 0xa0 -#define RH_RF95_SPREADING_FACTOR_2048CPS 0xb0 -#define RH_RF95_SPREADING_FACTOR_4096CPS 0xc0 -#define RH_RF95_TX_CONTINUOUS_MODE 0x08 +#define RH_RF95_SPREADING_FACTOR 0xf0 +#define RH_RF95_SPREADING_FACTOR_64CPS 0x60 +#define RH_RF95_SPREADING_FACTOR_128CPS 0x70 +#define RH_RF95_SPREADING_FACTOR_256CPS 0x80 +#define RH_RF95_SPREADING_FACTOR_512CPS 0x90 +#define RH_RF95_SPREADING_FACTOR_1024CPS 0xa0 +#define RH_RF95_SPREADING_FACTOR_2048CPS 0xb0 +#define RH_RF95_SPREADING_FACTOR_4096CPS 0xc0 +#define RH_RF95_TX_CONTINUOUS_MODE 0x08 -#define RH_RF95_PAYLOAD_CRC_ON 0x04 -#define RH_RF95_SYM_TIMEOUT_MSB 0x03 +#define RH_RF95_PAYLOAD_CRC_ON 0x04 +#define RH_RF95_SYM_TIMEOUT_MSB 0x03 // RH_RF95_REG_26_MODEM_CONFIG3 -#define RH_RF95_MOBILE_NODE 0x08 // HopeRF term -#define RH_RF95_LOW_DATA_RATE_OPTIMIZE 0x08 // Semtechs term -#define RH_RF95_AGC_AUTO_ON 0x04 +#define RH_RF95_MOBILE_NODE 0x08 // HopeRF term +#define RH_RF95_LOW_DATA_RATE_OPTIMIZE 0x08 // Semtechs term +#define RH_RF95_AGC_AUTO_ON 0x04 // RH_RF95_REG_4B_TCXO 0x4b -#define RH_RF95_TCXO_TCXO_INPUT_ON 0x10 +#define RH_RF95_TCXO_TCXO_INPUT_ON 0x10 // RH_RF95_REG_4D_PA_DAC 0x4d -#define RH_RF95_PA_DAC_DISABLE 0x04 -#define RH_RF95_PA_DAC_ENABLE 0x07 +#define RH_RF95_PA_DAC_DISABLE 0x04 +#define RH_RF95_PA_DAC_ENABLE 0x07 ///////////////////////////////////////////////////////////////////// /// \class RH_RF95 RH_RF95.h -/// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa +/// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa /// capable radio transceiver. /// /// For Semtech SX1276/77/78/79 and HopeRF RF95/96/97/98 and other similar LoRa capable radios. @@ -257,27 +256,28 @@ /// /// Works with /// - the excellent MiniWirelessLoRa from Anarduino http://www.anarduino.com/miniwireless -/// - The excellent Modtronix inAir4 http://modtronix.com/inair4.html +/// - The excellent Modtronix inAir4 http://modtronix.com/inair4.html /// and inAir9 modules http://modtronix.com/inair9.html. -/// - the excellent Rocket Scream Mini Ultra Pro with the RFM95W +/// - the excellent Rocket Scream Mini Ultra Pro with the RFM95W /// http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ /// - Lora1276 module from NiceRF http://www.nicerf.com/product_view.aspx?id=99 /// - Adafruit Feather M0 with RFM95 /// - The very fine Talk2 Whisper Node LoRa boards https://wisen.com.au/store/products/whisper-node-lora -/// an Arduino compatible board, which include an on-board RFM95/96 LoRa Radio (Semtech SX1276), external antenna, +/// an Arduino compatible board, which include an on-board RFM95/96 LoRa Radio (Semtech SX1276), external antenna, /// run on 2xAAA batteries and support low power operations. RF95 examples work without modification. /// Use Arduino Board Manager to install the Talk2 code support. Upload the code with an FTDI adapter set to 5V. -/// - heltec / TTGO ESP32 LoRa OLED https://www.aliexpress.com/item/Internet-Development-Board-SX1278-ESP32-WIFI-chip-0-96-inch-OLED-Bluetooth-WIFI-Lora-Kit-32/32824535649.html +/// - heltec / TTGO ESP32 LoRa OLED +/// https://www.aliexpress.com/item/Internet-Development-Board-SX1278-ESP32-WIFI-chip-0-96-inch-OLED-Bluetooth-WIFI-Lora-Kit-32/32824535649.html /// /// \par Overview /// -/// This class provides basic functions for sending and receiving unaddressed, +/// This class provides basic functions for sending and receiving unaddressed, /// unreliable datagrams of arbitrary length to 251 octets per packet. /// -/// Manager classes may use this class to implement reliable, addressed datagrams and streams, +/// Manager classes may use this class to implement reliable, addressed datagrams and streams, /// mesh routers, repeaters, translators etc. /// -/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and /// modulation scheme. /// /// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF @@ -301,7 +301,7 @@ /// also provided. /// /// Tested on MinWirelessLoRa with arduino-1.0.5 -/// on OpenSuSE 13.1. +/// on OpenSuSE 13.1. /// Also tested with Teensy3.1, Modtronix inAir4 and Arduino 1.6.5 on OpenSuSE 13.1 /// /// \par Packet Format @@ -312,7 +312,7 @@ /// - 8 symbol PREAMBLE /// - Explicit header with header CRC (handled internally by the radio) /// - 4 octets HEADER: (TO, FROM, ID, FLAGS) -/// - 0 to 251 octets DATA +/// - 0 to 251 octets DATA /// - CRC (handled internally by the radio) /// /// \par Connecting RFM95/96/97/98 and Semtech SX1276/77/78/79 to Arduino @@ -326,7 +326,7 @@ /// Arduino, otherwise you will also need voltage level shifters between the /// Arduino and the RFM95. CAUTION, you must also ensure you connect an /// antenna. -/// +/// /// \code /// Arduino RFM95/96/97/98 /// GND----------GND (ground in) @@ -363,11 +363,11 @@ /// /// Note that if you are using Modtronix inAir4 or inAir9,or any other module which uses the /// transmitter RFO pins and not the PA_BOOST pins -/// that you must configure the power transmitter power for -1 to 14 dBm and with useRFO true. +/// that you must configure the power transmitter power for -1 to 14 dBm and with useRFO true. /// Failure to do that will result in extremely low transmit powers. /// -/// If you have an Arduino M0 Pro from arduino.org, -/// you should note that you cannot use Pin 2 for the interrupt line +/// If you have an Arduino M0 Pro from arduino.org, +/// you should note that you cannot use Pin 2 for the interrupt line /// (Pin 2 is for the NMI only). The same comments apply to Pin 4 on Arduino Zero from arduino.cc. /// Instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this: /// \code @@ -377,7 +377,7 @@ /// /// If you have a Rocket Scream Mini Ultra Pro with the RFM95W: /// - Ensure you have Arduino SAMD board support 1.6.5 or later in Arduino IDE 1.6.8 or later. -/// - The radio SS is hardwired to pin D5 and the DIO0 interrupt to pin D2, +/// - The radio SS is hardwired to pin D5 and the DIO0 interrupt to pin D2, /// so you need to initialise the radio like this: /// \code /// RH_RF95 driver(5, 2); @@ -387,7 +387,7 @@ /// \code /// #define Serial SerialUSB /// \endcode -/// - You also need this in setup before radio initialisation +/// - You also need this in setup before radio initialisation /// \code /// // Ensure serial flash is not interfering with radio communication on SPI bus /// pinMode(4, OUTPUT); @@ -403,13 +403,13 @@ /// #include /// RH_RF95 rf95(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W /// #define Serial SerialUSB -/// -/// void setup() +/// +/// void setup() /// { /// // Ensure serial flash is not interfering with radio communication on SPI bus /// pinMode(4, OUTPUT); /// digitalWrite(4, HIGH); -/// +/// /// Serial.begin(9600); /// while (!Serial) ; // Wait for serial port to be available /// if (!rf95.init()) @@ -424,7 +424,7 @@ /// RH_RF95 rf95(8, 3); /// \endcode /// -/// If you have a talk2 Whisper Node LoRa board with on-board RF95 radio, +/// If you have a talk2 Whisper Node LoRa board with on-board RF95 radio, /// the example rf95_* sketches work without modification. Initialise the radio like /// with the default constructor: /// \code @@ -440,7 +440,7 @@ /// need to set the usual SS pin to be an output to force the Arduino into SPI /// master mode. /// -/// Caution: Power supply requirements of the RFM module may be relevant in some circumstances: +/// Caution: Power supply requirements of the RFM module may be relevant in some circumstances: /// RFM95/96/97/98 modules are capable of pulling 120mA+ at full power, where Arduino's 3.3V line can /// give 50mA. You may need to make provision for alternate power supply for /// the RFM module, especially if you wish to use full transmit power, and/or you have @@ -469,7 +469,7 @@ /// more critical. Therefore, you should be vary sparing with RAM use in /// programs that use the RH_RF95 driver. /// -/// It is often hard to accurately identify when you are hitting RAM limits on Arduino. +/// It is often hard to accurately identify when you are hitting RAM limits on Arduino. /// The symptoms can include: /// - Mysterious crashes and restarts /// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements) @@ -489,18 +489,18 @@ /// /// It should be noted that at this data rate, a 12 octet message takes 2 seconds to transmit. /// -/// At 20dBm (100mW) with Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. +/// At 20dBm (100mW) with Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. /// (Default medium range) in the conditions described above. /// - Range over flat ground through heavy trees and vegetation approx 2km. /// /// Caution: the performance of this radio, especially with narrow bandwidths is strongly dependent on the -/// accuracy and stability of the chip clock. HopeRF and Semtech do not appear to -/// recommend bandwidths of less than 62.5 kHz -/// unless you have the optional Temperature Compensated Crystal Oscillator (TCXO) installed and +/// accuracy and stability of the chip clock. HopeRF and Semtech do not appear to +/// recommend bandwidths of less than 62.5 kHz +/// unless you have the optional Temperature Compensated Crystal Oscillator (TCXO) installed and /// enabled on your radio module. See the refernece manual for more data. /// Also https://lowpowerlab.com/forum/rf-range-antennas-rfm69-library/lora-library-experiences-range/15/ /// and http://www.semtech.com/images/datasheet/an120014-xo-guidance-lora-modulation.pdf -/// +/// /// \par Transmitter Power /// /// You can control the transmitter power on the RF transceiver @@ -533,10 +533,10 @@ /// 15 15 /// 17 16 /// 19 18 -/// 20 20 -/// 21 21 -/// 22 22 -/// 23 23 +/// 20 20 +/// 21 21 +/// 22 22 +/// 23 23 /// \endcode /// /// We have also measured the actual power output from a Modtronix inAir4 http://modtronix.com/inair4.html @@ -563,7 +563,7 @@ /// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna. class RH_RF95 : public RHSPIDriver { -public: + public: /// \brief Defines register values for a set of modem configuration registers /// /// Defines register values for a set of modem configuration registers @@ -571,32 +571,30 @@ public: /// ModemConfigChoice suit your need setModemRegisters() writes the /// register values from this structure to the appropriate registers /// to set the desired spreading factor, coding rate and bandwidth - typedef struct - { - uint8_t reg_1d; ///< Value for register RH_RF95_REG_1D_MODEM_CONFIG1 - uint8_t reg_1e; ///< Value for register RH_RF95_REG_1E_MODEM_CONFIG2 - uint8_t reg_26; ///< Value for register RH_RF95_REG_26_MODEM_CONFIG3 + typedef struct { + uint8_t reg_1d; ///< Value for register RH_RF95_REG_1D_MODEM_CONFIG1 + uint8_t reg_1e; ///< Value for register RH_RF95_REG_1E_MODEM_CONFIG2 + uint8_t reg_26; ///< Value for register RH_RF95_REG_26_MODEM_CONFIG3 } ModemConfig; - + /// Choices for setModemConfig() for a selected subset of common /// data rates. If you need another configuration, /// determine the necessary settings and call setModemRegisters() with your - /// desired settings. It might be helpful to use the LoRa calculator mentioned in + /// desired settings. It might be helpful to use the LoRa calculator mentioned in /// http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic /// definitions and not their integer equivalents: its possible that new values will be /// introduced in later versions (though we will try to avoid it). /// Caution: if you are using slow packet rates and long packets with RHReliableDatagram or subclasses /// you may need to change the RHReliableDatagram timeout for reliable operations. - /// Caution: for some slow rates nad with ReliableDatagrams youi may need to increase the reply timeout + /// Caution: for some slow rates nad with ReliableDatagrams youi may need to increase the reply timeout /// with manager.setTimeout() to /// deal with the long transmission times. - typedef enum - { - Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range - Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range - Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range - Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range + typedef enum { + Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range } ModemConfigChoice; /// Constructor. You can have multiple instances, but each instance must have its own @@ -605,28 +603,28 @@ public: /// distinct interrupt lines, one for each instance. /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RH_RF22 before /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) - /// \param[in] interruptPin The interrupt Pin number that is connected to the RFM DIO0 interrupt line. + /// \param[in] interruptPin The interrupt Pin number that is connected to the RFM DIO0 interrupt line. /// Defaults to pin 2, as required by Anarduino MinWirelessLoRa module. /// Caution: You must specify an interrupt capable pin. /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. /// On Arduino Zero from arduino.cc, any digital pin other than 4. /// On Arduino M0 Pro from arduino.org, any digital pin other than 2. - /// On other Arduinos pins 2 or 3. + /// On other Arduinos pins 2 or 3. /// See http://arduino.cc/en/Reference/attachInterrupt for more details. /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. /// On other boards, any digital pin may be used. - /// \param[in] spi Pointer to the SPI interface object to use. + /// \param[in] spi Pointer to the SPI interface object to use. /// Defaults to the standard Arduino hardware SPI interface - RH_RF95(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI& spi = hardware_spi); - + RH_RF95(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI &spi = hardware_spi); + /// 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(); + virtual bool init(); /// The main CPU is about to enter deep sleep, prepare the RF95 so it will be able to wake properly after we reboot - /// i.e. confirm we are in idle or rx mode, set a rtcram flag with state we need to restore after boot. Later in boot + /// i.e. confirm we are in idle or rx mode, set a rtcram flag with state we need to restore after boot. Later in boot /// we'll need to be careful not to wipe registers and be ready to handle any pending interrupts that occurred while /// the main CPU was powered down. void prepareDeepSleep(); @@ -637,110 +635,87 @@ public: /// \return true on success bool printRegisters(); - /// Sets all the registered required to configure the data modem in the RF95/96/97/98, including the bandwidth, - /// spreading factor etc. You can use this to configure the modem with custom configurations if none of the + /// Sets all the registered required to configure the data modem in the RF95/96/97/98, including the bandwidth, + /// spreading factor etc. You can use this to configure the modem with custom configurations if none of the /// canned configurations in ModemConfigChoice suit you. /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. - void setModemRegisters(const ModemConfig* config); + void setModemRegisters(const ModemConfig *config); - /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// Select one of the predefined modem configurations. If you need a modem configuration not provided /// here, use setModemRegisters() with your own ModemConfig. /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator /// for reliable operation. /// \param[in] index The configuration choice. /// \return true if index is a valid choice. - bool setModemConfig(ModemConfigChoice index); + bool setModemConfig(ModemConfigChoice index); /// Tests whether a new message is available - /// from the Driver. + /// from the Driver. /// On most drivers, this will also put the Driver into RHModeRx mode until /// a message is actually received by the transport, when it wil be returned to RHModeIdle. /// This can be called multiple times in a timeout loop /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() - virtual bool available(); - - /// Turns the receiver on if it not already on. - /// If there is a valid message available, copy it to buf and return true - /// else return false. - /// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted). - /// You should be sure to call this function frequently enough to not miss any messages - /// It is recommended that you call it in your main loop. - /// \param[in] buf Location to copy the received message - /// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied. - /// \return true if a valid message was copied to buf - virtual bool recv(uint8_t* buf, uint8_t* len); - - /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). - /// Then optionally waits for Channel Activity Detection (CAD) - /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). - /// Then loads a message into the transmitter and starts the transmitter. Note that a message length - /// of 0 is permitted. - /// \param[in] data Array of data to be sent - /// \param[in] len Number of bytes of data to send - /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. - /// \return true if the message length was valid and it was correctly queued for transmit. Return false - /// if CAD was requested and the CAD timeout timed out before clear channel was detected. - virtual bool send(const uint8_t* data, uint8_t len); + virtual bool available(); /// Sets the length of the preamble - /// in bytes. - /// Caution: this should be set to the same + /// in bytes. + /// Caution: this should be set to the same /// value on all nodes in your network. Default is 8. /// Sets the message preamble length in RH_RF95_REG_??_PREAMBLE_?SB - /// \param[in] bytes Preamble length in bytes. - void setPreambleLength(uint16_t bytes); + /// \param[in] bytes Preamble length in bytes. + void setPreambleLength(uint16_t bytes); - /// Returns the maximum message length + /// Returns the maximum message length /// available in this Driver. /// \return The maximum legal message length virtual uint8_t maxMessageLength(); - /// Sets the transmitter and receiver + /// Sets the transmitter and receiver /// centre frequency. /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work /// \return true if the selected frquency centre is within range - bool setFrequency(float centre); + bool setFrequency(float centre); - /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, /// disables them. - void setModeIdle(); + void setModeIdle(); - /// If current mode is Tx or Idle, changes it to Rx. + /// If current mode is Tx or Idle, changes it to Rx. /// Starts the receiver in the RF95/96/97/98. - void setModeRx(); + void setModeRx(); /// If current mode is Rx or Idle, changes it to Rx. F /// Starts the transmitter in the RF95/96/97/98. - void setModeTx(); + void setModeTx(); /// Sets the transmitter power output level, and configures the transmitter pin. /// Be a good neighbour and set the lowest power level you need. - /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) + /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) - /// while some (such as the Modtronix inAir4 and inAir9) + /// while some (such as the Modtronix inAir4 and inAir9) /// use the RFO transmitter pin for lower power but higher efficiency. /// You must set the appropriate power level and useRFO argument for your module. /// Check with your module manufacturer which transmtter pin is used on your module - /// to ensure you are setting useRFO correctly. - /// Failure to do so will result in very low + /// to ensure you are setting useRFO correctly. + /// Failure to do so will result in very low /// transmitter power output. /// Caution: legal power limits may apply in certain countries. /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). - /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, + /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, /// valid values are from +5 to +23. - /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), + /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), /// valid values are from -1 to 14. /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of /// the PA_BOOST pin (false). Choose the correct setting for your module. - void setTxPower(int8_t power, bool useRFO = false); + void setTxPower(int8_t power, bool useRFO = false); /// Sets the radio into low-power sleep mode. - /// If successful, the transport will stay in sleep mode until woken by + /// If successful, the transport will stay in sleep mode until woken by /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. /// \return true if sleep mode was successfully entered. - virtual bool sleep(); + virtual bool sleep(); // Bent G Christensen (bentor@gmail.com), 08/15/2016 /// Use the radio's Channel Activity Detect (CAD) function to detect channel activity. @@ -748,11 +723,11 @@ public: /// To be used in a listen-before-talk mechanism (Collision Avoidance) /// with a reasonable time backoff algorithm. /// This is called automatically by waitCAD(). - /// \return true if channel is in use. - virtual bool isChannelActive(); + /// \return true if channel is in use. + virtual bool isChannelActive(); /// Enable TCXO mode - /// Call this immediately after init(), to force your radio to use an external + /// Call this immediately after init(), to force your radio to use an external /// frequency source, such as a Temperature Compensated Crystal Oscillator (TCXO), if available. /// See the comments in the main documentation about the sensitivity of this radio to /// clock frequency especially when using narrow bandwidths. @@ -764,12 +739,12 @@ public: /// Returns the last measured frequency error. /// The LoRa receiver estimates the frequency offset between the receiver centre frequency - /// and that of the received LoRa signal. This function returns the estimates offset (in Hz) - /// of the last received message. Caution: this measurement is not absolute, but is measured - /// relative to the local receiver's oscillator. + /// and that of the received LoRa signal. This function returns the estimates offset (in Hz) + /// of the last received message. Caution: this measurement is not absolute, but is measured + /// relative to the local receiver's oscillator. /// Apparent errors may be due to the transmitter, the receiver or both. - /// \return The estimated centre frequency offset in Hz of the last received message. - /// If the modem bandwidth selector in + /// \return The estimated centre frequency offset in Hz of the last received message. + /// If the modem bandwidth selector in /// register RH_RF95_REG_1D_MODEM_CONFIG1 is invalid, returns 0. int frequencyError(); @@ -787,8 +762,8 @@ public: /// /// \param[in] uint8_t sf (spreading factor 6..12) /// \return nothing - void setSpreadingFactor(uint8_t sf); - + void setSpreadingFactor(uint8_t sf); + /// brian.n.norman@gmail.com 9th Nov 2018 /// Sets the radio signal bandwidth /// sbw ranges and resultant settings are as follows:- @@ -806,32 +781,32 @@ public: /// NOTE caution Earlier - Semtech do not recommend BW below 62.5 although, in testing /// I managed 31.25 with two devices in close proximity. /// \param[in] sbw long, signal bandwidth e.g. 125000 - void setSignalBandwidth(long sbw); - + void setSignalBandwidth(long sbw); + /// brian.n.norman@gmail.com 9th Nov 2018 /// Sets the coding rate to 4/5, 4/6, 4/7 or 4/8. /// Valid denominator values are 5, 6, 7 or 8. A value of 5 sets the coding rate to 4/5 etc. /// Values below 5 are clamped at 5 /// values above 8 are clamped at 8 /// \param[in] denominator uint8_t range 5..8 - void setCodingRate4(uint8_t denominator); - + void setCodingRate4(uint8_t denominator); + /// brian.n.norman@gmail.com 9th Nov 2018 /// sets the low data rate flag if symbol time exceeds 16ms /// ref: https://www.thethingsnetwork.org/forum/t/a-point-to-note-lora-low-data-rate-optimisation-flag/12007 /// called by setBandwidth() and setSpreadingfactor() since these affect the symbol time. - void setLowDatarate(); - + void setLowDatarate(); + /// brian.n.norman@gmail.com 9th Nov 2018 /// allows the payload CRC bit to be turned on/off. Normally this should be left on /// so that packets with a bad CRC are rejected /// \patam[in] on bool, true turns the payload CRC on, false turns it off void setPayloadCRC(bool on); - + /// Return true if we are currently receiving a packet bool isReceiving(); - -protected: + + protected: /// This is a low level function to handle the interrupts for one instance of RH_RF95. /// Called automatically by isr*() /// Should not need to be called by user code. @@ -843,44 +818,56 @@ protected: /// Clear our local receive buffer void clearRxBuf(); -private: + /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). + /// Then optionally waits for Channel Activity Detection (CAD) + /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). + /// Then loads a message into the transmitter and starts the transmitter. Note that a message length + /// of 0 is permitted. + /// \param[in] data Array of data to be sent + /// \param[in] len Number of bytes of data to send + /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. + /// \return true if the message length was valid and it was correctly queued for transmit. Return false + /// if CAD was requested and the CAD timeout timed out before clear channel was detected. + virtual bool send(const uint8_t *data, uint8_t len); + + private: /// Low level interrupt service routine for device connected to interrupt 0 - static void isr0(); + static void isr0(); /// Low level interrupt service routine for device connected to interrupt 1 - static void isr1(); + static void isr1(); /// Low level interrupt service routine for device connected to interrupt 1 - static void isr2(); + static void isr2(); /// Array of instances connected to interrupts 0 and 1 - static RH_RF95* _deviceForInterrupt[]; + static RH_RF95 *_deviceForInterrupt[]; /// Index of next interrupt number to use in _deviceForInterrupt - static uint8_t _interruptCount; + static uint8_t _interruptCount; /// The configured interrupt pin connected to this instance - uint8_t _interruptPin; + uint8_t _interruptPin; /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) /// else 0xff - uint8_t _myInterruptIndex; + uint8_t _myInterruptIndex; // True if we are using the HF port (779.0 MHz and above) - bool _usingHFport; + bool _usingHFport; // Last measured SNR, dB - int8_t _lastSNR; + int8_t _lastSNR; -protected: + protected: /// Number of octets in the buffer - volatile uint8_t _bufLen; - + volatile uint8_t _bufLen; + /// The receiver/transmitter buffer - uint8_t _buf[RH_RF95_MAX_PAYLOAD_LEN]; + uint8_t _buf[RH_RF95_MAX_PAYLOAD_LEN]; /// True when there is a valid message in the buffer - volatile bool _rxBufValid; + volatile bool _rxBufValid; }; /// @example rf95_client.pde @@ -891,4 +878,3 @@ protected: /// @example rf95_reliable_datagram_server.pde #endif - From 80c69c28cd14239fa50880549379ccb1153c31a1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 14 Apr 2020 13:20:36 -0700 Subject: [PATCH 009/197] move pool/queue management into the rf95 lib --- src/{ => rf95}/MemoryPool.h | 0 src/{ => rf95}/PointerQueue.h | 0 src/{ => rf95}/TypedQueue.h | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => rf95}/MemoryPool.h (100%) rename src/{ => rf95}/PointerQueue.h (100%) rename src/{ => rf95}/TypedQueue.h (100%) diff --git a/src/MemoryPool.h b/src/rf95/MemoryPool.h similarity index 100% rename from src/MemoryPool.h rename to src/rf95/MemoryPool.h diff --git a/src/PointerQueue.h b/src/rf95/PointerQueue.h similarity index 100% rename from src/PointerQueue.h rename to src/rf95/PointerQueue.h diff --git a/src/TypedQueue.h b/src/rf95/TypedQueue.h similarity index 100% rename from src/TypedQueue.h rename to src/rf95/TypedQueue.h From 5ca149fac92be9c55e791b60ae043b51d8d9e2a1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 14 Apr 2020 14:36:26 -0700 Subject: [PATCH 010/197] move radiointerface into lib --- src/{ => rf95}/RadioInterface.cpp | 0 src/{ => rf95}/RadioInterface.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => rf95}/RadioInterface.cpp (100%) rename src/{ => rf95}/RadioInterface.h (100%) diff --git a/src/RadioInterface.cpp b/src/rf95/RadioInterface.cpp similarity index 100% rename from src/RadioInterface.cpp rename to src/rf95/RadioInterface.cpp diff --git a/src/RadioInterface.h b/src/rf95/RadioInterface.h similarity index 100% rename from src/RadioInterface.h rename to src/rf95/RadioInterface.h From 0b62083e355f3468d83b614d0de79e38ffb1c7e8 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 14 Apr 2020 16:45:26 -0700 Subject: [PATCH 011/197] wip - plan --- platformio.ini | 1 + src/rf95/kh-todo.txt | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 src/rf95/kh-todo.txt diff --git a/platformio.ini b/platformio.ini index 077e825dc..5d388aafe 100644 --- a/platformio.ini +++ b/platformio.ini @@ -65,6 +65,7 @@ debug_tool = jlink debug_init_break = tbreak setup lib_deps = + https://github.com/meshtastic/LoRaLayer2.git https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306 SPI ; 1260 ; OneButton - not used yet diff --git a/src/rf95/kh-todo.txt b/src/rf95/kh-todo.txt new file mode 100644 index 000000000..fbd22439b --- /dev/null +++ b/src/rf95/kh-todo.txt @@ -0,0 +1,21 @@ +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 + +much later: +* allow packets to be filtered at the device level RX time based on dest addr (to avoid waking main CPU unnecessarily) From 0a6af936ed800e6750a51eb448cf7d3307a24286 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 14 Apr 2020 20:22:27 -0700 Subject: [PATCH 012/197] Get build (kinda, not really) running on a NRF52 Lots of NO_ESP32 to remove later... --- .vscode/launch.json | 10 +++--- .vscode/settings.json | 7 +++- platformio.ini | 41 ++++++++++++---------- src/GPS.cpp | 10 ++++++ src/MeshService.cpp | 1 + src/MeshService.h | 1 + src/NodeDB.cpp | 27 +++++++++++---- src/PowerFSM.cpp | 2 ++ src/bare/FS.h | 3 ++ src/bare/SPIFFS.h | 3 ++ src/configuration.h | 18 ++++++++++ src/debug.cpp | 6 ++-- src/freertosinc.h | 16 +++++++++ src/lock.h | 3 +- src/main.cpp | 26 ++++++++++---- src/mesh-pb-constants.cpp | 9 +++-- src/rf95/MemoryPool.h | 6 ++-- src/rf95/RHutil/atomic.h | 71 +++++++++++++++++++++++++++++++++++++++ src/rf95/TypedQueue.h | 3 +- src/screen.h | 1 + src/sleep.cpp | 23 +++++++++---- src/sleep.h | 8 +++-- 22 files changed, 240 insertions(+), 55 deletions(-) create mode 100644 src/bare/FS.h create mode 100644 src/bare/SPIFFS.h create mode 100644 src/freertosinc.h create mode 100644 src/rf95/RHutil/atomic.h diff --git a/.vscode/launch.json b/.vscode/launch.json index 914831d68..77b1fb363 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,8 +12,9 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", + "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", "preLaunchTask": { "type": "PlatformIO", "task": "Pre-Debug" @@ -24,8 +25,9 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug (skip Pre-Debug)", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", + "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", "internalConsoleOptions": "openOnSessionStart" } ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 6622b9aeb..dfe3b542f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -42,7 +42,12 @@ "typeinfo": "cpp", "string": "cpp", "*.xbm": "cpp", - "list": "cpp" + "list": "cpp", + "atomic": "cpp", + "memory_resource": "cpp", + "optional": "cpp", + "string_view": "cpp", + "cassert": "cpp" }, "cSpell.words": [ "Meshtastic", diff --git a/platformio.ini b/platformio.ini index 5d388aafe..c15d4d0db 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,16 +9,12 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +default_envs = bare [common] ; default to a US frequency range, change it as needed for your region and hardware (CN, JP, EU433, EU865) hw_version = US -; Common settings for ESP targes, mixin with extends = esp32_base -[esp32_base] -src_filter = - ${env.src_filter} - [env] platform = espressif32 @@ -30,7 +26,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 = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${common.hw_version} +build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${common.hw_version} ; not needed included in ttgo-t-beam board file ; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram @@ -43,8 +39,6 @@ build_flags = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os ;upload_port = /dev/ttyUSB0 ;monitor_port = /dev/ttyUSB0 -upload_speed = 921600 - ; the default is esptool ; upload_protocol = esp-prog @@ -62,8 +56,6 @@ debug_tool = jlink ;debug_init_cmds = ; monitor adapter_khz 10000 -debug_init_break = tbreak setup - lib_deps = https://github.com/meshtastic/LoRaLayer2.git https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306 @@ -74,6 +66,15 @@ lib_deps = https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git +; Common settings for ESP targes, mixin with extends = esp32_base +[esp32_base] +src_filter = + ${env.src_filter} - +upload_speed = 921600 +debug_init_break = tbreak setup +build_flags = + ${env.build_flags} -Wall -Wextra + ; The 1.0 release of the TBEAM board [env:tbeam] extends = esp32_base @@ -82,14 +83,14 @@ lib_deps = ${env.lib_deps} AXP202X_Library build_flags = - ${env.build_flags} -D TBEAM_V10 + ${esp32_base.build_flags} -D TBEAM_V10 ; The original TBEAM board without the AXP power chip and a few other changes [env:tbeam0.7] extends = esp32_base board = ttgo-t-beam build_flags = - ${env.build_flags} -D TBEAM_V07 + ${esp32_base.build_flags} -D TBEAM_V07 [env:heltec] ;build_type = debug ; to make it possible to step through our jtag debugger @@ -100,22 +101,28 @@ board = heltec_wifi_lora_32_V2 extends = esp32_base board = ttgo-lora32-v1 build_flags = - ${env.build_flags} -D TTGO_LORA_V1 + ${esp32_base.build_flags} -D TTGO_LORA_V1 ; note: the platformio definition for lora32-v2 seems stale, it is missing a pins_arduino.h file, therefore I don't think it works [env:ttgo-lora32-v2] extends = esp32_base board = ttgo-lora32-v1 build_flags = - ${env.build_flags} -D TTGO_LORA_V2 + ${esp32_base.build_flags} -D TTGO_LORA_V2 ; This is a temporary build target to test turning off particular hardare bits in the build (to improve modularity) [env:bare] -board = ttgo-lora32-v1 +platform = nordicnrf52 +board = nrf52840_dk_adafruit ; nicer than nrf52840_dk - more full gpio mappings +framework = arduino +debug_tool = jlink build_flags = - ${env.build_flags} -D BARE_BOARD + ${env.build_flags} -D BARE_BOARD -Wno-unused-variable -Isrc/bare src_filter = ${env.src_filter} - lib_ignore = - BluetoothOTA \ No newline at end of file + BluetoothOTA +lib_deps = + ${env.lib_deps} +monitor_port = /dev/ttyACM1 \ No newline at end of file diff --git a/src/GPS.cpp b/src/GPS.cpp index ea04b09f9..60dedf429 100644 --- a/src/GPS.cpp +++ b/src/GPS.cpp @@ -2,9 +2,15 @@ #include "GPS.h" #include "configuration.h" #include "time.h" +#include #include +#ifdef GPS_RX_PIN HardwareSerial _serial_gps(GPS_SERIAL_NUM); +#else +// Assume NRF52 +// Uart _serial_gps(GPS_SERIAL_NUM); +#endif bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep @@ -97,7 +103,11 @@ void GPS::perhapsSetRTC(const struct timeval *tv) if (!timeSetFromGPS) { timeSetFromGPS = true; DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec); +#ifndef NO_ESP32 settimeofday(tv, NULL); +#else + assert(0); +#endif readFromRTC(); } } diff --git a/src/MeshService.cpp b/src/MeshService.cpp index c8e947acf..c7794bde8 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -1,6 +1,7 @@ #include #include +#include #include "GPS.h" //#include "MeshBluetoothService.h" diff --git a/src/MeshService.h b/src/MeshService.h index e9d4fe488..235801211 100644 --- a/src/MeshService.h +++ b/src/MeshService.h @@ -2,6 +2,7 @@ #include #include +#include #include "MemoryPool.h" #include "MeshRadio.h" diff --git a/src/NodeDB.cpp b/src/NodeDB.cpp index 3dad39dd2..525136760 100644 --- a/src/NodeDB.cpp +++ b/src/NodeDB.cpp @@ -30,7 +30,12 @@ DeviceState versions used to be defined in the .proto file but really only this #define DEVICESTATE_CUR_VER 7 #define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER +#ifndef NO_ESP32 #define FS SPIFFS +#endif + +// FIXME - move this somewhere else +extern void getMacAddr(uint8_t *dmac); /** * @@ -97,7 +102,7 @@ void NodeDB::init() strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model)); // Init our blank owner info to reasonable defaults - esp_efuse_mac_get_default(ourMacAddr); + getMacAddr(ourMacAddr); sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]); memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr)); @@ -116,12 +121,6 @@ void NodeDB::init() info->user = owner; info->has_user = true; - if (!FS.begin(true)) // FIXME - do this in main? - { - DEBUG_MSG("ERROR SPIFFS Mount Failed\n"); - // FIXME - report failure to phone - } - // saveToDisk(); loadFromDisk(); resetRadioConfig(); // If bogus settings got saved, then fix them @@ -157,8 +156,15 @@ const char *preftmp = "/db.proto.tmp"; void NodeDB::loadFromDisk() { +#ifdef FS static DeviceState scratch; + if (!FS.begin(true)) // FIXME - do this in main? + { + DEBUG_MSG("ERROR SPIFFS Mount Failed\n"); + // FIXME - report failure to phone + } + File f = FS.open(preffile); if (f) { DEBUG_MSG("Loading saved preferences\n"); @@ -185,10 +191,14 @@ void NodeDB::loadFromDisk() } else { DEBUG_MSG("No saved preferences found\n"); } +#else + DEBUG_MSG("ERROR: Filesystem not implemented\n"); +#endif } void NodeDB::saveToDisk() { +#ifdef FS File f = FS.open(preftmp, "w"); if (f) { DEBUG_MSG("Writing preferences\n"); @@ -213,6 +223,9 @@ void NodeDB::saveToDisk() } else { DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app } +#else + DEBUG_MSG("ERROR filesystem not implemented\n"); +#endif } const NodeInfo *NodeDB::readNextInfo() diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 716e4e2c3..45c68805a 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -38,6 +38,7 @@ static void lsIdle() { DEBUG_MSG("lsIdle begin ls_secs=%u\n", radioConfig.preferences.ls_secs); +#ifndef NO_ESP32 uint32_t secsSlept = 0; esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED; bool reached_ls_secs = false; @@ -80,6 +81,7 @@ static void lsIdle() powerFSM.trigger(EVENT_WAKE_TIMER); } } +#endif } static void lsExit() diff --git a/src/bare/FS.h b/src/bare/FS.h new file mode 100644 index 000000000..819693660 --- /dev/null +++ b/src/bare/FS.h @@ -0,0 +1,3 @@ +#pragma once + +// FIXME - make a FS abstraction for NRF52 \ No newline at end of file diff --git a/src/bare/SPIFFS.h b/src/bare/SPIFFS.h new file mode 100644 index 000000000..819693660 --- /dev/null +++ b/src/bare/SPIFFS.h @@ -0,0 +1,3 @@ +#pragma once + +// FIXME - make a FS abstraction for NRF52 \ No newline at end of file diff --git a/src/configuration.h b/src/configuration.h index 9d6ca47d3..b35e4beec 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -73,6 +73,10 @@ along with this program. If not, see . // devices. Comment this out to not rotate screen 180 degrees. #define FLIP_SCREEN_VERTICALLY +// DEBUG LED + +#define LED_INVERTED 0 // define as 1 if LED is active low (on) + // ----------------------------------------------------------------------------- // GPS // ----------------------------------------------------------------------------- @@ -204,6 +208,20 @@ along with this program. If not, see . #define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) +// Turn off GPS code for now +#undef GPS_RX_PIN +#undef GPS_TX_PIN + +// FIXME, not yet ready for NRF52 +#define RTC_DATA_ATTR + +#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK +#define BUTTON_PIN PIN_BUTTON1 + +// This board uses 0 to be mean LED on +#undef LED_INVERTED +#define LED_INVERTED 1 + #endif // ----------------------------------------------------------------------------- diff --git a/src/debug.cpp b/src/debug.cpp index a866d56a9..9d8f19f09 100644 --- a/src/debug.cpp +++ b/src/debug.cpp @@ -2,9 +2,7 @@ #include -#include -#include - +#include "freertosinc.h" #include "configuration.h" namespace meshtastic @@ -12,9 +10,11 @@ namespace meshtastic void printThreadInfo(const char *extra) { +#ifndef NO_ESP32 uint32_t taskHandle = reinterpret_cast(xTaskGetCurrentTaskHandle()); DEBUG_MSG("printThreadInfo(%s) task: %" PRIx32 " core id: %u min free stack: %u\n", extra, taskHandle, xPortGetCoreID(), uxTaskGetStackHighWaterMark(nullptr)); +#endif } } // namespace meshtastic diff --git a/src/freertosinc.h b/src/freertosinc.h new file mode 100644 index 000000000..0d86ee2c9 --- /dev/null +++ b/src/freertosinc.h @@ -0,0 +1,16 @@ +#pragma once + +// The FreeRTOS includes are in a different directory on ESP32 and I can't figure out how to make that work with platformio gcc options +// so this is my quick hack to make things work + +#ifdef ARDUINO_ARCH_ESP32 +#include +#include +#include +#include +#else +#include +#include +#include +#include +#endif \ No newline at end of file diff --git a/src/lock.h b/src/lock.h index 57d466a89..a6afa2f6c 100644 --- a/src/lock.h +++ b/src/lock.h @@ -1,7 +1,6 @@ #pragma once -#include -#include +#include "freertosinc.h" namespace meshtastic { diff --git a/src/main.cpp b/src/main.cpp index a10de160b..758a6a824 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,14 +29,12 @@ #include "PowerFSM.h" #include "configuration.h" #include "error.h" -#include "esp32/pm.h" -#include "esp_pm.h" #include "power.h" -#include "rom/rtc.h" +// #include "rom/rtc.h" #include "screen.h" #include "sleep.h" #include -#include +// #include #ifndef NO_ESP32 #include "BluetoothUtil.h" @@ -195,10 +193,24 @@ void axp192Init() #endif } +void getMacAddr(uint8_t *dmac) { +#ifndef NO_ESP32 + assert(esp_efuse_mac_get_default(dmac) == ESP_OK); +#else + dmac[0] = 0xde; + dmac[1] = 0xad; + dmac[2] = 0xbe; + dmac[3] = 0xef; + dmac[4] = 0x01; + dmac[5] = 0x02; // FIXME, macaddr stuff needed for NRF52 +#endif +} + const char *getDeviceName() { uint8_t dmac[6]; - assert(esp_efuse_mac_get_default(dmac) == ESP_OK); + + getMacAddr(dmac); // Meshtastic_ab3c static char name[20]; @@ -239,15 +251,17 @@ void setup() #endif #ifdef LED_PIN pinMode(LED_PIN, OUTPUT); - digitalWrite(LED_PIN, 1); // turn on for now + digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now #endif // Hello DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_VERSION)); +#ifndef NO_ESP32 // Don't init display if we don't have one or we are waking headless due to a timer event if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) ssd1306_found = false; // forget we even have the hardware +#endif // Initialize the screen first so we can show the logo while we start up everything else. if (ssd1306_found) diff --git a/src/mesh-pb-constants.cpp b/src/mesh-pb-constants.cpp index 204fdd8fe..7e3800112 100644 --- a/src/mesh-pb-constants.cpp +++ b/src/mesh-pb-constants.cpp @@ -35,8 +35,9 @@ bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msg /// Read from an Arduino File bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count) { + bool status = false; +#ifndef NO_ESP32 File *file = (File *)stream->state; - bool status; if (buf == NULL) { while (count-- && file->read() != EOF) @@ -48,14 +49,18 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count) if (file->available() == 0) stream->bytes_left = 0; - +#endif return status; } /// Write to an arduino file bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count) { +#ifndef NO_ESP32 File *file = (File *)stream->state; // DEBUG_MSG("writing %d bytes to protobuf file\n", count); return file->write(buf, count) == count; +#else + return false; +#endif } diff --git a/src/rf95/MemoryPool.h b/src/rf95/MemoryPool.h index babcd2225..a0033d9d8 100644 --- a/src/rf95/MemoryPool.h +++ b/src/rf95/MemoryPool.h @@ -24,7 +24,7 @@ template class MemoryPool buf = new T[maxElements]; // prefill dead - for (int i = 0; i < maxElements; i++) + for (size_t i = 0; i < maxElements; i++) release(&buf[i]); } @@ -65,7 +65,7 @@ template class MemoryPool { assert(dead.enqueue(p, 0)); assert(p >= buf && - (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 } @@ -74,7 +74,7 @@ template class MemoryPool { assert(dead.enqueueFromISR(p, higherPriWoken)); assert(p >= buf && - (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 } }; diff --git a/src/rf95/RHutil/atomic.h b/src/rf95/RHutil/atomic.h new file mode 100644 index 000000000..019219827 --- /dev/null +++ b/src/rf95/RHutil/atomic.h @@ -0,0 +1,71 @@ +/* +* This is port of Dean Camera's ATOMIC_BLOCK macros for AVR to ARM Cortex M3 +* v1.0 +* Mark Pendrith, Nov 27, 2012. +* +* From Mark: +* >When I ported the macros I emailed Dean to ask what attribution would be +* >appropriate, and here is his response: +* > +* >>Mark, +* >>I think it's great that you've ported the macros; consider them +* >>public domain, to do with whatever you wish. I hope you find them >useful . +* >> +* >>Cheers! +* >>- Dean +*/ + +#ifdef __arm__ +#ifndef _CORTEX_M3_ATOMIC_H_ +#define _CORTEX_M3_ATOMIC_H_ + +static __inline__ uint32_t __get_primask(void) \ +{ uint32_t primask = 0; \ + __asm__ volatile ("MRS %[result], PRIMASK\n\t":[result]"=r"(primask)::); \ + return primask; } // returns 0 if interrupts enabled, 1 if disabled + +static __inline__ void __set_primask(uint32_t setval) \ +{ __asm__ volatile ("MSR PRIMASK, %[value]\n\t""dmb\n\t""dsb\n\t""isb\n\t"::[value]"r"(setval):); + __asm__ volatile ("" ::: "memory");} + +static __inline__ uint32_t __iSeiRetVal(void) \ +{ __asm__ volatile ("CPSIE i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); return 1; } + +static __inline__ uint32_t __iCliRetVal(void) \ +{ __asm__ volatile ("CPSID i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); return 1; } + +static __inline__ void __iSeiParam(const uint32_t *__s) \ +{ __asm__ volatile ("CPSIE i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); (void)__s; } + +static __inline__ void __iCliParam(const uint32_t *__s) \ +{ __asm__ volatile ("CPSID i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); (void)__s; } + +static __inline__ void __iRestore(const uint32_t *__s) \ +{ __set_primask(*__s); __asm__ volatile ("dmb\n\t""dsb\n\t""isb\n\t"); \ + __asm__ volatile ("" ::: "memory"); } + + +#define ATOMIC_BLOCK(type) \ +for ( type, __ToDo = __iCliRetVal(); __ToDo ; __ToDo = 0 ) + +#define ATOMIC_RESTORESTATE \ +uint32_t primask_save __attribute__((__cleanup__(__iRestore))) = __get_primask() + +#define ATOMIC_FORCEON \ +uint32_t primask_save __attribute__((__cleanup__(__iSeiParam))) = 0 + +#define NONATOMIC_BLOCK(type) \ +for ( type, __ToDo = __iSeiRetVal(); __ToDo ; __ToDo = 0 ) + +#define NONATOMIC_RESTORESTATE \ +uint32_t primask_save __attribute__((__cleanup__(__iRestore))) = __get_primask() + +#define NONATOMIC_FORCEOFF \ +uint32_t primask_save __attribute__((__cleanup__(__iCliParam))) = 0 + +#endif +#endif diff --git a/src/rf95/TypedQueue.h b/src/rf95/TypedQueue.h index 6dfdc8e4f..a16e624f9 100644 --- a/src/rf95/TypedQueue.h +++ b/src/rf95/TypedQueue.h @@ -3,8 +3,7 @@ #include #include -#include -#include +#include "freertosinc.h" /** * A wrapper for freertos queues. Note: each element object should be small diff --git a/src/screen.h b/src/screen.h index 6d088b1f9..45b6a694a 100644 --- a/src/screen.h +++ b/src/screen.h @@ -9,6 +9,7 @@ #include "TypedQueue.h" #include "lock.h" #include "power.h" +#include namespace meshtastic { diff --git a/src/sleep.cpp b/src/sleep.cpp index 0ae911b04..0459dbe7a 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -6,16 +6,20 @@ #include "Periodic.h" #include "configuration.h" #include "error.h" -#include "esp32/pm.h" -#include "esp_pm.h" + #include "main.h" -#include "rom/rtc.h" #include "target_specific.h" #include -#include #ifndef NO_ESP32 +#include "rom/rtc.h" +#include "esp32/pm.h" +#include "esp_pm.h" +#include + #include "BluetoothUtil.h" + +esp_sleep_source_t wakeCause; // the reason we booted this time #endif #ifdef TBEAM_V10 @@ -31,7 +35,6 @@ Observable notifySleep, notifyDeepSleep; // deep sleep support RTC_DATA_ATTR int bootCount = 0; -esp_sleep_source_t wakeCause; // the reason we booted this time #define xstr(s) str(s) #define str(s) #s @@ -48,14 +51,16 @@ esp_sleep_source_t wakeCause; // the reason we booted this time */ void setCPUFast(bool on) { +#ifndef NO_ESP32 setCpuFrequencyMhz(on ? 240 : 80); +#endif } void setLed(bool ledOn) { #ifdef LED_PIN // toggle the led so we can get some rough sense of how often loop is pausing - digitalWrite(LED_PIN, ledOn); + digitalWrite(LED_PIN, ledOn ^ LED_INVERTED); #endif #ifdef TBEAM_V10 @@ -79,6 +84,7 @@ void setGPSPower(bool on) // Perform power on init that we do on each wake from deep sleep void initDeepSleep() { +#ifndef NO_ESP32 bootCount++; wakeCause = esp_sleep_get_wakeup_cause(); /* @@ -106,6 +112,7 @@ void initDeepSleep() reason = "timeout"; DEBUG_MSG("booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason); +#endif } /// return true if sleep is allowed @@ -148,6 +155,7 @@ void doDeepSleep(uint64_t msecToWake) { DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000); +#ifndef NO_ESP32 // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); waitEnterSleep(); @@ -232,8 +240,10 @@ void doDeepSleep(uint64_t msecToWake) esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs esp_deep_sleep_start(); // TBD mA sleep current (battery) +#endif } +#ifndef NO_ESP32 /** * enter light sleep (preserves ram but stops everything about CPU). * @@ -274,6 +284,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r // digitalRead(PMU_IRQ)); return esp_sleep_get_wakeup_cause(); } +#endif #if 0 // not legal on the stock android ESP build diff --git a/src/sleep.h b/src/sleep.h index e63fa70a6..b3446882a 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -2,10 +2,15 @@ #include "Arduino.h" #include "Observer.h" -#include "esp_sleep.h" +#include "configuration.h" void doDeepSleep(uint64_t msecToWake); +#ifndef NO_ESP32 +#include "esp_sleep.h" esp_sleep_wakeup_cause_t doLightSleep(uint64_t msecToWake); + +extern esp_sleep_source_t wakeCause; +#endif void setGPSPower(bool on); // Perform power on init that we do on each wake from deep sleep @@ -15,7 +20,6 @@ void setCPUFast(bool on); void setLed(bool ledOn); extern int bootCount; -extern esp_sleep_source_t wakeCause; // is bluetooth sw currently running? extern bool bluetoothOn; From 2464784f00f8ae888b8a6ef3bbb2a060c9920a63 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 15 Apr 2020 14:51:17 -0700 Subject: [PATCH 013/197] todo updates --- .vscode/launch.json | 10 ++++------ platformio.ini | 2 +- src/rf95/kh-todo.txt | 3 ++- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 77b1fb363..914831d68 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,9 +12,8 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", - "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", "preLaunchTask": { "type": "PlatformIO", "task": "Pre-Debug" @@ -25,9 +24,8 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug (skip Pre-Debug)", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", - "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", "internalConsoleOptions": "openOnSessionStart" } ] diff --git a/platformio.ini b/platformio.ini index c15d4d0db..4030a68c3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = bare +default_envs = tbeam [common] ; default to a US frequency range, change it as needed for your region and hardware (CN, JP, EU433, EU865) diff --git a/src/rf95/kh-todo.txt b/src/rf95/kh-todo.txt index fbd22439b..96664a2c0 100644 --- a/src/rf95/kh-todo.txt +++ b/src/rf95/kh-todo.txt @@ -17,5 +17,6 @@ Now meshtastic is sharing layer two with disaster radio. * make simulation code work with new API * make disaster radio app work with new API -much later: +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) From 0a07c5692c837dcc09825accbae623ab3049f4ff Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 15 Apr 2020 17:45:51 -0700 Subject: [PATCH 014/197] add .well_known so that hopefully I can prove I own this domain to android --- docs/_config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/_config.yml b/docs/_config.yml index 5bf84d80b..0913c38b3 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -3,3 +3,6 @@ theme: jekyll-theme-cayman title: Meshtastic description: An opensource hiking, pilot, skiing, Signal-App-extending GPS mesh communicator google_analytics: G-DRZ5H5EXHV + +include: [".well_known"] + From bf5be49186dcc73d57a13a05f5711a2a6f43cb87 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 15 Apr 2020 18:01:43 -0700 Subject: [PATCH 015/197] It helps if I use the right filename when I'm remote debugging githubpages --- docs/_config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_config.yml b/docs/_config.yml index 0913c38b3..9c1ecd436 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -4,5 +4,5 @@ title: Meshtastic description: An opensource hiking, pilot, skiing, Signal-App-extending GPS mesh communicator google_analytics: G-DRZ5H5EXHV -include: [".well_known"] +include: [".well-known"] From cff255a3973c9e1a89b194bb5c9279f5eac8dd39 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 15 Apr 2020 18:20:45 -0700 Subject: [PATCH 016/197] add production android fingerprints --- docs/.well-known/assetlinks.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/.well-known/assetlinks.json b/docs/.well-known/assetlinks.json index ac10fce86..85d22f4a6 100644 --- a/docs/.well-known/assetlinks.json +++ b/docs/.well-known/assetlinks.json @@ -4,6 +4,6 @@ "namespace": "android_app", "package_name": "com.geeksville.mesh", "sha256_cert_fingerprints": - ["D0:05:E7:8B:D2:1B:FA:94:56:1D:6B:90:EB:53:07:1A:74:4F:D9:C2:6F:13:87:6A:D9:17:4F:C2:59:48:02:9D"] + ["D0:05:E7:8B:D2:1B:FA:94:56:1D:6B:90:EB:53:07:1A:74:4F:D9:C2:6F:13:87:6A:D9:17:4F:C2:59:48:02:9D", "42:17:52:DC:57:40:38:B5:6B:86:61:1C:2F:47:DB:2B:0F:A2:EA:59:E1:18:9C:AA:90:8D:37:D6:CD:40:0E:BB", "A9:3B:45:65:68:C1:75:DB:08:00:A0:9F:06:77:7F:89:2D:81:24:32:AD:B8:A3:DF:73:BC:3E:7F:06:C8:0C:6D"] } -}] \ No newline at end of file +}] From 86716c439760b83d95d2c395b24210992cd6f2f2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 16 Apr 2020 09:05:53 -0700 Subject: [PATCH 017/197] remove tbeam0.7 until someone who has the hardware can debug it --- bin/build-all.sh | 2 +- platformio.ini | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bin/build-all.sh b/bin/build-all.sh index 29a6af3a6..2c52dd19a 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -36,7 +36,7 @@ for COUNTRY in $COUNTRIES; do export PLATFORMIO_BUILD_FLAGS="$COMMONOPTS" - do_build "tbeam0.7" + #do_build "tbeam0.7" do_build "ttgo-lora32-v2" do_build "ttgo-lora32-v1" do_build "tbeam" diff --git a/platformio.ini b/platformio.ini index 4030a68c3..470bc69d5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -86,11 +86,12 @@ build_flags = ${esp32_base.build_flags} -D TBEAM_V10 ; The original TBEAM board without the AXP power chip and a few other changes -[env:tbeam0.7] -extends = esp32_base -board = ttgo-t-beam -build_flags = - ${esp32_base.build_flags} -D TBEAM_V07 +; Note: I've heard reports this didn't work. Disabled until someone with a 0.7 can test and debug. +;[env:tbeam0.7] +;extends = esp32_base +;board = ttgo-t-beam +;build_flags = +; ${esp32_base.build_flags} -D TBEAM_V07 [env:heltec] ;build_type = debug ; to make it possible to step through our jtag debugger From d4eb47e837fb0a18b11d36cf2fa08090ea2188cc Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 16 Apr 2020 17:30:33 -0700 Subject: [PATCH 018/197] doc updates --- docs/software/mesh-alg.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index d0935d34f..d33052ff7 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -14,6 +14,7 @@ TODO: * update duty cycle spreadsheet for our typical usecase * generalize naive flooding on top of radiohead or disaster.radio? (and fix radiohead to use my new driver) +a description of DSR: https://tools.ietf.org/html/rfc4728 good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf From 0d14b69a24356184868c7da48f46c498fabf0fd2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 16 Apr 2020 17:30:46 -0700 Subject: [PATCH 019/197] remove disasterradio experiment --- platformio.ini | 1 - 1 file changed, 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 470bc69d5..e9355233f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -57,7 +57,6 @@ debug_tool = jlink ; monitor adapter_khz 10000 lib_deps = - https://github.com/meshtastic/LoRaLayer2.git https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306 SPI ; 1260 ; OneButton - not used yet From 6eb74415abe3823dd4c2638453d988a972fd6997 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 16 Apr 2020 17:32:36 -0700 Subject: [PATCH 020/197] protobuf changes as part of getting ready for mesh again --- proto | 2 +- src/MeshService.cpp | 37 ++++++++++++++++----------------- src/NodeDB.cpp | 29 +++++++++----------------- src/mesh.pb.h | 50 +++++++++++++++++++++++++-------------------- src/screen.cpp | 3 ++- 5 files changed, 59 insertions(+), 62 deletions(-) diff --git a/proto b/proto index d13d741a9..fc4214e34 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit d13d741a985f75b953a9b7f8df6c8c61fcc4730d +Subproject commit fc4214e34dc90689b5efd4657bfdce63ca9add24 diff --git a/src/MeshService.cpp b/src/MeshService.cpp index c7794bde8..26b0d1f2e 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -72,8 +72,8 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) MeshPacket *p = allocForSending(); p->to = dest; p->payload.want_response = wantReplies; - p->payload.which_variant = SubPacket_user_tag; - User &u = p->payload.variant.user; + p->payload.has_user = true; + User &u = p->payload.user; u = owner; DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); @@ -87,7 +87,7 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) bool isCollision = mp->from == myNodeInfo.my_node_num; // we win if we have a lower macaddr - bool weWin = memcmp(&owner.macaddr, &mp->payload.variant.user.macaddr, sizeof(owner.macaddr)) < 0; + bool weWin = memcmp(&owner.macaddr, &mp->payload.user.macaddr, sizeof(owner.macaddr)) < 0; if (isCollision) { if (weWin) { @@ -114,7 +114,7 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) sendOurOwner(mp->from); - String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n"; + String lcd = String("Joined: ") + mp->payload.user.long_name + "\n"; screen.print(lcd.c_str()); } @@ -123,12 +123,12 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) void MeshService::handleIncomingPosition(MeshPacket *mp) { - if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag) { - DEBUG_MSG("handled incoming position time=%u\n", mp->payload.variant.position.time); + if (mp->has_payload && mp->payload.has_position) { + DEBUG_MSG("handled incoming position time=%u\n", mp->payload.position.time); - if (mp->payload.variant.position.time) { + if (mp->payload.position.time) { struct timeval tv; - uint32_t secs = mp->payload.variant.position.time; + uint32_t secs = mp->payload.position.time; tv.tv_sec = secs; tv.tv_usec = 0; @@ -153,7 +153,7 @@ void MeshService::handleFromRadio(MeshPacket *mp) DEBUG_MSG("Ignoring incoming time, because we have a GPS\n"); } - if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag) { + if (mp->has_payload && mp->payload.has_user) { mp = handleFromRadioUser(mp); } @@ -258,12 +258,12 @@ void MeshService::sendToMesh(MeshPacket *p) // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other // nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless // devices can get time. - if (p->has_payload && p->payload.which_variant == SubPacket_position_tag) { + if (p->has_payload && p->payload.has_position) { if (!gps.isConnected) { - DEBUG_MSG("Stripping time %u from position send\n", p->payload.variant.position.time); - p->payload.variant.position.time = 0; + DEBUG_MSG("Stripping time %u from position send\n", p->payload.position.time); + p->payload.position.time = 0; } else - DEBUG_MSG("Providing time to mesh %u\n", p->payload.variant.position.time); + DEBUG_MSG("Providing time to mesh %u\n", p->payload.position.time); } // If the phone sent a packet just to us, don't send it out into the network @@ -312,11 +312,10 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); p->to = dest; - p->payload.which_variant = SubPacket_position_tag; - p->payload.variant.position = node->position; + p->payload.has_position = true; + p->payload.position = node->position; p->payload.want_response = wantReplies; - p->payload.variant.position.time = - gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. + p->payload.position.time = gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. sendToMesh(p); } @@ -326,9 +325,9 @@ int MeshService::onGPSChanged(void *unused) // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); - p->payload.which_variant = SubPacket_position_tag; + p->payload.has_position = true; - Position &pos = p->payload.variant.position; + Position &pos = p->payload.position; // !zero or !zero lat/long means valid if (gps.latitude != 0 || gps.longitude != 0) { if (gps.altitude != 0) diff --git a/src/NodeDB.cpp b/src/NodeDB.cpp index 525136760..3791a5ae5 100644 --- a/src/NodeDB.cpp +++ b/src/NodeDB.cpp @@ -32,7 +32,7 @@ DeviceState versions used to be defined in the .proto file but really only this #ifndef NO_ESP32 #define FS SPIFFS -#endif +#endif // FIXME - move this somewhere else extern void getMacAddr(uint8_t *dmac); @@ -269,7 +269,7 @@ void NodeDB::updateFrom(const MeshPacket &mp) { if (mp.has_payload) { const SubPacket &p = mp.payload; - DEBUG_MSG("Update DB node 0x%x for variant %d, rx_time=%u\n", mp.from, p.which_variant, mp.rx_time); + DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time); int oldNumNodes = *numNodes; NodeInfo *info = getOrCreateNode(mp.from); @@ -282,22 +282,19 @@ void NodeDB::updateFrom(const MeshPacket &mp) info->position.time = mp.rx_time; } - switch (p.which_variant) { - case SubPacket_position_tag: { + if (p.has_position) { // we carefully preserve the old time, because we always trust our local timestamps more uint32_t oldtime = info->position.time; - info->position = p.variant.position; + info->position = p.position; info->position.time = oldtime; info->has_position = true; updateGUIforNode = info; - break; } - case SubPacket_data_tag: { + if (p.has_data) { // Keep a copy of the most recent text message. - if (p.variant.data.typ == Data_Type_CLEAR_TEXT) { - DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.variant.data.payload.size, - p.variant.data.payload.bytes); + if (p.data.typ == Data_Type_CLEAR_TEXT) { + DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.data.payload.size, p.data.payload.bytes); if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) { // We only store/display messages destined for us. devicestate.rx_text_message = mp; @@ -306,16 +303,15 @@ void NodeDB::updateFrom(const MeshPacket &mp) powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); } } - break; } - case SubPacket_user_tag: { + if (p.has_user) { DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name); - bool changed = memcmp(&info->user, &p.variant.user, + bool changed = memcmp(&info->user, &p.user, sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay - info->user = p.variant.user; + info->user = p.user; DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name); info->has_user = true; @@ -327,11 +323,6 @@ void NodeDB::updateFrom(const MeshPacket &mp) // We just changed something important about the user, store our DB // saveToDisk(); } - break; - } - - default: - break; // Ignore other packet types } } } diff --git a/src/mesh.pb.h b/src/mesh.pb.h index 2c524662e..48b6ad9fe 100644 --- a/src/mesh.pb.h +++ b/src/mesh.pb.h @@ -106,12 +106,12 @@ typedef struct _RadioConfig { } RadioConfig; typedef struct _SubPacket { - pb_size_t which_variant; - union { - Position position; - Data data; - User user; - } variant; + bool has_position; + Position position; + bool has_data; + Data data; + bool has_user; + User user; bool want_response; } SubPacket; @@ -121,6 +121,8 @@ typedef struct _MeshPacket { bool has_payload; SubPacket payload; uint32_t rx_time; + int32_t rx_snr; + uint32_t id; } MeshPacket; typedef struct _DeviceState { @@ -173,8 +175,8 @@ typedef struct _ToRadio { #define Position_init_default {0, 0, 0, 0, 0} #define Data_init_default {_Data_Type_MIN, {0, {0}}} #define User_init_default {"", "", "", {0}} -#define SubPacket_init_default {0, {Position_init_default}, 0} -#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0} +#define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0} +#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} #define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -186,8 +188,8 @@ typedef struct _ToRadio { #define Position_init_zero {0, 0, 0, 0, 0} #define Data_init_zero {_Data_Type_MIN, {0, {0}}} #define User_init_zero {"", "", "", {0}} -#define SubPacket_init_zero {0, {Position_init_zero}, 0} -#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0} +#define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0} +#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} #define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -250,6 +252,8 @@ typedef struct _ToRadio { #define MeshPacket_to_tag 2 #define MeshPacket_payload_tag 3 #define MeshPacket_rx_time_tag 4 +#define MeshPacket_rx_snr_tag 5 +#define MeshPacket_id_tag 6 #define DeviceState_radio_tag 1 #define DeviceState_my_node_tag 2 #define DeviceState_owner_tag 3 @@ -286,21 +290,23 @@ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) #define User_DEFAULT NULL #define SubPacket_FIELDLIST(X, a) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,position,variant.position), 1) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,data,variant.data), 3) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,user,variant.user), 4) \ +X(a, STATIC, OPTIONAL, MESSAGE, position, 1) \ +X(a, STATIC, OPTIONAL, MESSAGE, data, 3) \ +X(a, STATIC, OPTIONAL, MESSAGE, user, 4) \ X(a, STATIC, SINGULAR, BOOL, want_response, 5) #define SubPacket_CALLBACK NULL #define SubPacket_DEFAULT NULL -#define SubPacket_variant_position_MSGTYPE Position -#define SubPacket_variant_data_MSGTYPE Data -#define SubPacket_variant_user_MSGTYPE User +#define SubPacket_position_MSGTYPE Position +#define SubPacket_data_MSGTYPE Data +#define SubPacket_user_MSGTYPE User #define MeshPacket_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, from, 1) \ X(a, STATIC, SINGULAR, INT32, to, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, payload, 3) \ -X(a, STATIC, SINGULAR, UINT32, rx_time, 4) +X(a, STATIC, SINGULAR, UINT32, rx_time, 4) \ +X(a, STATIC, SINGULAR, SINT32, rx_snr, 5) \ +X(a, STATIC, SINGULAR, UINT32, id, 6) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL #define MeshPacket_payload_MSGTYPE SubPacket @@ -425,16 +431,16 @@ extern const pb_msgdesc_t ToRadio_msg; #define Position_size 46 #define Data_size 256 #define User_size 72 -#define SubPacket_size 261 -#define MeshPacket_size 292 +#define SubPacket_size 383 +#define MeshPacket_size 426 #define ChannelSettings_size 44 #define RadioConfig_size 120 #define RadioConfig_UserPreferences_size 72 #define NodeInfo_size 155 #define MyNodeInfo_size 85 -#define DeviceState_size 15080 -#define FromRadio_size 301 -#define ToRadio_size 295 +#define DeviceState_size 19502 +#define FromRadio_size 435 +#define ToRadio_size 429 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/screen.cpp b/src/screen.cpp index b552e1b17..583b67b8b 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -96,7 +96,8 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state // the max length of this buffer is much longer than we can possibly print static char tempBuf[96]; - snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.variant.data.payload.bytes); + assert(mp.payload.has_data); + snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.data.payload.bytes); display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf); } From f108c576a79e0ee2aa843db95cb98dfbbba1c0b6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 09:48:54 -0700 Subject: [PATCH 021/197] massive WIP updates to create a clean Router abstraction for mesh --- docs/mesh-proto.md | 20 ------- docs/software/mesh-alg.md | 102 ++++++++++++++++++++++-------------- proto | 2 +- src/MeshRadio.cpp | 5 +- src/MeshRadio.h | 2 +- src/MeshService.cpp | 44 ++++++---------- src/MeshService.h | 25 ++++----- src/MeshTypes.h | 7 ++- src/main.cpp | 12 +++-- src/mesh.pb.c | 3 ++ src/mesh.pb.h | 15 ++++++ src/rf95/CustomRF95.cpp | 15 +++--- src/rf95/CustomRF95.h | 2 +- src/rf95/MemoryPool.h | 7 +-- src/rf95/RadioInterface.cpp | 11 +++- src/rf95/RadioInterface.h | 35 ++++++------- src/rf95/Router.cpp | 72 +++++++++++++++++++++++++ src/rf95/Router.h | 70 +++++++++++++++++++++++++ 18 files changed, 303 insertions(+), 146 deletions(-) delete mode 100644 docs/mesh-proto.md create mode 100644 src/rf95/Router.cpp create mode 100644 src/rf95/Router.h diff --git a/docs/mesh-proto.md b/docs/mesh-proto.md deleted file mode 100644 index aa5a84df9..000000000 --- a/docs/mesh-proto.md +++ /dev/null @@ -1,20 +0,0 @@ -TODO: -* reread the radiohead mesh implementation -* read about general mesh flooding solutions -* reread the disaster radio protocol docs - -good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept - -interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf -It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 -https://en.wikipedia.org/wiki/Dynamic_Source_Routing - -broadcast solution: -Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) -Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set - -compare to db sync algorithm? - -what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. - -If Y were to die, at least the neighbor nodes of Y would have their last known position of Y. diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index d33052ff7..d90d85c9c 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -1,73 +1,95 @@ # Mesh broadcast algorithm -FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If +FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If all else fails could always use the stock Radiohead solution - though super inefficient. great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ TODO: -* DONE reread the radiohead mesh implementation - hop to hop acknoledgement seems VERY expensive but otherwise it seems like DSR -* DONE read about mesh routing solutions (DSR and AODV) -* DONE read about general mesh flooding solutions (naive, MPR, geo assisted) -* DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) -* possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead -* update duty cycle spreadsheet for our typical usecase -* generalize naive flooding on top of radiohead or disaster.radio? (and fix radiohead to use my new driver) -a description of DSR: https://tools.ietf.org/html/rfc4728 +- DONE reread the radiohead mesh implementation - hop to hop acknoledgement seems VERY expensive but otherwise it seems like DSR +- DONE read about mesh routing solutions (DSR and AODV) +- DONE read about general mesh flooding solutions (naive, MPR, geo assisted) +- DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) +- possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead +- update duty cycle spreadsheet for our typical usecase +- generalize naive flooding on top of radiohead or disaster.radio? (and fix radiohead to use my new driver) + +a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf -It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 +It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 https://en.wikipedia.org/wiki/Dynamic_Source_Routing broadcast solution: -Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) -Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set +Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) +Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set compare to db sync algorithm? -what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. +what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. If Y were to die, at least the neighbor nodes of Y would have their last known position of Y. ## approach 1 -* send all broadcasts with a TTL -* periodically(?) do a survey to find the max TTL that is needed to fully cover the current network. -* to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request) -* survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets) -* count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts -* repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up. -* it is important for any node to do listen before talk to prevent stomping on other rebroadcasters... -* For these little networks I bet a max TTL would never be higher than 3? +- send all broadcasts with a TTL +- periodically(?) do a survey to find the max TTL that is needed to fully cover the current network. +- to do a study first send a broadcast (maybe our current initial user announcement?) with TTL set to one (so therefore no one will rebroadcast our request) +- survey replies are sent unicast back to us (and intervening nodes will need to keep the route table that they have built up based on past packets) +- count the number of replies to this TTL 1 attempt. That is the number of nodes we can reach without any rebroadcasts +- repeat the study with a TTL of 2 and then 3. stop once the # of replies stops going up. +- it is important for any node to do listen before talk to prevent stomping on other rebroadcasters... +- For these little networks I bet a max TTL would never be higher than 3? ## approach 2 -* send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis) -* we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?" -* if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to) -* given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful -* once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast +- send a TTL1 broadcast, the replies let us build a list of the nodes (stored as a bitvector?) that we can see (and their rssis) +- we then broadcast out that bitvector (also TTL1) asking "can any of ya'll (even indirectly) see anyone else?" +- if a node can see someone I missed (and they are the best person to see that node), they reply (unidirectionally) with the missing nodes and their rssis (other nodes might sniff (and update their db) based on this reply but they don't have to) +- given that the max number of nodes in this mesh will be like 20 (for normal cases), I bet globally updating this db of "nodenums and who has the best rssi for packets from that node" would be useful +- once the global DB is shared, when a node wants to broadcast, it just sends out its broadcast . the first level receivers then make a decision "am I the best to rebroadcast to someone who likely missed this packet?" if so, rebroadcast ## approach 3 -* when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?) -* all nodes constantly update their local db based on replies they witnessed. -* after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf. -* could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set). +- when a node X wants to know other nodes positions, it broadcasts its position with want_replies=true. Then each of the nodes that received that request broadcast their replies (possibly by using special timeslots?) +- all nodes constantly update their local db based on replies they witnessed. +- after 10s (or whatever) if node Y notices that it didn't hear a reply from node Z (that Y has heard from recently ) to that initial request, that means Z never heard the request from X. Node Y will reply to X on Z's behalf. +- could this work for more than one hop? Is more than one hop needed? Could it work for sending messages (i.e. for a msg sent to Z with want-reply set). ## approach 4 look into the literature for this idea specifically. -* don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together -to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the -other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually). -* handle group messages the same way, there would be a table of messages and time of creation. -* when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet). -* Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date. -* essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to -build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db. -* construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here? -* possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database. +- don't view it as a mesh protocol as much as a "distributed db unification problem". When nodes talk to nearby nodes they work together + to update their nodedbs. Each nodedb would have a last change date and any new changes that only one node has would get passed to the + other node. This would nicely allow distant nodes to propogate their position to all other nodes (eventually). +- handle group messages the same way, there would be a table of messages and time of creation. +- when a node has a new position or message to send out, it does a broadcast. All the adjacent nodes update their db instantly (this handles 90% of messages I'll bet). +- Occasionally a node might broadcast saying "anyone have anything newer than time X?" If someone does, they send the diffs since that date. +- essentially everything in this variant becomes broadcasts of "request db updates for >time X - for _all_ or for a particular nodenum" and nodes sending (either due to request or because they changed state) "here's a set of db updates". Every node is constantly trying to + build the most recent version of reality, and if some nodes are too far, then nodes closer in will eventually forward their changes to the distributed db. +- construct non ambigious rules for who broadcasts to request db updates. ideally the algorithm should nicely realize node X can see most other nodes, so they should just listen to all those nodes and minimize the # of broadcasts. the distributed picture of nodes rssi could be useful here? +- possibly view the BLE protocol to the radio the same way - just a process of reconverging the node/msgdb database. + +# Old notes + +FIXME, merge into the above: + + +good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept + +interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf +It seems like DSR might be the algorithm used by RadioheadMesh. DSR is described in https://tools.ietf.org/html/rfc4728 +https://en.wikipedia.org/wiki/Dynamic_Source_Routing + +broadcast solution: +Use naive flooding at first (FIXME - do some math for a 20 node, 3 hop mesh. A single flood will require a max of 20 messages sent) +Then move to MPR later (http://www.olsr.org/docs/report_html/node28.html). Use altitude and location as heursitics in selecting the MPR set + +compare to db sync algorithm? + +what about never flooding gps broadcasts. instead only have them go one hop in the common case, but if any node X is looking at the position of Y on their gui, then send a unicast to Y asking for position update. Y replies. + +If Y were to die, at least the neighbor nodes of Y would have their last known position of Y. diff --git a/proto b/proto index fc4214e34..793d3e65c 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit fc4214e34dc90689b5efd4657bfdce63ca9add24 +Subproject commit 793d3e65ca66c3a0914e74a285a729429952a042 diff --git a/src/MeshRadio.cpp b/src/MeshRadio.cpp index 7bf2ce45d..a5d1d9d62 100644 --- a/src/MeshRadio.cpp +++ b/src/MeshRadio.cpp @@ -24,8 +24,7 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts /// Sometimes while debugging it is useful to set this false, to disable rf95 accesses bool useHardware = true; -MeshRadio::MeshRadio(MemoryPool &_pool, PointerQueue &_rxDest) - : radioIf(_pool, _rxDest), sendPacketObserver(this, &MeshRadio::send) // , manager(radioIf) +MeshRadio::MeshRadio() : sendPacketObserver(this, &MeshRadio::send) // , manager(radioIf) { myNodeInfo.num_channels = NUM_CHANNELS; @@ -150,7 +149,7 @@ void MeshRadio::loop() DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode\n"); radioIf.setModeRx(); if (radioIf.sendingPacket) { // There was probably a packet we were trying to send, free it - radioIf.pool.release(radioIf.sendingPacket); + packetPool.release(radioIf.sendingPacket); radioIf.sendingPacket = NULL; } recordCriticalError(ErrTxWatchdog); diff --git a/src/MeshRadio.h b/src/MeshRadio.h index 7e39e0c32..84c4bea33 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -76,7 +76,7 @@ class MeshRadio /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool */ - MeshRadio(MemoryPool &pool, PointerQueue &rxDest); + MeshRadio(); bool init(); diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 26b0d1f2e..ce7fa697b 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -44,15 +44,9 @@ FIXME in the initial proof of concept we just skip the entire want/deny flow and MeshService service; -// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX -#define MAX_PACKETS \ - (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \ - 2) // max number of packets which can be in flight (either queued from reception or queued for sending) +#include "Router.h" -#define MAX_RX_FROMRADIO \ - 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big - -MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE), packetPool(MAX_PACKETS), fromRadioQueue(MAX_RX_FROMRADIO) +MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro } @@ -62,6 +56,7 @@ void MeshService::init() nodeDB.init(); gpsObserver.observe(&gps); + packetReceivedObserver.observe(&router.notifyPacketReceived); // No need to call this here, our periodic task will fire quite soon // sendOwnerPeriod(); @@ -81,7 +76,7 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) } /// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all -MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) +const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) { bool wasBroadcast = mp->to == NODENUM_BROADCAST; bool isCollision = mp->from == myNodeInfo.my_node_num; @@ -93,7 +88,6 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) if (weWin) { DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n"); - releaseToPool(mp); // discard it mp = NULL; sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum @@ -121,7 +115,7 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp) return mp; } -void MeshService::handleIncomingPosition(MeshPacket *mp) +void MeshService::handleIncomingPosition(const MeshPacket *mp) { if (mp->has_payload && mp->payload.has_position) { DEBUG_MSG("handled incoming position time=%u\n", mp->payload.position.time); @@ -140,12 +134,10 @@ void MeshService::handleIncomingPosition(MeshPacket *mp) } } -void MeshService::handleFromRadio(MeshPacket *mp) +int MeshService::handleFromRadio(const MeshPacket *mp) { powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping - mp->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone - // If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work) if (!gps.isConnected) handleIncomingPosition(mp); @@ -170,24 +162,17 @@ void MeshService::handleFromRadio(MeshPacket *mp) if (d) releaseToPool(d); } - assert(toPhoneQueue.enqueue(mp, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages + + MeshPacket *copied = packetPool.allocCopy(*mp); + assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages if (mp->payload.want_response) sendNetworkPing(mp->from); } else { DEBUG_MSG("Not delivering vetoed User message\n"); } -} -void MeshService::handleFromRadio() -{ - MeshPacket *mp; - uint32_t oldFromNum = fromNum; - while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { - handleFromRadio(mp); - } - if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets - fromNumChanged.notifyObservers(fromNum); + return 0; } uint32_t sendOwnerCb() @@ -202,7 +187,10 @@ Periodic sendOwnerPeriod(sendOwnerCb); /// Do idle processing (mostly processing messages which have been queued from the radio) void MeshService::loop() { - handleFromRadio(); + if (oldFromNum != fromNum) { // We don't want to generate extra notifies for multiple new packets + fromNumChanged.notifyObservers(fromNum); + oldFromNum = true; + } // occasionally send our owner info sendOwnerPeriod.loop(); @@ -236,7 +224,9 @@ void MeshService::handleToRadio(std::string s) bool loopback = false; // if true send any packet the phone sends back itself (for testing) if (loopback) { - MeshPacket *mp = packetPool.allocCopy(r.variant.packet); + const MeshPacket *mp = &r.variant.packet; + // no need to copy anymore because handle from radio assumes it should _not_ delete + // packetPool.allocCopy(r.variant.packet); handleFromRadio(mp); // handleFromRadio will tell the phone a new packet arrived } diff --git a/src/MeshService.h b/src/MeshService.h index 235801211..d8349a55b 100644 --- a/src/MeshService.h +++ b/src/MeshService.h @@ -6,6 +6,7 @@ #include "MemoryPool.h" #include "MeshRadio.h" +#include "MeshTypes.h" #include "Observer.h" #include "PointerQueue.h" #include "mesh.pb.h" @@ -17,6 +18,8 @@ class MeshService { CallbackObserver gpsObserver = CallbackObserver(this, &MeshService::onGPSChanged); + CallbackObserver packetReceivedObserver = + CallbackObserver(this, &MeshService::handleFromRadio); /// received packets waiting for the phone to process them /// FIXME, change to a DropOldestQueue and keep a count of the number of dropped packets to ensure @@ -27,13 +30,10 @@ class MeshService /// The current nonce for the newest packet which has been queued for the phone uint32_t fromNum = 0; + /// Updated in loop() to detect when fromNum changes + uint32_t oldFromNum = 0; + public: - MemoryPool packetPool; - - /// Packets which have just arrived from the radio, ready to be processed by this service and possibly - /// forwarded to the phone. - PointerQueue fromRadioQueue; - /// Called when some new packets have arrived from one of the radios Observable fromNumChanged; @@ -90,18 +90,15 @@ class MeshService /// returns 0 to allow futher processing int onGPSChanged(void *arg); - /// handle all the packets that just arrived from the mesh radio - void handleFromRadio(); - - /// Handle a packet that just arrived from the radio. We will either eventually enqueue the message to the phone or return it - /// to the free pool - void handleFromRadio(MeshPacket *p); + /// Handle a packet that just arrived from the radio. This method does _not_ free the provided packet. If it needs + /// to keep the packet around it makes a copy + int handleFromRadio(const MeshPacket *p); /// handle a user packet that just arrived on the radio, return NULL if we should not process this packet at all - MeshPacket *handleFromRadioUser(MeshPacket *mp); + const MeshPacket *handleFromRadioUser(const MeshPacket *mp); /// look at inbound packets and if they contain a position with time, possibly set our clock - void handleIncomingPosition(MeshPacket *mp); + void handleIncomingPosition(const MeshPacket *mp); }; extern MeshService service; diff --git a/src/MeshTypes.h b/src/MeshTypes.h index ea31f6c19..13ead0463 100644 --- a/src/MeshTypes.h +++ b/src/MeshTypes.h @@ -2,6 +2,8 @@ // low level types +#include "MemoryPool.h" +#include "mesh.pb.h" #include typedef uint8_t NodeNum; @@ -10,4 +12,7 @@ typedef uint8_t NodeNum; #define ERRNO_OK 0 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER -typedef int ErrorCode; \ No newline at end of file +typedef int ErrorCode; + +/// Alloc and free packets to our global, ISR safe pool +extern MemoryPool packetPool; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 758a6a824..905c1c00a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -193,7 +193,8 @@ void axp192Init() #endif } -void getMacAddr(uint8_t *dmac) { +void getMacAddr(uint8_t *dmac) +{ #ifndef NO_ESP32 assert(esp_efuse_mac_get_default(dmac) == ESP_OK); #else @@ -203,7 +204,7 @@ void getMacAddr(uint8_t *dmac) { dmac[3] = 0xef; dmac[4] = 0x01; dmac[5] = 0x02; // FIXME, macaddr stuff needed for NRF52 -#endif +#endif } const char *getDeviceName() @@ -220,6 +221,8 @@ const char *getDeviceName() static MeshRadio *radio = NULL; +#include "Router.h" + void setup() { // Debug @@ -261,7 +264,7 @@ void setup() // Don't init display if we don't have one or we are waking headless due to a timer event if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) ssd1306_found = false; // forget we even have the hardware -#endif +#endif // Initialize the screen first so we can show the logo while we start up everything else. if (ssd1306_found) @@ -278,7 +281,8 @@ void setup() #ifndef NO_ESP32 // MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) - radio = new MeshRadio(service.packetPool, service.fromRadioQueue); + radio = new MeshRadio(); + router.addInterface(&radio->radioIf); #endif if (radio && !radio->init()) diff --git a/src/mesh.pb.c b/src/mesh.pb.c index e8c0faa28..b4e21ebde 100644 --- a/src/mesh.pb.c +++ b/src/mesh.pb.c @@ -15,6 +15,9 @@ PB_BIND(Data, Data, 2) PB_BIND(User, User, AUTO) +PB_BIND(RouteDiscovery, RouteDiscovery, AUTO) + + PB_BIND(SubPacket, SubPacket, 2) diff --git a/src/mesh.pb.h b/src/mesh.pb.h index 48b6ad9fe..dda10e710 100644 --- a/src/mesh.pb.h +++ b/src/mesh.pb.h @@ -32,6 +32,10 @@ typedef enum _ChannelSettings_ModemConfig { } ChannelSettings_ModemConfig; /* Struct definitions */ +typedef struct _RouteDiscovery { + pb_callback_t route; +} RouteDiscovery; + typedef struct _ChannelSettings { int32_t tx_power; ChannelSettings_ModemConfig modem_config; @@ -175,6 +179,7 @@ typedef struct _ToRadio { #define Position_init_default {0, 0, 0, 0, 0} #define Data_init_default {_Data_Type_MIN, {0, {0}}} #define User_init_default {"", "", "", {0}} +#define RouteDiscovery_init_default {{{NULL}, NULL}} #define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0} #define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} @@ -188,6 +193,7 @@ typedef struct _ToRadio { #define Position_init_zero {0, 0, 0, 0, 0} #define Data_init_zero {_Data_Type_MIN, {0, {0}}} #define User_init_zero {"", "", "", {0}} +#define RouteDiscovery_init_zero {{{NULL}, NULL}} #define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0} #define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} @@ -200,6 +206,7 @@ typedef struct _ToRadio { #define ToRadio_init_zero {0, {MeshPacket_init_zero}} /* Field tags (for use in manual encoding/decoding) */ +#define RouteDiscovery_route_tag 2 #define ChannelSettings_tx_power_tag 1 #define ChannelSettings_modem_config_tag 3 #define ChannelSettings_psk_tag 4 @@ -289,6 +296,11 @@ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) #define User_CALLBACK NULL #define User_DEFAULT NULL +#define RouteDiscovery_FIELDLIST(X, a) \ +X(a, CALLBACK, REPEATED, INT32, route, 2) +#define RouteDiscovery_CALLBACK pb_default_field_callback +#define RouteDiscovery_DEFAULT NULL + #define SubPacket_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, position, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, data, 3) \ @@ -401,6 +413,7 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 1) extern const pb_msgdesc_t Position_msg; extern const pb_msgdesc_t Data_msg; extern const pb_msgdesc_t User_msg; +extern const pb_msgdesc_t RouteDiscovery_msg; extern const pb_msgdesc_t SubPacket_msg; extern const pb_msgdesc_t MeshPacket_msg; extern const pb_msgdesc_t ChannelSettings_msg; @@ -416,6 +429,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define Position_fields &Position_msg #define Data_fields &Data_msg #define User_fields &User_msg +#define RouteDiscovery_fields &RouteDiscovery_msg #define SubPacket_fields &SubPacket_msg #define MeshPacket_fields &MeshPacket_msg #define ChannelSettings_fields &ChannelSettings_msg @@ -431,6 +445,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define Position_size 46 #define Data_size 256 #define User_size 72 +/* RouteDiscovery_size depends on runtime parameters */ #define SubPacket_size 383 #define MeshPacket_size 426 #define ChannelSettings_size 44 diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index 6e158cf89..a2a7b17aa 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -11,10 +11,7 @@ #define MAX_RHPACKETLEN 251 static uint8_t radiobuf[MAX_RHPACKETLEN]; -CustomRF95::CustomRF95(MemoryPool &_pool, PointerQueue &_rxDest) - : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), RadioInterface(_pool, _rxDest), txQueue(MAX_TX_QUEUE) -{ -} +CustomRF95::CustomRF95() : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), txQueue(MAX_TX_QUEUE) {} bool CustomRF95::canSleep() { @@ -58,7 +55,7 @@ ErrorCode CustomRF95::send(MeshPacket *p) ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks - pool.release(p); + packetPool.release(p); return res; } @@ -76,7 +73,7 @@ void CustomRF95::handleInterrupt() if (sendingPacket) // Were we sending? { // We are done sending that packet, release it - pool.releaseFromISR(sendingPacket, &higherPriWoken); + packetPool.releaseFromISR(sendingPacket, &higherPriWoken); sendingPacket = NULL; // DEBUG_MSG("Done with send\n"); } @@ -94,7 +91,7 @@ void CustomRF95::handleInterrupt() // DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n", // srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr); - MeshPacket *mp = pool.allocZeroed(); + MeshPacket *mp = packetPool.allocZeroed(); SubPacket *p = &mp->payload; @@ -113,12 +110,12 @@ void CustomRF95::handleInterrupt() } if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { - pool.releaseFromISR(mp, &higherPriWoken); + packetPool.releaseFromISR(mp, &higherPriWoken); } else { // parsing was successful, queue for our recipient mp->has_payload = true; - assert(rxDest.enqueueFromISR(mp, &higherPriWoken)); // NOWAIT - fixme, if queue is full, delete older messages + deliverToReceiverISR(mp, &higherPriWoken); } clearRxBuf(); // This message accepted and cleared diff --git a/src/rf95/CustomRF95.h b/src/rf95/CustomRF95.h index 9f4e6b65c..65e49f057 100644 --- a/src/rf95/CustomRF95.h +++ b/src/rf95/CustomRF95.h @@ -19,7 +19,7 @@ class CustomRF95 : public RH_RF95, public RadioInterface /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool */ - CustomRF95(MemoryPool &pool, PointerQueue &rxDest); + CustomRF95(); /** * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) diff --git a/src/rf95/MemoryPool.h b/src/rf95/MemoryPool.h index a0033d9d8..89c514c90 100644 --- a/src/rf95/MemoryPool.h +++ b/src/rf95/MemoryPool.h @@ -31,6 +31,7 @@ template class MemoryPool ~MemoryPool() { delete[] buf; } /// Return a queable object which has been prefilled with zeros. Panic if no buffer is available + /// Note: this method is safe to call from regular OR ISR code T *allocZeroed() { T *p = allocZeroed(0); @@ -40,7 +41,7 @@ template class MemoryPool } /// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably - /// don't want this version) + /// don't want this version). T *allocZeroed(TickType_t maxWait) { T *p = dead.dequeuePtr(maxWait); @@ -65,7 +66,7 @@ template class MemoryPool { assert(dead.enqueue(p, 0)); assert(p >= buf && - (size_t) (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 } @@ -74,7 +75,7 @@ template class MemoryPool { assert(dead.enqueueFromISR(p, higherPriWoken)); assert(p >= buf && - (size_t) (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 } }; diff --git a/src/rf95/RadioInterface.cpp b/src/rf95/RadioInterface.cpp index 1406646b8..bd8bb1310 100644 --- a/src/rf95/RadioInterface.cpp +++ b/src/rf95/RadioInterface.cpp @@ -2,14 +2,21 @@ #include "NodeDB.h" #include "assert.h" #include "configuration.h" +#include #include #include -RadioInterface::RadioInterface(MemoryPool &_pool, PointerQueue &_rxDest) : pool(_pool), rxDest(_rxDest) {} +RadioInterface::RadioInterface() {} ErrorCode SimRadio::send(MeshPacket *p) { DEBUG_MSG("SimRadio.send\n"); - pool.release(p); + packetPool.release(p); return ERRNO_OK; +} + +void RadioInterface::deliverToReceiverISR(MeshPacket *p, BaseType_t *higherPriWoken) +{ + assert(rxDest); + assert(rxDest->enqueueFromISR(p, higherPriWoken)); // NOWAIT - fixme, if queue is full, delete older messages } \ No newline at end of file diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index 452b8841c..48f205851 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -16,43 +16,38 @@ class RadioInterface { friend class MeshRadio; // for debugging we let that class touch pool + PointerQueue *rxDest = NULL; protected: - MemoryPool &pool; - PointerQueue &rxDest; - MeshPacket *sendingPacket = NULL; // The packet we are currently sending + + /** + * Enqueue a received packet for the registered receiver + */ + void deliverToReceiverISR(MeshPacket *p, BaseType_t *higherPriWoken); + public: /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool */ - RadioInterface(MemoryPool &pool, PointerQueue &rxDest); + RadioInterface(); /** - * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) - * - * This method must be used before putting the CPU into deep or light sleep. + * Set where to deliver received packets. This method should only be used by the Router class */ - virtual bool canSleep() { return true; } + void setReceiver(PointerQueue *_rxDest) { rxDest = _rxDest; } - /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. - /// return true for success - virtual bool sleep() { return true; } - - /// Send a packet (possibly by enquing in a private fifo). This routine will - /// later free() the packet to pool. This routine is not allowed to stall because it is called from - /// bluetooth comms code. If the txmit queue is empty it might return an error + /** + * Send a packet (possibly by enquing in a private fifo). This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ virtual ErrorCode send(MeshPacket *p) = 0; }; class SimRadio : public RadioInterface { public: - /** pool is the pool we will alloc our rx packets from - * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool - */ - SimRadio(MemoryPool &_pool, PointerQueue &_rxDest) : RadioInterface(_pool, _rxDest) {} - virtual ErrorCode send(MeshPacket *p); // methods from radiohead diff --git a/src/rf95/Router.cpp b/src/rf95/Router.cpp new file mode 100644 index 000000000..715ab712b --- /dev/null +++ b/src/rf95/Router.cpp @@ -0,0 +1,72 @@ +#include "Router.h" +#include "configuration.h" +#include "mesh-pb-constants.h" + +/** + * Router todo + * + * Implement basic interface and use it elsewhere in app + * Add naive flooding mixin (& drop duplicate rx broadcasts), add tools for sending broadcasts with incrementing sequence #s + * Add an optional adjacent node only 'send with ack' mixin. If we timeout waiting for the ack, call handleAckTimeout(packet) + * Add DSR mixin + * + **/ + +#define MAX_RX_FROMRADIO \ + 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big + +// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX +#define MAX_PACKETS \ + (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \ + 2) // max number of packets which can be in flight (either queued from reception or queued for sending) + +MemoryPool packetPool(MAX_PACKETS); + +Router router; + +/** + * Constructor + * + * Currently we only allow one interface, that may change in the future + */ +Router::Router() : fromRadioQueue(MAX_RX_FROMRADIO) {} + +/** + * do idle processing + * Mostly looking in our incoming rxPacket queue and calling handleReceived. + */ +void Router::loop() +{ + MeshPacket *mp; + while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { + handleReceived(mp); + } +} + +/** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ +ErrorCode Router::send(MeshPacket *p) +{ + assert(iface); + return iface->send(p); +} + +#include "GPS.h" + +/** + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + */ +void Router::handleReceived(MeshPacket *p) +{ + // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function + // Also, we should set the time from the ISR and it should have msec level resolution + p->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone + + DEBUG_MSG("Notifying observers of received packet\n"); + notifyPacketReceived.notifyObservers(p); + packetPool.release(p); +} \ No newline at end of file diff --git a/src/rf95/Router.h b/src/rf95/Router.h new file mode 100644 index 000000000..00f346666 --- /dev/null +++ b/src/rf95/Router.h @@ -0,0 +1,70 @@ +#pragma once + +#include "MemoryPool.h" +#include "MeshTypes.h" +#include "Observer.h" +#include "PointerQueue.h" +#include "RadioInterface.h" +#include "mesh.pb.h" +#include + + + +/** + * A mesh aware router that supports multiple interfaces. + */ +class Router +{ + private: + RadioInterface *iface; + + /// Packets which have just arrived from the radio, ready to be processed by this service and possibly + /// forwarded to the phone. + PointerQueue fromRadioQueue; + + public: + /// Local services that want to see _every_ packet this node receives can observe this. + /// Observers should always return 0 and _copy_ any packets they want to keep for use later (this packet will be getting + /// freed) + Observable notifyPacketReceived; + + /** + * Constructor + * + */ + Router(); + + /** + * Currently we only allow one interface, that may change in the future + */ + void addInterface(RadioInterface *_iface) + { + iface = _iface; + iface->setReceiver(&fromRadioQueue); + } + + /** + * do idle processing + * Mostly looking in our incoming rxPacket queue and calling handleReceived. + */ + void loop(); + + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + + private: + /** + * Called from loop() + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + * + * Note: this method will free the provided packet + */ + void handleReceived(MeshPacket *p); +}; + +extern Router router; \ No newline at end of file From 6afeb3e456a29e7a507f389291f8589ef4cd3f4f Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 10:38:44 -0700 Subject: [PATCH 022/197] ok - new router seems to approximately work --- bin/run-1-monitor.sh | 1 + src/main.cpp | 1 + 2 files changed, 2 insertions(+) create mode 100755 bin/run-1-monitor.sh diff --git a/bin/run-1-monitor.sh b/bin/run-1-monitor.sh new file mode 100755 index 000000000..a25d99489 --- /dev/null +++ b/bin/run-1-monitor.sh @@ -0,0 +1 @@ +pio run --upload-port /dev/ttyUSB1 -t upload -t monitor diff --git a/src/main.cpp b/src/main.cpp index 905c1c00a..b98e86c77 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -334,6 +334,7 @@ void loop() powerFSM.run_machine(); gps.loop(); + router.loop(); service.loop(); if (radio) From ea24394110759a35f1aa141ea6809e74f4f677ab Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 11:52:20 -0700 Subject: [PATCH 023/197] add first cut of mesh naive flooding --- proto | 2 +- src/MeshService.cpp | 16 ++++--- src/MeshTypes.h | 1 + src/main.cpp | 4 ++ src/rf95/CustomRF95.cpp | 10 +++-- src/rf95/FloodingRouter.cpp | 90 +++++++++++++++++++++++++++++++++++++ src/rf95/Router.cpp | 4 +- src/rf95/Router.h | 8 ++-- 8 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 src/rf95/FloodingRouter.cpp diff --git a/proto b/proto index 793d3e65c..e06645d8d 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 793d3e65ca66c3a0914e74a285a729429952a042 +Subproject commit e06645d8db16b9e4f23e74a931b8d5cd07bcbe3c diff --git a/src/MeshService.cpp b/src/MeshService.cpp index ce7fa697b..a8a2ff605 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -214,20 +214,24 @@ void MeshService::handleToRadio(std::string s) switch (r.which_variant) { case ToRadio_packet_tag: { // If our phone is sending a position, see if we can use it to set our RTC - handleIncomingPosition(&r.variant.packet); // If it is a position packet, perhaps set our clock + MeshPacket &p = r.variant.packet; + handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock - r.variant.packet.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone (so we update our - // nodedb for the local node) + if (p.from == 0) // If the phone didn't set a sending node ID, use ours + p.from = nodeDB.getNodeNum(); + + p.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone + // (so we update our nodedb for the local node) // Send the packet into the mesh - sendToMesh(packetPool.allocCopy(r.variant.packet)); + + sendToMesh(packetPool.allocCopy(p)); bool loopback = false; // if true send any packet the phone sends back itself (for testing) if (loopback) { - const MeshPacket *mp = &r.variant.packet; // no need to copy anymore because handle from radio assumes it should _not_ delete // packetPool.allocCopy(r.variant.packet); - handleFromRadio(mp); + handleFromRadio(&p); // handleFromRadio will tell the phone a new packet arrived } break; diff --git a/src/MeshTypes.h b/src/MeshTypes.h index 13ead0463..ab4203e66 100644 --- a/src/MeshTypes.h +++ b/src/MeshTypes.h @@ -7,6 +7,7 @@ #include typedef uint8_t NodeNum; +typedef uint8_t PacketId; // A packet sequence number #define NODENUM_BROADCAST 255 #define ERRNO_OK 0 diff --git a/src/main.cpp b/src/main.cpp index b98e86c77..1fed2a743 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,6 +31,7 @@ #include "error.h" #include "power.h" // #include "rom/rtc.h" +#include "FloodingRouter.h" #include "screen.h" #include "sleep.h" #include @@ -60,6 +61,9 @@ static meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; +FloodingRouter realRouter; +Router &router = realRouter; // Users of router don't care what sort of subclass implements that API + // ----------------------------------------------------------------------------- // Application // ----------------------------------------------------------------------------- diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index a2a7b17aa..f1269cff3 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -1,5 +1,5 @@ #include "CustomRF95.h" -#include "NodeDB.h" +#include "NodeDB.h" // FIXME, this class should not need to touch nodedb #include "assert.h" #include "configuration.h" #include @@ -97,6 +97,8 @@ void CustomRF95::handleInterrupt() mp->from = _rxHeaderFrom; mp->to = _rxHeaderTo; + mp->id = _rxHeaderId; + //_rxHeaderId = _buf[2]; //_rxHeaderFlags = _buf[3]; @@ -162,9 +164,11 @@ void CustomRF95::startSend(MeshPacket *txp) sendingPacket = txp; setHeaderTo(txp->to); - setHeaderFrom(nodeDB.getNodeNum()); // We must do this before each send, because we might have just changed our nodenum + setHeaderId(txp->id); - // setHeaderId(0); + // if the sender nodenum is zero, that means uninitialized + assert(txp->from); + setHeaderFrom(txp->from); // We must do this before each send, because we might have just changed our nodenum assert(numbytes <= 251); // Make sure we don't overflow the tiny max packet size diff --git a/src/rf95/FloodingRouter.cpp b/src/rf95/FloodingRouter.cpp new file mode 100644 index 000000000..388fbf3cb --- /dev/null +++ b/src/rf95/FloodingRouter.cpp @@ -0,0 +1,90 @@ +#include "FloodingRouter.h" +#include "configuration.h" +#include "mesh-pb-constants.h" + +/// We clear our old flood record five minute after we see the last of it +#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L) + +FloodingRouter::FloodingRouter() +{ + recentBroadcasts.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation +} + +/** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ +ErrorCode FloodingRouter::send(MeshPacket *p) +{ + // We update our table of recent broadcasts, even for messages we send + wasSeenRecently(p); + + return Router::send(p); +} + +/** + * Called from loop() + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + * + * Note: this method will free the provided packet + */ +void FloodingRouter::handleReceived(MeshPacket *p) +{ + if (wasSeenRecently(p)) { + DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n"); + packetPool.release(p); + } else { + if (p->to == NODENUM_BROADCAST && p->id != 0) { + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors\n"); + // FIXME, wait a random delay + + MeshPacket *tosend = packetPool.allocCopy(*p); + // Note: we are careful to resend using the original senders node id + Router::send(tosend); // We are careful not to call our hooked version of send() + } + + // handle the packet as normal + Router::handleReceived(p); + } +} + +/** + * Update recentBroadcasts and return true if we have already seen this packet + */ +bool FloodingRouter::wasSeenRecently(const MeshPacket *p) +{ + if (p->to != NODENUM_BROADCAST) + return false; // Not a broadcast, so we don't care + + if (p->id == 0) + return false; // Not a floodable message ID, so we don't care + + uint32_t now = millis(); + for (int i = 0; i < recentBroadcasts.size();) { + BroadcastRecord &r = recentBroadcasts[i]; + + if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { + DEBUG_MSG("Deleting old recentBroadcast %d\n", i); + recentBroadcasts.erase(recentBroadcasts.begin() + i); // delete old record + } else { + if (r.id == p->id && r.sender == p->from) { + // Update the time on this record to now + r.rxTimeMsec = now; + return true; + } + + i++; + } + } + + // Didn't find an existing record, make one + BroadcastRecord r; + r.id = p->id; + r.sender = p->from; + r.rxTimeMsec = now; + recentBroadcasts.push_back(r); + + return false; +} \ No newline at end of file diff --git a/src/rf95/Router.cpp b/src/rf95/Router.cpp index 715ab712b..abd0006c0 100644 --- a/src/rf95/Router.cpp +++ b/src/rf95/Router.cpp @@ -5,7 +5,7 @@ /** * Router todo * - * Implement basic interface and use it elsewhere in app + * DONE: Implement basic interface and use it elsewhere in app * Add naive flooding mixin (& drop duplicate rx broadcasts), add tools for sending broadcasts with incrementing sequence #s * Add an optional adjacent node only 'send with ack' mixin. If we timeout waiting for the ack, call handleAckTimeout(packet) * Add DSR mixin @@ -22,8 +22,6 @@ MemoryPool packetPool(MAX_PACKETS); -Router router; - /** * Constructor * diff --git a/src/rf95/Router.h b/src/rf95/Router.h index 00f346666..8f2ef6fa1 100644 --- a/src/rf95/Router.h +++ b/src/rf95/Router.h @@ -8,8 +8,6 @@ #include "mesh.pb.h" #include - - /** * A mesh aware router that supports multiple interfaces. */ @@ -56,7 +54,7 @@ class Router */ virtual ErrorCode send(MeshPacket *p); - private: + protected: /** * Called from loop() * Handle any packet that is received by an interface on this node. @@ -64,7 +62,7 @@ class Router * * Note: this method will free the provided packet */ - void handleReceived(MeshPacket *p); + virtual void handleReceived(MeshPacket *p); }; -extern Router router; \ No newline at end of file +extern Router &router; \ No newline at end of file From 65406eaa086dba49baed1ef099b35b5377329abc Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 12:41:01 -0700 Subject: [PATCH 024/197] mesh flooding seems to work pretty well! --- docs/faq.md | 24 ++++++------- src/MeshRadio.cpp | 35 +----------------- src/MeshRadio.h | 17 --------- src/MeshService.cpp | 19 ++++++++-- src/MeshService.h | 4 --- src/main.cpp | 3 -- src/rf95/CustomRF95.cpp | 27 +++++++++++++- src/rf95/CustomRF95.h | 4 +++ src/rf95/FloodingRouter.cpp | 25 ++++++++----- src/rf95/FloodingRouter.h | 72 +++++++++++++++++++++++++++++++++++++ src/rf95/RadioInterface.h | 2 ++ src/rf95/Router.cpp | 6 +++- 12 files changed, 155 insertions(+), 83 deletions(-) create mode 100644 src/rf95/FloodingRouter.h diff --git a/docs/faq.md b/docs/faq.md index c8ceae960..ec145fe3d 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,27 +1,25 @@ # Disclaimers -This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds. +This project is still pretty young but moving at a pretty good pace. Not all features are fully implemented in the current alpha builds. Most of these problems should be solved by the beta release (within three months): -* We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-) -* Encryption is turned off for now -* A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations. -* The current Android GUI is slightly ugly still -* The Android API needs to be documented better -* The mesh protocol is turned off for now, currently we only send packets one hop distant. The mesh feature will be turned on again [soonish](https://github.com/meshtastic/Meshtastic-esp32/issues/3). -* No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-) +- We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-) +- Encryption is turned off for now +- A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations. +- The Android API needs to be documented better +- No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-) For more details see the [device software TODO](https://github.com/meshtastic/Meshtastic-esp32/blob/master/docs/software/TODO.md) or the [Android app TODO](https://github.com/meshtastic/Meshtastic-Android/blob/master/TODO.md). # FAQ -If you have a question missing from this faq, please [ask in our discussion forum](https://meshtastic.discourse.group/). And if you are feeling extra generous send in a pull-request for this faq.md with whatever we answered ;-). +If you have a question missing from this faq, please [ask in our discussion forum](https://meshtastic.discourse.group/). And if you are feeling extra generous send in a pull-request for this faq.md with whatever we answered ;-). ## Q: Which of the various supported radios should I buy? -Basically you just need the radio + (optional but recommended) battery. The TBEAM is usually better because it has gps and huge battery socket. The Heltec is basically the same hardware but without the GPS (the phone provides position data to the radio in that case, so the behavior is similar - but it does burn some battery in the phone). Also the battery for the Heltec can be smaller. +Basically you just need the radio + (optional but recommended) battery. The TBEAM is usually better because it has gps and huge battery socket. The Heltec is basically the same hardware but without the GPS (the phone provides position data to the radio in that case, so the behavior is similar - but it does burn some battery in the phone). Also the battery for the Heltec can be smaller. -In addition to Aliexpress, (banggood.com) usually has stock and faster shipping, or Amazon. If buying a TBEAM, make sure to buy a version that includes the OLED screen - this project doesn't absolutely require the screen, but we use it if is installed. +In addition to Aliexpress, (banggood.com) usually has stock and faster shipping, or Amazon. If buying a TBEAM, make sure to buy a version that includes the OLED screen - this project doesn't absolutely require the screen, but we use it if is installed. @claesg has added links to various 3D printable cases, you can see them at (www.meshtastic.org). @@ -29,8 +27,8 @@ In addition to Aliexpress, (banggood.com) usually has stock and faster shipping, Nope. though if some other person/group wanted to use this software and a more customized device we think that would be awesome (as long as they obey the GPL license). -## Q: Does this project use patented algorithms? +## Q: Does this project use patented algorithms? (Kindly borrowed from the geeks at [ffmpeg](http://ffmpeg.org/legal.html)) -We do not know, we are not lawyers so we are not qualified to answer this. Also we have never read patents to implement any part of this, so even if we were qualified we could not answer it as we do not know what is patented. Furthermore the sheer number of software patents makes it impossible to read them all so no one (lawyer or not) could answer such a question with a definite no. We are merely geeks experimenting on a fun and free project. +We do not know, we are not lawyers so we are not qualified to answer this. Also we have never read patents to implement any part of this, so even if we were qualified we could not answer it as we do not know what is patented. Furthermore the sheer number of software patents makes it impossible to read them all so no one (lawyer or not) could answer such a question with a definite no. We are merely geeks experimenting on a fun and free project. diff --git a/src/MeshRadio.cpp b/src/MeshRadio.cpp index a5d1d9d62..4242a94a2 100644 --- a/src/MeshRadio.cpp +++ b/src/MeshRadio.cpp @@ -24,7 +24,7 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts /// Sometimes while debugging it is useful to set this false, to disable rf95 accesses bool useHardware = true; -MeshRadio::MeshRadio() : sendPacketObserver(this, &MeshRadio::send) // , manager(radioIf) +MeshRadio::MeshRadio() // , manager(radioIf) { myNodeInfo.num_channels = NUM_CHANNELS; @@ -40,7 +40,6 @@ bool MeshRadio::init() DEBUG_MSG("Starting meshradio init...\n"); configChangedObserver.observe(&service.configChanged); - sendPacketObserver.observe(&service.sendViaRadio); preflightSleepObserver.observe(&preflightSleep); notifyDeepSleepObserver.observe(¬ifyDeepSleep); @@ -124,35 +123,3 @@ int MeshRadio::reloadConfig(void *unused) return 0; } -int MeshRadio::send(MeshPacket *p) -{ - lastTxStart = millis(); - - if (useHardware) { - radioIf.send(p); - // Note: we ignore the error code, because no matter what the interface has already freed the packet. - return 1; // Indicate success - stop offering this packet to radios - } else { - // fail - return 0; - } -} - -#define TX_WATCHDOG_TIMEOUT 30 * 1000 - -void MeshRadio::loop() -{ - // It should never take us more than 30 secs to send a packet, if it does, we have a bug, FIXME, move most of this - // into CustomRF95 - uint32_t now = millis(); - if (lastTxStart != 0 && (now - lastTxStart) > TX_WATCHDOG_TIMEOUT && radioIf.mode() == RHGenericDriver::RHModeTx) { - DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode\n"); - radioIf.setModeRx(); - if (radioIf.sendingPacket) { // There was probably a packet we were trying to send, free it - packetPool.release(radioIf.sendingPacket); - radioIf.sendingPacket = NULL; - } - recordCriticalError(ErrTxWatchdog); - lastTxStart = 0; // Stop checking for now, because we just warned the developer - } -} diff --git a/src/MeshRadio.h b/src/MeshRadio.h index 84c4bea33..bea13d4f8 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -80,14 +80,7 @@ class MeshRadio bool init(); - /// Do loop callback operations (we currently FIXME poll the receive mailbox here) - /// for received packets it will call the rx handler - void loop(); - private: - /// Used for the tx timer watchdog, to check for bugs in our transmit code, msec of last time we did a send - uint32_t lastTxStart = 0; - CallbackObserver configChangedObserver = CallbackObserver(this, &MeshRadio::reloadConfig); @@ -97,16 +90,6 @@ class MeshRadio CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &MeshRadio::notifyDeepSleepDb); - CallbackObserver sendPacketObserver; /* = - CallbackObserver(this, &MeshRadio::send); */ - - /// Send a packet (possibly by enquing in a private fifo). This routine will - /// later free() the packet to pool. This routine is not allowed to stall because it is called from - /// bluetooth comms code. If the txmit queue is empty it might return an error. - /// - /// Returns 1 for success or 0 for failure (and if we fail it is the _callers_ responsibility to free the packet) - int send(MeshPacket *p); - /// The radioConfig object just changed, call this to force the hw to change to the new settings int reloadConfig(void *unused = NULL); diff --git a/src/MeshService.cpp b/src/MeshService.cpp index a8a2ff605..0c8fbdf3e 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -46,6 +46,18 @@ MeshService service; #include "Router.h" +#define NUM_PACKET_ID 255 // 0 is consider invalid + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId() +{ + static uint32_t i; + + i++; + return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 +} + MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro @@ -220,6 +232,9 @@ void MeshService::handleToRadio(std::string s) if (p.from == 0) // If the phone didn't set a sending node ID, use ours p.from = nodeDB.getNodeNum(); + if (p.id == 0) + p.id = generatePacketId(); // If the phone didn't supply one, then pick one + p.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone // (so we update our nodedb for the local node) @@ -265,8 +280,7 @@ void MeshService::sendToMesh(MeshPacket *p) DEBUG_MSG("Dropping locally processed message\n"); else { // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - int didSend = sendViaRadio.notifyObservers(p); - if (!didSend) { + if (router.send(p) != ERRNO_OK) { DEBUG_MSG("No radio was able to send packet, discarding...\n"); releaseToPool(p); } @@ -280,6 +294,7 @@ MeshPacket *MeshService::allocForSending() p->has_payload = true; p->from = nodeDB.getNodeNum(); p->to = NODENUM_BROADCAST; + p->id = generatePacketId(); p->rx_time = gps.getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp return p; diff --git a/src/MeshService.h b/src/MeshService.h index d8349a55b..f1e45d0bb 100644 --- a/src/MeshService.h +++ b/src/MeshService.h @@ -40,10 +40,6 @@ class MeshService /// Called when radio config has changed (radios should observe this and set their hardware as required) Observable configChanged; - /// Radios should observe this and return 0 if they were unable to process the packet or 1 if they were (and therefore it - /// should not be offered to other radios) - Observable sendViaRadio; - MeshService(); void init(); diff --git a/src/main.cpp b/src/main.cpp index 1fed2a743..0c95aaf55 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -341,9 +341,6 @@ void loop() router.loop(); service.loop(); - if (radio) - radio->loop(); - ledPeriodic.loop(); // axpDebugOutput.loop(); diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index f1269cff3..62900c355 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -47,7 +47,9 @@ ErrorCode CustomRF95::send(MeshPacket *p) // we almost certainly guarantee no one outside will like the packet we are sending. if (_mode == RHModeIdle || (_mode == RHModeRx && !isReceiving())) { // if the radio is idle, we can send right away - DEBUG_MSG("immedate send on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", txGood(), rxGood(), rxBad()); + DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, + txGood(), rxGood(), rxBad()); + startSend(p); return ERRNO_OK; } else { @@ -159,6 +161,8 @@ void CustomRF95::startSend(MeshPacket *txp) // DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad()); assert(txp->has_payload); + lastTxStart = millis(); + size_t numbytes = pb_encode_to_bytes(radiobuf, sizeof(radiobuf), SubPacket_fields, &txp->payload); sendingPacket = txp; @@ -178,4 +182,25 @@ void CustomRF95::startSend(MeshPacket *txp) assert(res); } +#define TX_WATCHDOG_TIMEOUT 30 * 1000 + +#include "error.h" + +void CustomRF95::loop() +{ + // It should never take us more than 30 secs to send a packet, if it does, we have a bug, FIXME, move most of this + // into CustomRF95 + uint32_t now = millis(); + if (lastTxStart != 0 && (now - lastTxStart) > TX_WATCHDOG_TIMEOUT && mode() == RHGenericDriver::RHModeTx) { + DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode\n"); + setModeRx(); + if (sendingPacket) { // There was probably a packet we were trying to send, free it + packetPool.release(sendingPacket); + sendingPacket = NULL; + } + recordCriticalError(ErrTxWatchdog); + lastTxStart = 0; // Stop checking for now, because we just warned the developer + } +} + #endif \ No newline at end of file diff --git a/src/rf95/CustomRF95.h b/src/rf95/CustomRF95.h index 65e49f057..a424f2493 100644 --- a/src/rf95/CustomRF95.h +++ b/src/rf95/CustomRF95.h @@ -15,6 +15,8 @@ class CustomRF95 : public RH_RF95, public RadioInterface PointerQueue txQueue; + uint32_t lastTxStart = 0L; + public: /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool @@ -38,6 +40,8 @@ class CustomRF95 : public RH_RF95, public RadioInterface bool init(); + void loop(); // Idle processing + protected: // After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as // necessary diff --git a/src/rf95/FloodingRouter.cpp b/src/rf95/FloodingRouter.cpp index 388fbf3cb..2bad7f4c9 100644 --- a/src/rf95/FloodingRouter.cpp +++ b/src/rf95/FloodingRouter.cpp @@ -36,13 +36,17 @@ void FloodingRouter::handleReceived(MeshPacket *p) DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n"); packetPool.release(p); } else { - if (p->to == NODENUM_BROADCAST && p->id != 0) { - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors\n"); - // FIXME, wait a random delay + if (p->to == NODENUM_BROADCAST) { + if (p->id != 0) { + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors\n"); + // FIXME, wait a random delay - MeshPacket *tosend = packetPool.allocCopy(*p); - // Note: we are careful to resend using the original senders node id - Router::send(tosend); // We are careful not to call our hooked version of send() + MeshPacket *tosend = packetPool.allocCopy(*p); + // Note: we are careful to resend using the original senders node id + Router::send(tosend); // We are careful not to call our hooked version of send() + } else { + DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); + } } // handle the packet as normal @@ -58,18 +62,22 @@ bool FloodingRouter::wasSeenRecently(const MeshPacket *p) if (p->to != NODENUM_BROADCAST) return false; // Not a broadcast, so we don't care - if (p->id == 0) + if (p->id == 0) { + DEBUG_MSG("Ignoring message with zero id\n"); return false; // Not a floodable message ID, so we don't care + } uint32_t now = millis(); for (int i = 0; i < recentBroadcasts.size();) { BroadcastRecord &r = recentBroadcasts[i]; if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { - DEBUG_MSG("Deleting old recentBroadcast %d\n", i); + DEBUG_MSG("Deleting old broadcast record %d\n", i); recentBroadcasts.erase(recentBroadcasts.begin() + i); // delete old record } else { if (r.id == p->id && r.sender == p->from) { + DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // Update the time on this record to now r.rxTimeMsec = now; return true; @@ -85,6 +93,7 @@ bool FloodingRouter::wasSeenRecently(const MeshPacket *p) r.sender = p->from; r.rxTimeMsec = now; recentBroadcasts.push_back(r); + DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); return false; } \ No newline at end of file diff --git a/src/rf95/FloodingRouter.h b/src/rf95/FloodingRouter.h new file mode 100644 index 000000000..7ce7541a7 --- /dev/null +++ b/src/rf95/FloodingRouter.h @@ -0,0 +1,72 @@ +#pragma once + +#include "Router.h" +#include + +/** + * A record of a recent message broadcast + */ +struct BroadcastRecord { + NodeNum sender; + PacketId id; + uint32_t rxTimeMsec; // Unix time in msecs - the time we received it +}; + +/** + * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) + * + * Rules for broadcasting (listing here for now, will move elsewhere eventually): + + If to==BROADCAST and id==0, this is a simple broadcast (0 hops). It will be + sent only by the current node and other nodes will not attempt to rebroadcast + it. + + If to==BROADCAST and id!=0, this is a "naive flooding" broadcast. The initial + node will send it on all local interfaces. + + When other nodes receive this message, they will + first check if their recentBroadcasts table contains the (from, id) pair that + indicates this message. If so, we've already seen it - so we discard it. If + not, we add it to the table and then resend this message on all interfaces. + When resending we are careful to use the "from" ID of the original sender. Not + our own ID. When resending we pick a random delay between 0 and 10 seconds to + decrease the chance of collisions with transmitters we can not even hear. + + Any entries in recentBroadcasts that are older than X seconds (longer than the + max time a flood can take) will be discarded. + */ +class FloodingRouter : public Router +{ + private: + std::vector recentBroadcasts; + + public: + /** + * Constructor + * + */ + FloodingRouter(); + + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + + protected: + /** + * Called from loop() + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + * + * Note: this method will free the provided packet + */ + virtual void handleReceived(MeshPacket *p); + + private: + /** + * Update recentBroadcasts and return true if we have already seen this packet + */ + bool wasSeenRecently(const MeshPacket *p); +}; diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index 48f205851..1172745fa 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -37,6 +37,8 @@ class RadioInterface */ void setReceiver(PointerQueue *_rxDest) { rxDest = _rxDest; } + virtual void loop() {} // Idle processing + /** * Send a packet (possibly by enquing in a private fifo). This routine will * later free() the packet to pool. This routine is not allowed to stall. diff --git a/src/rf95/Router.cpp b/src/rf95/Router.cpp index abd0006c0..6b949c1fa 100644 --- a/src/rf95/Router.cpp +++ b/src/rf95/Router.cpp @@ -35,6 +35,9 @@ Router::Router() : fromRadioQueue(MAX_RX_FROMRADIO) {} */ void Router::loop() { + if (iface) + iface->loop(); + MeshPacket *mp; while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) { handleReceived(mp); @@ -49,6 +52,7 @@ void Router::loop() ErrorCode Router::send(MeshPacket *p) { assert(iface); + DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); return iface->send(p); } @@ -64,7 +68,7 @@ void Router::handleReceived(MeshPacket *p) // Also, we should set the time from the ISR and it should have msec level resolution p->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone - DEBUG_MSG("Notifying observers of received packet\n"); + DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); notifyPacketReceived.notifyObservers(p); packetPool.release(p); } \ No newline at end of file From 25cca0628d921c37c86f209f202ba1d476509943 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 12:46:57 -0700 Subject: [PATCH 025/197] more debug output --- src/rf95/FloodingRouter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rf95/FloodingRouter.cpp b/src/rf95/FloodingRouter.cpp index 2bad7f4c9..817870131 100644 --- a/src/rf95/FloodingRouter.cpp +++ b/src/rf95/FloodingRouter.cpp @@ -38,7 +38,7 @@ void FloodingRouter::handleReceived(MeshPacket *p) } else { if (p->to == NODENUM_BROADCAST) { if (p->id != 0) { - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors\n"); + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); // FIXME, wait a random delay MeshPacket *tosend = packetPool.allocCopy(*p); From 62286fff5288cccbed318fc0a283a523c5f89d75 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 13:05:16 -0700 Subject: [PATCH 026/197] 0.4.1 release --- bin/version.sh | 2 +- src/MeshService.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/version.sh b/bin/version.sh index 429d819cf..edd162e43 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.2.3 \ No newline at end of file +export VERSION=0.4.1 \ No newline at end of file diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 0c8fbdf3e..32a939030 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -201,7 +201,7 @@ void MeshService::loop() { if (oldFromNum != fromNum) { // We don't want to generate extra notifies for multiple new packets fromNumChanged.notifyObservers(fromNum); - oldFromNum = true; + oldFromNum = fromNum; } // occasionally send our owner info From 7730bd762a9bd4bb7b54d93d3246a8ecdbf4ccfa Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 13:18:33 -0700 Subject: [PATCH 027/197] be less chatty about sleep --- src/rf95/CustomRF95.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index 62900c355..5dc5d0bb9 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -17,8 +17,12 @@ bool CustomRF95::canSleep() { // We allow initializing mode, because sometimes while testing we don't ever call init() to turn on the hardware bool isRx = isReceiving(); - DEBUG_MSG("canSleep, mode=%d, isRx=%d, txEmpty=%d, txGood=%d\n", _mode, isRx, txQueue.isEmpty(), _txGood); - return (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !isRx && txQueue.isEmpty(); + + bool res = (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !isRx && txQueue.isEmpty(); + if (!res) // only print debug messages if we are vetoing sleep + DEBUG_MSG("canSleep, mode=%d, isRx=%d, txEmpty=%d, txGood=%d\n", _mode, isRx, txQueue.isEmpty(), _txGood); + + return res; } bool CustomRF95::sleep() From 04a83fd6b730772fd8d902c53610eb5141bbf47d Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 13:24:38 -0700 Subject: [PATCH 028/197] properly detach observers at destruction --- src/Observer.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Observer.h b/src/Observer.h index eab1a4a30..0c0cb9a92 100644 --- a/src/Observer.h +++ b/src/Observer.h @@ -11,10 +11,9 @@ template class Observable; */ template class Observer { - Observable *observed; + Observable *observed = NULL; public: - Observer() : observed(NULL) {} virtual ~Observer(); @@ -92,5 +91,9 @@ template Observer::~Observer() template void Observer::observe(Observable *o) { + // We can only watch one thing at a time + assert(!observed); + + observed = o; o->addObserver(this); } \ No newline at end of file From 5b17417e0ceee9f68299d35e1be176c228075e35 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 14:30:42 -0700 Subject: [PATCH 029/197] debugging GPIO wake on heltec- seems fine. --- src/NodeDB.cpp | 6 ++++++ src/sleep.cpp | 11 +++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/NodeDB.cpp b/src/NodeDB.cpp index 3791a5ae5..0d8fe6a50 100644 --- a/src/NodeDB.cpp +++ b/src/NodeDB.cpp @@ -78,6 +78,12 @@ void NodeDB::resetRadioConfig() memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk)); strcpy(channelSettings.name, "Default"); } + + // temp hack for quicker testing + /* + radioConfig.preferences.screen_on_secs = 30; + radioConfig.preferences.wait_bluetooth_secs = 30; + */ } void NodeDB::init() diff --git a/src/sleep.cpp b/src/sleep.cpp index 0459dbe7a..ad8aa7359 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -12,9 +12,9 @@ #include #ifndef NO_ESP32 -#include "rom/rtc.h" #include "esp32/pm.h" #include "esp_pm.h" +#include "rom/rtc.h" #include #include "BluetoothUtil.h" @@ -280,9 +280,12 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r assert(esp_sleep_enable_gpio_wakeup() == ESP_OK); assert(esp_sleep_enable_timer_wakeup(sleepUsec) == ESP_OK); assert(esp_light_sleep_start() == ESP_OK); - // DEBUG_MSG("Exit light sleep b=%d, rf95=%d, pmu=%d\n", digitalRead(BUTTON_PIN), digitalRead(RF95_IRQ_GPIO), - // digitalRead(PMU_IRQ)); - return esp_sleep_get_wakeup_cause(); + + esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); + if (cause == ESP_SLEEP_WAKEUP_GPIO) + DEBUG_MSG("Exit light sleep gpio: btn=%d, rf95=%d\n", !digitalRead(BUTTON_PIN), digitalRead(RF95_IRQ_GPIO)); + + return cause; } #endif From 2fe145aed9be715ecdf5d729393bd966aff7c624 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 18:48:37 -0700 Subject: [PATCH 030/197] debugging goo --- src/MeshService.cpp | 3 --- src/NodeDB.cpp | 1 + 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 32a939030..4331f939f 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -69,9 +69,6 @@ void MeshService::init() gpsObserver.observe(&gps); packetReceivedObserver.observe(&router.notifyPacketReceived); - - // No need to call this here, our periodic task will fire quite soon - // sendOwnerPeriod(); } void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) diff --git a/src/NodeDB.cpp b/src/NodeDB.cpp index 0d8fe6a50..6348b7478 100644 --- a/src/NodeDB.cpp +++ b/src/NodeDB.cpp @@ -83,6 +83,7 @@ void NodeDB::resetRadioConfig() /* radioConfig.preferences.screen_on_secs = 30; radioConfig.preferences.wait_bluetooth_secs = 30; + radioConfig.preferences.position_broadcast_secs = 15; */ } From 8eb30454518b30a2b5e875fe74a0ad462f8f4c17 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 18:49:54 -0700 Subject: [PATCH 031/197] Fix #85, we were stalling sometimes on send while in ISR which is NEVER legal --- src/rf95/CustomRF95.cpp | 5 +++++ src/rf95/RH_RF95.cpp | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index 5dc5d0bb9..62c4ff8b3 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -54,6 +54,11 @@ ErrorCode CustomRF95::send(MeshPacket *p) DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, txGood(), rxGood(), rxBad()); + waitPacketSent(); // Make sure we dont interrupt an outgoing message + + if (!waitCAD()) + return false; // Check channel activity + startSend(p); return ERRNO_OK; } else { diff --git a/src/rf95/RH_RF95.cpp b/src/rf95/RH_RF95.cpp index d08de8b1d..c29929637 100644 --- a/src/rf95/RH_RF95.cpp +++ b/src/rf95/RH_RF95.cpp @@ -280,17 +280,14 @@ void RH_RF95::clearRxBuf() ATOMIC_BLOCK_END; } +/// Note: This routine might be called from inside the RF95 ISR bool RH_RF95::send(const uint8_t *data, uint8_t len) { if (len > RH_RF95_MAX_MESSAGE_LEN) return false; - waitPacketSent(); // Make sure we dont interrupt an outgoing message setModeIdle(); - if (!waitCAD()) - return false; // Check channel activity - // Position at the beginning of the FIFO spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, 0); // The headers From 176532f55f7a49ecad8d4bcfda7221434aa84d64 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 18:50:07 -0700 Subject: [PATCH 032/197] autoformat per formatting rules --- src/rf95/RHGenericDriver.cpp | 76 +++++++++++++++--------------------- 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/src/rf95/RHGenericDriver.cpp b/src/rf95/RHGenericDriver.cpp index 332596fda..0dbb995fb 100644 --- a/src/rf95/RHGenericDriver.cpp +++ b/src/rf95/RHGenericDriver.cpp @@ -6,17 +6,8 @@ #include RHGenericDriver::RHGenericDriver() - : - _mode(RHModeInitialising), - _thisAddress(RH_BROADCAST_ADDRESS), - _txHeaderTo(RH_BROADCAST_ADDRESS), - _txHeaderFrom(RH_BROADCAST_ADDRESS), - _txHeaderId(0), - _txHeaderFlags(0), - _rxBad(0), - _rxGood(0), - _txGood(0), - _cad_timeout(0) + : _mode(RHModeInitialising), _thisAddress(RH_BROADCAST_ADDRESS), _txHeaderTo(RH_BROADCAST_ADDRESS), + _txHeaderFrom(RH_BROADCAST_ADDRESS), _txHeaderId(0), _txHeaderFlags(0), _rxBad(0), _rxGood(0), _txGood(0), _cad_timeout(0) { } @@ -29,7 +20,7 @@ bool RHGenericDriver::init() void RHGenericDriver::waitAvailable() { while (!available()) - YIELD; + YIELD; } // Blocks until a valid message is received or timeout expires @@ -38,13 +29,11 @@ void RHGenericDriver::waitAvailable() bool RHGenericDriver::waitAvailableTimeout(uint16_t timeout) { unsigned long starttime = millis(); - while ((millis() - starttime) < timeout) - { - if (available()) - { - return true; - } - YIELD; + while ((millis() - starttime) < timeout) { + if (available()) { + return true; + } + YIELD; } return false; } @@ -52,18 +41,17 @@ bool RHGenericDriver::waitAvailableTimeout(uint16_t timeout) bool RHGenericDriver::waitPacketSent() { while (_mode == RHModeTx) - YIELD; // Wait for any previous transmit to finish + YIELD; // Wait for any previous transmit to finish return true; } bool RHGenericDriver::waitPacketSent(uint16_t timeout) { unsigned long starttime = millis(); - while ((millis() - starttime) < timeout) - { + while ((millis() - starttime) < timeout) { if (_mode != RHModeTx) // Any previous transmit finished? - return true; - YIELD; + return true; + YIELD; } return false; } @@ -72,7 +60,7 @@ bool RHGenericDriver::waitPacketSent(uint16_t timeout) bool RHGenericDriver::waitCAD() { if (!_cad_timeout) - return true; + return true; // Wait for any channel activity to finish or timeout // Sophisticated DCF function... @@ -80,14 +68,13 @@ bool RHGenericDriver::waitCAD() // 100 - 1000 ms // 10 sec timeout unsigned long t = millis(); - while (isChannelActive()) - { - if (millis() - t > _cad_timeout) - return false; + while (isChannelActive()) { + if (millis() - t > _cad_timeout) + return false; #if (RH_PLATFORM == RH_PLATFORM_STM32) // stdlib on STMF103 gets confused if random is redefined - delay(_random(1, 10) * 100); + delay(_random(1, 10) * 100); #else - delay(random(1, 10) * 100); // Should these values be configurable? Macros? + delay(random(1, 10) * 100); // Should these values be configurable? Macros? #endif } @@ -156,36 +143,34 @@ int16_t RHGenericDriver::lastRssi() return _lastRssi; } -RHGenericDriver::RHMode RHGenericDriver::mode() +RHGenericDriver::RHMode RHGenericDriver::mode() { return _mode; } -void RHGenericDriver::setMode(RHMode mode) +void RHGenericDriver::setMode(RHMode mode) { _mode = mode; } -bool RHGenericDriver::sleep() +bool RHGenericDriver::sleep() { return false; } // Diagnostic help -void RHGenericDriver::printBuffer(const char* prompt, const uint8_t* buf, uint8_t len) +void RHGenericDriver::printBuffer(const char *prompt, const uint8_t *buf, uint8_t len) { #ifdef RH_HAVE_SERIAL Serial.println(prompt); uint8_t i; - for (i = 0; i < len; i++) - { - if (i % 16 == 15) - Serial.println(buf[i], HEX); - else - { - Serial.print(buf[i], HEX); - Serial.print(' '); - } + for (i = 0; i < len; i++) { + if (i % 16 == 15) + Serial.println(buf[i], HEX); + else { + Serial.print(buf[i], HEX); + Serial.print(' '); + } } Serial.println(""); #endif @@ -216,6 +201,7 @@ void RHGenericDriver::setCADTimeout(unsigned long cad_timeout) // get linking complaints from the default code generated for pure virtual functions extern "C" void __cxa_pure_virtual() { - while (1); + while (1) + ; } #endif From 184eac6281fa61c8be2f62dffbf2b38699c49dc7 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 17 Apr 2020 18:51:46 -0700 Subject: [PATCH 033/197] 0.4.2 --- bin/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/version.sh b/bin/version.sh index edd162e43..703441d91 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.4.1 \ No newline at end of file +export VERSION=0.4.2 \ No newline at end of file From 4ce7df295e34d5c3129bb4962d44bf626d8d40db Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 18 Apr 2020 08:39:05 -0700 Subject: [PATCH 034/197] don't poll for completion so quickly - the log messages scare people --- src/sleep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index ad8aa7359..69b80e91b 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -135,7 +135,7 @@ static void waitEnterSleep() uint32_t now = millis(); while (!doPreflightSleep()) { - delay(10); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) + delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) if (millis() - now > 30 * 1000) { // If we wait too long just report an error and go to sleep recordCriticalError(ErrSleepEnterWait); From 78470ed3f59f5c84fbd1325bcff1fd95b2b20183 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 18 Apr 2020 08:48:03 -0700 Subject: [PATCH 035/197] fix #97, we need the RF95 IRQ to be level triggered, or we have slim chance of missing events --- src/rf95/RH_RF95.cpp | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/rf95/RH_RF95.cpp b/src/rf95/RH_RF95.cpp index c29929637..3929156f5 100644 --- a/src/rf95/RH_RF95.cpp +++ b/src/rf95/RH_RF95.cpp @@ -115,11 +115,11 @@ bool RH_RF95::init() } _deviceForInterrupt[_myInterruptIndex] = this; if (_myInterruptIndex == 0) - attachInterrupt(interruptNumber, isr0, RISING); + attachInterrupt(interruptNumber, isr0, ONHIGH); else if (_myInterruptIndex == 1) - attachInterrupt(interruptNumber, isr1, RISING); + attachInterrupt(interruptNumber, isr1, ONHIGH); else if (_myInterruptIndex == 2) - attachInterrupt(interruptNumber, isr2, RISING); + attachInterrupt(interruptNumber, isr2, ONHIGH); else return false; // Too many devices, not enough interrupt vectors @@ -153,6 +153,17 @@ void RH_RF95::handleInterrupt() // Read the interrupt register uint8_t irq_flags = spiRead(RH_RF95_REG_12_IRQ_FLAGS); + // ack all interrupts, note - we did this already in the RX_DONE case above, and we don't want to do it twice + // Sigh: on some processors, for some unknown reason, doing this only once does not actually + // clear the radio's interrupt flag. So we do it twice. Why? (kevinh - I think the root cause we want level + // triggered interrupts here - not edge. Because edge allows us to miss handling secondard interrupts that occurred + // while this ISR was running. Better to instead, configure the interrupts as level triggered and clear pending + // at the _beginning_ of the ISR. If any interrupts occur while handling the ISR, the signal will remain asserted and + // our ISR will be reinvoked to handle that case) + // kevinh: turn this off until root cause is known, because it can cause missed interrupts! + // spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags + spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags + // Note: there can be substantial latency between ISR assertion and this function being run, therefore // multiple flags might be set. Handle them all @@ -170,8 +181,6 @@ void RH_RF95::handleInterrupt() // in the header. If not it might be a stray (noise) packet.* uint8_t crc_present = spiRead(RH_RF95_REG_1C_HOP_CHANNEL) & RH_RF95_RX_PAYLOAD_CRC_IS_ON; - spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags, required before reading fifo (according to datasheet) - if (!crc_present) { _rxBad++; clearRxBuf(); @@ -218,15 +227,6 @@ void RH_RF95::handleInterrupt() _cad = irq_flags & RH_RF95_CAD_DETECTED; setModeIdle(); } - - // ack all interrupts, note - we did this already in the RX_DONE case above, and we don't want to do it twice - if (!(irq_flags & RH_RF95_RX_DONE)) { - // Sigh: on some processors, for some unknown reason, doing this only once does not actually - // clear the radio's interrupt flag. So we do it twice. Why? - // kevinh: turn this off until root cause is known, because it can cause missed interrupts! - // spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags - spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags - } } // These are low level functions that call the interrupt handler for the correct From 20b41836e255d14ab27aeed7d24ebbddedfa7119 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 18 Apr 2020 09:22:08 -0700 Subject: [PATCH 036/197] clarify log msg --- src/rf95/CustomRF95.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index 62c4ff8b3..697800e53 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -20,7 +20,7 @@ bool CustomRF95::canSleep() bool res = (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !isRx && txQueue.isEmpty(); if (!res) // only print debug messages if we are vetoing sleep - DEBUG_MSG("canSleep, mode=%d, isRx=%d, txEmpty=%d, txGood=%d\n", _mode, isRx, txQueue.isEmpty(), _txGood); + DEBUG_MSG("radio wait to sleep, mode=%d, isRx=%d, txEmpty=%d, txGood=%d\n", _mode, isRx, txQueue.isEmpty(), _txGood); return res; } From e5f9a752d8cc5fb67d726d6a9eeade1677b180f2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 18 Apr 2020 09:22:26 -0700 Subject: [PATCH 037/197] fix comments and cleanup ISR code --- src/rf95/RH_RF95.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/rf95/RH_RF95.cpp b/src/rf95/RH_RF95.cpp index 3929156f5..9810428e3 100644 --- a/src/rf95/RH_RF95.cpp +++ b/src/rf95/RH_RF95.cpp @@ -153,15 +153,14 @@ void RH_RF95::handleInterrupt() // Read the interrupt register uint8_t irq_flags = spiRead(RH_RF95_REG_12_IRQ_FLAGS); - // ack all interrupts, note - we did this already in the RX_DONE case above, and we don't want to do it twice + // ack all interrupts + // note from radiohead author wrt old code (with IMO wrong fix) // Sigh: on some processors, for some unknown reason, doing this only once does not actually // clear the radio's interrupt flag. So we do it twice. Why? (kevinh - I think the root cause we want level // triggered interrupts here - not edge. Because edge allows us to miss handling secondard interrupts that occurred // while this ISR was running. Better to instead, configure the interrupts as level triggered and clear pending // at the _beginning_ of the ISR. If any interrupts occur while handling the ISR, the signal will remain asserted and // our ISR will be reinvoked to handle that case) - // kevinh: turn this off until root cause is known, because it can cause missed interrupts! - // spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags // Note: there can be substantial latency between ISR assertion and this function being run, therefore @@ -169,14 +168,10 @@ void RH_RF95::handleInterrupt() // Note: we are running the chip in continuous receive mode (currently, so RX_TIMEOUT shouldn't ever occur) bool haveRxError = irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR); - if (haveRxError) - // if (_mode == RHModeRx && irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR)) - { + if (haveRxError) { _rxBad++; clearRxBuf(); - } - - if ((irq_flags & RH_RF95_RX_DONE) && !haveRxError) { + } else if (irq_flags & RH_RF95_RX_DONE) { // Read the RegHopChannel register to check if CRC presence is signalled // in the header. If not it might be a stray (noise) packet.* uint8_t crc_present = spiRead(RH_RF95_REG_1C_HOP_CHANNEL) & RH_RF95_RX_PAYLOAD_CRC_IS_ON; From db766f18edb4fd9e4b1b5f7da81010f324e5d387 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 18 Apr 2020 14:22:24 -0700 Subject: [PATCH 038/197] Fix #99: move spi ISR operations into helper thread. SPI from ISR is bad! --- src/main.cpp | 2 +- src/rf95/CustomRF95.cpp | 23 +++++++------------ src/rf95/CustomRF95.h | 2 +- src/rf95/RH_RF95.cpp | 46 ++++++++++++++++++++++++++++++------- src/rf95/RH_RF95.h | 10 ++++++++ src/rf95/RadioInterface.cpp | 4 ++-- src/rf95/RadioInterface.h | 2 +- 7 files changed, 61 insertions(+), 28 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 0c95aaf55..b39e61fc9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -336,9 +336,9 @@ void loop() { uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop? - powerFSM.run_machine(); gps.loop(); router.loop(); + powerFSM.run_machine(); service.loop(); ledPeriodic.loop(); diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index 697800e53..7005a9ce1 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -78,13 +78,12 @@ void CustomRF95::handleInterrupt() { RH_RF95::handleInterrupt(); - BaseType_t higherPriWoken = false; if (_mode == RHModeIdle) // We are now done sending or receiving { if (sendingPacket) // Were we sending? { // We are done sending that packet, release it - packetPool.releaseFromISR(sendingPacket, &higherPriWoken); + packetPool.release(sendingPacket); sendingPacket = NULL; // DEBUG_MSG("Done with send\n"); } @@ -123,43 +122,35 @@ void CustomRF95::handleInterrupt() } if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { - packetPool.releaseFromISR(mp, &higherPriWoken); + packetPool.release(mp); } else { // parsing was successful, queue for our recipient mp->has_payload = true; - deliverToReceiverISR(mp, &higherPriWoken); + deliverToReceiver(mp); } clearRxBuf(); // This message accepted and cleared } - higherPriWoken |= handleIdleISR(); + handleIdleISR(); } - - // If we call this _IT WILL NOT RETURN_ - if (higherPriWoken) - portYIELD_FROM_ISR(); } /** The ISR doesn't have any good work to do, give a new assignment. * * Return true if a higher pri task has woken */ -bool CustomRF95::handleIdleISR() +void CustomRF95::handleIdleISR() { - BaseType_t higherPriWoken = false; - // First send any outgoing packets we have ready - MeshPacket *txp = txQueue.dequeuePtrFromISR(0); + MeshPacket *txp = txQueue.dequeuePtr(0); if (txp) startSend(txp); else { // Nothing to send, let's switch back to receive mode setModeRx(); } - - return higherPriWoken; } /// This routine might be called either from user space or ISR @@ -197,6 +188,8 @@ void CustomRF95::startSend(MeshPacket *txp) void CustomRF95::loop() { + RH_RF95::loop(); + // It should never take us more than 30 secs to send a packet, if it does, we have a bug, FIXME, move most of this // into CustomRF95 uint32_t now = millis(); diff --git a/src/rf95/CustomRF95.h b/src/rf95/CustomRF95.h index a424f2493..ad7996480 100644 --- a/src/rf95/CustomRF95.h +++ b/src/rf95/CustomRF95.h @@ -52,5 +52,5 @@ class CustomRF95 : public RH_RF95, public RadioInterface void startSend(MeshPacket *txp); /// Return true if a higher pri task has woken - bool handleIdleISR(); + void handleIdleISR(); }; \ No newline at end of file diff --git a/src/rf95/RH_RF95.cpp b/src/rf95/RH_RF95.cpp index 9810428e3..14a2f1826 100644 --- a/src/rf95/RH_RF95.cpp +++ b/src/rf95/RH_RF95.cpp @@ -34,16 +34,12 @@ bool RH_RF95::init() if (!RHSPIDriver::init()) return false; - // Determine the interrupt number that corresponds to the interruptPin - int interruptNumber = digitalPinToInterrupt(_interruptPin); - if (interruptNumber == NOT_AN_INTERRUPT) - return false; #ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER interruptNumber = _interruptPin; #endif // Tell the low level SPI interface we will use SPI within this interrupt - spiUsingInterrupt(interruptNumber); + // spiUsingInterrupt(interruptNumber); // No way to check the device type :-( @@ -114,6 +110,17 @@ bool RH_RF95::init() return false; // Too many devices, not enough interrupt vectors } _deviceForInterrupt[_myInterruptIndex] = this; + + return enableInterrupt(); +} + +bool RH_RF95::enableInterrupt() +{ + // Determine the interrupt number that corresponds to the interruptPin + int interruptNumber = digitalPinToInterrupt(_interruptPin); + if (interruptNumber == NOT_AN_INTERRUPT) + return false; + if (_myInterruptIndex == 0) attachInterrupt(interruptNumber, isr0, ONHIGH); else if (_myInterruptIndex == 1) @@ -126,6 +133,12 @@ bool RH_RF95::init() return true; } +void RH_INTERRUPT_ATTR RH_RF95::disableInterrupt() +{ + int interruptNumber = digitalPinToInterrupt(_interruptPin); + detachInterrupt(interruptNumber); +} + void RH_RF95::prepareDeepSleep() { // Determine the interrupt number that corresponds to the interruptPin @@ -143,6 +156,13 @@ bool RH_RF95::isReceiving() RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0; } +void RH_INTERRUPT_ATTR RH_RF95::handleInterruptLevel0() +{ + disableInterrupt(); // Disable our interrupt until our helper thread can run (because the IRQ will remain asserted until we + // talk to it via SPI) + pendingInterrupt = true; +} + // C++ level interrupt handler for this instance // LORA is unusual in that it has several interrupt lines, and not a single, combined one. // On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly @@ -222,6 +242,16 @@ void RH_RF95::handleInterrupt() _cad = irq_flags & RH_RF95_CAD_DETECTED; setModeIdle(); } + + enableInterrupt(); // Let ISR run again +} + +void RH_RF95::loop() +{ + while (pendingInterrupt) { + pendingInterrupt = false; // If the flag was set, it is _guaranteed_ the ISR won't be running, because it masked itself + handleInterrupt(); + } } // These are low level functions that call the interrupt handler for the correct @@ -230,17 +260,17 @@ void RH_RF95::handleInterrupt() void RH_INTERRUPT_ATTR RH_RF95::isr0() { if (_deviceForInterrupt[0]) - _deviceForInterrupt[0]->handleInterrupt(); + _deviceForInterrupt[0]->handleInterruptLevel0(); } void RH_INTERRUPT_ATTR RH_RF95::isr1() { if (_deviceForInterrupt[1]) - _deviceForInterrupt[1]->handleInterrupt(); + _deviceForInterrupt[1]->handleInterruptLevel0(); } void RH_INTERRUPT_ATTR RH_RF95::isr2() { if (_deviceForInterrupt[2]) - _deviceForInterrupt[2]->handleInterrupt(); + _deviceForInterrupt[2]->handleInterruptLevel0(); } // Check whether the latest received message is complete and uncorrupted diff --git a/src/rf95/RH_RF95.h b/src/rf95/RH_RF95.h index 7a4a34a64..3e0776036 100644 --- a/src/rf95/RH_RF95.h +++ b/src/rf95/RH_RF95.h @@ -806,12 +806,17 @@ class RH_RF95 : public RHSPIDriver /// Return true if we are currently receiving a packet bool isReceiving(); + void loop(); // Perform idle processing + protected: /// This is a low level function to handle the interrupts for one instance of RH_RF95. /// Called automatically by isr*() /// Should not need to be called by user code. virtual void handleInterrupt(); + /// This is the only code called in ISR context, it just queues up our helper thread to run handleInterrupt(); + void RH_INTERRUPT_ATTR handleInterruptLevel0(); + /// Examine the revceive buffer to determine whether the message is for this node void validateRxBuf(); @@ -846,6 +851,11 @@ class RH_RF95 : public RHSPIDriver /// Index of next interrupt number to use in _deviceForInterrupt static uint8_t _interruptCount; + bool enableInterrupt(); // enable our IRQ + void disableInterrupt(); // disable our IRQ + + volatile bool pendingInterrupt = false; + /// The configured interrupt pin connected to this instance uint8_t _interruptPin; diff --git a/src/rf95/RadioInterface.cpp b/src/rf95/RadioInterface.cpp index bd8bb1310..d43922ffb 100644 --- a/src/rf95/RadioInterface.cpp +++ b/src/rf95/RadioInterface.cpp @@ -15,8 +15,8 @@ ErrorCode SimRadio::send(MeshPacket *p) return ERRNO_OK; } -void RadioInterface::deliverToReceiverISR(MeshPacket *p, BaseType_t *higherPriWoken) +void RadioInterface::deliverToReceiver(MeshPacket *p) { assert(rxDest); - assert(rxDest->enqueueFromISR(p, higherPriWoken)); // NOWAIT - fixme, if queue is full, delete older messages + assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages } \ No newline at end of file diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index 1172745fa..ec7dfce42 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -24,7 +24,7 @@ class RadioInterface /** * Enqueue a received packet for the registered receiver */ - void deliverToReceiverISR(MeshPacket *p, BaseType_t *higherPriWoken); + void deliverToReceiver(MeshPacket *p); public: /** pool is the pool we will alloc our rx packets from From 2419ebb04e87f6c8b81a3b51aa13077b662c6633 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 19 Apr 2020 08:33:59 -0700 Subject: [PATCH 039/197] 0.4.3 Fix #92: omg - for the last couple of weeks the official builds were all using US frequencies. This build fixes this (and makes the build system cleaner in general). If you are building your own builds in the IDE you'll need to start setting an environment variable called COUNTRY to your two letter country code (or leave unset to get US frequencies). See new comment in platformio.ini. --- bin/build-all.sh | 26 ++++++++++++++------------ bin/version.sh | 2 +- platformio.ini | 15 ++++++++++++--- src/MeshRadio.h | 5 ++++- src/configuration.h | 5 +++-- 5 files changed, 34 insertions(+), 19 deletions(-) diff --git a/bin/build-all.sh b/bin/build-all.sh index 2c52dd19a..2b31a3061 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -6,6 +6,10 @@ source bin/version.sh COUNTRIES="US EU433 EU865 CN JP" #COUNTRIES=US +#COUNTRIES=CN + +BOARDS="ttgo-lora32-v2 ttgo-lora32-v1 tbeam heltec" +#BOARDS=tbeam OUTDIR=release/latest @@ -24,23 +28,21 @@ function do_build { SRCBIN=.pio/build/$ENV_NAME/firmware.bin SRCELF=.pio/build/$ENV_NAME/firmware.elf rm -f $SRCBIN - pio run --environment $ENV_NAME # -v + + # The shell vars the build tool expects to find + export HW_VERSION="1.0-$COUNTRY" + export APP_VERSION=$VERSION + export COUNTRY + + pio run --jobs 4 --environment $ENV_NAME # -v cp $SRCBIN $OUTDIR/bins/firmware-$ENV_NAME-$COUNTRY-$VERSION.bin cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf } for COUNTRY in $COUNTRIES; do - - HWVERSTR="1.0-$COUNTRY" - COMMONOPTS="-DAPP_VERSION=$VERSION -DHW_VERSION_$COUNTRY -DHW_VERSION=$HWVERSTR -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -DAXP_DEBUG_PORT=Serial" - - export PLATFORMIO_BUILD_FLAGS="$COMMONOPTS" - - #do_build "tbeam0.7" - do_build "ttgo-lora32-v2" - do_build "ttgo-lora32-v1" - do_build "tbeam" - do_build "heltec" + for BOARD in $BOARDS; do + do_build $BOARD + done done # keep the bins in archive also diff --git a/bin/version.sh b/bin/version.sh index 703441d91..6733e32e6 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.4.2 \ No newline at end of file +export VERSION=0.4.3 \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index e9355233f..fbfb3a459 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,9 +12,14 @@ default_envs = tbeam [common] -; default to a US frequency range, change it as needed for your region and hardware (CN, JP, EU433, EU865) -hw_version = US +; common is not currently used +; REQUIRED environment variables - if not set the specified default will be sued +; The following environment variables must be set in the shell if you'd like to override them. +; They are used in this ini file as systenv.VARNAME, so in your shell do export "VARNAME=fish" +; HW_VERSION (default US) +; APP_VERSION (default emptystring) +; HW_VERSION (default emptystring) [env] platform = espressif32 @@ -26,7 +31,11 @@ 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 -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${common.hw_version} +build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os -Wl,-Map,.pio/build/output.map + -DAXP_DEBUG_PORT=Serial + -DHW_VERSION_${sysenv.COUNTRY} + -DAPP_VERSION=${sysenv.APP_VERSION} + -DHW_VERSION=${sysenv.HW_VERSION} ; not needed included in ttgo-t-beam board file ; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram diff --git a/src/MeshRadio.h b/src/MeshRadio.h index bea13d4f8..186af9f5e 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -55,7 +55,10 @@ #define CH_SPACING CH_SPACING_JP #define NUM_CHANNELS NUM_CHANNELS_JP #else -#error "HW_VERSION not set" +// HW version not set - assume US +#define CH0 CH0_US +#define CH_SPACING CH_SPACING_US +#define NUM_CHANNELS NUM_CHANNELS_US #endif /** diff --git a/src/configuration.h b/src/configuration.h index b35e4beec..4ef859aa3 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -31,8 +31,9 @@ along with this program. If not, see . // If app version is not specified we assume we are not being invoked by the build script #ifndef APP_VERSION -#define APP_VERSION 0.0.0 // this def normally comes from build-all.sh -#define HW_VERSION 1.0 - US // normally comes from build-all.sh and contains the region code +#error APP_VERSION, HW_VERSION, and HW_VERSION_countryname must be set by the build environment +//#define APP_VERSION 0.0.0 // this def normally comes from build-all.sh +//#define HW_VERSION 1.0 - US // normally comes from build-all.sh and contains the region code #endif // ----------------------------------------------------------------------------- From 9232dfcccf108b9487a59ebe12b11047b9e9fb3e Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 20 Apr 2020 18:03:13 -0700 Subject: [PATCH 040/197] WIP - add new baseclass for all api endpoints (serial, bluetooth, udp) https://github.com/meshtastic/Meshtastic-esp32/issues/69 --- proto | 2 +- src/PhoneAPI.cpp | 53 +++++++++++++++++++++++++++++++++ src/PhoneAPI.h | 76 ++++++++++++++++++++++++++++++++++++++++++++++++ src/mesh.pb.c | 3 ++ src/mesh.pb.h | 53 +++++++++++++++++++++++++++++++-- 5 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 src/PhoneAPI.cpp create mode 100644 src/PhoneAPI.h diff --git a/proto b/proto index e06645d8d..0cef75501 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit e06645d8db16b9e4f23e74a931b8d5cd07bcbe3c +Subproject commit 0cef75501578a2c4adf63da09fdc34db20b3d862 diff --git a/src/PhoneAPI.cpp b/src/PhoneAPI.cpp new file mode 100644 index 000000000..f9c2d9406 --- /dev/null +++ b/src/PhoneAPI.cpp @@ -0,0 +1,53 @@ +#include "PhoneAPI.h" +#include + +PhoneAPI::PhoneAPI() +{ + // Make sure that we never let our packets grow too large for one BLE packet + assert(FromRadio_size <= 512); + assert(ToRadio_size <= 512); +} + +/** + * Handle a ToRadio protobuf + */ +void PhoneAPI::handleToRadio(const char *buf, size_t len) +{ + // FIXME +} + +/** + * Get the next packet we want to send to the phone, or NULL if no such packet is available. + * + * We assume buf is at least FromRadio_size bytes long. + */ +bool PhoneAPI::getFromRadio(char *buf) +{ + return false; // FIXME +} + +/** + * Return true if we have data available to send to the phone + */ +bool PhoneAPI::available() +{ + return true; // FIXME +} + +// +// The following routines are only public for now - until the rev1 bluetooth API is removed +// + +void PhoneAPI::handleSetOwner(const User &o) {} + +void PhoneAPI::handleSetRadio(const RadioConfig &r) {} + +/** + * The client wants to start a new set of config reads + */ +void PhoneAPI::handleWantConfig(uint32_t nonce) {} + +/** + * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool + */ +void PhoneAPI::handleToRadioPacket(MeshPacket *p) {} \ No newline at end of file diff --git a/src/PhoneAPI.h b/src/PhoneAPI.h new file mode 100644 index 000000000..e27fab772 --- /dev/null +++ b/src/PhoneAPI.h @@ -0,0 +1,76 @@ +#pragma once + +#include "mesh-pb-constants.h" +#include "mesh.pb.h" +#include + +/** + * Provides our protobuf based API which phone/PC clients can use to talk to our device + * over UDP, bluetooth or serial. + * + * Eventually there should be once instance of this class for each live connection (because it has a bit of state + * for that connection) + */ +class PhoneAPI +{ + enum State { + STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config + STATE_SEND_MY_NODEINFO, + STATE_SEND_OWNER, + STATE_SEND_RADIO, + STATE_SEND_COMPLETE_ID, + STATE_SEND_PACKETS // send packets or debug strings + }; + + State state = STATE_SEND_NOTHING; + + /** + * Each packet sent to the phone has an incrementing count + */ + uint32_t fromRadioNum = 0; + + public: + PhoneAPI(); + + /** + * Handle a ToRadio protobuf + */ + void handleToRadio(const char *buf, size_t len); + + /** + * Get the next packet we want to send to the phone, or NULL if no such packet is available. + * + * We assume buf is at least FromRadio_size bytes long. + */ + bool getFromRadio(char *buf); + + /** + * Return true if we have data available to send to the phone + */ + bool available(); + + // + // The following routines are only public for now - until the rev1 bluetooth API is removed + // + + void handleSetOwner(const User &o); + void handleSetRadio(const RadioConfig &r); + + protected: + + /** + * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) + */ + void onNowHasData(uint32_t fromRadioNum) {} + + private: + /** + * The client wants to start a new set of config reads + */ + void handleWantConfig(uint32_t nonce); + + /** + * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool + */ + void handleToRadioPacket(MeshPacket *p); +}; diff --git a/src/mesh.pb.c b/src/mesh.pb.c index b4e21ebde..096f28896 100644 --- a/src/mesh.pb.c +++ b/src/mesh.pb.c @@ -42,6 +42,9 @@ PB_BIND(MyNodeInfo, MyNodeInfo, AUTO) PB_BIND(DeviceState, DeviceState, 4) +PB_BIND(DebugString, DebugString, 2) + + PB_BIND(FromRadio, FromRadio, 2) diff --git a/src/mesh.pb.h b/src/mesh.pb.h index dda10e710..67ca16227 100644 --- a/src/mesh.pb.h +++ b/src/mesh.pb.h @@ -49,6 +49,10 @@ typedef struct _Data { Data_payload_t payload; } Data; +typedef struct _DebugString { + char message[256]; +} DebugString; + typedef struct _MyNodeInfo { int32_t my_node_num; bool has_gps; @@ -150,6 +154,12 @@ typedef struct _FromRadio { pb_size_t which_variant; union { MeshPacket packet; + MyNodeInfo my_info; + NodeInfo node_info; + User owner; + RadioConfig radio; + DebugString debug_string; + uint32_t config_complete_id; } variant; } FromRadio; @@ -157,6 +167,9 @@ typedef struct _ToRadio { pb_size_t which_variant; union { MeshPacket packet; + uint32_t want_config_id; + RadioConfig set_radio; + User set_owner; } variant; } ToRadio; @@ -188,6 +201,7 @@ typedef struct _ToRadio { #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, false, MeshPacket_init_default, 0} +#define DebugString_init_default {""} #define FromRadio_init_default {0, 0, {MeshPacket_init_default}} #define ToRadio_init_default {0, {MeshPacket_init_default}} #define Position_init_zero {0, 0, 0, 0, 0} @@ -202,6 +216,7 @@ typedef struct _ToRadio { #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, false, MeshPacket_init_zero, 0} +#define DebugString_init_zero {""} #define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}} #define ToRadio_init_zero {0, {MeshPacket_init_zero}} @@ -213,6 +228,7 @@ typedef struct _ToRadio { #define ChannelSettings_name_tag 5 #define Data_typ_tag 1 #define Data_payload_tag 2 +#define DebugString_message_tag 1 #define MyNodeInfo_my_node_num_tag 1 #define MyNodeInfo_has_gps_tag 2 #define MyNodeInfo_num_channels_tag 3 @@ -269,8 +285,17 @@ typedef struct _ToRadio { #define DeviceState_version_tag 8 #define DeviceState_rx_text_message_tag 7 #define FromRadio_packet_tag 2 +#define FromRadio_my_info_tag 3 +#define FromRadio_node_info_tag 4 +#define FromRadio_owner_tag 5 +#define FromRadio_radio_tag 6 +#define FromRadio_debug_string_tag 7 +#define FromRadio_config_complete_id_tag 8 #define FromRadio_num_tag 1 #define ToRadio_packet_tag 1 +#define ToRadio_want_config_id_tag 100 +#define ToRadio_set_radio_tag 101 +#define ToRadio_set_owner_tag 102 /* Struct field encoding specification for nanopb */ #define Position_FIELDLIST(X, a) \ @@ -397,18 +422,39 @@ X(a, STATIC, SINGULAR, UINT32, version, 8) #define DeviceState_receive_queue_MSGTYPE MeshPacket #define DeviceState_rx_text_message_MSGTYPE MeshPacket +#define DebugString_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, STRING, message, 1) +#define DebugString_CALLBACK NULL +#define DebugString_DEFAULT NULL + #define FromRadio_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, num, 1) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 2) +X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 2) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,my_info,variant.my_info), 3) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,node_info,variant.node_info), 4) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,owner,variant.owner), 5) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,radio,variant.radio), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,debug_string,variant.debug_string), 7) \ +X(a, STATIC, ONEOF, UINT32, (variant,config_complete_id,variant.config_complete_id), 8) #define FromRadio_CALLBACK NULL #define FromRadio_DEFAULT NULL #define FromRadio_variant_packet_MSGTYPE MeshPacket +#define FromRadio_variant_my_info_MSGTYPE MyNodeInfo +#define FromRadio_variant_node_info_MSGTYPE NodeInfo +#define FromRadio_variant_owner_MSGTYPE User +#define FromRadio_variant_radio_MSGTYPE RadioConfig +#define FromRadio_variant_debug_string_MSGTYPE DebugString #define ToRadio_FIELDLIST(X, a) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 1) +X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 1) \ +X(a, STATIC, ONEOF, UINT32, (variant,want_config_id,variant.want_config_id), 100) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,set_radio,variant.set_radio), 101) \ +X(a, STATIC, ONEOF, MESSAGE, (variant,set_owner,variant.set_owner), 102) #define ToRadio_CALLBACK NULL #define ToRadio_DEFAULT NULL #define ToRadio_variant_packet_MSGTYPE MeshPacket +#define ToRadio_variant_set_radio_MSGTYPE RadioConfig +#define ToRadio_variant_set_owner_MSGTYPE User extern const pb_msgdesc_t Position_msg; extern const pb_msgdesc_t Data_msg; @@ -422,6 +468,7 @@ extern const pb_msgdesc_t RadioConfig_UserPreferences_msg; extern const pb_msgdesc_t NodeInfo_msg; extern const pb_msgdesc_t MyNodeInfo_msg; extern const pb_msgdesc_t DeviceState_msg; +extern const pb_msgdesc_t DebugString_msg; extern const pb_msgdesc_t FromRadio_msg; extern const pb_msgdesc_t ToRadio_msg; @@ -438,6 +485,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define NodeInfo_fields &NodeInfo_msg #define MyNodeInfo_fields &MyNodeInfo_msg #define DeviceState_fields &DeviceState_msg +#define DebugString_fields &DebugString_msg #define FromRadio_fields &FromRadio_msg #define ToRadio_fields &ToRadio_msg @@ -454,6 +502,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define NodeInfo_size 155 #define MyNodeInfo_size 85 #define DeviceState_size 19502 +#define DebugString_size 258 #define FromRadio_size 435 #define ToRadio_size 429 From 31f735ae1fb0e81150060a3effa4bcddc409dfae Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 20 Apr 2020 19:30:41 -0700 Subject: [PATCH 041/197] minor status update --- docs/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 65d616bce..e3bbff93b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -36,8 +36,9 @@ This software is 100% open source and developed by a group of hobbyist experimen # Updates -Note: Updates are happening almost daily, only major updates are listed below. For more details see our chat, github releases or the Android alpha tester emails. +Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum. +- 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon. - 03/03/2020 - 0.0.9 of the Android app and device code is released. Still an alpha but fairly functional. - 02/25/2020 - 0.0.4 of the Android app is released. This is a very early alpha, see below to join the alpha-testers group. - 02/23/2020 - 0.0.4 release. Still very bleeding edge but much closer to the final power management, a charged T-BEAM should run for many days with this load. If you'd like to try it, we'd love your feedback. Click [here](https://github.com/meshtastic/Meshtastic-esp32/blob/master/README.md) for instructions. From e40524baf049cd8fa39bf43c2c85fe4dabb9df26 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 22 Apr 2020 14:55:36 -0700 Subject: [PATCH 042/197] begin moving comms glue from the old crufty BLE code to the new cleaner PhoneAPI class --- proto | 2 +- src/MeshService.cpp | 53 +++++++----------- src/MeshService.h | 8 ++- src/PhoneAPI.cpp | 87 ++++++++++++++++++++++++++--- src/PhoneAPI.h | 28 ++++++++-- src/esp32/MeshBluetoothService.cpp | 90 +++++++++++++----------------- 6 files changed, 168 insertions(+), 100 deletions(-) diff --git a/proto b/proto index 0cef75501..083ba7931 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 0cef75501578a2c4adf63da09fdc34db20b3d862 +Subproject commit 083ba793108c34044e6abc8c94a5f250343b4f32 diff --git a/src/MeshService.cpp b/src/MeshService.cpp index 4331f939f..d82f46b55 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -214,46 +214,33 @@ void MeshService::reloadConfig() nodeDB.saveToDisk(); } -/// Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) -void MeshService::handleToRadio(std::string s) +/** + * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) + * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep a reference + */ +void MeshService::handleToRadio(MeshPacket &p) { - static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning + handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock - if (pb_decode_from_bytes((const uint8_t *)s.c_str(), s.length(), ToRadio_fields, &r)) { - switch (r.which_variant) { - case ToRadio_packet_tag: { - // If our phone is sending a position, see if we can use it to set our RTC - MeshPacket &p = r.variant.packet; - handleIncomingPosition(&p); // If it is a position packet, perhaps set our clock + if (p.from == 0) // If the phone didn't set a sending node ID, use ours + p.from = nodeDB.getNodeNum(); - if (p.from == 0) // If the phone didn't set a sending node ID, use ours - p.from = nodeDB.getNodeNum(); + if (p.id == 0) + p.id = generatePacketId(); // If the phone didn't supply one, then pick one - if (p.id == 0) - p.id = generatePacketId(); // If the phone didn't supply one, then pick one + p.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone + // (so we update our nodedb for the local node) - p.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone - // (so we update our nodedb for the local node) + // Send the packet into the mesh - // Send the packet into the mesh + sendToMesh(packetPool.allocCopy(p)); - sendToMesh(packetPool.allocCopy(p)); - - bool loopback = false; // if true send any packet the phone sends back itself (for testing) - if (loopback) { - // no need to copy anymore because handle from radio assumes it should _not_ delete - // packetPool.allocCopy(r.variant.packet); - handleFromRadio(&p); - // handleFromRadio will tell the phone a new packet arrived - } - break; - } - default: - DEBUG_MSG("Error: unexpected ToRadio variant\n"); - break; - } - } else { - DEBUG_MSG("Error: ignoring malformed toradio\n"); + bool loopback = false; // if true send any packet the phone sends back itself (for testing) + if (loopback) { + // no need to copy anymore because handle from radio assumes it should _not_ delete + // packetPool.allocCopy(r.variant.packet); + handleFromRadio(&p); + // handleFromRadio will tell the phone a new packet arrived } } diff --git a/src/MeshService.h b/src/MeshService.h index f1e45d0bb..f3328225b 100644 --- a/src/MeshService.h +++ b/src/MeshService.h @@ -54,8 +54,12 @@ class MeshService /// Allows the bluetooth handler to free packets after they have been sent void releaseToPool(MeshPacket *p) { packetPool.release(p); } - /// Given a ToRadio buffer (from bluetooth) parse it and properly handle it (setup radio, owner or send packet into the mesh) - void handleToRadio(std::string s); + /** + * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) + * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep + * a reference + */ + void handleToRadio(MeshPacket &p); /// The radioConfig object just changed, call this to force the hw to change to the new settings void reloadConfig(); diff --git a/src/PhoneAPI.cpp b/src/PhoneAPI.cpp index f9c2d9406..66524b2cf 100644 --- a/src/PhoneAPI.cpp +++ b/src/PhoneAPI.cpp @@ -1,4 +1,6 @@ #include "PhoneAPI.h" +#include "MeshService.h" +#include "NodeDB.h" #include PhoneAPI::PhoneAPI() @@ -8,12 +10,31 @@ PhoneAPI::PhoneAPI() assert(ToRadio_size <= 512); } +void PhoneAPI::init() +{ + observe(&service.fromNumChanged); +} + /** * Handle a ToRadio protobuf */ -void PhoneAPI::handleToRadio(const char *buf, size_t len) +void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) { - // FIXME + if (pb_decode_from_bytes(buf, bufLength, ToRadio_fields, &toRadioScratch)) { + switch (toRadioScratch.which_variant) { + case ToRadio_packet_tag: { + // If our phone is sending a position, see if we can use it to set our RTC + MeshPacket &p = toRadioScratch.variant.packet; + service.handleToRadio(p); + break; + } + default: + DEBUG_MSG("Error: unexpected ToRadio variant\n"); + break; + } + } else { + DEBUG_MSG("Error: ignoring malformed toradio\n"); + } } /** @@ -21,9 +42,28 @@ void PhoneAPI::handleToRadio(const char *buf, size_t len) * * We assume buf is at least FromRadio_size bytes long. */ -bool PhoneAPI::getFromRadio(char *buf) +size_t PhoneAPI::getFromRadio(uint8_t *buf) { - return false; // FIXME + if (!available()) + return false; + + // Do we have a message from the mesh? + if (packetForPhone) { + // Encapsulate as a FromRadio packet + memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); + fromRadioScratch.which_variant = FromRadio_packet_tag; + fromRadioScratch.variant.packet = *packetForPhone; + + size_t numbytes = pb_encode_to_bytes(buf, sizeof(FromRadio_size), FromRadio_fields, &fromRadioScratch); + DEBUG_MSG("delivering toPhone packet to phone %d bytes\n", numbytes); + + service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore + packetForPhone = NULL; + return numbytes; + } + + DEBUG_MSG("toPhone queue is empty\n"); + return 0; } /** @@ -31,6 +71,8 @@ bool PhoneAPI::getFromRadio(char *buf) */ bool PhoneAPI::available() { + packetForPhone = service.getForPhone(); + return true; // FIXME } @@ -38,9 +80,33 @@ bool PhoneAPI::available() // The following routines are only public for now - until the rev1 bluetooth API is removed // -void PhoneAPI::handleSetOwner(const User &o) {} +void PhoneAPI::handleSetOwner(const User &o) +{ + int changed = 0; -void PhoneAPI::handleSetRadio(const RadioConfig &r) {} + if (*o.long_name) { + changed |= strcmp(owner.long_name, o.long_name); + strcpy(owner.long_name, o.long_name); + } + if (*o.short_name) { + changed |= strcmp(owner.short_name, o.short_name); + strcpy(owner.short_name, o.short_name); + } + if (*o.id) { + changed |= strcmp(owner.id, o.id); + strcpy(owner.id, o.id); + } + + if (changed) // If nothing really changed, don't broadcast on the network or write to flash + service.reloadOwner(); +} + +void PhoneAPI::handleSetRadio(const RadioConfig &r) +{ + radioConfig = r; + + service.reloadConfig(); +} /** * The client wants to start a new set of config reads @@ -50,4 +116,11 @@ void PhoneAPI::handleWantConfig(uint32_t nonce) {} /** * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool */ -void PhoneAPI::handleToRadioPacket(MeshPacket *p) {} \ No newline at end of file +void PhoneAPI::handleToRadioPacket(MeshPacket *p) {} + +/// If the mesh service tells us fromNum has changed, tell the phone +int PhoneAPI::onNotify(uint32_t newValue) +{ + onNowHasData(newValue); + return 0; +} \ No newline at end of file diff --git a/src/PhoneAPI.h b/src/PhoneAPI.h index e27fab772..d8c129cce 100644 --- a/src/PhoneAPI.h +++ b/src/PhoneAPI.h @@ -1,5 +1,6 @@ #pragma once +#include "Observer.h" #include "mesh-pb-constants.h" #include "mesh.pb.h" #include @@ -8,10 +9,13 @@ * Provides our protobuf based API which phone/PC clients can use to talk to our device * over UDP, bluetooth or serial. * + * Subclass to customize behavior for particular type of transport (BLE, UDP, TCP, serial) + * * Eventually there should be once instance of this class for each live connection (because it has a bit of state * for that connection) */ class PhoneAPI + : public Observer // FIXME, we shouldn't be inheriting from Observer, instead use CallbackObserver as a member { enum State { STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config @@ -27,22 +31,34 @@ class PhoneAPI /** * Each packet sent to the phone has an incrementing count */ - uint32_t fromRadioNum = 0; + uint32_t fromRadioNum = 0; + + /// We temporarily keep the packet here between the call to available and getFromRadio + MeshPacket *packetForPhone = NULL; + + /// Our fromradio packet while it is being assembled + FromRadio fromRadioScratch; + + ToRadio toRadioScratch; // this is a static scratch object, any data must be copied elsewhere before returning public: PhoneAPI(); + /// Do late init that can't happen at constructor time + void init(); + /** * Handle a ToRadio protobuf */ - void handleToRadio(const char *buf, size_t len); + void handleToRadio(const uint8_t *buf, size_t len); /** - * Get the next packet we want to send to the phone, or NULL if no such packet is available. + * Get the next packet we want to send to the phone * * We assume buf is at least FromRadio_size bytes long. + * Returns number of bytes in the FromRadio packet (or 0 if no packet available) */ - bool getFromRadio(char *buf); + size_t getFromRadio(uint8_t *buf); /** * Return true if we have data available to send to the phone @@ -57,7 +73,6 @@ class PhoneAPI void handleSetRadio(const RadioConfig &r); protected: - /** * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) */ @@ -73,4 +88,7 @@ class PhoneAPI * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool */ void handleToRadioPacket(MeshPacket *p); + + /// If the mesh service tells us fromNum has changed, tell the phone + virtual int onNotify(uint32_t newValue); }; diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index 086f0afe6..cadf60121 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -6,19 +6,41 @@ #include #include "CallbackCharacteristic.h" +#include "GPS.h" #include "MeshService.h" #include "NodeDB.h" +#include "PhoneAPI.h" #include "PowerFSM.h" #include "configuration.h" #include "mesh-pb-constants.h" #include "mesh.pb.h" -#include "GPS.h" - // This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in // proccess at once static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)]; +static CallbackCharacteristic *meshFromNumCharacteristic; + +BLEService *meshService; + +class BluetoothPhoneAPI : public PhoneAPI +{ + /** + * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) + */ + virtual void onNowHasData(uint32_t fromRadioNum) + { + PhoneAPI::onNowHasData(fromRadioNum); + + if (meshFromNumCharacteristic) { // this ptr might change from sleep to sleep, or even be null + meshFromNumCharacteristic->setValue(fromRadioNum); + meshFromNumCharacteristic->notify(); + } + } +}; + +BluetoothPhoneAPI *bluetoothPhoneAPI; + class ProtobufCharacteristic : public CallbackCharacteristic { const pb_msgdesc_t *fields; @@ -114,7 +136,7 @@ class RadioCharacteristic : public ProtobufCharacteristic { DEBUG_MSG("Writing radio config\n"); ProtobufCharacteristic::onWrite(c); - service.reloadConfig(); + bluetoothPhoneAPI->handleSetRadio(radioConfig); } }; @@ -135,23 +157,7 @@ class OwnerCharacteristic : public ProtobufCharacteristic static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr if (writeToDest(c, &o)) { - int changed = 0; - - if (*o.long_name) { - changed |= strcmp(owner.long_name, o.long_name); - strcpy(owner.long_name, o.long_name); - } - if (*o.short_name) { - changed |= strcmp(owner.short_name, o.short_name); - strcpy(owner.short_name, o.short_name); - } - if (*o.id) { - changed |= strcmp(owner.id, o.id); - strcpy(owner.id, o.id); - } - - if (changed) // If nothing really changed, don't broadcast on the network or write to flash - service.reloadOwner(); + bluetoothPhoneAPI->handleSetOwner(o); } } }; @@ -166,7 +172,7 @@ class ToRadioCharacteristic : public CallbackCharacteristic BLEKeepAliveCallbacks::onWrite(c); DEBUG_MSG("Got on write\n"); - service.handleToRadio(c->getValue()); + bluetoothPhoneAPI->handleToRadio(c->getData(), c->getValue().length()); } }; @@ -180,31 +186,17 @@ class FromRadioCharacteristic : public CallbackCharacteristic void onRead(BLECharacteristic *c) { BLEKeepAliveCallbacks::onRead(c); - MeshPacket *mp = service.getForPhone(); + size_t numBytes = bluetoothPhoneAPI->getFromRadio(trBytes); // Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue // or make empty if the queue is empty - if (!mp) { - DEBUG_MSG("toPhone queue is empty\n"); - c->setValue((uint8_t *)"", 0); - } else { - static FromRadio fRadio; - - // Encapsulate as a FromRadio packet - memset(&fRadio, 0, sizeof(fRadio)); - fRadio.which_variant = FromRadio_packet_tag; - fRadio.variant.packet = *mp; - - size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), FromRadio_fields, &fRadio); - DEBUG_MSG("delivering toPhone packet to phone %d bytes\n", numbytes); - c->setValue(trBytes, numbytes); - - service.releaseToPool(mp); // we just copied the bytes, so don't need this buffer anymore + if (numBytes) { + c->setValue(trBytes, numBytes); } } }; -class FromNumCharacteristic : public CallbackCharacteristic, public Observer +class FromNumCharacteristic : public CallbackCharacteristic { public: FromNumCharacteristic() @@ -212,7 +204,7 @@ class FromNumCharacteristic : public CallbackCharacteristic, public Observerinit(); + } + // Create the BLE Service, we need more than the default of 15 handles BLEService *service = server->createService(BLEUUID("6ba1b218-15a8-461f-9fa8-5dcae273eafd"), 30, 0); From bd77d47215acc4173ddd88ca7c3470b40c2f0f89 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 22 Apr 2020 14:58:35 -0700 Subject: [PATCH 043/197] change serial baud rate to 921600 --- bin/start-terminal0.sh | 2 +- bin/start-terminal1.sh | 2 +- platformio.ini | 2 +- src/configuration.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/start-terminal0.sh b/bin/start-terminal0.sh index 895fb94b5..3ad4e7822 100755 --- a/bin/start-terminal0.sh +++ b/bin/start-terminal0.sh @@ -1 +1 @@ -pio device monitor -b 115200 +pio device monitor -b 921600 diff --git a/bin/start-terminal1.sh b/bin/start-terminal1.sh index c5236bbe3..3433501ea 100755 --- a/bin/start-terminal1.sh +++ b/bin/start-terminal1.sh @@ -1 +1 @@ -pio device monitor -p /dev/ttyUSB1 -b 115200 +pio device monitor -p /dev/ttyUSB1 -b 921600 diff --git a/platformio.ini b/platformio.ini index fbfb3a459..d4b105129 100644 --- a/platformio.ini +++ b/platformio.ini @@ -51,7 +51,7 @@ build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os -Wl,-Map,.pio ; the default is esptool ; upload_protocol = esp-prog -monitor_speed = 115200 +monitor_speed = 921600 # debug_tool = esp-prog # debug_port = /dev/ttyACM0 diff --git a/src/configuration.h b/src/configuration.h index 4ef859aa3..733e40a76 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -47,7 +47,7 @@ along with this program. If not, see . #endif #define DEBUG_PORT Serial // Serial debug port -#define SERIAL_BAUD 115200 // Serial debug baud rate +#define SERIAL_BAUD 921600 // Serial debug baud rate #define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found From 169d85d0fa2a1506c2f106da14a36f84b8ae2685 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 22 Apr 2020 15:13:05 -0700 Subject: [PATCH 044/197] handle the new set_owner and set_radio messages --- src/PhoneAPI.cpp | 16 ++++++++++++++++ src/PhoneAPI.h | 3 +++ 2 files changed, 19 insertions(+) diff --git a/src/PhoneAPI.cpp b/src/PhoneAPI.cpp index 66524b2cf..18a05228e 100644 --- a/src/PhoneAPI.cpp +++ b/src/PhoneAPI.cpp @@ -28,6 +28,22 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) service.handleToRadio(p); break; } + case ToRadio_want_config_id_tag: + config_nonce = toRadioScratch.variant.want_config_id; + DEBUG_MSG("Client wants config, nonce=%u\n", config_nonce); + state = STATE_SEND_MY_NODEINFO; + break; + + case ToRadio_set_owner_tag: + DEBUG_MSG("Client is setting owner\n"); + handleSetOwner(toRadioScratch.variant.set_owner); + break; + + case ToRadio_set_radio_tag: + DEBUG_MSG("Client is setting radio\n"); + handleSetRadio(toRadioScratch.variant.set_radio); + break; + default: DEBUG_MSG("Error: unexpected ToRadio variant\n"); break; diff --git a/src/PhoneAPI.h b/src/PhoneAPI.h index d8c129cce..3439730fa 100644 --- a/src/PhoneAPI.h +++ b/src/PhoneAPI.h @@ -41,6 +41,9 @@ class PhoneAPI ToRadio toRadioScratch; // this is a static scratch object, any data must be copied elsewhere before returning + /// Use to ensure that clients don't get confused about old messages from the radio + uint32_t config_nonce = 0; + public: PhoneAPI(); From 562b227c737dc345624037657f409d4f3072b6a1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 22 Apr 2020 16:03:54 -0700 Subject: [PATCH 045/197] new API now seems fully implemented - now on to testing. #69 --- src/PhoneAPI.cpp | 138 ++++++++++++++++++++++++++++++++++++++++------- src/PhoneAPI.h | 15 +++--- src/error.h | 2 +- 3 files changed, 127 insertions(+), 28 deletions(-) diff --git a/src/PhoneAPI.cpp b/src/PhoneAPI.cpp index 18a05228e..5649d9165 100644 --- a/src/PhoneAPI.cpp +++ b/src/PhoneAPI.cpp @@ -31,7 +31,11 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) case ToRadio_want_config_id_tag: config_nonce = toRadioScratch.variant.want_config_id; DEBUG_MSG("Client wants config, nonce=%u\n", config_nonce); - state = STATE_SEND_MY_NODEINFO; + state = STATE_SEND_MY_INFO; + + DEBUG_MSG("Reset nodeinfo read pointer\n"); + nodeDB.resetReadPointer(); // FIXME, this read pointer should be moved out of nodeDB and into this class - because + // this will break once we have multiple instances of PhoneAPI running independently break; case ToRadio_set_owner_tag: @@ -57,28 +61,97 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) * Get the next packet we want to send to the phone, or NULL if no such packet is available. * * We assume buf is at least FromRadio_size bytes long. + * + * Our sending states progress in the following sequence: + * STATE_SEND_MY_INFO, // send our my info record + STATE_SEND_RADIO, + STATE_SEND_OWNER, + STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client + STATE_SEND_COMPLETE_ID, + STATE_SEND_PACKETS // send packets or debug strings */ size_t PhoneAPI::getFromRadio(uint8_t *buf) { if (!available()) return false; + // In case we send a FromRadio packet + memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); + + // Advance states as needed + switch (state) { + case STATE_SEND_NOTHING: + break; + + case STATE_SEND_MY_INFO: + fromRadioScratch.which_variant = FromRadio_my_info_tag; + fromRadioScratch.variant.my_info = myNodeInfo; + state = STATE_SEND_RADIO; + break; + + case STATE_SEND_RADIO: + fromRadioScratch.which_variant = FromRadio_radio_tag; + fromRadioScratch.variant.radio = radioConfig; + state = STATE_SEND_OWNER; + break; + + case STATE_SEND_OWNER: + fromRadioScratch.which_variant = FromRadio_owner_tag; + fromRadioScratch.variant.owner = owner; + state = STATE_SEND_NODEINFO; + break; + + case STATE_SEND_NODEINFO: { + const NodeInfo *info = nodeDB.readNextInfo(); + + if (info) { + DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id, + info->user.long_name); + fromRadioScratch.which_variant = FromRadio_node_info_tag; + fromRadioScratch.variant.node_info = *info; + // Stay in current state until done sending nodeinfos + } else { + DEBUG_MSG("Done sending nodeinfos\n"); + state = STATE_SEND_COMPLETE_ID; + // Go ahead and send that ID right now + return getFromRadio(buf); + } + break; + } + + case STATE_SEND_COMPLETE_ID: + fromRadioScratch.which_variant = FromRadio_config_complete_id_tag; + fromRadioScratch.variant.config_complete_id = config_nonce; + config_nonce = 0; + state = STATE_SEND_PACKETS; + break; + + case STATE_LEGACY: // Treat as the same as send packets + case STATE_SEND_PACKETS: + // Do we have a message from the mesh? + if (packetForPhone) { + // Encapsulate as a FromRadio packet + fromRadioScratch.which_variant = FromRadio_packet_tag; + fromRadioScratch.variant.packet = *packetForPhone; + + service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore + packetForPhone = NULL; + } + break; + + default: + assert(0); // unexpected state - FIXME, make an error code and reboot + } + // Do we have a message from the mesh? - if (packetForPhone) { + if (fromRadioScratch.which_variant != 0) { // Encapsulate as a FromRadio packet - memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); - fromRadioScratch.which_variant = FromRadio_packet_tag; - fromRadioScratch.variant.packet = *packetForPhone; - size_t numbytes = pb_encode_to_bytes(buf, sizeof(FromRadio_size), FromRadio_fields, &fromRadioScratch); - DEBUG_MSG("delivering toPhone packet to phone %d bytes\n", numbytes); - - service.releaseToPool(packetForPhone); // we just copied the bytes, so don't need this buffer anymore - packetForPhone = NULL; + DEBUG_MSG("delivering toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_variant, numbytes); return numbytes; } - DEBUG_MSG("toPhone queue is empty\n"); + DEBUG_MSG("no FromRadio packet available\n"); return 0; } @@ -87,9 +160,37 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) */ bool PhoneAPI::available() { - packetForPhone = service.getForPhone(); + switch (state) { + case STATE_SEND_NOTHING: + return false; - return true; // FIXME + case STATE_SEND_MY_INFO: + return true; + + case STATE_SEND_NODEINFO: + return true; + + case STATE_SEND_OWNER: + return true; + + case STATE_SEND_RADIO: + return true; + + case STATE_SEND_COMPLETE_ID: + return true; + + case STATE_LEGACY: // Treat as the same as send packets + case STATE_SEND_PACKETS: + // Try to pull a new packet from the service (if we haven't already) + if (!packetForPhone) + packetForPhone = service.getForPhone(); + return !!packetForPhone; + + default: + assert(0); // unexpected state - FIXME, make an error code and reboot + } + + return false; } // @@ -124,10 +225,6 @@ void PhoneAPI::handleSetRadio(const RadioConfig &r) service.reloadConfig(); } -/** - * The client wants to start a new set of config reads - */ -void PhoneAPI::handleWantConfig(uint32_t nonce) {} /** * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool @@ -137,6 +234,11 @@ void PhoneAPI::handleToRadioPacket(MeshPacket *p) {} /// If the mesh service tells us fromNum has changed, tell the phone int PhoneAPI::onNotify(uint32_t newValue) { - onNowHasData(newValue); + if (state == STATE_SEND_PACKETS || state == STATE_LEGACY) { + DEBUG_MSG("Telling client we have new packets %u\n", newValue); + onNowHasData(newValue); + } else + DEBUG_MSG("(Client not yet interested in packets)\n"); + return 0; } \ No newline at end of file diff --git a/src/PhoneAPI.h b/src/PhoneAPI.h index 3439730fa..8f9af78df 100644 --- a/src/PhoneAPI.h +++ b/src/PhoneAPI.h @@ -18,15 +18,17 @@ class PhoneAPI : public Observer // FIXME, we shouldn't be inheriting from Observer, instead use CallbackObserver as a member { enum State { - STATE_SEND_NOTHING, // Initial state, don't send anything until the client starts asking for config - STATE_SEND_MY_NODEINFO, - STATE_SEND_OWNER, + STATE_LEGACY, // Temporary default state - until Android apps are all updated, uses the old BLE API + STATE_SEND_NOTHING, // (Eventual) Initial state, don't send anything until the client starts asking for config + STATE_SEND_MY_INFO, // send our my info record STATE_SEND_RADIO, + STATE_SEND_OWNER, + STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings }; - State state = STATE_SEND_NOTHING; + State state = STATE_LEGACY; /** * Each packet sent to the phone has an incrementing count @@ -82,11 +84,6 @@ class PhoneAPI void onNowHasData(uint32_t fromRadioNum) {} private: - /** - * The client wants to start a new set of config reads - */ - void handleWantConfig(uint32_t nonce); - /** * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool */ diff --git a/src/error.h b/src/error.h index 4fae3825b..6adcc0ecb 100644 --- a/src/error.h +++ b/src/error.h @@ -3,7 +3,7 @@ #include /// Error codes for critical error -enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio }; +enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio, ErrUnspecified }; /// Record an error that should be reported via analytics void recordCriticalError(CriticalErrorCode code, uint32_t address = 0); From c67b53b96993eeaa4b3910deecac7a122f551f5a Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 10:30:14 -0700 Subject: [PATCH 046/197] remove owner from ToRadio --- proto | 2 +- src/PhoneAPI.cpp | 20 ++++++-------------- src/PhoneAPI.h | 8 ++++++-- src/mesh.pb.h | 4 ---- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/proto b/proto index 083ba7931..79b2cf728 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 083ba793108c34044e6abc8c94a5f250343b4f32 +Subproject commit 79b2cf728c08007284542b32d9d075d01e8153d8 diff --git a/src/PhoneAPI.cpp b/src/PhoneAPI.cpp index 5649d9165..4776cd969 100644 --- a/src/PhoneAPI.cpp +++ b/src/PhoneAPI.cpp @@ -34,6 +34,7 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) state = STATE_SEND_MY_INFO; DEBUG_MSG("Reset nodeinfo read pointer\n"); + nodeInfoForPhone = NULL; // Don't keep returning old nodeinfos nodeDB.resetReadPointer(); // FIXME, this read pointer should be moved out of nodeDB and into this class - because // this will break once we have multiple instances of PhoneAPI running independently break; @@ -62,10 +63,9 @@ void PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) * * We assume buf is at least FromRadio_size bytes long. * - * Our sending states progress in the following sequence: + * Our sending states progress in the following sequence (the client app ASSUMES THIS SEQUENCE, DO NOT CHANGE IT): * STATE_SEND_MY_INFO, // send our my info record STATE_SEND_RADIO, - STATE_SEND_OWNER, STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings @@ -92,17 +92,11 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) case STATE_SEND_RADIO: fromRadioScratch.which_variant = FromRadio_radio_tag; fromRadioScratch.variant.radio = radioConfig; - state = STATE_SEND_OWNER; - break; - - case STATE_SEND_OWNER: - fromRadioScratch.which_variant = FromRadio_owner_tag; - fromRadioScratch.variant.owner = owner; state = STATE_SEND_NODEINFO; break; case STATE_SEND_NODEINFO: { - const NodeInfo *info = nodeDB.readNextInfo(); + const NodeInfo *info = nodeInfoForPhone; if (info) { DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id, @@ -168,10 +162,9 @@ bool PhoneAPI::available() return true; case STATE_SEND_NODEINFO: - return true; - - case STATE_SEND_OWNER: - return true; + if (!nodeInfoForPhone) + nodeInfoForPhone = nodeDB.readNextInfo(); + return true; // Always say we have something, because we might need to advance our state machine case STATE_SEND_RADIO: return true; @@ -225,7 +218,6 @@ void PhoneAPI::handleSetRadio(const RadioConfig &r) service.reloadConfig(); } - /** * Handle a packet that the phone wants us to send. It is our responsibility to free the packet to the pool */ diff --git a/src/PhoneAPI.h b/src/PhoneAPI.h index 8f9af78df..56eac067d 100644 --- a/src/PhoneAPI.h +++ b/src/PhoneAPI.h @@ -22,7 +22,7 @@ class PhoneAPI STATE_SEND_NOTHING, // (Eventual) Initial state, don't send anything until the client starts asking for config STATE_SEND_MY_INFO, // send our my info record STATE_SEND_RADIO, - STATE_SEND_OWNER, + // STATE_SEND_OWNER, no need to send Owner specially, it is just part of the nodedb STATE_SEND_NODEINFO, // states progress in this order as the device sends to to the client STATE_SEND_COMPLETE_ID, STATE_SEND_PACKETS // send packets or debug strings @@ -35,9 +35,13 @@ class PhoneAPI */ uint32_t fromRadioNum = 0; - /// We temporarily keep the packet here between the call to available and getFromRadio + /// We temporarily keep the packet here between the call to available and getFromRadio. We will free it after the phone + /// downloads it MeshPacket *packetForPhone = NULL; + /// We temporarily keep the nodeInfo here between the call to available and getFromRadio + const NodeInfo *nodeInfoForPhone = NULL; + /// Our fromradio packet while it is being assembled FromRadio fromRadioScratch; diff --git a/src/mesh.pb.h b/src/mesh.pb.h index 67ca16227..f18b5c4f4 100644 --- a/src/mesh.pb.h +++ b/src/mesh.pb.h @@ -156,7 +156,6 @@ typedef struct _FromRadio { MeshPacket packet; MyNodeInfo my_info; NodeInfo node_info; - User owner; RadioConfig radio; DebugString debug_string; uint32_t config_complete_id; @@ -287,7 +286,6 @@ typedef struct _ToRadio { #define FromRadio_packet_tag 2 #define FromRadio_my_info_tag 3 #define FromRadio_node_info_tag 4 -#define FromRadio_owner_tag 5 #define FromRadio_radio_tag 6 #define FromRadio_debug_string_tag 7 #define FromRadio_config_complete_id_tag 8 @@ -432,7 +430,6 @@ X(a, STATIC, SINGULAR, UINT32, num, 1) \ X(a, STATIC, ONEOF, MESSAGE, (variant,packet,variant.packet), 2) \ X(a, STATIC, ONEOF, MESSAGE, (variant,my_info,variant.my_info), 3) \ X(a, STATIC, ONEOF, MESSAGE, (variant,node_info,variant.node_info), 4) \ -X(a, STATIC, ONEOF, MESSAGE, (variant,owner,variant.owner), 5) \ X(a, STATIC, ONEOF, MESSAGE, (variant,radio,variant.radio), 6) \ X(a, STATIC, ONEOF, MESSAGE, (variant,debug_string,variant.debug_string), 7) \ X(a, STATIC, ONEOF, UINT32, (variant,config_complete_id,variant.config_complete_id), 8) @@ -441,7 +438,6 @@ X(a, STATIC, ONEOF, UINT32, (variant,config_complete_id,variant.config_co #define FromRadio_variant_packet_MSGTYPE MeshPacket #define FromRadio_variant_my_info_MSGTYPE MyNodeInfo #define FromRadio_variant_node_info_MSGTYPE NodeInfo -#define FromRadio_variant_owner_MSGTYPE User #define FromRadio_variant_radio_MSGTYPE RadioConfig #define FromRadio_variant_debug_string_MSGTYPE DebugString From 3673f95fe573d3992511c5dce2313b9d39294fc6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 11:02:14 -0700 Subject: [PATCH 047/197] woot! using new BLE api approximately works for reading --- lib/BluetoothOTA/src/BluetoothUtil.cpp | 362 ++++++++++++------------- src/PhoneAPI.cpp | 6 +- src/esp32/MeshBluetoothService.cpp | 2 + 3 files changed, 184 insertions(+), 186 deletions(-) diff --git a/lib/BluetoothOTA/src/BluetoothUtil.cpp b/lib/BluetoothOTA/src/BluetoothUtil.cpp index 682db0b9e..0da8df85d 100644 --- a/lib/BluetoothOTA/src/BluetoothUtil.cpp +++ b/lib/BluetoothOTA/src/BluetoothUtil.cpp @@ -1,70 +1,68 @@ #include "BluetoothUtil.h" #include "BluetoothSoftwareUpdate.h" -#include -#include -#include -#include #include "configuration.h" +#include +#include +#include +#include SimpleAllocator btPool; /** * Create standard device info service **/ -BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion = "") +BLEService *createDeviceInfomationService(BLEServer *server, std::string hwVendor, std::string swVersion, + std::string hwVersion = "") { - BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC)); + BLEService *deviceInfoService = server->createService(BLEUUID((uint16_t)ESP_GATT_UUID_DEVICE_INFO_SVC)); - BLECharacteristic *swC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); - BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ); - // BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *swC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *mfC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_MANU_NAME), BLECharacteristic::PROPERTY_READ); + // BLECharacteristic SerialNumberCharacteristic(BLEUUID((uint16_t) ESP_GATT_UUID_SERIAL_NUMBER_STR), + // BLECharacteristic::PROPERTY_READ); - /* - * Mandatory characteristic for device info service? - - BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, BLECharacteristic::PROPERTY_READ); + /* + * Mandatory characteristic for device info service? - uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version; - uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version }; - m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); - */ - swC->setValue(swVersion); - deviceInfoService->addCharacteristic(addBLECharacteristic(swC)); - mfC->setValue(hwVendor); - deviceInfoService->addCharacteristic(addBLECharacteristic(mfC)); - if (!hwVersion.empty()) - { - BLECharacteristic *hwvC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); - hwvC->setValue(hwVersion); - deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC)); - } - //SerialNumberCharacteristic.setValue("FIXME"); - //deviceInfoService->addCharacteristic(&SerialNumberCharacteristic); + BLECharacteristic *m_pnpCharacteristic = m_deviceInfoService->createCharacteristic(ESP_GATT_UUID_PNP_ID, + BLECharacteristic::PROPERTY_READ); - // m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, BLECharacteristic::PROPERTY_READ); - // m_manufacturerCharacteristic->setValue(name); + uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version; + uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> + 8), (uint8_t) version }; m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); + */ + swC->setValue(swVersion); + deviceInfoService->addCharacteristic(addBLECharacteristic(swC)); + mfC->setValue(hwVendor); + deviceInfoService->addCharacteristic(addBLECharacteristic(mfC)); + if (!hwVersion.empty()) { + BLECharacteristic *hwvC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + hwvC->setValue(hwVersion); + deviceInfoService->addCharacteristic(addBLECharacteristic(hwvC)); + } + // SerialNumberCharacteristic.setValue("FIXME"); + // deviceInfoService->addCharacteristic(&SerialNumberCharacteristic); - /* add these later? - ESP_GATT_UUID_SYSTEM_ID - */ + // m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, + // BLECharacteristic::PROPERTY_READ); m_manufacturerCharacteristic->setValue(name); - // caller must call service->start(); - return deviceInfoService; + /* add these later? + ESP_GATT_UUID_SYSTEM_ID + */ + + // caller must call service->start(); + return deviceInfoService; } bool _BLEClientConnected = false; class MyServerCallbacks : public BLEServerCallbacks { - void onConnect(BLEServer *pServer) - { - _BLEClientConnected = true; - }; + void onConnect(BLEServer *pServer) { _BLEClientConnected = true; }; - void onDisconnect(BLEServer *pServer) - { - _BLEClientConnected = false; - } + void onDisconnect(BLEServer *pServer) { _BLEClientConnected = false; } }; #define MAX_DESCRIPTORS 32 @@ -78,34 +76,34 @@ static size_t numDescs; /// Add a characteristic that we will delete when we restart BLECharacteristic *addBLECharacteristic(BLECharacteristic *c) { - assert(numChars < MAX_CHARACTERISTICS); - chars[numChars++] = c; - return c; + assert(numChars < MAX_CHARACTERISTICS); + chars[numChars++] = c; + return c; } /// Add a characteristic that we will delete when we restart BLEDescriptor *addBLEDescriptor(BLEDescriptor *c) { - assert(numDescs < MAX_DESCRIPTORS); - descs[numDescs++] = c; + assert(numDescs < MAX_DESCRIPTORS); + descs[numDescs++] = c; - return c; + return c; } // Help routine to add a description to any BLECharacteristic and add it to the service // We default to require an encrypted BOND for all these these characterstics void addWithDesc(BLEService *service, BLECharacteristic *c, const char *description) { - c->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + c->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t)ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1); - assert(desc); - desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); - desc->setValue(description); - c->addDescriptor(desc); - service->addCharacteristic(c); - addBLECharacteristic(c); - addBLEDescriptor(desc); + BLEDescriptor *desc = new BLEDescriptor(BLEUUID((uint16_t)ESP_GATT_UUID_CHAR_DESCRIPTION), strlen(description) + 1); + assert(desc); + desc->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED); + desc->setValue(description); + c->addDescriptor(desc); + service->addCharacteristic(c); + addBLECharacteristic(c); + addBLEDescriptor(desc); } static BLECharacteristic *batteryLevelC; @@ -115,19 +113,20 @@ static BLECharacteristic *batteryLevelC; */ BLEService *createBatteryService(BLEServer *server) { - // Create the BLE Service - BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F)); + // Create the BLE Service + BLEService *pBattery = server->createService(BLEUUID((uint16_t)0x180F)); - batteryLevelC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL), BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + batteryLevelC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_BATTERY_LEVEL), + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); - addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100"); - batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification + addWithDesc(pBattery, batteryLevelC, "Percentage 0 - 100"); + batteryLevelC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification - // I don't think we need to advertise this - // server->getAdvertising()->addServiceUUID(pBattery->getUUID()); - pBattery->start(); + // I don't think we need to advertise this + // server->getAdvertising()->addServiceUUID(pBattery->getUUID()); + pBattery->start(); - return pBattery; + return pBattery; } /** @@ -136,87 +135,82 @@ BLEService *createBatteryService(BLEServer *server) */ void updateBatteryLevel(uint8_t level) { - // Pretend to update battery levels - fixme do elsewhere - if (batteryLevelC) - { - batteryLevelC->setValue(&level, 1); - batteryLevelC->notify(); - } + // Pretend to update battery levels - fixme do elsewhere + if (batteryLevelC) { + batteryLevelC->setValue(&level, 1); + batteryLevelC->notify(); + } } void dumpCharacteristic(BLECharacteristic *c) { - std::string value = c->getValue(); + std::string value = c->getValue(); - if (value.length() > 0) - { - DEBUG_MSG("New value: "); - for (int i = 0; i < value.length(); i++) - DEBUG_MSG("%c", value[i]); + if (value.length() > 0) { + DEBUG_MSG("New value: "); + for (int i = 0; i < value.length(); i++) + DEBUG_MSG("%c", value[i]); - DEBUG_MSG("\n"); - } + DEBUG_MSG("\n"); + } } /** converting endianness pull out a 32 bit value */ uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue) { - std::string value = c->getValue(); - uint32_t r = defaultValue; + std::string value = c->getValue(); + uint32_t r = defaultValue; - if (value.length() == 4) - r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL); + if (value.length() == 4) + r = value[0] | (value[1] << 8UL) | (value[2] << 16UL) | (value[3] << 24UL); - return r; + return r; } class MySecurity : public BLESecurityCallbacks { - protected: - bool onConfirmPIN(uint32_t pin) - { - Serial.printf("onConfirmPIN %u\n", pin); - return false; - } - - uint32_t onPassKeyRequest() - { - Serial.println("onPassKeyRequest"); - return 123511; // not used - } - - void onPassKeyNotify(uint32_t pass_key) - { - Serial.printf("onPassKeyNotify %u\n", pass_key); - startCb(pass_key); - } - - bool onSecurityRequest() - { - Serial.println("onSecurityRequest"); - return true; - } - - void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) - { - if (cmpl.success) + protected: + bool onConfirmPIN(uint32_t pin) { - uint16_t length; - esp_ble_gap_get_whitelist_size(&length); - Serial.printf(" onAuthenticationComplete -> success size: %d\n", length); - } - else - { - Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason); + Serial.printf("onConfirmPIN %u\n", pin); + return false; } - // Remove our custom PIN request screen. - stopCb(); - } + uint32_t onPassKeyRequest() + { + Serial.println("onPassKeyRequest"); + return 123511; // not used + } - public: - StartBluetoothPinScreenCallback startCb; - StopBluetoothPinScreenCallback stopCb; + void onPassKeyNotify(uint32_t pass_key) + { + Serial.printf("onPassKeyNotify %u\n", pass_key); + startCb(pass_key); + } + + bool onSecurityRequest() + { + Serial.println("onSecurityRequest"); + return true; + } + + void onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) + { + if (cmpl.success) { + uint16_t length; + esp_ble_gap_get_whitelist_size(&length); + Serial.printf(" authenticated and connected to phone\n"); + } else { + Serial.printf("phone authenticate failed %d\n", cmpl.fail_reason); + } + + // Remove our custom PIN request screen. + stopCb(); + } + + public: + StartBluetoothPinScreenCallback startCb; + StopBluetoothPinScreenCallback stopCb; }; BLEServer *pServer; @@ -225,88 +219,88 @@ BLEService *pDevInfo, *pUpdate; void deinitBLE() { - assert(pServer); + assert(pServer); - pServer->getAdvertising()->stop(); + pServer->getAdvertising()->stop(); - destroyUpdateService(); + destroyUpdateService(); - pUpdate->stop(); - pDevInfo->stop(); - pUpdate->stop(); // we delete them below + pUpdate->stop(); + pDevInfo->stop(); + pUpdate->stop(); // we delete them below - // First shutdown bluetooth - BLEDevice::deinit(false); + // First shutdown bluetooth + BLEDevice::deinit(false); - // do not delete this - it is dynamically allocated, but only once - statically in BLEDevice - // delete pServer->getAdvertising(); + // do not delete this - it is dynamically allocated, but only once - statically in BLEDevice + // delete pServer->getAdvertising(); - delete pUpdate; - delete pDevInfo; - delete pServer; + delete pUpdate; + delete pDevInfo; + delete pServer; - batteryLevelC = NULL; // Don't let anyone generate bogus notifies + batteryLevelC = NULL; // Don't let anyone generate bogus notifies - for (int i = 0; i < numChars; i++) - delete chars[i]; - numChars = 0; + for (int i = 0; i < numChars; i++) + delete chars[i]; + numChars = 0; - for (int i = 0; i < numDescs; i++) - delete descs[i]; - numDescs = 0; + for (int i = 0; i < numDescs; i++) + delete descs[i]; + numDescs = 0; - btPool.reset(); + btPool.reset(); } -BLEServer *initBLE( - StartBluetoothPinScreenCallback startBtPinScreen, - StopBluetoothPinScreenCallback stopBtPinScreen, - std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion) +BLEServer *initBLE(StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoothPinScreenCallback stopBtPinScreen, + std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion) { - BLEDevice::init(deviceName); - BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); + BLEDevice::init(deviceName); + BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); - /* - * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation - */ - static MySecurity mySecurity; - mySecurity.startCb = startBtPinScreen; - mySecurity.stopCb = stopBtPinScreen; - BLEDevice::setSecurityCallbacks(&mySecurity); + /* + * Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation + */ + static MySecurity mySecurity; + mySecurity.startCb = startBtPinScreen; + mySecurity.stopCb = stopBtPinScreen; + BLEDevice::setSecurityCallbacks(&mySecurity); - // Create the BLE Server - pServer = BLEDevice::createServer(); - static MyServerCallbacks myCallbacks; - pServer->setCallbacks(&myCallbacks); + // Create the BLE Server + pServer = BLEDevice::createServer(); + static MyServerCallbacks myCallbacks; + pServer->setCallbacks(&myCallbacks); - pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion); + pDevInfo = createDeviceInfomationService(pServer, hwVendor, swVersion, hwVersion); - // We now let users create the battery service only if they really want (not all devices have a battery) - // BLEService *pBattery = createBatteryService(pServer); + // We now let users create the battery service only if they really want (not all devices have a battery) + // BLEService *pBattery = createBatteryService(pServer); - pUpdate = createUpdateService(pServer, hwVendor, swVersion, hwVersion); // We need to advertise this so our android ble scan operation can see it + pUpdate = createUpdateService(pServer, hwVendor, swVersion, + hwVersion); // We need to advertise this so our android ble scan operation can see it - // It seems only one service can be advertised - so for now don't advertise our updater - // pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID()); + // It seems only one service can be advertised - so for now don't advertise our updater + // pServer->getAdvertising()->addServiceUUID(pUpdate->getUUID()); - // start all our services (do this after creating all of them) - pDevInfo->start(); - pUpdate->start(); + // start all our services (do this after creating all of them) + pDevInfo->start(); + pUpdate->start(); - // FIXME turn on this restriction only after the device is paired with a phone - // advert->setScanFilter(false, true); // We let anyone scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured) but only let whitelist phones connect + // FIXME turn on this restriction only after the device is paired with a phone + // advert->setScanFilter(false, true); // We let anyone scan for us (FIXME, perhaps only allow that until we are paired with a + // phone and configured) but only let whitelist phones connect - static BLESecurity security; // static to avoid allocs - BLESecurity *pSecurity = &security; - pSecurity->setCapability(ESP_IO_CAP_OUT); - pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); - pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); + static BLESecurity security; // static to avoid allocs + BLESecurity *pSecurity = &security; + pSecurity->setCapability(ESP_IO_CAP_OUT); + pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND); + pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); - return pServer; + return pServer; } // Called from loop void loopBLE() { - bluetoothRebootCheck(); + bluetoothRebootCheck(); } diff --git a/src/PhoneAPI.cpp b/src/PhoneAPI.cpp index 4776cd969..043d1fb36 100644 --- a/src/PhoneAPI.cpp +++ b/src/PhoneAPI.cpp @@ -97,6 +97,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) case STATE_SEND_NODEINFO: { const NodeInfo *info = nodeInfoForPhone; + nodeInfoForPhone = NULL; // We just consumed a nodeinfo, will need a new one next time if (info) { DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id, @@ -140,8 +141,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf) // Do we have a message from the mesh? if (fromRadioScratch.which_variant != 0) { // Encapsulate as a FromRadio packet - size_t numbytes = pb_encode_to_bytes(buf, sizeof(FromRadio_size), FromRadio_fields, &fromRadioScratch); - DEBUG_MSG("delivering toPhone packet to phone variant=%d, %d bytes\n", fromRadioScratch.which_variant, numbytes); + DEBUG_MSG("encoding toPhone packet to phone variant=%d", fromRadioScratch.which_variant); + size_t numbytes = pb_encode_to_bytes(buf, FromRadio_size, FromRadio_fields, &fromRadioScratch); + DEBUG_MSG(", %d bytes\n", numbytes); return numbytes; } diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index cadf60121..024f2d532 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -192,6 +192,8 @@ class FromRadioCharacteristic : public CallbackCharacteristic // or make empty if the queue is empty if (numBytes) { c->setValue(trBytes, numBytes); + } else { + c->setValue((uint8_t *)"", 0); } } }; From a0b6d57591e1df6bc3a5a928c71713c1c8507e4d Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 11:41:30 -0700 Subject: [PATCH 048/197] Fix #69 - new BLE API is in and tested from android --- bin/version.sh | 2 +- docs/software/bluetooth-api.md | 86 +++++++++++++----------------- src/esp32/MeshBluetoothService.cpp | 50 +++++++++-------- 3 files changed, 67 insertions(+), 71 deletions(-) diff --git a/bin/version.sh b/bin/version.sh index 6733e32e6..6c7385723 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.4.3 \ No newline at end of file +export VERSION=0.5.5 \ No newline at end of file diff --git a/docs/software/bluetooth-api.md b/docs/software/bluetooth-api.md index 69f808616..cc0df5a1d 100644 --- a/docs/software/bluetooth-api.md +++ b/docs/software/bluetooth-api.md @@ -1,30 +1,30 @@ # Bluetooth API -The Bluetooth API is design to have only a few characteristics and most polymorphism comes from the flexible set of Google Protocol Buffers which are sent over the wire. We use protocol buffers extensively both for the bluetooth API and for packets inside the mesh or when providing packets to other applications on the phone. +The Bluetooth API is design to have only a few characteristics and most polymorphism comes from the flexible set of Google Protocol Buffers which are sent over the wire. We use protocol buffers extensively both for the bluetooth API and for packets inside the mesh or when providing packets to other applications on the phone. ## A note on MTU sizes -This device will work with any MTU size, but it is highly recommended that you call your phone's "setMTU function to increase MTU to 512 bytes" as soon as you connect to a service. This will dramatically improve performance when reading/writing packets. +This device will work with any MTU size, but it is highly recommended that you call your phone's "setMTU function to increase MTU to 512 bytes" as soon as you connect to a service. This will dramatically improve performance when reading/writing packets. -## MeshBluetoothService +## MeshBluetoothService -This is the main bluetooth service for the device and provides the API your app should use to get information about the mesh, send packets or provision the radio. +This is the main bluetooth service for the device and provides the API your app should use to get information about the mesh, send packets or provision the radio. -For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt). Typical flow when +For a reference implementation of a client that uses this service see [RadioInterfaceService](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/RadioInterfaceService.kt). Typical flow when a phone connects to the device should be the following: -* SetMTU size to 512 -* Read a RadioConfig from "radio" - used to get the channel and radio settings -* Read (and write if incorrect) a User from "user" - to get the username for this node -* Read a MyNodeInfo from "mynode" to get information about this local device -* Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine -* Read from "nodeinfo" until it returns empty to build the phone's copy of the current NodeDB for the mesh -* Read from "fromradio" until it returns empty to get any messages that arrived for this node while the phone was away -* Subscribe to notify on "fromnum" to get notified whenever the device has a new received packet -* Read that new packet from "fromradio" -* Whenever the phone has a packet to send write to "toradio" +- SetMTU size to 512 +- Read a RadioConfig from "radio" - used to get the channel and radio settings +- Read (and write if incorrect) a User from "user" - to get the username for this node +- Read a MyNodeInfo from "mynode" to get information about this local device +- Write an empty record to "nodeinfo" to restart the nodeinfo reading state machine +- Read from "nodeinfo" until it returns empty to build the phone's copy of the current NodeDB for the mesh +- Read from "fromradio" until it returns empty to get any messages that arrived for this node while the phone was away +- Subscribe to notify on "fromnum" to get notified whenever the device has a new received packet +- Read that new packet from "fromradio" +- Whenever the phone has a packet to send write to "toradio" -For definitions (and documentation) on FromRadio, ToRadio, MyNodeInfo, NodeInfo and User protocol buffers see [mesh.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/mesh.proto) +For definitions (and documentation) on FromRadio, ToRadio, MyNodeInfo, NodeInfo and User protocol buffers see [mesh.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/mesh.proto) UUID for the service: 6ba1b218-15a8-461f-9fa8-5dcae273eafd @@ -37,7 +37,7 @@ Description (including human readable name) 8ba2bcc2-ee02-4a55-a531-c525c5e454d5 read fromradio - contains a newly received FromRadio packet destined towards the phone (up to MAXPACKET bytes per packet). -After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this +After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this mailbox. f75c76d2-129e-4dad-a1dd-7866124401e7 @@ -49,34 +49,22 @@ read,notify,write fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages until it catches up with this number. -The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio. +The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32 callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio. When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio. Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted. -ea9f3f82-8dc4-4733-9452-1f6da28892a2 -read -mynode - read this to access a MyNodeInfo protobuf - -d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8 -read, write -nodeinfo - read this to get a series of NodeInfos (ending with a null empty record), write to this to restart the read statemachine that returns all the node infos - -b56786c8-839a-44a1-b98e-a1724c4a0262 -read,write -radio - read/write this to access a RadioConfig protobuf - -6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6 -read,write -owner - read/write this to access a User protobuf - Re: queue management Not all messages are kept in the fromradio queue (filtered based on SubPacket): -* only the most recent Position and User messages for a particular node are kept -* all Data SubPackets are kept -* No WantNodeNum / DenyNodeNum messages are kept -A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging) +- only the most recent Position and User messages for a particular node are kept +- all Data SubPackets are kept +- No WantNodeNum / DenyNodeNum messages are kept + A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging) + +## Protobuf API + +On connect, you should send a want_config_id protobuf to the device. This will cause the device to send its node DB and radio config via the fromradio endpoint. After sending the full DB, the radio will send a want_config_id to indicate it is done sending the configuration. ## Other bluetooth services @@ -85,21 +73,21 @@ provided by the device. ### BluetoothSoftwareUpdate -The software update service. For a sample function that performs a software update using this API see [startUpdate](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt). +The software update service. For a sample function that performs a software update using this API see [startUpdate](https://github.com/meshtastic/Meshtastic-Android/blob/master/app/src/main/java/com/geeksville/mesh/service/SoftwareUpdateService.kt). SoftwareUpdateService UUID cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30 Characteristics -| UUID | properties | description| -|--------------------------------------|------------------|------------| -| e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e | write,read | total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted) | -| e272ebac-d463-4b98-bc84-5cc1a39ee517 | write | data, variable sized, recommended 512 bytes, write one for each block of file | -| 4826129c-c22a-43a3-b066-ce8f0d5bacc6 | write | crc32, write last - writing this will complete the OTA operation, now you can read result | -| 5e134862-7411-4424-ac4a-210937432c77 | read,notify | result code, readable but will notify when the OTA operation completes | -| GATT_UUID_SW_VERSION_STR/0x2a28 | read | We also implement these standard GATT entries because SW update probably needs them: | -| GATT_UUID_MANU_NAME/0x2a29 | read | | -| GATT_UUID_HW_VERSION_STR/0x2a27 | read | | +| UUID | properties | description | +| ------------------------------------ | ----------- | ----------------------------------------------------------------------------------------------------------------- | +| e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e | write,read | total image size, 32 bit, write this first, then read read back to see if it was acceptable (0 mean not accepted) | +| e272ebac-d463-4b98-bc84-5cc1a39ee517 | write | data, variable sized, recommended 512 bytes, write one for each block of file | +| 4826129c-c22a-43a3-b066-ce8f0d5bacc6 | write | crc32, write last - writing this will complete the OTA operation, now you can read result | +| 5e134862-7411-4424-ac4a-210937432c77 | read,notify | result code, readable but will notify when the OTA operation completes | +| GATT_UUID_SW_VERSION_STR/0x2a28 | read | We also implement these standard GATT entries because SW update probably needs them: | +| GATT_UUID_MANU_NAME/0x2a29 | read | | +| GATT_UUID_HW_VERSION_STR/0x2a27 | read | | ### DeviceInformationService @@ -107,4 +95,4 @@ Implements the standard BLE contract for this service (has software version, har ### BatteryLevelService -Implements the standard BLE contract service, provides battery level in a way that most client devices should automatically understand (i.e. it should show in the bluetooth devices screen automatically) \ No newline at end of file +Implements the standard BLE contract service, provides battery level in a way that most client devices should automatically understand (i.e. it should show in the bluetooth devices screen automatically) diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index 024f2d532..9b3f14804 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -23,6 +23,9 @@ static CallbackCharacteristic *meshFromNumCharacteristic; BLEService *meshService; +// If defined we will also support the old API +#define SUPPORT_OLD_BLE_API + class BluetoothPhoneAPI : public PhoneAPI { /** @@ -80,6 +83,7 @@ class ProtobufCharacteristic : public CallbackCharacteristic } }; +#ifdef SUPPORT_OLD_BLE_API class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks { public: @@ -162,6 +166,29 @@ class OwnerCharacteristic : public ProtobufCharacteristic } }; +class MyNodeInfoCharacteristic : public ProtobufCharacteristic +{ + public: + MyNodeInfoCharacteristic() + : ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields, + &myNodeInfo) + { + } + + void onRead(BLECharacteristic *c) + { + // update gps connection state + myNodeInfo.has_gps = gps.isConnected; + + ProtobufCharacteristic::onRead(c); + + myNodeInfo.error_code = 0; // The phone just read us, so throw it away + myNodeInfo.error_address = 0; + } +}; + +#endif + class ToRadioCharacteristic : public CallbackCharacteristic { public: @@ -216,27 +243,6 @@ class FromNumCharacteristic : public CallbackCharacteristic } }; -class MyNodeInfoCharacteristic : public ProtobufCharacteristic -{ - public: - MyNodeInfoCharacteristic() - : ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields, - &myNodeInfo) - { - } - - void onRead(BLECharacteristic *c) - { - // update gps connection state - myNodeInfo.has_gps = gps.isConnected; - - ProtobufCharacteristic::onRead(c); - - myNodeInfo.error_code = 0; // The phone just read us, so throw it away - myNodeInfo.error_address = 0; - } -}; - /* See bluetooth-api.md for documentation. */ @@ -257,10 +263,12 @@ BLEService *createMeshBluetoothService(BLEServer *server) addWithDesc(service, meshFromNumCharacteristic, "fromRadio"); addWithDesc(service, new ToRadioCharacteristic, "toRadio"); addWithDesc(service, new FromRadioCharacteristic, "fromNum"); +#ifdef SUPPORT_OLD_BLE_API addWithDesc(service, new MyNodeInfoCharacteristic, "myNode"); addWithDesc(service, new RadioCharacteristic, "radio"); addWithDesc(service, new OwnerCharacteristic, "owner"); addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo"); +#endif meshFromNumCharacteristic->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification From fe3cbeed3a5d678672f8b43e9ed36fdd377eef6f Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 12:47:41 -0700 Subject: [PATCH 049/197] misc NRF52 fixes --- .vscode/launch.json | 10 ++++++---- platformio.ini | 2 +- src/MeshTypes.h | 1 + src/Observer.h | 3 +-- src/bare/main-nrf52.cpp | 13 +++++++++++++ src/rf95/RH_RF95.cpp | 5 +++++ src/rf95/RadioInterface.h | 10 ++++++++++ 7 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/bare/main-nrf52.cpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 914831d68..77b1fb363 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,8 +12,9 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", + "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", "preLaunchTask": { "type": "PlatformIO", "task": "Pre-Debug" @@ -24,8 +25,9 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug (skip Pre-Debug)", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", + "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", "internalConsoleOptions": "openOnSessionStart" } ] diff --git a/platformio.ini b/platformio.ini index d4b105129..83f6adac5 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +default_envs = bare [common] ; common is not currently used diff --git a/src/MeshTypes.h b/src/MeshTypes.h index ab4203e66..0d9783e14 100644 --- a/src/MeshTypes.h +++ b/src/MeshTypes.h @@ -11,6 +11,7 @@ typedef uint8_t PacketId; // A packet sequence number #define NODENUM_BROADCAST 255 #define ERRNO_OK 0 +#define ERRNO_NO_INTERFACES 33 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER typedef int ErrorCode; diff --git a/src/Observer.h b/src/Observer.h index 0c0cb9a92..701495b38 100644 --- a/src/Observer.h +++ b/src/Observer.h @@ -1,7 +1,7 @@ #pragma once #include - +#include #include template class Observable; @@ -14,7 +14,6 @@ template class Observer Observable *observed = NULL; public: - virtual ~Observer(); void observe(Observable *o); diff --git a/src/bare/main-nrf52.cpp b/src/bare/main-nrf52.cpp new file mode 100644 index 000000000..1ccb79615 --- /dev/null +++ b/src/bare/main-nrf52.cpp @@ -0,0 +1,13 @@ +#include + +static inline void debugger_break(void) +{ + __asm volatile("bkpt #0x01\n\t" + "mov pc, lr\n\t"); +} + +// handle standard gcc assert failures +void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr) +{ + debugger_break(); +} \ No newline at end of file diff --git a/src/rf95/RH_RF95.cpp b/src/rf95/RH_RF95.cpp index 14a2f1826..a5d6b47a7 100644 --- a/src/rf95/RH_RF95.cpp +++ b/src/rf95/RH_RF95.cpp @@ -114,6 +114,11 @@ bool RH_RF95::init() return enableInterrupt(); } +// If on a platform without level trigger definitions, just use RISING and suck it up. +#ifndef ONHIGH +#define ONHIGH RISING +#endif + bool RH_RF95::enableInterrupt() { // Determine the interrupt number that corresponds to the interruptPin diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index ec7dfce42..bad532a1e 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -39,6 +39,16 @@ class RadioInterface 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) + * + * This method must be used before putting the CPU into deep or light sleep. + */ + bool canSleep() { return true; } + + /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. + virtual bool sleep() { return true; } + /** * Send a packet (possibly by enquing in a private fifo). This routine will * later free() the packet to pool. This routine is not allowed to stall. From e94227cddd923852598003c27b0719e38236c479 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 12:48:00 -0700 Subject: [PATCH 050/197] cope with missing interfaces in send --- src/rf95/Router.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/rf95/Router.cpp b/src/rf95/Router.cpp index 6b949c1fa..dff4b7795 100644 --- a/src/rf95/Router.cpp +++ b/src/rf95/Router.cpp @@ -51,9 +51,13 @@ void Router::loop() */ ErrorCode Router::send(MeshPacket *p) { - assert(iface); - DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - return iface->send(p); + if (iface) { + DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return iface->send(p); + } else { + DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return ERRNO_NO_INTERFACES; + } } #include "GPS.h" From b77c068881f600f5378466ca94cc4780d8c3cc72 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 12:50:54 -0700 Subject: [PATCH 051/197] create MeshRadio even on NRF52 (though it is currently using a Sim interface) --- src/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b39e61fc9..f9a12181c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -283,11 +283,9 @@ void setup() service.init(); -#ifndef NO_ESP32 // MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) radio = new MeshRadio(); router.addInterface(&radio->radioIf); -#endif if (radio && !radio->init()) recordCriticalError(ErrNoRadio); From 16998ebd8d04390109e62dfa618ff29f8931e675 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 13:26:53 -0700 Subject: [PATCH 052/197] fix compiler warnings --- src/screen.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/screen.cpp b/src/screen.cpp index 583b67b8b..db58a6072 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -320,11 +320,11 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; if (agoSecs < 120) // last 2 mins? - snprintf(lastStr, sizeof(lastStr), "%d seconds ago", agoSecs); + snprintf(lastStr, sizeof(lastStr), "%lu seconds ago", agoSecs); else if (agoSecs < 120 * 60) // last 2 hrs - snprintf(lastStr, sizeof(lastStr), "%d minutes ago", agoSecs / 60); + snprintf(lastStr, sizeof(lastStr), "%lu minutes ago", agoSecs / 60); else - snprintf(lastStr, sizeof(lastStr), "%d hours ago", agoSecs / 60 / 60); + snprintf(lastStr, sizeof(lastStr), "%lu hours ago", agoSecs / 60 / 60); static float simRadian; simRadian += 0.1; // For testing, have the compass spin unless both From f0f6c4950b748b71ba9a1df9b445e2e07a213114 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 13:27:16 -0700 Subject: [PATCH 053/197] on NRF52 use the Segger debug console for debug logging --- lib/segger_rtt/examples/Main_RTT_MenuApp.c | 70 + lib/segger_rtt/examples/Main_RTT_PrintfTest.c | 118 + .../examples/Main_RTT_SpeedTestApp.c | 69 + lib/segger_rtt/include/SEGGER_RTT.h | 321 +++ lib/segger_rtt/include/SEGGER_RTT_Conf.h | 384 ++++ lib/segger_rtt/src/SEGGER_RTT.c | 2005 +++++++++++++++++ lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S | 235 ++ lib/segger_rtt/src/SEGGER_RTT_printf.c | 500 ++++ src/configuration.h | 8 + 9 files changed, 3710 insertions(+) create mode 100644 lib/segger_rtt/examples/Main_RTT_MenuApp.c create mode 100644 lib/segger_rtt/examples/Main_RTT_PrintfTest.c create mode 100644 lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c create mode 100644 lib/segger_rtt/include/SEGGER_RTT.h create mode 100644 lib/segger_rtt/include/SEGGER_RTT_Conf.h create mode 100644 lib/segger_rtt/src/SEGGER_RTT.c create mode 100644 lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S create mode 100644 lib/segger_rtt/src/SEGGER_RTT_printf.c diff --git a/lib/segger_rtt/examples/Main_RTT_MenuApp.c b/lib/segger_rtt/examples/Main_RTT_MenuApp.c new file mode 100644 index 000000000..c6a277ac1 --- /dev/null +++ b/lib/segger_rtt/examples/Main_RTT_MenuApp.c @@ -0,0 +1,70 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* Solutions for real time microcontroller applications * +********************************************************************** +* * +* (c) 1995 - 2018 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +--------- END-OF-HEADER -------------------------------------------- +File : Main_RTT_MenuApp.c +Purpose : Sample application to demonstrate RTT bi-directional functionality +*/ + +#define MAIN_C + +#include + +#include "SEGGER_RTT.h" + +volatile int _Cnt; +volatile int _Delay; + +/********************************************************************* +* +* main +*/ +void main(void) { + int r; + int CancelOp; + + do { + _Cnt = 0; + + SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n"); + SEGGER_RTT_WriteString(0, "Press <1> to continue in blocking mode (Application waits if necessary, no data lost)\r\n"); + SEGGER_RTT_WriteString(0, "Press <2> to continue in non-blocking mode (Application does not wait, data lost if fifo full)\r\n"); + do { + r = SEGGER_RTT_WaitKey(); + } while ((r != '1') && (r != '2')); + if (r == '1') { + SEGGER_RTT_WriteString(0, "\r\nSelected <1>. Configuring RTT and starting...\r\n"); + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL); + } else { + SEGGER_RTT_WriteString(0, "\r\nSelected <2>. Configuring RTT and starting...\r\n"); + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); + } + CancelOp = 0; + do { + //for (_Delay = 0; _Delay < 10000; _Delay++); + SEGGER_RTT_printf(0, "Count: %d. Press to get back to menu.\r\n", _Cnt++); + r = SEGGER_RTT_HasKey(); + if (r) { + CancelOp = (SEGGER_RTT_GetKey() == ' ') ? 1 : 0; + } + // + // Check if user selected to cancel the current operation + // + if (CancelOp) { + SEGGER_RTT_WriteString(0, "Operation cancelled, going back to menu...\r\n"); + break; + } + } while (1); + SEGGER_RTT_GetKey(); + SEGGER_RTT_WriteString(0, "\r\n"); + } while (1); +} + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_PrintfTest.c b/lib/segger_rtt/examples/Main_RTT_PrintfTest.c new file mode 100644 index 000000000..de81b15db --- /dev/null +++ b/lib/segger_rtt/examples/Main_RTT_PrintfTest.c @@ -0,0 +1,118 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* Solutions for real time microcontroller applications * +********************************************************************** +* * +* (c) 1995 - 2018 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +--------- END-OF-HEADER -------------------------------------------- +File : Main_RTT_MenuApp.c +Purpose : Sample application to demonstrate RTT bi-directional functionality +*/ + +#define MAIN_C + +#include + +#include "SEGGER_RTT.h" + +volatile int _Cnt; + +/********************************************************************* +* +* main +*/ +void main(void) { + + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL); + + SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n\r\n"); + SEGGER_RTT_WriteString(0, "###### Testing SEGGER_printf() ######\r\n"); + + SEGGER_RTT_printf(0, "printf Test: %%c, 'S' : %c.\r\n", 'S'); + SEGGER_RTT_printf(0, "printf Test: %%5c, 'E' : %5c.\r\n", 'E'); + SEGGER_RTT_printf(0, "printf Test: %%-5c, 'G' : %-5c.\r\n", 'G'); + SEGGER_RTT_printf(0, "printf Test: %%5.3c, 'G' : %-5c.\r\n", 'G'); + SEGGER_RTT_printf(0, "printf Test: %%.3c, 'E' : %-5c.\r\n", 'E'); + SEGGER_RTT_printf(0, "printf Test: %%c, 'R' : %c.\r\n", 'R'); + + SEGGER_RTT_printf(0, "printf Test: %%s, \"RTT\" : %s.\r\n", "RTT"); + SEGGER_RTT_printf(0, "printf Test: %%s, \"RTT\\r\\nRocks.\" : %s.\r\n", "RTT\r\nRocks."); + + SEGGER_RTT_printf(0, "printf Test: %%u, 12345 : %u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%+u, 12345 : %+u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%.3u, 12345 : %.3u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%.6u, 12345 : %.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%6.3u, 12345 : %6.3u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%8.6u, 12345 : %8.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%08u, 12345 : %08u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%08.6u, 12345 : %08.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%0u, 12345 : %0u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-.6u, 12345 : %-.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-6.3u, 12345 : %-6.3u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-8.6u, 12345 : %-8.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-08u, 12345 : %-08u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-08.6u, 12345 : %-08.6u.\r\n", 12345); + SEGGER_RTT_printf(0, "printf Test: %%-0u, 12345 : %-0u.\r\n", 12345); + + SEGGER_RTT_printf(0, "printf Test: %%u, -12345 : %u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%+u, -12345 : %+u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%.3u, -12345 : %.3u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%.6u, -12345 : %.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%6.3u, -12345 : %6.3u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%8.6u, -12345 : %8.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%08u, -12345 : %08u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%08.6u, -12345 : %08.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%0u, -12345 : %0u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-.6u, -12345 : %-.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-6.3u, -12345 : %-6.3u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-8.6u, -12345 : %-8.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-08u, -12345 : %-08u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-08.6u, -12345 : %-08.6u.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-0u, -12345 : %-0u.\r\n", -12345); + + SEGGER_RTT_printf(0, "printf Test: %%d, -12345 : %d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%+d, -12345 : %+d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%.3d, -12345 : %.3d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%.6d, -12345 : %.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%6.3d, -12345 : %6.3d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%8.6d, -12345 : %8.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%08d, -12345 : %08d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%08.6d, -12345 : %08.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%0d, -12345 : %0d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-.6d, -12345 : %-.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-6.3d, -12345 : %-6.3d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-8.6d, -12345 : %-8.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-08d, -12345 : %-08d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-08.6d, -12345 : %-08.6d.\r\n", -12345); + SEGGER_RTT_printf(0, "printf Test: %%-0d, -12345 : %-0d.\r\n", -12345); + + SEGGER_RTT_printf(0, "printf Test: %%x, 0x1234ABC : %x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%+x, 0x1234ABC : %+x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%.3x, 0x1234ABC : %.3x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%.6x, 0x1234ABC : %.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%6.3x, 0x1234ABC : %6.3x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%8.6x, 0x1234ABC : %8.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%08x, 0x1234ABC : %08x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%08.6x, 0x1234ABC : %08.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%0x, 0x1234ABC : %0x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-.6x, 0x1234ABC : %-.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-6.3x, 0x1234ABC : %-6.3x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-8.6x, 0x1234ABC : %-8.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-08x, 0x1234ABC : %-08x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-08.6x, 0x1234ABC : %-08.6x.\r\n", 0x1234ABC); + SEGGER_RTT_printf(0, "printf Test: %%-0x, 0x1234ABC : %-0x.\r\n", 0x1234ABC); + + SEGGER_RTT_printf(0, "printf Test: %%p, &_Cnt : %p.\r\n", &_Cnt); + + SEGGER_RTT_WriteString(0, "###### SEGGER_printf() Tests done. ######\r\n"); + do { + _Cnt++; + } while (1); +} + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c b/lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c new file mode 100644 index 000000000..304b16f4f --- /dev/null +++ b/lib/segger_rtt/examples/Main_RTT_SpeedTestApp.c @@ -0,0 +1,69 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* Solutions for real time microcontroller applications * +********************************************************************** +* * +* (c) 1995 - 2018 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +--------- END-OF-HEADER -------------------------------------------- +File : Main_RTT_SpeedTestApp.c +Purpose : Sample program for measuring RTT performance. +*/ + +#include "RTOS.h" +#include "BSP.h" + +#include "SEGGER_RTT.h" +#include + +OS_STACKPTR int StackHP[128], StackLP[128]; /* Task stacks */ +OS_TASK TCBHP, TCBLP; /* Task-control-blocks */ + +static void HPTask(void) { + while (1) { + // + // Measure time needed for RTT output + // Perform dummy write with 0 characters, so we know the overhead of toggling LEDs and RTT in general + // +// Set BP here. Then start sampling on scope + BSP_ClrLED(0); + SEGGER_RTT_Write(0, 0, 0); + BSP_SetLED(0); + BSP_ClrLED(0); + SEGGER_RTT_Write(0, "01234567890123456789012345678901234567890123456789012345678901234567890123456789\r\n", 82); + BSP_SetLED(0); +// Set BP here. Then stop sampling on scope + OS_Delay(200); + } +} + +static void LPTask(void) { + while (1) { + BSP_ToggleLED(1); + OS_Delay (500); + } +} + +/********************************************************************* +* +* main +* +*********************************************************************/ + +int main(void) { + OS_IncDI(); /* Initially disable interrupts */ + OS_InitKern(); /* Initialize OS */ + OS_InitHW(); /* Initialize Hardware for OS */ + BSP_Init(); /* Initialize LED ports */ + BSP_SetLED(0); + /* You need to create at least one task before calling OS_Start() */ + OS_CREATETASK(&TCBHP, "HP Task", HPTask, 100, StackHP); + OS_CREATETASK(&TCBLP, "LP Task", LPTask, 50, StackLP); + OS_Start(); /* Start multitasking */ + return 0; +} + diff --git a/lib/segger_rtt/include/SEGGER_RTT.h b/lib/segger_rtt/include/SEGGER_RTT.h new file mode 100644 index 000000000..0945e347e --- /dev/null +++ b/lib/segger_rtt/include/SEGGER_RTT.h @@ -0,0 +1,321 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT.h +Purpose : Implementation of SEGGER real-time transfer which allows + real-time communication on targets which support debugger + memory accesses while the CPU is running. +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ + +#ifndef SEGGER_RTT_H +#define SEGGER_RTT_H + +#include "SEGGER_RTT_Conf.h" + + + +/********************************************************************* +* +* Defines, defaults +* +********************************************************************** +*/ +#ifndef RTT_USE_ASM + #if (defined __SES_ARM) // SEGGER Embedded Studio + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __CROSSWORKS_ARM) // Rowley Crossworks + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __GNUC__) // GCC + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __clang__) // Clang compiler + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __IASMARM__) // IAR assembler + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __ICCARM__) // IAR compiler + #define _CC_HAS_RTT_ASM_SUPPORT 1 + #else + #define _CC_HAS_RTT_ASM_SUPPORT 0 + #endif + #if (defined __ARM_ARCH_7M__) // Cortex-M3/4 + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __ARM_ARCH_7EM__) // Cortex-M7 + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __ARM_ARCH_8M_MAIN__) // Cortex-M33 + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #elif (defined __ARM7M__) // IAR Cortex-M3/4 + #if (__CORE__ == __ARM7M__) + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #else + #define _CORE_HAS_RTT_ASM_SUPPORT 0 + #endif + #elif (defined __ARM7EM__) // IAR Cortex-M7 + #if (__CORE__ == __ARM7EM__) + #define _CORE_HAS_RTT_ASM_SUPPORT 1 + #else + #define _CORE_HAS_RTT_ASM_SUPPORT 0 + #endif + #else + #define _CORE_HAS_RTT_ASM_SUPPORT 0 + #endif + // + // If IDE and core support the ASM version, enable ASM version by default + // + #if (_CC_HAS_RTT_ASM_SUPPORT && _CORE_HAS_RTT_ASM_SUPPORT) + #define RTT_USE_ASM (1) + #else + #define RTT_USE_ASM (0) + #endif +#endif + +#ifndef SEGGER_RTT_ASM // defined when SEGGER_RTT.h is included from assembly file +#include +#include + +/********************************************************************* +* +* Defines, fixed +* +********************************************************************** +*/ + +/********************************************************************* +* +* Types +* +********************************************************************** +*/ + +// +// Description for a circular buffer (also called "ring buffer") +// which is used as up-buffer (T->H) +// +typedef struct { + const char* sName; // Optional name. Standard names so far are: "Terminal", "SysView", "J-Scope_t4i4" + char* pBuffer; // Pointer to start of buffer + unsigned SizeOfBuffer; // Buffer size in bytes. Note that one byte is lost, as this implementation does not fill up the buffer in order to avoid the problem of being unable to distinguish between full and empty. + unsigned WrOff; // Position of next item to be written by either target. + volatile unsigned RdOff; // Position of next item to be read by host. Must be volatile since it may be modified by host. + unsigned Flags; // Contains configuration flags +} SEGGER_RTT_BUFFER_UP; + +// +// Description for a circular buffer (also called "ring buffer") +// which is used as down-buffer (H->T) +// +typedef struct { + const char* sName; // Optional name. Standard names so far are: "Terminal", "SysView", "J-Scope_t4i4" + char* pBuffer; // Pointer to start of buffer + unsigned SizeOfBuffer; // Buffer size in bytes. Note that one byte is lost, as this implementation does not fill up the buffer in order to avoid the problem of being unable to distinguish between full and empty. + volatile unsigned WrOff; // Position of next item to be written by host. Must be volatile since it may be modified by host. + unsigned RdOff; // Position of next item to be read by target (down-buffer). + unsigned Flags; // Contains configuration flags +} SEGGER_RTT_BUFFER_DOWN; + +// +// RTT control block which describes the number of buffers available +// as well as the configuration for each buffer +// +// +typedef struct { + char acID[16]; // Initialized to "SEGGER RTT" + int MaxNumUpBuffers; // Initialized to SEGGER_RTT_MAX_NUM_UP_BUFFERS (type. 2) + int MaxNumDownBuffers; // Initialized to SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (type. 2) + SEGGER_RTT_BUFFER_UP aUp[SEGGER_RTT_MAX_NUM_UP_BUFFERS]; // Up buffers, transferring information up from target via debug probe to host + SEGGER_RTT_BUFFER_DOWN aDown[SEGGER_RTT_MAX_NUM_DOWN_BUFFERS]; // Down buffers, transferring information down from host via debug probe to target +} SEGGER_RTT_CB; + +/********************************************************************* +* +* Global data +* +********************************************************************** +*/ +extern SEGGER_RTT_CB _SEGGER_RTT; + +/********************************************************************* +* +* RTT API functions +* +********************************************************************** +*/ +#ifdef __cplusplus + extern "C" { +#endif +int SEGGER_RTT_AllocDownBuffer (const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); +int SEGGER_RTT_AllocUpBuffer (const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); +int SEGGER_RTT_ConfigUpBuffer (unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); +int SEGGER_RTT_ConfigDownBuffer (unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags); +int SEGGER_RTT_GetKey (void); +unsigned SEGGER_RTT_HasData (unsigned BufferIndex); +int SEGGER_RTT_HasKey (void); +unsigned SEGGER_RTT_HasDataUp (unsigned BufferIndex); +void SEGGER_RTT_Init (void); +unsigned SEGGER_RTT_Read (unsigned BufferIndex, void* pBuffer, unsigned BufferSize); +unsigned SEGGER_RTT_ReadNoLock (unsigned BufferIndex, void* pData, unsigned BufferSize); +int SEGGER_RTT_SetNameDownBuffer (unsigned BufferIndex, const char* sName); +int SEGGER_RTT_SetNameUpBuffer (unsigned BufferIndex, const char* sName); +int SEGGER_RTT_SetFlagsDownBuffer (unsigned BufferIndex, unsigned Flags); +int SEGGER_RTT_SetFlagsUpBuffer (unsigned BufferIndex, unsigned Flags); +int SEGGER_RTT_WaitKey (void); +unsigned SEGGER_RTT_Write (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_WriteNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_WriteSkipNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_ASM_WriteSkipNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_WriteString (unsigned BufferIndex, const char* s); +void SEGGER_RTT_WriteWithOverwriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_PutChar (unsigned BufferIndex, char c); +unsigned SEGGER_RTT_PutCharSkip (unsigned BufferIndex, char c); +unsigned SEGGER_RTT_PutCharSkipNoLock (unsigned BufferIndex, char c); +unsigned SEGGER_RTT_GetAvailWriteSpace (unsigned BufferIndex); +unsigned SEGGER_RTT_GetBytesInBuffer (unsigned BufferIndex); +// +// Function macro for performance optimization +// +#define SEGGER_RTT_HASDATA(n) (_SEGGER_RTT.aDown[n].WrOff - _SEGGER_RTT.aDown[n].RdOff) + +#if RTT_USE_ASM + #define SEGGER_RTT_WriteSkipNoLock SEGGER_RTT_ASM_WriteSkipNoLock +#endif + +/********************************************************************* +* +* RTT transfer functions to send RTT data via other channels. +* +********************************************************************** +*/ +unsigned SEGGER_RTT_ReadUpBuffer (unsigned BufferIndex, void* pBuffer, unsigned BufferSize); +unsigned SEGGER_RTT_ReadUpBufferNoLock (unsigned BufferIndex, void* pData, unsigned BufferSize); +unsigned SEGGER_RTT_WriteDownBuffer (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); +unsigned SEGGER_RTT_WriteDownBufferNoLock (unsigned BufferIndex, const void* pBuffer, unsigned NumBytes); + +#define SEGGER_RTT_HASDATA_UP(n) (_SEGGER_RTT.aUp[n].WrOff - _SEGGER_RTT.aUp[n].RdOff) + +/********************************************************************* +* +* RTT "Terminal" API functions +* +********************************************************************** +*/ +int SEGGER_RTT_SetTerminal (unsigned char TerminalId); +int SEGGER_RTT_TerminalOut (unsigned char TerminalId, const char* s); + +/********************************************************************* +* +* RTT printf functions (require SEGGER_RTT_printf.c) +* +********************************************************************** +*/ +int SEGGER_RTT_printf(unsigned BufferIndex, const char * sFormat, ...); +int SEGGER_RTT_vprintf(unsigned BufferIndex, const char * sFormat, va_list * pParamList); + +#ifdef __cplusplus + } +#endif + +#endif // ifndef(SEGGER_RTT_ASM) + +/********************************************************************* +* +* Defines +* +********************************************************************** +*/ + +// +// Operating modes. Define behavior if buffer is full (not enough space for entire message) +// +#define SEGGER_RTT_MODE_NO_BLOCK_SKIP (0) // Skip. Do not block, output nothing. (Default) +#define SEGGER_RTT_MODE_NO_BLOCK_TRIM (1) // Trim: Do not block, output as much as fits. +#define SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL (2) // Block: Wait until there is space in the buffer. +#define SEGGER_RTT_MODE_MASK (3) + +// +// Control sequences, based on ANSI. +// Can be used to control color, and clear the screen +// +#define RTT_CTRL_RESET "\x1B[0m" // Reset to default colors +#define RTT_CTRL_CLEAR "\x1B[2J" // Clear screen, reposition cursor to top left + +#define RTT_CTRL_TEXT_BLACK "\x1B[2;30m" +#define RTT_CTRL_TEXT_RED "\x1B[2;31m" +#define RTT_CTRL_TEXT_GREEN "\x1B[2;32m" +#define RTT_CTRL_TEXT_YELLOW "\x1B[2;33m" +#define RTT_CTRL_TEXT_BLUE "\x1B[2;34m" +#define RTT_CTRL_TEXT_MAGENTA "\x1B[2;35m" +#define RTT_CTRL_TEXT_CYAN "\x1B[2;36m" +#define RTT_CTRL_TEXT_WHITE "\x1B[2;37m" + +#define RTT_CTRL_TEXT_BRIGHT_BLACK "\x1B[1;30m" +#define RTT_CTRL_TEXT_BRIGHT_RED "\x1B[1;31m" +#define RTT_CTRL_TEXT_BRIGHT_GREEN "\x1B[1;32m" +#define RTT_CTRL_TEXT_BRIGHT_YELLOW "\x1B[1;33m" +#define RTT_CTRL_TEXT_BRIGHT_BLUE "\x1B[1;34m" +#define RTT_CTRL_TEXT_BRIGHT_MAGENTA "\x1B[1;35m" +#define RTT_CTRL_TEXT_BRIGHT_CYAN "\x1B[1;36m" +#define RTT_CTRL_TEXT_BRIGHT_WHITE "\x1B[1;37m" + +#define RTT_CTRL_BG_BLACK "\x1B[24;40m" +#define RTT_CTRL_BG_RED "\x1B[24;41m" +#define RTT_CTRL_BG_GREEN "\x1B[24;42m" +#define RTT_CTRL_BG_YELLOW "\x1B[24;43m" +#define RTT_CTRL_BG_BLUE "\x1B[24;44m" +#define RTT_CTRL_BG_MAGENTA "\x1B[24;45m" +#define RTT_CTRL_BG_CYAN "\x1B[24;46m" +#define RTT_CTRL_BG_WHITE "\x1B[24;47m" + +#define RTT_CTRL_BG_BRIGHT_BLACK "\x1B[4;40m" +#define RTT_CTRL_BG_BRIGHT_RED "\x1B[4;41m" +#define RTT_CTRL_BG_BRIGHT_GREEN "\x1B[4;42m" +#define RTT_CTRL_BG_BRIGHT_YELLOW "\x1B[4;43m" +#define RTT_CTRL_BG_BRIGHT_BLUE "\x1B[4;44m" +#define RTT_CTRL_BG_BRIGHT_MAGENTA "\x1B[4;45m" +#define RTT_CTRL_BG_BRIGHT_CYAN "\x1B[4;46m" +#define RTT_CTRL_BG_BRIGHT_WHITE "\x1B[4;47m" + + +#endif + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/include/SEGGER_RTT_Conf.h b/lib/segger_rtt/include/SEGGER_RTT_Conf.h new file mode 100644 index 000000000..a4d8dd2e0 --- /dev/null +++ b/lib/segger_rtt/include/SEGGER_RTT_Conf.h @@ -0,0 +1,384 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_Conf.h +Purpose : Implementation of SEGGER real-time transfer (RTT) which + allows real-time communication on targets which support + debugger memory accesses while the CPU is running. +Revision: $Rev: 18601 $ + +*/ + +#ifndef SEGGER_RTT_CONF_H +#define SEGGER_RTT_CONF_H + +#ifdef __IAR_SYSTEMS_ICC__ + #include +#endif + +/********************************************************************* +* +* Defines, configurable +* +********************************************************************** +*/ +#ifndef SEGGER_RTT_MAX_NUM_UP_BUFFERS + #define SEGGER_RTT_MAX_NUM_UP_BUFFERS (3) // Max. number of up-buffers (T->H) available on this target (Default: 3) +#endif + +#ifndef SEGGER_RTT_MAX_NUM_DOWN_BUFFERS + #define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS (3) // Max. number of down-buffers (H->T) available on this target (Default: 3) +#endif + +#ifndef BUFFER_SIZE_UP + #define BUFFER_SIZE_UP (1024) // Size of the buffer for terminal output of target, up to host (Default: 1k) +#endif + +#ifndef BUFFER_SIZE_DOWN + #define BUFFER_SIZE_DOWN (16) // Size of the buffer for terminal input to target from host (Usually keyboard input) (Default: 16) +#endif + +#ifndef SEGGER_RTT_PRINTF_BUFFER_SIZE + #define SEGGER_RTT_PRINTF_BUFFER_SIZE (64u) // Size of buffer for RTT printf to bulk-send chars via RTT (Default: 64) +#endif + +#ifndef SEGGER_RTT_MODE_DEFAULT + #define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP // Mode for pre-initialized terminal channel (buffer 0) +#endif + +/********************************************************************* +* +* RTT memcpy configuration +* +* memcpy() is good for large amounts of data, +* but the overhead is big for small amounts, which are usually stored via RTT. +* With SEGGER_RTT_MEMCPY_USE_BYTELOOP a simple byte loop can be used instead. +* +* SEGGER_RTT_MEMCPY() can be used to replace standard memcpy() in RTT functions. +* This is may be required with memory access restrictions, +* such as on Cortex-A devices with MMU. +*/ +#ifndef SEGGER_RTT_MEMCPY_USE_BYTELOOP + #define SEGGER_RTT_MEMCPY_USE_BYTELOOP 0 // 0: Use memcpy/SEGGER_RTT_MEMCPY, 1: Use a simple byte-loop +#endif +// +// Example definition of SEGGER_RTT_MEMCPY to external memcpy with GCC toolchains and Cortex-A targets +// +//#if ((defined __SES_ARM) || (defined __CROSSWORKS_ARM) || (defined __GNUC__)) && (defined (__ARM_ARCH_7A__)) +// #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) SEGGER_memcpy((pDest), (pSrc), (NumBytes)) +//#endif + +// +// Target is not allowed to perform other RTT operations while string still has not been stored completely. +// Otherwise we would probably end up with a mixed string in the buffer. +// If using RTT from within interrupts, multiple tasks or multi processors, define the SEGGER_RTT_LOCK() and SEGGER_RTT_UNLOCK() function here. +// +// SEGGER_RTT_MAX_INTERRUPT_PRIORITY can be used in the sample lock routines on Cortex-M3/4. +// Make sure to mask all interrupts which can send RTT data, i.e. generate SystemView events, or cause task switches. +// When high-priority interrupts must not be masked while sending RTT data, SEGGER_RTT_MAX_INTERRUPT_PRIORITY needs to be adjusted accordingly. +// (Higher priority = lower priority number) +// Default value for embOS: 128u +// Default configuration in FreeRTOS: configMAX_SYSCALL_INTERRUPT_PRIORITY: ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) ) +// In case of doubt mask all interrupts: 1 << (8 - BASEPRI_PRIO_BITS) i.e. 1 << 5 when 3 bits are implemented in NVIC +// or define SEGGER_RTT_LOCK() to completely disable interrupts. +// +#ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) // Interrupt priority to lock on SEGGER_RTT_LOCK on Cortex-M3/4 (Default: 0x20) +#endif + +/********************************************************************* +* +* RTT lock configuration for SEGGER Embedded Studio, +* Rowley CrossStudio and GCC +*/ +#if ((defined(__SES_ARM) || defined(__SES_RISCV) || defined(__CROSSWORKS_ARM) || defined(__GNUC__) || defined(__clang__)) && !defined (__CC_ARM) && !defined(WIN32)) + #if (defined(__ARM_ARCH_6M__) || defined(__ARM_ARCH_8M_BASE__)) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + __asm volatile ("mrs %0, primask \n\t" \ + "movs r1, $1 \n\t" \ + "msr primask, r1 \n\t" \ + : "=r" (LockState) \ + : \ + : "r1" \ + ); + + #define SEGGER_RTT_UNLOCK() __asm volatile ("msr primask, %0 \n\t" \ + : \ + : "r" (LockState) \ + : \ + ); \ + } + #elif (defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_8M_MAIN__)) + #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) + #endif + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + __asm volatile ("mrs %0, basepri \n\t" \ + "mov r1, %1 \n\t" \ + "msr basepri, r1 \n\t" \ + : "=r" (LockState) \ + : "i"(SEGGER_RTT_MAX_INTERRUPT_PRIORITY) \ + : "r1" \ + ); + + #define SEGGER_RTT_UNLOCK() __asm volatile ("msr basepri, %0 \n\t" \ + : \ + : "r" (LockState) \ + : \ + ); \ + } + + #elif defined(__ARM_ARCH_7A__) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + __asm volatile ("mrs r1, CPSR \n\t" \ + "mov %0, r1 \n\t" \ + "orr r1, r1, #0xC0 \n\t" \ + "msr CPSR_c, r1 \n\t" \ + : "=r" (LockState) \ + : \ + : "r1" \ + ); + + #define SEGGER_RTT_UNLOCK() __asm volatile ("mov r0, %0 \n\t" \ + "mrs r1, CPSR \n\t" \ + "bic r1, r1, #0xC0 \n\t" \ + "and r0, r0, #0xC0 \n\t" \ + "orr r1, r1, r0 \n\t" \ + "msr CPSR_c, r1 \n\t" \ + : \ + : "r" (LockState) \ + : "r0", "r1" \ + ); \ + } + #elif defined(__riscv) || defined(__riscv_xlen) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + __asm volatile ("csrr %0, mstatus \n\t" \ + "csrci mstatus, 8 \n\t" \ + "andi %0, %0, 8 \n\t" \ + : "=r" (LockState) \ + : \ + : \ + ); + + #define SEGGER_RTT_UNLOCK() __asm volatile ("csrr a1, mstatus \n\t" \ + "or %0, %0, a1 \n\t" \ + "csrs mstatus, %0 \n\t" \ + : \ + : "r" (LockState) \ + : "a1" \ + ); \ + } + #else + #define SEGGER_RTT_LOCK() + #define SEGGER_RTT_UNLOCK() + #endif +#endif + +/********************************************************************* +* +* RTT lock configuration for IAR EWARM +*/ +#ifdef __ICCARM__ + #if (defined (__ARM6M__) && (__CORE__ == __ARM6M__)) || \ + (defined (__ARM8M_BASELINE__) && (__CORE__ == __ARM8M_BASELINE__)) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + LockState = __get_PRIMASK(); \ + __set_PRIMASK(1); + + #define SEGGER_RTT_UNLOCK() __set_PRIMASK(LockState); \ + } + #elif (defined (__ARM7EM__) && (__CORE__ == __ARM7EM__)) || \ + (defined (__ARM7M__) && (__CORE__ == __ARM7M__)) || \ + (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) || \ + (defined (__ARM8M_MAINLINE__) && (__CORE__ == __ARM8M_MAINLINE__)) + #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) + #endif + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + LockState = __get_BASEPRI(); \ + __set_BASEPRI(SEGGER_RTT_MAX_INTERRUPT_PRIORITY); + + #define SEGGER_RTT_UNLOCK() __set_BASEPRI(LockState); \ + } + #endif +#endif + +/********************************************************************* +* +* RTT lock configuration for IAR RX +*/ +#ifdef __ICCRX__ + #define SEGGER_RTT_LOCK() { \ + unsigned long LockState; \ + LockState = __get_interrupt_state(); \ + __disable_interrupt(); + + #define SEGGER_RTT_UNLOCK() __set_interrupt_state(LockState); \ + } +#endif + +/********************************************************************* +* +* RTT lock configuration for IAR RL78 +*/ +#ifdef __ICCRL78__ + #define SEGGER_RTT_LOCK() { \ + __istate_t LockState; \ + LockState = __get_interrupt_state(); \ + __disable_interrupt(); + + #define SEGGER_RTT_UNLOCK() __set_interrupt_state(LockState); \ + } +#endif + +/********************************************************************* +* +* RTT lock configuration for KEIL ARM +*/ +#ifdef __CC_ARM + #if (defined __TARGET_ARCH_6S_M) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + register unsigned char PRIMASK __asm( "primask"); \ + LockState = PRIMASK; \ + PRIMASK = 1u; \ + __schedule_barrier(); + + #define SEGGER_RTT_UNLOCK() PRIMASK = LockState; \ + __schedule_barrier(); \ + } + #elif (defined(__TARGET_ARCH_7_M) || defined(__TARGET_ARCH_7E_M)) + #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) + #endif + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + register unsigned char BASEPRI __asm( "basepri"); \ + LockState = BASEPRI; \ + BASEPRI = SEGGER_RTT_MAX_INTERRUPT_PRIORITY; \ + __schedule_barrier(); + + #define SEGGER_RTT_UNLOCK() BASEPRI = LockState; \ + __schedule_barrier(); \ + } + #endif +#endif + +/********************************************************************* +* +* RTT lock configuration for TI ARM +*/ +#ifdef __TI_ARM__ + #if defined (__TI_ARM_V6M0__) + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + LockState = __get_PRIMASK(); \ + __set_PRIMASK(1); + + #define SEGGER_RTT_UNLOCK() __set_PRIMASK(LockState); \ + } + #elif (defined (__TI_ARM_V7M3__) || defined (__TI_ARM_V7M4__)) + #ifndef SEGGER_RTT_MAX_INTERRUPT_PRIORITY + #define SEGGER_RTT_MAX_INTERRUPT_PRIORITY (0x20) + #endif + #define SEGGER_RTT_LOCK() { \ + unsigned int LockState; \ + LockState = _set_interrupt_priority(SEGGER_RTT_MAX_INTERRUPT_PRIORITY); + + #define SEGGER_RTT_UNLOCK() _set_interrupt_priority(LockState); \ + } + #endif +#endif + +/********************************************************************* +* +* RTT lock configuration for CCRX +*/ +#ifdef __RX + #define SEGGER_RTT_LOCK() { \ + unsigned long LockState; \ + LockState = get_psw() & 0x010000; \ + clrpsw_i(); + + #define SEGGER_RTT_UNLOCK() set_psw(get_psw() | LockState); \ + } +#endif + +/********************************************************************* +* +* RTT lock configuration for embOS Simulation on Windows +* (Can also be used for generic RTT locking with embOS) +*/ +#if defined(WIN32) || defined(SEGGER_RTT_LOCK_EMBOS) + +void OS_SIM_EnterCriticalSection(void); +void OS_SIM_LeaveCriticalSection(void); + +#define SEGGER_RTT_LOCK() { \ + OS_SIM_EnterCriticalSection(); + +#define SEGGER_RTT_UNLOCK() OS_SIM_LeaveCriticalSection(); \ + } +#endif + +/********************************************************************* +* +* RTT lock configuration fallback +*/ +#ifndef SEGGER_RTT_LOCK + #define SEGGER_RTT_LOCK() // Lock RTT (nestable) (i.e. disable interrupts) +#endif + +#ifndef SEGGER_RTT_UNLOCK + #define SEGGER_RTT_UNLOCK() // Unlock RTT (nestable) (i.e. enable previous interrupt lock state) +#endif + +#endif +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/src/SEGGER_RTT.c b/lib/segger_rtt/src/SEGGER_RTT.c new file mode 100644 index 000000000..74791e0ac --- /dev/null +++ b/lib/segger_rtt/src/SEGGER_RTT.c @@ -0,0 +1,2005 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT.c +Purpose : Implementation of SEGGER real-time transfer (RTT) which + allows real-time communication on targets which support + debugger memory accesses while the CPU is running. +Revision: $Rev: 17697 $ + +Additional information: + Type "int" is assumed to be 32-bits in size + H->T Host to target communication + T->H Target to host communication + + RTT channel 0 is always present and reserved for Terminal usage. + Name is fixed to "Terminal" + + Effective buffer size: SizeOfBuffer - 1 + + WrOff == RdOff: Buffer is empty + WrOff == (RdOff - 1): Buffer is full + WrOff > RdOff: Free space includes wrap-around + WrOff < RdOff: Used space includes wrap-around + (WrOff == (SizeOfBuffer - 1)) && (RdOff == 0): + Buffer full and wrap-around after next byte + + +---------------------------------------------------------------------- +*/ + +#include "SEGGER_RTT.h" + +#include // for memcpy + +/********************************************************************* +* +* Configuration, default values +* +********************************************************************** +*/ + +#ifndef BUFFER_SIZE_UP + #define BUFFER_SIZE_UP 1024 // Size of the buffer for terminal output of target, up to host +#endif + +#ifndef BUFFER_SIZE_DOWN + #define BUFFER_SIZE_DOWN 16 // Size of the buffer for terminal input to target from host (Usually keyboard input) +#endif + +#ifndef SEGGER_RTT_MAX_NUM_UP_BUFFERS + #define SEGGER_RTT_MAX_NUM_UP_BUFFERS 2 // Number of up-buffers (T->H) available on this target +#endif + +#ifndef SEGGER_RTT_MAX_NUM_DOWN_BUFFERS + #define SEGGER_RTT_MAX_NUM_DOWN_BUFFERS 2 // Number of down-buffers (H->T) available on this target +#endif + +#ifndef SEGGER_RTT_BUFFER_SECTION + #if defined(SEGGER_RTT_SECTION) + #define SEGGER_RTT_BUFFER_SECTION SEGGER_RTT_SECTION + #endif +#endif + +#ifndef SEGGER_RTT_ALIGNMENT + #define SEGGER_RTT_ALIGNMENT 0 +#endif + +#ifndef SEGGER_RTT_BUFFER_ALIGNMENT + #define SEGGER_RTT_BUFFER_ALIGNMENT 0 +#endif + +#ifndef SEGGER_RTT_MODE_DEFAULT + #define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP +#endif + +#ifndef SEGGER_RTT_LOCK + #define SEGGER_RTT_LOCK() +#endif + +#ifndef SEGGER_RTT_UNLOCK + #define SEGGER_RTT_UNLOCK() +#endif + +#ifndef STRLEN + #define STRLEN(a) strlen((a)) +#endif + +#ifndef STRCPY + #define STRCPY(pDest, pSrc, NumBytes) strcpy((pDest), (pSrc)) +#endif + +#ifndef SEGGER_RTT_MEMCPY_USE_BYTELOOP + #define SEGGER_RTT_MEMCPY_USE_BYTELOOP 0 +#endif + +#ifndef SEGGER_RTT_MEMCPY + #ifdef MEMCPY + #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) MEMCPY((pDest), (pSrc), (NumBytes)) + #else + #define SEGGER_RTT_MEMCPY(pDest, pSrc, NumBytes) memcpy((pDest), (pSrc), (NumBytes)) + #endif +#endif + +#ifndef MIN + #define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef MAX + #define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif +// +// For some environments, NULL may not be defined until certain headers are included +// +#ifndef NULL + #define NULL 0 +#endif + +/********************************************************************* +* +* Defines, fixed +* +********************************************************************** +*/ +#if (defined __ICCARM__) || (defined __ICCRX__) + #define RTT_PRAGMA(P) _Pragma(#P) +#endif + +#if SEGGER_RTT_ALIGNMENT || SEGGER_RTT_BUFFER_ALIGNMENT + #if (defined __GNUC__) + #define SEGGER_RTT_ALIGN(Var, Alignment) Var __attribute__ ((aligned (Alignment))) + #elif (defined __ICCARM__) || (defined __ICCRX__) + #define PRAGMA(A) _Pragma(#A) +#define SEGGER_RTT_ALIGN(Var, Alignment) RTT_PRAGMA(data_alignment=Alignment) \ + Var + #elif (defined __CC_ARM) + #define SEGGER_RTT_ALIGN(Var, Alignment) Var __attribute__ ((aligned (Alignment))) + #else + #error "Alignment not supported for this compiler." + #endif +#else + #define SEGGER_RTT_ALIGN(Var, Alignment) Var +#endif + +#if defined(SEGGER_RTT_SECTION) || defined (SEGGER_RTT_BUFFER_SECTION) + #if (defined __GNUC__) + #define SEGGER_RTT_PUT_SECTION(Var, Section) __attribute__ ((section (Section))) Var + #elif (defined __ICCARM__) || (defined __ICCRX__) +#define SEGGER_RTT_PUT_SECTION(Var, Section) RTT_PRAGMA(location=Section) \ + Var + #elif (defined __CC_ARM) + #define SEGGER_RTT_PUT_SECTION(Var, Section) __attribute__ ((section (Section), zero_init)) Var + #else + #error "Section placement not supported for this compiler." + #endif +#else + #define SEGGER_RTT_PUT_SECTION(Var, Section) Var +#endif + + +#if SEGGER_RTT_ALIGNMENT + #define SEGGER_RTT_CB_ALIGN(Var) SEGGER_RTT_ALIGN(Var, SEGGER_RTT_ALIGNMENT) +#else + #define SEGGER_RTT_CB_ALIGN(Var) Var +#endif + +#if SEGGER_RTT_BUFFER_ALIGNMENT + #define SEGGER_RTT_BUFFER_ALIGN(Var) SEGGER_RTT_ALIGN(Var, SEGGER_RTT_BUFFER_ALIGNMENT) +#else + #define SEGGER_RTT_BUFFER_ALIGN(Var) Var +#endif + + +#if defined(SEGGER_RTT_SECTION) + #define SEGGER_RTT_PUT_CB_SECTION(Var) SEGGER_RTT_PUT_SECTION(Var, SEGGER_RTT_SECTION) +#else + #define SEGGER_RTT_PUT_CB_SECTION(Var) Var +#endif + +#if defined(SEGGER_RTT_BUFFER_SECTION) + #define SEGGER_RTT_PUT_BUFFER_SECTION(Var) SEGGER_RTT_PUT_SECTION(Var, SEGGER_RTT_BUFFER_SECTION) +#else + #define SEGGER_RTT_PUT_BUFFER_SECTION(Var) Var +#endif + +/********************************************************************* +* +* Static const data +* +********************************************************************** +*/ + +static unsigned char _aTerminalId[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + +/********************************************************************* +* +* Static data +* +********************************************************************** +*/ +// +// RTT Control Block and allocate buffers for channel 0 +// +SEGGER_RTT_PUT_CB_SECTION(SEGGER_RTT_CB_ALIGN(SEGGER_RTT_CB _SEGGER_RTT)); + +SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acUpBuffer [BUFFER_SIZE_UP])); +SEGGER_RTT_PUT_BUFFER_SECTION(SEGGER_RTT_BUFFER_ALIGN(static char _acDownBuffer[BUFFER_SIZE_DOWN])); + +static unsigned char _ActiveTerminal; + +/********************************************************************* +* +* Static functions +* +********************************************************************** +*/ + +/********************************************************************* +* +* _DoInit() +* +* Function description +* Initializes the control block an buffers. +* May only be called via INIT() to avoid overriding settings. +* +*/ +#define INIT() do { \ + if (_SEGGER_RTT.acID[0] == '\0') { _DoInit(); } \ + } while (0) +static void _DoInit(void) { + SEGGER_RTT_CB* p; + // + // Initialize control block + // + p = &_SEGGER_RTT; + p->MaxNumUpBuffers = SEGGER_RTT_MAX_NUM_UP_BUFFERS; + p->MaxNumDownBuffers = SEGGER_RTT_MAX_NUM_DOWN_BUFFERS; + // + // Initialize up buffer 0 + // + p->aUp[0].sName = "Terminal"; + p->aUp[0].pBuffer = _acUpBuffer; + p->aUp[0].SizeOfBuffer = sizeof(_acUpBuffer); + p->aUp[0].RdOff = 0u; + p->aUp[0].WrOff = 0u; + p->aUp[0].Flags = SEGGER_RTT_MODE_DEFAULT; + // + // Initialize down buffer 0 + // + p->aDown[0].sName = "Terminal"; + p->aDown[0].pBuffer = _acDownBuffer; + p->aDown[0].SizeOfBuffer = sizeof(_acDownBuffer); + p->aDown[0].RdOff = 0u; + p->aDown[0].WrOff = 0u; + p->aDown[0].Flags = SEGGER_RTT_MODE_DEFAULT; + // + // Finish initialization of the control block. + // Copy Id string in three steps to make sure "SEGGER RTT" is not found + // in initializer memory (usually flash) by J-Link + // + STRCPY(&p->acID[7], "RTT", 9); + STRCPY(&p->acID[0], "SEGGER", 7); + p->acID[6] = ' '; +} + +/********************************************************************* +* +* _WriteBlocking() +* +* Function description +* Stores a specified number of characters in SEGGER RTT ring buffer +* and updates the associated write pointer which is periodically +* read by the host. +* The caller is responsible for managing the write chunk sizes as +* _WriteBlocking() will block until all data has been posted successfully. +* +* Parameters +* pRing Ring buffer to post to. +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* >= 0 - Number of bytes written into buffer. +*/ +static unsigned _WriteBlocking(SEGGER_RTT_BUFFER_UP* pRing, const char* pBuffer, unsigned NumBytes) { + unsigned NumBytesToWrite; + unsigned NumBytesWritten; + unsigned RdOff; + unsigned WrOff; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + char* pDst; +#endif + // + // Write data to buffer and handle wrap-around if necessary + // + NumBytesWritten = 0u; + WrOff = pRing->WrOff; + do { + RdOff = pRing->RdOff; // May be changed by host (debug probe) in the meantime + if (RdOff > WrOff) { + NumBytesToWrite = RdOff - WrOff - 1u; + } else { + NumBytesToWrite = pRing->SizeOfBuffer - (WrOff - RdOff + 1u); + } + NumBytesToWrite = MIN(NumBytesToWrite, (pRing->SizeOfBuffer - WrOff)); // Number of bytes that can be written until buffer wrap-around + NumBytesToWrite = MIN(NumBytesToWrite, NumBytes); +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + WrOff; + NumBytesWritten += NumBytesToWrite; + NumBytes -= NumBytesToWrite; + WrOff += NumBytesToWrite; + while (NumBytesToWrite--) { + *pDst++ = *pBuffer++; + }; +#else + SEGGER_RTT_MEMCPY(pRing->pBuffer + WrOff, pBuffer, NumBytesToWrite); + NumBytesWritten += NumBytesToWrite; + pBuffer += NumBytesToWrite; + NumBytes -= NumBytesToWrite; + WrOff += NumBytesToWrite; +#endif + if (WrOff == pRing->SizeOfBuffer) { + WrOff = 0u; + } + pRing->WrOff = WrOff; + } while (NumBytes); + // + return NumBytesWritten; +} + +/********************************************************************* +* +* _WriteNoCheck() +* +* Function description +* Stores a specified number of characters in SEGGER RTT ring buffer +* and updates the associated write pointer which is periodically +* read by the host. +* It is callers responsibility to make sure data actually fits in buffer. +* +* Parameters +* pRing Ring buffer to post to. +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Notes +* (1) If there might not be enough space in the "Up"-buffer, call _WriteBlocking +*/ +static void _WriteNoCheck(SEGGER_RTT_BUFFER_UP* pRing, const char* pData, unsigned NumBytes) { + unsigned NumBytesAtOnce; + unsigned WrOff; + unsigned Rem; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + char* pDst; +#endif + + WrOff = pRing->WrOff; + Rem = pRing->SizeOfBuffer - WrOff; + if (Rem > NumBytes) { + // + // All data fits before wrap around + // +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + WrOff; + WrOff += NumBytes; + while (NumBytes--) { + *pDst++ = *pData++; + }; + pRing->WrOff = WrOff; +#else + SEGGER_RTT_MEMCPY(pRing->pBuffer + WrOff, pData, NumBytes); + pRing->WrOff = WrOff + NumBytes; +#endif + } else { + // + // We reach the end of the buffer, so need to wrap around + // +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + WrOff; + NumBytesAtOnce = Rem; + while (NumBytesAtOnce--) { + *pDst++ = *pData++; + }; + pDst = pRing->pBuffer; + NumBytesAtOnce = NumBytes - Rem; + while (NumBytesAtOnce--) { + *pDst++ = *pData++; + }; + pRing->WrOff = NumBytes - Rem; +#else + NumBytesAtOnce = Rem; + SEGGER_RTT_MEMCPY(pRing->pBuffer + WrOff, pData, NumBytesAtOnce); + NumBytesAtOnce = NumBytes - Rem; + SEGGER_RTT_MEMCPY(pRing->pBuffer, pData + Rem, NumBytesAtOnce); + pRing->WrOff = NumBytesAtOnce; +#endif + } +} + +/********************************************************************* +* +* _PostTerminalSwitch() +* +* Function description +* Switch terminal to the given terminal ID. It is the caller's +* responsibility to ensure the terminal ID is correct and there is +* enough space in the buffer for this to complete successfully. +* +* Parameters +* pRing Ring buffer to post to. +* TerminalId Terminal ID to switch to. +*/ +static void _PostTerminalSwitch(SEGGER_RTT_BUFFER_UP* pRing, unsigned char TerminalId) { + unsigned char ac[2]; + + ac[0] = 0xFFu; + ac[1] = _aTerminalId[TerminalId]; // Caller made already sure that TerminalId does not exceed our terminal limit + _WriteBlocking(pRing, (const char*)ac, 2u); +} + +/********************************************************************* +* +* _GetAvailWriteSpace() +* +* Function description +* Returns the number of bytes that can be written to the ring +* buffer without blocking. +* +* Parameters +* pRing Ring buffer to check. +* +* Return value +* Number of bytes that are free in the buffer. +*/ +static unsigned _GetAvailWriteSpace(SEGGER_RTT_BUFFER_UP* pRing) { + unsigned RdOff; + unsigned WrOff; + unsigned r; + // + // Avoid warnings regarding volatile access order. It's not a problem + // in this case, but dampen compiler enthusiasm. + // + RdOff = pRing->RdOff; + WrOff = pRing->WrOff; + if (RdOff <= WrOff) { + r = pRing->SizeOfBuffer - 1u - WrOff + RdOff; + } else { + r = RdOff - WrOff - 1u; + } + return r; +} + +/********************************************************************* +* +* Public code +* +********************************************************************** +*/ +/********************************************************************* +* +* SEGGER_RTT_ReadUpBufferNoLock() +* +* Function description +* Reads characters from SEGGER real-time-terminal control block +* which have been previously stored by the application. +* Do not lock against interrupts and multiple access. +* Used to do the same operation that J-Link does, to transfer +* RTT data via other channels, such as TCP/IP or UART. +* +* Parameters +* BufferIndex Index of Up-buffer to be used. +* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-up-buffer to. +* BufferSize Size of the target application buffer. +* +* Return value +* Number of bytes that have been read. +* +* Additional information +* This function must not be called when J-Link might also do RTT. +*/ +unsigned SEGGER_RTT_ReadUpBufferNoLock(unsigned BufferIndex, void* pData, unsigned BufferSize) { + unsigned NumBytesRem; + unsigned NumBytesRead; + unsigned RdOff; + unsigned WrOff; + unsigned char* pBuffer; + SEGGER_RTT_BUFFER_UP* pRing; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + const char* pSrc; +#endif + // + INIT(); + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + pBuffer = (unsigned char*)pData; + RdOff = pRing->RdOff; + WrOff = pRing->WrOff; + NumBytesRead = 0u; + // + // Read from current read position to wrap-around of buffer, first + // + if (RdOff > WrOff) { + NumBytesRem = pRing->SizeOfBuffer - RdOff; + NumBytesRem = MIN(NumBytesRem, BufferSize); +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pSrc = pRing->pBuffer + RdOff; + NumBytesRead += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; + while (NumBytesRem--) { + *pBuffer++ = *pSrc++; + }; +#else + SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); + NumBytesRead += NumBytesRem; + pBuffer += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; +#endif + // + // Handle wrap-around of buffer + // + if (RdOff == pRing->SizeOfBuffer) { + RdOff = 0u; + } + } + // + // Read remaining items of buffer + // + NumBytesRem = WrOff - RdOff; + NumBytesRem = MIN(NumBytesRem, BufferSize); + if (NumBytesRem > 0u) { +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pSrc = pRing->pBuffer + RdOff; + NumBytesRead += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; + while (NumBytesRem--) { + *pBuffer++ = *pSrc++; + }; +#else + SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); + NumBytesRead += NumBytesRem; + pBuffer += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; +#endif + } + // + // Update read offset of buffer + // + if (NumBytesRead) { + pRing->RdOff = RdOff; + } + // + return NumBytesRead; +} + +/********************************************************************* +* +* SEGGER_RTT_ReadNoLock() +* +* Function description +* Reads characters from SEGGER real-time-terminal control block +* which have been previously stored by the host. +* Do not lock against interrupts and multiple access. +* +* Parameters +* BufferIndex Index of Down-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-down-buffer to. +* BufferSize Size of the target application buffer. +* +* Return value +* Number of bytes that have been read. +*/ +unsigned SEGGER_RTT_ReadNoLock(unsigned BufferIndex, void* pData, unsigned BufferSize) { + unsigned NumBytesRem; + unsigned NumBytesRead; + unsigned RdOff; + unsigned WrOff; + unsigned char* pBuffer; + SEGGER_RTT_BUFFER_DOWN* pRing; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + const char* pSrc; +#endif + // + INIT(); + pRing = &_SEGGER_RTT.aDown[BufferIndex]; + pBuffer = (unsigned char*)pData; + RdOff = pRing->RdOff; + WrOff = pRing->WrOff; + NumBytesRead = 0u; + // + // Read from current read position to wrap-around of buffer, first + // + if (RdOff > WrOff) { + NumBytesRem = pRing->SizeOfBuffer - RdOff; + NumBytesRem = MIN(NumBytesRem, BufferSize); +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pSrc = pRing->pBuffer + RdOff; + NumBytesRead += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; + while (NumBytesRem--) { + *pBuffer++ = *pSrc++; + }; +#else + SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); + NumBytesRead += NumBytesRem; + pBuffer += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; +#endif + // + // Handle wrap-around of buffer + // + if (RdOff == pRing->SizeOfBuffer) { + RdOff = 0u; + } + } + // + // Read remaining items of buffer + // + NumBytesRem = WrOff - RdOff; + NumBytesRem = MIN(NumBytesRem, BufferSize); + if (NumBytesRem > 0u) { +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pSrc = pRing->pBuffer + RdOff; + NumBytesRead += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; + while (NumBytesRem--) { + *pBuffer++ = *pSrc++; + }; +#else + SEGGER_RTT_MEMCPY(pBuffer, pRing->pBuffer + RdOff, NumBytesRem); + NumBytesRead += NumBytesRem; + pBuffer += NumBytesRem; + BufferSize -= NumBytesRem; + RdOff += NumBytesRem; +#endif + } + if (NumBytesRead) { + pRing->RdOff = RdOff; + } + // + return NumBytesRead; +} + +/********************************************************************* +* +* SEGGER_RTT_ReadUpBuffer +* +* Function description +* Reads characters from SEGGER real-time-terminal control block +* which have been previously stored by the application. +* Used to do the same operation that J-Link does, to transfer +* RTT data via other channels, such as TCP/IP or UART. +* +* Parameters +* BufferIndex Index of Up-buffer to be used. +* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-up-buffer to. +* BufferSize Size of the target application buffer. +* +* Return value +* Number of bytes that have been read. +* +* Additional information +* This function must not be called when J-Link might also do RTT. +* This function locks against all other RTT operations. I.e. during +* the read operation, writing is also locked. +* If only one consumer reads from the up buffer, +* call sEGGER_RTT_ReadUpBufferNoLock() instead. +*/ +unsigned SEGGER_RTT_ReadUpBuffer(unsigned BufferIndex, void* pBuffer, unsigned BufferSize) { + unsigned NumBytesRead; + // + SEGGER_RTT_LOCK(); + // + // Call the non-locking read function + // + NumBytesRead = SEGGER_RTT_ReadUpBufferNoLock(BufferIndex, pBuffer, BufferSize); + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return NumBytesRead; +} + +/********************************************************************* +* +* SEGGER_RTT_Read +* +* Function description +* Reads characters from SEGGER real-time-terminal control block +* which have been previously stored by the host. +* +* Parameters +* BufferIndex Index of Down-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to buffer provided by target application, to copy characters from RTT-down-buffer to. +* BufferSize Size of the target application buffer. +* +* Return value +* Number of bytes that have been read. +*/ +unsigned SEGGER_RTT_Read(unsigned BufferIndex, void* pBuffer, unsigned BufferSize) { + unsigned NumBytesRead; + // + SEGGER_RTT_LOCK(); + // + // Call the non-locking read function + // + NumBytesRead = SEGGER_RTT_ReadNoLock(BufferIndex, pBuffer, BufferSize); + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return NumBytesRead; +} + +/********************************************************************* +* +* SEGGER_RTT_WriteWithOverwriteNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block. +* SEGGER_RTT_WriteWithOverwriteNoLock does not lock the application +* and overwrites data if the data does not fit into the buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, data is overwritten. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +* (3) Do not use SEGGER_RTT_WriteWithOverwriteNoLock if a J-Link +* connection reads RTT data. +*/ +void SEGGER_RTT_WriteWithOverwriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + const char* pData; + SEGGER_RTT_BUFFER_UP* pRing; + unsigned Avail; +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + char* pDst; +#endif + + pData = (const char *)pBuffer; + // + // Get "to-host" ring buffer and copy some elements into local variables. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // Check if we will overwrite data and need to adjust the RdOff. + // + if (pRing->WrOff == pRing->RdOff) { + Avail = pRing->SizeOfBuffer - 1u; + } else if ( pRing->WrOff < pRing->RdOff) { + Avail = pRing->RdOff - pRing->WrOff - 1u; + } else { + Avail = pRing->RdOff - pRing->WrOff - 1u + pRing->SizeOfBuffer; + } + if (NumBytes > Avail) { + pRing->RdOff += (NumBytes - Avail); + while (pRing->RdOff >= pRing->SizeOfBuffer) { + pRing->RdOff -= pRing->SizeOfBuffer; + } + } + // + // Write all data, no need to check the RdOff, but possibly handle multiple wrap-arounds + // + Avail = pRing->SizeOfBuffer - pRing->WrOff; + do { + if (Avail > NumBytes) { + // + // Last round + // +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + pRing->WrOff; + Avail = NumBytes; + while (NumBytes--) { + *pDst++ = *pData++; + }; + pRing->WrOff += Avail; +#else + SEGGER_RTT_MEMCPY(pRing->pBuffer + pRing->WrOff, pData, NumBytes); + pRing->WrOff += NumBytes; +#endif + break; + } else { + // + // Wrap-around necessary, write until wrap-around and reset WrOff + // +#if SEGGER_RTT_MEMCPY_USE_BYTELOOP + pDst = pRing->pBuffer + pRing->WrOff; + NumBytes -= Avail; + while (Avail--) { + *pDst++ = *pData++; + }; + pRing->WrOff = 0; +#else + SEGGER_RTT_MEMCPY(pRing->pBuffer + pRing->WrOff, pData, Avail); + pData += Avail; + pRing->WrOff = 0; + NumBytes -= Avail; +#endif + Avail = (pRing->SizeOfBuffer - 1); + } + } while (NumBytes); +} + +/********************************************************************* +* +* SEGGER_RTT_WriteSkipNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block which is then read by the host. +* SEGGER_RTT_WriteSkipNoLock does not lock the application and +* skips all data, if the data does not fit into the buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* MUST be > 0!!! +* This is done for performance reasons, so no initial check has do be done. +* +* Return value +* 1: Data has been copied +* 0: No space, data has not been copied +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, all data is dropped. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +*/ +#if (RTT_USE_ASM == 0) +unsigned SEGGER_RTT_WriteSkipNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + const char* pData; + SEGGER_RTT_BUFFER_UP* pRing; + unsigned Avail; + unsigned RdOff; + unsigned WrOff; + unsigned Rem; + // + // Cases: + // 1) RdOff <= WrOff => Space until wrap-around is sufficient + // 2) RdOff <= WrOff => Space after wrap-around needed (copy in 2 chunks) + // 3) RdOff < WrOff => No space in buf + // 4) RdOff > WrOff => Space is sufficient + // 5) RdOff > WrOff => No space in buf + // + // 1) is the most common case for large buffers and assuming that J-Link reads the data fast enough + // + pData = (const char *)pBuffer; + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + RdOff = pRing->RdOff; + WrOff = pRing->WrOff; + if (RdOff <= WrOff) { // Case 1), 2) or 3) + Avail = pRing->SizeOfBuffer - WrOff - 1u; // Space until wrap-around (assume 1 byte not usable for case that RdOff == 0) + if (Avail >= NumBytes) { // Case 1)? +CopyStraight: + memcpy(pRing->pBuffer + WrOff, pData, NumBytes); + pRing->WrOff = WrOff + NumBytes; + return 1; + } + Avail += RdOff; // Space incl. wrap-around + if (Avail >= NumBytes) { // Case 2? => If not, we have case 3) (does not fit) + Rem = pRing->SizeOfBuffer - WrOff; // Space until end of buffer + memcpy(pRing->pBuffer + WrOff, pData, Rem); // Copy 1st chunk + NumBytes -= Rem; + // + // Special case: First check that assumed RdOff == 0 calculated that last element before wrap-around could not be used + // But 2nd check (considering space until wrap-around and until RdOff) revealed that RdOff is not 0, so we can use the last element + // In this case, we may use a copy straight until buffer end anyway without needing to copy 2 chunks + // Therefore, check if 2nd memcpy is necessary at all + // + if (NumBytes) { + memcpy(pRing->pBuffer, pData + Rem, NumBytes); + } + pRing->WrOff = NumBytes; + return 1; + } + } else { // Potential case 4) + Avail = RdOff - WrOff - 1u; + if (Avail >= NumBytes) { // Case 4)? => If not, we have case 5) (does not fit) + goto CopyStraight; + } + } + return 0; // No space in buffer +} +#endif + +/********************************************************************* +* +* SEGGER_RTT_WriteDownBufferNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block inside a buffer. +* SEGGER_RTT_WriteDownBufferNoLock does not lock the application. +* Used to do the same operation that J-Link does, to transfer +* RTT data from other channels, such as TCP/IP or UART. +* +* Parameters +* BufferIndex Index of "Down"-buffer to be used. +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* Number of bytes which have been stored in the "Down"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +* +* Additional information +* This function must not be called when J-Link might also do RTT. +*/ +unsigned SEGGER_RTT_WriteDownBufferNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + unsigned Status; + unsigned Avail; + const char* pData; + SEGGER_RTT_BUFFER_UP* pRing; + + pData = (const char *)pBuffer; + // + // Get "to-target" ring buffer. + // It is save to cast that to a "to-host" buffer. Up and Down buffer differ in volatility of offsets that might be modified by J-Link. + // + pRing = (SEGGER_RTT_BUFFER_UP*)&_SEGGER_RTT.aDown[BufferIndex]; + // + // How we output depends upon the mode... + // + switch (pRing->Flags) { + case SEGGER_RTT_MODE_NO_BLOCK_SKIP: + // + // If we are in skip mode and there is no space for the whole + // of this output, don't bother. + // + Avail = _GetAvailWriteSpace(pRing); + if (Avail < NumBytes) { + Status = 0u; + } else { + Status = NumBytes; + _WriteNoCheck(pRing, pData, NumBytes); + } + break; + case SEGGER_RTT_MODE_NO_BLOCK_TRIM: + // + // If we are in trim mode, trim to what we can output without blocking. + // + Avail = _GetAvailWriteSpace(pRing); + Status = Avail < NumBytes ? Avail : NumBytes; + _WriteNoCheck(pRing, pData, Status); + break; + case SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: + // + // If we are in blocking mode, output everything. + // + Status = _WriteBlocking(pRing, pData, NumBytes); + break; + default: + Status = 0u; + break; + } + // + // Finish up. + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_WriteNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block which is then read by the host. +* SEGGER_RTT_WriteNoLock does not lock the application. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +*/ +unsigned SEGGER_RTT_WriteNoLock(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + unsigned Status; + unsigned Avail; + const char* pData; + SEGGER_RTT_BUFFER_UP* pRing; + + pData = (const char *)pBuffer; + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // How we output depends upon the mode... + // + switch (pRing->Flags) { + case SEGGER_RTT_MODE_NO_BLOCK_SKIP: + // + // If we are in skip mode and there is no space for the whole + // of this output, don't bother. + // + Avail = _GetAvailWriteSpace(pRing); + if (Avail < NumBytes) { + Status = 0u; + } else { + Status = NumBytes; + _WriteNoCheck(pRing, pData, NumBytes); + } + break; + case SEGGER_RTT_MODE_NO_BLOCK_TRIM: + // + // If we are in trim mode, trim to what we can output without blocking. + // + Avail = _GetAvailWriteSpace(pRing); + Status = Avail < NumBytes ? Avail : NumBytes; + _WriteNoCheck(pRing, pData, Status); + break; + case SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: + // + // If we are in blocking mode, output everything. + // + Status = _WriteBlocking(pRing, pData, NumBytes); + break; + default: + Status = 0u; + break; + } + // + // Finish up. + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_WriteDownBuffer +* +* Function description +* Stores a specified number of characters in SEGGER RTT control block in a buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* Number of bytes which have been stored in the "Down"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +* +* Additional information +* This function must not be called when J-Link might also do RTT. +* This function locks against all other RTT operations. I.e. during +* the write operation, writing from the application is also locked. +* If only one consumer writes to the down buffer, +* call SEGGER_RTT_WriteDownBufferNoLock() instead. +*/ +unsigned SEGGER_RTT_WriteDownBuffer(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + unsigned Status; + // + INIT(); + SEGGER_RTT_LOCK(); + // + // Call the non-locking write function + // + Status = SEGGER_RTT_WriteDownBufferNoLock(BufferIndex, pBuffer, NumBytes); + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_Write +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block which is then read by the host. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +*/ +unsigned SEGGER_RTT_Write(unsigned BufferIndex, const void* pBuffer, unsigned NumBytes) { + unsigned Status; + // + INIT(); + SEGGER_RTT_LOCK(); + // + // Call the non-locking write function + // + Status = SEGGER_RTT_WriteNoLock(BufferIndex, pBuffer, NumBytes); + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_WriteString +* +* Function description +* Stores string in SEGGER RTT control block. +* This data is read by the host. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* s Pointer to string. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +* (2) String passed to this function has to be \0 terminated +* (3) \0 termination character is *not* stored in RTT buffer +*/ +unsigned SEGGER_RTT_WriteString(unsigned BufferIndex, const char* s) { + unsigned Len; + + Len = STRLEN(s); + return SEGGER_RTT_Write(BufferIndex, s, Len); +} + +/********************************************************************* +* +* SEGGER_RTT_PutCharSkipNoLock +* +* Function description +* Stores a single character/byte in SEGGER RTT buffer. +* SEGGER_RTT_PutCharSkipNoLock does not lock the application and +* skips the byte, if it does not fit into the buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* c Byte to be stored. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, the character is dropped. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +*/ + +unsigned SEGGER_RTT_PutCharSkipNoLock(unsigned BufferIndex, char c) { + SEGGER_RTT_BUFFER_UP* pRing; + unsigned WrOff; + unsigned Status; + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // Get write position and handle wrap-around if necessary + // + WrOff = pRing->WrOff + 1; + if (WrOff == pRing->SizeOfBuffer) { + WrOff = 0; + } + // + // Output byte if free space is available + // + if (WrOff != pRing->RdOff) { + pRing->pBuffer[pRing->WrOff] = c; + pRing->WrOff = WrOff; + Status = 1; + } else { + Status = 0; + } + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_PutCharSkip +* +* Function description +* Stores a single character/byte in SEGGER RTT buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* c Byte to be stored. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, the character is dropped. +*/ + +unsigned SEGGER_RTT_PutCharSkip(unsigned BufferIndex, char c) { + SEGGER_RTT_BUFFER_UP* pRing; + unsigned WrOff; + unsigned Status; + // + // Prepare + // + INIT(); + SEGGER_RTT_LOCK(); + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // Get write position and handle wrap-around if necessary + // + WrOff = pRing->WrOff + 1; + if (WrOff == pRing->SizeOfBuffer) { + WrOff = 0; + } + // + // Output byte if free space is available + // + if (WrOff != pRing->RdOff) { + pRing->pBuffer[pRing->WrOff] = c; + pRing->WrOff = WrOff; + Status = 1; + } else { + Status = 0; + } + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return Status; +} + + /********************************************************************* +* +* SEGGER_RTT_PutChar +* +* Function description +* Stores a single character/byte in SEGGER RTT buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* c Byte to be stored. +* +* Return value +* Number of bytes which have been stored in the "Up"-buffer. +* +* Notes +* (1) Data is stored according to buffer flags. +*/ + +unsigned SEGGER_RTT_PutChar(unsigned BufferIndex, char c) { + SEGGER_RTT_BUFFER_UP* pRing; + unsigned WrOff; + unsigned Status; + // + // Prepare + // + INIT(); + SEGGER_RTT_LOCK(); + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + // + // Get write position and handle wrap-around if necessary + // + WrOff = pRing->WrOff + 1; + if (WrOff == pRing->SizeOfBuffer) { + WrOff = 0; + } + // + // Wait for free space if mode is set to blocking + // + if (pRing->Flags == SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) { + while (WrOff == pRing->RdOff) { + ; + } + } + // + // Output byte if free space is available + // + if (WrOff != pRing->RdOff) { + pRing->pBuffer[pRing->WrOff] = c; + pRing->WrOff = WrOff; + Status = 1; + } else { + Status = 0; + } + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + // + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_GetKey +* +* Function description +* Reads one character from the SEGGER RTT buffer. +* Host has previously stored data there. +* +* Return value +* < 0 - No character available (buffer empty). +* >= 0 - Character which has been read. (Possible values: 0 - 255) +* +* Notes +* (1) This function is only specified for accesses to RTT buffer 0. +*/ +int SEGGER_RTT_GetKey(void) { + char c; + int r; + + r = (int)SEGGER_RTT_Read(0u, &c, 1u); + if (r == 1) { + r = (int)(unsigned char)c; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_WaitKey +* +* Function description +* Waits until at least one character is avaible in the SEGGER RTT buffer. +* Once a character is available, it is read and this function returns. +* +* Return value +* >=0 - Character which has been read. +* +* Notes +* (1) This function is only specified for accesses to RTT buffer 0 +* (2) This function is blocking if no character is present in RTT buffer +*/ +int SEGGER_RTT_WaitKey(void) { + int r; + + do { + r = SEGGER_RTT_GetKey(); + } while (r < 0); + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_HasKey +* +* Function description +* Checks if at least one character for reading is available in the SEGGER RTT buffer. +* +* Return value +* == 0 - No characters are available to read. +* == 1 - At least one character is available. +* +* Notes +* (1) This function is only specified for accesses to RTT buffer 0 +*/ +int SEGGER_RTT_HasKey(void) { + unsigned RdOff; + int r; + + INIT(); + RdOff = _SEGGER_RTT.aDown[0].RdOff; + if (RdOff != _SEGGER_RTT.aDown[0].WrOff) { + r = 1; + } else { + r = 0; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_HasData +* +* Function description +* Check if there is data from the host in the given buffer. +* +* Return value: +* ==0: No data +* !=0: Data in buffer +* +*/ +unsigned SEGGER_RTT_HasData(unsigned BufferIndex) { + SEGGER_RTT_BUFFER_DOWN* pRing; + unsigned v; + + pRing = &_SEGGER_RTT.aDown[BufferIndex]; + v = pRing->WrOff; + return v - pRing->RdOff; +} + +/********************************************************************* +* +* SEGGER_RTT_HasDataUp +* +* Function description +* Check if there is data remaining to be sent in the given buffer. +* +* Return value: +* ==0: No data +* !=0: Data in buffer +* +*/ +unsigned SEGGER_RTT_HasDataUp(unsigned BufferIndex) { + SEGGER_RTT_BUFFER_UP* pRing; + unsigned v; + + pRing = &_SEGGER_RTT.aUp[BufferIndex]; + v = pRing->RdOff; + return pRing->WrOff - v; +} + +/********************************************************************* +* +* SEGGER_RTT_AllocDownBuffer +* +* Function description +* Run-time configuration of the next down-buffer (H->T). +* The next buffer, which is not used yet is configured. +* This includes: Buffer address, size, name, flags, ... +* +* Parameters +* sName Pointer to a constant name string. +* pBuffer Pointer to a buffer to be used. +* BufferSize Size of the buffer. +* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). +* +* Return value +* >= 0 - O.K. Buffer Index +* < 0 - Error +*/ +int SEGGER_RTT_AllocDownBuffer(const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { + int BufferIndex; + + INIT(); + SEGGER_RTT_LOCK(); + BufferIndex = 0; + do { + if (_SEGGER_RTT.aDown[BufferIndex].pBuffer == NULL) { + break; + } + BufferIndex++; + } while (BufferIndex < _SEGGER_RTT.MaxNumDownBuffers); + if (BufferIndex < _SEGGER_RTT.MaxNumDownBuffers) { + _SEGGER_RTT.aDown[BufferIndex].sName = sName; + _SEGGER_RTT.aDown[BufferIndex].pBuffer = (char*)pBuffer; + _SEGGER_RTT.aDown[BufferIndex].SizeOfBuffer = BufferSize; + _SEGGER_RTT.aDown[BufferIndex].RdOff = 0u; + _SEGGER_RTT.aDown[BufferIndex].WrOff = 0u; + _SEGGER_RTT.aDown[BufferIndex].Flags = Flags; + } else { + BufferIndex = -1; + } + SEGGER_RTT_UNLOCK(); + return BufferIndex; +} + +/********************************************************************* +* +* SEGGER_RTT_AllocUpBuffer +* +* Function description +* Run-time configuration of the next up-buffer (T->H). +* The next buffer, which is not used yet is configured. +* This includes: Buffer address, size, name, flags, ... +* +* Parameters +* sName Pointer to a constant name string. +* pBuffer Pointer to a buffer to be used. +* BufferSize Size of the buffer. +* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). +* +* Return value +* >= 0 - O.K. Buffer Index +* < 0 - Error +*/ +int SEGGER_RTT_AllocUpBuffer(const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { + int BufferIndex; + + INIT(); + SEGGER_RTT_LOCK(); + BufferIndex = 0; + do { + if (_SEGGER_RTT.aUp[BufferIndex].pBuffer == NULL) { + break; + } + BufferIndex++; + } while (BufferIndex < _SEGGER_RTT.MaxNumUpBuffers); + if (BufferIndex < _SEGGER_RTT.MaxNumUpBuffers) { + _SEGGER_RTT.aUp[BufferIndex].sName = sName; + _SEGGER_RTT.aUp[BufferIndex].pBuffer = (char*)pBuffer; + _SEGGER_RTT.aUp[BufferIndex].SizeOfBuffer = BufferSize; + _SEGGER_RTT.aUp[BufferIndex].RdOff = 0u; + _SEGGER_RTT.aUp[BufferIndex].WrOff = 0u; + _SEGGER_RTT.aUp[BufferIndex].Flags = Flags; + } else { + BufferIndex = -1; + } + SEGGER_RTT_UNLOCK(); + return BufferIndex; +} + +/********************************************************************* +* +* SEGGER_RTT_ConfigUpBuffer +* +* Function description +* Run-time configuration of a specific up-buffer (T->H). +* Buffer to be configured is specified by index. +* This includes: Buffer address, size, name, flags, ... +* +* Parameters +* BufferIndex Index of the buffer to configure. +* sName Pointer to a constant name string. +* pBuffer Pointer to a buffer to be used. +* BufferSize Size of the buffer. +* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). +* +* Return value +* >= 0 - O.K. +* < 0 - Error +* +* Additional information +* Buffer 0 is configured on compile-time. +* May only be called once per buffer. +* Buffer name and flags can be reconfigured using the appropriate functions. +*/ +int SEGGER_RTT_ConfigUpBuffer(unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumUpBuffers) { + SEGGER_RTT_LOCK(); + if (BufferIndex > 0u) { + _SEGGER_RTT.aUp[BufferIndex].sName = sName; + _SEGGER_RTT.aUp[BufferIndex].pBuffer = (char*)pBuffer; + _SEGGER_RTT.aUp[BufferIndex].SizeOfBuffer = BufferSize; + _SEGGER_RTT.aUp[BufferIndex].RdOff = 0u; + _SEGGER_RTT.aUp[BufferIndex].WrOff = 0u; + } + _SEGGER_RTT.aUp[BufferIndex].Flags = Flags; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_ConfigDownBuffer +* +* Function description +* Run-time configuration of a specific down-buffer (H->T). +* Buffer to be configured is specified by index. +* This includes: Buffer address, size, name, flags, ... +* +* Parameters +* BufferIndex Index of the buffer to configure. +* sName Pointer to a constant name string. +* pBuffer Pointer to a buffer to be used. +* BufferSize Size of the buffer. +* Flags Operating modes. Define behavior if buffer is full (not enough space for entire message). +* +* Return value +* >= 0 O.K. +* < 0 Error +* +* Additional information +* Buffer 0 is configured on compile-time. +* May only be called once per buffer. +* Buffer name and flags can be reconfigured using the appropriate functions. +*/ +int SEGGER_RTT_ConfigDownBuffer(unsigned BufferIndex, const char* sName, void* pBuffer, unsigned BufferSize, unsigned Flags) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumDownBuffers) { + SEGGER_RTT_LOCK(); + if (BufferIndex > 0u) { + _SEGGER_RTT.aDown[BufferIndex].sName = sName; + _SEGGER_RTT.aDown[BufferIndex].pBuffer = (char*)pBuffer; + _SEGGER_RTT.aDown[BufferIndex].SizeOfBuffer = BufferSize; + _SEGGER_RTT.aDown[BufferIndex].RdOff = 0u; + _SEGGER_RTT.aDown[BufferIndex].WrOff = 0u; + } + _SEGGER_RTT.aDown[BufferIndex].Flags = Flags; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_SetNameUpBuffer +* +* Function description +* Run-time configuration of a specific up-buffer name (T->H). +* Buffer to be configured is specified by index. +* +* Parameters +* BufferIndex Index of the buffer to renamed. +* sName Pointer to a constant name string. +* +* Return value +* >= 0 O.K. +* < 0 Error +*/ +int SEGGER_RTT_SetNameUpBuffer(unsigned BufferIndex, const char* sName) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumUpBuffers) { + SEGGER_RTT_LOCK(); + _SEGGER_RTT.aUp[BufferIndex].sName = sName; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_SetNameDownBuffer +* +* Function description +* Run-time configuration of a specific Down-buffer name (T->H). +* Buffer to be configured is specified by index. +* +* Parameters +* BufferIndex Index of the buffer to renamed. +* sName Pointer to a constant name string. +* +* Return value +* >= 0 O.K. +* < 0 Error +*/ +int SEGGER_RTT_SetNameDownBuffer(unsigned BufferIndex, const char* sName) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumDownBuffers) { + SEGGER_RTT_LOCK(); + _SEGGER_RTT.aDown[BufferIndex].sName = sName; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_SetFlagsUpBuffer +* +* Function description +* Run-time configuration of specific up-buffer flags (T->H). +* Buffer to be configured is specified by index. +* +* Parameters +* BufferIndex Index of the buffer. +* Flags Flags to set for the buffer. +* +* Return value +* >= 0 O.K. +* < 0 Error +*/ +int SEGGER_RTT_SetFlagsUpBuffer(unsigned BufferIndex, unsigned Flags) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumUpBuffers) { + SEGGER_RTT_LOCK(); + _SEGGER_RTT.aUp[BufferIndex].Flags = Flags; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_SetFlagsDownBuffer +* +* Function description +* Run-time configuration of specific Down-buffer flags (T->H). +* Buffer to be configured is specified by index. +* +* Parameters +* BufferIndex Index of the buffer to renamed. +* Flags Flags to set for the buffer. +* +* Return value +* >= 0 O.K. +* < 0 Error +*/ +int SEGGER_RTT_SetFlagsDownBuffer(unsigned BufferIndex, unsigned Flags) { + int r; + + INIT(); + if (BufferIndex < (unsigned)_SEGGER_RTT.MaxNumDownBuffers) { + SEGGER_RTT_LOCK(); + _SEGGER_RTT.aDown[BufferIndex].Flags = Flags; + SEGGER_RTT_UNLOCK(); + r = 0; + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_Init +* +* Function description +* Initializes the RTT Control Block. +* Should be used in RAM targets, at start of the application. +* +*/ +void SEGGER_RTT_Init (void) { + _DoInit(); +} + +/********************************************************************* +* +* SEGGER_RTT_SetTerminal +* +* Function description +* Sets the terminal to be used for output on channel 0. +* +* Parameters +* TerminalId Index of the terminal. +* +* Return value +* >= 0 O.K. +* < 0 Error (e.g. if RTT is configured for non-blocking mode and there was no space in the buffer to set the new terminal Id) +*/ +int SEGGER_RTT_SetTerminal (unsigned char TerminalId) { + unsigned char ac[2]; + SEGGER_RTT_BUFFER_UP* pRing; + unsigned Avail; + int r; + // + INIT(); + // + r = 0; + ac[0] = 0xFFu; + if (TerminalId < sizeof(_aTerminalId)) { // We only support a certain number of channels + ac[1] = _aTerminalId[TerminalId]; + pRing = &_SEGGER_RTT.aUp[0]; // Buffer 0 is always reserved for terminal I/O, so we can use index 0 here, fixed + SEGGER_RTT_LOCK(); // Lock to make sure that no other task is writing into buffer, while we are and number of free bytes in buffer does not change downwards after checking and before writing + if ((pRing->Flags & SEGGER_RTT_MODE_MASK) == SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) { + _ActiveTerminal = TerminalId; + _WriteBlocking(pRing, (const char*)ac, 2u); + } else { // Skipping mode or trim mode? => We cannot trim this command so handling is the same for both modes + Avail = _GetAvailWriteSpace(pRing); + if (Avail >= 2) { + _ActiveTerminal = TerminalId; // Only change active terminal in case of success + _WriteNoCheck(pRing, (const char*)ac, 2u); + } else { + r = -1; + } + } + SEGGER_RTT_UNLOCK(); + } else { + r = -1; + } + return r; +} + +/********************************************************************* +* +* SEGGER_RTT_TerminalOut +* +* Function description +* Writes a string to the given terminal +* without changing the terminal for channel 0. +* +* Parameters +* TerminalId Index of the terminal. +* s String to be printed on the terminal. +* +* Return value +* >= 0 - Number of bytes written. +* < 0 - Error. +* +*/ +int SEGGER_RTT_TerminalOut (unsigned char TerminalId, const char* s) { + int Status; + unsigned FragLen; + unsigned Avail; + SEGGER_RTT_BUFFER_UP* pRing; + // + INIT(); + // + // Validate terminal ID. + // + if (TerminalId < (char)sizeof(_aTerminalId)) { // We only support a certain number of channels + // + // Get "to-host" ring buffer. + // + pRing = &_SEGGER_RTT.aUp[0]; + // + // Need to be able to change terminal, write data, change back. + // Compute the fixed and variable sizes. + // + FragLen = STRLEN(s); + // + // How we output depends upon the mode... + // + SEGGER_RTT_LOCK(); + Avail = _GetAvailWriteSpace(pRing); + switch (pRing->Flags & SEGGER_RTT_MODE_MASK) { + case SEGGER_RTT_MODE_NO_BLOCK_SKIP: + // + // If we are in skip mode and there is no space for the whole + // of this output, don't bother switching terminals at all. + // + if (Avail < (FragLen + 4u)) { + Status = 0; + } else { + _PostTerminalSwitch(pRing, TerminalId); + Status = (int)_WriteBlocking(pRing, s, FragLen); + _PostTerminalSwitch(pRing, _ActiveTerminal); + } + break; + case SEGGER_RTT_MODE_NO_BLOCK_TRIM: + // + // If we are in trim mode and there is not enough space for everything, + // trim the output but always include the terminal switch. If no room + // for terminal switch, skip that totally. + // + if (Avail < 4u) { + Status = -1; + } else { + _PostTerminalSwitch(pRing, TerminalId); + Status = (int)_WriteBlocking(pRing, s, (FragLen < (Avail - 4u)) ? FragLen : (Avail - 4u)); + _PostTerminalSwitch(pRing, _ActiveTerminal); + } + break; + case SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL: + // + // If we are in blocking mode, output everything. + // + _PostTerminalSwitch(pRing, TerminalId); + Status = (int)_WriteBlocking(pRing, s, FragLen); + _PostTerminalSwitch(pRing, _ActiveTerminal); + break; + default: + Status = -1; + break; + } + // + // Finish up. + // + SEGGER_RTT_UNLOCK(); + } else { + Status = -1; + } + return Status; +} + +/********************************************************************* +* +* SEGGER_RTT_GetAvailWriteSpace +* +* Function description +* Returns the number of bytes available in the ring buffer. +* +* Parameters +* BufferIndex Index of the up buffer. +* +* Return value +* Number of bytes that are free in the selected up buffer. +*/ +unsigned SEGGER_RTT_GetAvailWriteSpace (unsigned BufferIndex){ + return _GetAvailWriteSpace(&_SEGGER_RTT.aUp[BufferIndex]); +} + + +/********************************************************************* +* +* SEGGER_RTT_GetBytesInBuffer() +* +* Function description +* Returns the number of bytes currently used in the up buffer. +* +* Parameters +* BufferIndex Index of the up buffer. +* +* Return value +* Number of bytes that are used in the buffer. +*/ +unsigned SEGGER_RTT_GetBytesInBuffer(unsigned BufferIndex) { + unsigned RdOff; + unsigned WrOff; + unsigned r; + // + // Avoid warnings regarding volatile access order. It's not a problem + // in this case, but dampen compiler enthusiasm. + // + RdOff = _SEGGER_RTT.aUp[BufferIndex].RdOff; + WrOff = _SEGGER_RTT.aUp[BufferIndex].WrOff; + if (RdOff <= WrOff) { + r = WrOff - RdOff; + } else { + r = _SEGGER_RTT.aUp[BufferIndex].SizeOfBuffer - (WrOff - RdOff); + } + return r; +} + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S b/lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S new file mode 100644 index 000000000..0c0eae1c2 --- /dev/null +++ b/lib/segger_rtt/src/SEGGER_RTT_ASM_ARMv7M.S @@ -0,0 +1,235 @@ +/********************************************************************* +* (c) SEGGER Microcontroller GmbH * +* The Embedded Experts * +* www.segger.com * +********************************************************************** + +-------------------------- END-OF-HEADER ----------------------------- + +File : SEGGER_RTT_ASM_ARMv7M.S +Purpose : Assembler implementation of RTT functions for ARMv7M + +Additional information: + This module is written to be assembler-independent and works with + GCC and clang (Embedded Studio) and IAR. +*/ + +#define SEGGER_RTT_ASM // Used to control processed input from header file +#include "SEGGER_RTT.h" + +/********************************************************************* +* +* Defines, fixed +* +********************************************************************** +*/ +#define _CCIAR 0 +#define _CCCLANG 1 + +#if (defined __SES_ARM) || (defined __GNUC__) || (defined __clang__) + #define _CC_TYPE _CCCLANG + #define _PUB_SYM .global + #define _EXT_SYM .extern + #define _END .end + #define _WEAK .weak + #define _THUMB_FUNC .thumb_func + #define _THUMB_CODE .code 16 + #define _WORD .word + #define _SECTION(Sect, Type, AlignExp) .section Sect ##, "ax" + #define _ALIGN(Exp) .align Exp + #define _PLACE_LITS .ltorg + #define _DATA_SECT_START + #define _C_STARTUP _start + #define _STACK_END __stack_end__ + #define _RAMFUNC + // + // .text => Link to flash + // .fast => Link to RAM + // OtherSect => Usually link to RAM + // Alignment is 2^x + // +#elif defined (__IASMARM__) + #define _CC_TYPE _CCIAR + #define _PUB_SYM PUBLIC + #define _EXT_SYM EXTERN + #define _END END + #define _WEAK _WEAK + #define _THUMB_FUNC + #define _THUMB_CODE THUMB + #define _WORD DCD + #define _SECTION(Sect, Type, AlignExp) SECTION Sect ## : ## Type ## :REORDER:NOROOT ## (AlignExp) + #define _ALIGN(Exp) alignrom Exp + #define _PLACE_LITS + #define _DATA_SECT_START DATA + #define _C_STARTUP __iar_program_start + #define _STACK_END sfe(CSTACK) + #define _RAMFUNC SECTION_TYPE SHT_PROGBITS, SHF_WRITE | SHF_EXECINSTR + // + // .text => Link to flash + // .textrw => Link to RAM + // OtherSect => Usually link to RAM + // NOROOT => Allows linker to throw away the function, if not referenced + // Alignment is 2^x + // +#endif + +#if (_CC_TYPE == _CCIAR) + NAME SEGGER_RTT_ASM_ARMv7M +#else + .syntax unified +#endif + +#if defined (RTT_USE_ASM) && (RTT_USE_ASM == 1) + #define SHT_PROGBITS 0x1 + +/********************************************************************* +* +* Public / external symbols +* +********************************************************************** +*/ + + _EXT_SYM __aeabi_memcpy + _EXT_SYM __aeabi_memcpy4 + _EXT_SYM _SEGGER_RTT + + _PUB_SYM SEGGER_RTT_ASM_WriteSkipNoLock + +/********************************************************************* +* +* SEGGER_RTT_WriteSkipNoLock +* +* Function description +* Stores a specified number of characters in SEGGER RTT +* control block which is then read by the host. +* SEGGER_RTT_WriteSkipNoLock does not lock the application and +* skips all data, if the data does not fit into the buffer. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used (e.g. 0 for "Terminal"). +* pBuffer Pointer to character array. Does not need to point to a \0 terminated string. +* NumBytes Number of bytes to be stored in the SEGGER RTT control block. +* MUST be > 0!!! +* This is done for performance reasons, so no initial check has do be done. +* +* Return value +* 1: Data has been copied +* 0: No space, data has not been copied +* +* Notes +* (1) If there is not enough space in the "Up"-buffer, all data is dropped. +* (2) For performance reasons this function does not call Init() +* and may only be called after RTT has been initialized. +* Either by calling SEGGER_RTT_Init() or calling another RTT API function first. +*/ + _SECTION(.text, CODE, 2) + _ALIGN(2) + _THUMB_FUNC +SEGGER_RTT_ASM_WriteSkipNoLock: // unsigned SEGGER_RTT_WriteSkipNoLock(unsigned BufferIndex, const void* pData, unsigned NumBytes) { + // + // Cases: + // 1) RdOff <= WrOff => Space until wrap-around is sufficient + // 2) RdOff <= WrOff => Space after wrap-around needed (copy in 2 chunks) + // 3) RdOff < WrOff => No space in buf + // 4) RdOff > WrOff => Space is sufficient + // 5) RdOff > WrOff => No space in buf + // + // 1) is the most common case for large buffers and assuming that J-Link reads the data fast enough + // + // Register usage: + // R0 Temporary needed as RdOff, register later on + // R1 pData + // R2 + // R3 register. Hold free for subroutine calls + // R4 + // R5 pRing->pBuffer + // R6 pRing (Points to active struct SEGGER_RTT_BUFFER_DOWN) + // R7 WrOff + // + PUSH {R4-R7} + ADD R3,R0,R0, LSL #+1 + LDR.W R0,=_SEGGER_RTT // pRing = &_SEGGER_RTT.aUp[BufferIndex]; + ADD R0,R0,R3, LSL #+3 + ADD R6,R0,#+24 + LDR R0,[R6, #+16] // RdOff = pRing->RdOff; + LDR R7,[R6, #+12] // WrOff = pRing->WrOff; + LDR R5,[R6, #+4] // pRing->pBuffer + CMP R7,R0 + BCC.N _CheckCase4 // if (RdOff <= WrOff) { => Case 1), 2) or 3) + // + // Handling for case 1, later on identical to case 4 + // + LDR R3,[R6, #+8] // Avail = pRing->SizeOfBuffer - WrOff - 1u; => Space until wrap-around (assume 1 byte not usable for case that RdOff == 0) + SUBS R4,R3,R7 // (Used in case we jump into case 2 afterwards) + SUBS R3,R4,#+1 // + CMP R3,R2 + BCC.N _CheckCase2 // if (Avail >= NumBytes) { => Case 1)? +_Case4: + ADDS R5,R7,R5 // pBuffer += WrOff + ADDS R0,R2,R7 // v = WrOff + NumBytes + // + // 2x unrolling for the copy loop that is used most of the time + // This is a special optimization for small SystemView packets and makes them even faster + // + _ALIGN(2) +_LoopCopyStraight: // memcpy(pRing->pBuffer + WrOff, pData, NumBytes); + LDRB R3,[R1], #+1 + STRB R3,[R5], #+1 // *pDest++ = *pSrc++ + SUBS R2,R2,#+1 + BEQ _CSDone + LDRB R3,[R1], #+1 + STRB R3,[R5], #+1 // *pDest++ = *pSrc++ + SUBS R2,R2,#+1 + BNE _LoopCopyStraight +_CSDone: + STR R0,[R6, #+12] // pRing->WrOff = WrOff + NumBytes; + MOVS R0,#+1 + POP {R4-R7} + BX LR // Return 1 +_CheckCase2: + ADDS R0,R0,R3 // Avail += RdOff; => Space incl. wrap-around + CMP R0,R2 + BCC.N _Case3 // if (Avail >= NumBytes) { => Case 2? => If not, we have case 3) (does not fit) + // + // Handling for case 2 + // + ADDS R0,R7,R5 // v = pRing->pBuffer + WrOff => Do not change pRing->pBuffer here because 2nd chunk needs org. value + SUBS R2,R2,R4 // NumBytes -= Rem; (Rem = pRing->SizeOfBuffer - WrOff; => Space until end of buffer) +_LoopCopyBeforeWrapAround: // memcpy(pRing->pBuffer + WrOff, pData, Rem); => Copy 1st chunk + LDRB R3,[R1], #+1 + STRB R3,[R0], #+1 // *pDest++ = *pSrc++ + SUBS R4,R4,#+1 + BNE _LoopCopyBeforeWrapAround + // + // Special case: First check that assumed RdOff == 0 calculated that last element before wrap-around could not be used + // But 2nd check (considering space until wrap-around and until RdOff) revealed that RdOff is not 0, so we can use the last element + // In this case, we may use a copy straight until buffer end anyway without needing to copy 2 chunks + // Therefore, check if 2nd memcpy is necessary at all + // + ADDS R4,R2,#+0 // Save (needed as counter in loop but must be written to after the loop). Also use this inst to update the flags to skip 2nd loop if possible + BEQ.N _No2ChunkNeeded // if (NumBytes) { +_LoopCopyAfterWrapAround: // memcpy(pRing->pBuffer, pData + Rem, NumBytes); + LDRB R3,[R1], #+1 // pData already points to the next src byte due to copy loop increment before this loop + STRB R3,[R5], #+1 // *pDest++ = *pSrc++ + SUBS R2,R2,#+1 + BNE _LoopCopyAfterWrapAround +_No2ChunkNeeded: + STR R4,[R6, #+12] // pRing->WrOff = NumBytes; => Must be written after copying data because J-Link may read control block asynchronously while writing into buffer + MOVS R0,#+1 + POP {R4-R7} + BX LR // Return 1 +_CheckCase4: + SUBS R0,R0,R7 + SUBS R0,R0,#+1 // Avail = RdOff - WrOff - 1u; + CMP R0,R2 + BCS.N _Case4 // if (Avail >= NumBytes) { => Case 4) == 1) ? => If not, we have case 5) == 3) (does not fit) +_Case3: + MOVS R0,#+0 + POP {R4-R7} + BX LR // Return 0 + _PLACE_LITS + +#endif // defined (RTT_USE_ASM) && (RTT_USE_ASM == 1) + _END + +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/src/SEGGER_RTT_printf.c b/lib/segger_rtt/src/SEGGER_RTT_printf.c new file mode 100644 index 000000000..6a83cd91b --- /dev/null +++ b/lib/segger_rtt/src/SEGGER_RTT_printf.c @@ -0,0 +1,500 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_printf.c +Purpose : Replacement for printf to write formatted data via RTT +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ +#include "SEGGER_RTT.h" +#include "SEGGER_RTT_Conf.h" + +/********************************************************************* +* +* Defines, configurable +* +********************************************************************** +*/ + +#ifndef SEGGER_RTT_PRINTF_BUFFER_SIZE + #define SEGGER_RTT_PRINTF_BUFFER_SIZE (64) +#endif + +#include +#include + + +#define FORMAT_FLAG_LEFT_JUSTIFY (1u << 0) +#define FORMAT_FLAG_PAD_ZERO (1u << 1) +#define FORMAT_FLAG_PRINT_SIGN (1u << 2) +#define FORMAT_FLAG_ALTERNATE (1u << 3) + +/********************************************************************* +* +* Types +* +********************************************************************** +*/ + +typedef struct { + char* pBuffer; + unsigned BufferSize; + unsigned Cnt; + + int ReturnValue; + + unsigned RTTBufferIndex; +} SEGGER_RTT_PRINTF_DESC; + +/********************************************************************* +* +* Function prototypes +* +********************************************************************** +*/ + +/********************************************************************* +* +* Static code +* +********************************************************************** +*/ +/********************************************************************* +* +* _StoreChar +*/ +static void _StoreChar(SEGGER_RTT_PRINTF_DESC * p, char c) { + unsigned Cnt; + + Cnt = p->Cnt; + if ((Cnt + 1u) <= p->BufferSize) { + *(p->pBuffer + Cnt) = c; + p->Cnt = Cnt + 1u; + p->ReturnValue++; + } + // + // Write part of string, when the buffer is full + // + if (p->Cnt == p->BufferSize) { + if (SEGGER_RTT_Write(p->RTTBufferIndex, p->pBuffer, p->Cnt) != p->Cnt) { + p->ReturnValue = -1; + } else { + p->Cnt = 0u; + } + } +} + +/********************************************************************* +* +* _PrintUnsigned +*/ +static void _PrintUnsigned(SEGGER_RTT_PRINTF_DESC * pBufferDesc, unsigned v, unsigned Base, unsigned NumDigits, unsigned FieldWidth, unsigned FormatFlags) { + static const char _aV2C[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + unsigned Div; + unsigned Digit; + unsigned Number; + unsigned Width; + char c; + + Number = v; + Digit = 1u; + // + // Get actual field width + // + Width = 1u; + while (Number >= Base) { + Number = (Number / Base); + Width++; + } + if (NumDigits > Width) { + Width = NumDigits; + } + // + // Print leading chars if necessary + // + if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) { + if (FieldWidth != 0u) { + if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && (NumDigits == 0u)) { + c = '0'; + } else { + c = ' '; + } + while ((FieldWidth != 0u) && (Width < FieldWidth)) { + FieldWidth--; + _StoreChar(pBufferDesc, c); + if (pBufferDesc->ReturnValue < 0) { + break; + } + } + } + } + if (pBufferDesc->ReturnValue >= 0) { + // + // Compute Digit. + // Loop until Digit has the value of the highest digit required. + // Example: If the output is 345 (Base 10), loop 2 times until Digit is 100. + // + while (1) { + if (NumDigits > 1u) { // User specified a min number of digits to print? => Make sure we loop at least that often, before checking anything else (> 1 check avoids problems with NumDigits being signed / unsigned) + NumDigits--; + } else { + Div = v / Digit; + if (Div < Base) { // Is our divider big enough to extract the highest digit from value? => Done + break; + } + } + Digit *= Base; + } + // + // Output digits + // + do { + Div = v / Digit; + v -= Div * Digit; + _StoreChar(pBufferDesc, _aV2C[Div]); + if (pBufferDesc->ReturnValue < 0) { + break; + } + Digit /= Base; + } while (Digit); + // + // Print trailing spaces if necessary + // + if ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == FORMAT_FLAG_LEFT_JUSTIFY) { + if (FieldWidth != 0u) { + while ((FieldWidth != 0u) && (Width < FieldWidth)) { + FieldWidth--; + _StoreChar(pBufferDesc, ' '); + if (pBufferDesc->ReturnValue < 0) { + break; + } + } + } + } + } +} + +/********************************************************************* +* +* _PrintInt +*/ +static void _PrintInt(SEGGER_RTT_PRINTF_DESC * pBufferDesc, int v, unsigned Base, unsigned NumDigits, unsigned FieldWidth, unsigned FormatFlags) { + unsigned Width; + int Number; + + Number = (v < 0) ? -v : v; + + // + // Get actual field width + // + Width = 1u; + while (Number >= (int)Base) { + Number = (Number / (int)Base); + Width++; + } + if (NumDigits > Width) { + Width = NumDigits; + } + if ((FieldWidth > 0u) && ((v < 0) || ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN))) { + FieldWidth--; + } + + // + // Print leading spaces if necessary + // + if ((((FormatFlags & FORMAT_FLAG_PAD_ZERO) == 0u) || (NumDigits != 0u)) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u)) { + if (FieldWidth != 0u) { + while ((FieldWidth != 0u) && (Width < FieldWidth)) { + FieldWidth--; + _StoreChar(pBufferDesc, ' '); + if (pBufferDesc->ReturnValue < 0) { + break; + } + } + } + } + // + // Print sign if necessary + // + if (pBufferDesc->ReturnValue >= 0) { + if (v < 0) { + v = -v; + _StoreChar(pBufferDesc, '-'); + } else if ((FormatFlags & FORMAT_FLAG_PRINT_SIGN) == FORMAT_FLAG_PRINT_SIGN) { + _StoreChar(pBufferDesc, '+'); + } else { + + } + if (pBufferDesc->ReturnValue >= 0) { + // + // Print leading zeros if necessary + // + if (((FormatFlags & FORMAT_FLAG_PAD_ZERO) == FORMAT_FLAG_PAD_ZERO) && ((FormatFlags & FORMAT_FLAG_LEFT_JUSTIFY) == 0u) && (NumDigits == 0u)) { + if (FieldWidth != 0u) { + while ((FieldWidth != 0u) && (Width < FieldWidth)) { + FieldWidth--; + _StoreChar(pBufferDesc, '0'); + if (pBufferDesc->ReturnValue < 0) { + break; + } + } + } + } + if (pBufferDesc->ReturnValue >= 0) { + // + // Print number without sign + // + _PrintUnsigned(pBufferDesc, (unsigned)v, Base, NumDigits, FieldWidth, FormatFlags); + } + } + } +} + +/********************************************************************* +* +* Public code +* +********************************************************************** +*/ +/********************************************************************* +* +* SEGGER_RTT_vprintf +* +* Function description +* Stores a formatted string in SEGGER RTT control block. +* This data is read by the host. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used. (e.g. 0 for "Terminal") +* sFormat Pointer to format string +* pParamList Pointer to the list of arguments for the format string +* +* Return values +* >= 0: Number of bytes which have been stored in the "Up"-buffer. +* < 0: Error +*/ +int SEGGER_RTT_vprintf(unsigned BufferIndex, const char * sFormat, va_list * pParamList) { + char c; + SEGGER_RTT_PRINTF_DESC BufferDesc; + int v; + unsigned NumDigits; + unsigned FormatFlags; + unsigned FieldWidth; + char acBuffer[SEGGER_RTT_PRINTF_BUFFER_SIZE]; + + BufferDesc.pBuffer = acBuffer; + BufferDesc.BufferSize = SEGGER_RTT_PRINTF_BUFFER_SIZE; + BufferDesc.Cnt = 0u; + BufferDesc.RTTBufferIndex = BufferIndex; + BufferDesc.ReturnValue = 0; + + do { + c = *sFormat; + sFormat++; + if (c == 0u) { + break; + } + if (c == '%') { + // + // Filter out flags + // + FormatFlags = 0u; + v = 1; + do { + c = *sFormat; + switch (c) { + case '-': FormatFlags |= FORMAT_FLAG_LEFT_JUSTIFY; sFormat++; break; + case '0': FormatFlags |= FORMAT_FLAG_PAD_ZERO; sFormat++; break; + case '+': FormatFlags |= FORMAT_FLAG_PRINT_SIGN; sFormat++; break; + case '#': FormatFlags |= FORMAT_FLAG_ALTERNATE; sFormat++; break; + default: v = 0; break; + } + } while (v); + // + // filter out field with + // + FieldWidth = 0u; + do { + c = *sFormat; + if ((c < '0') || (c > '9')) { + break; + } + sFormat++; + FieldWidth = (FieldWidth * 10u) + ((unsigned)c - '0'); + } while (1); + + // + // Filter out precision (number of digits to display) + // + NumDigits = 0u; + c = *sFormat; + if (c == '.') { + sFormat++; + do { + c = *sFormat; + if ((c < '0') || (c > '9')) { + break; + } + sFormat++; + NumDigits = NumDigits * 10u + ((unsigned)c - '0'); + } while (1); + } + // + // Filter out length modifier + // + c = *sFormat; + do { + if ((c == 'l') || (c == 'h')) { + sFormat++; + c = *sFormat; + } else { + break; + } + } while (1); + // + // Handle specifiers + // + switch (c) { + case 'c': { + char c0; + v = va_arg(*pParamList, int); + c0 = (char)v; + _StoreChar(&BufferDesc, c0); + break; + } + case 'd': + v = va_arg(*pParamList, int); + _PrintInt(&BufferDesc, v, 10u, NumDigits, FieldWidth, FormatFlags); + break; + case 'u': + v = va_arg(*pParamList, int); + _PrintUnsigned(&BufferDesc, (unsigned)v, 10u, NumDigits, FieldWidth, FormatFlags); + break; + case 'x': + case 'X': + v = va_arg(*pParamList, int); + _PrintUnsigned(&BufferDesc, (unsigned)v, 16u, NumDigits, FieldWidth, FormatFlags); + break; + case 's': + { + const char * s = va_arg(*pParamList, const char *); + do { + c = *s; + s++; + if (c == '\0') { + break; + } + _StoreChar(&BufferDesc, c); + } while (BufferDesc.ReturnValue >= 0); + } + break; + case 'p': + v = va_arg(*pParamList, int); + _PrintUnsigned(&BufferDesc, (unsigned)v, 16u, 8u, 8u, 0u); + break; + case '%': + _StoreChar(&BufferDesc, '%'); + break; + default: + break; + } + sFormat++; + } else { + _StoreChar(&BufferDesc, c); + } + } while (BufferDesc.ReturnValue >= 0); + + if (BufferDesc.ReturnValue > 0) { + // + // Write remaining data, if any + // + if (BufferDesc.Cnt != 0u) { + SEGGER_RTT_Write(BufferIndex, acBuffer, BufferDesc.Cnt); + } + BufferDesc.ReturnValue += (int)BufferDesc.Cnt; + } + return BufferDesc.ReturnValue; +} + +/********************************************************************* +* +* SEGGER_RTT_printf +* +* Function description +* Stores a formatted string in SEGGER RTT control block. +* This data is read by the host. +* +* Parameters +* BufferIndex Index of "Up"-buffer to be used. (e.g. 0 for "Terminal") +* sFormat Pointer to format string, followed by the arguments for conversion +* +* Return values +* >= 0: Number of bytes which have been stored in the "Up"-buffer. +* < 0: Error +* +* Notes +* (1) Conversion specifications have following syntax: +* %[flags][FieldWidth][.Precision]ConversionSpecifier +* (2) Supported flags: +* -: Left justify within the field width +* +: Always print sign extension for signed conversions +* 0: Pad with 0 instead of spaces. Ignored when using '-'-flag or precision +* Supported conversion specifiers: +* c: Print the argument as one char +* d: Print the argument as a signed integer +* u: Print the argument as an unsigned integer +* x: Print the argument as an hexadecimal integer +* s: Print the string pointed to by the argument +* p: Print the argument as an 8-digit hexadecimal integer. (Argument shall be a pointer to void.) +*/ +int SEGGER_RTT_printf(unsigned BufferIndex, const char * sFormat, ...) { + int r; + va_list ParamList; + + va_start(ParamList, sFormat); + r = SEGGER_RTT_vprintf(BufferIndex, sFormat, &ParamList); + va_end(ParamList); + return r; +} +/*************************** End of file ****************************/ diff --git a/src/configuration.h b/src/configuration.h index 733e40a76..98638606d 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -58,11 +58,19 @@ along with this program. If not, see . // DEBUG // ----------------------------------------------------------------------------- +#ifndef NO_ESP32 +#define USE_SEGGER +#endif +#ifdef USE_SEGGER +#include "SEGGER_RTT.h" +#define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__) +#else #ifdef DEBUG_PORT #define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__) #else #define DEBUG_MSG(...) #endif +#endif // ----------------------------------------------------------------------------- // OLED From 309e7be00cb42c385e7cf687ce9177636daf9b8f Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 13:52:46 -0700 Subject: [PATCH 054/197] use segger console on nrf52 --- bin/nrf52-console.sh | 1 + lib/segger_rtt/License.txt | 34 ++ lib/segger_rtt/README.txt | 20 + .../Syscalls/SEGGER_RTT_Syscalls_GCC.c | 121 ++++++ .../Syscalls/SEGGER_RTT_Syscalls_IAR.c | 115 ++++++ .../Syscalls/SEGGER_RTT_Syscalls_KEIL.c | 386 ++++++++++++++++++ .../Syscalls/SEGGER_RTT_Syscalls_SES.c | 247 +++++++++++ .../examples/Main_RTT_InputEchoApp.c | 43 ++ 8 files changed, 967 insertions(+) create mode 100755 bin/nrf52-console.sh create mode 100644 lib/segger_rtt/License.txt create mode 100644 lib/segger_rtt/README.txt create mode 100644 lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c create mode 100644 lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c create mode 100644 lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c create mode 100644 lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c create mode 100644 lib/segger_rtt/examples/Main_RTT_InputEchoApp.c diff --git a/bin/nrf52-console.sh b/bin/nrf52-console.sh new file mode 100755 index 000000000..c7ad903c1 --- /dev/null +++ b/bin/nrf52-console.sh @@ -0,0 +1 @@ +JLinkRTTViewer diff --git a/lib/segger_rtt/License.txt b/lib/segger_rtt/License.txt new file mode 100644 index 000000000..bc83e48a1 --- /dev/null +++ b/lib/segger_rtt/License.txt @@ -0,0 +1,34 @@ +Important - Read carefully: + +SEGGER RTT - Real Time Transfer for embedded targets + +All rights reserved. + +SEGGER strongly recommends to not make any changes +to or modify the source code of this software in order to stay +compatible with the RTT protocol and J-Link. + +Redistribution and use in source and binary forms, with or +without modification, are permitted provided that the following +condition is met: + +o Redistributions of source code must retain the above copyright + notice, this condition and the following disclaimer. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + + +(c) 2014 - 2016 SEGGER Microcontroller GmbH +www.segger.com diff --git a/lib/segger_rtt/README.txt b/lib/segger_rtt/README.txt new file mode 100644 index 000000000..4dd23eba2 --- /dev/null +++ b/lib/segger_rtt/README.txt @@ -0,0 +1,20 @@ +README.txt for the SEGGER RTT Implementation Pack. + +Included files: +=============== +Root Directory + - Examples + - Main_RTT_InputEchoApp.c - Sample application which echoes input on Channel 0. + - Main_RTT_MenuApp.c - Sample application to demonstrate RTT bi-directional functionality. + - Main_RTT_PrintfTest.c - Sample application to test RTT small printf implementation. + - Main_RTT_SpeedTestApp.c - Sample application for measuring RTT performance. embOS needed. + - RTT + - SEGGER_RTT.c - The RTT implementation. + - SEGGER_RTT.h - Header for RTT implementation. + - SEGGER_RTT_Conf.h - Pre-processor configuration for the RTT implementation. + - SEGGER_RTT_Printf.c - Simple implementation of printf to write formatted strings via RTT. + - Syscalls + - RTT_Syscalls_GCC.c - Low-level syscalls to retarget printf() to RTT with GCC / Newlib. + - RTT_Syscalls_IAR.c - Low-level syscalls to retarget printf() to RTT with IAR compiler. + - RTT_Syscalls_KEIL.c - Low-level syscalls to retarget printf() to RTT with KEIL/uVision compiler. + - RTT_Syscalls_SES.c - Low-level syscalls to retarget printf() to RTT with SEGGER Embedded Studio. diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c new file mode 100644 index 000000000..32dab72bf --- /dev/null +++ b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_GCC.c @@ -0,0 +1,121 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_Syscalls_GCC.c +Purpose : Low-level functions for using printf() via RTT in GCC. + To use RTT for printf output, include this file in your + application. +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ +#if (defined __GNUC__) && !(defined __SES_ARM) && !(defined __CROSSWORKS_ARM) + +#include "SEGGER_RTT.h" +#include // required for _write_r + +/********************************************************************* + * + * Types + * + ********************************************************************** + */ +// +// If necessary define the _reent struct +// to match the one passed by the used standard library. +// +struct _reent; + +/********************************************************************* + * + * Function prototypes + * + ********************************************************************** + */ +int _write(int file, char *ptr, int len); +// _ssize_t _write_r(struct _reent *r, int file, const void *ptr, int len); + +/********************************************************************* + * + * Global functions + * + ********************************************************************** + */ + +/********************************************************************* + * + * _write() + * + * Function description + * Low-level write function. + * libc subroutines will use this system routine for output to all files, + * including stdout. + * Write data via RTT. + */ +int _write(int file, char *ptr, int len) +{ + (void)file; /* Not used, avoid warning */ + SEGGER_RTT_Write(0, ptr, len); + return len; +} + +/********************************************************************* + * + * _write_r() + * + * Function description + * Low-level reentrant write function. + * libc subroutines will use this system routine for output to all files, + * including stdout. + * Write data via RTT. + */ +_ssize_t _write_r(struct _reent *r, int file, const void *ptr, int len) +{ + (void)file; /* Not used, avoid warning */ + (void)r; /* Not used, avoid warning */ + SEGGER_RTT_Write(0, ptr, len); + return len; +} + +#endif +/****** End Of File *************************************************/ diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c new file mode 100644 index 000000000..11a968941 --- /dev/null +++ b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_IAR.c @@ -0,0 +1,115 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_Syscalls_IAR.c +Purpose : Low-level functions for using printf() via RTT in IAR. + To use RTT for printf output, include this file in your + application and set the Library Configuration to Normal. +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ +#ifdef __IAR_SYSTEMS_ICC__ + +// +// Since IAR EWARM V8 and EWRX V4, yfuns.h is considered as deprecated and LowLevelIOInterface.h +// shall be used instead. To not break any compatibility with older compiler versions, we have a +// version check in here. +// +#if ((defined __ICCARM__) && (__VER__ >= 8000000)) || ((defined __ICCRX__) && (__VER__ >= 400)) + #include +#else + #include +#endif + +#include "SEGGER_RTT.h" +#pragma module_name = "?__write" + +/********************************************************************* +* +* Function prototypes +* +********************************************************************** +*/ +size_t __write(int handle, const unsigned char * buffer, size_t size); + +/********************************************************************* +* +* Global functions +* +********************************************************************** +*/ +/********************************************************************* +* +* __write() +* +* Function description +* Low-level write function. +* Standard library subroutines will use this system routine +* for output to all files, including stdout. +* Write data via RTT. +*/ +size_t __write(int handle, const unsigned char * buffer, size_t size) { + (void) handle; /* Not used, avoid warning */ + SEGGER_RTT_Write(0, (const char*)buffer, size); + return size; +} + +/********************************************************************* +* +* __write_buffered() +* +* Function description +* Low-level write function. +* Standard library subroutines will use this system routine +* for output to all files, including stdout. +* Write data via RTT. +*/ +size_t __write_buffered(int handle, const unsigned char * buffer, size_t size) { + (void) handle; /* Not used, avoid warning */ + SEGGER_RTT_Write(0, (const char*)buffer, size); + return size; +} + +#endif +/****** End Of File *************************************************/ diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c new file mode 100644 index 000000000..f84bc1a19 --- /dev/null +++ b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_KEIL.c @@ -0,0 +1,386 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : RTT_Syscalls_KEIL.c +Purpose : Retargeting module for KEIL MDK-CM3. + Low-level functions for using printf() via RTT +Revision: $Rev: 17697 $ +---------------------------------------------------------------------- +*/ +#ifdef __CC_ARM + +#include +#include +#include +#include +#include + +#include "SEGGER_RTT.h" +/********************************************************************* +* +* #pragmas +* +********************************************************************** +*/ +#pragma import(__use_no_semihosting) + +#ifdef _MICROLIB + #pragma import(__use_full_stdio) +#endif + +/********************************************************************* +* +* Defines non-configurable +* +********************************************************************** +*/ + +/* Standard IO device handles - arbitrary, but any real file system handles must be + less than 0x8000. */ +#define STDIN 0x8001 // Standard Input Stream +#define STDOUT 0x8002 // Standard Output Stream +#define STDERR 0x8003 // Standard Error Stream + +/********************************************************************* +* +* Public const +* +********************************************************************** +*/ +#if __ARMCC_VERSION < 5000000 +//const char __stdin_name[] = "STDIN"; +const char __stdout_name[] = "STDOUT"; +const char __stderr_name[] = "STDERR"; +#endif + +/********************************************************************* +* +* Public code +* +********************************************************************** +*/ + +/********************************************************************* +* +* _ttywrch +* +* Function description: +* Outputs a character to the console +* +* Parameters: +* c - character to output +* +*/ +void _ttywrch(int c) { + fputc(c, stdout); // stdout + fflush(stdout); +} + +/********************************************************************* +* +* _sys_open +* +* Function description: +* Opens the device/file in order to do read/write operations +* +* Parameters: +* sName - sName of the device/file to open +* OpenMode - This parameter is currently ignored +* +* Return value: +* != 0 - Handle to the object to open, otherwise +* == 0 -"device" is not handled by this module +* +*/ +FILEHANDLE _sys_open(const char * sName, int OpenMode) { + (void)OpenMode; + // Register standard Input Output devices. + if (strcmp(sName, __stdout_name) == 0) { + return (STDOUT); + } else if (strcmp(sName, __stderr_name) == 0) { + return (STDERR); + } else + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_close +* +* Function description: +* Closes the handle to the open device/file +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* +* Return value: +* 0 - device/file closed +* +*/ +int _sys_close(FILEHANDLE hFile) { + (void)hFile; + return 0; // Not implemented +} + +/********************************************************************* +* +* _sys_write +* +* Function description: +* Writes the data to an open handle. +* Currently this function only outputs data to the console +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* pBuffer - Pointer to the data that shall be written +* NumBytes - Number of bytes to write +* Mode - The Mode that shall be used +* +* Return value: +* Number of bytes *not* written to the file/device +* +*/ +int _sys_write(FILEHANDLE hFile, const unsigned char * pBuffer, unsigned NumBytes, int Mode) { + int r = 0; + + (void)Mode; + if (hFile == STDOUT) { + SEGGER_RTT_Write(0, (const char*)pBuffer, NumBytes); + return 0; + } + return r; +} + +/********************************************************************* +* +* _sys_read +* +* Function description: +* Reads data from an open handle. +* Currently this modules does nothing. +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* pBuffer - Pointer to buffer to store the read data +* NumBytes - Number of bytes to read +* Mode - The Mode that shall be used +* +* Return value: +* Number of bytes read from the file/device +* +*/ +int _sys_read(FILEHANDLE hFile, unsigned char * pBuffer, unsigned NumBytes, int Mode) { + (void)hFile; + (void)pBuffer; + (void)NumBytes; + (void)Mode; + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_istty +* +* Function description: +* This function shall return whether the opened file +* is a console device or not. +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* +* Return value: +* 1 - Device is a console +* 0 - Device is not a console +* +*/ +int _sys_istty(FILEHANDLE hFile) { + if (hFile > 0x8000) { + return (1); + } + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_seek +* +* Function description: +* Seeks via the file to a specific position +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* Pos - +* +* Return value: +* int - +* +*/ +int _sys_seek(FILEHANDLE hFile, long Pos) { + (void)hFile; + (void)Pos; + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_ensure +* +* Function description: +* +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* +* Return value: +* int - +* +*/ +int _sys_ensure(FILEHANDLE hFile) { + (void)hFile; + return (-1); // Not implemented +} + +/********************************************************************* +* +* _sys_flen +* +* Function description: +* Returns the length of the opened file handle +* +* Parameters: +* hFile - Handle to a file opened via _sys_open +* +* Return value: +* Length of the file +* +*/ +long _sys_flen(FILEHANDLE hFile) { + (void)hFile; + return (0); // Not implemented +} + +/********************************************************************* +* +* _sys_tmpnam +* +* Function description: +* This function converts the file number fileno for a temporary +* file to a unique filename, for example, tmp0001. +* +* Parameters: +* pBuffer - Pointer to a buffer to store the name +* FileNum - file number to convert +* MaxLen - Size of the buffer +* +* Return value: +* 1 - Error +* 0 - Success +* +*/ +int _sys_tmpnam(char * pBuffer, int FileNum, unsigned MaxLen) { + (void)pBuffer; + (void)FileNum; + (void)MaxLen; + return (1); // Not implemented +} + +/********************************************************************* +* +* _sys_command_string +* +* Function description: +* This function shall execute a system command. +* +* Parameters: +* cmd - Pointer to the command string +* len - Length of the string +* +* Return value: +* == NULL - Command was not successfully executed +* == sCmd - Command was passed successfully +* +*/ +char * _sys_command_string(char * cmd, int len) { + (void)len; + return cmd; // Not implemented +} + +/********************************************************************* +* +* _sys_exit +* +* Function description: +* This function is called when the application returns from main +* +* Parameters: +* ReturnCode - Return code from the main function +* +* +*/ +void _sys_exit(int ReturnCode) { + (void)ReturnCode; + while (1); // Not implemented +} + +#if __ARMCC_VERSION >= 5000000 +/********************************************************************* +* +* stdout_putchar +* +* Function description: +* Put a character to the stdout +* +* Parameters: +* ch - Character to output +* +* +*/ +int stdout_putchar(int ch) { + (void)ch; + return ch; // Not implemented +} +#endif + +#endif +/*************************** End of file ****************************/ diff --git a/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c new file mode 100644 index 000000000..3744f01fd --- /dev/null +++ b/lib/segger_rtt/Syscalls/SEGGER_RTT_Syscalls_SES.c @@ -0,0 +1,247 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* The Embedded Experts * +********************************************************************** +* * +* (c) 1995 - 2019 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** +* * +* SEGGER RTT * Real Time Transfer for embedded targets * +* * +********************************************************************** +* * +* All rights reserved. * +* * +* SEGGER strongly recommends to not make any changes * +* to or modify the source code of this software in order to stay * +* compatible with the RTT protocol and J-Link. * +* * +* Redistribution and use in source and binary forms, with or * +* without modification, are permitted provided that the following * +* condition is met: * +* * +* o Redistributions of source code must retain the above copyright * +* notice, this condition and the following disclaimer. * +* * +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * +* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * +* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * +* DISCLAIMED. IN NO EVENT SHALL SEGGER Microcontroller BE LIABLE FOR * +* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * +* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * +* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * +* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * +* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * +* DAMAGE. * +* * +********************************************************************** +---------------------------END-OF-HEADER------------------------------ +File : SEGGER_RTT_Syscalls_SES.c +Purpose : Reimplementation of printf, puts and __getchar using RTT + in SEGGER Embedded Studio. + To use RTT for printf output, include this file in your + application. +Revision: $Rev: 18539 $ +---------------------------------------------------------------------- +*/ +#if (defined __SES_ARM) || (defined __SES_RISCV) || (defined __CROSSWORKS_ARM) + +#include "SEGGER_RTT.h" +#include +#include +#include "limits.h" +#include "__libc.h" +#include "__vfprintf.h" + +/********************************************************************* +* +* Defines, configurable +* +********************************************************************** +*/ +// +// Select string formatting implementation. +// +// RTT printf formatting +// - Configurable stack usage. (SEGGER_RTT_PRINTF_BUFFER_SIZE in SEGGER_RTT_Conf.h) +// - No maximum string length. +// - Limited conversion specifiers and flags. (See SEGGER_RTT_printf.c) +// Standard library printf formatting +// - Configurable formatting capabilities. +// - Full conversion specifier and flag support. +// - Maximum string length has to be known or (slightly) slower character-wise output. +// +// #define PRINTF_USE_SEGGER_RTT_FORMATTING 0 // Use standard library formatting +// #define PRINTF_USE_SEGGER_RTT_FORMATTING 1 // Use RTT formatting +// +#ifndef PRINTF_USE_SEGGER_RTT_FORMATTING + #define PRINTF_USE_SEGGER_RTT_FORMATTING 0 +#endif +// +// If using standard library formatting, +// select maximum output string buffer size or character-wise output. +// +// #define PRINTF_BUFFER_SIZE 0 // Use character-wise output +// #define PRINTF_BUFFER_SIZE 128 // Default maximum string length +// +#ifndef PRINTF_BUFFER_SIZE + #define PRINTF_BUFFER_SIZE 128 +#endif + +#if PRINTF_USE_SEGGER_RTT_FORMATTING // Use SEGGER RTT formatting implementation +/********************************************************************* +* +* Function prototypes +* +********************************************************************** +*/ +int SEGGER_RTT_vprintf(unsigned BufferIndex, const char * sFormat, va_list * pParamList); + +/********************************************************************* +* +* Global functions, printf +* +********************************************************************** +*/ +/********************************************************************* +* +* printf() +* +* Function description +* print a formatted string using RTT and SEGGER RTT formatting. +*/ +int printf(const char *fmt,...) { + int n; + va_list args; + + va_start (args, fmt); + n = SEGGER_RTT_vprintf(0, fmt, &args); + va_end(args); + return n; +} + +#elif PRINTF_BUFFER_SIZE == 0 // Use standard library formatting with character-wise output + +/********************************************************************* +* +* Static functions +* +********************************************************************** +*/ +static int _putchar(int x, __printf_tag_ptr ctx) { + (void)ctx; + SEGGER_RTT_Write(0, (char *)&x, 1); + return x; +} + +/********************************************************************* +* +* Global functions, printf +* +********************************************************************** +*/ +/********************************************************************* +* +* printf() +* +* Function description +* print a formatted string character-wise, using RTT and standard +* library formatting. +*/ +int printf(const char *fmt, ...) { + int n; + va_list args; + __printf_t iod; + + va_start(args, fmt); + iod.string = 0; + iod.maxchars = INT_MAX; + iod.output_fn = _putchar; + SEGGER_RTT_LOCK(); + n = __vfprintf(&iod, fmt, args); + SEGGER_RTT_UNLOCK(); + va_end(args); + return n; +} + +#else // Use standard library formatting with static buffer + +/********************************************************************* +* +* Global functions, printf +* +********************************************************************** +*/ +/********************************************************************* +* +* printf() +* +* Function description +* print a formatted string using RTT and standard library formatting. +*/ +int printf(const char *fmt,...) { + int n; + char aBuffer[PRINTF_BUFFER_SIZE]; + va_list args; + + va_start (args, fmt); + n = vsnprintf(aBuffer, sizeof(aBuffer), fmt, args); + if (n > (int)sizeof(aBuffer)) { + SEGGER_RTT_Write(0, aBuffer, sizeof(aBuffer)); + } else if (n > 0) { + SEGGER_RTT_Write(0, aBuffer, n); + } + va_end(args); + return n; +} +#endif + +/********************************************************************* +* +* Global functions +* +********************************************************************** +*/ +/********************************************************************* +* +* puts() +* +* Function description +* print a string using RTT. +*/ +int puts(const char *s) { + return SEGGER_RTT_WriteString(0, s); +} + +/********************************************************************* +* +* __putchar() +* +* Function description +* Write one character via RTT. +*/ +int __putchar(int x, __printf_tag_ptr ctx) { + (void)ctx; + SEGGER_RTT_Write(0, (char *)&x, 1); + return x; +} + +/********************************************************************* +* +* __getchar() +* +* Function description +* Wait for and get a character via RTT. +*/ +int __getchar() { + return SEGGER_RTT_WaitKey(); +} + +#endif +/****** End Of File *************************************************/ diff --git a/lib/segger_rtt/examples/Main_RTT_InputEchoApp.c b/lib/segger_rtt/examples/Main_RTT_InputEchoApp.c new file mode 100644 index 000000000..156169283 --- /dev/null +++ b/lib/segger_rtt/examples/Main_RTT_InputEchoApp.c @@ -0,0 +1,43 @@ +/********************************************************************* +* SEGGER Microcontroller GmbH * +* Solutions for real time microcontroller applications * +********************************************************************** +* * +* (c) 1995 - 2018 SEGGER Microcontroller GmbH * +* * +* www.segger.com Support: support@segger.com * +* * +********************************************************************** + +--------- END-OF-HEADER -------------------------------------------- +File : Main_RTT_MenuApp.c +Purpose : Sample application to demonstrate RTT bi-directional functionality +*/ + +#define MAIN_C + +#include + +#include "SEGGER_RTT.h" + +volatile int _Cnt; +volatile int _Delay; + +static char r; + +/********************************************************************* +* +* main +*/ +void main(void) { + + SEGGER_RTT_WriteString(0, "SEGGER Real-Time-Terminal Sample\r\n"); + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_SKIP); + do { + r = SEGGER_RTT_WaitKey(); + SEGGER_RTT_Write(0, &r, 1); + r++; + } while (1); +} + +/*************************** End of file ****************************/ From 3e4ccef992d3a69c55a81a46ca16677dad48cda8 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 13:53:29 -0700 Subject: [PATCH 055/197] fix warnings --- src/screen.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/screen.cpp b/src/screen.cpp index db58a6072..38d0fdd30 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: %d", node->snr); + snprintf(signalStr, sizeof(signalStr), "Signal: %ld", node->snr); uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; @@ -593,7 +593,7 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin) static FrameCallback btFrames[] = {drawFrameBluetooth}; - snprintf(btPIN, sizeof(btPIN), "%06d", pin); + snprintf(btPIN, sizeof(btPIN), "%06lu", pin); ui.disableAllIndicators(); ui.setFrames(btFrames, 1); From ffe95f62ab0f6eb46d8ec7e5a868c54ec708a783 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 13:53:51 -0700 Subject: [PATCH 056/197] no need to pass in scl & sda into screen constructor --- docs/software/nrf52-TODO.md | 7 +++++++ src/main.cpp | 7 +++++-- src/screen.h | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 docs/software/nrf52-TODO.md diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md new file mode 100644 index 000000000..2606684c5 --- /dev/null +++ b/docs/software/nrf52-TODO.md @@ -0,0 +1,7 @@ + +* make a new boarddef with a variant.h file. Fix pins in that file. In particular: +#define PIN_SPI_MISO (46) +#define PIN_SPI_MOSI (45) +#define PIN_SPI_SCK (47) +#define PIN_WIRE_SDA (26) +#define PIN_WIRE_SCL (27) \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f9a12181c..12c362b93 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -52,7 +52,7 @@ bool pmu_irq = false; meshtastic::Screen screen(SSD1306_ADDRESS, I2C_SDA, I2C_SCL); #else // Fake values for pins to keep build happy, we won't ever initialize it. -meshtastic::Screen screen(SSD1306_ADDRESS, 0, 0); +meshtastic::Screen screen(SSD1306_ADDRESS); #endif // Global power status singleton @@ -248,8 +248,11 @@ void setup() #ifdef I2C_SDA Wire.begin(I2C_SDA, I2C_SCL); - scanI2Cdevice(); +#else + Wire.begin(); #endif + scanI2Cdevice(); + // Buttons & LED #ifdef BUTTON_PIN diff --git a/src/screen.h b/src/screen.h index 45b6a694a..017e6ae88 100644 --- a/src/screen.h +++ b/src/screen.h @@ -89,7 +89,7 @@ class DebugInfo class Screen : public PeriodicTask { public: - Screen(uint8_t address, uint8_t sda, uint8_t scl); + Screen(uint8_t address, uint8_t sda = 0, uint8_t scl = 0); Screen(const Screen &) = delete; Screen &operator=(const Screen &) = delete; From fbd12e19296f89ac54f8268850fe995a4186c9df Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 13:56:15 -0700 Subject: [PATCH 057/197] oled screen probably works now on nrf52 --- src/main.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 12c362b93..c25b2a9c2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,13 +47,8 @@ AXP20X_Class axp; bool pmu_irq = false; #endif -// Global Screen singleton -#ifdef I2C_SDA -meshtastic::Screen screen(SSD1306_ADDRESS, I2C_SDA, I2C_SCL); -#else -// Fake values for pins to keep build happy, we won't ever initialize it. +// We always create a screen object, but we only init it if we find the hardware meshtastic::Screen screen(SSD1306_ADDRESS); -#endif // Global power status singleton static meshtastic::PowerStatus powerStatus; From 2fdb75efdf9e0752a64f818df44be18ba7aff2f1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 16:20:07 -0700 Subject: [PATCH 058/197] make GPS 'work' on nrf52 --- platformio.ini | 1 + src/GPS.cpp | 10 +++++----- src/configuration.h | 38 +++++++++++++++++++------------------- src/screen.cpp | 2 +- src/screen.h | 2 +- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/platformio.ini b/platformio.ini index 83f6adac5..6505e6647 100644 --- a/platformio.ini +++ b/platformio.ini @@ -82,6 +82,7 @@ upload_speed = 921600 debug_init_break = tbreak setup build_flags = ${env.build_flags} -Wall -Wextra +lib_ignore = segger_rtt ; The 1.0 release of the TBEAM board [env:tbeam] diff --git a/src/GPS.cpp b/src/GPS.cpp index 60dedf429..668e9f02c 100644 --- a/src/GPS.cpp +++ b/src/GPS.cpp @@ -9,7 +9,7 @@ HardwareSerial _serial_gps(GPS_SERIAL_NUM); #else // Assume NRF52 -// Uart _serial_gps(GPS_SERIAL_NUM); +HardwareSerial &_serial_gps = Serial1; #endif bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep @@ -31,6 +31,9 @@ void GPS::setup() #ifdef GPS_RX_PIN _serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); +#else + _serial_gps.begin(GPS_BAUDRATE); +#endif // _serial_gps.setRxBufferSize(1024); // the default is 256 // ublox.enableDebugging(Serial); @@ -43,7 +46,7 @@ void GPS::setup() isConnected = ublox.begin(_serial_gps); if (isConnected) { - DEBUG_MSG("Connected to GPS successfully, TXpin=%d\n", GPS_TX_PIN); + DEBUG_MSG("Connected to GPS successfully\n"); bool factoryReset = false; bool ok; @@ -81,7 +84,6 @@ void GPS::setup() // checkUblox cyclically) ublox.assumeAutoPVT(true, true); } -#endif } void GPS::readFromRTC() @@ -145,7 +147,6 @@ void GPS::prepareSleep() void GPS::doTask() { -#ifdef GPS_RX_PIN uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix if (isConnected) { @@ -207,7 +208,6 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s } } else // we didn't get a location update, go back to sleep and hope the characters show up wantNewLocation = true; -#endif // Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over // the serial diff --git a/src/configuration.h b/src/configuration.h index 98638606d..cbf930f66 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -54,24 +54,6 @@ along with this program. If not, see . #define xstr(s) str(s) #define str(s) #s -// ----------------------------------------------------------------------------- -// DEBUG -// ----------------------------------------------------------------------------- - -#ifndef NO_ESP32 -#define USE_SEGGER -#endif -#ifdef USE_SEGGER -#include "SEGGER_RTT.h" -#define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__) -#else -#ifdef DEBUG_PORT -#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__) -#else -#define DEBUG_MSG(...) -#endif -#endif - // ----------------------------------------------------------------------------- // OLED // ----------------------------------------------------------------------------- @@ -217,7 +199,7 @@ along with this program. If not, see . #define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) -// Turn off GPS code for now +// We bind to the GPS using variant.h instead for this platform (Serial1) #undef GPS_RX_PIN #undef GPS_TX_PIN @@ -233,6 +215,24 @@ along with this program. If not, see . #endif +// ----------------------------------------------------------------------------- +// DEBUG +// ----------------------------------------------------------------------------- + +#ifdef NO_ESP32 +#define USE_SEGGER +#endif +#ifdef USE_SEGGER +#include "SEGGER_RTT.h" +#define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__) +#else +#ifdef DEBUG_PORT +#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__) +#else +#define DEBUG_MSG(...) +#endif +#endif + // ----------------------------------------------------------------------------- // AXP192 (Rev1-specific options) // ----------------------------------------------------------------------------- diff --git a/src/screen.cpp b/src/screen.cpp index 38d0fdd30..3fe089f0a 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -402,7 +402,7 @@ void _screen_header() } #endif -Screen::Screen(uint8_t address, uint8_t sda, uint8_t scl) : cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {} +Screen::Screen(uint8_t address, int sda, int scl) : cmdQueue(32), dispdev(address, sda, scl), ui(&dispdev) {} void Screen::handleSetOn(bool on) { diff --git a/src/screen.h b/src/screen.h index 017e6ae88..634cdd10c 100644 --- a/src/screen.h +++ b/src/screen.h @@ -89,7 +89,7 @@ class DebugInfo class Screen : public PeriodicTask { public: - Screen(uint8_t address, uint8_t sda = 0, uint8_t scl = 0); + Screen(uint8_t address, int sda = -1, int scl = -1); Screen(const Screen &) = delete; Screen &operator=(const Screen &) = delete; From 8f3b33c84c06ec0c9ae0e576b87263fc78ff7ae6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 16:55:25 -0700 Subject: [PATCH 059/197] use a real macaddr on the nrf52 --- docs/software/nrf52-TODO.md | 54 ++++++++++++++++++++++++++++++++++++- src/bare/main-bare.cpp | 3 ++- src/bare/main-nrf52.cpp | 21 +++++++++++++++ src/esp32/main-esp32.cpp | 5 ++++ src/main.cpp | 16 +---------- src/main.h | 2 ++ 6 files changed, 84 insertions(+), 17 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 2606684c5..88720e0f2 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -4,4 +4,56 @@ #define PIN_SPI_MOSI (45) #define PIN_SPI_SCK (47) #define PIN_WIRE_SDA (26) -#define PIN_WIRE_SCL (27) \ No newline at end of file +#define PIN_WIRE_SCL (27) + + +/* +per +https://docs.platformio.org/en/latest/tutorials/nordicnrf52/arduino_debugging_unit_testing.html + +ardunino github is here https://github.com/sandeepmistry/arduino-nRF5 +devboard hw docs here: +https://infocenter.nordicsemi.com/topic/ug_nrf52840_dk/UG/nrf52840_DK/hw_buttons_leds.html?cp=4_0_4_7_6 + +https://docs.platformio.org/en/latest/boards/nordicnrf52/nrf52840_dk_adafruit.html + +must install adafruit bootloader first! +https://learn.adafruit.com/circuitpython-on-the-nrf52/nrf52840-bootloader +see link above and turn off jlink filesystem if we see unreliable serial comms +over USBCDC + +adafruit bootloader install commands (from their readme) +kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$ +nrfjprog -e -f nrf52 Erasing user available code and UICR flash areas. Applying +system reset. +kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$ +nrfjprog --program pca10056/pca10056_bootloader-0.3.2_s140_6.1.1.hex -f nrf52 +Parsing hex file. +Reading flash area to program to guarantee it is erased. +Checking that the area to write is not protected. +Programming device. +kevinh@kevin-server:~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader$ +nrfjprog --reset -f nrf52 Applying system reset. Run. + +install jlink tools from here: +https://www.segger.com/downloads/jlink#J-LinkSoftwareAndDocumentationPack + +install nrf tools from here: +https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Command-Line-Tools/Download#infotabs + +examples of turning off the loop call to save power: +https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/advertising-beacon + +example of a more complex BLE service: +https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/custom-hrm +*/ + +// See g_ADigitalPinMap to see how arduino maps to the real gpio#s - and all in +// P0 +#define LED1 14 +#define LED2 13 + +/* +good led ble demo: +https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/Bluefruit52Lib/examples/Peripheral/nrf_blinky/nrf_blinky.ino +*/ \ No newline at end of file diff --git a/src/bare/main-bare.cpp b/src/bare/main-bare.cpp index 29532a639..d327c2384 100644 --- a/src/bare/main-bare.cpp +++ b/src/bare/main-bare.cpp @@ -3,4 +3,5 @@ void setBluetoothEnable(bool on) { // Do nothing -} \ No newline at end of file +} + diff --git a/src/bare/main-nrf52.cpp b/src/bare/main-nrf52.cpp index 1ccb79615..2c4bcbc19 100644 --- a/src/bare/main-nrf52.cpp +++ b/src/bare/main-nrf52.cpp @@ -1,4 +1,9 @@ #include +#include +#include +#include + +// #define USE_SOFTDEVICE static inline void debugger_break(void) { @@ -10,4 +15,20 @@ static inline void debugger_break(void) void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr) { debugger_break(); + while (1) + ; +} + +void getMacAddr(uint8_t *dmac) +{ + ble_gap_addr_t addr; + +#ifdef USE_SOFTDEVICE + uint32_t res = sd_ble_gap_addr_get(&addr); + assert(res == NRF_SUCCESS); + memcpy(dmac, addr.addr, 6); +#else + // FIXME - byte order might be wrong and high bits might be wrong + memcpy(dmac, (const void *)NRF_FICR->DEVICEADDR, 6); +#endif } \ No newline at end of file diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index baf0d85b7..e04231568 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -50,4 +50,9 @@ void setBluetoothEnable(bool on) // heap_trace_dump(); } } +} + +void getMacAddr(uint8_t *dmac) +{ + assert(esp_efuse_mac_get_default(dmac) == ESP_OK); } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c25b2a9c2..37bf0b14d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,6 +32,7 @@ #include "power.h" // #include "rom/rtc.h" #include "FloodingRouter.h" +#include "main.h" #include "screen.h" #include "sleep.h" #include @@ -192,20 +193,6 @@ void axp192Init() #endif } -void getMacAddr(uint8_t *dmac) -{ -#ifndef NO_ESP32 - assert(esp_efuse_mac_get_default(dmac) == ESP_OK); -#else - dmac[0] = 0xde; - dmac[1] = 0xad; - dmac[2] = 0xbe; - dmac[3] = 0xef; - dmac[4] = 0x01; - dmac[5] = 0x02; // FIXME, macaddr stuff needed for NRF52 -#endif -} - const char *getDeviceName() { uint8_t dmac[6]; @@ -248,7 +235,6 @@ void setup() #endif scanI2Cdevice(); - // Buttons & LED #ifdef BUTTON_PIN pinMode(BUTTON_PIN, INPUT_PULLUP); diff --git a/src/main.h b/src/main.h index c8c379715..2a29da591 100644 --- a/src/main.h +++ b/src/main.h @@ -12,3 +12,5 @@ extern meshtastic::Screen screen; // Return a human readable string of the form "Meshtastic_ab13" const char *getDeviceName(); + +void getMacAddr(uint8_t *dmac); \ No newline at end of file From 5b0451f25c32365d695f2646d7ad0762f9419a32 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 18:02:28 -0700 Subject: [PATCH 060/197] add NRF52 BLE example code --- docs/software/nrf52-TODO.md | 29 +++-- src/bare/NRF52Bluetooth.cpp | 206 ++++++++++++++++++++++++++++++++++++ src/bare/NRF52Bluetooth.h | 8 ++ src/bare/main-bare.cpp | 5 - src/bare/main-nrf52.cpp | 22 +++- 5 files changed, 257 insertions(+), 13 deletions(-) create mode 100644 src/bare/NRF52Bluetooth.cpp create mode 100644 src/bare/NRF52Bluetooth.h diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 88720e0f2..c9912bec1 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -1,12 +1,26 @@ +# Initial work items -* make a new boarddef with a variant.h file. Fix pins in that file. In particular: -#define PIN_SPI_MISO (46) -#define PIN_SPI_MOSI (45) -#define PIN_SPI_SCK (47) -#define PIN_WIRE_SDA (26) -#define PIN_WIRE_SCL (27) +- get old radio driver working on NRF52 +- get BLE working +- add PMU driver +- add new radio driver +- make a file system implementation (preferably one that can see the files the bootloader also sees) +- add LCD driver +- make a new boarddef with a variant.h file. Fix pins in that file. In particular: + #define PIN_SPI_MISO (46) + #define PIN_SPI_MOSI (45) + #define PIN_SPI_SCK (47) + #define PIN_WIRE_SDA (26) + #define PIN_WIRE_SCL (27) +# Secondary work items +- turn on security for BLE +- make power management/sleep work properly +- make a settimeofday implementation +- make ble endpoints not require "start config", jsut have them start in config mode + +``` /* per https://docs.platformio.org/en/latest/tutorials/nordicnrf52/arduino_debugging_unit_testing.html @@ -56,4 +70,5 @@ https://learn.adafruit.com/bluefruit-nrf52-feather-learning-guide/custom-hrm /* good led ble demo: https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/libraries/Bluefruit52Lib/examples/Peripheral/nrf_blinky/nrf_blinky.ino -*/ \ No newline at end of file +*/ +``` diff --git a/src/bare/NRF52Bluetooth.cpp b/src/bare/NRF52Bluetooth.cpp new file mode 100644 index 000000000..843a61173 --- /dev/null +++ b/src/bare/NRF52Bluetooth.cpp @@ -0,0 +1,206 @@ +#include "NRF52Bluetooth.h" +#include "configuration.h" +#include + +/* HRM Service Definitions + * Heart Rate Monitor Service: 0x180D + * Heart Rate Measurement Char: 0x2A37 + * Body Sensor Location Char: 0x2A38 + */ +BLEService hrms = BLEService(UUID16_SVC_HEART_RATE); +BLECharacteristic hrmc = BLECharacteristic(UUID16_CHR_HEART_RATE_MEASUREMENT); +BLECharacteristic bslc = BLECharacteristic(UUID16_CHR_BODY_SENSOR_LOCATION); + +BLEDis bledis; // DIS (Device Information Service) helper class instance +BLEBas blebas; // BAS (Battery Service) helper class instance + +uint8_t bps = 0; + +void connect_callback(uint16_t conn_handle) +{ + // Get the reference to current connection + BLEConnection *connection = Bluefruit.Connection(conn_handle); + + char central_name[32] = {0}; + connection->getPeerName(central_name, sizeof(central_name)); + + DEBUG_MSG("Connected to %s\n", central_name); +} + +/** + * Callback invoked when a connection is dropped + * @param conn_handle connection where this event happens + * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h + */ +void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void)conn_handle; + (void)reason; + + DEBUG_MSG("Disconnected, reason = 0x%x\n", reason); +} + +void cccd_callback(uint16_t conn_hdl, BLECharacteristic *chr, uint16_t cccd_value) +{ + // Display the raw request packet + DEBUG_MSG("CCCD Updated: %u\n", cccd_value); + + // Check the characteristic this CCCD update is associated with in case + // this handler is used for multiple CCCD records. + if (chr->uuid == hrmc.uuid) { + if (chr->notifyEnabled(conn_hdl)) { + DEBUG_MSG("Heart Rate Measurement 'Notify' enabled\n"); + } else { + DEBUG_MSG("Heart Rate Measurement 'Notify' disabled\n"); + } + } +} + +void startAdv(void) +{ + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + + // Include HRM Service UUID + Bluefruit.Advertising.addService(hrms); + + // Include Name + Bluefruit.Advertising.addName(); + + /* Start Advertising + * - Enable auto advertising if disconnected + * - Interval: fast mode = 20 ms, slow mode = 152.5 ms + * - Timeout for fast mode is 30 seconds + * - Start(timeout) with timeout = 0 will advertise forever (until connected) + * + * For recommended advertising interval + * https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds +} + +void setupHRM(void) +{ + // Configure the Heart Rate Monitor service + // See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.heart_rate.xml + // Supported Characteristics: + // Name UUID Requirement Properties + // ---------------------------- ------ ----------- ---------- + // Heart Rate Measurement 0x2A37 Mandatory Notify + // Body Sensor Location 0x2A38 Optional Read + // Heart Rate Control Point 0x2A39 Conditional Write <-- Not used here + hrms.begin(); + + // Note: You must call .begin() on the BLEService before calling .begin() on + // any characteristic(s) within that service definition.. Calling .begin() on + // a BLECharacteristic will cause it to be added to the last BLEService that + // was 'begin()'ed! + + // Configure the Heart Rate Measurement characteristic + // See: + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.heart_rate_measurement.xml + // Properties = Notify + // Min Len = 1 + // Max Len = 8 + // B0 = UINT8 - Flag (MANDATORY) + // b5:7 = Reserved + // b4 = RR-Internal (0 = Not present, 1 = Present) + // b3 = Energy expended status (0 = Not present, 1 = Present) + // b1:2 = Sensor contact status (0+1 = Not supported, 2 = Supported but contact not detected, 3 = Supported and + // detected) b0 = Value format (0 = UINT8, 1 = UINT16) + // B1 = UINT8 - 8-bit heart rate measurement value in BPM + // B2:3 = UINT16 - 16-bit heart rate measurement value in BPM + // B4:5 = UINT16 - Energy expended in joules + // B6:7 = UINT16 - RR Internal (1/1024 second resolution) + hrmc.setProperties(CHR_PROPS_NOTIFY); + hrmc.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + hrmc.setFixedLen(2); + hrmc.setCccdWriteCallback(cccd_callback); // Optionally capture CCCD updates + hrmc.begin(); + uint8_t hrmdata[2] = {0b00000110, 0x40}; // Set the characteristic to use 8-bit values, with the sensor connected and detected + hrmc.write(hrmdata, 2); + + // Configure the Body Sensor Location characteristic + // See: + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.body_sensor_location.xml + // Properties = Read + // Min Len = 1 + // Max Len = 1 + // B0 = UINT8 - Body Sensor Location + // 0 = Other + // 1 = Chest + // 2 = Wrist + // 3 = Finger + // 4 = Hand + // 5 = Ear Lobe + // 6 = Foot + // 7:255 = Reserved + bslc.setProperties(CHR_PROPS_READ); + bslc.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS); + bslc.setFixedLen(1); + bslc.begin(); + bslc.write8(2); // Set the characteristic to 'Wrist' (2) +} + +void NRF52Bluetooth::setup() +{ + // Initialise the Bluefruit module + DEBUG_MSG("Initialise the Bluefruit nRF52 module\n"); + Bluefruit.begin(); + + // Set the advertised device name (keep it short!) + Bluefruit.setName("Meshtastic52"); + + // Set the connect/disconnect callback handlers + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // Configure and Start the Device Information Service + DEBUG_MSG("Configuring the Device Information Service"); + bledis.setManufacturer("meshtastic.org"); + bledis.setModel("NRF52-meshtastic"); + bledis.begin(); + + // Start the BLE Battery Service and set it to 100% + DEBUG_MSG("Configuring the Battery Service"); + blebas.begin(); + blebas.write(42); // FIXME, report real power levels + + // Setup the Heart Rate Monitor service using + // BLEService and BLECharacteristic classes + DEBUG_MSG("Configuring the Heart Rate Monitor Service"); + setupHRM(); + + // Setup the advertising packet(s) + DEBUG_MSG("Setting up the advertising payload(s)\n"); + startAdv(); + + DEBUG_MSG("Advertising\n"); +} + +/* +void loop() +{ + digitalToggle(LED_RED); + + if ( Bluefruit.connected() ) { + uint8_t hrmdata[2] = { 0b00000110, bps++ }; // Sensor connected, increment BPS value + + // Note: We use .notify instead of .write! + // If it is connected but CCCD is not enabled + // The characteristic's value is still updated although notification is not sent + if ( hrmc.notify(hrmdata, sizeof(hrmdata)) ){ + Serial.print("Heart Rate Measurement updated to: "); Serial.println(bps); + }else{ + Serial.println("ERROR: Notify not set in the CCCD or not connected!"); + } + } + + // Only send update once per second + delay(1000); +} +*/ \ No newline at end of file diff --git a/src/bare/NRF52Bluetooth.h b/src/bare/NRF52Bluetooth.h new file mode 100644 index 000000000..40f13d8bc --- /dev/null +++ b/src/bare/NRF52Bluetooth.h @@ -0,0 +1,8 @@ +#pragma once + +class NRF52Bluetooth +{ + public: + void setup(); +}; + diff --git a/src/bare/main-bare.cpp b/src/bare/main-bare.cpp index d327c2384..fb1b4a27e 100644 --- a/src/bare/main-bare.cpp +++ b/src/bare/main-bare.cpp @@ -1,7 +1,2 @@ #include "target_specific.h" -void setBluetoothEnable(bool on) -{ - // Do nothing -} - diff --git a/src/bare/main-nrf52.cpp b/src/bare/main-nrf52.cpp index 2c4bcbc19..89180950e 100644 --- a/src/bare/main-nrf52.cpp +++ b/src/bare/main-nrf52.cpp @@ -1,3 +1,5 @@ +#include "NRF52Bluetooth.h" +#include "configuration.h" #include #include #include @@ -31,4 +33,22 @@ void getMacAddr(uint8_t *dmac) // FIXME - byte order might be wrong and high bits might be wrong memcpy(dmac, (const void *)NRF_FICR->DEVICEADDR, 6); #endif -} \ No newline at end of file +} + +NRF52Bluetooth *nrf52Bluetooth; + +static bool bleOn = false; +void setBluetoothEnable(bool on) +{ + if (on != bleOn) { + if (on) { + if (!nrf52Bluetooth) { + nrf52Bluetooth = new NRF52Bluetooth(); + nrf52Bluetooth->setup(); + } + } else { + DEBUG_MSG("FIXME: implement BLE disable\n"); + } + bleOn = on; + } +} From 0c7c3f17e5fbb26804e2acd3a1b53ed7c1c76883 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 18:06:46 -0700 Subject: [PATCH 061/197] fix nrf52 macaddr byte order --- src/bare/main-nrf52.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/bare/main-nrf52.cpp b/src/bare/main-nrf52.cpp index 89180950e..f825097ac 100644 --- a/src/bare/main-nrf52.cpp +++ b/src/bare/main-nrf52.cpp @@ -30,8 +30,13 @@ void getMacAddr(uint8_t *dmac) assert(res == NRF_SUCCESS); memcpy(dmac, addr.addr, 6); #else - // FIXME - byte order might be wrong and high bits might be wrong - memcpy(dmac, (const void *)NRF_FICR->DEVICEADDR, 6); + const uint8_t *src = (const uint8_t *)NRF_FICR->DEVICEADDR; + dmac[5] = src[0]; + dmac[4] = src[1]; + dmac[3] = src[2]; + dmac[2] = src[3]; + dmac[1] = src[4]; + dmac[0] = src[5] | 0xc0; // MSB high two bits get set elsewhere in the bluetooth stack #endif } From 4f3a9d864699394ca546cf0b8febf95e88cbef61 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 18:11:32 -0700 Subject: [PATCH 062/197] example BLE code approximately works --- docs/software/nrf52-TODO.md | 1 + src/bare/NRF52Bluetooth.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index c9912bec1..65b360b6d 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -15,6 +15,7 @@ # Secondary work items +- currently using soft device SD140, is that ideal? - turn on security for BLE - make power management/sleep work properly - make a settimeofday implementation diff --git a/src/bare/NRF52Bluetooth.cpp b/src/bare/NRF52Bluetooth.cpp index 843a61173..ea88b82b8 100644 --- a/src/bare/NRF52Bluetooth.cpp +++ b/src/bare/NRF52Bluetooth.cpp @@ -160,19 +160,19 @@ void NRF52Bluetooth::setup() Bluefruit.Periph.setDisconnectCallback(disconnect_callback); // Configure and Start the Device Information Service - DEBUG_MSG("Configuring the Device Information Service"); + DEBUG_MSG("Configuring the Device Information Service\n"); bledis.setManufacturer("meshtastic.org"); bledis.setModel("NRF52-meshtastic"); bledis.begin(); // Start the BLE Battery Service and set it to 100% - DEBUG_MSG("Configuring the Battery Service"); + DEBUG_MSG("Configuring the Battery Service\n"); blebas.begin(); blebas.write(42); // FIXME, report real power levels // Setup the Heart Rate Monitor service using // BLEService and BLECharacteristic classes - DEBUG_MSG("Configuring the Heart Rate Monitor Service"); + DEBUG_MSG("Configuring the Heart Rate Monitor Service\n"); setupHRM(); // Setup the advertising packet(s) From 3c9c01189d4b4124a35b311f9e1198b53d4294c5 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 18:47:27 -0700 Subject: [PATCH 063/197] old RF95 driver probably works on NRF52 now --- docs/software/nrf52-TODO.md | 4 ++-- platformio.ini | 7 ++++++- src/bare/NRF52Bluetooth.cpp | 4 ++-- src/configuration.h | 6 ++++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 65b360b6d..d42f5b0d3 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -1,9 +1,9 @@ # Initial work items -- get old radio driver working on NRF52 +- DONE get old radio driver working on NRF52 - get BLE working - add PMU driver -- add new radio driver +- add new radio driver - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ - make a file system implementation (preferably one that can see the files the bootloader also sees) - add LCD driver - make a new boarddef with a variant.h file. Fix pins in that file. In particular: diff --git a/platformio.ini b/platformio.ini index 6505e6647..2a4591987 100644 --- a/platformio.ini +++ b/platformio.ini @@ -135,4 +135,9 @@ lib_ignore = BluetoothOTA lib_deps = ${env.lib_deps} -monitor_port = /dev/ttyACM1 \ No newline at end of file +monitor_port = /dev/ttyACM1 + +; Set initial breakpoint (defaults to main) +debug_init_break = +;debug_init_break = tbreak loop +;debug_init_break = tbreak Reset_Handler \ No newline at end of file diff --git a/src/bare/NRF52Bluetooth.cpp b/src/bare/NRF52Bluetooth.cpp index ea88b82b8..4d9813f1d 100644 --- a/src/bare/NRF52Bluetooth.cpp +++ b/src/bare/NRF52Bluetooth.cpp @@ -153,7 +153,7 @@ void NRF52Bluetooth::setup() Bluefruit.begin(); // Set the advertised device name (keep it short!) - Bluefruit.setName("Meshtastic52"); + Bluefruit.setName("Meshtastic52"); // FIXME // Set the connect/disconnect callback handlers Bluefruit.Periph.setConnectCallback(connect_callback); @@ -162,7 +162,7 @@ void NRF52Bluetooth::setup() // Configure and Start the Device Information Service DEBUG_MSG("Configuring the Device Information Service\n"); bledis.setManufacturer("meshtastic.org"); - bledis.setModel("NRF52-meshtastic"); + bledis.setModel("NRF52-meshtastic"); // FIXME bledis.begin(); // Start the BLE Battery Service and set it to 100% diff --git a/src/configuration.h b/src/configuration.h index cbf930f66..6c474c92d 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -213,6 +213,12 @@ along with this program. If not, see . #undef LED_INVERTED #define LED_INVERTED 1 +// Temporarily testing if we can build the RF95 driver for NRF52 +#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio +#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio +#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number + #endif // ----------------------------------------------------------------------------- From 12599849dbd6e812b40f6840b9a34d7b73723507 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 18:52:34 -0700 Subject: [PATCH 064/197] update todo list --- docs/software/nrf52-TODO.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index d42f5b0d3..1ecd41508 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -1,12 +1,17 @@ # Initial work items +Minimum items needed to make sure hardware is good. + +- DONE select and install a bootloader (adafruit) - DONE get old radio driver working on NRF52 -- get BLE working +- DONE basic test of BLE +- DONE get a debug 'serial' console working via the ICE passthrough feater - add PMU driver - add new radio driver - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ -- make a file system implementation (preferably one that can see the files the bootloader also sees) - add LCD driver -- make a new boarddef with a variant.h file. Fix pins in that file. In particular: +- test the LEDs +- test the buttons +- make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): #define PIN_SPI_MISO (46) #define PIN_SPI_MOSI (45) #define PIN_SPI_SCK (47) @@ -15,11 +20,22 @@ # Secondary work items -- currently using soft device SD140, is that ideal? -- turn on security for BLE +Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. + +- get full BLE api working +- we need to enable the external xtal for the sx1262 (on dio3) +- figure out which regulator mode the sx1262 is operating in +- turn on security for BLE, make pairing work - make power management/sleep work properly - make a settimeofday implementation +- make a file system implementation (preferably one that can see the files the bootloader also sees) - make ble endpoints not require "start config", jsut have them start in config mode +- measure power management and confirm battery life + +# Items to be 'feature complete' + +- use the new buttons in the UX +- currently using soft device SD140, is that ideal? ``` /* From d445cbe083b6826fb33f3657bb0ff112a8787a03 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 23 Apr 2020 21:22:58 -0700 Subject: [PATCH 065/197] fix device name --- src/bare/NRF52Bluetooth.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bare/NRF52Bluetooth.cpp b/src/bare/NRF52Bluetooth.cpp index 4d9813f1d..676065a12 100644 --- a/src/bare/NRF52Bluetooth.cpp +++ b/src/bare/NRF52Bluetooth.cpp @@ -1,5 +1,6 @@ #include "NRF52Bluetooth.h" #include "configuration.h" +#include "main.h" #include /* HRM Service Definitions @@ -153,7 +154,7 @@ void NRF52Bluetooth::setup() Bluefruit.begin(); // Set the advertised device name (keep it short!) - Bluefruit.setName("Meshtastic52"); // FIXME + Bluefruit.setName(getDeviceName()); // FIXME // Set the connect/disconnect callback handlers Bluefruit.Periph.setConnectCallback(connect_callback); From 8f26ae240a748f283bab7d5fe0735fa088275fde Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 08:05:25 -0700 Subject: [PATCH 066/197] Add UC1701 and SX126X drivers (not yet using them though) --- platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platformio.ini b/platformio.ini index 2a4591987..5dee77968 100644 --- a/platformio.ini +++ b/platformio.ini @@ -73,6 +73,8 @@ lib_deps = Wire ; explicitly needed here because the AXP202 library forgets to add it https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git + https://github.com/meshtastic/SX126x-Arduino.git + UC1701 ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] From 5ad30caf672d36b437053d13260181f58eac247f Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 08:05:33 -0700 Subject: [PATCH 067/197] todo updates --- docs/software/nrf52-TODO.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 1ecd41508..7437030df 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -1,4 +1,6 @@ -# Initial work items +# NRF52 TODO + +## Initial work items Minimum items needed to make sure hardware is good. @@ -8,7 +10,7 @@ Minimum items needed to make sure hardware is good. - DONE get a debug 'serial' console working via the ICE passthrough feater - add PMU driver - add new radio driver - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ -- add LCD driver +- added UC1701 LCD driver. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen. - test the LEDs - test the buttons - make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): @@ -18,7 +20,7 @@ Minimum items needed to make sure hardware is good. #define PIN_WIRE_SDA (26) #define PIN_WIRE_SCL (27) -# Secondary work items +## Secondary work items Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. @@ -32,10 +34,20 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - make ble endpoints not require "start config", jsut have them start in config mode - measure power management and confirm battery life -# Items to be 'feature complete' +## Items to be 'feature complete' - use the new buttons in the UX - currently using soft device SD140, is that ideal? +- turn on the watchdog timer, require servicing from key application threads +- install a hardfault handler for null ptrs (if one isn't already installed) + +## Things to do 'someday' + +Nice ideas worth considering... + +- turn on DFU assistance in the appload using the nordic DFU helper lib call +- make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 +- convert hardfaults/panics/asserts/wd exceptions into fault codes sent to phone ``` /* From 4f7e85c1a494d7cd8fb637ba2584aacaa241df59 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 08:05:56 -0700 Subject: [PATCH 068/197] cleanup serial instanciation on boards where we might not use it --- src/configuration.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index 6c474c92d..25a4e25d7 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -46,9 +46,6 @@ along with this program. If not, see . //#define USE_JTAG #endif -#define DEBUG_PORT Serial // Serial debug port -#define SERIAL_BAUD 921600 // Serial debug baud rate - #define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found #define xstr(s) str(s) @@ -225,6 +222,8 @@ along with this program. If not, see . // DEBUG // ----------------------------------------------------------------------------- +#define SERIAL_BAUD 921600 // Serial debug baud rate + #ifdef NO_ESP32 #define USE_SEGGER #endif @@ -232,6 +231,8 @@ along with this program. If not, see . #include "SEGGER_RTT.h" #define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__) #else +#define DEBUG_PORT Serial // Serial debug port + #ifdef DEBUG_PORT #define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__) #else From 5e75beff3ff04cf9de1255b577c7b4a662eda808 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 08:06:29 -0700 Subject: [PATCH 069/197] don't block but queue log messages for the ICE (and eventual crash reports) --- src/main.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index 37bf0b14d..0737e4df5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -211,6 +211,10 @@ static MeshRadio *radio = NULL; void setup() { +#ifdef USE_SEGGER + SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_TRIM); +#endif + // Debug #ifdef DEBUG_PORT DEBUG_PORT.begin(SERIAL_BAUD); From e0a1855429a5edf6bac30031f3f7fc35f1553ec3 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 08:38:00 -0700 Subject: [PATCH 070/197] Add PMU driver --- docs/software/nrf52-TODO.md | 9 ++++++--- platformio.ini | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 7437030df..3128fc099 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -8,9 +8,10 @@ Minimum items needed to make sure hardware is good. - DONE get old radio driver working on NRF52 - DONE basic test of BLE - DONE get a debug 'serial' console working via the ICE passthrough feater -- add PMU driver -- add new radio driver - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ -- added UC1701 LCD driver. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen. +- Use the PMU driver +- add a NEMA based GPS driver to test GPS +- Use new radio driver - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ +- Use UC1701 LCD driver. Still need to create at startup and probe on SPI - test the LEDs - test the buttons - make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): @@ -24,6 +25,7 @@ Minimum items needed to make sure hardware is good. Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. +- use new LCD driver from screen.cpp. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen. - get full BLE api working - we need to enable the external xtal for the sx1262 (on dio3) - figure out which regulator mode the sx1262 is operating in @@ -45,6 +47,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering... +- make Lorro_BQ25703A read/write operations atomic, current version could let other threads sneak in (once we start using threads) - turn on DFU assistance in the appload using the nordic DFU helper lib call - make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 - convert hardfaults/panics/asserts/wd exceptions into fault codes sent to phone diff --git a/platformio.ini b/platformio.ini index 5dee77968..f6af1af4b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -74,7 +74,6 @@ lib_deps = https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git https://github.com/meshtastic/SX126x-Arduino.git - UC1701 ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] @@ -137,6 +136,8 @@ lib_ignore = BluetoothOTA lib_deps = ${env.lib_deps} + UC1701 + https://github.com/meshtastic/BQ25703A.git monitor_port = /dev/ttyACM1 ; Set initial breakpoint (defaults to main) From 7bc299573f284d5df98a2262e8067dd96dd46854 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 08:52:49 -0700 Subject: [PATCH 071/197] move esp32 specific code into esp32 land --- src/esp32/main-esp32.cpp | 167 +++++++++++++++++++++++++++++++++++++++ src/main.cpp | 162 +------------------------------------ 2 files changed, 171 insertions(+), 158 deletions(-) diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index e04231568..76999cc82 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -55,4 +55,171 @@ void setBluetoothEnable(bool on) void getMacAddr(uint8_t *dmac) { assert(esp_efuse_mac_get_default(dmac) == ESP_OK); +} + +#ifdef TBEAM_V10 +/// Reads power status to powerStatus singleton. +// +// TODO(girts): move this and other axp stuff to power.h/power.cpp. +void readPowerStatus() +{ + powerStatus.haveBattery = axp.isBatteryConnect(); + if (powerStatus.haveBattery) { + powerStatus.batteryVoltageMv = axp.getBattVoltage(); + } + powerStatus.usb = axp.isVBUSPlug(); + powerStatus.charging = axp.isChargeing(); +} +#endif // TBEAM_V10 + +#ifdef AXP192_SLAVE_ADDRESS +/** + * Init the power manager chip + * + * axp192 power + DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192 + share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1 + 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can + not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS + */ +void axp192Init() +{ + if (axp192_found) { + if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) { + DEBUG_MSG("AXP192 Begin PASS\n"); + + // axp.setChgLEDMode(LED_BLINK_4HZ); + DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("----------------------------------------\n"); + + axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio + axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power + axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON); + axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON); + axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON); + axp.setDCDC1Voltage(3300); // for the OLED power + + DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); + DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); + +#if 0 + // cribbing from https://github.com/m5stack/M5StickC/blob/master/src/AXP192.cpp to fix charger to be more like 300ms. + // I finally found an english datasheet. Will look at this later - but suffice it to say the default code from TTGO has 'issues' + + axp.adc1Enable(0xff, 1); // turn on all adcs + uint8_t val = 0xc2; + axp._writeByte(0x33, 1, &val); // Bat charge voltage to 4.2, Current 280mA + val = 0b11110010; + // Set ADC sample rate to 200hz + // axp._writeByte(0x84, 1, &val); + + // Not connected + //val = 0xfc; + //axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection + + //not used + //val = 0x46; + //axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection +#endif + axp.debugCharging(); + +#ifdef PMU_IRQ + pinMode(PMU_IRQ, INPUT); + attachInterrupt( + PMU_IRQ, [] { pmu_irq = true; }, FALLING); + + axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1); + axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ | + AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, + 1); + + axp.clearIRQ(); +#endif + readPowerStatus(); + } else { + DEBUG_MSG("AXP192 Begin FAIL\n"); + } + } else { + DEBUG_MSG("AXP192 not found\n"); + } +} +#endif + +void esp32Setup() +{ +#ifdef AXP192_SLAVE_ADDRESS + axp192Init(); +#endif +} + +#if 0 +// Turn off for now + +uint32_t axpDebugRead() +{ + axp.debugCharging(); + DEBUG_MSG("vbus current %f\n", axp.getVbusCurrent()); + DEBUG_MSG("charge current %f\n", axp.getBattChargeCurrent()); + DEBUG_MSG("bat voltage %f\n", axp.getBattVoltage()); + DEBUG_MSG("batt pct %d\n", axp.getBattPercentage()); + DEBUG_MSG("is battery connected %d\n", axp.isBatteryConnect()); + DEBUG_MSG("is USB connected %d\n", axp.isVBUSPlug()); + DEBUG_MSG("is charging %d\n", axp.isChargeing()); + + return 30 * 1000; +} + +Periodic axpDebugOutput(axpDebugRead); +#endif + +/// loop code specific to ESP32 targets +void esp32Loop() +{ + loopBLE(); + + // for debug printing + // radio.radioIf.canSleep(); + +#ifdef PMU_IRQ + if (pmu_irq) { + pmu_irq = false; + axp.readIRQ(); + + DEBUG_MSG("pmu irq!\n"); + + if (axp.isChargingIRQ()) { + DEBUG_MSG("Battery start charging\n"); + } + if (axp.isChargingDoneIRQ()) { + DEBUG_MSG("Battery fully charged\n"); + } + if (axp.isVbusRemoveIRQ()) { + DEBUG_MSG("USB unplugged\n"); + } + if (axp.isVbusPlugInIRQ()) { + DEBUG_MSG("USB plugged In\n"); + } + if (axp.isBattPlugInIRQ()) { + DEBUG_MSG("Battery inserted\n"); + } + if (axp.isBattRemoveIRQ()) { + DEBUG_MSG("Battery removed\n"); + } + if (axp.isPEKShortPressIRQ()) { + DEBUG_MSG("PEK short button press\n"); + } + + readPowerStatus(); + axp.clearIRQ(); + } +#endif // T_BEAM_V10 } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 0737e4df5..4ed5e9a8b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,103 +96,6 @@ void scanI2Cdevice(void) DEBUG_MSG("done\n"); } -#ifdef TBEAM_V10 -/// Reads power status to powerStatus singleton. -// -// TODO(girts): move this and other axp stuff to power.h/power.cpp. -void readPowerStatus() -{ - powerStatus.haveBattery = axp.isBatteryConnect(); - if (powerStatus.haveBattery) { - powerStatus.batteryVoltageMv = axp.getBattVoltage(); - } - powerStatus.usb = axp.isVBUSPlug(); - powerStatus.charging = axp.isChargeing(); -} -#endif // TBEAM_V10 - -/** - * Init the power manager chip - * - * axp192 power - DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192 - share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1 - 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can - not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS - */ -void axp192Init() -{ -#ifdef TBEAM_V10 - if (axp192_found) { - if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) { - DEBUG_MSG("AXP192 Begin PASS\n"); - - // axp.setChgLEDMode(LED_BLINK_4HZ); - DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("----------------------------------------\n"); - - axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio - axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power - axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON); - axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON); - axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON); - axp.setDCDC1Voltage(3300); // for the OLED power - - DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); - DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); - -#if 0 - // cribbing from https://github.com/m5stack/M5StickC/blob/master/src/AXP192.cpp to fix charger to be more like 300ms. - // I finally found an english datasheet. Will look at this later - but suffice it to say the default code from TTGO has 'issues' - - axp.adc1Enable(0xff, 1); // turn on all adcs - uint8_t val = 0xc2; - axp._writeByte(0x33, 1, &val); // Bat charge voltage to 4.2, Current 280mA - val = 0b11110010; - // Set ADC sample rate to 200hz - // axp._writeByte(0x84, 1, &val); - - // Not connected - //val = 0xfc; - //axp._writeByte(AXP202_VHTF_CHGSET, 1, &val); // Set temperature protection - - //not used - //val = 0x46; - //axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection -#endif - axp.debugCharging(); - -#ifdef PMU_IRQ - pinMode(PMU_IRQ, INPUT); - attachInterrupt( - PMU_IRQ, [] { pmu_irq = true; }, FALLING); - - axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1); - axp.enableIRQ(AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | AXP202_CHARGING_FINISHED_IRQ | AXP202_CHARGING_IRQ | - AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_PEK_SHORTPRESS_IRQ, - 1); - - axp.clearIRQ(); -#endif - readPowerStatus(); - } else { - DEBUG_MSG("AXP192 Begin FAIL\n"); - } - } else { - DEBUG_MSG("AXP192 not found\n"); - } -#endif -} - const char *getDeviceName() { uint8_t dmac[6]; @@ -214,7 +117,7 @@ void setup() #ifdef USE_SEGGER SEGGER_RTT_ConfigUpBuffer(0, NULL, NULL, 0, SEGGER_RTT_MODE_NO_BLOCK_TRIM); #endif - + // Debug #ifdef DEBUG_PORT DEBUG_PORT.begin(SERIAL_BAUD); @@ -256,14 +159,14 @@ void setup() // Don't init display if we don't have one or we are waking headless due to a timer event if (wakeCause == ESP_SLEEP_WAKEUP_TIMER) ssd1306_found = false; // forget we even have the hardware + + esp32Setup(); #endif // Initialize the screen first so we can show the logo while we start up everything else. if (ssd1306_found) screen.setup(); - axp192Init(); - screen.print("Started...\n"); // Init GPS @@ -298,26 +201,6 @@ uint32_t ledBlinker() Periodic ledPeriodic(ledBlinker); -#if 0 -// Turn off for now - -uint32_t axpDebugRead() -{ - axp.debugCharging(); - DEBUG_MSG("vbus current %f\n", axp.getVbusCurrent()); - DEBUG_MSG("charge current %f\n", axp.getBattChargeCurrent()); - DEBUG_MSG("bat voltage %f\n", axp.getBattVoltage()); - DEBUG_MSG("batt pct %d\n", axp.getBattPercentage()); - DEBUG_MSG("is battery connected %d\n", axp.isBatteryConnect()); - DEBUG_MSG("is USB connected %d\n", axp.isVBUSPlug()); - DEBUG_MSG("is charging %d\n", axp.isChargeing()); - - return 30 * 1000; -} - -Periodic axpDebugOutput(axpDebugRead); -#endif - void loop() { uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop? @@ -331,46 +214,9 @@ void loop() // axpDebugOutput.loop(); #ifndef NO_ESP32 - loopBLE(); + esp32Loop(); #endif - // for debug printing - // radio.radioIf.canSleep(); - -#ifdef PMU_IRQ - if (pmu_irq) { - pmu_irq = false; - axp.readIRQ(); - - DEBUG_MSG("pmu irq!\n"); - - if (axp.isChargingIRQ()) { - DEBUG_MSG("Battery start charging\n"); - } - if (axp.isChargingDoneIRQ()) { - DEBUG_MSG("Battery fully charged\n"); - } - if (axp.isVbusRemoveIRQ()) { - DEBUG_MSG("USB unplugged\n"); - } - if (axp.isVbusPlugInIRQ()) { - DEBUG_MSG("USB plugged In\n"); - } - if (axp.isBattPlugInIRQ()) { - DEBUG_MSG("Battery inserted\n"); - } - if (axp.isBattRemoveIRQ()) { - DEBUG_MSG("Battery removed\n"); - } - if (axp.isPEKShortPressIRQ()) { - DEBUG_MSG("PEK short button press\n"); - } - - readPowerStatus(); - axp.clearIRQ(); - } -#endif // T_BEAM_V10 - #ifdef BUTTON_PIN // if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of // this boilerplate) From 7fa9d09d9fbc8d61e2245448a4fe0af878bfea1e Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 09:33:45 -0700 Subject: [PATCH 072/197] placeholder guess at PMU code until I have HW --- docs/software/nrf52-TODO.md | 17 +++++-- src/bare/PmuBQ25703A.cpp | 97 +++++++++++++++++++++++++++++++++++++ src/bare/PmuBQ25703A.h | 22 +++++++++ src/bare/main-nrf52.cpp | 13 ++++- src/main.cpp | 4 ++ src/main.h | 4 +- 6 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 src/bare/PmuBQ25703A.cpp create mode 100644 src/bare/PmuBQ25703A.h diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 3128fc099..1bce53f89 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -11,7 +11,7 @@ Minimum items needed to make sure hardware is good. - Use the PMU driver - add a NEMA based GPS driver to test GPS - Use new radio driver - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ -- Use UC1701 LCD driver. Still need to create at startup and probe on SPI +- Use UC1701 LCD driver. Still need to create at startup and probe on SPI - test the LEDs - test the buttons - make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): @@ -25,6 +25,9 @@ Minimum items needed to make sure hardware is good. Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. +- enable BLE DFU somehow +- set appversion/hwversion +- report appversion/hwversion in BLE - use new LCD driver from screen.cpp. Still need to hook it to a subclass of (poorly named) OLEDDisplay, and override display() to stream bytes out to the screen. - get full BLE api working - we need to enable the external xtal for the sx1262 (on dio3) @@ -35,9 +38,14 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - make a file system implementation (preferably one that can see the files the bootloader also sees) - make ble endpoints not require "start config", jsut have them start in config mode - measure power management and confirm battery life +- use new PMU to provide battery voltage/% full to app (both bluetooth and screen) +- do initial power measurements ## Items to be 'feature complete' +- call PMU set_ADC_CONV(0) during sleep, to stop reading PMU adcs and decrease current draw +- do final power measurements +- backport the common PMU API between AXP192 and PmuBQ25703A - use the new buttons in the UX - currently using soft device SD140, is that ideal? - turn on the watchdog timer, require servicing from key application threads @@ -45,12 +53,15 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At ## Things to do 'someday' -Nice ideas worth considering... +Nice ideas worth considering someday... +- in addition to the main CPU watchdog, use the PMU watchdog as a really big emergency hammer +- turn on 'shipping mode' in the PMU when device is 'off' - to cut battery draw to essentially zero - make Lorro_BQ25703A read/write operations atomic, current version could let other threads sneak in (once we start using threads) - turn on DFU assistance in the appload using the nordic DFU helper lib call -- make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 +- make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 - convert hardfaults/panics/asserts/wd exceptions into fault codes sent to phone +- stop enumerating all i2c devices at boot, it wastes power & time ``` /* diff --git a/src/bare/PmuBQ25703A.cpp b/src/bare/PmuBQ25703A.cpp new file mode 100644 index 000000000..25dc8fb9c --- /dev/null +++ b/src/bare/PmuBQ25703A.cpp @@ -0,0 +1,97 @@ +#include "PmuBQ25703A.h" +#include + +// Default address for device. Note, it is without read/write bit. When read with analyser, +// this will appear 1 bit shifted to the left +#define BQ25703ADevaddr 0xD6 + +const byte Lorro_BQ25703A::BQ25703Aaddr = BQ25703ADevaddr; + +void PmuBQ25703A::init() +{ + // Set the watchdog timer to not have a timeout + regs.chargeOption0.set_WDTMR_ADJ(0); + assert(writeRegEx(regs.chargeOption0)); // FIXME, instead log a critical hw failure and reboot + delay(15); // FIXME, why are these delays required? - check datasheet + + // Set the ADC on IBAT and PSYS to record values + // When changing bitfield values, call the writeRegEx function + // This is so you can change all the bits you want before sending out the byte. + regs.chargeOption1.set_EN_IBAT(1); + regs.chargeOption1.set_EN_PSYS(1); + assert(writeRegEx(regs.chargeOption1)); + delay(15); + + // Set ADC to make continuous readings. (uses more power) + regs.aDCOption.set_ADC_CONV(1); + // Set individual ADC registers to read. All have default off. + regs.aDCOption.set_EN_ADC_VBUS(1); + regs.aDCOption.set_EN_ADC_PSYS(1); + regs.aDCOption.set_EN_ADC_IDCHG(1); + regs.aDCOption.set_EN_ADC_ICHG(1); + regs.aDCOption.set_EN_ADC_VSYS(1); + regs.aDCOption.set_EN_ADC_VBAT(1); + // Once bits have been twiddled, send bytes to device + assert(writeRegEx(regs.aDCOption)); + delay(15); +} + +/* + + +//Initialise the device and library +Lorro_BQ25703A BQ25703A; + +//Instantiate with reference to global set +extern Lorro_BQ25703A::Regt BQ25703Areg; + +uint32_t previousMillis; +uint16_t loopInterval = 1000; + +void setup() { + + Serial.begin(115200); + + +} + +void loop() { + + uint32_t currentMillis = millis(); + + if( currentMillis - previousMillis > loopInterval ){ + previousMillis = currentMillis; + + Serial.print( "Voltage of VBUS: " ); + Serial.print( BQ25703Areg.aDCVBUSPSYS.get_VBUS() ); + Serial.println( "mV" ); + delay( 15 ); + + Serial.print( "System power usage: " ); + Serial.print( BQ25703Areg.aDCVBUSPSYS.get_sysPower() ); + Serial.println( "W" ); + delay( 15 ); + + Serial.print( "Voltage of VBAT: " ); + Serial.print( BQ25703Areg.aDCVSYSVBAT.get_VBAT() ); + Serial.println( "mV" ); + delay( 15 ); + + Serial.print( "Voltage of VSYS: " ); + Serial.print( BQ25703Areg.aDCVSYSVBAT.get_VSYS() ); + Serial.println( "mV" ); + delay( 15 ); + + Serial.print( "Charging current: " ); + Serial.print( BQ25703Areg.aDCIBAT.get_ICHG() ); + Serial.println( "mA" ); + delay( 15 ); + + Serial.print( "Voltage of VSYS: " ); + Serial.print( BQ25703Areg.aDCIBAT.get_IDCHG() ); + Serial.println( "mA" ); + delay( 15 ); + + } + +}*/ \ No newline at end of file diff --git a/src/bare/PmuBQ25703A.h b/src/bare/PmuBQ25703A.h new file mode 100644 index 000000000..804b85bf9 --- /dev/null +++ b/src/bare/PmuBQ25703A.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +class PmuBQ25703A : private Lorro_BQ25703A +{ + Lorro_BQ25703A::Regt regs; + + public: + /** + * Configure the PMU for our board + */ + void init(); + + // Methods to have a common API with AXP192 + bool isBatteryConnect() { return true; } // FIXME + bool isVBUSPlug() { return true; } + bool isChargeing() { return true; } // FIXME, intentional misspelling + + /// battery voltage in mV + int getBattVoltage() { return 3200; } +}; diff --git a/src/bare/main-nrf52.cpp b/src/bare/main-nrf52.cpp index f825097ac..70e733b90 100644 --- a/src/bare/main-nrf52.cpp +++ b/src/bare/main-nrf52.cpp @@ -16,9 +16,10 @@ static inline void debugger_break(void) // handle standard gcc assert failures void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr) { + DEBUG_MSG("assert failed %s: %d, %s, test=%s\n", file, line, func, failedexpr); debugger_break(); while (1) - ; + ; // FIXME, reboot! } void getMacAddr(uint8_t *dmac) @@ -57,3 +58,13 @@ void setBluetoothEnable(bool on) bleOn = on; } } + +#include "PmuBQ25703A.h" + +PmuBQ25703A pmu; + +void nrf52Setup() +{ + // Not yet on board + // pmu.init(); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 4ed5e9a8b..9a19d88ae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -163,6 +163,10 @@ void setup() esp32Setup(); #endif +#ifdef NRF52_SERIES + nrf52Setup(); +#endif + // Initialize the screen first so we can show the logo while we start up everything else. if (ssd1306_found) screen.setup(); diff --git a/src/main.h b/src/main.h index 2a29da591..9d0cde8db 100644 --- a/src/main.h +++ b/src/main.h @@ -13,4 +13,6 @@ extern meshtastic::Screen screen; // Return a human readable string of the form "Meshtastic_ab13" const char *getDeviceName(); -void getMacAddr(uint8_t *dmac); \ No newline at end of file +void getMacAddr(uint8_t *dmac); + +void nrf52Setup(), esp32Setup(), nrf52Loop(), esp32Loop(); \ No newline at end of file From bebaa838c451e64f4f3be5bb3eb74ec3ffe605a5 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 11:21:10 -0700 Subject: [PATCH 073/197] no need for LightSleep state on NRF52 CPUs --- docs/software/nrf52-TODO.md | 17 ++++--- docs/software/power.md | 91 +++++++++++++++++++------------------ src/PowerFSM.cpp | 3 ++ 3 files changed, 60 insertions(+), 51 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 1bce53f89..9460eadb5 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -8,10 +8,10 @@ Minimum items needed to make sure hardware is good. - DONE get old radio driver working on NRF52 - DONE basic test of BLE - DONE get a debug 'serial' console working via the ICE passthrough feater -- Use the PMU driver +- Use the PMU driver on real hardware - add a NEMA based GPS driver to test GPS -- Use new radio driver - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ -- Use UC1701 LCD driver. Still need to create at startup and probe on SPI +- Use new radio driver on real hardware - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ +- Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI - test the LEDs - test the buttons - make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): @@ -35,14 +35,15 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - turn on security for BLE, make pairing work - make power management/sleep work properly - make a settimeofday implementation -- make a file system implementation (preferably one that can see the files the bootloader also sees) -- make ble endpoints not require "start config", jsut have them start in config mode +- make a file system implementation (preferably one that can see the files the bootloader also sees) - use https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.3.0/lib_fds_usage.html?cp=7_5_0_3_55_3 +- make ble endpoints not require "start config", just have them start in config mode - measure power management and confirm battery life - use new PMU to provide battery voltage/% full to app (both bluetooth and screen) - do initial power measurements ## Items to be 'feature complete' +- good power management tips: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs - call PMU set_ADC_CONV(0) during sleep, to stop reading PMU adcs and decrease current draw - do final power measurements - backport the common PMU API between AXP192 and PmuBQ25703A @@ -55,13 +56,17 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- 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. +- decrease BLE xmit power "At 0dBm with the DC/DC on, the nRF52832 transmitter draws 5.3mA. Increasing the TX power to +4dBm adds only 2.2mA. Decreasing it to -40 dBm saves only 2.6mA." - in addition to the main CPU watchdog, use the PMU watchdog as a really big emergency hammer - turn on 'shipping mode' in the PMU when device is 'off' - to cut battery draw to essentially zero - make Lorro_BQ25703A read/write operations atomic, current version could let other threads sneak in (once we start using threads) - turn on DFU assistance in the appload using the nordic DFU helper lib call -- make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 +- make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 (use gcc noinit attribute) - convert hardfaults/panics/asserts/wd exceptions into fault codes sent to phone - stop enumerating all i2c devices at boot, it wastes power & time +- consider using "SYSTEMOFF" deep sleep mode, without RAM retension. Only useful for 'truly off - wake only by button press' only saves 1.5uA vs SYSTEMON. (SYSTEMON only costs 1.5uA). Possibly put PMU into shipping mode? ``` /* diff --git a/docs/software/power.md b/docs/software/power.md index 0367e2f66..84c397519 100644 --- a/docs/software/power.md +++ b/docs/software/power.md @@ -4,34 +4,35 @@ i.e. sleep behavior ## Power measurements -Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing). +Since one of the main goals of this project is long battery life, it is important to consider that in our software/protocol design. Based on initial measurements it seems that the current code should run about three days between charging, and with a bit more software work (see the [TODO list](TODO.md)) a battery life of eight days should be quite doable. Our current power measurements/model is in [this spreadsheet](https://docs.google.com/spreadsheets/d/1ft1bS3iXqFKU8SApU8ZLTq9r7QQEGESYnVgdtvdT67k/edit?usp=sharing). ## States From lower to higher power consumption. -* Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press. We enter this mode only after no radio comms for a few hours, used to put the device into what is effectively "off" mode. +- Super-deep-sleep (SDS) - everything is off, CPU, radio, bluetooth, GPS. Only wakes due to timer or button press. We enter this mode only after no radio comms for a few hours, used to put the device into what is effectively "off" mode. onEntry: setBluetoothOn(false), call doDeepSleep onExit: (standard bootup code, starts in DARK) -* deep-sleep (DS) - CPU is off, radio is on, bluetooth and GPS is off. Note: This mode is never used currently, because it only saves 1.5mA vs light-sleep +- deep-sleep (DS) - CPU is off, radio is on, bluetooth and GPS is off. Note: This mode is never used currently, because it only saves 1.5mA vs light-sleep (Not currently used) -* light-sleep (LS) - CPU is suspended (RAM stays alive), radio is on, bluetooth is off, GPS is off. Note: currently GPS is not turned -off during light sleep, but there is a TODO item to fix this. - onEntry: setBluetoothOn(false), setGPSPower(false), doLightSleep() +- light-sleep (LS) - CPU is suspended (RAM stays alive), radio is on, bluetooth is off, GPS is off. Note: currently GPS is not turned + off during light sleep, but there is a TODO item to fix this. + NOTE: On NRF52 platforms (because CPU current draw is so low), light-sleep state is never used. + onEntry: setBluetoothOn(false), setGPSPower(false), doLightSleep() onIdle: (if we wake because our led blink timer has expired) blink the led then go back to sleep until we sleep for ls_secs onExit: setGPSPower(true), start trying to get gps lock: gps.startLock(), once lock arrives service.sendPosition(BROADCAST) -* No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off. +- No bluetooth (NB) - CPU is running, radio is on, GPS is on but bluetooth is off, screen is off. onEntry: setBluetoothOn(false) - onExit: + onExit: -* running dark (DARK) - Everything is on except screen +- running dark (DARK) - Everything is on except screen onEntry: setBluetoothOn(true) - onExit: + onExit: -* full on (ON) - Everything is on +- full on (ON) - Everything is on onEntry: setBluetoothOn(true), screen.setOn(true) onExit: screen.setOn(false) @@ -39,47 +40,47 @@ off during light sleep, but there is a TODO item to fix this. ### events that increase CPU activity -* At cold boot: The initial state (after setup() has run) is DARK -* While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep) -* While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages. -* While in LS: Every send_owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we _also_ broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments. -* While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout -* While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), we go to full ON mode for screen_on_secs (same as if user pressed a button) -* While in LS: while we receive packets on the radio (EVENT_RECEIVED_PACKET) we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds) -* While in NB: If we do have packets the phone (EVENT_PACKETS_FOR_PHONE) would want we transition to DARK mode for wait_bluetooth secs. -* While in DARK/ON: If we receive EVENT_BLUETOOTH_PAIR we transition to ON and start our screen_on_secs timeout -* While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown) -* While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts) +- At cold boot: The initial state (after setup() has run) is DARK +- While in DARK: if we receive EVENT_BOOT, transition to ON (and show the bootscreen). This event will be sent if we detect we woke due to reset (as opposed to deep sleep) +- While in LS: Once every position_broadcast_secs (default 15 mins) - the unit will wake into DARK mode and broadcast a "networkPing" (our position) and stay alive for wait_bluetooth_secs (default 30 seconds). This allows other nodes to have a record of our last known position if we go away and allows a paired phone to hear from us and download messages. +- While in LS: Every send*owner_interval (defaults to 4, i.e. one hour), when we wake to send our position we \_also* broadcast our owner. This lets new nodes on the network find out about us or correct duplicate node number assignments. +- While in LS/NB/DARK: If the user presses a button (EVENT_PRESS) we go to full ON mode for screen_on_secs (default 30 seconds). Multiple presses keeps resetting this timeout +- While in LS/NB/DARK: If we receive new text messages (EVENT_RECEIVED_TEXT_MSG), we go to full ON mode for screen_on_secs (same as if user pressed a button) +- While in LS: while we receive packets on the radio (EVENT_RECEIVED_PACKET) we will wake and handle them and stay awake in NB mode for min_wake_secs (default 10 seconds) +- While in NB: If we do have packets the phone (EVENT_PACKETS_FOR_PHONE) would want we transition to DARK mode for wait_bluetooth secs. +- While in DARK/ON: If we receive EVENT_BLUETOOTH_PAIR we transition to ON and start our screen_on_secs timeout +- While in NB/DARK/ON: If we receive EVENT_NODEDB_UPDATED we transition to ON (so the new screen can be shown) +- While in DARK: While the phone talks to us over BLE (EVENT_CONTACT_FROM_PHONE) reset any sleep timers and stay in DARK (needed for bluetooth sw update and nice user experience if the user is reading/replying to texts) ### events that decrease cpu activity -* While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess -* While in ON: If it has been more than screen_on_secs since a press, lower to DARK -* While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode -* While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state -* While in LS: If either phone_sds_timeout_secs (default 2 hr) or mesh_sds_timeout_secs (default 2 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 yr) (or a button press). (Note: phone_sds_timeout_secs is currently disabled for now, because most users -are using without a phone) -* Any time we enter LS mode: We stay in that until an interrupt, button press or other state transition. Every ls_secs (default 1 hr) and let the arduino loop() run one iteration (FIXME, not sure if we need this at all), and then immediately reenter lightsleep mode on the CPU. +- While in ON: If PRESS event occurs, reset screen_on_secs timer and tell the screen to handle the pess +- While in ON: If it has been more than screen_on_secs since a press, lower to DARK +- While in DARK: If time since last contact by our phone exceeds phone_timeout_secs (15 minutes), we transition down into NB mode +- While in DARK or NB: If nothing above is forcing us to stay in a higher mode (wait_bluetooth_secs, min_wake_secs) we will lower down to LS state +- While in LS: If either phone_sds_timeout_secs (default 2 hr) or mesh_sds_timeout_secs (default 2 hr) are exceeded we will lower into SDS mode for sds_secs (default 1 yr) (or a button press). (Note: phone_sds_timeout_secs is currently disabled for now, because most users + are using without a phone) +- Any time we enter LS mode: We stay in that until an interrupt, button press or other state transition. Every ls_secs (default 1 hr) and let the arduino loop() run one iteration (FIXME, not sure if we need this at all), and then immediately reenter lightsleep mode on the CPU. TODO: Eventually these scheduled intervals should be synchronized to the GPS clock, so that we can consider leaving the lora receiver off to save even more power. TODO: In NB mode we should put cpu into light sleep any time we really aren't that busy (without declaring LS state) - i.e. we should leave GPS on etc... # Low power consumption tasks -General ideas to hit the power draws our spreadsheet predicts. Do the easy ones before beta, the last 15% can be done after 1.0. +General ideas to hit the power draws our spreadsheet predicts. Do the easy ones before beta, the last 15% can be done after 1.0. -* don't even power on the gps until someone else wants our position, just stay in lora deep sleep until press or rxpacket (except for once an hour updates) -* (possibly bad idea - better to have lora radio always listen - check spreadsheet) have every node wake at the same tick and do their position syncs then go back to deep sleep -* lower BT announce interval to save battery -* change to use RXcontinuous mode and config to drop packets with bad CRC (see section 6.4 of datasheet) - I think this is already the case -* have mesh service run in a thread that stays blocked until a packet arrives from the RF95 -* platformio sdkconfig CONFIG_PM and turn on modem sleep mode -* keep cpu 100% in deepsleep until irq from radio wakes it. Then stay awake for 30 secs to attempt delivery to phone. -* use https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ association sleep pattern to save power - but see https://github.com/espressif/esp-idf/issues/2070 and https://esp32.com/viewtopic.php?f=13&t=12182 it seems with BLE on the 'easy' draw people are getting is 80mA -* stop using loop() instead use a job queue and let cpu sleep -* measure power consumption and calculate battery life assuming no deep sleep -* do lowest sleep level possible where BT still works during normal sleeping, make sure cpu stays in that mode unless lora rx packet happens, bt rx packet happens or button press happens -* optionally do lora messaging only during special scheduled intervals (unless nodes are told to go to low latency mode), then deep sleep except during those intervals - before implementing calculate what battery life would be with this feature -* see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO. -* never enter deep sleep while connected to USB power (but still go to other low power modes) -* when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt. +- don't even power on the gps until someone else wants our position, just stay in lora deep sleep until press or rxpacket (except for once an hour updates) +- (possibly bad idea - better to have lora radio always listen - check spreadsheet) have every node wake at the same tick and do their position syncs then go back to deep sleep +- lower BT announce interval to save battery +- change to use RXcontinuous mode and config to drop packets with bad CRC (see section 6.4 of datasheet) - I think this is already the case +- have mesh service run in a thread that stays blocked until a packet arrives from the RF95 +- platformio sdkconfig CONFIG_PM and turn on modem sleep mode +- keep cpu 100% in deepsleep until irq from radio wakes it. Then stay awake for 30 secs to attempt delivery to phone. +- use https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/ association sleep pattern to save power - but see https://github.com/espressif/esp-idf/issues/2070 and https://esp32.com/viewtopic.php?f=13&t=12182 it seems with BLE on the 'easy' draw people are getting is 80mA +- stop using loop() instead use a job queue and let cpu sleep +- measure power consumption and calculate battery life assuming no deep sleep +- do lowest sleep level possible where BT still works during normal sleeping, make sure cpu stays in that mode unless lora rx packet happens, bt rx packet happens or button press happens +- optionally do lora messaging only during special scheduled intervals (unless nodes are told to go to low latency mode), then deep sleep except during those intervals - before implementing calculate what battery life would be with this feature +- see section 7.3 of https://cdn.sparkfun.com/assets/learn_tutorials/8/0/4/RFM95_96_97_98W.pdf and have hope radio wake only when a valid packet is received. Possibly even wake the ESP32 from deep sleep via GPIO. +- never enter deep sleep while connected to USB power (but still go to other low power modes) +- when main cpu is idle (in loop), turn cpu clock rate down and/or activate special sleep modes. We want almost everything shutdown until it gets an interrupt. diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 45c68805a..4c4944b0f 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -173,10 +173,13 @@ void PowerFSM_setup() powerFSM.add_timed_transition(&stateDARK, &stateNB, radioConfig.preferences.phone_timeout_secs * 1000, NULL, "Phone timeout"); +#ifndef NRF52_SERIES + // We never enter light-sleep state on NRF52 (because the CPU uses so little power normally) powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout"); powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL, "Bluetooth timeout"); +#endif powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL, "mesh timeout"); From bb885a51102c5f4a67783012e22a843537250c5c Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 12:30:06 -0700 Subject: [PATCH 074/197] add a variant based on the nrf52840-dk but with a RC clock. Because I was dumb and accidentally ran some code that configured gpio 0 as an output and that was enough to smoke the xtal that was preinstalled between P0.0 and P0.1. --- boards/ppr.json | 46 ++++++++ docs/software/nrf52-TODO.md | 1 + platformio.ini | 3 +- variants/pca10056-rc-clock/variant.cpp | 49 ++++++++ variants/pca10056-rc-clock/variant.h | 150 +++++++++++++++++++++++++ 5 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 boards/ppr.json create mode 100644 variants/pca10056-rc-clock/variant.cpp create mode 100644 variants/pca10056-rc-clock/variant.h diff --git a/boards/ppr.json b/boards/ppr.json new file mode 100644 index 000000000..5050758f7 --- /dev/null +++ b/boards/ppr.json @@ -0,0 +1,46 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_PPR -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [["0x239A", "0x4403"]], + "usb_product": "PPR", + "mcu": "nrf52840", + "variant": "pca10056-rc-clock", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "Meshtastic PPR (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "require_upload_port": true, + "speed": 115200, + "protocol": "jlink", + "protocols": ["jlink", "nrfjprog", "stlink"] + }, + "url": "https://meshtastic.org/", + "vendor": "Othernet" +} diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 9460eadb5..c636a71ca 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -56,6 +56,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- 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. - decrease BLE xmit power "At 0dBm with the DC/DC on, the nRF52832 transmitter draws 5.3mA. Increasing the TX power to +4dBm adds only 2.2mA. Decreasing it to -40 dBm saves only 2.6mA." diff --git a/platformio.ini b/platformio.ini index f6af1af4b..a03fd4330 100644 --- a/platformio.ini +++ b/platformio.ini @@ -125,7 +125,8 @@ build_flags = ; This is a temporary build target to test turning off particular hardare bits in the build (to improve modularity) [env:bare] platform = nordicnrf52 -board = nrf52840_dk_adafruit ; nicer than nrf52840_dk - more full gpio mappings +; board = nrf52840_dk_adafruit ; nicer than nrf52840_dk - more full gpio mappings +board = ppr framework = arduino debug_tool = jlink build_flags = diff --git a/variants/pca10056-rc-clock/variant.cpp b/variants/pca10056-rc-clock/variant.cpp new file mode 100644 index 000000000..bd85e9713 --- /dev/null +++ b/variants/pca10056-rc-clock/variant.cpp @@ -0,0 +1,49 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // P0 + 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , + 8 , 9 , 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2);; +} + diff --git a/variants/pca10056-rc-clock/variant.h b/variants/pca10056-rc-clock/variant.h new file mode 100644 index 000000000..ee939780b --- /dev/null +++ b/variants/pca10056-rc-clock/variant.h @@ -0,0 +1,150 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_PCA10056_ +#define _VARIANT_PCA10056_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +// This file is the same as the standard pac10056 variant, except that @geeksville broke the xtal on his devboard so +// he has to use a RC clock. + +// #define USE_LFXO // Board uses 32khz crystal for LF +#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (13) +#define PIN_LED2 (14) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_RED PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 0 // State when LED is litted + +/* + * Buttons + */ +#define PIN_BUTTON1 11 +#define PIN_BUTTON2 12 +#define PIN_BUTTON3 24 +#define PIN_BUTTON4 25 + +/* + * Analog pins + */ +#define PIN_A0 (3) +#define PIN_A1 (4) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ + +// Arduino Header D0, D1 +#define PIN_SERIAL1_RX (33) // P1.01 +#define PIN_SERIAL1_TX (34) // P1.02 + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (46) +#define PIN_SPI_MOSI (45) +#define PIN_SPI_SCK (47) + +static const uint8_t SS = 44; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (26) +#define PIN_WIRE_SCL (27) + +// QSPI Pins +#define PIN_QSPI_SCK 19 +#define PIN_QSPI_CS 17 +#define PIN_QSPI_IO0 20 +#define PIN_QSPI_IO1 21 +#define PIN_QSPI_IO2 22 +#define PIN_QSPI_IO3 23 + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES MX25R6435F +#define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From b8b503cb0a8ad6127e71a019a76f3905bb8a80f8 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 12:40:22 -0700 Subject: [PATCH 075/197] Add starting point of PPR variants definition --- boards/ppr.json | 2 +- docs/software/nrf52-TODO.md | 4 +- variants/ppr/variant.cpp | 49 +++++++++++++ variants/ppr/variant.h | 138 ++++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 variants/ppr/variant.cpp create mode 100644 variants/ppr/variant.h diff --git a/boards/ppr.json b/boards/ppr.json index 5050758f7..5283fdc4e 100644 --- a/boards/ppr.json +++ b/boards/ppr.json @@ -10,7 +10,7 @@ "hwids": [["0x239A", "0x4403"]], "usb_product": "PPR", "mcu": "nrf52840", - "variant": "pca10056-rc-clock", + "variant": "ppr", "variants_dir": "variants", "bsp": { "name": "adafruit" diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index c636a71ca..7c1257163 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -7,7 +7,9 @@ Minimum items needed to make sure hardware is good. - DONE select and install a bootloader (adafruit) - DONE get old radio driver working on NRF52 - DONE basic test of BLE -- DONE get a debug 'serial' console working via the ICE passthrough feater +- DONE get a debug 'serial' console working via the ICE passthrough feature +- use "variants" to get all gpio bindings +- plug in correct variants for the real board - Use the PMU driver on real hardware - add a NEMA based GPS driver to test GPS - Use new radio driver on real hardware - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ diff --git a/variants/ppr/variant.cpp b/variants/ppr/variant.cpp new file mode 100644 index 000000000..bd85e9713 --- /dev/null +++ b/variants/ppr/variant.cpp @@ -0,0 +1,49 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // P0 + 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , + 8 , 9 , 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2);; +} + diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h new file mode 100644 index 000000000..5033a4ecc --- /dev/null +++ b/variants/ppr/variant.h @@ -0,0 +1,138 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_PCA10056_ +#define _VARIANT_PCA10056_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +// This file is the same as the standard pac10056 variant, except that @geeksville broke the xtal on his devboard so +// he has to use a RC clock. + +// #define USE_LFXO // Board uses 32khz crystal for LF +#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (13) +#define PIN_LED2 (14) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_RED PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 0 // State when LED is litted + +/* + * Buttons + */ +#define PIN_BUTTON1 11 +#define PIN_BUTTON2 12 +#define PIN_BUTTON3 24 +#define PIN_BUTTON4 25 + +/* + * Analog pins + */ +#define PIN_A0 (3) +#define PIN_A1 (4) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ + +// Arduino Header D0, D1 +#define PIN_SERIAL1_RX (33) // P1.01 +#define PIN_SERIAL1_TX (34) // P1.02 + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (46) +#define PIN_SPI_MOSI (45) +#define PIN_SPI_SCK (47) + +static const uint8_t SS = 44; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (26) +#define PIN_WIRE_SCL (27) + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From ca03110932240db55db50c799341a08089ab8b5a Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 14:55:51 -0700 Subject: [PATCH 076/197] Update ESP32 build to work with latest NRF52 changes --- .vscode/launch.json | 10 ++++------ platformio.ini | 3 ++- src/configuration.h | 3 ++- src/esp32/main-esp32.cpp | 8 ++++++++ src/main.cpp | 10 ++-------- src/power.h | 2 ++ 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 77b1fb363..914831d68 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,9 +12,8 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", - "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", "preLaunchTask": { "type": "PlatformIO", "task": "Pre-Debug" @@ -25,9 +24,8 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug (skip Pre-Debug)", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", - "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", "internalConsoleOptions": "openOnSessionStart" } ] diff --git a/platformio.ini b/platformio.ini index a03fd4330..ffcff4735 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = bare +default_envs = tbeam [common] ; common is not currently used @@ -74,6 +74,7 @@ lib_deps = https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git https://github.com/meshtastic/SX126x-Arduino.git + Ticker ; Needed for SX126x-Arduino on ESP32 ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] diff --git a/src/configuration.h b/src/configuration.h index 25a4e25d7..7f89b08c4 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -109,6 +109,8 @@ along with this program. If not, see . // Leave undefined to disable our PMU IRQ handler #define PMU_IRQ 35 +#define AXP192_SLAVE_ADDRESS 0x34 + #elif defined(TBEAM_V07) // This string must exactly match the case used in release file names or the android updater won't work #define HW_VENDOR "tbeam0.7" @@ -244,6 +246,5 @@ along with this program. If not, see . // AXP192 (Rev1-specific options) // ----------------------------------------------------------------------------- -// #define AXP192_SLAVE_ADDRESS 0x34 // Now defined in axp20x.h #define GPS_POWER_CTRL_CH 3 #define LORA_POWER_CTRL_CH 2 diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 76999cc82..b15d60732 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -3,6 +3,7 @@ #include "PowerFSM.h" #include "configuration.h" #include "main.h" +#include "power.h" #include "target_specific.h" bool bluetoothOn; @@ -58,6 +59,13 @@ void getMacAddr(uint8_t *dmac) } #ifdef TBEAM_V10 + +// FIXME. nasty hack cleanup how we load axp192 +#undef AXP192_SLAVE_ADDRESS +#include "axp20x.h" +AXP20X_Class axp; +bool pmu_irq = false; + /// Reads power status to powerStatus singleton. // // TODO(girts): move this and other axp stuff to power.h/power.cpp. diff --git a/src/main.cpp b/src/main.cpp index 9a19d88ae..7f2e686c0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -42,17 +42,11 @@ #include "BluetoothUtil.h" #endif -#ifdef TBEAM_V10 -#include "axp20x.h" -AXP20X_Class axp; -bool pmu_irq = false; -#endif - // We always create a screen object, but we only init it if we find the hardware meshtastic::Screen screen(SSD1306_ADDRESS); // Global power status singleton -static meshtastic::PowerStatus powerStatus; +meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; @@ -80,7 +74,7 @@ void scanI2Cdevice(void) ssd1306_found = true; DEBUG_MSG("ssd1306 display found\n"); } -#ifdef TBEAM_V10 +#ifdef AXP192_SLAVE_ADDRESS if (addr == AXP192_SLAVE_ADDRESS) { axp192_found = true; DEBUG_MSG("axp192 PMU found\n"); diff --git a/src/power.h b/src/power.h index 9172592ce..6b190cbf8 100644 --- a/src/power.h +++ b/src/power.h @@ -16,3 +16,5 @@ struct PowerStatus { }; } // namespace meshtastic + +extern meshtastic::PowerStatus powerStatus; From db11d9280c30fb819906016cdc051e33042d3d95 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 21:04:10 -0700 Subject: [PATCH 077/197] add nrf52 DFU app helper --- .vscode/launch.json | 10 ++++++---- platformio.ini | 2 +- src/bare/NRF52Bluetooth.cpp | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 914831d68..77b1fb363 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,8 +12,9 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", + "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", "preLaunchTask": { "type": "PlatformIO", "task": "Pre-Debug" @@ -24,8 +25,9 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug (skip Pre-Debug)", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", + "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", "internalConsoleOptions": "openOnSessionStart" } ] diff --git a/platformio.ini b/platformio.ini index ffcff4735..37ae10d3c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam +default_envs = bare [common] ; common is not currently used diff --git a/src/bare/NRF52Bluetooth.cpp b/src/bare/NRF52Bluetooth.cpp index 676065a12..9cf6e3708 100644 --- a/src/bare/NRF52Bluetooth.cpp +++ b/src/bare/NRF52Bluetooth.cpp @@ -14,6 +14,7 @@ BLECharacteristic bslc = BLECharacteristic(UUID16_CHR_BODY_SENSOR_LOCATION); BLEDis bledis; // DIS (Device Information Service) helper class instance BLEBas blebas; // BAS (Battery Service) helper class instance +BLEDfu bledfu; // DFU software update helper service uint8_t bps = 0; @@ -171,6 +172,8 @@ void NRF52Bluetooth::setup() blebas.begin(); blebas.write(42); // FIXME, report real power levels + bledfu.begin(); // Install the DFU helper + // Setup the Heart Rate Monitor service using // BLEService and BLECharacteristic classes DEBUG_MSG("Configuring the Heart Rate Monitor Service\n"); @@ -204,4 +207,35 @@ void loop() // Only send update once per second delay(1000); } +*/ + +/* +examples of advanced characteristics. use setReadAuthorizeCallback to prepare data for reads by others + +err_t BLEDfu::begin(void) +{ + // Invoke base class begin() + VERIFY_STATUS( BLEService::begin() ); + + // No need to keep packet & revision characteristics + BLECharacteristic chr_packet(UUID128_CHR_DFU_PACKET); + chr_packet.setTempMemory(); + chr_packet.setProperties(CHR_PROPS_WRITE_WO_RESP); + chr_packet.setMaxLen(20); + VERIFY_STATUS( chr_packet.begin() ); + + _chr_control.setProperties(CHR_PROPS_WRITE | CHR_PROPS_NOTIFY); + _chr_control.setMaxLen(23); + _chr_control.setWriteAuthorizeCallback(bledfu_control_wr_authorize_cb); + VERIFY_STATUS( _chr_control.begin() ); + + BLECharacteristic chr_revision(UUID128_CHR_DFU_REVISON); + chr_revision.setTempMemory(); + chr_revision.setProperties(CHR_PROPS_READ); + chr_revision.setFixedLen(2); + VERIFY_STATUS( chr_revision.begin()); + chr_revision.write16(DFU_REV_APPMODE); + + return ERROR_NONE; +} */ \ No newline at end of file From 7cd60d859ee4542281411ccd695d011bb3a466d1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 24 Apr 2020 21:59:05 -0700 Subject: [PATCH 078/197] possibly use radiohub for the new radio --- docs/software/nrf52-TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 7c1257163..e1c3c6b78 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -8,6 +8,7 @@ Minimum items needed to make sure hardware is good. - DONE get old radio driver working on NRF52 - DONE basic test of BLE - DONE get a debug 'serial' console working via the ICE passthrough feature +- switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib - use "variants" to get all gpio bindings - plug in correct variants for the real board - Use the PMU driver on real hardware From 64f6c0f5c035cac5ae7379481b58ba13300221b4 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 25 Apr 2020 10:59:40 -0700 Subject: [PATCH 079/197] clean up PeriodicTask so I can eventually use it with a scheduler --- src/GPS.cpp | 8 ++---- src/GPS.h | 1 - src/MeshService.cpp | 25 ++++++++--------- src/PeriodicTask.cpp | 38 ++++++++++++++++++------- src/PeriodicTask.h | 67 +++++++++++++++++++++++++++++++++++--------- src/main.cpp | 34 +++++++++++----------- src/screen.cpp | 3 +- src/sleep.cpp | 1 - 8 files changed, 114 insertions(+), 63 deletions(-) diff --git a/src/GPS.cpp b/src/GPS.cpp index 60dedf429..471130b34 100644 --- a/src/GPS.cpp +++ b/src/GPS.cpp @@ -27,6 +27,8 @@ GPS::GPS() : PeriodicTask() {} void GPS::setup() { + PeriodicTask::setup(); + readFromRTC(); // read the main CPU RTC at first #ifdef GPS_RX_PIN @@ -114,12 +116,6 @@ void GPS::perhapsSetRTC(const struct timeval *tv) #include -// for the time being we need to rapidly read from the serial port to prevent overruns -void GPS::loop() -{ - PeriodicTask::loop(); -} - uint32_t GPS::getTime() { return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs; diff --git a/src/GPS.h b/src/GPS.h index caf3fc249..912356c42 100644 --- a/src/GPS.h +++ b/src/GPS.h @@ -29,7 +29,6 @@ class GPS : public PeriodicTask, public Observable void setup(); - virtual void loop(); virtual void doTask(); diff --git a/src/MeshService.cpp b/src/MeshService.cpp index d82f46b55..82d6921e1 100644 --- a/src/MeshService.cpp +++ b/src/MeshService.cpp @@ -48,6 +48,15 @@ MeshService service; #define NUM_PACKET_ID 255 // 0 is consider invalid +static uint32_t sendOwnerCb() +{ + service.sendOurOwner(); + + return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000; +} + +static Periodic sendOwnerPeriod(sendOwnerCb); + /// Generate a unique packet id // FIXME, move this someplace better PacketId generatePacketId() @@ -65,6 +74,7 @@ MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) void MeshService::init() { + sendOwnerPeriod.setup(); nodeDB.init(); gpsObserver.observe(&gps); @@ -184,15 +194,6 @@ int MeshService::handleFromRadio(const MeshPacket *mp) return 0; } -uint32_t sendOwnerCb() -{ - service.sendOurOwner(); - - return radioConfig.preferences.send_owner_interval * radioConfig.preferences.position_broadcast_secs * 1000; -} - -Periodic sendOwnerPeriod(sendOwnerCb); - /// Do idle processing (mostly processing messages which have been queued from the radio) void MeshService::loop() { @@ -200,9 +201,6 @@ void MeshService::loop() fromNumChanged.notifyObservers(fromNum); oldFromNum = fromNum; } - - // occasionally send our owner info - sendOwnerPeriod.loop(); } /// The radioConfig object just changed, call this to force the hw to change to the new settings @@ -216,7 +214,8 @@ void MeshService::reloadConfig() /** * Given a ToRadio buffer parse it and properly handle it (setup radio, owner or send packet into the mesh) - * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep a reference + * Called by PhoneAPI.handleToRadio. Note: p is a scratch buffer, this function is allowed to write to it but it can not keep a + * reference */ void MeshService::handleToRadio(MeshPacket &p) { diff --git a/src/PeriodicTask.cpp b/src/PeriodicTask.cpp index 99115faf9..5a5d3621c 100644 --- a/src/PeriodicTask.cpp +++ b/src/PeriodicTask.cpp @@ -1,21 +1,39 @@ #include "PeriodicTask.h" #include "Periodic.h" +PeriodicScheduler periodicScheduler; PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {} -/// call this from loop -void PeriodicTask::loop() +void PeriodicTask::setup() { - { - meshtastic::LockGuard lg(&lock); - uint32_t now = millis(); - if (!period || (now - lastMsec) < period) { - return; + periodicScheduler.schedule(this); +} + +/// call this from loop +void PeriodicScheduler::loop() +{ + meshtastic::LockGuard lg(&lock); + + uint32_t now = millis(); + for (auto t : tasks) { + if (t->period && (now - t->lastMsec) >= t->period) { + + t->doTask(); + t->lastMsec = now; } - lastMsec = now; } - // Release the lock in case the task wants to change the period. - doTask(); +} + +void PeriodicScheduler::schedule(PeriodicTask *t) +{ + meshtastic::LockGuard lg(&lock); + tasks.insert(t); +} + +void PeriodicScheduler::unschedule(PeriodicTask *t) +{ + meshtastic::LockGuard lg(&lock); + tasks.erase(t); } void Periodic::doTask() diff --git a/src/PeriodicTask.h b/src/PeriodicTask.h index f4a35a2c5..9d2a06b6d 100644 --- a/src/PeriodicTask.h +++ b/src/PeriodicTask.h @@ -1,8 +1,40 @@ #pragma once -#include - #include "lock.h" +#include +#include + +class PeriodicTask; + +/** + * Runs all PeriodicTasks in the system. + * + * Currently called from main loop() but eventually should be its own thread blocked on a freertos timer. + */ +class PeriodicScheduler +{ + friend class PeriodicTask; + + /** + * This really should be some form of heap, and when the period gets changed on a task it should get + * rescheduled in that heap. Currently it is just a dumb array and everytime we run loop() we check + * _every_ tasks. If it was a heap we'd only have to check the first task. + */ + std::unordered_set tasks; + + // Protects the above variables. + meshtastic::Lock lock; + + public: + /// Run any next tasks which are due for execution + void loop(); + + private: + void schedule(PeriodicTask *t); + void unschedule(PeriodicTask *t); +}; + +extern PeriodicScheduler periodicScheduler; /** * A base class for tasks that want their doTask() method invoked periodically @@ -13,26 +45,33 @@ */ class PeriodicTask { + friend class PeriodicScheduler; + uint32_t lastMsec = 0; uint32_t period = 1; // call soon after creation - // Protects the above variables. - meshtastic::Lock lock; - public: - virtual ~PeriodicTask() {} + virtual ~PeriodicTask() { periodicScheduler.unschedule(this); } + /** + * Constructor (will schedule with the global PeriodicScheduler) + */ PeriodicTask(uint32_t initialPeriod = 1); - /// call this from loop - virtual void loop(); + /** MUST be be called once at startup (but after threading is running - i.e. not from a constructor) + */ + void setup(); - /// Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope) - void setPeriod(uint32_t p) - { - meshtastic::LockGuard lg(&lock); - period = p; - } + /** + * Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope) + * While zero this task is disabled and will not run + */ + void setPeriod(uint32_t p) { period = p; } + + /** + * Syntatic sugar for suspending tasks + */ + void disable() { setPeriod(0); } protected: virtual void doTask() = 0; diff --git a/src/main.cpp b/src/main.cpp index b39e61fc9..da3ee40a0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,6 +27,7 @@ #include "NodeDB.h" #include "Periodic.h" #include "PowerFSM.h" +#include "Router.h" #include "configuration.h" #include "error.h" #include "power.h" @@ -225,7 +226,18 @@ const char *getDeviceName() static MeshRadio *radio = NULL; -#include "Router.h" +static uint32_t ledBlinker() +{ + static bool ledOn; + ledOn ^= 1; + + setLed(ledOn); + + // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that + return powerStatus.charging ? 1000 : (ledOn ? 2 : 1000); +} + +Periodic ledPeriodic(ledBlinker); void setup() { @@ -261,6 +273,8 @@ void setup() digitalWrite(LED_PIN, 1 ^ LED_INVERTED); // turn on for now #endif + ledPeriodic.setup(); + // Hello DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_VERSION)); @@ -299,19 +313,6 @@ void setup() setCPUFast(false); // 80MHz is fine for our slow peripherals } -uint32_t ledBlinker() -{ - static bool ledOn; - ledOn ^= 1; - - setLed(ledOn); - - // have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that - return powerStatus.charging ? 1000 : (ledOn ? 2 : 1000); -} - -Periodic ledPeriodic(ledBlinker); - #if 0 // Turn off for now @@ -330,18 +331,18 @@ uint32_t axpDebugRead() } Periodic axpDebugOutput(axpDebugRead); +axpDebugOutput.setup(); #endif void loop() { uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop? - gps.loop(); router.loop(); powerFSM.run_machine(); service.loop(); - ledPeriodic.loop(); + periodicScheduler.loop(); // axpDebugOutput.loop(); #ifndef NO_ESP32 @@ -419,7 +420,6 @@ void loop() screen.debug()->setPowerStatus(powerStatus); // TODO(#4): use something based on hdop to show GPS "signal" strength. screen.debug()->setGPSStatus(gps.hasLock() ? "ok" : ":("); - screen.loop(); // No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in) // i.e. don't just keep spinning in loop as fast as we can. diff --git a/src/screen.cpp b/src/screen.cpp index 583b67b8b..3acf2c52e 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -384,7 +384,6 @@ void _screen_header() if (!disp) return; - // Message count //snprintf(buffer, sizeof(buffer), "#%03d", ttn_get_count() % 1000); //display->setTextAlignment(TEXT_ALIGN_LEFT); @@ -423,6 +422,8 @@ void Screen::handleSetOn(bool on) void Screen::setup() { + PeriodicTask::setup(); + // We don't set useDisplay until setup() is called, because some boards have a declaration of this object but the device // is never found when probing i2c and therefore we don't call setup and never want to do (invalid) accesses to this device. useDisplay = true; diff --git a/src/sleep.cpp b/src/sleep.cpp index 69b80e91b..39710ad4c 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -3,7 +3,6 @@ #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" -#include "Periodic.h" #include "configuration.h" #include "error.h" From 3f3a1a11dfce688d40c1e060f05eabcd92c0a599 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 25 Apr 2020 11:43:28 -0700 Subject: [PATCH 080/197] when flooding, randomly delay sent packets to decrease chances of... stomping on other senders that we can't even hear. --- src/main.cpp | 2 ++ src/rf95/FloodingRouter.cpp | 37 ++++++++++++++++++++++++++++++++----- src/rf95/FloodingRouter.h | 13 ++++++++++++- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index da3ee40a0..1cb40f966 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -297,6 +297,8 @@ void setup() service.init(); + realRouter.setup(); // required for our periodic task (kinda skanky FIXME) + #ifndef NO_ESP32 // MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) radio = new MeshRadio(); diff --git a/src/rf95/FloodingRouter.cpp b/src/rf95/FloodingRouter.cpp index 817870131..9f496cb80 100644 --- a/src/rf95/FloodingRouter.cpp +++ b/src/rf95/FloodingRouter.cpp @@ -5,9 +5,10 @@ /// We clear our old flood record five minute after we see the last of it #define FLOOD_EXPIRE_TIME (5 * 60 * 1000L) -FloodingRouter::FloodingRouter() +FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) { recentBroadcasts.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation + // setup our periodic task } /** @@ -23,6 +24,12 @@ ErrorCode FloodingRouter::send(MeshPacket *p) return Router::send(p); } +// Return a delay in msec before sending the next packet +uint32_t getRandomDelay() +{ + return random(200, 10 * 1000L); // between 200ms and 10s +} + /** * Called from loop() * Handle any packet that is received by an interface on this node. @@ -38,12 +45,14 @@ void FloodingRouter::handleReceived(MeshPacket *p) } else { if (p->to == NODENUM_BROADCAST) { if (p->id != 0) { - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - // FIXME, wait a random delay + uint32_t delay = getRandomDelay(); + + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors in %u msec, fr=0x%x,to=0x%x,id=%d\n", delay, p->from, + p->to, p->id); MeshPacket *tosend = packetPool.allocCopy(*p); - // Note: we are careful to resend using the original senders node id - Router::send(tosend); // We are careful not to call our hooked version of send() + toResend.enqueue(tosend); + setPeriod(delay); // This will work even if we were already waiting a random delay } else { DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); } @@ -54,6 +63,24 @@ void FloodingRouter::handleReceived(MeshPacket *p) } } +void FloodingRouter::doTask() +{ + MeshPacket *p = toResend.dequeuePtr(0); + + DEBUG_MSG("Sending delayed message!\n"); + if (p) { + // Note: we are careful to resend using the original senders node id + // We are careful not to call our hooked version of send() - because we don't want to check this again + Router::send(p); + } + + if (toResend.isEmpty()) + disable(); // no more work right now + else { + setPeriod(getRandomDelay()); + } +} + /** * Update recentBroadcasts and return true if we have already seen this packet */ diff --git a/src/rf95/FloodingRouter.h b/src/rf95/FloodingRouter.h index 7ce7541a7..4ca759ecc 100644 --- a/src/rf95/FloodingRouter.h +++ b/src/rf95/FloodingRouter.h @@ -1,5 +1,6 @@ #pragma once +#include "PeriodicTask.h" #include "Router.h" #include @@ -35,11 +36,19 @@ struct BroadcastRecord { Any entries in recentBroadcasts that are older than X seconds (longer than the max time a flood can take) will be discarded. */ -class FloodingRouter : public Router +class FloodingRouter : public Router, public PeriodicTask { private: + /** FIXME: really should be a std::unordered_set with the key being sender,id. + * This would make checking packets in wasSeenRecently faster. + */ std::vector recentBroadcasts; + /** + * Packets we've received that we need to resend after a short delay + */ + PointerQueue toResend; + public: /** * Constructor @@ -64,6 +73,8 @@ class FloodingRouter : public Router */ virtual void handleReceived(MeshPacket *p); + virtual void doTask(); + private: /** * Update recentBroadcasts and return true if we have already seen this packet From 8f1c1a9049b6b6f5570ad1f539ca82d41afa3240 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 25 Apr 2020 11:46:46 -0700 Subject: [PATCH 081/197] move debug msg --- src/rf95/FloodingRouter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rf95/FloodingRouter.cpp b/src/rf95/FloodingRouter.cpp index 9f496cb80..3965dc049 100644 --- a/src/rf95/FloodingRouter.cpp +++ b/src/rf95/FloodingRouter.cpp @@ -67,8 +67,8 @@ void FloodingRouter::doTask() { MeshPacket *p = toResend.dequeuePtr(0); - DEBUG_MSG("Sending delayed message!\n"); if (p) { + DEBUG_MSG("Sending delayed message!\n"); // Note: we are careful to resend using the original senders node id // We are careful not to call our hooked version of send() - because we don't want to check this again Router::send(p); From 038b7c9c91ad8b27515afe7ac04752df7cfbf736 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 07:29:36 -0700 Subject: [PATCH 082/197] update todos --- docs/software/nrf52-TODO.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index e1c3c6b78..bf1d71b32 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -72,7 +72,22 @@ Nice ideas worth considering someday... - stop enumerating all i2c devices at boot, it wastes power & time - consider using "SYSTEMOFF" deep sleep mode, without RAM retension. Only useful for 'truly off - wake only by button press' only saves 1.5uA vs SYSTEMON. (SYSTEMON only costs 1.5uA). Possibly put PMU into shipping mode? +## Old unorganized notes + +## Notes on PCA10059 Dongle + +- docs: https://infocenter.nordicsemi.com/pdf/nRF52840_Dongle_User_Guide_v1.0.pdf + +- Currently using Nordic PCA10059 Dongle hardware +- https://community.platformio.org/t/same-bootloader-same-softdevice-different-board-different-pins/11411/9 + +## Done + +- DONE add "DFU trigger library" to application load +- DONE: using this: Possibly use this bootloader? https://github.com/adafruit/Adafruit_nRF52_Bootloader + ``` + /* per https://docs.platformio.org/en/latest/tutorials/nordicnrf52/arduino_debugging_unit_testing.html From 15cb599cd1a52e9992fed2d396b8cc79e518dc08 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 07:39:50 -0700 Subject: [PATCH 083/197] move nrf52 stuff to correct directory name --- .vscode/launch.json | 4 ++-- platformio.ini | 11 +++++------ src/configuration.h | 18 +++++++++++------- src/{bare => nrf52}/FS.h | 0 src/{bare => nrf52}/NRF52Bluetooth.cpp | 0 src/{bare => nrf52}/NRF52Bluetooth.h | 0 src/{bare => nrf52}/PmuBQ25703A.cpp | 0 src/{bare => nrf52}/PmuBQ25703A.h | 0 src/{bare => nrf52}/SPIFFS.h | 0 src/{bare => nrf52}/main-bare.cpp | 0 src/{bare => nrf52}/main-nrf52.cpp | 0 11 files changed, 18 insertions(+), 15 deletions(-) rename src/{bare => nrf52}/FS.h (100%) rename src/{bare => nrf52}/NRF52Bluetooth.cpp (100%) rename src/{bare => nrf52}/NRF52Bluetooth.h (100%) rename src/{bare => nrf52}/PmuBQ25703A.cpp (100%) rename src/{bare => nrf52}/PmuBQ25703A.h (100%) rename src/{bare => nrf52}/SPIFFS.h (100%) rename src/{bare => nrf52}/main-bare.cpp (100%) rename src/{bare => nrf52}/main-nrf52.cpp (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 77b1fb363..68cb8d526 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/nrf52dk/firmware.elf", "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", "preLaunchTask": { @@ -25,7 +25,7 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug (skip Pre-Debug)", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/bare/firmware.elf", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/nrf52dk/firmware.elf", "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", "internalConsoleOptions": "openOnSessionStart" diff --git a/platformio.ini b/platformio.ini index 37ae10d3c..430e7b0d6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = bare +default_envs = nrf52dk [common] ; common is not currently used @@ -79,7 +79,7 @@ lib_deps = ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] src_filter = - ${env.src_filter} - + ${env.src_filter} - upload_speed = 921600 debug_init_break = tbreak setup build_flags = @@ -123,15 +123,14 @@ build_flags = ${esp32_base.build_flags} -D TTGO_LORA_V2 -; This is a temporary build target to test turning off particular hardare bits in the build (to improve modularity) -[env:bare] +; The NRF52840-dk development board +[env:nrf52dk] platform = nordicnrf52 -; board = nrf52840_dk_adafruit ; nicer than nrf52840_dk - more full gpio mappings board = ppr framework = arduino debug_tool = jlink build_flags = - ${env.build_flags} -D BARE_BOARD -Wno-unused-variable -Isrc/bare + ${env.build_flags} -Wno-unused-variable -Isrc/nrf52 src_filter = ${env.src_filter} - lib_ignore = diff --git a/src/configuration.h b/src/configuration.h index 7f89b08c4..a47148ce9 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -188,13 +188,17 @@ along with this program. If not, see . 0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one // between this pin and ground -#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio -#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio -#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number -#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number -#elif defined(BARE_BOARD) +#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio +#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio +#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#elif defined(NRF52840_XXAA) // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be + // board specific + +// FIXME, use variant.h defs for all of this!!! + // This string must exactly match the case used in release file names or the android updater won't work -#define HW_VENDOR "bare" +#define HW_VENDOR "nrf52" #define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) @@ -233,7 +237,7 @@ along with this program. If not, see . #include "SEGGER_RTT.h" #define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__) #else -#define DEBUG_PORT Serial // Serial debug port +#define DEBUG_PORT Serial // Serial debug port #ifdef DEBUG_PORT #define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__) diff --git a/src/bare/FS.h b/src/nrf52/FS.h similarity index 100% rename from src/bare/FS.h rename to src/nrf52/FS.h diff --git a/src/bare/NRF52Bluetooth.cpp b/src/nrf52/NRF52Bluetooth.cpp similarity index 100% rename from src/bare/NRF52Bluetooth.cpp rename to src/nrf52/NRF52Bluetooth.cpp diff --git a/src/bare/NRF52Bluetooth.h b/src/nrf52/NRF52Bluetooth.h similarity index 100% rename from src/bare/NRF52Bluetooth.h rename to src/nrf52/NRF52Bluetooth.h diff --git a/src/bare/PmuBQ25703A.cpp b/src/nrf52/PmuBQ25703A.cpp similarity index 100% rename from src/bare/PmuBQ25703A.cpp rename to src/nrf52/PmuBQ25703A.cpp diff --git a/src/bare/PmuBQ25703A.h b/src/nrf52/PmuBQ25703A.h similarity index 100% rename from src/bare/PmuBQ25703A.h rename to src/nrf52/PmuBQ25703A.h diff --git a/src/bare/SPIFFS.h b/src/nrf52/SPIFFS.h similarity index 100% rename from src/bare/SPIFFS.h rename to src/nrf52/SPIFFS.h diff --git a/src/bare/main-bare.cpp b/src/nrf52/main-bare.cpp similarity index 100% rename from src/bare/main-bare.cpp rename to src/nrf52/main-bare.cpp diff --git a/src/bare/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp similarity index 100% rename from src/bare/main-nrf52.cpp rename to src/nrf52/main-nrf52.cpp From 7ff42b89a6cb16917bcd1955a98657084e175520 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 07:44:57 -0700 Subject: [PATCH 084/197] CI autobuilder can't yet build nrf52 targets --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 430e7b0d6..afba2b65f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = nrf52dk +default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used From dec487064939ac2fcf58351356eff58eb0622597 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 07:54:19 -0700 Subject: [PATCH 085/197] begin cleaning up mesh library layer so that it could be split someday --- .vscode/launch.json | 10 ++++------ bin/regen-protos.sh | 2 +- platformio.ini | 2 +- src/{ => mesh}/MeshRadio.cpp | 0 src/{ => mesh}/MeshRadio.h | 0 src/{ => mesh}/MeshService.cpp | 0 src/{ => mesh}/MeshService.h | 0 src/{ => mesh}/MeshTypes.h | 0 src/{ => mesh}/NodeDB.cpp | 0 src/{ => mesh}/NodeDB.h | 0 src/{ => mesh}/PhoneAPI.cpp | 0 src/{ => mesh}/PhoneAPI.h | 0 src/{ => mesh}/mesh-pb-constants.cpp | 0 src/{ => mesh}/mesh-pb-constants.h | 0 src/{ => mesh}/mesh.pb.c | 0 src/{ => mesh}/mesh.pb.h | 0 16 files changed, 6 insertions(+), 8 deletions(-) rename src/{ => mesh}/MeshRadio.cpp (100%) rename src/{ => mesh}/MeshRadio.h (100%) rename src/{ => mesh}/MeshService.cpp (100%) rename src/{ => mesh}/MeshService.h (100%) rename src/{ => mesh}/MeshTypes.h (100%) rename src/{ => mesh}/NodeDB.cpp (100%) rename src/{ => mesh}/NodeDB.h (100%) rename src/{ => mesh}/PhoneAPI.cpp (100%) rename src/{ => mesh}/PhoneAPI.h (100%) rename src/{ => mesh}/mesh-pb-constants.cpp (100%) rename src/{ => mesh}/mesh-pb-constants.h (100%) rename src/{ => mesh}/mesh.pb.c (100%) rename src/{ => mesh}/mesh.pb.h (100%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 68cb8d526..914831d68 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,9 +12,8 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/nrf52dk/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", - "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", "preLaunchTask": { "type": "PlatformIO", "task": "Pre-Debug" @@ -25,9 +24,8 @@ "type": "platformio-debug", "request": "launch", "name": "PIO Debug (skip Pre-Debug)", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/nrf52dk/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin", - "svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd", + "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", + "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", "internalConsoleOptions": "openOnSessionStart" } ] diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh index bc6bc9b72..0f1ae43c6 100755 --- a/bin/regen-protos.sh +++ b/bin/regen-protos.sh @@ -3,4 +3,4 @@ echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1" # the nanopb tool seems to require that the .options file be in the current directory! cd proto -../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src -I=../proto mesh.proto +../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src/mesh -I=../proto mesh.proto diff --git a/platformio.ini b/platformio.ini index afba2b65f..46c72b145 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 -Os -Wl,-Map,.pio/build/output.map +build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Isrc/mesh -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${sysenv.COUNTRY} -DAPP_VERSION=${sysenv.APP_VERSION} diff --git a/src/MeshRadio.cpp b/src/mesh/MeshRadio.cpp similarity index 100% rename from src/MeshRadio.cpp rename to src/mesh/MeshRadio.cpp diff --git a/src/MeshRadio.h b/src/mesh/MeshRadio.h similarity index 100% rename from src/MeshRadio.h rename to src/mesh/MeshRadio.h diff --git a/src/MeshService.cpp b/src/mesh/MeshService.cpp similarity index 100% rename from src/MeshService.cpp rename to src/mesh/MeshService.cpp diff --git a/src/MeshService.h b/src/mesh/MeshService.h similarity index 100% rename from src/MeshService.h rename to src/mesh/MeshService.h diff --git a/src/MeshTypes.h b/src/mesh/MeshTypes.h similarity index 100% rename from src/MeshTypes.h rename to src/mesh/MeshTypes.h diff --git a/src/NodeDB.cpp b/src/mesh/NodeDB.cpp similarity index 100% rename from src/NodeDB.cpp rename to src/mesh/NodeDB.cpp diff --git a/src/NodeDB.h b/src/mesh/NodeDB.h similarity index 100% rename from src/NodeDB.h rename to src/mesh/NodeDB.h diff --git a/src/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp similarity index 100% rename from src/PhoneAPI.cpp rename to src/mesh/PhoneAPI.cpp diff --git a/src/PhoneAPI.h b/src/mesh/PhoneAPI.h similarity index 100% rename from src/PhoneAPI.h rename to src/mesh/PhoneAPI.h diff --git a/src/mesh-pb-constants.cpp b/src/mesh/mesh-pb-constants.cpp similarity index 100% rename from src/mesh-pb-constants.cpp rename to src/mesh/mesh-pb-constants.cpp diff --git a/src/mesh-pb-constants.h b/src/mesh/mesh-pb-constants.h similarity index 100% rename from src/mesh-pb-constants.h rename to src/mesh/mesh-pb-constants.h diff --git a/src/mesh.pb.c b/src/mesh/mesh.pb.c similarity index 100% rename from src/mesh.pb.c rename to src/mesh/mesh.pb.c diff --git a/src/mesh.pb.h b/src/mesh/mesh.pb.h similarity index 100% rename from src/mesh.pb.h rename to src/mesh/mesh.pb.h From 178e8009699fedad85fe88ca4305df3375487a88 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 08:10:17 -0700 Subject: [PATCH 086/197] add beginnings of StreamAPI --- src/mesh/PhoneAPI.cpp | 5 ++--- src/mesh/PhoneAPI.h | 3 +++ src/mesh/StreamAPI.cpp | 1 + src/mesh/StreamAPI.h | 51 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 src/mesh/StreamAPI.cpp create mode 100644 src/mesh/StreamAPI.h diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index 043d1fb36..194a15e68 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -5,9 +5,8 @@ PhoneAPI::PhoneAPI() { - // Make sure that we never let our packets grow too large for one BLE packet - assert(FromRadio_size <= 512); - assert(ToRadio_size <= 512); + assert(FromRadio_size <= MAX_TO_FROM_RADIO_SIZE); + assert(ToRadio_size <= MAX_TO_FROM_RADIO_SIZE); } void PhoneAPI::init() diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 56eac067d..391644499 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -5,6 +5,9 @@ #include "mesh.pb.h" #include +// Make sure that we never let our packets grow too large for one BLE packet +#define MAX_TO_FROM_RADIO_SIZE 512 + /** * Provides our protobuf based API which phone/PC clients can use to talk to our device * over UDP, bluetooth or serial. diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp new file mode 100644 index 000000000..f363a001b --- /dev/null +++ b/src/mesh/StreamAPI.cpp @@ -0,0 +1 @@ +#include "StreamAPI.h" diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h new file mode 100644 index 000000000..0b5cfe64f --- /dev/null +++ b/src/mesh/StreamAPI.h @@ -0,0 +1,51 @@ +#pragma once + +#include "PhoneAPI.h" +#include "Stream.h" + +// A To/FromRadio packet + our 32 bit header +#define MAX_STREAM_BUF_SIZE (MAX_TO_FROM_RADIO_SIZE + sizeof(uint32_t)) + +/** + * A version of our 'phone' API that talks over a Stream. So therefore well suited to use with serial links + * or TCP connections. + * + * ## Wire encoding + +When sending protobuf packets over serial or TCP each packet is preceded by uint32 sent in network byte order (big endian). +The upper 16 bits must be 0x94C3. The lower 16 bits are packet length (this encoding gives room to eventually allow quite large +packets). + +Implementations validate length against the maximum possible size of a BLE packet (our lowest common denominator) of 512 bytes. If +the length provided is larger than that we assume the packet is corrupted and begin again looking for 0x4403 framing. + +The packets flowing towards the device are ToRadio protobufs, the packets flowing from the device are FromRadio protobufs. +The 0x94C3 marker can be used as framing to (eventually) resync if packets are corrupted over the wire. + +Note: the 0x94C3 framing was chosen to prevent confusion with the 7 bit ascii character set. It also doesn't collide with any +valid utf8 encoding. This makes it a bit easier to start a device outputting regular debug output on its serial port and then only +after it has received a valid packet from the PC, turn off unencoded debug printing and switch to this packet encoding. + + */ +class StreamAPI : public PhoneAPI +{ + /** + * The stream we read/write from + */ + Stream *stream; + + uint8_t rxBuf[MAX_STREAM_BUF_SIZE]; + size_t rxPtr = 0; + + public: + StreamAPI(Stream *_stream) : stream(_stream) {} + + /** + * Currently we require frequent invocation from loop() to check for arrived serial packets. + * FIXME, to support better power behavior instead move to a thread and block on serial reads. + */ + void loop(); + + private: + void readStream(); +}; \ No newline at end of file From dda946d9336424bd6ce5a73afa4bfc55415b47d2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 08:45:39 -0700 Subject: [PATCH 087/197] Stream API coded but not tested --- src/mesh/StreamAPI.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++ src/mesh/StreamAPI.h | 13 +++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index f363a001b..ece4f4f81 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -1 +1,69 @@ #include "StreamAPI.h" + +#define START1 0x94 +#define START2 0xc3 +#define HEADER_LEN 4 + +void StreamAPI::loop() +{ + writeStream(); + readStream(); +} + +/** + * Read any rx chars from the link and call handleToRadio + */ +void StreamAPI::readStream() +{ + while (stream->available()) { // Currently we never want to block + uint8_t c = stream->read(); + + // Use the read pointer for a little state machine, first look for framing, then length bytes, then payload + size_t ptr = rxPtr++; // assume we will probably advance the rxPtr + + rxBuf[ptr] = c; // store all bytes (including framing) + + if (ptr == 0) { // looking for START1 + if (c != START1) + rxPtr = 0; // failed to find framing + } else if (ptr == 1) { // looking for START2 + if (c != START2) + rxPtr = 0; // failed to find framing + } else if (ptr >= HEADER_LEN) { // we have at least read our 4 byte framing + uint16_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + + if (ptr == HEADER_LEN) { + // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid + // protobuf also) + if (len > MAX_TO_FROM_RADIO_SIZE) + rxPtr = 0; // length is bogus, restart search for framing + } + + if (rxPtr != 0 && ptr == len + HEADER_LEN) { + // If we didn't just fail the packet and we now have the right # of bytes, parse it + handleToRadio(rxBuf + HEADER_LEN, len); + } + } + } +} + +/** + * call getFromRadio() and deliver encapsulated packets to the Stream + */ +void StreamAPI::writeStream() +{ + uint32_t len; + + do { + // Send every packet we can + len = getFromRadio(txBuf + HEADER_LEN); + if (len != 0) { + txBuf[0] = START1; + txBuf[1] = START2; + txBuf[2] = (len >> 8) & 0xff; + txBuf[3] = len & 0xff; + + stream->write(txBuf, len + HEADER_LEN); + } + } while (len); +} \ No newline at end of file diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 0b5cfe64f..4b85ac4ef 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -37,15 +37,26 @@ class StreamAPI : public PhoneAPI uint8_t rxBuf[MAX_STREAM_BUF_SIZE]; size_t rxPtr = 0; + uint8_t txBuf[MAX_STREAM_BUF_SIZE]; + public: StreamAPI(Stream *_stream) : stream(_stream) {} /** - * Currently we require frequent invocation from loop() to check for arrived serial packets. + * Currently we require frequent invocation from loop() to check for arrived serial packets and to send new packets to the + * phone. * FIXME, to support better power behavior instead move to a thread and block on serial reads. */ void loop(); private: + /** + * Read any rx chars from the link and call handleToRadio + */ void readStream(); + + /** + * call getFromRadio() and deliver encapsulated packets to the Stream + */ + void writeStream(); }; \ No newline at end of file From eb40013ddcff35af8d6893f6623674783d723d35 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 09:01:25 -0700 Subject: [PATCH 088/197] Create RedirectablePrint and NoopPrint for serial debug redirection --- src/RedirectablePrint.cpp | 13 +++++++++++++ src/RedirectablePrint.h | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/RedirectablePrint.cpp create mode 100644 src/RedirectablePrint.h diff --git a/src/RedirectablePrint.cpp b/src/RedirectablePrint.cpp new file mode 100644 index 000000000..04205f498 --- /dev/null +++ b/src/RedirectablePrint.cpp @@ -0,0 +1,13 @@ +#include "RedirectablePrint.h" +#include + +/** + * A printer that doesn't go anywhere + */ +NoopPrint noopPrint; + +void RedirectablePrint::setDestination(Print *_dest) +{ + assert(_dest); + dest = _dest; +} \ No newline at end of file diff --git a/src/RedirectablePrint.h b/src/RedirectablePrint.h new file mode 100644 index 000000000..f75aea010 --- /dev/null +++ b/src/RedirectablePrint.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +/** + * A Printable that can be switched to squirt its bytes to a different sink. + * This class is mostly useful to allow debug printing to be redirected away from Serial + * to some other transport if we switch Serial usage (on the fly) to some other purpose. + */ +class RedirectablePrint : public Print +{ + Print *dest; + + public: + RedirectablePrint(Print *_dest) : dest(_dest) {} + + /** + * Set a new destination + */ + void setDestination(Print *dest); + + virtual size_t write(uint8_t c) { return dest->write(c); } +}; + +class NoopPrint : public Print +{ + public: + virtual size_t write(uint8_t c) { return 1; } +}; + +/** + * A printer that doesn't go anywhere + */ +extern NoopPrint noopPrint; \ No newline at end of file From e5d2d24e2c12e8bacf9dad224c69ec4812873186 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 09:27:36 -0700 Subject: [PATCH 089/197] move nanopb includes to correct directory --- lib/nanopb/{src => include}/pb.h | 0 lib/nanopb/{src => include}/pb_common.h | 0 lib/nanopb/{src => include}/pb_decode.h | 0 lib/nanopb/{src => include}/pb_encode.h | 0 platformio.ini | 2 +- 5 files changed, 1 insertion(+), 1 deletion(-) rename lib/nanopb/{src => include}/pb.h (100%) rename lib/nanopb/{src => include}/pb_common.h (100%) rename lib/nanopb/{src => include}/pb_decode.h (100%) rename lib/nanopb/{src => include}/pb_encode.h (100%) diff --git a/lib/nanopb/src/pb.h b/lib/nanopb/include/pb.h similarity index 100% rename from lib/nanopb/src/pb.h rename to lib/nanopb/include/pb.h diff --git a/lib/nanopb/src/pb_common.h b/lib/nanopb/include/pb_common.h similarity index 100% rename from lib/nanopb/src/pb_common.h rename to lib/nanopb/include/pb_common.h diff --git a/lib/nanopb/src/pb_decode.h b/lib/nanopb/include/pb_decode.h similarity index 100% rename from lib/nanopb/src/pb_decode.h rename to lib/nanopb/include/pb_decode.h diff --git a/lib/nanopb/src/pb_encode.h b/lib/nanopb/include/pb_encode.h similarity index 100% rename from lib/nanopb/src/pb_encode.h rename to lib/nanopb/include/pb_encode.h diff --git a/platformio.ini b/platformio.ini index 46c72b145..1db164b19 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 -Os -Wl,-Map,.pio/build/output.map +build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -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} From cceecf5f8e6ef6a4791f74e8bbca291c23e1a81b Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 09:36:39 -0700 Subject: [PATCH 090/197] New serial protobuf transport approximately works and is backward compatiable with the text debug output. --- src/SerialConsole.cpp | 32 ++++++++++++++++++++++++++++++++ src/SerialConsole.h | 24 ++++++++++++++++++++++++ src/configuration.h | 4 +++- src/main.cpp | 6 +++++- src/mesh/PhoneAPI.h | 4 ++-- src/mesh/StreamAPI.cpp | 27 ++++++++++++++------------- src/mesh/StreamAPI.h | 4 ++++ 7 files changed, 84 insertions(+), 17 deletions(-) create mode 100644 src/SerialConsole.cpp create mode 100644 src/SerialConsole.h diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp new file mode 100644 index 000000000..5a86dbc1d --- /dev/null +++ b/src/SerialConsole.cpp @@ -0,0 +1,32 @@ +#include "SerialConsole.h" +#include "configuration.h" +#include + +#define Port Serial + +SerialConsole console; + +SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port) +{ + canWrite = false; // We don't send packets to our port until it has talked to us first + // setDestination(&noopPrint); +} + +/// Do late init that can't happen at constructor time +void SerialConsole::init() +{ + Port.begin(SERIAL_BAUD); + StreamAPI::init(); +} + +/** + * we override this to notice when we've received a protobuf over the serial stream. Then we shunt off + * debug serial output. + */ +void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) +{ + setDestination(&noopPrint); + canWrite = true; + + StreamAPI::handleToRadio(buf, len); +} \ No newline at end of file diff --git a/src/SerialConsole.h b/src/SerialConsole.h new file mode 100644 index 000000000..50efb99af --- /dev/null +++ b/src/SerialConsole.h @@ -0,0 +1,24 @@ +#pragma once + +#include "RedirectablePrint.h" +#include "StreamAPI.h" +/** + * Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs + * (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs). + */ +class SerialConsole : public StreamAPI, public RedirectablePrint +{ + public: + SerialConsole(); + + /// Do late init that can't happen at constructor time + virtual void init(); + + /** + * we override this to notice when we've received a protobuf over the serial stream. Then we shunt off + * debug serial output. + */ + virtual void handleToRadio(const uint8_t *buf, size_t len); +}; + +extern SerialConsole console; diff --git a/src/configuration.h b/src/configuration.h index a47148ce9..8f443904e 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -237,7 +237,9 @@ along with this program. If not, see . #include "SEGGER_RTT.h" #define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__) #else -#define DEBUG_PORT Serial // Serial debug port +#include "SerialConsole.h" + +#define DEBUG_PORT console // Serial debug port #ifdef DEBUG_PORT #define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__) diff --git a/src/main.cpp b/src/main.cpp index ec59f9248..d0e8ddd7f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -126,7 +126,7 @@ void setup() // Debug #ifdef DEBUG_PORT - DEBUG_PORT.begin(SERIAL_BAUD); + DEBUG_PORT.init(); // Set serial baud rate and init our mesh console #endif initDeepSleep(); @@ -234,6 +234,10 @@ void loop() periodicScheduler.loop(); // axpDebugOutput.loop(); +#ifdef DEBUG_PORT + DEBUG_PORT.loop(); // Send/receive protobufs over the serial port +#endif + #ifndef NO_ESP32 esp32Loop(); #endif diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 391644499..337408246 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -57,12 +57,12 @@ class PhoneAPI PhoneAPI(); /// Do late init that can't happen at constructor time - void init(); + virtual void init(); /** * Handle a ToRadio protobuf */ - void handleToRadio(const uint8_t *buf, size_t len); + virtual void handleToRadio(const uint8_t *buf, size_t len); /** * Get the next packet we want to send to the phone diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index ece4f4f81..b82054877 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -52,18 +52,19 @@ void StreamAPI::readStream() */ void StreamAPI::writeStream() { - uint32_t len; + if (canWrite) { + uint32_t len; + do { + // Send every packet we can + len = getFromRadio(txBuf + HEADER_LEN); + if (len != 0) { + txBuf[0] = START1; + txBuf[1] = START2; + txBuf[2] = (len >> 8) & 0xff; + txBuf[3] = len & 0xff; - do { - // Send every packet we can - len = getFromRadio(txBuf + HEADER_LEN); - if (len != 0) { - txBuf[0] = START1; - txBuf[1] = START2; - txBuf[2] = (len >> 8) & 0xff; - txBuf[3] = len & 0xff; - - stream->write(txBuf, len + HEADER_LEN); - } - } while (len); + stream->write(txBuf, len + HEADER_LEN); + } + } while (len); + } } \ No newline at end of file diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index 4b85ac4ef..ec8e55946 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -59,4 +59,8 @@ class StreamAPI : public PhoneAPI * call getFromRadio() and deliver encapsulated packets to the Stream */ void writeStream(); + + protected: + /// Are we allowed to write packets to our output stream (subclasses can turn this off - i.e. SerialConsole) + bool canWrite = true; }; \ No newline at end of file From 9f49f90acd3f85645b94e29c7139f0d609f3f614 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 09:42:31 -0700 Subject: [PATCH 091/197] ignore vscode/launch.json --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 171f0f3c4..b26acad66 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,5 @@ main/credentials.h .vscode/* !.vscode/settings.json !.vscode/tasks.json -!.vscode/launch.json !.vscode/extensions.json *.code-workspace \ No newline at end of file From 88a704c4d3a5f5f7f5a2f50056bb2ba6b95e3814 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 09:46:06 -0700 Subject: [PATCH 092/197] for now, allow debug out to be interleaved with protobufs --- src/SerialConsole.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 5a86dbc1d..e5c2866c2 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -25,7 +25,11 @@ void SerialConsole::init() */ void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { - setDestination(&noopPrint); + // Note: for the time being we _allow_ debug printing to keep going out the console + // I _think_ this is okay because we currently only print debug msgs from loop() and we are only + // dispatching serial protobuf msgs from loop() as well. When things are more threaded in the future this + // will need to change. + // setDestination(&noopPrint); canWrite = true; StreamAPI::handleToRadio(buf, len); From 59086fd477d2b6a3a1da68febf7d978f527cce67 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 27 Apr 2020 18:52:57 -0700 Subject: [PATCH 093/197] fixes after testing stream protocol with python client --- src/SerialConsole.cpp | 4 ++-- src/mesh/StreamAPI.cpp | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index e5c2866c2..3a8917fd3 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -9,7 +9,7 @@ SerialConsole console; SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port) { canWrite = false; // We don't send packets to our port until it has talked to us first - // setDestination(&noopPrint); + // setDestination(&noopPrint); for testing, try turning off 'all' debug output and see what leaks } /// Do late init that can't happen at constructor time @@ -25,7 +25,7 @@ void SerialConsole::init() */ void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { - // Note: for the time being we _allow_ debug printing to keep going out the console + // Note: for the time being we could _allow_ debug printing to keep going out the console // I _think_ this is okay because we currently only print debug msgs from loop() and we are only // dispatching serial protobuf msgs from loop() as well. When things are more threaded in the future this // will need to change. diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index b82054877..674e758c9 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -39,9 +39,10 @@ void StreamAPI::readStream() rxPtr = 0; // length is bogus, restart search for framing } - if (rxPtr != 0 && ptr == len + HEADER_LEN) { + if (rxPtr != 0 && ptr + 1 == len + HEADER_LEN) { // If we didn't just fail the packet and we now have the right # of bytes, parse it handleToRadio(rxBuf + HEADER_LEN, len); + rxPtr = 0; // start over again } } } From 0193c281ef0e7713c6a68c7ea2f92db478ea30ef Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 28 Apr 2020 08:42:09 -0700 Subject: [PATCH 094/197] change webpage to say android app is in general availability --- docs/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index e3bbff93b..850ea89de 100644 --- a/docs/README.md +++ b/docs/README.md @@ -46,11 +46,11 @@ Note: Updates are happening almost daily, only major updates are listed below. F ## Meshtastic Android app -Once out of alpha the companion Android application will be released here: +Our Android application is available here: [![Download at https://play.google.com/store/apps/details?id=com.geeksville.mesh](https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png)](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dhomepage%26anid%3Dadmob) -But if you want the bleeding edge app now, we'd love to have your help testing. Three steps to opt-in to the alpha- test: +The link above will return older more stable releases. We would prefer if you join our alpha-test group, because the application is rapidly improving. Three steps to opt-in to the alpha- test: 1. Join [this Google group](https://groups.google.com/forum/#!forum/meshtastic-alpha-testers) with the account you use in Google Play. 2. Go to this [URL](https://play.google.com/apps/testing/com.geeksville.mesh) to opt-in to the alpha test. From b7049116039b3ddcd759ccb10c6dc539bd404ceb Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 28 Apr 2020 11:20:00 -0700 Subject: [PATCH 095/197] minor protobuf update --- proto | 2 +- src/mesh/mesh.pb.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/proto b/proto index 79b2cf728..8427b2301 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 79b2cf728c08007284542b32d9d075d01e8153d8 +Subproject commit 8427b23016dc96fc78885f05de5172e9eec5fe6d diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index f18b5c4f4..9e639ac7e 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -19,7 +19,7 @@ typedef enum _Constants { } Constants; typedef enum _Data_Type { - Data_Type_SIGNAL_OPAQUE = 0, + Data_Type_OPAQUE = 0, Data_Type_CLEAR_TEXT = 1, Data_Type_CLEAR_READACK = 2 } Data_Type; @@ -178,7 +178,7 @@ typedef struct _ToRadio { #define _Constants_MAX Constants_Unused #define _Constants_ARRAYSIZE ((Constants)(Constants_Unused+1)) -#define _Data_Type_MIN Data_Type_SIGNAL_OPAQUE +#define _Data_Type_MIN Data_Type_OPAQUE #define _Data_Type_MAX Data_Type_CLEAR_READACK #define _Data_Type_ARRAYSIZE ((Data_Type)(Data_Type_CLEAR_READACK+1)) From 02dfe7564f0c6ab215300922a147e99cb1fc022b Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 28 Apr 2020 15:39:05 -0700 Subject: [PATCH 096/197] 0.6.0 release --- bin/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/version.sh b/bin/version.sh index 6c7385723..f0cbb5657 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.5.5 \ No newline at end of file +export VERSION=0.6.0 \ No newline at end of file From 803d2dfefbd6605c6fd90c7ce080ac82db8830df Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 28 Apr 2020 17:06:00 -0700 Subject: [PATCH 097/197] add note about python API --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index 850ea89de..a10ff9f6a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,6 +13,7 @@ Note: Questions after reading this? See our new [forum](https://meshtastic.disco - Applications where closed source GPS communicators just won't cut it (it is easy to add features for glider pilots etc...) - Secure long-range communication within groups without depending on cellular providers - Finding your lost kids ;-) +- Through our [python API](https://pypi.org/project/meshtastic/) use these inexpensive radios to easily add mesh networking to your own projects. [![Youtube video demo](desk-video-screenshot.png)](https://www.youtube.com/watch?v=WlNbMbVZlHI "Meshtastic early demo") @@ -38,6 +39,7 @@ This software is 100% open source and developed by a group of hobbyist experimen Note: Updates are happening almost daily, only major updates are listed below. For more details see our forum. +- 04/28/2020 - 0.6.0 [Python API](https://pypi.org/project/meshtastic/) released. Makes it easy to use meshtastic devices as "zero config / just works" mesh transport adapters for other projects. - 04/20/2020 - 0.4.3 Pretty solid now both for the android app and the device code. Many people have donated translations and code. Probably going to call it a beta soon. - 03/03/2020 - 0.0.9 of the Android app and device code is released. Still an alpha but fairly functional. - 02/25/2020 - 0.0.4 of the Android app is released. This is a very early alpha, see below to join the alpha-testers group. From 2ab34357d5922c94055c74c227494f0e075acff3 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 28 Apr 2020 17:43:16 -0700 Subject: [PATCH 098/197] emit FromRadio.rebooted to serial test harness can detect reboots --- proto | 2 +- src/SerialConsole.cpp | 1 + src/mesh/PhoneAPI.h | 6 +++--- src/mesh/StreamAPI.cpp | 36 ++++++++++++++++++++++++++++-------- src/mesh/StreamAPI.h | 15 +++++++++++++-- src/mesh/mesh.pb.h | 5 ++++- 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/proto b/proto index 8427b2301..e570ee983 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 8427b23016dc96fc78885f05de5172e9eec5fe6d +Subproject commit e570ee9836949d9f420fd19cc59a2595c8669a6e diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 3a8917fd3..861219037 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -17,6 +17,7 @@ void SerialConsole::init() { Port.begin(SERIAL_BAUD); StreamAPI::init(); + emitRebooted(); } /** diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index 337408246..cb4ba1c34 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -45,9 +45,6 @@ class PhoneAPI /// We temporarily keep the nodeInfo here between the call to available and getFromRadio const NodeInfo *nodeInfoForPhone = NULL; - /// Our fromradio packet while it is being assembled - FromRadio fromRadioScratch; - ToRadio toRadioScratch; // this is a static scratch object, any data must be copied elsewhere before returning /// Use to ensure that clients don't get confused about old messages from the radio @@ -85,6 +82,9 @@ class PhoneAPI void handleSetRadio(const RadioConfig &r); protected: + /// Our fromradio packet while it is being assembled + FromRadio fromRadioScratch; + /** * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) */ diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 674e758c9..00434f90b 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -1,4 +1,5 @@ #include "StreamAPI.h" +#include "configuration.h" #define START1 0x94 #define START2 0xc3 @@ -58,14 +59,33 @@ void StreamAPI::writeStream() do { // Send every packet we can len = getFromRadio(txBuf + HEADER_LEN); - if (len != 0) { - txBuf[0] = START1; - txBuf[1] = START2; - txBuf[2] = (len >> 8) & 0xff; - txBuf[3] = len & 0xff; - - stream->write(txBuf, len + HEADER_LEN); - } + emitTxBuffer(len); } while (len); } +} + +/** + * Send the current txBuffer over our stream + */ +void StreamAPI::emitTxBuffer(size_t len) +{ + if (len != 0) { + txBuf[0] = START1; + txBuf[1] = START2; + txBuf[2] = (len >> 8) & 0xff; + txBuf[3] = len & 0xff; + + stream->write(txBuf, len + HEADER_LEN); + } +} + +void StreamAPI::emitRebooted() +{ + // In case we send a FromRadio packet + memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); + fromRadioScratch.which_variant = FromRadio_rebooted_tag; + fromRadioScratch.variant.rebooted = true; + + DEBUG_MSG("Emitting reboot packet for serial shell\n"); + emitTxBuffer(pb_encode_to_bytes(txBuf + HEADER_LEN, FromRadio_size, FromRadio_fields, &fromRadioScratch)); } \ No newline at end of file diff --git a/src/mesh/StreamAPI.h b/src/mesh/StreamAPI.h index ec8e55946..ed0a5fbd4 100644 --- a/src/mesh/StreamAPI.h +++ b/src/mesh/StreamAPI.h @@ -37,8 +37,6 @@ class StreamAPI : public PhoneAPI uint8_t rxBuf[MAX_STREAM_BUF_SIZE]; size_t rxPtr = 0; - uint8_t txBuf[MAX_STREAM_BUF_SIZE]; - public: StreamAPI(Stream *_stream) : stream(_stream) {} @@ -61,6 +59,19 @@ class StreamAPI : public PhoneAPI void writeStream(); protected: + /** + * Send a FromRadio.rebooted = true packet to the phone + */ + void emitRebooted(); + + /** + * Send the current txBuffer over our stream + */ + void emitTxBuffer(size_t len); + /// Are we allowed to write packets to our output stream (subclasses can turn this off - i.e. SerialConsole) bool canWrite = true; + + /// Subclasses can use this scratch buffer if they wish + uint8_t txBuf[MAX_STREAM_BUF_SIZE]; }; \ No newline at end of file diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 9e639ac7e..fdc266cdb 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -159,6 +159,7 @@ typedef struct _FromRadio { RadioConfig radio; DebugString debug_string; uint32_t config_complete_id; + bool rebooted; } variant; } FromRadio; @@ -289,6 +290,7 @@ typedef struct _ToRadio { #define FromRadio_radio_tag 6 #define FromRadio_debug_string_tag 7 #define FromRadio_config_complete_id_tag 8 +#define FromRadio_rebooted_tag 9 #define FromRadio_num_tag 1 #define ToRadio_packet_tag 1 #define ToRadio_want_config_id_tag 100 @@ -432,7 +434,8 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,my_info,variant.my_info), 3) \ X(a, STATIC, ONEOF, MESSAGE, (variant,node_info,variant.node_info), 4) \ X(a, STATIC, ONEOF, MESSAGE, (variant,radio,variant.radio), 6) \ X(a, STATIC, ONEOF, MESSAGE, (variant,debug_string,variant.debug_string), 7) \ -X(a, STATIC, ONEOF, UINT32, (variant,config_complete_id,variant.config_complete_id), 8) +X(a, STATIC, ONEOF, UINT32, (variant,config_complete_id,variant.config_complete_id), 8) \ +X(a, STATIC, ONEOF, BOOL, (variant,rebooted,variant.rebooted), 9) #define FromRadio_CALLBACK NULL #define FromRadio_DEFAULT NULL #define FromRadio_variant_packet_MSGTYPE MeshPacket From f1ec95f49bd83830ba523653629fa49a79d697af Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 28 Apr 2020 20:47:20 -0700 Subject: [PATCH 099/197] update project name --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 365786ac7..de814e081 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Meshtastic-esp32 +# Meshtastic-device This is the device side code for the [meshtastic.org](https://www.meshtastic.org) project. @@ -35,8 +35,8 @@ Please post comments on our [group chat](https://meshtastic.discourse.group/) if 1. Download and unzip the latest Meshtastic firmware [release](https://github.com/meshtastic/Meshtastic-esp32/releases). 2. Download [ESPHome Flasher](https://github.com/esphome/esphome-flasher/releases) (either x86-32bit Windows or x64-64 bit Windows). 3. Connect your radio to your USB port and open ESPHome Flasher. -4. If your board is not showing under Serial Port then you likely need to install the drivers for the CP210X serial chip. In Windows you can check by searching “Device Manager” and ensuring the device is shown under “Ports”. -5. If there is an error, download the drivers [here](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers), then unzip and run the Installer application. +4. If your board is not showing under Serial Port then you likely need to install the drivers for the CP210X serial chip. In Windows you can check by searching “Device Manager” and ensuring the device is shown under “Ports”. +5. If there is an error, download the drivers [here](https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers), then unzip and run the Installer application. 6. In ESPHome Flasher, refresh the serial ports and select your board. 7. Browse to the previously downloaded firmware and select the correct firmware based on the board type, country and frequency. 8. Select Flash ESP. From 94e80d3b44c4ab6d0bf42b1d2be53460adb746a0 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 28 Apr 2020 20:51:02 -0700 Subject: [PATCH 100/197] mention python API --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index de814e081..cacf68c91 100644 --- a/README.md +++ b/README.md @@ -143,15 +143,19 @@ Hard resetting via RTS pin... 7. The board will boot and show the Meshtastic logo. 8. Please post a comment on our chat so we know if these instructions worked for you ;-). If you find bugs/have-questions post there also - we will be rapidly iterating over the next few weeks. -## Meshtastic Android app +# Meshtastic Android app The source code for the (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android). -Alpha test builds are current available by opting into our alpha test group. See (www.meshtastic.org) for instructions. +Alpha test builds available by opting into our alpha test group. See (www.meshtastic.org) for instructions. -After our rate of change slows a bit, we will make beta builds available here (without needing to join the alphatest group): +If you don't want to live on the 'bleeding edge' you can opt-in to the beta-test or use the released version: [![Download at https://play.google.com/store/apps/details?id=com.geeksville.mesh](https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png)](https://play.google.com/store/apps/details?id=com.geeksville.mesh&referrer=utm_source%3Dgithub%26utm_medium%3Desp32-readme%26utm_campaign%3Dmeshtastic-esp32%2520readme%26anid%3Dadmob&pcampaignid=pcampaignidMKT-Other-global-all-co-prtnr-py-PartBadge-Mar2515-1) +# Python API + +We offer a [python API](https://github.com/meshtastic/Meshtastic-python) that makes it easy to use these devices to provide mesh networking for your custom projects. + # Development We'd love to have you join us on this merry little project. Please see our [development documents](./docs/software/sw-design.md) and [join us in our discussion forum](https://meshtastic.discourse.group/). From 1b265eb48d9b41ad91f3f2f5d6feb49529dba891 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 29 Apr 2020 10:50:50 -0700 Subject: [PATCH 101/197] switch from sx126x-arduino to radiolab --- platformio.ini | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index 1db164b19..9d10d06a6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -73,9 +73,8 @@ lib_deps = Wire ; explicitly needed here because the AXP202 library forgets to add it https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git - https://github.com/meshtastic/SX126x-Arduino.git - Ticker ; Needed for SX126x-Arduino on ESP32 - + https://github.com/jgromes/RadioLib.git + ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] src_filter = From a7d153abcb10474609c490621aa8fc506528ad94 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 29 Apr 2020 12:57:34 -0700 Subject: [PATCH 102/197] CUSTOM GPIOs the SX1262MB2CAS shield when installed on the NRF52840-DK development board --- .vscode/launch.json | 32 ---------------------------- platformio.ini | 4 ++-- variants/pca10056-rc-clock/variant.h | 6 ++++++ 3 files changed, 8 insertions(+), 34 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 914831d68..000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,32 +0,0 @@ -// AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY - -// PIO Unified Debugger -// -// Documentation: https://docs.platformio.org/page/plus/debugging.html -// Configuration: https://docs.platformio.org/page/projectconf/section_env_debug.html - -{ - "version": "0.2.0", - "configurations": [ - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", - "preLaunchTask": { - "type": "PlatformIO", - "task": "Pre-Debug" - }, - "internalConsoleOptions": "openOnSessionStart" - }, - { - "type": "platformio-debug", - "request": "launch", - "name": "PIO Debug (skip Pre-Debug)", - "executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf", - "toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin", - "internalConsoleOptions": "openOnSessionStart" - } - ] -} \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 9d10d06a6..ceb57fbc6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = nrf52dk ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used @@ -74,7 +74,7 @@ lib_deps = https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git https://github.com/jgromes/RadioLib.git - + ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] src_filter = diff --git a/variants/pca10056-rc-clock/variant.h b/variants/pca10056-rc-clock/variant.h index ee939780b..db5c71b67 100644 --- a/variants/pca10056-rc-clock/variant.h +++ b/variants/pca10056-rc-clock/variant.h @@ -139,6 +139,12 @@ static const uint8_t SCK = PIN_SPI_SCK; #define EXTERNAL_FLASH_DEVICES MX25R6435F #define EXTERNAL_FLASH_USE_QSPI +// CUSTOM GPIOs the SX1262MB2CAS shield when installed on the NRF52840-DK development board +#define SX1262_CS (32 + 8) // P1.08 +#define SX1262_DIO1 (32 + 6) // P1.06 +#define SX1262_RESET (0 + 3) // P0.03 +#define SX1262_ANT_SW (32 + 10) // P1.10 + #ifdef __cplusplus } #endif From 814c126e678b53d3fc1607f2d8b5495b86935338 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 29 Apr 2020 14:54:03 -0700 Subject: [PATCH 103/197] ugly WIP on switching to RadioLib, still need to set freq etc... --- boards/ppr.json | 2 +- docs/software/nrf52-TODO.md | 1 + platformio.ini | 2 +- src/configuration.h | 2 + src/main.cpp | 13 ++- src/mesh/MeshRadio.cpp | 3 +- src/mesh/MeshRadio.h | 10 +-- src/rf95/CustomRF95.cpp | 6 +- src/rf95/RadioInterface.h | 66 +++++++++++++++ src/rf95/RadioLibInterface.cpp | 118 +++++++++++++++++++++++++++ src/rf95/RadioLibInterface.h | 82 +++++++++++++++++++ variants/pca10056-rc-clock/variant.h | 1 + 12 files changed, 291 insertions(+), 15 deletions(-) create mode 100644 src/rf95/RadioLibInterface.cpp create mode 100644 src/rf95/RadioLibInterface.h diff --git a/boards/ppr.json b/boards/ppr.json index 5283fdc4e..5050758f7 100644 --- a/boards/ppr.json +++ b/boards/ppr.json @@ -10,7 +10,7 @@ "hwids": [["0x239A", "0x4403"]], "usb_product": "PPR", "mcu": "nrf52840", - "variant": "ppr", + "variant": "pca10056-rc-clock", "variants_dir": "variants", "bsp": { "name": "adafruit" diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index bf1d71b32..6e6555083 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -11,6 +11,7 @@ Minimum items needed to make sure hardware is good. - switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib - use "variants" to get all gpio bindings - plug in correct variants for the real board +- remove unused sx1262 lib from github - Use the PMU driver on real hardware - add a NEMA based GPS driver to test GPS - Use new radio driver on real hardware - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ diff --git a/platformio.ini b/platformio.ini index ceb57fbc6..d570b1190 100644 --- a/platformio.ini +++ b/platformio.ini @@ -73,7 +73,7 @@ lib_deps = Wire ; explicitly needed here because the AXP202 library forgets to add it https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git - https://github.com/jgromes/RadioLib.git + https://github.com/meshtastic/RadioLib.git ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] diff --git a/src/configuration.h b/src/configuration.h index 8f443904e..b02a1a00f 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -217,10 +217,12 @@ along with this program. If not, see . #define LED_INVERTED 1 // Temporarily testing if we can build the RF95 driver for NRF52 +#if 0 #define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio #define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio #define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number #define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#endif #endif diff --git a/src/main.cpp b/src/main.cpp index d0e8ddd7f..f79c9e016 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -118,6 +118,9 @@ static uint32_t ledBlinker() Periodic ledPeriodic(ledBlinker); +#include "RadioLibInterface.h" +#include "variant.h" + void setup() { #ifdef USE_SEGGER @@ -189,7 +192,15 @@ void setup() realRouter.setup(); // required for our periodic task (kinda skanky FIXME) // MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) - radio = new MeshRadio(); + RadioInterface *rIf = +#if defined(RF95_IRQ_GPIO) + new CustomRF95(); +#elif defined(SX1262_CS) + new RadioLibInterface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI); +#else + new SimRadio(); +#endif + radio = new MeshRadio(rIf); router.addInterface(&radio->radioIf); if (radio && !radio->init()) diff --git a/src/mesh/MeshRadio.cpp b/src/mesh/MeshRadio.cpp index 4242a94a2..9c21937cc 100644 --- a/src/mesh/MeshRadio.cpp +++ b/src/mesh/MeshRadio.cpp @@ -24,7 +24,7 @@ separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts /// Sometimes while debugging it is useful to set this false, to disable rf95 accesses bool useHardware = true; -MeshRadio::MeshRadio() // , manager(radioIf) +MeshRadio::MeshRadio(RadioInterface *rIf) : radioIf(*rIf) // , manager(radioIf) { myNodeInfo.num_channels = NUM_CHANNELS; @@ -122,4 +122,3 @@ int MeshRadio::reloadConfig(void *unused) return 0; } - diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index 186af9f5e..da19eebb8 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -63,23 +63,19 @@ /** * A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids) + * FIXME - REMOVE THIS CLASS */ class MeshRadio { public: // Kinda ugly way of selecting different radio implementations, but soon this MeshRadio class will be going away // entirely. At that point we can make things pretty. -#ifdef RF95_IRQ_GPIO - CustomRF95 - radioIf; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing -#else - SimRadio radioIf; -#endif + RadioInterface &radioIf; /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool */ - MeshRadio(); + MeshRadio(RadioInterface *rIf); bool init(); diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index 7005a9ce1..eba19006a 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -149,7 +149,7 @@ void CustomRF95::handleIdleISR() startSend(txp); else { // Nothing to send, let's switch back to receive mode - setModeRx(); + RH_RF95::setModeRx(); } } @@ -193,9 +193,9 @@ void CustomRF95::loop() // It should never take us more than 30 secs to send a packet, if it does, we have a bug, FIXME, move most of this // into CustomRF95 uint32_t now = millis(); - if (lastTxStart != 0 && (now - lastTxStart) > TX_WATCHDOG_TIMEOUT && mode() == RHGenericDriver::RHModeTx) { + if (lastTxStart != 0 && (now - lastTxStart) > TX_WATCHDOG_TIMEOUT && RH_RF95::mode() == RHGenericDriver::RHModeTx) { DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode\n"); - setModeRx(); + RH_RF95::setModeRx(); if (sendingPacket) { // There was probably a packet we were trying to send, free it packetPool.release(sendingPacket); sendingPacket = NULL; diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index bad532a1e..59f64cdf7 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -55,6 +55,72 @@ class RadioInterface * If the txmit queue is full it might return an error */ virtual ErrorCode send(MeshPacket *p) = 0; + + // methods from radiohead + + /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. + /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, + /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. + /// In promiscuous mode, all messages will be accepted regardless of the TO header. + /// In a conventional multinode system, all nodes will have a unique address + /// (which you could store in EEPROM). + /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, + /// allowing the possibilty of address spoofing). + /// \param[in] thisAddress The address of this node. + virtual void setThisAddress(uint8_t thisAddress) = 0; + + /// 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; + + /// Sets the transmitter and receiver + /// centre frequency. + /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several + /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work + /// \return true if the selected frquency centre is within range + virtual bool setFrequency(float centre) = 0; + + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. + /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator + /// for reliable operation. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + virtual bool setModemConfig(RH_RF95::ModemConfigChoice index) = 0; + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + virtual void setModeIdle() = 0; + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the RF95/96/97/98. + virtual void setModeRx() = 0; + + /// Returns the operating mode of the library. + /// \return the current mode, one of RF69_MODE_* + virtual RHGenericDriver::RHMode mode() = 0; + + /// Sets the transmitter power output level, and configures the transmitter pin. + /// Be a good neighbour and set the lowest power level you need. + /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) + /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) + /// while some (such as the Modtronix inAir4 and inAir9) + /// use the RFO transmitter pin for lower power but higher efficiency. + /// You must set the appropriate power level and useRFO argument for your module. + /// Check with your module manufacturer which transmtter pin is used on your module + /// to ensure you are setting useRFO correctly. + /// Failure to do so will result in very low + /// transmitter power output. + /// Caution: legal power limits may apply in certain countries. + /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). + /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, + /// valid values are from +5 to +23. + /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), + /// valid values are from -1 to 14. + /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of + /// the PA_BOOST pin (false). Choose the correct setting for your module. + void setTxPower(int8_t power, bool useRFO = false) {} }; class SimRadio : public RadioInterface diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp new file mode 100644 index 000000000..b387a253e --- /dev/null +++ b/src/rf95/RadioLibInterface.cpp @@ -0,0 +1,118 @@ +#include "RadioLibInterface.h" +#include + +// FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that +static SPISettings spiSettings; + +RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, + SPIClass &spi) + : module(cs, irq, rst, busy, spi, spiSettings), lora(&module) +{ +} + +ErrorCode RadioLibInterface::send(MeshPacket *p) +{ + return ERR_NONE; +} + +/// Initialise the Driver transport hardware and software. +/// Make sure the Driver is properly configured before calling init(). +/// \return true if initialisation succeeded. +bool RadioLibInterface::init() +{ + // FIXME, move this to main + SPI.begin(); + + int res = lora.begin(); + DEBUG_MSG("LORA init result %d\n", res); + + return res == ERR_NONE; +} + +/** + * + * + * +// include the library + + +// SX1262 has the following connections: +// NSS pin: 10 +// DIO1 pin: 2 +// NRST pin: 3 +// BUSY pin: 9 +SX1262 lora = new Module(10, 2, 3, 9); + +// or using RadioShield +// https://github.com/jgromes/RadioShield +//SX1262 lora = RadioShield.ModuleA; + +void setup() { + Serial.begin(9600); + + // initialize SX1262 with default settings + Serial.print(F("[SX1262] Initializing ... ")); + // carrier frequency: 434.0 MHz + // bandwidth: 125.0 kHz + // spreading factor: 9 + // coding rate: 7 + // sync word: 0x12 (private network) + // output power: 14 dBm + // current limit: 60 mA + // preamble length: 8 symbols + // TCXO voltage: 1.6 V (set to 0 to not use TCXO) + // regulator: DC-DC (set to true to use LDO) + // CRC: enabled + int state = lora.begin(); + if (state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while (true); + } +} + +void loop() { + Serial.print(F("[SX1262] Transmitting packet ... ")); + + // you can transmit C-string or Arduino string up to + // 256 characters long + // NOTE: transmit() is a blocking method! + // See example SX126x_Transmit_Interrupt for details + // on non-blocking transmission method. + int state = lora.transmit("Hello World!"); + + // you can also transmit byte array up to 256 bytes long + + byte byteArr[] = {0x01, 0x23, 0x45, 0x56, 0x78, 0xAB, 0xCD, 0xEF}; + int state = lora.transmit(byteArr, 8); + + +if (state == ERR_NONE) { + // the packet was successfully transmitted + Serial.println(F("success!")); + + // print measured data rate + Serial.print(F("[SX1262] Datarate:\t")); + Serial.print(lora.getDataRate()); + Serial.println(F(" bps")); + +} else if (state == ERR_PACKET_TOO_LONG) { + // the supplied packet was longer than 256 bytes + Serial.println(F("too long!")); + +} else if (state == ERR_TX_TIMEOUT) { + // timeout occured while transmitting packet + Serial.println(F("timeout!")); + +} else { + // some other error occurred + Serial.print(F("failed, code ")); + Serial.println(state); +} + +// wait for a second before transmitting again +delay(1000); +} + */ \ No newline at end of file diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h new file mode 100644 index 000000000..a4ee06f9f --- /dev/null +++ b/src/rf95/RadioLibInterface.h @@ -0,0 +1,82 @@ +#pragma once + +#include "RadioInterface.h" + +#include + +class RadioLibInterface : public RadioInterface +{ + Module module; // The HW interface to the radio + SX1262 lora; + + public: + RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi); + + virtual ErrorCode send(MeshPacket *p); + + // methods from radiohead + + /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. + /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, + /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. + /// In promiscuous mode, all messages will be accepted regardless of the TO header. + /// In a conventional multinode system, all nodes will have a unique address + /// (which you could store in EEPROM). + /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, + /// allowing the possibilty of address spoofing). + /// \param[in] thisAddress The address of this node. + virtual void setThisAddress(uint8_t thisAddress) {} + + /// 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(); + + /// Sets the transmitter and receiver + /// centre frequency. + /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several + /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work + /// \return true if the selected frquency centre is within range + bool setFrequency(float centre) { return true; } + + /// Select one of the predefined modem configurations. If you need a modem configuration not provided + /// here, use setModemRegisters() with your own ModemConfig. + /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator + /// for reliable operation. + /// \param[in] index The configuration choice. + /// \return true if index is a valid choice. + bool setModemConfig(RH_RF95::ModemConfigChoice index) { return true; } + + /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, + /// disables them. + void setModeIdle() {} + + /// If current mode is Tx or Idle, changes it to Rx. + /// Starts the receiver in the RF95/96/97/98. + void setModeRx() {} + + /// Returns the operating mode of the library. + /// \return the current mode, one of RF69_MODE_* + virtual RHGenericDriver::RHMode mode() { return RHGenericDriver::RHModeIdle; } + + /// Sets the transmitter power output level, and configures the transmitter pin. + /// Be a good neighbour and set the lowest power level you need. + /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) + /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) + /// while some (such as the Modtronix inAir4 and inAir9) + /// use the RFO transmitter pin for lower power but higher efficiency. + /// You must set the appropriate power level and useRFO argument for your module. + /// Check with your module manufacturer which transmtter pin is used on your module + /// to ensure you are setting useRFO correctly. + /// Failure to do so will result in very low + /// transmitter power output. + /// Caution: legal power limits may apply in certain countries. + /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). + /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, + /// valid values are from +5 to +23. + /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), + /// valid values are from -1 to 14. + /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of + /// the PA_BOOST pin (false). Choose the correct setting for your module. + void setTxPower(int8_t power, bool useRFO = false) {} +}; \ No newline at end of file diff --git a/variants/pca10056-rc-clock/variant.h b/variants/pca10056-rc-clock/variant.h index db5c71b67..b7201812a 100644 --- a/variants/pca10056-rc-clock/variant.h +++ b/variants/pca10056-rc-clock/variant.h @@ -142,6 +142,7 @@ static const uint8_t SCK = PIN_SPI_SCK; // CUSTOM GPIOs the SX1262MB2CAS shield when installed on the NRF52840-DK development board #define SX1262_CS (32 + 8) // P1.08 #define SX1262_DIO1 (32 + 6) // P1.06 +#define SX1262_BUSY (32 + 4) // P1.04 #define SX1262_RESET (0 + 3) // P0.03 #define SX1262_ANT_SW (32 + 10) // P1.10 From 4693302d820bae9a69d98156003a1dac6f1af2c5 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 29 Apr 2020 16:06:23 -0700 Subject: [PATCH 104/197] crummy sx1262 fake init kinda works --- src/rf95/RadioLibInterface.cpp | 43 ++++------------------------------ src/rf95/RadioLibInterface.h | 10 ++++++++ 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index b387a253e..32d4704ff 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -2,7 +2,7 @@ #include // FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that -static SPISettings spiSettings; +static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi) @@ -23,7 +23,10 @@ bool RadioLibInterface::init() // FIXME, move this to main SPI.begin(); - int res = lora.begin(); + 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? + + int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO); DEBUG_MSG("LORA init result %d\n", res); return res == ERR_NONE; @@ -36,42 +39,6 @@ bool RadioLibInterface::init() // include the library -// SX1262 has the following connections: -// NSS pin: 10 -// DIO1 pin: 2 -// NRST pin: 3 -// BUSY pin: 9 -SX1262 lora = new Module(10, 2, 3, 9); - -// or using RadioShield -// https://github.com/jgromes/RadioShield -//SX1262 lora = RadioShield.ModuleA; - -void setup() { - Serial.begin(9600); - - // initialize SX1262 with default settings - Serial.print(F("[SX1262] Initializing ... ")); - // carrier frequency: 434.0 MHz - // bandwidth: 125.0 kHz - // spreading factor: 9 - // coding rate: 7 - // sync word: 0x12 (private network) - // output power: 14 dBm - // current limit: 60 mA - // preamble length: 8 symbols - // TCXO voltage: 1.6 V (set to 0 to not use TCXO) - // regulator: DC-DC (set to true to use LDO) - // CRC: enabled - int state = lora.begin(); - if (state == ERR_NONE) { - Serial.println(F("success!")); - } else { - Serial.print(F("failed, code ")); - Serial.println(state); - while (true); - } -} void loop() { Serial.print(F("[SX1262] Transmitting packet ... ")); diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index a4ee06f9f..86bcd95c4 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -79,4 +79,14 @@ class RadioLibInterface : public RadioInterface /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of /// the PA_BOOST pin (false). Choose the correct setting for your module. void setTxPower(int8_t power, bool useRFO = false) {} + + private: + float freq = 915.0; // FIXME, init all these params from suer setings + float bw = 125; + uint8_t sf = 9; + uint8_t cr = 7; + uint8_t syncWord = 0; // FIXME, use a meshtastic sync word, but hashed with the Channel name + int8_t power = 17; + float currentLimit = 100; // FIXME + uint16_t preambleLength = 8; }; \ No newline at end of file From 8d985cfd37330f11d815a43129aef8ae96231841 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 29 Apr 2020 16:28:11 -0700 Subject: [PATCH 105/197] cleanup so eventually rf95 can share common msg code with sx1262 --- src/main.cpp | 4 ++-- src/rf95/RadioLibInterface.cpp | 21 ++------------------- src/rf95/RadioLibInterface.h | 32 +++++++++++++++++++------------- src/rf95/SX1262Interface.cpp | 28 ++++++++++++++++++++++++++++ src/rf95/SX1262Interface.h | 16 ++++++++++++++++ 5 files changed, 67 insertions(+), 34 deletions(-) create mode 100644 src/rf95/SX1262Interface.cpp create mode 100644 src/rf95/SX1262Interface.h diff --git a/src/main.cpp b/src/main.cpp index f79c9e016..22187b40e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -118,7 +118,7 @@ static uint32_t ledBlinker() Periodic ledPeriodic(ledBlinker); -#include "RadioLibInterface.h" +#include "SX1262Interface.h" #include "variant.h" void setup() @@ -196,7 +196,7 @@ void setup() #if defined(RF95_IRQ_GPIO) new CustomRF95(); #elif defined(SX1262_CS) - new RadioLibInterface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI); + new SX1262Interface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI); #else new SimRadio(); #endif diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 32d4704ff..9f5858e06 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -5,8 +5,8 @@ static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, - SPIClass &spi) - : module(cs, irq, rst, busy, spi, spiSettings), lora(&module) + SPIClass &spi, PhysicalLayer *_iface) + : module(cs, irq, rst, busy, spi, spiSettings), iface(*_iface) { } @@ -15,23 +15,6 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) return ERR_NONE; } -/// Initialise the Driver transport hardware and software. -/// Make sure the Driver is properly configured before calling init(). -/// \return true if initialisation succeeded. -bool RadioLibInterface::init() -{ - // FIXME, move this to main - SPI.begin(); - - 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? - - int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO); - DEBUG_MSG("LORA init result %d\n", res); - - return res == ERR_NONE; -} - /** * * diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index 86bcd95c4..6bccd7b2c 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -6,11 +6,27 @@ class RadioLibInterface : public RadioInterface { + protected: + float freq = 915.0; // FIXME, init all these params from suer setings + float bw = 125; + uint8_t sf = 9; + uint8_t cr = 7; + uint8_t syncWord = 0; // FIXME, use a meshtastic sync word, but hashed with the Channel name + int8_t power = 17; + float currentLimit = 100; // FIXME + uint16_t preambleLength = 8; + Module module; // The HW interface to the radio - SX1262 lora; + + /** + * provides lowest common denominator RadioLib API + */ + PhysicalLayer &iface; public: - RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi); + RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, + SPIClass &spi, + PhysicalLayer *iface); virtual ErrorCode send(MeshPacket *p); @@ -30,7 +46,7 @@ class RadioLibInterface : public 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(); + virtual bool init() { return true; } /// Sets the transmitter and receiver /// centre frequency. @@ -79,14 +95,4 @@ class RadioLibInterface : public RadioInterface /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of /// the PA_BOOST pin (false). Choose the correct setting for your module. void setTxPower(int8_t power, bool useRFO = false) {} - - private: - float freq = 915.0; // FIXME, init all these params from suer setings - float bw = 125; - uint8_t sf = 9; - uint8_t cr = 7; - uint8_t syncWord = 0; // FIXME, use a meshtastic sync word, but hashed with the Channel name - int8_t power = 17; - float currentLimit = 100; // FIXME - uint16_t preambleLength = 8; }; \ No newline at end of file diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp new file mode 100644 index 000000000..90604c6c1 --- /dev/null +++ b/src/rf95/SX1262Interface.cpp @@ -0,0 +1,28 @@ +#include "SX1262Interface.h" +#include + +SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, + SPIClass &spi) + : RadioLibInterface(cs, irq, rst, busy, spi, &lora), lora(&module) +{ +} + +/// Initialise the Driver transport hardware and software. +/// Make sure the Driver is properly configured before calling init(). +/// \return true if initialisation succeeded. +bool SX1262Interface::init() +{ + if (!RadioLibInterface::init()) + return false; + + // FIXME, move this to main + SPI.begin(); + + 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? + + int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO); + DEBUG_MSG("LORA init result %d\n", res); + + return res == ERR_NONE; +} diff --git a/src/rf95/SX1262Interface.h b/src/rf95/SX1262Interface.h new file mode 100644 index 000000000..aa577a734 --- /dev/null +++ b/src/rf95/SX1262Interface.h @@ -0,0 +1,16 @@ +#pragma once + +#include "RadioLibInterface.h" + +class SX1262Interface : public RadioLibInterface +{ + SX1262 lora; + + public: + SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi); + + /// 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(); +}; \ No newline at end of file From f69ddf168b6914685720649e6e26ea5622c4f80c Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 29 Apr 2020 18:46:32 -0700 Subject: [PATCH 106/197] we now hopefully apply the same radio settings as we did for the RF95 --- src/main.cpp | 6 +++ src/mesh/MeshRadio.cpp | 41 ++++++--------- src/mesh/MeshRadio.h | 5 ++ src/rf95/CustomRF95.cpp | 29 +++++++++++ src/rf95/CustomRF95.h | 2 + src/rf95/RadioInterface.cpp | 3 +- src/rf95/RadioInterface.h | 91 +++------------------------------- src/rf95/RadioLibInterface.cpp | 31 ++++++++++++ src/rf95/RadioLibInterface.h | 67 ++++++------------------- src/rf95/SX1262Interface.cpp | 39 +++++++++++++++ src/rf95/SX1262Interface.h | 5 ++ 11 files changed, 156 insertions(+), 163 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 22187b40e..8d95b9609 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -191,6 +191,12 @@ void setup() realRouter.setup(); // required for our periodic task (kinda skanky FIXME) +#ifdef SX1262_ANT_SW + // make analog PA vs not PA switch on SX1262 eval board work properly + pinMode(SX1262_ANT_SW, OUTPUT); + digitalWrite(SX1262_ANT_SW, 1); +#endif + // MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) RadioInterface *rIf = #if defined(RF95_IRQ_GPIO) diff --git a/src/mesh/MeshRadio.cpp b/src/mesh/MeshRadio.cpp index 9c21937cc..0437c3f36 100644 --- a/src/mesh/MeshRadio.cpp +++ b/src/mesh/MeshRadio.cpp @@ -57,16 +57,15 @@ bool MeshRadio::init() radioIf.setThisAddress( nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time. + applySettings(); + if (!radioIf.init()) { DEBUG_MSG("LoRa radio init failed\n"); DEBUG_MSG("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info\n"); return false; } - // not needed - defaults on - // rf95.setPayloadCRC(true); - - reloadConfig(); + // No need to call this now, init is supposed to do same. reloadConfig(); return true; } @@ -87,38 +86,28 @@ unsigned long hash(char *str) return hash; } -int MeshRadio::reloadConfig(void *unused) +/** + * Pull our channel settings etc... from protobufs to the dumb interface settings + */ +void MeshRadio::applySettings() { - radioIf.setModeIdle(); // Need to be idle before doing init - // Set up default configuration // No Sync Words in LORA mode. - radioIf.setModemConfig( - (RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default - // setModemConfig(Bw125Cr48Sf4096); // slow and reliable? - // rf95.setPreambleLength(8); // Default is 8 + radioIf.modemConfig = (RH_RF95::ModemConfigChoice)channelSettings.modem_config; // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM int channel_num = hash(channelSettings.name) % NUM_CHANNELS; - float center_freq = CH0 + CH_SPACING * channel_num; - if (!radioIf.setFrequency(center_freq)) { - DEBUG_MSG("setFrequency failed\n"); - assert(0); // fixme panic - } - - // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on - - // The default transmitter power is 13dBm, using PA_BOOST. - // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then - // you can set transmitter powers from 5 to 23 dBm: - // FIXME - can we do this? It seems to be in the Heltec board. - radioIf.setTxPower(channelSettings.tx_power, false); + radioIf.freq = CH0 + CH_SPACING * channel_num; + radioIf.power = channelSettings.tx_power; DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config, channel_num, channelSettings.tx_power); +} - // Done with init tell radio to start receiving - radioIf.setModeRx(); +int MeshRadio::reloadConfig(void *unused) +{ + applySettings(); + radioIf.reconfigure(); return 0; } diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index da19eebb8..1580cea47 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -100,4 +100,9 @@ class MeshRadio radioIf.sleep(); return 0; } + + /** + * Pull our channel settings etc... from protobufs to the dumb interface settings + */ + void applySettings(); }; diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index eba19006a..59eb38a78 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -38,6 +38,9 @@ bool CustomRF95::init() { bool ok = RH_RF95::init(); + if (ok) + reconfigure(); // Finish our device setup + return ok; } @@ -205,4 +208,30 @@ void CustomRF95::loop() } } +bool CustomRF95::reconfigure() +{ + radioIf.setModeIdle(); // Need to be idle before doing init + + // Set up default configuration + // No Sync Words in LORA mode. + setModemConfig(modemConfig); // Radio default + // setModemConfig(Bw125Cr48Sf4096); // slow and reliable? + // rf95.setPreambleLength(8); // Default is 8 + + if (!setFrequency(freq)) { + DEBUG_MSG("setFrequency failed\n"); + assert(0); // fixme panic + } + + // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on + + // The default transmitter power is 13dBm, using PA_BOOST. + // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then + // you can set transmitter powers from 5 to 23 dBm: + // FIXME - can we do this? It seems to be in the Heltec board. + radioIf.setTxPower(tx_power, false); + + // Done with init tell radio to start receiving + radioIf.setModeRx(); +} #endif \ No newline at end of file diff --git a/src/rf95/CustomRF95.h b/src/rf95/CustomRF95.h index ad7996480..5582ab830 100644 --- a/src/rf95/CustomRF95.h +++ b/src/rf95/CustomRF95.h @@ -40,6 +40,8 @@ class CustomRF95 : public RH_RF95, public RadioInterface bool init(); + bool reconfigure(); + void loop(); // Idle processing protected: diff --git a/src/rf95/RadioInterface.cpp b/src/rf95/RadioInterface.cpp index d43922ffb..c2f555c32 100644 --- a/src/rf95/RadioInterface.cpp +++ b/src/rf95/RadioInterface.cpp @@ -19,4 +19,5 @@ void RadioInterface::deliverToReceiver(MeshPacket *p) { assert(rxDest); assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages -} \ No newline at end of file +} + diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index 59f64cdf7..612c25a65 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -27,6 +27,10 @@ class RadioInterface void deliverToReceiver(MeshPacket *p); public: + float freq = 915.0; // FIXME, init all these params from user setings + int8_t power = 17; + RH_RF95::ModemConfigChoice modemConfig; + /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool */ @@ -74,53 +78,10 @@ class RadioInterface /// \return true if initialisation succeeded. virtual bool init() = 0; - /// Sets the transmitter and receiver - /// centre frequency. - /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several - /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work - /// \return true if the selected frquency centre is within range - virtual bool setFrequency(float centre) = 0; - - /// Select one of the predefined modem configurations. If you need a modem configuration not provided - /// here, use setModemRegisters() with your own ModemConfig. - /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator - /// for reliable operation. - /// \param[in] index The configuration choice. - /// \return true if index is a valid choice. - virtual bool setModemConfig(RH_RF95::ModemConfigChoice index) = 0; - - /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, - /// disables them. - virtual void setModeIdle() = 0; - - /// If current mode is Tx or Idle, changes it to Rx. - /// Starts the receiver in the RF95/96/97/98. - virtual void setModeRx() = 0; - - /// Returns the operating mode of the library. - /// \return the current mode, one of RF69_MODE_* - virtual RHGenericDriver::RHMode mode() = 0; - - /// Sets the transmitter power output level, and configures the transmitter pin. - /// Be a good neighbour and set the lowest power level you need. - /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) - /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) - /// while some (such as the Modtronix inAir4 and inAir9) - /// use the RFO transmitter pin for lower power but higher efficiency. - /// You must set the appropriate power level and useRFO argument for your module. - /// Check with your module manufacturer which transmtter pin is used on your module - /// to ensure you are setting useRFO correctly. - /// Failure to do so will result in very low - /// transmitter power output. - /// Caution: legal power limits may apply in certain countries. - /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). - /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, - /// valid values are from +5 to +23. - /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), - /// valid values are from -1 to 14. - /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of - /// the PA_BOOST pin (false). Choose the correct setting for your module. - void setTxPower(int8_t power, bool useRFO = false) {} + /// Apply any radio provisioning changes + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool reconfigure() = 0; }; class SimRadio : public RadioInterface @@ -146,21 +107,6 @@ class SimRadio : public RadioInterface /// \return true if initialisation succeeded. virtual bool init() { return true; } - /// Sets the transmitter and receiver - /// centre frequency. - /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several - /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work - /// \return true if the selected frquency centre is within range - bool setFrequency(float centre) { return true; } - - /// Select one of the predefined modem configurations. If you need a modem configuration not provided - /// here, use setModemRegisters() with your own ModemConfig. - /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator - /// for reliable operation. - /// \param[in] index The configuration choice. - /// \return true if index is a valid choice. - bool setModemConfig(RH_RF95::ModemConfigChoice index) { return true; } - /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, /// disables them. void setModeIdle() {} @@ -172,25 +118,4 @@ class SimRadio : public RadioInterface /// Returns the operating mode of the library. /// \return the current mode, one of RF69_MODE_* virtual RHGenericDriver::RHMode mode() { return RHGenericDriver::RHModeIdle; } - - /// Sets the transmitter power output level, and configures the transmitter pin. - /// Be a good neighbour and set the lowest power level you need. - /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) - /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) - /// while some (such as the Modtronix inAir4 and inAir9) - /// use the RFO transmitter pin for lower power but higher efficiency. - /// You must set the appropriate power level and useRFO argument for your module. - /// Check with your module manufacturer which transmtter pin is used on your module - /// to ensure you are setting useRFO correctly. - /// Failure to do so will result in very low - /// transmitter power output. - /// Caution: legal power limits may apply in certain countries. - /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). - /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, - /// valid values are from +5 to +23. - /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), - /// valid values are from -1 to 14. - /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of - /// the PA_BOOST pin (false). Choose the correct setting for your module. - void setTxPower(int8_t power, bool useRFO = false) {} }; diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 9f5858e06..c9f59b05d 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -10,6 +10,37 @@ RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq { } +/** + * Convert our modemConfig enum into wf, sf, etc... + */ +void RadioLibInterface::applyModemConfig() +{ + switch (modemConfig) { + case RH_RF95::Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + bw = 125; + cr = 5; + sf = 7; + break; + case RH_RF95::Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + bw = 500; + cr = 5; + sf = 7; + break; + case RH_RF95::Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + bw = 31.25; + cr = 8; + sf = 9; + break; + case RH_RF95::Bw125Cr48Sf4096: + bw = 125; + cr = 8; + sf = 12; + break; + default: + assert(0); // Unknown enum + } +} + ErrorCode RadioLibInterface::send(MeshPacket *p) { return ERR_NONE; diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index 6bccd7b2c..9ca0f0de7 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -7,12 +7,16 @@ class RadioLibInterface : public RadioInterface { protected: - float freq = 915.0; // FIXME, init all these params from suer setings float bw = 125; uint8_t sf = 9; uint8_t cr = 7; - uint8_t syncWord = 0; // FIXME, use a meshtastic sync word, but hashed with the Channel name - int8_t power = 17; + + /** + * FIXME, use a meshtastic sync word, but hashed with the Channel name. Currently picking the same default + * the RF95 used (0x14). Note: do not use 0x34 - that is reserved for lorawan + */ + uint8_t syncWord = SX126X_SYNC_WORD_PRIVATE; + float currentLimit = 100; // FIXME uint16_t preambleLength = 8; @@ -24,9 +28,8 @@ class RadioLibInterface : public RadioInterface PhysicalLayer &iface; public: - RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, - SPIClass &spi, - PhysicalLayer *iface); + RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi, + PhysicalLayer *iface); virtual ErrorCode send(MeshPacket *p); @@ -48,51 +51,9 @@ class RadioLibInterface : public RadioInterface /// \return true if initialisation succeeded. virtual bool init() { return true; } - /// Sets the transmitter and receiver - /// centre frequency. - /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several - /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work - /// \return true if the selected frquency centre is within range - bool setFrequency(float centre) { return true; } - - /// Select one of the predefined modem configurations. If you need a modem configuration not provided - /// here, use setModemRegisters() with your own ModemConfig. - /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator - /// for reliable operation. - /// \param[in] index The configuration choice. - /// \return true if index is a valid choice. - bool setModemConfig(RH_RF95::ModemConfigChoice index) { return true; } - - /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, - /// disables them. - void setModeIdle() {} - - /// If current mode is Tx or Idle, changes it to Rx. - /// Starts the receiver in the RF95/96/97/98. - void setModeRx() {} - - /// Returns the operating mode of the library. - /// \return the current mode, one of RF69_MODE_* - virtual RHGenericDriver::RHMode mode() { return RHGenericDriver::RHModeIdle; } - - /// Sets the transmitter power output level, and configures the transmitter pin. - /// Be a good neighbour and set the lowest power level you need. - /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) - /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) - /// while some (such as the Modtronix inAir4 and inAir9) - /// use the RFO transmitter pin for lower power but higher efficiency. - /// You must set the appropriate power level and useRFO argument for your module. - /// Check with your module manufacturer which transmtter pin is used on your module - /// to ensure you are setting useRFO correctly. - /// Failure to do so will result in very low - /// transmitter power output. - /// Caution: legal power limits may apply in certain countries. - /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). - /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, - /// valid values are from +5 to +23. - /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), - /// valid values are from -1 to 14. - /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of - /// the PA_BOOST pin (false). Choose the correct setting for your module. - void setTxPower(int8_t power, bool useRFO = false) {} + protected: + /** + * Convert our modemConfig enum into wf, sf, etc... + */ + void applyModemConfig(); }; \ No newline at end of file diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index 90604c6c1..73ee2e5ed 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -21,8 +21,47 @@ bool SX1262Interface::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? + applyModemConfig(); int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO); DEBUG_MSG("LORA init result %d\n", res); return res == ERR_NONE; } + +bool SX1262Interface::reconfigure() +{ + applyModemConfig(); + + // set mode to standby + int err = lora.standby(); + assert(err == ERR_NONE); + + // configure publicly accessible settings + err = lora.setSpreadingFactor(sf); + assert(err == ERR_NONE); + + err = lora.setBandwidth(bw); + assert(err == ERR_NONE); + + err = lora.setCodingRate(cr); + assert(err == ERR_NONE); + + err = lora.setSyncWord(syncWord); + assert(err == ERR_NONE); + + err = lora.setCurrentLimit(currentLimit); + assert(err == ERR_NONE); + + err = lora.setPreambleLength(preambleLength); + assert(err == ERR_NONE); + + err = lora.setFrequency(freq); + assert(err == ERR_NONE); + + err = lora.setOutputPower(power); + assert(err == ERR_NONE); + + assert(0); // FIXME - set mode back to receive? + + return true; +} diff --git a/src/rf95/SX1262Interface.h b/src/rf95/SX1262Interface.h index aa577a734..c700145ef 100644 --- a/src/rf95/SX1262Interface.h +++ b/src/rf95/SX1262Interface.h @@ -13,4 +13,9 @@ class SX1262Interface : public RadioLibInterface /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. virtual bool init(); + + /// Apply any radio provisioning changes + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool reconfigure(); }; \ No newline at end of file From 2982e197e092587f436520e8f43771dc0f1f85d9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 29 Apr 2020 19:04:59 -0700 Subject: [PATCH 107/197] radio settings now work on real sx1262 hw --- platformio.ini | 1 + src/rf95/SX1262Interface.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/platformio.ini b/platformio.ini index d570b1190..4f39e7904 100644 --- a/platformio.ini +++ b/platformio.ini @@ -128,6 +128,7 @@ platform = nordicnrf52 board = ppr framework = arduino debug_tool = jlink +build_type = debug ; I'm debugging with ICE a lot now build_flags = ${env.build_flags} -Wno-unused-variable -Isrc/nrf52 src_filter = diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index 73ee2e5ed..bd951c46a 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -22,6 +22,8 @@ bool SX1262Interface::init() bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC? applyModemConfig(); + if (power > 22) // This chip has lower power limits than some + power = 22; int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO); DEBUG_MSG("LORA init result %d\n", res); @@ -58,6 +60,8 @@ bool SX1262Interface::reconfigure() err = lora.setFrequency(freq); assert(err == ERR_NONE); + if (power > 22) // This chip has lower power limits than some + power = 22; err = lora.setOutputPower(power); assert(err == ERR_NONE); From 074ac33b8a646726f84917b78fa31e95fc706704 Mon Sep 17 00:00:00 2001 From: geeksville Date: Wed, 29 Apr 2020 20:23:59 -0700 Subject: [PATCH 108/197] make a gdb "restart" command that allows restarting without rebuilding --- gdbinit | 6 ++++++ platformio.ini | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 gdbinit diff --git a/gdbinit b/gdbinit new file mode 100644 index 000000000..2af350b3a --- /dev/null +++ b/gdbinit @@ -0,0 +1,6 @@ +# the jlink debugger seems to want a pause after reset before we tell it to start running +define restart + monitor reset + shell sleep 1 + cont +end diff --git a/platformio.ini b/platformio.ini index 4f39e7904..b8ba35f53 100644 --- a/platformio.ini +++ b/platformio.ini @@ -141,7 +141,11 @@ lib_deps = https://github.com/meshtastic/BQ25703A.git monitor_port = /dev/ttyACM1 +debug_extra_cmds = + source gdbinit + ; Set initial breakpoint (defaults to main) debug_init_break = ;debug_init_break = tbreak loop -;debug_init_break = tbreak Reset_Handler \ No newline at end of file +;debug_init_break = tbreak Reset_Handler + From fce31560c640563549ec17f5f16a7d1c1915c4e5 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 09:44:16 -0700 Subject: [PATCH 109/197] the mountain of changes needed to kinda make tx work compiles. --- docs/software/nrf52-TODO.md | 3 + src/rf95/CustomRF95.cpp | 25 +-- src/rf95/CustomRF95.h | 4 - src/rf95/RadioInterface.cpp | 34 +++- src/rf95/RadioInterface.h | 27 +++ src/rf95/RadioLibInterface.cpp | 345 ++++++++++++++++++++++++++++++--- src/rf95/RadioLibInterface.h | 43 ++++ src/rf95/SX1262Interface.cpp | 19 ++ src/rf95/SX1262Interface.h | 14 ++ 9 files changed, 465 insertions(+), 49 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 6e6555083..cdf57fac9 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -47,6 +47,9 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At ## Items to be 'feature complete' +- figure out what the correct current limit should be for the sx1262, currently we just use the default 100 +- use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. +- put sx1262 in sleepmode when processor gets shutdown (or rebooted), ideally even for critical faults (to keep power draw low). repurpose deepsleep state for this. - good power management tips: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs - call PMU set_ADC_CONV(0) during sleep, to stop reading PMU adcs and decrease current draw - do final power measurements diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index 59eb38a78..612cf8a29 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -7,11 +7,7 @@ #ifdef RF95_IRQ_GPIO -/// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need -#define MAX_RHPACKETLEN 251 -static uint8_t radiobuf[MAX_RHPACKETLEN]; - -CustomRF95::CustomRF95() : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), txQueue(MAX_TX_QUEUE) {} +CustomRF95::CustomRF95() : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO) {} bool CustomRF95::canSleep() { @@ -52,13 +48,11 @@ ErrorCode CustomRF95::send(MeshPacket *p) // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, // we almost certainly guarantee no one outside will like the packet we are sending. - if (_mode == RHModeIdle || (_mode == RHModeRx && !isReceiving())) { + if (_mode == RHModeIdle || !isReceiving()) { // if the radio is idle, we can send right away DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, txGood(), rxGood(), rxBad()); - waitPacketSent(); // Make sure we dont interrupt an outgoing message - if (!waitCAD()) return false; // Check channel activity @@ -159,29 +153,20 @@ void CustomRF95::handleIdleISR() /// This routine might be called either from user space or ISR void CustomRF95::startSend(MeshPacket *txp) { - assert(!sendingPacket); - - // DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad()); - assert(txp->has_payload); - - lastTxStart = millis(); - - size_t numbytes = pb_encode_to_bytes(radiobuf, sizeof(radiobuf), SubPacket_fields, &txp->payload); - - sendingPacket = txp; + size_t numbytes = beginSending(txp); setHeaderTo(txp->to); setHeaderId(txp->id); // if the sender nodenum is zero, that means uninitialized - assert(txp->from); setHeaderFrom(txp->from); // We must do this before each send, because we might have just changed our nodenum assert(numbytes <= 251); // Make sure we don't overflow the tiny max packet size // uint32_t start = millis(); // FIXME, store this in the class - int res = RH_RF95::send(radiobuf, numbytes); + // This legacy implementation doesn't use our inserted packet header + int res = RH_RF95::send(radiobuf + sizeof(PacketHeader), numbytes - sizeof(PacketHeader)); assert(res); } diff --git a/src/rf95/CustomRF95.h b/src/rf95/CustomRF95.h index 5582ab830..e40c1f020 100644 --- a/src/rf95/CustomRF95.h +++ b/src/rf95/CustomRF95.h @@ -13,10 +13,6 @@ class CustomRF95 : public RH_RF95, public RadioInterface { friend class MeshRadio; // for debugging we let that class touch pool - PointerQueue txQueue; - - uint32_t lastTxStart = 0L; - public: /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool diff --git a/src/rf95/RadioInterface.cpp b/src/rf95/RadioInterface.cpp index c2f555c32..fa618f1cd 100644 --- a/src/rf95/RadioInterface.cpp +++ b/src/rf95/RadioInterface.cpp @@ -6,7 +6,10 @@ #include #include -RadioInterface::RadioInterface() {} +RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE) +{ + assert(sizeof(PacketHeader) == 4); // make sure the compiler did what we expected +} ErrorCode SimRadio::send(MeshPacket *p) { @@ -21,3 +24,32 @@ void RadioInterface::deliverToReceiver(MeshPacket *p) assert(rxDest->enqueue(p, 0)); // NOWAIT - fixme, if queue is full, delete older messages } +/*** + * given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of payload bytes to send + */ +size_t RadioInterface::beginSending(MeshPacket *p) +{ + assert(!sendingPacket); + + // DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad()); + assert(p->has_payload); + + lastTxStart = millis(); + + PacketHeader *h = (PacketHeader *)radiobuf; + + h->from = p->from; + h->to = p->to; + h->flags = 0; + h->id = p->id; + + // if the sender nodenum is zero, that means uninitialized + assert(h->from); + + size_t numbytes = pb_encode_to_bytes(radiobuf + sizeof(PacketHeader), sizeof(radiobuf), SubPacket_fields, &p->payload) + sizeof(PacketHeader); + + assert(numbytes <= MAX_RHPACKETLEN); + + sendingPacket = p; + return numbytes; +} \ No newline at end of file diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index 612c25a65..09fd960b8 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -8,6 +8,18 @@ #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission +#define MAX_RHPACKETLEN 256 + +/** + * This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility + * wtih the old radiohead implementation. + */ +typedef struct { + uint8_t to, from, id, flags; +} PacketHeader; + + + /** * Basic operations all radio chipsets must implement. * @@ -20,6 +32,13 @@ class RadioInterface protected: MeshPacket *sendingPacket = NULL; // The packet we are currently sending + PointerQueue txQueue; + uint32_t lastTxStart = 0L; + + /** + * A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need + * */ + uint8_t radiobuf[MAX_RHPACKETLEN]; /** * Enqueue a received packet for the registered receiver @@ -82,6 +101,14 @@ class RadioInterface /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. virtual bool reconfigure() = 0; + + protected: + /*** + * given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of bytes to send (including the PacketHeader & payload). + * + * Used as the first step of + */ + size_t beginSending(MeshPacket *p); }; class SimRadio : public RadioInterface diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index c9f59b05d..9c772fc66 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -8,8 +8,26 @@ RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq SPIClass &spi, PhysicalLayer *_iface) : module(cs, irq, rst, busy, spi, spiSettings), iface(*_iface) { + assert(!instance); // We assume only one for now + instance = this; } +void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0() +{ + instance->pending = ISR_RX; + instance->disableInterrupt(); +} + +void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0() +{ + instance->pending = ISR_TX; + instance->disableInterrupt(); +} + +/** Our ISR code currently needs this to find our active instance + */ +RadioLibInterface *RadioLibInterface::instance; + /** * Convert our modemConfig enum into wf, sf, etc... */ @@ -41,9 +59,144 @@ void RadioLibInterface::applyModemConfig() } } +/// Send a packet (possibly by enquing in a private fifo). This routine will +/// later free() the packet to pool. This routine is not allowed to stall because it is called from +/// bluetooth comms code. If the txmit queue is empty it might return an error ErrorCode RadioLibInterface::send(MeshPacket *p) { - return ERR_NONE; + // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). + // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, + // we almost certainly guarantee no one outside will like the packet we are sending. + if (canSendImmediately()) { + // if the radio is idle, we can send right away + DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, -1, + -1, -1); + + startSend(p); + return ERRNO_OK; + } else { + DEBUG_MSG("enqueuing packet for send from=0x%x, to=0x%x\n", p->from, p->to); + ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; + + if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks + packetPool.release(p); + + return res; + } +} + +void RadioLibInterface::loop() +{ + PendingISR wasPending = pending; // atomic read + if (wasPending) { + pending = ISR_NONE; // If the flag was set, it is _guaranteed_ the ISR won't be running, because it masked itself + + if (wasPending == ISR_TX) + handleTransmitInterrupt(); + else if (wasPending == ISR_RX) + handleReceiveInterrupt(); + else + assert(0); + + // First send any outgoing packets we have ready + MeshPacket *txp = txQueue.dequeuePtr(0); + if (txp) + startSend(txp); + else { + // Nothing to send, let's switch back to receive mode + // FIXME - RH_RF95::setModeRx(); + } + } +} + +void RadioLibInterface::handleTransmitInterrupt() +{ + assert(sendingPacket); // Were we sending? + + // FIXME - check result code from ISR + + // We are done sending that packet, release it + packetPool.release(sendingPacket); + sendingPacket = NULL; + // DEBUG_MSG("Done with send\n"); +} + +void RadioLibInterface::handleReceiveInterrupt() +{ + // FIXME +} + +#if 0 +// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as +// necessary +void CustomRF95::handleInterrupt() +{ + RH_RF95::handleInterrupt(); + enableInterrupt(); // Let ISR run again + + if (_mode == RHModeIdle) // We are now done sending or receiving + { + + // If we just finished receiving a packet, forward it into a queue + if (_rxBufValid) { + // We received a packet + + // Skip the 4 headers that are at the beginning of the rxBuf + size_t payloadLen = _bufLen - RH_RF95_HEADER_LEN; + uint8_t *payload = _buf + RH_RF95_HEADER_LEN; + + // FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math + int32_t freqerr = -1, snr = lastSNR(); + // DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n", + // srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr); + + MeshPacket *mp = packetPool.allocZeroed(); + + SubPacket *p = &mp->payload; + + mp->from = _rxHeaderFrom; + mp->to = _rxHeaderTo; + mp->id = _rxHeaderId; + + //_rxHeaderId = _buf[2]; + //_rxHeaderFlags = _buf[3]; + + // If we already have an entry in the DB for this nodenum, goahead and hide the snr/freqerr info there. + // Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will + // already have a record we can hide this debugging info in. + NodeInfo *info = nodeDB.getNode(mp->from); + if (info) { + info->snr = snr; + info->frequency_error = freqerr; + } + + if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { + packetPool.release(mp); + } else { + // parsing was successful, queue for our recipient + mp->has_payload = true; + + deliverToReceiver(mp); + } + + clearRxBuf(); // This message accepted and cleared + } + + handleIdleISR(); + } +} +#endif + +/** start an immediate transmit */ +void RadioLibInterface::startSend(MeshPacket *txp) +{ + size_t numbytes = beginSending(txp); + + int res = iface.startTransmit(radiobuf, numbytes); + assert(res); + + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits + enableInterrupt(isrTxLevel0); } /** @@ -53,39 +206,178 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) // include the library +// save transmission state between loops +int transmissionState = ERR_NONE; -void loop() { - Serial.print(F("[SX1262] Transmitting packet ... ")); +void setup() { + Serial.begin(9600); + + // initialize SX1262 with default settings + Serial.print(F("[SX1262] Initializing ... ")); + // carrier frequency: 434.0 MHz + // bandwidth: 125.0 kHz + // spreading factor: 9 + // coding rate: 7 + // sync word: 0x12 (private network) + // output power: 14 dBm + // current limit: 60 mA + // preamble length: 8 symbols + // TCXO voltage: 1.6 V (set to 0 to not use TCXO) + // regulator: DC-DC (set to true to use LDO) + // CRC: enabled + int state = lora.begin(); + if (state == ERR_NONE) { + Serial.println(F("success!")); + } else { + Serial.print(F("failed, code ")); + Serial.println(state); + while (true); + } + + // set the function that will be called + // when packet transmission is finished + lora.setDio1Action(setFlag); + + // start transmitting the first packet + Serial.print(F("[SX1262] Sending first packet ... ")); // you can transmit C-string or Arduino string up to // 256 characters long - // NOTE: transmit() is a blocking method! - // See example SX126x_Transmit_Interrupt for details - // on non-blocking transmission method. - int state = lora.transmit("Hello World!"); + transmissionState = lora.startTransmit("Hello World!"); // you can also transmit byte array up to 256 bytes long - byte byteArr[] = {0x01, 0x23, 0x45, 0x56, 0x78, 0xAB, 0xCD, 0xEF}; - int state = lora.transmit(byteArr, 8); + byte byteArr[] = {0x01, 0x23, 0x45, 0x67, + 0x89, 0xAB, 0xCD, 0xEF}; + state = lora.startTransmit(byteArr, 8); + +} + +// flag to indicate that a packet was sent +volatile bool transmittedFlag = false; + +// disable interrupt when it's not needed +volatile bool enableInterrupt = true; + +// this function is called when a complete packet +// is transmitted by the module +// IMPORTANT: this function MUST be 'void' type +// and MUST NOT have any arguments! +void setFlag(void) +{ + // check if the interrupt is enabled + if (!enableInterrupt) { + return; + } + + // we sent a packet, set the flag + transmittedFlag = true; +} + +void loop() +{ + // check if the previous transmission finished + if (transmittedFlag) { + // disable the interrupt service routine while + // processing the data + enableInterrupt = false; + + // reset flag + transmittedFlag = false; + + if (transmissionState == ERR_NONE) { + // packet was successfully sent + Serial.println(F("transmission finished!")); + + // NOTE: when using interrupt-driven transmit method, + // it is not possible to automatically measure + // transmission data rate using getDataRate() + + } else { + Serial.print(F("failed, code ")); + Serial.println(transmissionState); + } + + // wait a second before transmitting again + delay(1000); + + // send another one + Serial.print(F("[SX1262] Sending another packet ... ")); + + // you can transmit C-string or Arduino string up to + // 256 characters long + transmissionState = lora.startTransmit("Hello World!"); + + // you can also transmit byte array up to 256 bytes long + + byte byteArr[] = {0x01, 0x23, 0x45, 0x67, + 0x89, 0xAB, 0xCD, 0xEF}; + int state = lora.startTransmit(byteArr, 8); + + +// we're ready to send more packets, +// enable interrupt service routine +enableInterrupt = true; +} +} + +// this function is called when a complete packet +// is received by the module +// IMPORTANT: this function MUST be 'void' type +// and MUST NOT have any arguments! +void setFlag(void) +{ + // check if the interrupt is enabled + if (!enableInterrupt) { + return; + } + + // we got a packet, set the flag + receivedFlag = true; +} + +void loop() +{ + // check if the flag is set + if (receivedFlag) { + // disable the interrupt service routine while + // processing the data + enableInterrupt = false; + + // reset flag + receivedFlag = false; + + // you can read received data as an Arduino String + String str; + int state = lora.readData(str); + + // you can also read received data as byte array + + byte byteArr[8]; + int state = lora.readData(byteArr, 8); if (state == ERR_NONE) { - // the packet was successfully transmitted - Serial.println(F("success!")); + // packet was successfully received + Serial.println(F("[SX1262] Received packet!")); - // print measured data rate - Serial.print(F("[SX1262] Datarate:\t")); - Serial.print(lora.getDataRate()); - Serial.println(F(" bps")); + // print data of the packet + Serial.print(F("[SX1262] Data:\t\t")); + Serial.println(str); -} else if (state == ERR_PACKET_TOO_LONG) { - // the supplied packet was longer than 256 bytes - Serial.println(F("too long!")); + // print RSSI (Received Signal Strength Indicator) + Serial.print(F("[SX1262] RSSI:\t\t")); + Serial.print(lora.getRSSI()); + Serial.println(F(" dBm")); -} else if (state == ERR_TX_TIMEOUT) { - // timeout occured while transmitting packet - Serial.println(F("timeout!")); + // print SNR (Signal-to-Noise Ratio) + Serial.print(F("[SX1262] SNR:\t\t")); + Serial.print(lora.getSNR()); + Serial.println(F(" dB")); + +} else if (state == ERR_CRC_MISMATCH) { + // packet was received, but is malformed + Serial.println(F("CRC error!")); } else { // some other error occurred @@ -93,7 +385,12 @@ if (state == ERR_NONE) { Serial.println(state); } -// wait for a second before transmitting again -delay(1000); +// put module back to listen mode +lora.startReceive(); + +// we're ready to receive more packets, +// enable interrupt service routine +enableInterrupt = true; } - */ \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index 9ca0f0de7..77ee11eb4 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -4,8 +4,30 @@ #include +// ESP32 has special rules about ISR code +#ifdef ARDUINO_ARCH_ESP32 +#define INTERRUPT_ATTR IRAM_ATTR +#else +#define INTERRUPT_ATTR +#endif + class RadioLibInterface : public RadioInterface { + 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; + + /** Our ISR code currently needs this to find our active instance + */ + static RadioLibInterface *instance; + + /** + * Raw ISR handler that just calls our polymorphic method + */ + static void isrRxLevel0(), isrTxLevel0(); + protected: float bw = 125; uint8_t sf = 9; @@ -27,6 +49,16 @@ class RadioLibInterface : public RadioInterface */ PhysicalLayer &iface; + /** + * Glue functions called from ISR land + */ + virtual void disableInterrupt() = 0; + + /** + * Enable a particular ISR callback glue function + */ + virtual void enableInterrupt(void (*)()) = 0; + public: RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi, PhysicalLayer *iface); @@ -51,9 +83,20 @@ class RadioLibInterface : public RadioInterface /// \return true if initialisation succeeded. virtual bool init() { return true; } + virtual void loop(); // Idle processing + protected: /** * Convert our modemConfig enum into wf, sf, etc... */ void applyModemConfig(); + + /** Could we send right now (i.e. either not actively receiving or transmitting)? */ + virtual bool canSendImmediately() = 0; + + /** start an immediate transmit */ + void startSend(MeshPacket *txp); + + void handleTransmitInterrupt(); + void handleReceiveInterrupt(); }; \ No newline at end of file diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index bd951c46a..7d5bc4f22 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -27,6 +27,9 @@ bool SX1262Interface::init() int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO); DEBUG_MSG("LORA init result %d\n", res); + if (res != ERR_NONE) + res = lora.setCRC(SX126X_LORA_CRC_ON); + return res == ERR_NONE; } @@ -69,3 +72,19 @@ bool SX1262Interface::reconfigure() return true; } + +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +bool SX1262Interface::canSendImmediately() +{ + return true; // FIXME +#if 0 + // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). + // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, + // we almost certainly guarantee no one outside will like the packet we are sending. + if (_mode == RHModeIdle || isReceiving()) { + // if the radio is idle, we can send right away + DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, + txGood(), rxGood(), rxBad()); + } +#endif +} \ No newline at end of file diff --git a/src/rf95/SX1262Interface.h b/src/rf95/SX1262Interface.h index c700145ef..f480415ff 100644 --- a/src/rf95/SX1262Interface.h +++ b/src/rf95/SX1262Interface.h @@ -18,4 +18,18 @@ class SX1262Interface : public RadioLibInterface /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. virtual bool reconfigure(); + + protected: + /** + * Glue functions called from ISR land + */ + virtual void INTERRUPT_ATTR disableInterrupt() { lora.clearDio1Action(); } + + /** + * Enable a particular ISR callback glue function + */ + virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); } + + /** Could we send right now (i.e. either not actively receiving or transmitting)? */ + virtual bool canSendImmediately(); }; \ No newline at end of file From 3c3e722181df7c99ed491852f0b8c65172d2e109 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 10:00:40 -0700 Subject: [PATCH 110/197] new sending kinda works --- docs/software/nrf52-TODO.md | 1 + src/nrf52/main-nrf52.cpp | 2 +- src/rf95/RadioLibInterface.cpp | 4 +++- src/rf95/SX1262Interface.cpp | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index cdf57fac9..a0743de44 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -8,6 +8,7 @@ Minimum items needed to make sure hardware is good. - DONE get old radio driver working on NRF52 - DONE basic test of BLE - DONE get a debug 'serial' console working via the ICE passthrough feature +- add a hard fault handler - switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib - use "variants" to get all gpio bindings - plug in correct variants for the real board diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index 70e733b90..255b80101 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -17,7 +17,7 @@ static inline void debugger_break(void) void __attribute__((noreturn)) __assert_func(const char *file, int line, const char *func, const char *failedexpr) { DEBUG_MSG("assert failed %s: %d, %s, test=%s\n", file, line, func, failedexpr); - debugger_break(); + // debugger_break(); FIXME doesn't work, possibly not for segger while (1) ; // FIXME, reboot! } diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 9c772fc66..57d64a3c5 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -91,6 +91,8 @@ void RadioLibInterface::loop() if (wasPending) { pending = ISR_NONE; // If the flag was set, it is _guaranteed_ the ISR won't be running, because it masked itself + DEBUG_MSG("Handling a LORA interrupt %d!\n", wasPending); + if (wasPending == ISR_TX) handleTransmitInterrupt(); else if (wasPending == ISR_RX) @@ -193,7 +195,7 @@ void RadioLibInterface::startSend(MeshPacket *txp) size_t numbytes = beginSending(txp); int res = iface.startTransmit(radiobuf, numbytes); - assert(res); + assert(res == ERR_NONE); // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrTxLevel0); diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index 7d5bc4f22..88dfae80e 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -27,7 +27,7 @@ bool SX1262Interface::init() int res = lora.begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength, tcxoVoltage, useRegulatorLDO); DEBUG_MSG("LORA init result %d\n", res); - if (res != ERR_NONE) + if (res == ERR_NONE) res = lora.setCRC(SX126X_LORA_CRC_ON); return res == ERR_NONE; From 11b79a942d93ad0a7e3a1714e95682a674c8af16 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 10:54:53 -0700 Subject: [PATCH 111/197] add todos --- docs/software/nrf52-TODO.md | 4 +++- src/rf95/RadioLibInterface.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index a0743de44..e315f923b 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -10,6 +10,8 @@ Minimum items needed to make sure hardware is good. - DONE get a debug 'serial' console working via the ICE passthrough feature - add a hard fault handler - switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib +- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug +- use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. - use "variants" to get all gpio bindings - plug in correct variants for the real board - remove unused sx1262 lib from github @@ -30,6 +32,7 @@ Minimum items needed to make sure hardware is good. Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. +- increase preamble length? - will break other clients? so all devices must update - enable BLE DFU somehow - set appversion/hwversion - report appversion/hwversion in BLE @@ -49,7 +52,6 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At ## Items to be 'feature complete' - figure out what the correct current limit should be for the sx1262, currently we just use the default 100 -- use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. - put sx1262 in sleepmode when processor gets shutdown (or rebooted), ideally even for critical faults (to keep power draw low). repurpose deepsleep state for this. - good power management tips: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs - call PMU set_ADC_CONV(0) during sleep, to stop reading PMU adcs and decrease current draw diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index 77ee11eb4..dca7dda9d 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -40,7 +40,7 @@ class RadioLibInterface : public RadioInterface uint8_t syncWord = SX126X_SYNC_WORD_PRIVATE; float currentLimit = 100; // FIXME - uint16_t preambleLength = 8; + uint16_t preambleLength = 8; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving Module module; // The HW interface to the radio From 22720e9f6339d3f4661ff471dc61794f8992647f Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 12:37:58 -0700 Subject: [PATCH 112/197] ex1262 receiving kinda works --- proto | 2 +- src/mesh/NodeDB.cpp | 2 + src/mesh/mesh.pb.h | 31 ++-- src/rf95/RadioLibInterface.cpp | 296 +++++---------------------------- src/rf95/RadioLibInterface.h | 31 ++-- src/rf95/SX1262Interface.cpp | 23 ++- src/rf95/SX1262Interface.h | 5 + 7 files changed, 98 insertions(+), 292 deletions(-) diff --git a/proto b/proto index e570ee983..bd002e5a1 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit e570ee9836949d9f420fd19cc59a2595c8669a6e +Subproject commit bd002e5a144f209e42c97b64fea9a05a2e513b28 diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6348b7478..b27c49e12 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -289,6 +289,8 @@ void NodeDB::updateFrom(const MeshPacket &mp) info->position.time = mp.rx_time; } + info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. + if (p.has_position) { // we carefully preserve the old time, because we always trust our local timestamps more uint32_t oldtime = info->position.time; diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index fdc266cdb..bf79305b8 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -102,8 +102,7 @@ typedef struct _NodeInfo { User user; bool has_position; Position position; - int32_t snr; - int32_t frequency_error; + float snr; } NodeInfo; typedef struct _RadioConfig { @@ -129,8 +128,8 @@ typedef struct _MeshPacket { bool has_payload; SubPacket payload; uint32_t rx_time; - int32_t rx_snr; uint32_t id; + float rx_snr; } MeshPacket; typedef struct _DeviceState { @@ -198,7 +197,7 @@ typedef struct _ToRadio { #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} #define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} +#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, false, MeshPacket_init_default, 0} #define DebugString_init_default {""} @@ -213,7 +212,7 @@ typedef struct _ToRadio { #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} #define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} +#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, false, MeshPacket_init_zero, 0} #define DebugString_init_zero {""} @@ -263,8 +262,7 @@ typedef struct _ToRadio { #define NodeInfo_num_tag 1 #define NodeInfo_user_tag 2 #define NodeInfo_position_tag 3 -#define NodeInfo_snr_tag 5 -#define NodeInfo_frequency_error_tag 6 +#define NodeInfo_snr_tag 7 #define RadioConfig_preferences_tag 1 #define RadioConfig_channel_settings_tag 2 #define SubPacket_position_tag 1 @@ -275,8 +273,8 @@ typedef struct _ToRadio { #define MeshPacket_to_tag 2 #define MeshPacket_payload_tag 3 #define MeshPacket_rx_time_tag 4 -#define MeshPacket_rx_snr_tag 5 #define MeshPacket_id_tag 6 +#define MeshPacket_rx_snr_tag 7 #define DeviceState_radio_tag 1 #define DeviceState_my_node_tag 2 #define DeviceState_owner_tag 3 @@ -342,8 +340,8 @@ X(a, STATIC, SINGULAR, INT32, from, 1) \ X(a, STATIC, SINGULAR, INT32, to, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, payload, 3) \ X(a, STATIC, SINGULAR, UINT32, rx_time, 4) \ -X(a, STATIC, SINGULAR, SINT32, rx_snr, 5) \ -X(a, STATIC, SINGULAR, UINT32, id, 6) +X(a, STATIC, SINGULAR, UINT32, id, 6) \ +X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL #define MeshPacket_payload_MSGTYPE SubPacket @@ -385,8 +383,7 @@ X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) X(a, STATIC, SINGULAR, INT32, num, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ -X(a, STATIC, SINGULAR, INT32, snr, 5) \ -X(a, STATIC, SINGULAR, INT32, frequency_error, 6) +X(a, STATIC, SINGULAR, FLOAT, snr, 7) #define NodeInfo_CALLBACK NULL #define NodeInfo_DEFAULT NULL #define NodeInfo_user_MSGTYPE User @@ -494,16 +491,16 @@ extern const pb_msgdesc_t ToRadio_msg; #define User_size 72 /* RouteDiscovery_size depends on runtime parameters */ #define SubPacket_size 383 -#define MeshPacket_size 426 +#define MeshPacket_size 425 #define ChannelSettings_size 44 #define RadioConfig_size 120 #define RadioConfig_UserPreferences_size 72 -#define NodeInfo_size 155 +#define NodeInfo_size 138 #define MyNodeInfo_size 85 -#define DeviceState_size 19502 +#define DeviceState_size 18925 #define DebugString_size 258 -#define FromRadio_size 435 -#define ToRadio_size 429 +#define FromRadio_size 434 +#define ToRadio_size 428 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 57d64a3c5..156791ea3 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -1,5 +1,9 @@ #include "RadioLibInterface.h" +#include "MeshTypes.h" +#include "mesh-pb-constants.h" #include +#include +#include // FIXME, we default to 4MHz SPI, SPI mode 0, check if the datasheet says it can really do that static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); @@ -100,14 +104,19 @@ void RadioLibInterface::loop() else assert(0); - // First send any outgoing packets we have ready - MeshPacket *txp = txQueue.dequeuePtr(0); - if (txp) - startSend(txp); - else { - // Nothing to send, let's switch back to receive mode - // FIXME - RH_RF95::setModeRx(); - } + startNextWork(); + } +} + +void RadioLibInterface::startNextWork() +{ + // First send any outgoing packets we have ready + MeshPacket *txp = txQueue.dequeuePtr(0); + if (txp) + startSend(txp); + else { + // Nothing to send, let's switch back to receive mode + startReceive(); } } @@ -125,69 +134,39 @@ void RadioLibInterface::handleTransmitInterrupt() void RadioLibInterface::handleReceiveInterrupt() { - // FIXME -} + // read the number of actually received bytes + size_t length = iface.getPacketLength(); -#if 0 -// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as -// necessary -void CustomRF95::handleInterrupt() -{ - RH_RF95::handleInterrupt(); - enableInterrupt(); // Let ISR run again + int state = iface.readData(radiobuf, length); + if (state != ERR_NONE) { + DEBUG_MSG("ignoring received packet due to error=%d\n", state); + } else { + // Skip the 4 headers that are at the beginning of the rxBuf + int32_t payloadLen = length - sizeof(PacketHeader); + const uint8_t *payload = radiobuf + sizeof(PacketHeader); + const PacketHeader *h = (PacketHeader *)radiobuf; - if (_mode == RHModeIdle) // We are now done sending or receiving - { + // fixme check for short packets - // If we just finished receiving a packet, forward it into a queue - if (_rxBufValid) { - // We received a packet + MeshPacket *mp = packetPool.allocZeroed(); - // Skip the 4 headers that are at the beginning of the rxBuf - size_t payloadLen = _bufLen - RH_RF95_HEADER_LEN; - uint8_t *payload = _buf + RH_RF95_HEADER_LEN; + SubPacket *p = &mp->payload; - // FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math - int32_t freqerr = -1, snr = lastSNR(); - // DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n", - // srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr); + mp->from = h->from; + mp->to = h->to; + mp->id = h->id; - MeshPacket *mp = packetPool.allocZeroed(); + if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { + DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); + packetPool.release(mp); + } else { + // parsing was successful, queue for our recipient + mp->has_payload = true; - SubPacket *p = &mp->payload; - - mp->from = _rxHeaderFrom; - mp->to = _rxHeaderTo; - mp->id = _rxHeaderId; - - //_rxHeaderId = _buf[2]; - //_rxHeaderFlags = _buf[3]; - - // If we already have an entry in the DB for this nodenum, goahead and hide the snr/freqerr info there. - // Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will - // already have a record we can hide this debugging info in. - NodeInfo *info = nodeDB.getNode(mp->from); - if (info) { - info->snr = snr; - info->frequency_error = freqerr; - } - - if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { - packetPool.release(mp); - } else { - // parsing was successful, queue for our recipient - mp->has_payload = true; - - deliverToReceiver(mp); - } - - clearRxBuf(); // This message accepted and cleared + deliverToReceiver(mp); } - - handleIdleISR(); } } -#endif /** start an immediate transmit */ void RadioLibInterface::startSend(MeshPacket *txp) @@ -201,198 +180,3 @@ void RadioLibInterface::startSend(MeshPacket *txp) enableInterrupt(isrTxLevel0); } -/** - * - * - * -// include the library - - -// save transmission state between loops -int transmissionState = ERR_NONE; - -void setup() { - Serial.begin(9600); - - // initialize SX1262 with default settings - Serial.print(F("[SX1262] Initializing ... ")); - // carrier frequency: 434.0 MHz - // bandwidth: 125.0 kHz - // spreading factor: 9 - // coding rate: 7 - // sync word: 0x12 (private network) - // output power: 14 dBm - // current limit: 60 mA - // preamble length: 8 symbols - // TCXO voltage: 1.6 V (set to 0 to not use TCXO) - // regulator: DC-DC (set to true to use LDO) - // CRC: enabled - int state = lora.begin(); - if (state == ERR_NONE) { - Serial.println(F("success!")); - } else { - Serial.print(F("failed, code ")); - Serial.println(state); - while (true); - } - - // set the function that will be called - // when packet transmission is finished - lora.setDio1Action(setFlag); - - // start transmitting the first packet - Serial.print(F("[SX1262] Sending first packet ... ")); - - // you can transmit C-string or Arduino string up to - // 256 characters long - transmissionState = lora.startTransmit("Hello World!"); - - // you can also transmit byte array up to 256 bytes long - - byte byteArr[] = {0x01, 0x23, 0x45, 0x67, - 0x89, 0xAB, 0xCD, 0xEF}; - state = lora.startTransmit(byteArr, 8); - -} - -// flag to indicate that a packet was sent -volatile bool transmittedFlag = false; - -// disable interrupt when it's not needed -volatile bool enableInterrupt = true; - -// this function is called when a complete packet -// is transmitted by the module -// IMPORTANT: this function MUST be 'void' type -// and MUST NOT have any arguments! -void setFlag(void) -{ - // check if the interrupt is enabled - if (!enableInterrupt) { - return; - } - - // we sent a packet, set the flag - transmittedFlag = true; -} - -void loop() -{ - // check if the previous transmission finished - if (transmittedFlag) { - // disable the interrupt service routine while - // processing the data - enableInterrupt = false; - - // reset flag - transmittedFlag = false; - - if (transmissionState == ERR_NONE) { - // packet was successfully sent - Serial.println(F("transmission finished!")); - - // NOTE: when using interrupt-driven transmit method, - // it is not possible to automatically measure - // transmission data rate using getDataRate() - - } else { - Serial.print(F("failed, code ")); - Serial.println(transmissionState); - } - - // wait a second before transmitting again - delay(1000); - - // send another one - Serial.print(F("[SX1262] Sending another packet ... ")); - - // you can transmit C-string or Arduino string up to - // 256 characters long - transmissionState = lora.startTransmit("Hello World!"); - - // you can also transmit byte array up to 256 bytes long - - byte byteArr[] = {0x01, 0x23, 0x45, 0x67, - 0x89, 0xAB, 0xCD, 0xEF}; - int state = lora.startTransmit(byteArr, 8); - - -// we're ready to send more packets, -// enable interrupt service routine -enableInterrupt = true; -} -} - -// this function is called when a complete packet -// is received by the module -// IMPORTANT: this function MUST be 'void' type -// and MUST NOT have any arguments! -void setFlag(void) -{ - // check if the interrupt is enabled - if (!enableInterrupt) { - return; - } - - // we got a packet, set the flag - receivedFlag = true; -} - -void loop() -{ - // check if the flag is set - if (receivedFlag) { - // disable the interrupt service routine while - // processing the data - enableInterrupt = false; - - // reset flag - receivedFlag = false; - - // you can read received data as an Arduino String - String str; - int state = lora.readData(str); - - // you can also read received data as byte array - - byte byteArr[8]; - int state = lora.readData(byteArr, 8); - - -if (state == ERR_NONE) { - // packet was successfully received - Serial.println(F("[SX1262] Received packet!")); - - // print data of the packet - Serial.print(F("[SX1262] Data:\t\t")); - Serial.println(str); - - // print RSSI (Received Signal Strength Indicator) - Serial.print(F("[SX1262] RSSI:\t\t")); - Serial.print(lora.getRSSI()); - Serial.println(F(" dBm")); - - // print SNR (Signal-to-Noise Ratio) - Serial.print(F("[SX1262] SNR:\t\t")); - Serial.print(lora.getSNR()); - Serial.println(F(" dB")); - -} else if (state == ERR_CRC_MISMATCH) { - // packet was received, but is malformed - Serial.println(F("CRC error!")); - -} else { - // some other error occurred - Serial.print(F("failed, code ")); - Serial.println(state); -} - -// put module back to listen mode -lora.startReceive(); - -// we're ready to receive more packets, -// enable interrupt service routine -enableInterrupt = true; -} -} -*/ \ No newline at end of file diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index dca7dda9d..3d0f8e04a 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -26,7 +26,7 @@ class RadioLibInterface : public RadioInterface /** * Raw ISR handler that just calls our polymorphic method */ - static void isrRxLevel0(), isrTxLevel0(); + static void isrTxLevel0(); protected: float bw = 125; @@ -39,7 +39,7 @@ class RadioLibInterface : public RadioInterface */ uint8_t syncWord = SX126X_SYNC_WORD_PRIVATE; - float currentLimit = 100; // FIXME + float currentLimit = 100; // FIXME uint16_t preambleLength = 8; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving Module module; // The HW interface to the radio @@ -78,13 +78,18 @@ class RadioLibInterface : public RadioInterface /// \param[in] thisAddress The address of this node. virtual void setThisAddress(uint8_t thisAddress) {} - /// 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() { return true; } - virtual void loop(); // Idle processing + private: + /** start an immediate transmit */ + void startSend(MeshPacket *txp); + + /** start a queued transmit (if we have one), else start receiving */ + void startNextWork(); + + void handleTransmitInterrupt(); + void handleReceiveInterrupt(); + protected: /** * Convert our modemConfig enum into wf, sf, etc... @@ -94,9 +99,13 @@ class RadioLibInterface : public RadioInterface /** Could we send right now (i.e. either not actively receiving or transmitting)? */ virtual bool canSendImmediately() = 0; - /** start an immediate transmit */ - void startSend(MeshPacket *txp); + /** + * Start waiting to receive a message + */ + virtual void startReceive() = 0; - void handleTransmitInterrupt(); - void handleReceiveInterrupt(); + /** + * Raw ISR handler that just calls our polymorphic method + */ + static void isrRxLevel0(); }; \ No newline at end of file diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index 88dfae80e..d72c828a0 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -12,9 +12,6 @@ SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RA /// \return true if initialisation succeeded. bool SX1262Interface::init() { - if (!RadioLibInterface::init()) - return false; - // FIXME, move this to main SPI.begin(); @@ -30,7 +27,10 @@ bool SX1262Interface::init() if (res == ERR_NONE) res = lora.setCRC(SX126X_LORA_CRC_ON); - return res == ERR_NONE; + if (res == ERR_NONE) + startReceive(); // start receiving + + return res; } bool SX1262Interface::reconfigure() @@ -68,9 +68,18 @@ bool SX1262Interface::reconfigure() err = lora.setOutputPower(power); assert(err == ERR_NONE); - assert(0); // FIXME - set mode back to receive? + startReceive(); // restart receiving - return true; + return ERR_NONE; +} + +void SX1262Interface::startReceive() +{ + int err = lora.startReceive(); + assert(err == ERR_NONE); + + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits + enableInterrupt(isrRxLevel0); } /** Could we send right now (i.e. either not actively receving or transmitting)? */ @@ -86,5 +95,5 @@ bool SX1262Interface::canSendImmediately() DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, txGood(), rxGood(), rxBad()); } -#endif +#endif } \ No newline at end of file diff --git a/src/rf95/SX1262Interface.h b/src/rf95/SX1262Interface.h index f480415ff..edb7cad78 100644 --- a/src/rf95/SX1262Interface.h +++ b/src/rf95/SX1262Interface.h @@ -32,4 +32,9 @@ class SX1262Interface : public RadioLibInterface /** Could we send right now (i.e. either not actively receiving or transmitting)? */ virtual bool canSendImmediately(); + + /** + * Start waiting to receive a message + */ + virtual void startReceive(); }; \ No newline at end of file From a2ba9d3c44d2d95a60fc93605e55f896d3e9ae5d Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 13:50:40 -0700 Subject: [PATCH 113/197] new receive code works a little better --- docs/software/nrf52-TODO.md | 6 +++++- src/GPS.cpp | 4 ++-- src/rf95/RadioLibInterface.cpp | 19 +++++++++++++------ src/rf95/RadioLibInterface.h | 7 +++++++ src/rf95/SX1262Interface.cpp | 31 +++++++++++++++++++------------ src/rf95/SX1262Interface.h | 3 +++ 6 files changed, 49 insertions(+), 21 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index e315f923b..6a52c4711 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -9,7 +9,10 @@ Minimum items needed to make sure hardware is good. - DONE basic test of BLE - DONE get a debug 'serial' console working via the ICE passthrough feature - add a hard fault handler -- switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib +- DONE switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib +- change rx95 to radiolib +- track rxbad, rxgood, txgood +- neg 7 error code from receive - at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug - use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. - use "variants" to get all gpio bindings @@ -51,6 +54,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At ## Items to be 'feature complete' +- remove the MeshRadio wrapper - we don't need it anymore, just do everythin in RadioInterface subclasses. - figure out what the correct current limit should be for the sx1262, currently we just use the default 100 - put sx1262 in sleepmode when processor gets shutdown (or rebooted), ideally even for critical faults (to keep power draw low). repurpose deepsleep state for this. - good power management tips: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs diff --git a/src/GPS.cpp b/src/GPS.cpp index 6386ec81c..6a2dad509 100644 --- a/src/GPS.cpp +++ b/src/GPS.cpp @@ -28,7 +28,7 @@ GPS::GPS() : PeriodicTask() {} void GPS::setup() { PeriodicTask::setup(); - + readFromRTC(); // read the main CPU RTC at first #ifdef GPS_RX_PIN @@ -110,7 +110,7 @@ void GPS::perhapsSetRTC(const struct timeval *tv) #ifndef NO_ESP32 settimeofday(tv, NULL); #else - assert(0); + DEBUG_MSG("ERROR TIME SETTING NOT IMPLEMENTED!\n"); #endif readFromRTC(); } diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 156791ea3..f1413fa8b 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -124,16 +124,24 @@ void RadioLibInterface::handleTransmitInterrupt() { assert(sendingPacket); // Were we sending? - // FIXME - check result code from ISR + completeSending(); +} - // We are done sending that packet, release it - packetPool.release(sendingPacket); - sendingPacket = NULL; - // DEBUG_MSG("Done with send\n"); +void RadioLibInterface::completeSending() +{ + if (sendingPacket) { + // We are done sending that packet, release it + packetPool.release(sendingPacket); + sendingPacket = NULL; + // DEBUG_MSG("Done with send\n"); + } } void RadioLibInterface::handleReceiveInterrupt() { + assert(isReceiving); + isReceiving = false; + // read the number of actually received bytes size_t length = iface.getPacketLength(); @@ -179,4 +187,3 @@ void RadioLibInterface::startSend(MeshPacket *txp) // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrTxLevel0); } - diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index 3d0f8e04a..eb5123390 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -49,6 +49,9 @@ class RadioLibInterface : public RadioInterface */ PhysicalLayer &iface; + /// are _trying_ to receive a packet currently (note - we might just be waiting for one) + bool isReceiving; + /** * Glue functions called from ISR land */ @@ -108,4 +111,8 @@ class RadioLibInterface : public RadioInterface * Raw ISR handler that just calls our polymorphic method */ static void isrRxLevel0(); + + /** + * If a send was in progress finish it and return the buffer to the pool */ + void completeSending(); }; \ No newline at end of file diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index d72c828a0..9b59804a0 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -30,7 +30,7 @@ bool SX1262Interface::init() if (res == ERR_NONE) startReceive(); // start receiving - return res; + return res == ERR_NONE; } bool SX1262Interface::reconfigure() @@ -38,11 +38,10 @@ bool SX1262Interface::reconfigure() applyModemConfig(); // set mode to standby - int err = lora.standby(); - assert(err == ERR_NONE); + setStandby(); // configure publicly accessible settings - err = lora.setSpreadingFactor(sf); + int err = lora.setSpreadingFactor(sf); assert(err == ERR_NONE); err = lora.setBandwidth(bw); @@ -73,11 +72,24 @@ bool SX1262Interface::reconfigure() return ERR_NONE; } +void SX1262Interface::setStandby() +{ + int err = lora.standby(); + assert(err == ERR_NONE); + + isReceiving = false; // If we were receiving, not any more + completeSending(); // If we were sending, not anymore + disableInterrupt(); +} + void SX1262Interface::startReceive() { + setStandby(); int err = lora.startReceive(); assert(err == ERR_NONE); + isReceiving = true; + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits enableInterrupt(isrRxLevel0); } @@ -85,15 +97,10 @@ void SX1262Interface::startReceive() /** Could we send right now (i.e. either not actively receving or transmitting)? */ bool SX1262Interface::canSendImmediately() { - return true; // FIXME -#if 0 // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, // we almost certainly guarantee no one outside will like the packet we are sending. - if (_mode == RHModeIdle || isReceiving()) { - // if the radio is idle, we can send right away - DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, - txGood(), rxGood(), rxBad()); - } -#endif + bool busy = sendingPacket != NULL || (isReceiving && lora.getPacketLength() > 0); + + return !busy; } \ No newline at end of file diff --git a/src/rf95/SX1262Interface.h b/src/rf95/SX1262Interface.h index edb7cad78..bacfb69eb 100644 --- a/src/rf95/SX1262Interface.h +++ b/src/rf95/SX1262Interface.h @@ -37,4 +37,7 @@ class SX1262Interface : public RadioLibInterface * Start waiting to receive a message */ virtual void startReceive(); + + private: + void setStandby(); }; \ No newline at end of file From 62a893c76042e56bc28d8aab76ed9350334a42d7 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 15:43:41 -0700 Subject: [PATCH 114/197] SX1262 approximately works top-to-bottom, but need to add sleep modes --- src/rf95/RadioLibInterface.cpp | 14 ++++++++++---- src/rf95/RadioLibInterface.h | 7 ++++++- src/rf95/SX1262Interface.cpp | 8 ++++++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index f1413fa8b..9ae539bd1 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -73,8 +73,8 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) // we almost certainly guarantee no one outside will like the packet we are sending. if (canSendImmediately()) { // if the radio is idle, we can send right away - DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, -1, - -1, -1); + DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, + txGood, rxGood, rxBad); startSend(p); return ERRNO_OK; @@ -95,8 +95,6 @@ void RadioLibInterface::loop() if (wasPending) { pending = ISR_NONE; // If the flag was set, it is _guaranteed_ the ISR won't be running, because it masked itself - DEBUG_MSG("Handling a LORA interrupt %d!\n", wasPending); - if (wasPending == ISR_TX) handleTransmitInterrupt(); else if (wasPending == ISR_RX) @@ -122,6 +120,7 @@ void RadioLibInterface::startNextWork() void RadioLibInterface::handleTransmitInterrupt() { + DEBUG_MSG("handling lora TX interrupt\n"); assert(sendingPacket); // Were we sending? completeSending(); @@ -130,6 +129,8 @@ void RadioLibInterface::handleTransmitInterrupt() void RadioLibInterface::completeSending() { if (sendingPacket) { + txGood++; + // We are done sending that packet, release it packetPool.release(sendingPacket); sendingPacket = NULL; @@ -142,12 +143,15 @@ void RadioLibInterface::handleReceiveInterrupt() assert(isReceiving); isReceiving = false; + DEBUG_MSG("handling lora RX interrupt\n"); + // read the number of actually received bytes size_t length = iface.getPacketLength(); int state = iface.readData(radiobuf, length); if (state != ERR_NONE) { DEBUG_MSG("ignoring received packet due to error=%d\n", state); + rxBad++; } else { // Skip the 4 headers that are at the beginning of the rxBuf int32_t payloadLen = length - sizeof(PacketHeader); @@ -167,9 +171,11 @@ void RadioLibInterface::handleReceiveInterrupt() if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); packetPool.release(mp); + // rxBad++; not really a hw errpr } else { // parsing was successful, queue for our recipient mp->has_payload = true; + txGood++; deliverToReceiver(mp); } diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index eb5123390..161b3c30d 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -17,7 +17,7 @@ class RadioLibInterface : public RadioInterface /** * What sort of interrupt do we expect our helper thread to now handle */ - volatile PendingISR pending; + volatile PendingISR pending = ISR_NONE; /** Our ISR code currently needs this to find our active instance */ @@ -28,6 +28,11 @@ class RadioLibInterface : public RadioInterface */ static void isrTxLevel0(); + /** + * Debugging counts + */ + uint32_t rxBad = 0, rxGood = 0, txGood = 0; + protected: float bw = 125; uint8_t sf = 9; diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index 9b59804a0..d8c5e28da 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -100,7 +100,11 @@ bool SX1262Interface::canSendImmediately() // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, // we almost certainly guarantee no one outside will like the packet we are sending. - bool busy = sendingPacket != NULL || (isReceiving && lora.getPacketLength() > 0); + bool busyTx = sendingPacket != NULL; + bool busyRx = isReceiving && lora.getPacketLength() > 0; - return !busy; + if (busyTx || busyRx) + DEBUG_MSG("Can not set now, busyTx=%d, busyRx=%d\n", busyTx, busyRx); + + return !busyTx && !busyRx; } \ No newline at end of file From d7d81880932d903d341ca419e2678b3238934d1c Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 15:50:07 -0700 Subject: [PATCH 115/197] implement most of sleep handling for the new radio stack --- src/rf95/RadioInterface.h | 2 +- src/rf95/RadioLibInterface.cpp | 9 +++++++++ src/rf95/RadioLibInterface.h | 7 +++++++ src/rf95/SX1262Interface.cpp | 11 +++++++++++ src/rf95/SX1262Interface.h | 3 +++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index 09fd960b8..c4323f545 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -67,7 +67,7 @@ class RadioInterface * * This method must be used before putting the CPU into deep or light sleep. */ - bool canSleep() { return true; } + virtual bool canSleep() { return true; } /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. virtual bool sleep() { return true; } diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 9ae539bd1..65561adeb 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -89,6 +89,15 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) } } +bool RadioLibInterface::canSleep() +{ + bool res = txQueue.isEmpty(); + if (!res) // only print debug messages if we are vetoing sleep + DEBUG_MSG("radio wait to sleep, txEmpty=%d\n", txQueue.isEmpty()); + + return res; +} + void RadioLibInterface::loop() { PendingISR wasPending = pending; // atomic read diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index 161b3c30d..a990bf8c0 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -88,6 +88,13 @@ class RadioLibInterface : public RadioInterface 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) + * + * This method must be used before putting the CPU into deep or light sleep. + */ + virtual bool canSleep(); + private: /** start an immediate transmit */ void startSend(MeshPacket *txp); diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index d8c5e28da..6a1bf851f 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -107,4 +107,15 @@ bool SX1262Interface::canSendImmediately() DEBUG_MSG("Can not set now, busyTx=%d, busyRx=%d\n", busyTx, busyRx); return !busyTx && !busyRx; +} + + + +bool SX1262Interface::sleep() +{ + // we no longer care about interrupts from this device + // prepareDeepSleep(); + + // FIXME - put chipset into sleep mode + return false; } \ No newline at end of file diff --git a/src/rf95/SX1262Interface.h b/src/rf95/SX1262Interface.h index bacfb69eb..8f1ed514b 100644 --- a/src/rf95/SX1262Interface.h +++ b/src/rf95/SX1262Interface.h @@ -19,6 +19,9 @@ class SX1262Interface : public RadioLibInterface /// \return true if initialisation succeeded. virtual bool reconfigure(); + /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. + virtual bool sleep(); + protected: /** * Glue functions called from ISR land From dd7452ad96333033e4530f8adf01b8bbb30059cc Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 16:36:59 -0700 Subject: [PATCH 116/197] old RF95 code builds again --- docs/software/nrf52-TODO.md | 1 + src/main.cpp | 2 ++ src/mesh/MeshRadio.cpp | 4 +-- src/rf95/CustomRF95.cpp | 25 +++++++++-------- src/rf95/RadioInterface.h | 21 --------------- src/rf95/RadioLibInterface.cpp | 49 +++++++++++++++++++++------------- src/rf95/RadioLibInterface.h | 11 -------- 7 files changed, 47 insertions(+), 66 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 6a52c4711..fb1138c5e 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -54,6 +54,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At ## Items to be 'feature complete' +- turn back on in-radio destaddr checking for RF95 - remove the MeshRadio wrapper - we don't need it anymore, just do everythin in RadioInterface subclasses. - figure out what the correct current limit should be for the sx1262, currently we just use the default 100 - put sx1262 in sleepmode when processor gets shutdown (or rebooted), ideally even for critical faults (to keep power draw low). repurpose deepsleep state for this. diff --git a/src/main.cpp b/src/main.cpp index 8d95b9609..68c1e1a1d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -118,8 +118,10 @@ static uint32_t ledBlinker() Periodic ledPeriodic(ledBlinker); +#ifdef NO_ESP32 #include "SX1262Interface.h" #include "variant.h" +#endif void setup() { diff --git a/src/mesh/MeshRadio.cpp b/src/mesh/MeshRadio.cpp index 0437c3f36..5ff3d46be 100644 --- a/src/mesh/MeshRadio.cpp +++ b/src/mesh/MeshRadio.cpp @@ -54,8 +54,8 @@ bool MeshRadio::init() delay(10); #endif - radioIf.setThisAddress( - nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time. + // we now expect interfaces to operate in promiscous mode + // radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time. applySettings(); diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index 612cf8a29..f6cd43708 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -34,6 +34,7 @@ bool CustomRF95::init() { bool ok = RH_RF95::init(); + // this->setPromiscuous(true); // Make the old RH stack work like the new one, make make CPU check dest addr if (ok) reconfigure(); // Finish our device setup @@ -73,6 +74,10 @@ ErrorCode CustomRF95::send(MeshPacket *p) // necessary void CustomRF95::handleInterrupt() { + setThisAddress( + nodeDB + .getNodeNum()); // temp hack to make sure we are looking for the right address. This class is going away soon anyways + RH_RF95::handleInterrupt(); if (_mode == RHModeIdle) // We are now done sending or receiving @@ -94,7 +99,7 @@ void CustomRF95::handleInterrupt() uint8_t *payload = _buf + RH_RF95_HEADER_LEN; // FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math - int32_t freqerr = -1, snr = lastSNR(); + int32_t snr = lastSNR(); // DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n", // srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr); @@ -105,19 +110,11 @@ void CustomRF95::handleInterrupt() mp->from = _rxHeaderFrom; mp->to = _rxHeaderTo; mp->id = _rxHeaderId; + mp->rx_snr = snr; //_rxHeaderId = _buf[2]; //_rxHeaderFlags = _buf[3]; - // If we already have an entry in the DB for this nodenum, goahead and hide the snr/freqerr info there. - // Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will - // already have a record we can hide this debugging info in. - NodeInfo *info = nodeDB.getNode(mp->from); - if (info) { - info->snr = snr; - info->frequency_error = freqerr; - } - if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { packetPool.release(mp); } else { @@ -195,7 +192,7 @@ void CustomRF95::loop() bool CustomRF95::reconfigure() { - radioIf.setModeIdle(); // Need to be idle before doing init + setModeIdle(); // Need to be idle before doing init // Set up default configuration // No Sync Words in LORA mode. @@ -214,9 +211,11 @@ bool CustomRF95::reconfigure() // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then // you can set transmitter powers from 5 to 23 dBm: // FIXME - can we do this? It seems to be in the Heltec board. - radioIf.setTxPower(tx_power, false); + setTxPower(power, false); // Done with init tell radio to start receiving - radioIf.setModeRx(); + setModeRx(); + + return true; } #endif \ No newline at end of file diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index c4323f545..e24f063db 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -81,16 +81,6 @@ class RadioInterface // methods from radiohead - /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. - /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, - /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. - /// In promiscuous mode, all messages will be accepted regardless of the TO header. - /// In a conventional multinode system, all nodes will have a unique address - /// (which you could store in EEPROM). - /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, - /// allowing the possibilty of address spoofing). - /// \param[in] thisAddress The address of this node. - virtual void setThisAddress(uint8_t thisAddress) = 0; /// Initialise the Driver transport hardware and software. /// Make sure the Driver is properly configured before calling init(). @@ -118,17 +108,6 @@ class SimRadio : public RadioInterface // methods from radiohead - /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. - /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, - /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. - /// In promiscuous mode, all messages will be accepted regardless of the TO header. - /// In a conventional multinode system, all nodes will have a unique address - /// (which you could store in EEPROM). - /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, - /// allowing the possibilty of address spoofing). - /// \param[in] thisAddress The address of this node. - virtual void setThisAddress(uint8_t thisAddress) {} - /// Initialise the Driver transport hardware and software. /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 65561adeb..dab0a1244 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -1,6 +1,7 @@ #include "RadioLibInterface.h" #include "MeshTypes.h" #include "mesh-pb-constants.h" +#include // FIXME, this class shouldn't need to look into nodedb #include #include #include @@ -165,28 +166,38 @@ void RadioLibInterface::handleReceiveInterrupt() // Skip the 4 headers that are at the beginning of the rxBuf int32_t payloadLen = length - sizeof(PacketHeader); const uint8_t *payload = radiobuf + sizeof(PacketHeader); - const PacketHeader *h = (PacketHeader *)radiobuf; - // fixme check for short packets - - MeshPacket *mp = packetPool.allocZeroed(); - - SubPacket *p = &mp->payload; - - mp->from = h->from; - mp->to = h->to; - mp->id = h->id; - - if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { - DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); - packetPool.release(mp); - // rxBad++; not really a hw errpr + // check for short packets + if (payloadLen < 0) { + DEBUG_MSG("ignoring received packet too short\n"); + rxBad++; } else { - // parsing was successful, queue for our recipient - mp->has_payload = true; - txGood++; + const PacketHeader *h = (PacketHeader *)radiobuf; + uint8_t ourAddr = nodeDB.getNodeNum(); - deliverToReceiver(mp); + if (h->to != 255 && h->to != ourAddr) { + DEBUG_MSG("ignoring packet not sent to us\n"); + } else { + MeshPacket *mp = packetPool.allocZeroed(); + + SubPacket *p = &mp->payload; + + mp->from = h->from; + mp->to = h->to; + mp->id = h->id; + + if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { + DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); + packetPool.release(mp); + // rxBad++; not really a hw error + } else { + // parsing was successful, queue for our recipient + mp->has_payload = true; + txGood++; + + deliverToReceiver(mp); + } + } } } } diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index a990bf8c0..f0e9e01ba 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -75,17 +75,6 @@ class RadioLibInterface : public RadioInterface // methods from radiohead - /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. - /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, - /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. - /// In promiscuous mode, all messages will be accepted regardless of the TO header. - /// In a conventional multinode system, all nodes will have a unique address - /// (which you could store in EEPROM). - /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, - /// allowing the possibilty of address spoofing). - /// \param[in] thisAddress The address of this node. - virtual void setThisAddress(uint8_t thisAddress) {} - virtual void loop(); // Idle processing /** From b1a55b457671e79a90aa1bffccec820f43febd66 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 16:47:56 -0700 Subject: [PATCH 117/197] old RF95 API works again --- platformio.ini | 2 +- src/rf95/CustomRF95.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index b8ba35f53..e543b06dc 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,7 +9,7 @@ ; https://docs.platformio.org/page/projectconf.html [platformio] -default_envs = nrf52dk ; Note: the github actions CI test build can't yet build NRF52 targets +default_envs = tbeam ; Note: the github actions CI test build can't yet build NRF52 targets [common] ; common is not currently used diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp index f6cd43708..b67404dfb 100644 --- a/src/rf95/CustomRF95.cpp +++ b/src/rf95/CustomRF95.cpp @@ -49,7 +49,7 @@ ErrorCode CustomRF95::send(MeshPacket *p) // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, // we almost certainly guarantee no one outside will like the packet we are sending. - if (_mode == RHModeIdle || !isReceiving()) { + if (_mode == RHModeIdle || (_mode == RHModeRx && !isReceiving())) { // if the radio is idle, we can send right away DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, txGood(), rxGood(), rxBad()); @@ -60,7 +60,7 @@ ErrorCode CustomRF95::send(MeshPacket *p) startSend(p); return ERRNO_OK; } else { - DEBUG_MSG("enquing packet for send from=0x%x, to=0x%x\n", p->from, p->to); + DEBUG_MSG("enqueuing packet for send from=0x%x, to=0x%x\n", p->from, p->to); ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks From e9ca7792eb032e424b41f194e064c440c0770c17 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 17:43:29 -0700 Subject: [PATCH 118/197] new RF95 driver is written --- src/rf95/RF95Interface.cpp | 132 +++++++++++++++++++++++++++++++++ src/rf95/RF95Interface.h | 50 +++++++++++++ src/rf95/RadioLibInterface.cpp | 8 +- src/rf95/RadioLibInterface.h | 4 +- src/rf95/SX1262Interface.cpp | 10 +-- src/rf95/SX1262Interface.h | 3 + 6 files changed, 195 insertions(+), 12 deletions(-) create mode 100644 src/rf95/RF95Interface.cpp create mode 100644 src/rf95/RF95Interface.h diff --git a/src/rf95/RF95Interface.cpp b/src/rf95/RF95Interface.cpp new file mode 100644 index 000000000..615088448 --- /dev/null +++ b/src/rf95/RF95Interface.cpp @@ -0,0 +1,132 @@ +#include "RF95Interface.h" +#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in + +#include + +RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi) + : RadioLibInterface(cs, irq, rst, 0, spi) +{ + // FIXME - we assume devices never get destroyed +} + +/// Initialise the Driver transport hardware and software. +/// Make sure the Driver is properly configured before calling init(). +/// \return true if initialisation succeeded. +bool RF95Interface::init() +{ + // FIXME, move this to main + SPI.begin(); + + applyModemConfig(); + if (power > 20) // This chip has lower power limits than some + power = 20; + + int res; + /** + * We do a nasty check on freq range to figure our RFM96 vs RFM95 + */ + if (CH0 < 500.0) { + auto dev = new RFM96(&module); + lora = dev; + res = dev->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); + } else { + auto dev = new RFM95(&module); + lora = dev; + res = dev->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); + } + + DEBUG_MSG("LORA init result %d\n", res); + + if (res == ERR_NONE) + res = lora->setCRC(SX126X_LORA_CRC_ON); + + if (res == ERR_NONE) + startReceive(); // start receiving + + return res == ERR_NONE; +} + +bool RF95Interface::reconfigure() +{ + applyModemConfig(); + + // set mode to standby + setStandby(); + + // configure publicly accessible settings + int err = lora->setSpreadingFactor(sf); + assert(err == ERR_NONE); + + err = lora->setBandwidth(bw); + assert(err == ERR_NONE); + + err = lora->setCodingRate(cr); + assert(err == ERR_NONE); + + err = lora->setSyncWord(syncWord); + assert(err == ERR_NONE); + + err = lora->setCurrentLimit(currentLimit); + assert(err == ERR_NONE); + + err = lora->setPreambleLength(preambleLength); + assert(err == ERR_NONE); + + err = lora->setFrequency(freq); + assert(err == ERR_NONE); + + if (power > 20) // This chip has lower power limits than some + power = 20; + err = lora->setOutputPower(power); + assert(err == ERR_NONE); + + startReceive(); // restart receiving + + return ERR_NONE; +} + +void RF95Interface::setStandby() +{ + int err = lora->standby(); + assert(err == ERR_NONE); + + isReceiving = false; // If we were receiving, not any more + completeSending(); // If we were sending, not anymore + disableInterrupt(); +} + +void RF95Interface::startReceive() +{ + setStandby(); + int err = lora->startReceive(); + assert(err == ERR_NONE); + + isReceiving = true; + + // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits + enableInterrupt(isrRxLevel0); +} + +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +bool RF95Interface::canSendImmediately() +{ + // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). + // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, + // we almost certainly guarantee no one outside will like the packet we are sending. + bool busyTx = sendingPacket != NULL; + bool busyRx = isReceiving && lora->getPacketLength() > 0; + + if (busyTx || busyRx) + DEBUG_MSG("Can not set now, busyTx=%d, busyRx=%d\n", busyTx, busyRx); + + return !busyTx && !busyRx; +} + +bool RF95Interface::sleep() +{ + // put chipset into sleep mode + disableInterrupt(); + lora->sleep(); + + return true; +} \ No newline at end of file diff --git a/src/rf95/RF95Interface.h b/src/rf95/RF95Interface.h new file mode 100644 index 000000000..4210f9ac3 --- /dev/null +++ b/src/rf95/RF95Interface.h @@ -0,0 +1,50 @@ +#pragma once + +#include "MeshRadio.h" // kinda yucky, but we need to know which region we are in +#include "RadioLibInterface.h" + +/** + * Our new not radiohead adapter for RF95 style radios + */ +class RF95Interface : public RadioLibInterface +{ + SX1278 *lora; // Either a RFM95 or RFM96 depending on what was stuffed on this board + + public: + RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi); + + /// 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(); + + /// Apply any radio provisioning changes + /// Make sure the Driver is properly configured before calling init(). + /// \return true if initialisation succeeded. + virtual bool reconfigure(); + + /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. + virtual bool sleep(); + + protected: + /** + * Glue functions called from ISR land + */ + virtual void INTERRUPT_ATTR disableInterrupt() { lora->clearDio0Action(); } + + /** + * Enable a particular ISR callback glue function + */ + virtual void enableInterrupt(void (*callback)()) { lora->setDio0Action(callback); } + + /** Could we send right now (i.e. either not actively receiving or transmitting)? */ + virtual bool canSendImmediately(); + + /** + * Start waiting to receive a message + */ + virtual void startReceive(); + + private: + void setStandby(); +}; \ No newline at end of file diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index dab0a1244..655788a8b 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -11,7 +11,7 @@ static SPISettings spiSettings(4000000, MSBFIRST, SPI_MODE0); RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi, PhysicalLayer *_iface) - : module(cs, irq, rst, busy, spi, spiSettings), iface(*_iface) + : module(cs, irq, rst, busy, spi, spiSettings), iface(_iface) { assert(!instance); // We assume only one for now instance = this; @@ -156,9 +156,9 @@ void RadioLibInterface::handleReceiveInterrupt() DEBUG_MSG("handling lora RX interrupt\n"); // read the number of actually received bytes - size_t length = iface.getPacketLength(); + size_t length = iface->getPacketLength(); - int state = iface.readData(radiobuf, length); + int state = iface->readData(radiobuf, length); if (state != ERR_NONE) { DEBUG_MSG("ignoring received packet due to error=%d\n", state); rxBad++; @@ -207,7 +207,7 @@ void RadioLibInterface::startSend(MeshPacket *txp) { size_t numbytes = beginSending(txp); - int res = iface.startTransmit(radiobuf, numbytes); + int res = iface->startTransmit(radiobuf, numbytes); assert(res == ERR_NONE); // Must be done AFTER, starting transmit, because startTransmit clears (possibly stale) interrupt pending register bits diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index f0e9e01ba..653869e8c 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -52,7 +52,7 @@ class RadioLibInterface : public RadioInterface /** * provides lowest common denominator RadioLib API */ - PhysicalLayer &iface; + PhysicalLayer *iface; /// are _trying_ to receive a packet currently (note - we might just be waiting for one) bool isReceiving; @@ -69,7 +69,7 @@ class RadioLibInterface : public RadioInterface public: RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi, - PhysicalLayer *iface); + PhysicalLayer *iface = NULL); virtual ErrorCode send(MeshPacket *p); diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index 6a1bf851f..aca0ac55a 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -109,13 +109,11 @@ bool SX1262Interface::canSendImmediately() return !busyTx && !busyRx; } - - bool SX1262Interface::sleep() { - // we no longer care about interrupts from this device - // prepareDeepSleep(); + // put chipset into sleep mode + disableInterrupt(); + lora.sleep(); - // FIXME - put chipset into sleep mode - return false; + return true; } \ No newline at end of file diff --git a/src/rf95/SX1262Interface.h b/src/rf95/SX1262Interface.h index 8f1ed514b..88a251879 100644 --- a/src/rf95/SX1262Interface.h +++ b/src/rf95/SX1262Interface.h @@ -2,6 +2,9 @@ #include "RadioLibInterface.h" +/** + * Our adapter for SX1262 radios + */ class SX1262Interface : public RadioLibInterface { SX1262 lora; From 48c045a253e23f5a384090962b09b5b8da6bd6f2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 17:56:30 -0700 Subject: [PATCH 119/197] move SPI init into main --- src/main.cpp | 16 ++++++++++++++-- src/rf95/RF95Interface.cpp | 8 +++++--- src/rf95/RF95Interface.h | 2 +- src/rf95/SX1262Interface.cpp | 3 --- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 68c1e1a1d..7e1b39ab7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -118,8 +118,10 @@ static uint32_t ledBlinker() Periodic ledPeriodic(ledBlinker); -#ifdef NO_ESP32 +#include "RF95Interface.h" #include "SX1262Interface.h" + +#ifdef NO_ESP32 #include "variant.h" #endif @@ -199,10 +201,20 @@ void setup() digitalWrite(SX1262_ANT_SW, 1); #endif + // Init our SPI controller +#ifdef NRF52_SERIES + SPI.begin(); +#else + // ESP32 + SPI.begin(SCK_GPIO, MISO_GPIO, MOSI_GPIO, NSS_GPIO); + SPI.setFrequency(4000000); +#endif + // MUST BE AFTER service.init, so we have our radio config settings (from nodedb init) RadioInterface *rIf = #if defined(RF95_IRQ_GPIO) - new CustomRF95(); + // new CustomRF95(); old Radiohead based driver + new RF95Interface(NSS_GPIO, RF95_IRQ_GPIO, RESET_GPIO, SPI); #elif defined(SX1262_CS) new SX1262Interface(SX1262_CS, SX1262_DIO1, SX1262_RESET, SX1262_BUSY, SPI); #else diff --git a/src/rf95/RF95Interface.cpp b/src/rf95/RF95Interface.cpp index 615088448..58176e060 100644 --- a/src/rf95/RF95Interface.cpp +++ b/src/rf95/RF95Interface.cpp @@ -14,9 +14,6 @@ RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOL /// \return true if initialisation succeeded. bool RF95Interface::init() { - // FIXME, move this to main - SPI.begin(); - applyModemConfig(); if (power > 20) // This chip has lower power limits than some power = 20; @@ -46,6 +43,11 @@ bool RF95Interface::init() return res == ERR_NONE; } +void INTERRUPT_ATTR RF95Interface::disableInterrupt() +{ + lora->clearDio0Action(); +} + bool RF95Interface::reconfigure() { applyModemConfig(); diff --git a/src/rf95/RF95Interface.h b/src/rf95/RF95Interface.h index 4210f9ac3..01aae56af 100644 --- a/src/rf95/RF95Interface.h +++ b/src/rf95/RF95Interface.h @@ -30,7 +30,7 @@ class RF95Interface : public RadioLibInterface /** * Glue functions called from ISR land */ - virtual void INTERRUPT_ATTR disableInterrupt() { lora->clearDio0Action(); } + virtual void disableInterrupt(); /** * Enable a particular ISR callback glue function diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index aca0ac55a..c4fa4d7bb 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -12,9 +12,6 @@ SX1262Interface::SX1262Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RA /// \return true if initialisation succeeded. bool SX1262Interface::init() { - // FIXME, move this to main - SPI.begin(); - 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? From 22bca31ce3e36aab8314212f08c8346647b81209 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 18:05:06 -0700 Subject: [PATCH 120/197] properly set the RF95 iface --- src/rf95/RF95Interface.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/rf95/RF95Interface.cpp b/src/rf95/RF95Interface.cpp index 58176e060..03270da15 100644 --- a/src/rf95/RF95Interface.cpp +++ b/src/rf95/RF95Interface.cpp @@ -24,14 +24,13 @@ bool RF95Interface::init() */ if (CH0 < 500.0) { auto dev = new RFM96(&module); - lora = dev; + iface = lora = dev; res = dev->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); } else { auto dev = new RFM95(&module); - lora = dev; + iface = lora = dev; res = dev->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); } - DEBUG_MSG("LORA init result %d\n", res); if (res == ERR_NONE) From 1fab9c5aac93b7caf71770b76cff0ca333bed725 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 19:31:17 -0700 Subject: [PATCH 121/197] temp hack to get new rf95 driver working --- src/rf95/RF95Interface.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rf95/RF95Interface.cpp b/src/rf95/RF95Interface.cpp index 03270da15..fc0175af3 100644 --- a/src/rf95/RF95Interface.cpp +++ b/src/rf95/RF95Interface.cpp @@ -21,8 +21,9 @@ bool RF95Interface::init() int res; /** * We do a nasty check on freq range to figure our RFM96 vs RFM95 + * */ - if (CH0 < 500.0) { + if (CH0 < 530.0) { auto dev = new RFM96(&module); iface = lora = dev; res = dev->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); @@ -115,7 +116,7 @@ bool RF95Interface::canSendImmediately() // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, // we almost certainly guarantee no one outside will like the packet we are sending. bool busyTx = sendingPacket != NULL; - bool busyRx = isReceiving && lora->getPacketLength() > 0; + bool busyRx = false; // FIXME - use old impl. isReceiving && lora->getPacketLength() > 0; if (busyTx || busyRx) DEBUG_MSG("Can not set now, busyTx=%d, busyRx=%d\n", busyTx, busyRx); From 968a2d7fbc906319111a4d5102e9f8b1f23edddb Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 19:58:10 -0700 Subject: [PATCH 122/197] store SNR in received packets --- src/rf95/RF95Interface.cpp | 8 ++++++++ src/rf95/RF95Interface.h | 4 ++++ src/rf95/RadioLibInterface.cpp | 3 ++- src/rf95/RadioLibInterface.h | 5 +++++ src/rf95/SX1262Interface.cpp | 7 +++++++ src/rf95/SX1262Interface.h | 5 ++++- 6 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/rf95/RF95Interface.cpp b/src/rf95/RF95Interface.cpp index fc0175af3..8076046ef 100644 --- a/src/rf95/RF95Interface.cpp +++ b/src/rf95/RF95Interface.cpp @@ -87,6 +87,14 @@ bool RF95Interface::reconfigure() return ERR_NONE; } +/** + * Add SNR data to received messages + */ +void RF95Interface::addReceiveMetadata(MeshPacket *mp) +{ + mp->rx_snr = lora->getSNR(); +} + void RF95Interface::setStandby() { int err = lora->standby(); diff --git a/src/rf95/RF95Interface.h b/src/rf95/RF95Interface.h index 01aae56af..187999f1c 100644 --- a/src/rf95/RF95Interface.h +++ b/src/rf95/RF95Interface.h @@ -45,6 +45,10 @@ class RF95Interface : public RadioLibInterface */ virtual void startReceive(); + /** + * Add SNR data to received messages + */ + virtual void addReceiveMetadata(MeshPacket *mp); private: void setStandby(); }; \ No newline at end of file diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 655788a8b..6cfb54fee 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -185,6 +185,7 @@ void RadioLibInterface::handleReceiveInterrupt() mp->from = h->from; mp->to = h->to; mp->id = h->id; + addReceiveMetadata(mp); if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); @@ -193,7 +194,7 @@ void RadioLibInterface::handleReceiveInterrupt() } else { // parsing was successful, queue for our recipient mp->has_payload = true; - txGood++; + rxGood++; deliverToReceiver(mp); } diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index 653869e8c..a71a0a00e 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -116,4 +116,9 @@ class RadioLibInterface : public RadioInterface /** * If a send was in progress finish it and return the buffer to the pool */ void completeSending(); + + /** + * Add SNR data to received messages + */ + virtual void addReceiveMetadata(MeshPacket *mp) = 0; }; \ No newline at end of file diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index c4fa4d7bb..af3e4603b 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -79,6 +79,13 @@ void SX1262Interface::setStandby() disableInterrupt(); } +/** + * Add SNR data to received messages + */ +void SX1262Interface::addReceiveMetadata(MeshPacket *mp) { + mp->rx_snr = lora.getSNR(); +} + void SX1262Interface::startReceive() { setStandby(); diff --git a/src/rf95/SX1262Interface.h b/src/rf95/SX1262Interface.h index 88a251879..39c6e7c09 100644 --- a/src/rf95/SX1262Interface.h +++ b/src/rf95/SX1262Interface.h @@ -43,7 +43,10 @@ class SX1262Interface : public RadioLibInterface * Start waiting to receive a message */ virtual void startReceive(); - + /** + * Add SNR data to received messages + */ + virtual void addReceiveMetadata(MeshPacket *mp); private: void setStandby(); }; \ No newline at end of file From a8f64c3cc826bd775350457a81c985ff2e2ca78e Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 21:11:03 -0700 Subject: [PATCH 123/197] make a custom version fo rf95 class, so we can can deal with chips that have bad version codes. --- src/rf95/RF95Interface.cpp | 19 +++-------- src/rf95/RadioLibRF95.cpp | 48 +++++++++++++++++++++++++++ src/rf95/RadioLibRF95.h | 67 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 15 deletions(-) create mode 100644 src/rf95/RadioLibRF95.cpp create mode 100644 src/rf95/RadioLibRF95.h diff --git a/src/rf95/RF95Interface.cpp b/src/rf95/RF95Interface.cpp index 8076046ef..0fe04477e 100644 --- a/src/rf95/RF95Interface.cpp +++ b/src/rf95/RF95Interface.cpp @@ -1,6 +1,6 @@ #include "RF95Interface.h" #include "MeshRadio.h" // kinda yucky, but we need to know which region we are in - +#include "RadioLibRF95.h" #include RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi) @@ -18,20 +18,9 @@ bool RF95Interface::init() if (power > 20) // This chip has lower power limits than some power = 20; - int res; - /** - * We do a nasty check on freq range to figure our RFM96 vs RFM95 - * - */ - if (CH0 < 530.0) { - auto dev = new RFM96(&module); - iface = lora = dev; - res = dev->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); - } else { - auto dev = new RFM95(&module); - iface = lora = dev; - res = dev->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); - } + auto dev = new RadioLibRF95(&module); + iface = lora = dev; + int res = dev->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); DEBUG_MSG("LORA init result %d\n", res); if (res == ERR_NONE) diff --git a/src/rf95/RadioLibRF95.cpp b/src/rf95/RadioLibRF95.cpp new file mode 100644 index 000000000..6ea982f01 --- /dev/null +++ b/src/rf95/RadioLibRF95.cpp @@ -0,0 +1,48 @@ +#include "RadioLibRF95.h" + +#define RFM95_CHIP_VERSION 0x12 +#define RFM95_ALT_VERSION 0x11 // Supposedly some versions of the chip have id 0x11 + +RadioLibRF95::RadioLibRF95(Module *mod) : SX1278(mod) {} + +int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_t syncWord, int8_t power, uint8_t currentLimit, + uint16_t preambleLength, uint8_t gain) +{ + // execute common part + int16_t state = SX127x::begin(RFM95_CHIP_VERSION, syncWord, currentLimit, preambleLength); + if (state != ERR_NONE) + state = SX127x::begin(RFM95_ALT_VERSION, syncWord, currentLimit, preambleLength); + RADIOLIB_ASSERT(state); + + // configure settings not accessible by API + state = config(); + RADIOLIB_ASSERT(state); + + // configure publicly accessible settings + state = setFrequency(freq); + RADIOLIB_ASSERT(state); + + state = setBandwidth(bw); + RADIOLIB_ASSERT(state); + + state = setSpreadingFactor(sf); + RADIOLIB_ASSERT(state); + + state = setCodingRate(cr); + RADIOLIB_ASSERT(state); + + state = setOutputPower(power); + RADIOLIB_ASSERT(state); + + state = setGain(gain); + + return (state); +} + +int16_t RadioLibRF95::setFrequency(float freq) +{ + // RADIOLIB_CHECK_RANGE(freq, 862.0, 1020.0, ERR_INVALID_FREQUENCY); + + // set frequency + return (SX127x::setFrequencyRaw(freq)); +} diff --git a/src/rf95/RadioLibRF95.h b/src/rf95/RadioLibRF95.h new file mode 100644 index 000000000..a36683370 --- /dev/null +++ b/src/rf95/RadioLibRF95.h @@ -0,0 +1,67 @@ +#pragma once +#include + +/*! + \class RFM95 + + \brief Derived class for %RFM95 modules. Overrides some methods from SX1278 due to different parameter ranges. +*/ +class RadioLibRF95: public SX1278 { + public: + + // constructor + + /*! + \brief Default constructor. Called from Arduino sketch when creating new LoRa instance. + + \param mod Instance of Module that will be used to communicate with the %LoRa chip. + */ + RadioLibRF95(Module* mod); + + // basic methods + + /*! + \brief %LoRa modem initialization method. Must be called at least once from Arduino sketch to initialize the module. + + \param freq Carrier frequency in MHz. Allowed values range from 868.0 MHz to 915.0 MHz. + + \param bw %LoRa link bandwidth in kHz. Allowed values are 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250 and 500 kHz. + + \param sf %LoRa link spreading factor. Allowed values range from 6 to 12. + + \param cr %LoRa link coding rate denominator. Allowed values range from 5 to 8. + + \param syncWord %LoRa sync word. Can be used to distinguish different networks. Note that value 0x34 is reserved for LoRaWAN networks. + + \param power Transmission output power in dBm. Allowed values range from 2 to 17 dBm. + + \param currentLimit Trim value for OCP (over current protection) in mA. Can be set to multiplies of 5 in range 45 to 120 mA and to multiples of 10 in range 120 to 240 mA. + Set to 0 to disable OCP (not recommended). + + \param preambleLength Length of %LoRa transmission preamble in symbols. The actual preamble length is 4.25 symbols longer than the set number. + Allowed values range from 6 to 65535. + + \param gain Gain of receiver LNA (low-noise amplifier). Can be set to any integer in range 1 to 6 where 1 is the highest gain. + Set to 0 to enable automatic gain control (recommended). + + \returns \ref status_codes + */ + int16_t begin(float freq = 915.0, float bw = 125.0, uint8_t sf = 9, uint8_t cr = 7, uint8_t syncWord = SX127X_SYNC_WORD, int8_t power = 17, uint8_t currentLimit = 100, uint16_t preambleLength = 8, uint8_t gain = 0); + + // configuration methods + + /*! + \brief Sets carrier frequency. Allowed values range from 868.0 MHz to 915.0 MHz. + + \param freq Carrier frequency to be set in MHz. + + \returns \ref status_codes + */ + int16_t setFrequency(float freq); + +#ifndef RADIOLIB_GODMODE + private: +#endif + +}; + From 1f1d683f4f04ddab53d1a07a81384c5c7693c471 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 21:29:51 -0700 Subject: [PATCH 124/197] add back the old code that checked if the radio was actvively receiving --- src/rf95/RF95Interface.cpp | 7 +++---- src/rf95/RF95Interface.h | 3 ++- src/rf95/RadioLibRF95.cpp | 23 +++++++++++++++++++---- src/rf95/RadioLibRF95.h | 3 +++ 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/rf95/RF95Interface.cpp b/src/rf95/RF95Interface.cpp index 0fe04477e..988a95696 100644 --- a/src/rf95/RF95Interface.cpp +++ b/src/rf95/RF95Interface.cpp @@ -18,9 +18,8 @@ bool RF95Interface::init() if (power > 20) // This chip has lower power limits than some power = 20; - auto dev = new RadioLibRF95(&module); - iface = lora = dev; - int res = dev->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); + iface = lora = new RadioLibRF95(&module); + int res = lora->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); DEBUG_MSG("LORA init result %d\n", res); if (res == ERR_NONE) @@ -113,7 +112,7 @@ bool RF95Interface::canSendImmediately() // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, // we almost certainly guarantee no one outside will like the packet we are sending. bool busyTx = sendingPacket != NULL; - bool busyRx = false; // FIXME - use old impl. isReceiving && lora->getPacketLength() > 0; + bool busyRx = isReceiving && lora->isReceiving(); if (busyTx || busyRx) DEBUG_MSG("Can not set now, busyTx=%d, busyRx=%d\n", busyTx, busyRx); diff --git a/src/rf95/RF95Interface.h b/src/rf95/RF95Interface.h index 187999f1c..911e81a01 100644 --- a/src/rf95/RF95Interface.h +++ b/src/rf95/RF95Interface.h @@ -2,13 +2,14 @@ #include "MeshRadio.h" // kinda yucky, but we need to know which region we are in #include "RadioLibInterface.h" +#include "RadioLibRF95.h" /** * Our new not radiohead adapter for RF95 style radios */ class RF95Interface : public RadioLibInterface { - SX1278 *lora; // Either a RFM95 or RFM96 depending on what was stuffed on this board + RadioLibRF95 *lora; // Either a RFM95 or RFM96 depending on what was stuffed on this board public: RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi); diff --git a/src/rf95/RadioLibRF95.cpp b/src/rf95/RadioLibRF95.cpp index 6ea982f01..cd8a3b824 100644 --- a/src/rf95/RadioLibRF95.cpp +++ b/src/rf95/RadioLibRF95.cpp @@ -1,7 +1,7 @@ #include "RadioLibRF95.h" -#define RFM95_CHIP_VERSION 0x12 -#define RFM95_ALT_VERSION 0x11 // Supposedly some versions of the chip have id 0x11 +#define RF95_CHIP_VERSION 0x12 +#define RF95_ALT_VERSION 0x11 // Supposedly some versions of the chip have id 0x11 RadioLibRF95::RadioLibRF95(Module *mod) : SX1278(mod) {} @@ -9,9 +9,9 @@ int16_t RadioLibRF95::begin(float freq, float bw, uint8_t sf, uint8_t cr, uint8_ uint16_t preambleLength, uint8_t gain) { // execute common part - int16_t state = SX127x::begin(RFM95_CHIP_VERSION, syncWord, currentLimit, preambleLength); + int16_t state = SX127x::begin(RF95_CHIP_VERSION, syncWord, currentLimit, preambleLength); if (state != ERR_NONE) - state = SX127x::begin(RFM95_ALT_VERSION, syncWord, currentLimit, preambleLength); + state = SX127x::begin(RF95_ALT_VERSION, syncWord, currentLimit, preambleLength); RADIOLIB_ASSERT(state); // configure settings not accessible by API @@ -46,3 +46,18 @@ int16_t RadioLibRF95::setFrequency(float freq) // set frequency return (SX127x::setFrequencyRaw(freq)); } + +#define RH_RF95_MODEM_STATUS_CLEAR 0x10 +#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08 +#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04 +#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02 +#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01 + +bool RadioLibRF95::isReceiving() +{ + // 0x0b == Look for header info valid, signal synchronized or signal detected + uint8_t reg = _mod->SPIreadRegister(SX127X_REG_MODEM_STAT) & 0x1f; + // Serial.printf("reg %x\n", reg); + return (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED | + RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0; +} diff --git a/src/rf95/RadioLibRF95.h b/src/rf95/RadioLibRF95.h index a36683370..1746200dd 100644 --- a/src/rf95/RadioLibRF95.h +++ b/src/rf95/RadioLibRF95.h @@ -59,6 +59,9 @@ class RadioLibRF95: public SX1278 { */ int16_t setFrequency(float freq); + // Return true if we are actively receiving a message currently + bool isReceiving(); + #ifndef RADIOLIB_GODMODE private: #endif From 4e106f4098be090cbb11a52ed1c4fef51640a2b9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 21:42:11 -0700 Subject: [PATCH 125/197] remove radiohead --- src/mesh/MeshRadio.cpp | 6 +- src/mesh/MeshRadio.h | 2 +- src/rf95/CustomRF95.cpp | 221 ----- src/rf95/CustomRF95.h | 54 -- src/rf95/RHGenericDriver.cpp | 207 ----- src/rf95/RHGenericDriver.h | 280 ------ src/rf95/RHGenericSPI.cpp | 31 - src/rf95/RHGenericSPI.h | 183 ---- src/rf95/RHHardwareSPI.cpp | 499 ---------- src/rf95/RHHardwareSPI.h | 116 --- src/rf95/RHNRFSPIDriver.cpp | 137 --- src/rf95/RHNRFSPIDriver.h | 101 -- src/rf95/RHSPIDriver.cpp | 95 -- src/rf95/RHSPIDriver.h | 100 -- src/rf95/RHSoftwareSPI.cpp | 166 ---- src/rf95/RHSoftwareSPI.h | 90 -- src/rf95/RH_RF95.cpp | 651 ------------- src/rf95/RH_RF95.h | 890 ------------------ src/rf95/RHutil/atomic.h | 71 -- src/rf95/RadioHead.h | 1595 -------------------------------- src/rf95/RadioInterface.cpp | 6 +- src/rf95/RadioInterface.h | 32 +- src/rf95/RadioLibInterface.cpp | 8 +- src/rf95/Router.h | 1 - 24 files changed, 24 insertions(+), 5518 deletions(-) delete mode 100644 src/rf95/CustomRF95.cpp delete mode 100644 src/rf95/CustomRF95.h delete mode 100644 src/rf95/RHGenericDriver.cpp delete mode 100644 src/rf95/RHGenericDriver.h delete mode 100644 src/rf95/RHGenericSPI.cpp delete mode 100644 src/rf95/RHGenericSPI.h delete mode 100644 src/rf95/RHHardwareSPI.cpp delete mode 100644 src/rf95/RHHardwareSPI.h delete mode 100644 src/rf95/RHNRFSPIDriver.cpp delete mode 100644 src/rf95/RHNRFSPIDriver.h delete mode 100644 src/rf95/RHSPIDriver.cpp delete mode 100644 src/rf95/RHSPIDriver.h delete mode 100644 src/rf95/RHSoftwareSPI.cpp delete mode 100644 src/rf95/RHSoftwareSPI.h delete mode 100644 src/rf95/RH_RF95.cpp delete mode 100644 src/rf95/RH_RF95.h delete mode 100644 src/rf95/RHutil/atomic.h delete mode 100644 src/rf95/RadioHead.h diff --git a/src/mesh/MeshRadio.cpp b/src/mesh/MeshRadio.cpp index 5ff3d46be..53c052be5 100644 --- a/src/mesh/MeshRadio.cpp +++ b/src/mesh/MeshRadio.cpp @@ -55,13 +55,13 @@ bool MeshRadio::init() #endif // we now expect interfaces to operate in promiscous mode - // radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time. + // radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor + // time. applySettings(); if (!radioIf.init()) { DEBUG_MSG("LoRa radio init failed\n"); - DEBUG_MSG("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info\n"); return false; } @@ -93,7 +93,7 @@ void MeshRadio::applySettings() { // Set up default configuration // No Sync Words in LORA mode. - radioIf.modemConfig = (RH_RF95::ModemConfigChoice)channelSettings.modem_config; + radioIf.modemConfig = (ModemConfigChoice)channelSettings.modem_config; // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM int channel_num = hash(channelSettings.name) % NUM_CHANNELS; diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index 1580cea47..85c3e77fd 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -1,10 +1,10 @@ #pragma once -#include "CustomRF95.h" #include "MemoryPool.h" #include "MeshTypes.h" #include "Observer.h" #include "PointerQueue.h" +#include "RadioInterface.h" #include "configuration.h" #include "mesh.pb.h" diff --git a/src/rf95/CustomRF95.cpp b/src/rf95/CustomRF95.cpp deleted file mode 100644 index b67404dfb..000000000 --- a/src/rf95/CustomRF95.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include "CustomRF95.h" -#include "NodeDB.h" // FIXME, this class should not need to touch nodedb -#include "assert.h" -#include "configuration.h" -#include -#include - -#ifdef RF95_IRQ_GPIO - -CustomRF95::CustomRF95() : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO) {} - -bool CustomRF95::canSleep() -{ - // We allow initializing mode, because sometimes while testing we don't ever call init() to turn on the hardware - bool isRx = isReceiving(); - - bool res = (_mode == RHModeInitialising || _mode == RHModeIdle || _mode == RHModeRx) && !isRx && txQueue.isEmpty(); - if (!res) // only print debug messages if we are vetoing sleep - DEBUG_MSG("radio wait to sleep, mode=%d, isRx=%d, txEmpty=%d, txGood=%d\n", _mode, isRx, txQueue.isEmpty(), _txGood); - - return res; -} - -bool CustomRF95::sleep() -{ - // we no longer care about interrupts from this device - prepareDeepSleep(); - - // FIXME - leave the device state in rx mode instead - return RH_RF95::sleep(); -} - -bool CustomRF95::init() -{ - bool ok = RH_RF95::init(); - - // this->setPromiscuous(true); // Make the old RH stack work like the new one, make make CPU check dest addr - if (ok) - reconfigure(); // Finish our device setup - - return ok; -} - -/// Send a packet (possibly by enquing in a private fifo). This routine will -/// later free() the packet to pool. This routine is not allowed to stall because it is called from -/// bluetooth comms code. If the txmit queue is empty it might return an error -ErrorCode CustomRF95::send(MeshPacket *p) -{ - // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). - // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, - // we almost certainly guarantee no one outside will like the packet we are sending. - if (_mode == RHModeIdle || (_mode == RHModeRx && !isReceiving())) { - // if the radio is idle, we can send right away - DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, - txGood(), rxGood(), rxBad()); - - if (!waitCAD()) - return false; // Check channel activity - - startSend(p); - return ERRNO_OK; - } else { - DEBUG_MSG("enqueuing packet for send from=0x%x, to=0x%x\n", p->from, p->to); - ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; - - if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks - packetPool.release(p); - - return res; - } -} - -// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as -// necessary -void CustomRF95::handleInterrupt() -{ - setThisAddress( - nodeDB - .getNodeNum()); // temp hack to make sure we are looking for the right address. This class is going away soon anyways - - RH_RF95::handleInterrupt(); - - if (_mode == RHModeIdle) // We are now done sending or receiving - { - if (sendingPacket) // Were we sending? - { - // We are done sending that packet, release it - packetPool.release(sendingPacket); - sendingPacket = NULL; - // DEBUG_MSG("Done with send\n"); - } - - // If we just finished receiving a packet, forward it into a queue - if (_rxBufValid) { - // We received a packet - - // Skip the 4 headers that are at the beginning of the rxBuf - size_t payloadLen = _bufLen - RH_RF95_HEADER_LEN; - uint8_t *payload = _buf + RH_RF95_HEADER_LEN; - - // FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math - int32_t snr = lastSNR(); - // DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n", - // srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr); - - MeshPacket *mp = packetPool.allocZeroed(); - - SubPacket *p = &mp->payload; - - mp->from = _rxHeaderFrom; - mp->to = _rxHeaderTo; - mp->id = _rxHeaderId; - mp->rx_snr = snr; - - //_rxHeaderId = _buf[2]; - //_rxHeaderFlags = _buf[3]; - - if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { - packetPool.release(mp); - } else { - // parsing was successful, queue for our recipient - mp->has_payload = true; - - deliverToReceiver(mp); - } - - clearRxBuf(); // This message accepted and cleared - } - - handleIdleISR(); - } -} - -/** The ISR doesn't have any good work to do, give a new assignment. - * - * Return true if a higher pri task has woken - */ -void CustomRF95::handleIdleISR() -{ - // First send any outgoing packets we have ready - MeshPacket *txp = txQueue.dequeuePtr(0); - if (txp) - startSend(txp); - else { - // Nothing to send, let's switch back to receive mode - RH_RF95::setModeRx(); - } -} - -/// This routine might be called either from user space or ISR -void CustomRF95::startSend(MeshPacket *txp) -{ - size_t numbytes = beginSending(txp); - - setHeaderTo(txp->to); - setHeaderId(txp->id); - - // if the sender nodenum is zero, that means uninitialized - setHeaderFrom(txp->from); // We must do this before each send, because we might have just changed our nodenum - - assert(numbytes <= 251); // Make sure we don't overflow the tiny max packet size - - // uint32_t start = millis(); // FIXME, store this in the class - - // This legacy implementation doesn't use our inserted packet header - int res = RH_RF95::send(radiobuf + sizeof(PacketHeader), numbytes - sizeof(PacketHeader)); - assert(res); -} - -#define TX_WATCHDOG_TIMEOUT 30 * 1000 - -#include "error.h" - -void CustomRF95::loop() -{ - RH_RF95::loop(); - - // It should never take us more than 30 secs to send a packet, if it does, we have a bug, FIXME, move most of this - // into CustomRF95 - uint32_t now = millis(); - if (lastTxStart != 0 && (now - lastTxStart) > TX_WATCHDOG_TIMEOUT && RH_RF95::mode() == RHGenericDriver::RHModeTx) { - DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode\n"); - RH_RF95::setModeRx(); - if (sendingPacket) { // There was probably a packet we were trying to send, free it - packetPool.release(sendingPacket); - sendingPacket = NULL; - } - recordCriticalError(ErrTxWatchdog); - lastTxStart = 0; // Stop checking for now, because we just warned the developer - } -} - -bool CustomRF95::reconfigure() -{ - setModeIdle(); // Need to be idle before doing init - - // Set up default configuration - // No Sync Words in LORA mode. - setModemConfig(modemConfig); // Radio default - // setModemConfig(Bw125Cr48Sf4096); // slow and reliable? - // rf95.setPreambleLength(8); // Default is 8 - - if (!setFrequency(freq)) { - DEBUG_MSG("setFrequency failed\n"); - assert(0); // fixme panic - } - - // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on - - // The default transmitter power is 13dBm, using PA_BOOST. - // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then - // you can set transmitter powers from 5 to 23 dBm: - // FIXME - can we do this? It seems to be in the Heltec board. - setTxPower(power, false); - - // Done with init tell radio to start receiving - setModeRx(); - - return true; -} -#endif \ No newline at end of file diff --git a/src/rf95/CustomRF95.h b/src/rf95/CustomRF95.h deleted file mode 100644 index e40c1f020..000000000 --- a/src/rf95/CustomRF95.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include "RadioInterface.h" -#include "mesh.pb.h" -#include - -#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission - -/** - * A version of the RF95 driver which is smart enough to manage packets via queues (no polling or blocking in user threads!) - */ -class CustomRF95 : public RH_RF95, public RadioInterface -{ - friend class MeshRadio; // for debugging we let that class touch pool - - public: - /** pool is the pool we will alloc our rx packets from - * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool - */ - CustomRF95(); - - /** - * Return true if we think the board can go to sleep (i.e. our tx queue is empty, we are not sending or receiving) - * - * This method must be used before putting the CPU into deep or light sleep. - */ - bool canSleep(); - - /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. - virtual bool sleep(); - - /// Send a packet (possibly by enquing in a private fifo). This routine will - /// later free() the packet to pool. This routine is not allowed to stall because it is called from - /// bluetooth comms code. If the txmit queue is empty it might return an error - ErrorCode send(MeshPacket *p); - - bool init(); - - bool reconfigure(); - - void loop(); // Idle processing - - protected: - // After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as - // necessary - virtual void handleInterrupt(); - - private: - /// Send a new packet - this low level call can be called from either ISR or userspace - void startSend(MeshPacket *txp); - - /// Return true if a higher pri task has woken - void handleIdleISR(); -}; \ No newline at end of file diff --git a/src/rf95/RHGenericDriver.cpp b/src/rf95/RHGenericDriver.cpp deleted file mode 100644 index 0dbb995fb..000000000 --- a/src/rf95/RHGenericDriver.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// RHGenericDriver.cpp -// -// Copyright (C) 2014 Mike McCauley -// $Id: RHGenericDriver.cpp,v 1.23 2018/02/11 23:57:18 mikem Exp $ - -#include - -RHGenericDriver::RHGenericDriver() - : _mode(RHModeInitialising), _thisAddress(RH_BROADCAST_ADDRESS), _txHeaderTo(RH_BROADCAST_ADDRESS), - _txHeaderFrom(RH_BROADCAST_ADDRESS), _txHeaderId(0), _txHeaderFlags(0), _rxBad(0), _rxGood(0), _txGood(0), _cad_timeout(0) -{ -} - -bool RHGenericDriver::init() -{ - return true; -} - -// Blocks until a valid message is received -void RHGenericDriver::waitAvailable() -{ - while (!available()) - YIELD; -} - -// Blocks until a valid message is received or timeout expires -// Return true if there is a message available -// Works correctly even on millis() rollover -bool RHGenericDriver::waitAvailableTimeout(uint16_t timeout) -{ - unsigned long starttime = millis(); - while ((millis() - starttime) < timeout) { - if (available()) { - return true; - } - YIELD; - } - return false; -} - -bool RHGenericDriver::waitPacketSent() -{ - while (_mode == RHModeTx) - YIELD; // Wait for any previous transmit to finish - return true; -} - -bool RHGenericDriver::waitPacketSent(uint16_t timeout) -{ - unsigned long starttime = millis(); - while ((millis() - starttime) < timeout) { - if (_mode != RHModeTx) // Any previous transmit finished? - return true; - YIELD; - } - return false; -} - -// Wait until no channel activity detected or timeout -bool RHGenericDriver::waitCAD() -{ - if (!_cad_timeout) - return true; - - // Wait for any channel activity to finish or timeout - // Sophisticated DCF function... - // DCF : BackoffTime = random() x aSlotTime - // 100 - 1000 ms - // 10 sec timeout - unsigned long t = millis(); - while (isChannelActive()) { - if (millis() - t > _cad_timeout) - return false; -#if (RH_PLATFORM == RH_PLATFORM_STM32) // stdlib on STMF103 gets confused if random is redefined - delay(_random(1, 10) * 100); -#else - delay(random(1, 10) * 100); // Should these values be configurable? Macros? -#endif - } - - return true; -} - -// subclasses are expected to override if CAD is available for that radio -bool RHGenericDriver::isChannelActive() -{ - return false; -} - -void RHGenericDriver::setPromiscuous(bool promiscuous) -{ - _promiscuous = promiscuous; -} - -void RHGenericDriver::setThisAddress(uint8_t address) -{ - _thisAddress = address; -} - -void RHGenericDriver::setHeaderTo(uint8_t to) -{ - _txHeaderTo = to; -} - -void RHGenericDriver::setHeaderFrom(uint8_t from) -{ - _txHeaderFrom = from; -} - -void RHGenericDriver::setHeaderId(uint8_t id) -{ - _txHeaderId = id; -} - -void RHGenericDriver::setHeaderFlags(uint8_t set, uint8_t clear) -{ - _txHeaderFlags &= ~clear; - _txHeaderFlags |= set; -} - -uint8_t RHGenericDriver::headerTo() -{ - return _rxHeaderTo; -} - -uint8_t RHGenericDriver::headerFrom() -{ - return _rxHeaderFrom; -} - -uint8_t RHGenericDriver::headerId() -{ - return _rxHeaderId; -} - -uint8_t RHGenericDriver::headerFlags() -{ - return _rxHeaderFlags; -} - -int16_t RHGenericDriver::lastRssi() -{ - return _lastRssi; -} - -RHGenericDriver::RHMode RHGenericDriver::mode() -{ - return _mode; -} - -void RHGenericDriver::setMode(RHMode mode) -{ - _mode = mode; -} - -bool RHGenericDriver::sleep() -{ - return false; -} - -// Diagnostic help -void RHGenericDriver::printBuffer(const char *prompt, const uint8_t *buf, uint8_t len) -{ -#ifdef RH_HAVE_SERIAL - Serial.println(prompt); - uint8_t i; - for (i = 0; i < len; i++) { - if (i % 16 == 15) - Serial.println(buf[i], HEX); - else { - Serial.print(buf[i], HEX); - Serial.print(' '); - } - } - Serial.println(""); -#endif -} - -uint16_t RHGenericDriver::rxBad() -{ - return _rxBad; -} - -uint16_t RHGenericDriver::rxGood() -{ - return _rxGood; -} - -uint16_t RHGenericDriver::txGood() -{ - return _txGood; -} - -void RHGenericDriver::setCADTimeout(unsigned long cad_timeout) -{ - _cad_timeout = cad_timeout; -} - -#if (RH_PLATFORM == RH_PLATFORM_ATTINY) -// Tinycore does not have __cxa_pure_virtual, so without this we -// get linking complaints from the default code generated for pure virtual functions -extern "C" void __cxa_pure_virtual() -{ - while (1) - ; -} -#endif diff --git a/src/rf95/RHGenericDriver.h b/src/rf95/RHGenericDriver.h deleted file mode 100644 index d06c9e4c0..000000000 --- a/src/rf95/RHGenericDriver.h +++ /dev/null @@ -1,280 +0,0 @@ -// RHGenericDriver.h -// Author: Mike McCauley (mikem@airspayce.com) -// Copyright (C) 2014 Mike McCauley -// $Id: RHGenericDriver.h,v 1.23 2018/09/23 23:54:01 mikem Exp $ - -#ifndef RHGenericDriver_h -#define RHGenericDriver_h - -#include - -// Defines bits of the FLAGS header reserved for use by the RadioHead library and -// the flags available for use by applications -#define RH_FLAGS_RESERVED 0xf0 -#define RH_FLAGS_APPLICATION_SPECIFIC 0x0f -#define RH_FLAGS_NONE 0 - -// Default timeout for waitCAD() in ms -#define RH_CAD_DEFAULT_TIMEOUT 10000 - -///////////////////////////////////////////////////////////////////// -/// \class RHGenericDriver RHGenericDriver.h -/// \brief Abstract base class for a RadioHead driver. -/// -/// This class defines the functions that must be provided by any RadioHead driver. -/// Different types of driver will implement all the abstract functions, and will perhaps override -/// other functions in this subclass, or perhaps add new functions specifically required by that driver. -/// Do not directly instantiate this class: it is only to be subclassed by driver classes. -/// -/// Subclasses are expected to implement a half-duplex, unreliable, error checked, unaddressed packet transport. -/// They are expected to carry a message payload with an appropriate maximum length for the transport hardware -/// and to also carry unaltered 4 message headers: TO, FROM, ID, FLAGS -/// -/// \par Headers -/// -/// Each message sent and received by a RadioHead driver includes 4 headers: -/// -TO The node address that the message is being sent to (broadcast RH_BROADCAST_ADDRESS (255) is permitted) -/// -FROM The node address of the sending node -/// -ID A message ID, distinct (over short time scales) for each message sent by a particilar node -/// -FLAGS A bitmask of flags. The most significant 4 bits are reserved for use by RadioHead. The least -/// significant 4 bits are reserved for applications. -class RHGenericDriver -{ - public: - /// \brief Defines different operating modes for the transport hardware - /// - /// These are the different values that can be adopted by the _mode variable and - /// returned by the mode() member function, - typedef enum { - RHModeInitialising = 0, ///< Transport is initialising. Initial default value until init() is called.. - RHModeSleep, ///< Transport hardware is in low power sleep mode (if supported) - RHModeIdle, ///< Transport is idle. - RHModeTx, ///< Transport is in the process of transmitting a message. - RHModeRx, ///< Transport is in the process of receiving a message. - RHModeCad ///< Transport is in the process of detecting channel activity (if supported) - } RHMode; - - /// Constructor - RHGenericDriver(); - - /// 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(); - - /// Tests whether a new message is available - /// from the Driver. - /// On most drivers, if there is an uncollected received message, and there is no message - /// currently bing transmitted, this will also put the Driver into RHModeRx mode until - /// a message is actually received by the transport, when it will be returned to RHModeIdle. - /// This can be called multiple times in a timeout loop. - /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv(). - virtual bool available() = 0; - - /// Returns the maximum message length - /// available in this Driver. - /// \return The maximum legal message length - virtual uint8_t maxMessageLength() = 0; - - /// Starts the receiver and blocks until a valid received - /// message is available. - virtual void waitAvailable(); - - /// Blocks until the transmitter - /// is no longer transmitting. - virtual bool waitPacketSent(); - - /// Blocks until the transmitter is no longer transmitting. - /// or until the timeout occuers, whichever happens first - /// \param[in] timeout Maximum time to wait in milliseconds. - /// \return true if the radio completed transmission within the timeout period. False if it timed out. - virtual bool waitPacketSent(uint16_t timeout); - - /// Starts the receiver and blocks until a received message is available or a timeout - /// \param[in] timeout Maximum time to wait in milliseconds. - /// \return true if a message is available - virtual bool waitAvailableTimeout(uint16_t timeout); - - // Bent G Christensen (bentor@gmail.com), 08/15/2016 - /// Channel Activity Detection (CAD). - /// Blocks until channel activity is finished or CAD timeout occurs. - /// Uses the radio's CAD function (if supported) to detect channel activity. - /// Implements random delays of 100 to 1000ms while activity is detected and until timeout. - /// Caution: the random() function is not seeded. If you want non-deterministic behaviour, consider - /// using something like randomSeed(analogRead(A0)); in your sketch. - /// Permits the implementation of listen-before-talk mechanism (Collision Avoidance). - /// Calls the isChannelActive() member function for the radio (if supported) - /// to determine if the channel is active. If the radio does not support isChannelActive(), - /// always returns true immediately - /// \return true if the radio-specific CAD (as returned by isChannelActive()) - /// shows the channel is clear within the timeout period (or the timeout period is 0), else returns false. - virtual bool waitCAD(); - - /// Sets the Channel Activity Detection timeout in milliseconds to be used by waitCAD(). - /// The default is 0, which means do not wait for CAD detection. - /// CAD detection depends on support for isChannelActive() by your particular radio. - void setCADTimeout(unsigned long cad_timeout); - - /// Determine if the currently selected radio channel is active. - /// This is expected to be subclassed by specific radios to implement their Channel Activity Detection - /// if supported. If the radio does not support CAD, returns true immediately. If a RadioHead radio - /// supports isChannelActive() it will be documented in the radio specific documentation. - /// This is called automatically by waitCAD(). - /// \return true if the radio-specific CAD (as returned by override of isChannelActive()) shows the - /// current radio channel as active, else false. If there is no radio-specific CAD, returns false. - virtual bool isChannelActive(); - - /// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this. - /// This will be used to test the adddress in incoming messages. In non-promiscuous mode, - /// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted. - /// In promiscuous mode, all messages will be accepted regardless of the TO header. - /// In a conventional multinode system, all nodes will have a unique address - /// (which you could store in EEPROM). - /// You would normally set the header FROM address to be the same as thisAddress (though you dont have to, - /// allowing the possibilty of address spoofing). - /// \param[in] thisAddress The address of this node. - virtual void setThisAddress(uint8_t thisAddress); - - /// Sets the TO header to be sent in all subsequent messages - /// \param[in] to The new TO header value - virtual void setHeaderTo(uint8_t to); - - /// Sets the FROM header to be sent in all subsequent messages - /// \param[in] from The new FROM header value - virtual void setHeaderFrom(uint8_t from); - - /// Sets the ID header to be sent in all subsequent messages - /// \param[in] id The new ID header value - virtual void setHeaderId(uint8_t id); - - /// Sets and clears bits in the FLAGS header to be sent in all subsequent messages - /// First it clears he FLAGS according to the clear argument, then sets the flags according to the - /// set argument. The default for clear always clears the application specific flags. - /// \param[in] set bitmask of bits to be set. Flags are cleared with the clear mask before being set. - /// \param[in] clear bitmask of flags to clear. Defaults to RH_FLAGS_APPLICATION_SPECIFIC - /// which clears the application specific flags, resulting in new application specific flags - /// identical to the set. - virtual void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_APPLICATION_SPECIFIC); - - /// Tells the receiver to accept messages with any TO address, not just messages - /// addressed to thisAddress or the broadcast address - /// \param[in] promiscuous true if you wish to receive messages with any TO address - virtual void setPromiscuous(bool promiscuous); - - /// Returns the TO header of the last received message - /// \return The TO header - virtual uint8_t headerTo(); - - /// Returns the FROM header of the last received message - /// \return The FROM header - virtual uint8_t headerFrom(); - - /// Returns the ID header of the last received message - /// \return The ID header - virtual uint8_t headerId(); - - /// Returns the FLAGS header of the last received message - /// \return The FLAGS header - virtual uint8_t headerFlags(); - - /// Returns the most recent RSSI (Receiver Signal Strength Indicator). - /// Usually it is the RSSI of the last received message, which is measured when the preamble is received. - /// If you called readRssi() more recently, it will return that more recent value. - /// \return The most recent RSSI measurement in dBm. - virtual int16_t lastRssi(); - - /// Returns the operating mode of the library. - /// \return the current mode, one of RF69_MODE_* - virtual RHMode mode(); - - /// Sets the operating mode of the transport. - virtual void setMode(RHMode mode); - - /// Sets the transport hardware into low-power sleep mode - /// (if supported). May be overridden by specific drivers to initialte sleep mode. - /// If successful, the transport will stay in sleep mode until woken by - /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) - /// \return true if sleep mode is supported by transport hardware and the RadioHead driver, and if sleep mode - /// was successfully entered. If sleep mode is not suported, return false. - virtual bool sleep(); - - /// Prints a data buffer in HEX. - /// For diagnostic use - /// \param[in] prompt string to preface the print - /// \param[in] buf Location of the buffer to print - /// \param[in] len Length of the buffer in octets. - static void printBuffer(const char *prompt, const uint8_t *buf, uint8_t len); - - /// Returns the count of the number of bad received packets (ie packets with bad lengths, checksum etc) - /// which were rejected and not delivered to the application. - /// Caution: not all drivers can correctly report this count. Some underlying hardware only report - /// good packets. - /// \return The number of bad packets received. - virtual uint16_t rxBad(); - - /// Returns the count of the number of - /// good received packets - /// \return The number of good packets received. - virtual uint16_t rxGood(); - - /// Returns the count of the number of - /// packets successfully transmitted (though not necessarily received by the destination) - /// \return The number of packets successfully transmitted - virtual uint16_t txGood(); - - protected: - /// The current transport operating mode - volatile RHMode _mode; - - /// This node id - uint8_t _thisAddress; - - /// Whether the transport is in promiscuous mode - bool _promiscuous; - - /// TO header in the last received mesasge - volatile uint8_t _rxHeaderTo; - - /// FROM header in the last received mesasge - volatile uint8_t _rxHeaderFrom; - - /// ID header in the last received mesasge - volatile uint8_t _rxHeaderId; - - /// FLAGS header in the last received mesasge - volatile uint8_t _rxHeaderFlags; - - /// TO header to send in all messages - uint8_t _txHeaderTo; - - /// FROM header to send in all messages - uint8_t _txHeaderFrom; - - /// ID header to send in all messages - uint8_t _txHeaderId; - - /// FLAGS header to send in all messages - uint8_t _txHeaderFlags; - - /// The value of the last received RSSI value, in some transport specific units - volatile int16_t _lastRssi; - - /// Count of the number of bad messages (eg bad checksum etc) received - volatile uint16_t _rxBad; - - /// Count of the number of successfully transmitted messaged - volatile uint16_t _rxGood; - - /// Count of the number of bad messages (correct checksum etc) received - volatile uint16_t _txGood; - - /// Channel activity detected - volatile bool _cad; - - /// Channel activity timeout in ms - unsigned int _cad_timeout; - - private: -}; - -#endif diff --git a/src/rf95/RHGenericSPI.cpp b/src/rf95/RHGenericSPI.cpp deleted file mode 100644 index ec43cd403..000000000 --- a/src/rf95/RHGenericSPI.cpp +++ /dev/null @@ -1,31 +0,0 @@ -// RHGenericSPI.cpp -// Author: Mike McCauley (mikem@airspayce.com) -// Copyright (C) 2011 Mike McCauley -// Contributed by Joanna Rutkowska -// $Id: RHGenericSPI.cpp,v 1.2 2014/04/12 05:26:05 mikem Exp $ - -#include - -RHGenericSPI::RHGenericSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode) - : - _frequency(frequency), - _bitOrder(bitOrder), - _dataMode(dataMode) -{ -} - -void RHGenericSPI::setBitOrder(BitOrder bitOrder) -{ - _bitOrder = bitOrder; -} - -void RHGenericSPI::setDataMode(DataMode dataMode) -{ - _dataMode = dataMode; -} - -void RHGenericSPI::setFrequency(Frequency frequency) -{ - _frequency = frequency; -} - diff --git a/src/rf95/RHGenericSPI.h b/src/rf95/RHGenericSPI.h deleted file mode 100644 index 4b961b310..000000000 --- a/src/rf95/RHGenericSPI.h +++ /dev/null @@ -1,183 +0,0 @@ -// RHGenericSPI.h -// Author: Mike McCauley (mikem@airspayce.com) -// Copyright (C) 2011 Mike McCauley -// Contributed by Joanna Rutkowska -// $Id: RHGenericSPI.h,v 1.9 2020/01/05 07:02:23 mikem Exp mikem $ - -#ifndef RHGenericSPI_h -#define RHGenericSPI_h - -#include - -///////////////////////////////////////////////////////////////////// -/// \class RHGenericSPI RHGenericSPI.h -/// \brief Base class for SPI interfaces -/// -/// This generic abstract class is used to encapsulate hardware or software SPI interfaces for -/// a variety of platforms. -/// The intention is so that driver classes can be configured to use hardware or software SPI -/// without changing the main code. -/// -/// You must provide a subclass of this class to driver constructors that require SPI. -/// A concrete subclass that encapsualates the standard Arduino hardware SPI and a bit-banged -/// software implementation is included. -/// -/// Do not directly use this class: it must be subclassed and the following abstract functions at least -/// must be implmented: -/// - begin() -/// - end() -/// - transfer() -class RHGenericSPI -{ -public: - - /// \brief Defines constants for different SPI modes - /// - /// Defines constants for different SPI modes - /// that can be passed to the constructor or setMode() - /// We need to define these in a device and platform independent way, because the - /// SPI implementation is different on each platform. - typedef enum - { - DataMode0 = 0, ///< SPI Mode 0: CPOL = 0, CPHA = 0 - DataMode1, ///< SPI Mode 1: CPOL = 0, CPHA = 1 - DataMode2, ///< SPI Mode 2: CPOL = 1, CPHA = 0 - DataMode3, ///< SPI Mode 3: CPOL = 1, CPHA = 1 - } DataMode; - - /// \brief Defines constants for different SPI bus frequencies - /// - /// Defines constants for different SPI bus frequencies - /// that can be passed to setFrequency(). - /// The frequency you get may not be exactly the one according to the name. - /// We need to define these in a device and platform independent way, because the - /// SPI implementation is different on each platform. - typedef enum - { - Frequency1MHz = 0, ///< SPI bus frequency close to 1MHz - Frequency2MHz, ///< SPI bus frequency close to 2MHz - Frequency4MHz, ///< SPI bus frequency close to 4MHz - Frequency8MHz, ///< SPI bus frequency close to 8MHz - Frequency16MHz ///< SPI bus frequency close to 16MHz - } Frequency; - - /// \brief Defines constants for different SPI endianness - /// - /// Defines constants for different SPI endianness - /// that can be passed to setBitOrder() - /// We need to define these in a device and platform independent way, because the - /// SPI implementation is different on each platform. - typedef enum - { - BitOrderMSBFirst = 0, ///< SPI MSB first - BitOrderLSBFirst, ///< SPI LSB first - } BitOrder; - - /// Constructor - /// Creates an instance of an abstract SPI interface. - /// Do not use this contructor directly: you must instead use on of the concrete subclasses provided - /// such as RHHardwareSPI or RHSoftwareSPI - /// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency - /// is mapped to the closest available bus frequency on the platform. - /// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or - /// RHGenericSPI::BitOrderLSBFirst. - /// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode - RHGenericSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0); - - /// Transfer a single octet to and from the SPI interface - /// \param[in] data The octet to send - /// \return The octet read from SPI while the data octet was sent - virtual uint8_t transfer(uint8_t data) = 0; - -#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) - /// Transfer up to 2 bytes on the SPI interface - /// \param[in] byte0 The first byte to be sent on the SPI interface - /// \param[in] byte1 The second byte to be sent on the SPI interface - /// \return The second byte clocked in as the second byte is sent. - virtual uint8_t transfer2B(uint8_t byte0, uint8_t byte1) = 0; - - /// Read a number of bytes on the SPI interface from an NRF device - /// \param[in] reg The NRF device register to read - /// \param[out] dest The buffer to hold the bytes read - /// \param[in] len The number of bytes to read - /// \return The NRF status byte - virtual uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) = 0; - - /// Wrte a number of bytes on the SPI interface to an NRF device - /// \param[in] reg The NRF device register to read - /// \param[out] src The buffer to hold the bytes write - /// \param[in] len The number of bytes to write - /// \return The NRF status byte - virtual uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) = 0; - -#endif - - /// SPI Configuration methods - /// Enable SPI interrupts (if supported) - /// This can be used in an SPI slave to indicate when an SPI message has been received - virtual void attachInterrupt() {}; - - /// Disable SPI interrupts (if supported) - /// This can be used to diable the SPI interrupt in slaves where that is supported. - virtual void detachInterrupt() {}; - - /// Initialise the SPI library. - /// Call this after configuring and before using the SPI library - virtual void begin() = 0; - - /// Disables the SPI bus (leaving pin modes unchanged). - /// Call this after you have finished using the SPI interface - virtual void end() = 0; - - /// Sets the bit order the SPI interface will use - /// Sets the order of the bits shifted out of and into the SPI bus, either - /// LSBFIRST (least-significant bit first) or MSBFIRST (most-significant bit first). - /// \param[in] bitOrder Bit order to be used: one of RHGenericSPI::BitOrder - virtual void setBitOrder(BitOrder bitOrder); - - /// Sets the SPI data mode: that is, clock polarity and phase. - /// See the Wikipedia article on SPI for details. - /// \param[in] dataMode The mode to use: one of RHGenericSPI::DataMode - virtual void setDataMode(DataMode dataMode); - - /// Sets the SPI clock divider relative to the system clock. - /// On AVR based boards, the dividers available are 2, 4, 8, 16, 32, 64 or 128. - /// The default setting is SPI_CLOCK_DIV4, which sets the SPI clock to one-quarter - /// the frequency of the system clock (4 Mhz for the boards at 16 MHz). - /// \param[in] frequency The data rate to use: one of RHGenericSPI::Frequency - virtual void setFrequency(Frequency frequency); - - /// Signal the start of an SPI transaction that must not be interrupted by other SPI actions - /// In subclasses that support transactions this will ensure that other SPI transactions - /// are blocked until this one is completed by endTransaction(). - /// Base does nothing - /// Might be overridden in subclass - virtual void beginTransaction(){} - - /// Signal the end of an SPI transaction - /// Base does nothing - /// Might be overridden in subclass - virtual void endTransaction(){} - - /// Specify the interrupt number of the interrupt that will use SPI transactions - /// Tells the SPI support software that SPI transactions will occur with the interrupt - /// handler assocated with interruptNumber - /// Base does nothing - /// Might be overridden in subclass - /// \param[in] interruptNumber The number of the interrupt - virtual void usingInterrupt(uint8_t interruptNumber){ - (void)interruptNumber; - } - -protected: - - /// The configure SPI Bus frequency, one of RHGenericSPI::Frequency - Frequency _frequency; // Bus frequency, one of RHGenericSPI::Frequency - - /// Bit order, one of RHGenericSPI::BitOrder - BitOrder _bitOrder; - - /// SPI bus mode, one of RHGenericSPI::DataMode - DataMode _dataMode; -}; -#endif diff --git a/src/rf95/RHHardwareSPI.cpp b/src/rf95/RHHardwareSPI.cpp deleted file mode 100644 index b4b1f6bd6..000000000 --- a/src/rf95/RHHardwareSPI.cpp +++ /dev/null @@ -1,499 +0,0 @@ -// RHHardwareSPI.cpp -// Author: Mike McCauley (mikem@airspayce.com) -// Copyright (C) 2011 Mike McCauley -// Contributed by Joanna Rutkowska -// $Id: RHHardwareSPI.cpp,v 1.25 2020/01/05 07:02:23 mikem Exp mikem $ - -#include - -#ifdef RH_HAVE_HARDWARE_SPI - -// Declare a single default instance of the hardware SPI interface class -RHHardwareSPI hardware_spi; - - -#if (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc -// Declare an SPI interface to use -HardwareSPI SPI(1); -#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 Discovery -// Declare an SPI interface to use -HardwareSPI SPI(1); -#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) // Mongoose OS platform -HardwareSPI SPI(1); -#endif - -// Arduino Due has default SPI pins on central SPI headers, and not on 10, 11, 12, 13 -// as per other Arduinos -// http://21stdigitalhome.blogspot.com.au/2013/02/arduino-due-hardware-spi.html -#if defined (__arm__) && !defined(CORE_TEENSY) && !defined(SPI_CLOCK_DIV16) && !defined(RH_PLATFORM_NRF52) - // Arduino Due in 1.5.5 has no definitions for SPI dividers - // SPI clock divider is based on MCK of 84MHz - #define SPI_CLOCK_DIV16 (VARIANT_MCK/84000000) // 1MHz - #define SPI_CLOCK_DIV8 (VARIANT_MCK/42000000) // 2MHz - #define SPI_CLOCK_DIV4 (VARIANT_MCK/21000000) // 4MHz - #define SPI_CLOCK_DIV2 (VARIANT_MCK/10500000) // 8MHz - #define SPI_CLOCK_DIV1 (VARIANT_MCK/5250000) // 16MHz -#endif - -RHHardwareSPI::RHHardwareSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode) - : - RHGenericSPI(frequency, bitOrder, dataMode) -{ -} - -uint8_t RHHardwareSPI::transfer(uint8_t data) -{ - return SPI.transfer(data); -} - -#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) -uint8_t RHHardwareSPI::transfer2B(uint8_t byte0, uint8_t byte1) -{ - return SPI.transfer2B(byte0, byte1); -} - -uint8_t RHHardwareSPI::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) -{ - return SPI.spiBurstRead(reg, dest, len); -} - -uint8_t RHHardwareSPI::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) -{ - uint8_t status = SPI.spiBurstWrite(reg, src, len); - return status; -} -#endif - -void RHHardwareSPI::attachInterrupt() -{ -#if (RH_PLATFORM == RH_PLATFORM_ARDUINO || RH_PLATFORM == RH_PLATFORM_NRF52) - SPI.attachInterrupt(); -#endif -} - -void RHHardwareSPI::detachInterrupt() -{ -#if (RH_PLATFORM == RH_PLATFORM_ARDUINO || RH_PLATFORM == RH_PLATFORM_NRF52) - SPI.detachInterrupt(); -#endif -} - -void RHHardwareSPI::begin() -{ -#if defined(SPI_HAS_TRANSACTION) - // Perhaps this is a uniform interface for SPI? - // Currently Teensy and ESP32 only - uint32_t frequency; - if (_frequency == Frequency16MHz) - frequency = 16000000; - else if (_frequency == Frequency8MHz) - frequency = 8000000; - else if (_frequency == Frequency4MHz) - frequency = 4000000; - else if (_frequency == Frequency2MHz) - frequency = 2000000; - else - frequency = 1000000; - -#if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))) || defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32) || defined(NRF52) - // Arduino Due in 1.5.5 has its own BitOrder :-( - // So too does Arduino Zero - // So too does rogerclarkmelbourne/Arduino_STM32 - ::BitOrder bitOrder; -#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA) - ::BitOrder bitOrder; -#else - uint8_t bitOrder; -#endif - - if (_bitOrder == BitOrderLSBFirst) - bitOrder = LSBFIRST; - else - bitOrder = MSBFIRST; - - uint8_t dataMode; - if (_dataMode == DataMode0) - dataMode = SPI_MODE0; - else if (_dataMode == DataMode1) - dataMode = SPI_MODE1; - else if (_dataMode == DataMode2) - dataMode = SPI_MODE2; - else if (_dataMode == DataMode3) - dataMode = SPI_MODE3; - else - dataMode = SPI_MODE0; - - // Save the settings for use in transactions - _settings = SPISettings(frequency, bitOrder, dataMode); - SPI.begin(); - -#else // SPI_HAS_TRANSACTION - - // Sigh: there are no common symbols for some of these SPI options across all platforms -#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE || RH_PLATFORM == RH_PLATFORM_NRF52) - uint8_t dataMode; - if (_dataMode == DataMode0) - dataMode = SPI_MODE0; - else if (_dataMode == DataMode1) - dataMode = SPI_MODE1; - else if (_dataMode == DataMode2) - dataMode = SPI_MODE2; - else if (_dataMode == DataMode3) - dataMode = SPI_MODE3; - else - dataMode = SPI_MODE0; -#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY) - // Temporary work-around due to problem where avr_emulation.h does not work properly for the setDataMode() cal - SPCR &= ~SPI_MODE_MASK; -#else - #if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && defined(ARDUINO_ARCH_SAMD)) || defined(ARDUINO_ARCH_NRF52) - // Zero requires begin() before anything else :-) - SPI.begin(); - #endif - - SPI.setDataMode(dataMode); -#endif - -#if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))) || defined(ARDUINO_ARCH_NRF52) || defined (ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32) - // Arduino Due in 1.5.5 has its own BitOrder :-( - // So too does Arduino Zero - // So too does rogerclarkmelbourne/Arduino_STM32 - ::BitOrder bitOrder; -#else - uint8_t bitOrder; -#endif - if (_bitOrder == BitOrderLSBFirst) - bitOrder = LSBFIRST; - else - bitOrder = MSBFIRST; - SPI.setBitOrder(bitOrder); - uint8_t divider; - switch (_frequency) - { - case Frequency1MHz: - default: -#if F_CPU == 8000000 - divider = SPI_CLOCK_DIV8; -#else - divider = SPI_CLOCK_DIV16; -#endif - break; - - case Frequency2MHz: -#if F_CPU == 8000000 - divider = SPI_CLOCK_DIV4; -#else - divider = SPI_CLOCK_DIV8; -#endif - break; - - case Frequency4MHz: -#if F_CPU == 8000000 - divider = SPI_CLOCK_DIV2; -#else - divider = SPI_CLOCK_DIV4; -#endif - break; - - case Frequency8MHz: - divider = SPI_CLOCK_DIV2; // 4MHz on an 8MHz Arduino - break; - - case Frequency16MHz: - divider = SPI_CLOCK_DIV2; // Not really 16MHz, only 8MHz. 4MHz on an 8MHz Arduino - break; - - } - SPI.setClockDivider(divider); - SPI.begin(); - // Teensy requires it to be set _after_ begin() - SPI.setClockDivider(divider); - -#elif (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc - spi_mode dataMode; - // Hmmm, if we do this as a switch, GCC on maple gets v confused! - if (_dataMode == DataMode0) - dataMode = SPI_MODE_0; - else if (_dataMode == DataMode1) - dataMode = SPI_MODE_1; - else if (_dataMode == DataMode2) - dataMode = SPI_MODE_2; - else if (_dataMode == DataMode3) - dataMode = SPI_MODE_3; - else - dataMode = SPI_MODE_0; - - uint32 bitOrder; - if (_bitOrder == BitOrderLSBFirst) - bitOrder = LSBFIRST; - else - bitOrder = MSBFIRST; - - SPIFrequency frequency; // Yes, I know these are not exact equivalents. - switch (_frequency) - { - case Frequency1MHz: - default: - frequency = SPI_1_125MHZ; - break; - - case Frequency2MHz: - frequency = SPI_2_25MHZ; - break; - - case Frequency4MHz: - frequency = SPI_4_5MHZ; - break; - - case Frequency8MHz: - frequency = SPI_9MHZ; - break; - - case Frequency16MHz: - frequency = SPI_18MHZ; - break; - - } - SPI.begin(frequency, bitOrder, dataMode); - -#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 discovery - uint8_t dataMode; - if (_dataMode == DataMode0) - dataMode = SPI_MODE0; - else if (_dataMode == DataMode1) - dataMode = SPI_MODE1; - else if (_dataMode == DataMode2) - dataMode = SPI_MODE2; - else if (_dataMode == DataMode3) - dataMode = SPI_MODE3; - else - dataMode = SPI_MODE0; - - uint32_t bitOrder; - if (_bitOrder == BitOrderLSBFirst) - bitOrder = LSBFIRST; - else - bitOrder = MSBFIRST; - - SPIFrequency frequency; // Yes, I know these are not exact equivalents. - switch (_frequency) - { - case Frequency1MHz: - default: - frequency = SPI_1_3125MHZ; - break; - - case Frequency2MHz: - frequency = SPI_2_625MHZ; - break; - - case Frequency4MHz: - frequency = SPI_5_25MHZ; - break; - - case Frequency8MHz: - frequency = SPI_10_5MHZ; - break; - - case Frequency16MHz: - frequency = SPI_21_0MHZ; - break; - - } - SPI.begin(frequency, bitOrder, dataMode); - -#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Photon - uint8_t dataMode; - if (_dataMode == DataMode0) - dataMode = SPI_MODE0; - else if (_dataMode == DataMode1) - dataMode = SPI_MODE1; - else if (_dataMode == DataMode2) - dataMode = SPI_MODE2; - else if (_dataMode == DataMode3) - dataMode = SPI_MODE3; - else - dataMode = SPI_MODE0; - SPI.setDataMode(dataMode); - if (_bitOrder == BitOrderLSBFirst) - SPI.setBitOrder(LSBFIRST); - else - SPI.setBitOrder(MSBFIRST); - - switch (_frequency) - { - case Frequency1MHz: - default: - SPI.setClockSpeed(1, MHZ); - break; - - case Frequency2MHz: - SPI.setClockSpeed(2, MHZ); - break; - - case Frequency4MHz: - SPI.setClockSpeed(4, MHZ); - break; - - case Frequency8MHz: - SPI.setClockSpeed(8, MHZ); - break; - - case Frequency16MHz: - SPI.setClockSpeed(16, MHZ); - break; - } - -// SPI.setClockDivider(SPI_CLOCK_DIV4); // 72MHz / 4MHz = 18MHz -// SPI.setClockSpeed(1, MHZ); - SPI.begin(); - -#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) - // Requires SPI driver for ESP8266 from https://github.com/esp8266/Arduino/tree/master/libraries/SPI - // Which ppears to be in Arduino Board Manager ESP8266 Community version 2.1.0 - // Contributed by David Skinner - // begin comes first - SPI.begin(); - - // datamode - switch ( _dataMode ) - { - case DataMode1: - SPI.setDataMode ( SPI_MODE1 ); - break; - case DataMode2: - SPI.setDataMode ( SPI_MODE2 ); - break; - case DataMode3: - SPI.setDataMode ( SPI_MODE3 ); - break; - case DataMode0: - default: - SPI.setDataMode ( SPI_MODE0 ); - break; - } - - // bitorder - SPI.setBitOrder(_bitOrder == BitOrderLSBFirst ? LSBFIRST : MSBFIRST); - - // frequency (this sets the divider) - switch (_frequency) - { - case Frequency1MHz: - default: - SPI.setFrequency(1000000); - break; - case Frequency2MHz: - SPI.setFrequency(2000000); - break; - case Frequency4MHz: - SPI.setFrequency(4000000); - break; - case Frequency8MHz: - SPI.setFrequency(8000000); - break; - case Frequency16MHz: - SPI.setFrequency(16000000); - break; - } - -#elif (RH_PLATFORM == RH_PLATFORM_RASPI) // Raspberry PI - uint8_t dataMode; - if (_dataMode == DataMode0) - dataMode = BCM2835_SPI_MODE0; - else if (_dataMode == DataMode1) - dataMode = BCM2835_SPI_MODE1; - else if (_dataMode == DataMode2) - dataMode = BCM2835_SPI_MODE2; - else if (_dataMode == DataMode3) - dataMode = BCM2835_SPI_MODE3; - - uint8_t bitOrder; - if (_bitOrder == BitOrderLSBFirst) - bitOrder = BCM2835_SPI_BIT_ORDER_LSBFIRST; - else - bitOrder = BCM2835_SPI_BIT_ORDER_MSBFIRST; - - uint32_t divider; - switch (_frequency) - { - case Frequency1MHz: - default: - divider = BCM2835_SPI_CLOCK_DIVIDER_256; - break; - case Frequency2MHz: - divider = BCM2835_SPI_CLOCK_DIVIDER_128; - break; - case Frequency4MHz: - divider = BCM2835_SPI_CLOCK_DIVIDER_64; - break; - case Frequency8MHz: - divider = BCM2835_SPI_CLOCK_DIVIDER_32; - break; - case Frequency16MHz: - divider = BCM2835_SPI_CLOCK_DIVIDER_16; - break; - } - SPI.begin(divider, bitOrder, dataMode); -#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) - uint8_t dataMode = SPI_MODE0; - uint32_t frequency = 4000000; //!!! ESP32/NRF902 works ok at 4MHz but not at 8MHz SPI clock. - uint32_t bitOrder = MSBFIRST; - - if (_dataMode == DataMode0) { - dataMode = SPI_MODE0; - } else if (_dataMode == DataMode1) { - dataMode = SPI_MODE1; - } else if (_dataMode == DataMode2) { - dataMode = SPI_MODE2; - } else if (_dataMode == DataMode3) { - dataMode = SPI_MODE3; - } - - if (_bitOrder == BitOrderLSBFirst) { - bitOrder = LSBFIRST; - } - - if (_frequency == Frequency4MHz) - frequency = 4000000; - else if (_frequency == Frequency2MHz) - frequency = 2000000; - else - frequency = 1000000; - - SPI.begin(frequency, bitOrder, dataMode); -#else - #warning RHHardwareSPI does not support this platform yet. Consider adding it and contributing a patch. -#endif - -#endif // SPI_HAS_TRANSACTION -} - -void RHHardwareSPI::end() -{ - return SPI.end(); -} - -void RHHardwareSPI::beginTransaction() -{ -#if defined(SPI_HAS_TRANSACTION) - SPI.beginTransaction(_settings); -#endif -} - -void RHHardwareSPI::endTransaction() -{ -#if defined(SPI_HAS_TRANSACTION) - SPI.endTransaction(); -#endif -} - -void RHHardwareSPI::usingInterrupt(uint8_t interrupt) -{ -#if defined(SPI_HAS_TRANSACTION) && !defined(RH_MISSING_SPIUSINGINTERRUPT) - SPI.usingInterrupt(interrupt); -#endif - (void)interrupt; -} - -#endif diff --git a/src/rf95/RHHardwareSPI.h b/src/rf95/RHHardwareSPI.h deleted file mode 100644 index 3238ff378..000000000 --- a/src/rf95/RHHardwareSPI.h +++ /dev/null @@ -1,116 +0,0 @@ -// RHHardwareSPI.h -// Author: Mike McCauley (mikem@airspayce.com) -// Copyright (C) 2011 Mike McCauley -// Contributed by Joanna Rutkowska -// $Id: RHHardwareSPI.h,v 1.12 2020/01/05 07:02:23 mikem Exp mikem $ - -#ifndef RHHardwareSPI_h -#define RHHardwareSPI_h - -#include - -///////////////////////////////////////////////////////////////////// -/// \class RHHardwareSPI RHHardwareSPI.h -/// \brief Encapsulate a hardware SPI bus interface -/// -/// This concrete subclass of GenericSPIClass encapsulates the standard Arduino hardware and other -/// hardware SPI interfaces. -/// -/// SPI transactions are supported in development environments that support it with SPI_HAS_TRANSACTION. -class RHHardwareSPI : public RHGenericSPI -{ -#ifdef RH_HAVE_HARDWARE_SPI -public: - /// Constructor - /// Creates an instance of a hardware SPI interface, using whatever SPI hardware is available on - /// your processor platform. On Arduino and Uno32, uses SPI. On Maple, uses HardwareSPI. - /// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency - /// is mapped to the closest available bus frequency on the platform. - /// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or - /// RHGenericSPI::BitOrderLSBFirst. - /// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode - RHHardwareSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0); - - /// Transfer a single octet to and from the SPI interface - /// \param[in] data The octet to send - /// \return The octet read from SPI while the data octet was sent - uint8_t transfer(uint8_t data); - -#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) - /// Transfer (write) 2 bytes on the SPI interface to an NRF device - /// \param[in] byte0 The first byte to be sent on the SPI interface - /// \param[in] byte1 The second byte to be sent on the SPI interface - /// \return The second byte clocked in as the second byte is sent. - uint8_t transfer2B(uint8_t byte0, uint8_t byte1); - - /// Read a number of bytes on the SPI interface from an NRF device - /// \param[in] reg The NRF device register to read - /// \param[out] dest The buffer to hold the bytes read - /// \param[in] len The number of bytes to read - /// \return The NRF status byte - uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); - - /// Wrte a number of bytes on the SPI interface to an NRF device - /// \param[in] reg The NRF device register to read - /// \param[out] src The buffer to hold the bytes write - /// \param[in] len The number of bytes to write - /// \return The NRF status byte - uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); - -#endif - - // SPI Configuration methods - /// Enable SPI interrupts - /// This can be used in an SPI slave to indicate when an SPI message has been received - /// It will cause the SPI_STC_vect interrupt vectr to be executed - void attachInterrupt(); - - /// Disable SPI interrupts - /// This can be used to diable the SPI interrupt in slaves where that is supported. - void detachInterrupt(); - - /// Initialise the SPI library - /// Call this after configuring the SPI interface and before using it to transfer data. - /// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high. - void begin(); - - /// Disables the SPI bus (leaving pin modes unchanged). - /// Call this after you have finished using the SPI interface. - void end(); -#else - // not supported on ATTiny etc - uint8_t transfer(uint8_t /*data*/) {return 0;} - void begin(){} - void end(){} - -#endif - - /// Signal the start of an SPI transaction that must not be interrupted by other SPI actions - /// In subclasses that support transactions this will ensure that other SPI transactions - /// are blocked until this one is completed by endTransaction(). - /// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION. - virtual void beginTransaction(); - - /// Signal the end of an SPI transaction - /// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION. - virtual void endTransaction(); - - /// Specify the interrupt number of the interrupt that will use SPI transactions - /// Tells the SPI support software that SPI transactions will occur with the interrupt - /// handler assocated with interruptNumber - /// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION. - /// \param[in] interruptNumber The number of the interrupt - virtual void usingInterrupt(uint8_t interruptNumber); - -protected: - -#if defined(SPI_HAS_TRANSACTION) - // Storage for SPI settings used in SPI transactions - SPISettings _settings; -#endif -}; - -// Built in default instance -extern RHHardwareSPI hardware_spi; - -#endif diff --git a/src/rf95/RHNRFSPIDriver.cpp b/src/rf95/RHNRFSPIDriver.cpp deleted file mode 100644 index a1e8f0da7..000000000 --- a/src/rf95/RHNRFSPIDriver.cpp +++ /dev/null @@ -1,137 +0,0 @@ -// RHNRFSPIDriver.cpp -// -// Copyright (C) 2014 Mike McCauley -// $Id: RHNRFSPIDriver.cpp,v 1.5 2020/01/05 07:02:23 mikem Exp mikem $ - -#include - -RHNRFSPIDriver::RHNRFSPIDriver(uint8_t slaveSelectPin, RHGenericSPI& spi) - : - _spi(spi), - _slaveSelectPin(slaveSelectPin) -{ -} - -bool RHNRFSPIDriver::init() -{ - // start the SPI library with the default speeds etc: - // On Arduino Due this defaults to SPI1 on the central group of 6 SPI pins - _spi.begin(); - - // Initialise the slave select pin - // On Maple, this must be _after_ spi.begin - pinMode(_slaveSelectPin, OUTPUT); - digitalWrite(_slaveSelectPin, HIGH); - - delay(100); - return true; -} - -// Low level commands for interfacing with the device -uint8_t RHNRFSPIDriver::spiCommand(uint8_t command) -{ - uint8_t status; - ATOMIC_BLOCK_START; -#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) - status = _spi.transfer(command); -#else - _spi.beginTransaction(); - digitalWrite(_slaveSelectPin, LOW); - status = _spi.transfer(command); - digitalWrite(_slaveSelectPin, HIGH); - _spi.endTransaction(); -#endif - ATOMIC_BLOCK_END; - return status; -} - -uint8_t RHNRFSPIDriver::spiRead(uint8_t reg) -{ - uint8_t val; - ATOMIC_BLOCK_START; -#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) - val = _spi.transfer2B(reg, 0); // Send the address, discard the status, The written value is ignored, reg value is read -#else - _spi.beginTransaction(); - digitalWrite(_slaveSelectPin, LOW); - _spi.transfer(reg); // Send the address, discard the status - val = _spi.transfer(0); // The written value is ignored, reg value is read - digitalWrite(_slaveSelectPin, HIGH); - _spi.endTransaction(); -#endif - ATOMIC_BLOCK_END; - return val; -} - -uint8_t RHNRFSPIDriver::spiWrite(uint8_t reg, uint8_t val) -{ - uint8_t status = 0; - ATOMIC_BLOCK_START; -#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) - status = _spi.transfer2B(reg, val); -#else - _spi.beginTransaction(); - digitalWrite(_slaveSelectPin, LOW); - status = _spi.transfer(reg); // Send the address - _spi.transfer(val); // New value follows -#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY) - // Sigh: some devices, such as MRF89XA dont work properly on Teensy 3.1: - // At 1MHz, the clock returns low _after_ slave select goes high, which prevents SPI - // write working. This delay gixes time for the clock to return low. -delayMicroseconds(5); -#endif - digitalWrite(_slaveSelectPin, HIGH); - _spi.endTransaction(); -#endif - ATOMIC_BLOCK_END; - return status; -} - -uint8_t RHNRFSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) -{ - uint8_t status = 0; - ATOMIC_BLOCK_START; -#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) - status = _spi.spiBurstRead(reg, dest, len); -#else - _spi.beginTransaction(); - digitalWrite(_slaveSelectPin, LOW); - status = _spi.transfer(reg); // Send the start address - while (len--) - *dest++ = _spi.transfer(0); - digitalWrite(_slaveSelectPin, HIGH); - _spi.endTransaction(); -#endif - ATOMIC_BLOCK_END; - return status; -} - -uint8_t RHNRFSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) -{ - uint8_t status = 0; - ATOMIC_BLOCK_START; -#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) - status = _spi.spiBurstWrite(reg, src, len); -#else - _spi.beginTransaction(); - digitalWrite(_slaveSelectPin, LOW); - status = _spi.transfer(reg); // Send the start address - while (len--) - _spi.transfer(*src++); - digitalWrite(_slaveSelectPin, HIGH); - _spi.endTransaction(); -#endif - ATOMIC_BLOCK_END; - return status; -} - -void RHNRFSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin) -{ - _slaveSelectPin = slaveSelectPin; -} - -void RHNRFSPIDriver::spiUsingInterrupt(uint8_t interruptNumber) -{ - _spi.usingInterrupt(interruptNumber); -} - diff --git a/src/rf95/RHNRFSPIDriver.h b/src/rf95/RHNRFSPIDriver.h deleted file mode 100644 index b93557d78..000000000 --- a/src/rf95/RHNRFSPIDriver.h +++ /dev/null @@ -1,101 +0,0 @@ -// RHNRFSPIDriver.h -// Author: Mike McCauley (mikem@airspayce.com) -// Copyright (C) 2014 Mike McCauley -// $Id: RHNRFSPIDriver.h,v 1.5 2017/11/06 00:04:08 mikem Exp $ - -#ifndef RHNRFSPIDriver_h -#define RHNRFSPIDriver_h - -#include -#include - -class RHGenericSPI; - -///////////////////////////////////////////////////////////////////// -/// \class RHNRFSPIDriver RHNRFSPIDriver.h -/// \brief Base class for RadioHead drivers that use the SPI bus -/// to communicate with its NRF family transport hardware. -/// -/// This class can be subclassed by Drivers that require to use the SPI bus. -/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform) -/// of the bitbanged RHSoftwareSPI class. The dfault behaviour is to use a pre-instantiated built-in RHHardwareSPI -/// interface. -/// -/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts -/// are disabled during access. -/// -/// The read and write routines use SPI conventions as used by Nordic NRF radios and otehr devices, -/// but these can be overriden -/// in subclasses if necessary. -/// -/// Application developers are not expected to instantiate this class directly: -/// it is for the use of Driver developers. -class RHNRFSPIDriver : public RHGenericDriver -{ -public: - /// Constructor - /// \param[in] slaveSelectPin The controller pin to use to select the desired SPI device. This pin will be driven LOW - /// during SPI communications with the SPI device that uis iused by this Driver. - /// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface. - RHNRFSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi); - - /// Initialise the Driver transport hardware and software. - /// Make sure the Driver is properly configured before calling init(). - /// \return true if initialisation succeeded. - bool init(); - - /// Sends a single command to the device - /// \param[in] command The command code to send to the device. - /// \return Some devices return a status byte during the first data transfer. This byte is returned. - /// it may or may not be meaningfule depending on the the type of device being accessed. - uint8_t spiCommand(uint8_t command); - - /// Reads a single register from the SPI device - /// \param[in] reg Register number - /// \return The value of the register - uint8_t spiRead(uint8_t reg); - - /// Writes a single byte to the SPI device - /// \param[in] reg Register number - /// \param[in] val The value to write - /// \return Some devices return a status byte during the first data transfer. This byte is returned. - /// it may or may not be meaningfule depending on the the type of device being accessed. - uint8_t spiWrite(uint8_t reg, uint8_t val); - - /// Reads a number of consecutive registers from the SPI device using burst read mode - /// \param[in] reg Register number of the first register - /// \param[in] dest Array to write the register values to. Must be at least len bytes - /// \param[in] len Number of bytes to read - /// \return Some devices return a status byte during the first data transfer. This byte is returned. - /// it may or may not be meaningfule depending on the the type of device being accessed. - uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); - - /// Write a number of consecutive registers using burst write mode - /// \param[in] reg Register number of the first register - /// \param[in] src Array of new register values to write. Must be at least len bytes - /// \param[in] len Number of bytes to write - /// \return Some devices return a status byte during the first data transfer. This byte is returned. - /// it may or may not be meaningfule depending on the the type of device being accessed. - uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); - - /// Set or change the pin to be used for SPI slave select. - /// This can be called at any time to change the - /// pin that will be used for slave select in subsquent SPI operations. - /// \param[in] slaveSelectPin The pin to use - void setSlaveSelectPin(uint8_t slaveSelectPin); - - /// Set the SPI interrupt number - /// If SPI transactions can occur within an interrupt, tell the low level SPI - /// interface which interrupt is used - /// \param[in] interruptNumber the interrupt number - void spiUsingInterrupt(uint8_t interruptNumber); - -protected: - /// Reference to the RHGenericSPI instance to use to trasnfer data with teh SPI device - RHGenericSPI& _spi; - - /// The pin number of the Slave Select pin that is used to select the desired device. - uint8_t _slaveSelectPin; -}; - -#endif diff --git a/src/rf95/RHSPIDriver.cpp b/src/rf95/RHSPIDriver.cpp deleted file mode 100644 index 25bae8a08..000000000 --- a/src/rf95/RHSPIDriver.cpp +++ /dev/null @@ -1,95 +0,0 @@ -// RHSPIDriver.cpp -// -// Copyright (C) 2014 Mike McCauley -// $Id: RHSPIDriver.cpp,v 1.11 2017/11/06 00:04:08 mikem Exp $ - -#include - -RHSPIDriver::RHSPIDriver(uint8_t slaveSelectPin, RHGenericSPI& spi) - : - _spi(spi), - _slaveSelectPin(slaveSelectPin) -{ -} - -bool RHSPIDriver::init() -{ - // start the SPI library with the default speeds etc: - // On Arduino Due this defaults to SPI1 on the central group of 6 SPI pins - _spi.begin(); - - // Initialise the slave select pin - // On Maple, this must be _after_ spi.begin - pinMode(_slaveSelectPin, OUTPUT); - digitalWrite(_slaveSelectPin, HIGH); - - delay(100); - return true; -} - -uint8_t RHSPIDriver::spiRead(uint8_t reg) -{ - uint8_t val; - ATOMIC_BLOCK_START; - digitalWrite(_slaveSelectPin, LOW); - _spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the address with the write mask off - val = _spi.transfer(0); // The written value is ignored, reg value is read - digitalWrite(_slaveSelectPin, HIGH); - ATOMIC_BLOCK_END; - return val; -} - -uint8_t RHSPIDriver::spiWrite(uint8_t reg, uint8_t val) -{ - uint8_t status = 0; - ATOMIC_BLOCK_START; - _spi.beginTransaction(); - digitalWrite(_slaveSelectPin, LOW); - status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the address with the write mask on - _spi.transfer(val); // New value follows - digitalWrite(_slaveSelectPin, HIGH); - _spi.endTransaction(); - ATOMIC_BLOCK_END; - return status; -} - -uint8_t RHSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) -{ - uint8_t status = 0; - ATOMIC_BLOCK_START; - _spi.beginTransaction(); - digitalWrite(_slaveSelectPin, LOW); - status = _spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the start address with the write mask off - while (len--) - *dest++ = _spi.transfer(0); - digitalWrite(_slaveSelectPin, HIGH); - _spi.endTransaction(); - ATOMIC_BLOCK_END; - return status; -} - -uint8_t RHSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) -{ - uint8_t status = 0; - ATOMIC_BLOCK_START; - _spi.beginTransaction(); - digitalWrite(_slaveSelectPin, LOW); - status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the start address with the write mask on - while (len--) - _spi.transfer(*src++); - digitalWrite(_slaveSelectPin, HIGH); - _spi.endTransaction(); - ATOMIC_BLOCK_END; - return status; -} - -void RHSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin) -{ - _slaveSelectPin = slaveSelectPin; -} - -void RHSPIDriver::spiUsingInterrupt(uint8_t interruptNumber) -{ - _spi.usingInterrupt(interruptNumber); -} - diff --git a/src/rf95/RHSPIDriver.h b/src/rf95/RHSPIDriver.h deleted file mode 100644 index fdd5de410..000000000 --- a/src/rf95/RHSPIDriver.h +++ /dev/null @@ -1,100 +0,0 @@ -// RHSPIDriver.h -// Author: Mike McCauley (mikem@airspayce.com) -// Copyright (C) 2014 Mike McCauley -// $Id: RHSPIDriver.h,v 1.14 2019/09/06 04:40:40 mikem Exp $ - -#ifndef RHSPIDriver_h -#define RHSPIDriver_h - -#include -#include - -// This is the bit in the SPI address that marks it as a write -#define RH_SPI_WRITE_MASK 0x80 - -class RHGenericSPI; - -///////////////////////////////////////////////////////////////////// -/// \class RHSPIDriver RHSPIDriver.h -/// \brief Base class for RadioHead drivers that use the SPI bus -/// to communicate with its transport hardware. -/// -/// This class can be subclassed by Drivers that require to use the SPI bus. -/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform) -/// of the bitbanged RHSoftwareSPI class. The default behaviour is to use a pre-instantiated built-in RHHardwareSPI -/// interface. -/// -/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts -/// are disabled during access. -/// -/// The read and write routines implement commonly used SPI conventions: specifically that the MSB -/// of the first byte transmitted indicates that it is a write and the remaining bits indicate the rehgister to access) -/// This can be overriden -/// in subclasses if necessaryor an alternative class, RHNRFSPIDriver can be used to access devices like -/// Nordic NRF series radios, which have different requirements. -/// -/// Application developers are not expected to instantiate this class directly: -/// it is for the use of Driver developers. -class RHSPIDriver : public RHGenericDriver -{ -public: - /// Constructor - /// \param[in] slaveSelectPin The controler pin to use to select the desired SPI device. This pin will be driven LOW - /// during SPI communications with the SPI device that uis iused by this Driver. - /// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface. - RHSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi); - - /// Initialise the Driver transport hardware and software. - /// Make sure the Driver is properly configured before calling init(). - /// \return true if initialisation succeeded. - bool init(); - - /// Reads a single register from the SPI device - /// \param[in] reg Register number - /// \return The value of the register - uint8_t spiRead(uint8_t reg); - - /// Writes a single byte to the SPI device - /// \param[in] reg Register number - /// \param[in] val The value to write - /// \return Some devices return a status byte during the first data transfer. This byte is returned. - /// it may or may not be meaningfule depending on the the type of device being accessed. - uint8_t spiWrite(uint8_t reg, uint8_t val); - - /// Reads a number of consecutive registers from the SPI device using burst read mode - /// \param[in] reg Register number of the first register - /// \param[in] dest Array to write the register values to. Must be at least len bytes - /// \param[in] len Number of bytes to read - /// \return Some devices return a status byte during the first data transfer. This byte is returned. - /// it may or may not be meaningfule depending on the the type of device being accessed. - uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len); - - /// Write a number of consecutive registers using burst write mode - /// \param[in] reg Register number of the first register - /// \param[in] src Array of new register values to write. Must be at least len bytes - /// \param[in] len Number of bytes to write - /// \return Some devices return a status byte during the first data transfer. This byte is returned. - /// it may or may not be meaningfule depending on the the type of device being accessed. - uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len); - - /// Set or change the pin to be used for SPI slave select. - /// This can be called at any time to change the - /// pin that will be used for slave select in subsquent SPI operations. - /// \param[in] slaveSelectPin The pin to use - void setSlaveSelectPin(uint8_t slaveSelectPin); - - /// Set the SPI interrupt number - /// If SPI transactions can occur within an interrupt, tell the low level SPI - /// interface which interrupt is used - /// \param[in] interruptNumber the interrupt number - void spiUsingInterrupt(uint8_t interruptNumber); - - protected: - /// Reference to the RHGenericSPI instance to use to transfer data with the SPI device - RHGenericSPI& _spi; - - /// The pin number of the Slave Select pin that is used to select the desired device. - uint8_t _slaveSelectPin; -}; - -#endif diff --git a/src/rf95/RHSoftwareSPI.cpp b/src/rf95/RHSoftwareSPI.cpp deleted file mode 100644 index f1959cb06..000000000 --- a/src/rf95/RHSoftwareSPI.cpp +++ /dev/null @@ -1,166 +0,0 @@ -// SoftwareSPI.cpp -// Author: Chris Lapa (chris@lapa.com.au) -// Copyright (C) 2014 Chris Lapa -// Contributed by Chris Lapa - -#include - -RHSoftwareSPI::RHSoftwareSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode) - : - RHGenericSPI(frequency, bitOrder, dataMode) -{ - setPins(12, 11, 13); -} - -// Caution: on Arduino Uno and many other CPUs, digitalWrite is quite slow, taking about 4us -// digitalWrite is also slow, taking about 3.5us -// resulting in very slow SPI bus speeds using this technique, up to about 120us per octet of transfer -uint8_t RHSoftwareSPI::transfer(uint8_t data) -{ - uint8_t readData; - uint8_t writeData; - uint8_t builtReturn; - uint8_t mask; - - if (_bitOrder == BitOrderMSBFirst) - { - mask = 0x80; - } - else - { - mask = 0x01; - } - builtReturn = 0; - readData = 0; - - for (uint8_t count=0; count<8; count++) - { - if (data & mask) - { - writeData = HIGH; - } - else - { - writeData = LOW; - } - - if (_clockPhase == 1) - { - // CPHA=1, miso/mosi changing state now - digitalWrite(_mosi, writeData); - digitalWrite(_sck, ~_clockPolarity); - delayPeriod(); - - // CPHA=1, miso/mosi stable now - readData = digitalRead(_miso); - digitalWrite(_sck, _clockPolarity); - delayPeriod(); - } - else - { - // CPHA=0, miso/mosi changing state now - digitalWrite(_mosi, writeData); - digitalWrite(_sck, _clockPolarity); - delayPeriod(); - - // CPHA=0, miso/mosi stable now - readData = digitalRead(_miso); - digitalWrite(_sck, ~_clockPolarity); - delayPeriod(); - } - - if (_bitOrder == BitOrderMSBFirst) - { - mask >>= 1; - builtReturn |= (readData << (7 - count)); - } - else - { - mask <<= 1; - builtReturn |= (readData << count); - } - } - - digitalWrite(_sck, _clockPolarity); - - return builtReturn; -} - -/// Initialise the SPI library -void RHSoftwareSPI::begin() -{ - if (_dataMode == DataMode0 || - _dataMode == DataMode1) - { - _clockPolarity = LOW; - } - else - { - _clockPolarity = HIGH; - } - - if (_dataMode == DataMode0 || - _dataMode == DataMode2) - { - _clockPhase = 0; - } - else - { - _clockPhase = 1; - } - digitalWrite(_sck, _clockPolarity); - - // Caution: these counts assume that digitalWrite is very fast, which is usually not true - switch (_frequency) - { - case Frequency1MHz: - _delayCounts = 8; - break; - - case Frequency2MHz: - _delayCounts = 4; - break; - - case Frequency4MHz: - _delayCounts = 2; - break; - - case Frequency8MHz: - _delayCounts = 1; - break; - - case Frequency16MHz: - _delayCounts = 0; - break; - } -} - -/// Disables the SPI bus usually, in this case -/// there is no hardware controller to disable. -void RHSoftwareSPI::end() { } - -/// Sets the pins used by this SoftwareSPIClass instance. -/// \param[in] miso master in slave out pin used -/// \param[in] mosi master out slave in pin used -/// \param[in] sck clock pin used -void RHSoftwareSPI::setPins(uint8_t miso, uint8_t mosi, uint8_t sck) -{ - _miso = miso; - _mosi = mosi; - _sck = sck; - - pinMode(_miso, INPUT); - pinMode(_mosi, OUTPUT); - pinMode(_sck, OUTPUT); - digitalWrite(_sck, _clockPolarity); -} - - -void RHSoftwareSPI::delayPeriod() -{ - for (uint8_t count = 0; count < _delayCounts; count++) - { - __asm__ __volatile__ ("nop"); - } -} - diff --git a/src/rf95/RHSoftwareSPI.h b/src/rf95/RHSoftwareSPI.h deleted file mode 100644 index 5e7e1a5dd..000000000 --- a/src/rf95/RHSoftwareSPI.h +++ /dev/null @@ -1,90 +0,0 @@ -// SoftwareSPI.h -// Author: Chris Lapa (chris@lapa.com.au) -// Copyright (C) 2014 Chris Lapa -// Contributed by Chris Lapa - -#ifndef RHSoftwareSPI_h -#define RHSoftwareSPI_h - -#include - -///////////////////////////////////////////////////////////////////// -/// \class RHSoftwareSPI RHSoftwareSPI.h -/// \brief Encapsulate a software SPI interface -/// -/// This concrete subclass of RHGenericSPI enapsulates a bit-banged software SPI interface. -/// Caution: this software SPI interface will be much slower than hardware SPI on most -/// platforms. -/// -/// SPI transactions are not supported, and associated functions do nothing. -/// -/// \par Usage -/// -/// Usage varies slightly depending on what driver you are using. -/// -/// For RF22, for example: -/// \code -/// #include -/// RHSoftwareSPI spi; -/// RH_RF22 driver(SS, 2, spi); -/// RHReliableDatagram(driver, CLIENT_ADDRESS); -/// void setup() -/// { -/// spi.setPins(6, 5, 7); // Or whatever SPI pins you need -/// .... -/// } -/// \endcode -class RHSoftwareSPI : public RHGenericSPI -{ -public: - - /// Constructor - /// Creates an instance of a bit-banged software SPI interface. - /// Sets the SPI pins to the defaults of - /// MISO = 12, MOSI = 11, SCK = 13. If you need other assigments, call setPins() before - /// calling manager.init() or driver.init(). - /// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency - /// is mapped to the closest available bus frequency on the platform. CAUTION: the achieved - /// frequency will almost certainly be very much slower on most platforms. eg on Arduino Uno, the - /// the clock rate is likely to be at best around 46kHz. - /// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or - /// RHGenericSPI::BitOrderLSBFirst. - /// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode - RHSoftwareSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0); - - /// Transfer a single octet to and from the SPI interface - /// \param[in] data The octet to send - /// \return The octet read from SPI while the data octet was sent. - uint8_t transfer(uint8_t data); - - /// Initialise the software SPI library - /// Call this after configuring the SPI interface and before using it to transfer data. - /// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high. - void begin(); - - /// Disables the SPI bus usually, in this case - /// there is no hardware controller to disable. - void end(); - - /// Sets the pins used by this SoftwareSPIClass instance. - /// The defaults are: MISO = 12, MOSI = 11, SCK = 13. - /// \param[in] miso master in slave out pin used - /// \param[in] mosi master out slave in pin used - /// \param[in] sck clock pin used - void setPins(uint8_t miso = 12, uint8_t mosi = 11, uint8_t sck = 13); - -private: - - /// Delay routine for bus timing. - void delayPeriod(); - -private: - uint8_t _miso; - uint8_t _mosi; - uint8_t _sck; - uint8_t _delayCounts; - uint8_t _clockPolarity; - uint8_t _clockPhase; -}; - -#endif diff --git a/src/rf95/RH_RF95.cpp b/src/rf95/RH_RF95.cpp deleted file mode 100644 index a5d6b47a7..000000000 --- a/src/rf95/RH_RF95.cpp +++ /dev/null @@ -1,651 +0,0 @@ -// RH_RF95.cpp -// -// Copyright (C) 2011 Mike McCauley -// $Id: RH_RF95.cpp,v 1.22 2020/01/05 07:02:23 mikem Exp mikem $ - -#include - -// Interrupt vectors for the 3 Arduino interrupt pins -// Each interrupt can be handled by a different instance of RH_RF95, allowing you to have -// 2 or more LORAs per Arduino -RH_RF95 *RH_RF95::_deviceForInterrupt[RH_RF95_NUM_INTERRUPTS] = {0, 0, 0}; -uint8_t RH_RF95::_interruptCount = 0; // Index into _deviceForInterrupt for next device - -// These are indexed by the values of ModemConfigChoice -// Stored in flash (program) memory to save SRAM -PROGMEM static const RH_RF95::ModemConfig MODEM_CONFIG_TABLE[] = { - // 1d, 1e, 26 - {0x72, 0x74, 0x04}, // Bw125Cr45Sf128 (the chip default), AGC enabled - {0x92, 0x74, 0x04}, // Bw500Cr45Sf128, AGC enabled - {0x48, 0x94, 0x04}, // Bw31_25Cr48Sf512, AGC enabled - {0x78, 0xc4, 0x0c}, // Bw125Cr48Sf4096, AGC enabled - -}; - -RH_RF95::RH_RF95(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI &spi) - : RHSPIDriver(slaveSelectPin, spi), _rxBufValid(0) -{ - _interruptPin = interruptPin; - _myInterruptIndex = 0xff; // Not allocated yet -} - -bool RH_RF95::init() -{ - if (!RHSPIDriver::init()) - return false; - -#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER - interruptNumber = _interruptPin; -#endif - - // Tell the low level SPI interface we will use SPI within this interrupt - // spiUsingInterrupt(interruptNumber); - - // No way to check the device type :-( - - // Add by Adrien van den Bossche for Teensy - // ARM M4 requires the below. else pin interrupt doesn't work properly. - // On all other platforms, its innocuous, belt and braces - pinMode(_interruptPin, INPUT); - - bool isWakeFromDeepSleep = - false; // true if we think we are waking from deep sleep AND the rf95 seems to have a valid configuration - - if (!isWakeFromDeepSleep) { - // Set sleep mode, so we can also set LORA mode: - spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE); - delay(10); // Wait for sleep mode to take over from say, CAD - // Check we are in sleep mode, with LORA set - if (spiRead(RH_RF95_REG_01_OP_MODE) != (RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE)) { - // Serial.println(spiRead(RH_RF95_REG_01_OP_MODE), HEX); - return false; // No device present? - } - - // Set up FIFO - // We configure so that we can use the entire 256 byte FIFO for either receive - // or transmit, but not both at the same time - spiWrite(RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0); - spiWrite(RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0); - - // Packet format is preamble + explicit-header + payload + crc - // Explicit Header Mode - // payload is TO + FROM + ID + FLAGS + message data - // RX mode is implmented with RXCONTINUOUS - // max message data length is 255 - 4 = 251 octets - - setModeIdle(); - - // Set up default configuration - // No Sync Words in LORA mode. - setModemConfig(Bw125Cr45Sf128); // Radio default - // setModemConfig(Bw125Cr48Sf4096); // slow and reliable? - setPreambleLength(8); // Default is 8 - // An innocuous ISM frequency, same as RF22's - setFrequency(434.0); - // Lowish power - setTxPower(13); - - Serial.printf("IRQ flag mask 0x%x\n", spiRead(RH_RF95_REG_11_IRQ_FLAGS_MASK)); - } else { - // FIXME - // restore mode base off reading RS95 registers - - // Only let CPU enter deep sleep if RF95 is sitting waiting on a receive or is in idle or sleep. - } - - // geeksville: we do this last, because if there is an interrupt pending from during the deep sleep, this attach will cause it - // to be taken. - - // Set up interrupt handler - // Since there are a limited number of interrupt glue functions isr*() available, - // we can only support a limited number of devices simultaneously - // ON some devices, notably most Arduinos, the interrupt pin passed in is actuallt the - // interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping - // yourself based on knwledge of what Arduino board you are running on. - if (_myInterruptIndex == 0xff) { - // First run, no interrupt allocated yet - if (_interruptCount <= RH_RF95_NUM_INTERRUPTS) - _myInterruptIndex = _interruptCount++; - else - return false; // Too many devices, not enough interrupt vectors - } - _deviceForInterrupt[_myInterruptIndex] = this; - - return enableInterrupt(); -} - -// If on a platform without level trigger definitions, just use RISING and suck it up. -#ifndef ONHIGH -#define ONHIGH RISING -#endif - -bool RH_RF95::enableInterrupt() -{ - // Determine the interrupt number that corresponds to the interruptPin - int interruptNumber = digitalPinToInterrupt(_interruptPin); - if (interruptNumber == NOT_AN_INTERRUPT) - return false; - - if (_myInterruptIndex == 0) - attachInterrupt(interruptNumber, isr0, ONHIGH); - else if (_myInterruptIndex == 1) - attachInterrupt(interruptNumber, isr1, ONHIGH); - else if (_myInterruptIndex == 2) - attachInterrupt(interruptNumber, isr2, ONHIGH); - else - return false; // Too many devices, not enough interrupt vectors - - return true; -} - -void RH_INTERRUPT_ATTR RH_RF95::disableInterrupt() -{ - int interruptNumber = digitalPinToInterrupt(_interruptPin); - detachInterrupt(interruptNumber); -} - -void RH_RF95::prepareDeepSleep() -{ - // Determine the interrupt number that corresponds to the interruptPin - int interruptNumber = digitalPinToInterrupt(_interruptPin); - - detachInterrupt(interruptNumber); -} - -bool RH_RF95::isReceiving() -{ - // 0x0b == Look for header info valid, signal synchronized or signal detected - uint8_t reg = spiRead(RH_RF95_REG_18_MODEM_STAT) & 0x1f; - // Serial.printf("reg %x\n", reg); - return _mode == RHModeRx && (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED | - RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0; -} - -void RH_INTERRUPT_ATTR RH_RF95::handleInterruptLevel0() -{ - disableInterrupt(); // Disable our interrupt until our helper thread can run (because the IRQ will remain asserted until we - // talk to it via SPI) - pendingInterrupt = true; -} - -// C++ level interrupt handler for this instance -// LORA is unusual in that it has several interrupt lines, and not a single, combined one. -// On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly -// connnected to the processor. -// We use this to get RxDone and TxDone interrupts -void RH_RF95::handleInterrupt() -{ - // Read the interrupt register - uint8_t irq_flags = spiRead(RH_RF95_REG_12_IRQ_FLAGS); - - // ack all interrupts - // note from radiohead author wrt old code (with IMO wrong fix) - // Sigh: on some processors, for some unknown reason, doing this only once does not actually - // clear the radio's interrupt flag. So we do it twice. Why? (kevinh - I think the root cause we want level - // triggered interrupts here - not edge. Because edge allows us to miss handling secondard interrupts that occurred - // while this ISR was running. Better to instead, configure the interrupts as level triggered and clear pending - // at the _beginning_ of the ISR. If any interrupts occur while handling the ISR, the signal will remain asserted and - // our ISR will be reinvoked to handle that case) - spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags - - // Note: there can be substantial latency between ISR assertion and this function being run, therefore - // multiple flags might be set. Handle them all - - // Note: we are running the chip in continuous receive mode (currently, so RX_TIMEOUT shouldn't ever occur) - bool haveRxError = irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR); - if (haveRxError) { - _rxBad++; - clearRxBuf(); - } else if (irq_flags & RH_RF95_RX_DONE) { - // Read the RegHopChannel register to check if CRC presence is signalled - // in the header. If not it might be a stray (noise) packet.* - uint8_t crc_present = spiRead(RH_RF95_REG_1C_HOP_CHANNEL) & RH_RF95_RX_PAYLOAD_CRC_IS_ON; - - if (!crc_present) { - _rxBad++; - clearRxBuf(); - } else { - // Have received a packet - uint8_t len = spiRead(RH_RF95_REG_13_RX_NB_BYTES); - - // Reset the fifo read ptr to the beginning of the packet - spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, spiRead(RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR)); - spiBurstRead(RH_RF95_REG_00_FIFO, _buf, len); - _bufLen = len; - - // Remember the last signal to noise ratio, LORA mode - // Per page 111, SX1276/77/78/79 datasheet - _lastSNR = (int8_t)spiRead(RH_RF95_REG_19_PKT_SNR_VALUE) / 4; - - // Remember the RSSI of this packet, LORA mode - // this is according to the doc, but is it really correct? - // weakest receiveable signals are reported RSSI at about -66 - _lastRssi = spiRead(RH_RF95_REG_1A_PKT_RSSI_VALUE); - // Adjust the RSSI, datasheet page 87 - if (_lastSNR < 0) - _lastRssi = _lastRssi + _lastSNR; - else - _lastRssi = (int)_lastRssi * 16 / 15; - if (_usingHFport) - _lastRssi -= 157; - else - _lastRssi -= 164; - - // We have received a message. - validateRxBuf(); - if (_rxBufValid) - setModeIdle(); // Got one - } - } - - if (irq_flags & RH_RF95_TX_DONE) { - _txGood++; - setModeIdle(); - } - - if (_mode == RHModeCad && (irq_flags & RH_RF95_CAD_DONE)) { - _cad = irq_flags & RH_RF95_CAD_DETECTED; - setModeIdle(); - } - - enableInterrupt(); // Let ISR run again -} - -void RH_RF95::loop() -{ - while (pendingInterrupt) { - pendingInterrupt = false; // If the flag was set, it is _guaranteed_ the ISR won't be running, because it masked itself - handleInterrupt(); - } -} - -// These are low level functions that call the interrupt handler for the correct -// instance of RH_RF95. -// 3 interrupts allows us to have 3 different devices -void RH_INTERRUPT_ATTR RH_RF95::isr0() -{ - if (_deviceForInterrupt[0]) - _deviceForInterrupt[0]->handleInterruptLevel0(); -} -void RH_INTERRUPT_ATTR RH_RF95::isr1() -{ - if (_deviceForInterrupt[1]) - _deviceForInterrupt[1]->handleInterruptLevel0(); -} -void RH_INTERRUPT_ATTR RH_RF95::isr2() -{ - if (_deviceForInterrupt[2]) - _deviceForInterrupt[2]->handleInterruptLevel0(); -} - -// Check whether the latest received message is complete and uncorrupted -void RH_RF95::validateRxBuf() -{ - if (_bufLen < 4) - return; // Too short to be a real message - // Extract the 4 headers - _rxHeaderTo = _buf[0]; - _rxHeaderFrom = _buf[1]; - _rxHeaderId = _buf[2]; - _rxHeaderFlags = _buf[3]; - if (_promiscuous || _rxHeaderTo == _thisAddress || _rxHeaderTo == RH_BROADCAST_ADDRESS) { - _rxGood++; - _rxBufValid = true; - } -} - -bool RH_RF95::available() -{ - if (_mode == RHModeTx) - return false; - setModeRx(); - return _rxBufValid; // Will be set by the interrupt handler when a good message is received -} - -void RH_RF95::clearRxBuf() -{ - ATOMIC_BLOCK_START; - _rxBufValid = false; - _bufLen = 0; - ATOMIC_BLOCK_END; -} - -/// Note: This routine might be called from inside the RF95 ISR -bool RH_RF95::send(const uint8_t *data, uint8_t len) -{ - if (len > RH_RF95_MAX_MESSAGE_LEN) - return false; - - setModeIdle(); - - // Position at the beginning of the FIFO - spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, 0); - // The headers - spiWrite(RH_RF95_REG_00_FIFO, _txHeaderTo); - spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFrom); - spiWrite(RH_RF95_REG_00_FIFO, _txHeaderId); - spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFlags); - // The message data - spiBurstWrite(RH_RF95_REG_00_FIFO, data, len); - spiWrite(RH_RF95_REG_22_PAYLOAD_LENGTH, len + RH_RF95_HEADER_LEN); - - setModeTx(); // Start the transmitter - // when Tx is done, interruptHandler will fire and radio mode will return to STANDBY - return true; -} - -bool RH_RF95::printRegisters() -{ -#ifdef RH_HAVE_SERIAL - uint8_t registers[] = {0x01, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x014, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, - 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}; - - uint8_t i; - for (i = 0; i < sizeof(registers); i++) { - Serial.print(registers[i], HEX); - Serial.print(": "); - Serial.println(spiRead(registers[i]), HEX); - } -#endif - return true; -} - -uint8_t RH_RF95::maxMessageLength() -{ - return RH_RF95_MAX_MESSAGE_LEN; -} - -bool RH_RF95::setFrequency(float centre) -{ - // Frf = FRF / FSTEP - uint32_t frf = (centre * 1000000.0) / RH_RF95_FSTEP; - spiWrite(RH_RF95_REG_06_FRF_MSB, (frf >> 16) & 0xff); - spiWrite(RH_RF95_REG_07_FRF_MID, (frf >> 8) & 0xff); - spiWrite(RH_RF95_REG_08_FRF_LSB, frf & 0xff); - _usingHFport = (centre >= 779.0); - - return true; -} - -void RH_RF95::setModeIdle() -{ - if (_mode != RHModeIdle) { - spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_STDBY); - _mode = RHModeIdle; - } -} - -bool RH_RF95::sleep() -{ - if (_mode != RHModeSleep) { - spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP); - _mode = RHModeSleep; - } - return true; -} - -void RH_RF95::setModeRx() -{ - if (_mode != RHModeRx) { - spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_RXCONTINUOUS); - spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x00); // Interrupt on RxDone - _mode = RHModeRx; - } -} - -void RH_RF95::setModeTx() -{ - if (_mode != RHModeTx) { - spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_TX); - spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone - _mode = RHModeTx; - } -} - -void RH_RF95::setTxPower(int8_t power, bool useRFO) -{ - // Sigh, different behaviours depending on whther the module use PA_BOOST or the RFO pin - // for the transmitter output - if (useRFO) { - if (power > 14) - power = 14; - if (power < -1) - power = -1; - spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_MAX_POWER | (power + 1)); - } else { - if (power > 23) - power = 23; - if (power < 5) - power = 5; - - // For RH_RF95_PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf' - // RH_RF95_PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will us it - // for 21, 22 and 23dBm - if (power > 20) { - spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_ENABLE); - power -= 3; - } else { - spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_DISABLE); - } - - // RFM95/96/97/98 does not have RFO pins connected to anything. Only PA_BOOST - // pin is connected, so must use PA_BOOST - // Pout = 2 + OutputPower. - // The documentation is pretty confusing on this topic: PaSelect says the max power is 20dBm, - // but OutputPower claims it would be 17dBm. - // My measurements show 20dBm is correct - spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_PA_SELECT | (power - 5)); - } -} - -// Sets registers from a canned modem configuration structure -void RH_RF95::setModemRegisters(const ModemConfig *config) -{ - spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, config->reg_1d); - spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, config->reg_1e); - spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, config->reg_26); -} - -// Set one of the canned FSK Modem configs -// Returns true if its a valid choice -bool RH_RF95::setModemConfig(ModemConfigChoice index) -{ - if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig))) - return false; - - ModemConfig cfg; - memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(RH_RF95::ModemConfig)); - setModemRegisters(&cfg); - - return true; -} - -void RH_RF95::setPreambleLength(uint16_t bytes) -{ - spiWrite(RH_RF95_REG_20_PREAMBLE_MSB, bytes >> 8); - spiWrite(RH_RF95_REG_21_PREAMBLE_LSB, bytes & 0xff); -} - -bool RH_RF95::isChannelActive() -{ - // Set mode RHModeCad - if (_mode != RHModeCad) { - spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_CAD); - spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x80); // Interrupt on CadDone - _mode = RHModeCad; - } - - while (_mode == RHModeCad) - YIELD; - - return _cad; -} - -void RH_RF95::enableTCXO() -{ - while ((spiRead(RH_RF95_REG_4B_TCXO) & RH_RF95_TCXO_TCXO_INPUT_ON) != RH_RF95_TCXO_TCXO_INPUT_ON) { - sleep(); - spiWrite(RH_RF95_REG_4B_TCXO, (spiRead(RH_RF95_REG_4B_TCXO) | RH_RF95_TCXO_TCXO_INPUT_ON)); - } -} - -// From section 4.1.5 of SX1276/77/78/79 -// Ferror = FreqError * 2**24 * BW / Fxtal / 500 -int RH_RF95::frequencyError() -{ - int32_t freqerror = 0; - - // Convert 2.5 bytes (5 nibbles, 20 bits) to 32 bit signed int - // Caution: some C compilers make errors with eg: - // freqerror = spiRead(RH_RF95_REG_28_FEI_MSB) << 16 - // so we go more carefully. - freqerror = spiRead(RH_RF95_REG_28_FEI_MSB); - freqerror <<= 8; - freqerror |= spiRead(RH_RF95_REG_29_FEI_MID); - freqerror <<= 8; - freqerror |= spiRead(RH_RF95_REG_2A_FEI_LSB); - // Sign extension into top 3 nibbles - if (freqerror & 0x80000) - freqerror |= 0xfff00000; - - int error = 0; // In hertz - float bw_tab[] = {7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250, 500}; - uint8_t bwindex = spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) >> 4; - if (bwindex < (sizeof(bw_tab) / sizeof(float))) - error = (float)freqerror * bw_tab[bwindex] * ((float)(1L << 24) / (float)RH_RF95_FXOSC / 500.0); - // else not defined - - return error; -} - -int RH_RF95::lastSNR() -{ - return _lastSNR; -} - -/////////////////////////////////////////////////// -// -// additions below by Brian Norman 9th Nov 2018 -// brian.n.norman@gmail.com -// -// Routines intended to make changing BW, SF and CR -// a bit more intuitive -// -/////////////////////////////////////////////////// - -void RH_RF95::setSpreadingFactor(uint8_t sf) -{ - if (sf <= 6) - sf = RH_RF95_SPREADING_FACTOR_64CPS; - else if (sf == 7) - sf = RH_RF95_SPREADING_FACTOR_128CPS; - else if (sf == 8) - sf = RH_RF95_SPREADING_FACTOR_256CPS; - else if (sf == 9) - sf = RH_RF95_SPREADING_FACTOR_512CPS; - else if (sf == 10) - sf = RH_RF95_SPREADING_FACTOR_1024CPS; - else if (sf == 11) - sf = RH_RF95_SPREADING_FACTOR_2048CPS; - else if (sf >= 12) - sf = RH_RF95_SPREADING_FACTOR_4096CPS; - - // set the new spreading factor - spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, (spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) & ~RH_RF95_SPREADING_FACTOR) | sf); - // check if Low data Rate bit should be set or cleared - setLowDatarate(); -} - -void RH_RF95::setSignalBandwidth(long sbw) -{ - uint8_t bw; // register bit pattern - - if (sbw <= 7800) - bw = RH_RF95_BW_7_8KHZ; - else if (sbw <= 10400) - bw = RH_RF95_BW_10_4KHZ; - else if (sbw <= 15600) - bw = RH_RF95_BW_15_6KHZ; - else if (sbw <= 20800) - bw = RH_RF95_BW_20_8KHZ; - else if (sbw <= 31250) - bw = RH_RF95_BW_31_25KHZ; - else if (sbw <= 41700) - bw = RH_RF95_BW_41_7KHZ; - else if (sbw <= 62500) - bw = RH_RF95_BW_62_5KHZ; - else if (sbw <= 125000) - bw = RH_RF95_BW_125KHZ; - else if (sbw <= 250000) - bw = RH_RF95_BW_250KHZ; - else - bw = RH_RF95_BW_500KHZ; - - // top 4 bits of reg 1D control bandwidth - spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, (spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) & ~RH_RF95_BW) | bw); - // check if low data rate bit should be set or cleared - setLowDatarate(); -} - -void RH_RF95::setCodingRate4(uint8_t denominator) -{ - int cr = RH_RF95_CODING_RATE_4_5; - - // if (denominator <= 5) - // cr = RH_RF95_CODING_RATE_4_5; - if (denominator == 6) - cr = RH_RF95_CODING_RATE_4_6; - else if (denominator == 7) - cr = RH_RF95_CODING_RATE_4_7; - else if (denominator >= 8) - cr = RH_RF95_CODING_RATE_4_8; - - // CR is bits 3..1 of RH_RF95_REG_1D_MODEM_CONFIG1 - spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, (spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) & ~RH_RF95_CODING_RATE) | cr); -} - -void RH_RF95::setLowDatarate() -{ - // called after changing bandwidth and/or spreading factor - // Semtech modem design guide AN1200.13 says - // "To avoid issues surrounding drift of the crystal reference oscillator due to either temperature change - // or motion,the low data rate optimization bit is used. Specifically for 125 kHz bandwidth and SF = 11 and 12, - // this adds a small overhead to increase robustness to reference frequency variations over the timescale of the LoRa - // packet." - - // read current value for BW and SF - uint8_t BW = spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) >> 4; // bw is in bits 7..4 - uint8_t SF = spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) >> 4; // sf is in bits 7..4 - - // calculate symbol time (see Semtech AN1200.22 section 4) - float bw_tab[] = {7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000}; - - float bandwidth = bw_tab[BW]; - - float symbolTime = 1000.0 * pow(2, SF) / bandwidth; // ms - - // the symbolTime for SF 11 BW 125 is 16.384ms. - // and, according to this :- - // https://www.thethingsnetwork.org/forum/t/a-point-to-note-lora-low-data-rate-optimisation-flag/12007 - // the LDR bit should be set if the Symbol Time is > 16ms - // So the threshold used here is 16.0ms - - // the LDR is bit 3 of RH_RF95_REG_26_MODEM_CONFIG3 - uint8_t current = spiRead(RH_RF95_REG_26_MODEM_CONFIG3) & ~RH_RF95_LOW_DATA_RATE_OPTIMIZE; // mask off the LDR bit - if (symbolTime > 16.0) - spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, current | RH_RF95_LOW_DATA_RATE_OPTIMIZE); - else - spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, current); -} - -void RH_RF95::setPayloadCRC(bool on) -{ - // Payload CRC is bit 2 of register 1E - uint8_t current = spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) & ~RH_RF95_PAYLOAD_CRC_ON; // mask off the CRC - - if (on) - spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, current | RH_RF95_PAYLOAD_CRC_ON); - else - spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, current); -} diff --git a/src/rf95/RH_RF95.h b/src/rf95/RH_RF95.h deleted file mode 100644 index 3e0776036..000000000 --- a/src/rf95/RH_RF95.h +++ /dev/null @@ -1,890 +0,0 @@ -// RH_RF95.h -// -// Definitions for HopeRF LoRa radios per: -// http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf -// http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf -// -// Author: Mike McCauley (mikem@airspayce.com) -// Copyright (C) 2014 Mike McCauley -// $Id: RH_RF95.h,v 1.23 2019/11/02 02:34:22 mikem Exp $ -// - -#ifndef RH_RF95_h -#define RH_RF95_h - -#include - -// This is the maximum number of interrupts the driver can support -// Most Arduinos can handle 2, Megas can handle more -#define RH_RF95_NUM_INTERRUPTS 3 - -// Max number of octets the LORA Rx/Tx FIFO can hold -#define RH_RF95_FIFO_SIZE 255 - -// This is the maximum number of bytes that can be carried by the LORA. -// We use some for headers, keeping fewer for RadioHead messages -#define RH_RF95_MAX_PAYLOAD_LEN RH_RF95_FIFO_SIZE - -// The length of the headers we add. -// The headers are inside the LORA's payload -#define RH_RF95_HEADER_LEN 4 - -// This is the maximum message length that can be supported by this driver. -// Can be pre-defined to a smaller size (to save SRAM) prior to including this header -// Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS -#ifndef RH_RF95_MAX_MESSAGE_LEN -#define RH_RF95_MAX_MESSAGE_LEN (RH_RF95_MAX_PAYLOAD_LEN - RH_RF95_HEADER_LEN) -#endif - -// The crystal oscillator frequency of the module -#define RH_RF95_FXOSC 32000000.0 - -// The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19 -#define RH_RF95_FSTEP (RH_RF95_FXOSC / 524288) - -// Register names (LoRa Mode, from table 85) -#define RH_RF95_REG_00_FIFO 0x00 -#define RH_RF95_REG_01_OP_MODE 0x01 -#define RH_RF95_REG_02_RESERVED 0x02 -#define RH_RF95_REG_03_RESERVED 0x03 -#define RH_RF95_REG_04_RESERVED 0x04 -#define RH_RF95_REG_05_RESERVED 0x05 -#define RH_RF95_REG_06_FRF_MSB 0x06 -#define RH_RF95_REG_07_FRF_MID 0x07 -#define RH_RF95_REG_08_FRF_LSB 0x08 -#define RH_RF95_REG_09_PA_CONFIG 0x09 -#define RH_RF95_REG_0A_PA_RAMP 0x0a -#define RH_RF95_REG_0B_OCP 0x0b -#define RH_RF95_REG_0C_LNA 0x0c -#define RH_RF95_REG_0D_FIFO_ADDR_PTR 0x0d -#define RH_RF95_REG_0E_FIFO_TX_BASE_ADDR 0x0e -#define RH_RF95_REG_0F_FIFO_RX_BASE_ADDR 0x0f -#define RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR 0x10 -#define RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 -#define RH_RF95_REG_12_IRQ_FLAGS 0x12 -#define RH_RF95_REG_13_RX_NB_BYTES 0x13 -#define RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB 0x14 -#define RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB 0x15 -#define RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB 0x16 -#define RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB 0x17 -#define RH_RF95_REG_18_MODEM_STAT 0x18 -#define RH_RF95_REG_19_PKT_SNR_VALUE 0x19 -#define RH_RF95_REG_1A_PKT_RSSI_VALUE 0x1a -#define RH_RF95_REG_1B_RSSI_VALUE 0x1b -#define RH_RF95_REG_1C_HOP_CHANNEL 0x1c -#define RH_RF95_REG_1D_MODEM_CONFIG1 0x1d -#define RH_RF95_REG_1E_MODEM_CONFIG2 0x1e -#define RH_RF95_REG_1F_SYMB_TIMEOUT_LSB 0x1f -#define RH_RF95_REG_20_PREAMBLE_MSB 0x20 -#define RH_RF95_REG_21_PREAMBLE_LSB 0x21 -#define RH_RF95_REG_22_PAYLOAD_LENGTH 0x22 -#define RH_RF95_REG_23_MAX_PAYLOAD_LENGTH 0x23 -#define RH_RF95_REG_24_HOP_PERIOD 0x24 -#define RH_RF95_REG_25_FIFO_RX_BYTE_ADDR 0x25 -#define RH_RF95_REG_26_MODEM_CONFIG3 0x26 - -#define RH_RF95_REG_27_PPM_CORRECTION 0x27 -#define RH_RF95_REG_28_FEI_MSB 0x28 -#define RH_RF95_REG_29_FEI_MID 0x29 -#define RH_RF95_REG_2A_FEI_LSB 0x2a -#define RH_RF95_REG_2C_RSSI_WIDEBAND 0x2c -#define RH_RF95_REG_31_DETECT_OPTIMIZE 0x31 -#define RH_RF95_REG_33_INVERT_IQ 0x33 -#define RH_RF95_REG_37_DETECTION_THRESHOLD 0x37 -#define RH_RF95_REG_39_SYNC_WORD 0x39 - -#define RH_RF95_REG_40_DIO_MAPPING1 0x40 -#define RH_RF95_REG_41_DIO_MAPPING2 0x41 -#define RH_RF95_REG_42_VERSION 0x42 - -#define RH_RF95_REG_4B_TCXO 0x4b -#define RH_RF95_REG_4D_PA_DAC 0x4d -#define RH_RF95_REG_5B_FORMER_TEMP 0x5b -#define RH_RF95_REG_61_AGC_REF 0x61 -#define RH_RF95_REG_62_AGC_THRESH1 0x62 -#define RH_RF95_REG_63_AGC_THRESH2 0x63 -#define RH_RF95_REG_64_AGC_THRESH3 0x64 - -// RH_RF95_REG_01_OP_MODE 0x01 -#define RH_RF95_LONG_RANGE_MODE 0x80 -#define RH_RF95_ACCESS_SHARED_REG 0x40 -#define RH_RF95_LOW_FREQUENCY_MODE 0x08 -#define RH_RF95_MODE 0x07 -#define RH_RF95_MODE_SLEEP 0x00 -#define RH_RF95_MODE_STDBY 0x01 -#define RH_RF95_MODE_FSTX 0x02 -#define RH_RF95_MODE_TX 0x03 -#define RH_RF95_MODE_FSRX 0x04 -#define RH_RF95_MODE_RXCONTINUOUS 0x05 -#define RH_RF95_MODE_RXSINGLE 0x06 -#define RH_RF95_MODE_CAD 0x07 - -// RH_RF95_REG_09_PA_CONFIG 0x09 -#define RH_RF95_PA_SELECT 0x80 -#define RH_RF95_MAX_POWER 0x70 -#define RH_RF95_OUTPUT_POWER 0x0f - -// RH_RF95_REG_0A_PA_RAMP 0x0a -#define RH_RF95_LOW_PN_TX_PLL_OFF 0x10 -#define RH_RF95_PA_RAMP 0x0f -#define RH_RF95_PA_RAMP_3_4MS 0x00 -#define RH_RF95_PA_RAMP_2MS 0x01 -#define RH_RF95_PA_RAMP_1MS 0x02 -#define RH_RF95_PA_RAMP_500US 0x03 -#define RH_RF95_PA_RAMP_250US 0x04 -#define RH_RF95_PA_RAMP_125US 0x05 -#define RH_RF95_PA_RAMP_100US 0x06 -#define RH_RF95_PA_RAMP_62US 0x07 -#define RH_RF95_PA_RAMP_50US 0x08 -#define RH_RF95_PA_RAMP_40US 0x09 -#define RH_RF95_PA_RAMP_31US 0x0a -#define RH_RF95_PA_RAMP_25US 0x0b -#define RH_RF95_PA_RAMP_20US 0x0c -#define RH_RF95_PA_RAMP_15US 0x0d -#define RH_RF95_PA_RAMP_12US 0x0e -#define RH_RF95_PA_RAMP_10US 0x0f - -// RH_RF95_REG_0B_OCP 0x0b -#define RH_RF95_OCP_ON 0x20 -#define RH_RF95_OCP_TRIM 0x1f - -// RH_RF95_REG_0C_LNA 0x0c -#define RH_RF95_LNA_GAIN 0xe0 -#define RH_RF95_LNA_GAIN_G1 0x20 -#define RH_RF95_LNA_GAIN_G2 0x40 -#define RH_RF95_LNA_GAIN_G3 0x60 -#define RH_RF95_LNA_GAIN_G4 0x80 -#define RH_RF95_LNA_GAIN_G5 0xa0 -#define RH_RF95_LNA_GAIN_G6 0xc0 -#define RH_RF95_LNA_BOOST_LF 0x18 -#define RH_RF95_LNA_BOOST_LF_DEFAULT 0x00 -#define RH_RF95_LNA_BOOST_HF 0x03 -#define RH_RF95_LNA_BOOST_HF_DEFAULT 0x00 -#define RH_RF95_LNA_BOOST_HF_150PC 0x03 - -// RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11 -#define RH_RF95_RX_TIMEOUT_MASK 0x80 -#define RH_RF95_RX_DONE_MASK 0x40 -#define RH_RF95_PAYLOAD_CRC_ERROR_MASK 0x20 -#define RH_RF95_VALID_HEADER_MASK 0x10 -#define RH_RF95_TX_DONE_MASK 0x08 -#define RH_RF95_CAD_DONE_MASK 0x04 -#define RH_RF95_FHSS_CHANGE_CHANNEL_MASK 0x02 -#define RH_RF95_CAD_DETECTED_MASK 0x01 - -// RH_RF95_REG_12_IRQ_FLAGS 0x12 -#define RH_RF95_RX_TIMEOUT 0x80 -#define RH_RF95_RX_DONE 0x40 -#define RH_RF95_PAYLOAD_CRC_ERROR 0x20 -#define RH_RF95_VALID_HEADER 0x10 -#define RH_RF95_TX_DONE 0x08 -#define RH_RF95_CAD_DONE 0x04 -#define RH_RF95_FHSS_CHANGE_CHANNEL 0x02 -#define RH_RF95_CAD_DETECTED 0x01 - -// RH_RF95_REG_18_MODEM_STAT 0x18 -#define RH_RF95_RX_CODING_RATE 0xe0 -#define RH_RF95_MODEM_STATUS_CLEAR 0x10 -#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08 -#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04 -#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02 -#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01 - -// RH_RF95_REG_1C_HOP_CHANNEL 0x1c -#define RH_RF95_PLL_TIMEOUT 0x80 -#define RH_RF95_RX_PAYLOAD_CRC_IS_ON 0x40 -#define RH_RF95_FHSS_PRESENT_CHANNEL 0x3f - -// RH_RF95_REG_1D_MODEM_CONFIG1 0x1d -#define RH_RF95_BW 0xf0 - -#define RH_RF95_BW_7_8KHZ 0x00 -#define RH_RF95_BW_10_4KHZ 0x10 -#define RH_RF95_BW_15_6KHZ 0x20 -#define RH_RF95_BW_20_8KHZ 0x30 -#define RH_RF95_BW_31_25KHZ 0x40 -#define RH_RF95_BW_41_7KHZ 0x50 -#define RH_RF95_BW_62_5KHZ 0x60 -#define RH_RF95_BW_125KHZ 0x70 -#define RH_RF95_BW_250KHZ 0x80 -#define RH_RF95_BW_500KHZ 0x90 -#define RH_RF95_CODING_RATE 0x0e -#define RH_RF95_CODING_RATE_4_5 0x02 -#define RH_RF95_CODING_RATE_4_6 0x04 -#define RH_RF95_CODING_RATE_4_7 0x06 -#define RH_RF95_CODING_RATE_4_8 0x08 -#define RH_RF95_IMPLICIT_HEADER_MODE_ON 0x01 - -// RH_RF95_REG_1E_MODEM_CONFIG2 0x1e -#define RH_RF95_SPREADING_FACTOR 0xf0 -#define RH_RF95_SPREADING_FACTOR_64CPS 0x60 -#define RH_RF95_SPREADING_FACTOR_128CPS 0x70 -#define RH_RF95_SPREADING_FACTOR_256CPS 0x80 -#define RH_RF95_SPREADING_FACTOR_512CPS 0x90 -#define RH_RF95_SPREADING_FACTOR_1024CPS 0xa0 -#define RH_RF95_SPREADING_FACTOR_2048CPS 0xb0 -#define RH_RF95_SPREADING_FACTOR_4096CPS 0xc0 -#define RH_RF95_TX_CONTINUOUS_MODE 0x08 - -#define RH_RF95_PAYLOAD_CRC_ON 0x04 -#define RH_RF95_SYM_TIMEOUT_MSB 0x03 - -// RH_RF95_REG_26_MODEM_CONFIG3 -#define RH_RF95_MOBILE_NODE 0x08 // HopeRF term -#define RH_RF95_LOW_DATA_RATE_OPTIMIZE 0x08 // Semtechs term -#define RH_RF95_AGC_AUTO_ON 0x04 - -// RH_RF95_REG_4B_TCXO 0x4b -#define RH_RF95_TCXO_TCXO_INPUT_ON 0x10 - -// RH_RF95_REG_4D_PA_DAC 0x4d -#define RH_RF95_PA_DAC_DISABLE 0x04 -#define RH_RF95_PA_DAC_ENABLE 0x07 - -///////////////////////////////////////////////////////////////////// -/// \class RH_RF95 RH_RF95.h -/// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa -/// capable radio transceiver. -/// -/// For Semtech SX1276/77/78/79 and HopeRF RF95/96/97/98 and other similar LoRa capable radios. -/// Based on http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf -/// and http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf -/// and http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf -/// and http://www.semtech.com/images/datasheet/sx1276.pdf -/// and http://www.semtech.com/images/datasheet/sx1276_77_78_79.pdf -/// FSK/GFSK/OOK modes are not (yet) supported. -/// -/// Works with -/// - the excellent MiniWirelessLoRa from Anarduino http://www.anarduino.com/miniwireless -/// - The excellent Modtronix inAir4 http://modtronix.com/inair4.html -/// and inAir9 modules http://modtronix.com/inair9.html. -/// - the excellent Rocket Scream Mini Ultra Pro with the RFM95W -/// http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ -/// - Lora1276 module from NiceRF http://www.nicerf.com/product_view.aspx?id=99 -/// - Adafruit Feather M0 with RFM95 -/// - The very fine Talk2 Whisper Node LoRa boards https://wisen.com.au/store/products/whisper-node-lora -/// an Arduino compatible board, which include an on-board RFM95/96 LoRa Radio (Semtech SX1276), external antenna, -/// run on 2xAAA batteries and support low power operations. RF95 examples work without modification. -/// Use Arduino Board Manager to install the Talk2 code support. Upload the code with an FTDI adapter set to 5V. -/// - heltec / TTGO ESP32 LoRa OLED -/// https://www.aliexpress.com/item/Internet-Development-Board-SX1278-ESP32-WIFI-chip-0-96-inch-OLED-Bluetooth-WIFI-Lora-Kit-32/32824535649.html -/// -/// \par Overview -/// -/// This class provides basic functions for sending and receiving unaddressed, -/// unreliable datagrams of arbitrary length to 251 octets per packet. -/// -/// Manager classes may use this class to implement reliable, addressed datagrams and streams, -/// mesh routers, repeaters, translators etc. -/// -/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and -/// modulation scheme. -/// -/// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF -/// RFM95/96/97/98(W), Semtech SX1276/77/78/79 and compatible radio modules in LoRa mode. -/// -/// The Hope-RF (http://www.hoperf.com) RFM95/96/97/98(W) and Semtech SX1276/77/78/79 is a low-cost ISM transceiver -/// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and -/// programmable data rates, and it also supports the proprietary LoRA (Long Range) mode, which -/// is the only mode supported in this RadioHead driver. -/// -/// This Driver provides functions for sending and receiving messages of up -/// to 251 octets on any frequency supported by the radio, in a range of -/// predefined Bandwidths, Spreading Factors and Coding Rates. Frequency can be set with -/// 61Hz precision to any frequency from 240.0MHz to 960.0MHz. Caution: most modules only support a more limited -/// range of frequencies due to antenna tuning. -/// -/// Up to 2 modules can be connected to an Arduino (3 on a Mega), -/// permitting the construction of translators and frequency changers, etc. -/// -/// Support for other features such as transmitter power control etc is -/// also provided. -/// -/// Tested on MinWirelessLoRa with arduino-1.0.5 -/// on OpenSuSE 13.1. -/// Also tested with Teensy3.1, Modtronix inAir4 and Arduino 1.6.5 on OpenSuSE 13.1 -/// -/// \par Packet Format -/// -/// All messages sent and received by this RH_RF95 Driver conform to this packet format: -/// -/// - LoRa mode: -/// - 8 symbol PREAMBLE -/// - Explicit header with header CRC (handled internally by the radio) -/// - 4 octets HEADER: (TO, FROM, ID, FLAGS) -/// - 0 to 251 octets DATA -/// - CRC (handled internally by the radio) -/// -/// \par Connecting RFM95/96/97/98 and Semtech SX1276/77/78/79 to Arduino -/// -/// We tested with Anarduino MiniWirelessLoRA, which is an Arduino Duemilanove compatible with a RFM96W -/// module on-board. Therefore it needs no connections other than the USB -/// programming connection and an antenna to make it work. -/// -/// If you have a bare RFM95/96/97/98 that you want to connect to an Arduino, you -/// might use these connections (untested): CAUTION: you must use a 3.3V type -/// Arduino, otherwise you will also need voltage level shifters between the -/// Arduino and the RFM95. CAUTION, you must also ensure you connect an -/// antenna. -/// -/// \code -/// Arduino RFM95/96/97/98 -/// GND----------GND (ground in) -/// 3V3----------3.3V (3.3V in) -/// interrupt 0 pin D2-----------DIO0 (interrupt request out) -/// SS pin D10----------NSS (CS chip select in) -/// SCK pin D13----------SCK (SPI clock in) -/// MOSI pin D11----------MOSI (SPI Data in) -/// MISO pin D12----------MISO (SPI Data out) -/// \endcode -/// With these connections, you can then use the default constructor RH_RF95(). -/// You can override the default settings for the SS pin and the interrupt in -/// the RH_RF95 constructor if you wish to connect the slave select SS to other -/// than the normal one for your Arduino (D10 for Diecimila, Uno etc and D53 -/// for Mega) or the interrupt request to other than pin D2 (Caution, -/// different processors have different constraints as to the pins available -/// for interrupts). -/// -/// You can connect a Modtronix inAir4 or inAir9 directly to a 3.3V part such as a Teensy 3.1 like -/// this (tested). -/// \code -/// Teensy inAir4 inAir9 -/// GND----------0V (ground in) -/// 3V3----------3.3V (3.3V in) -/// interrupt 0 pin D2-----------D0 (interrupt request out) -/// SS pin D10----------CS (CS chip select in) -/// SCK pin D13----------CK (SPI clock in) -/// MOSI pin D11----------SI (SPI Data in) -/// MISO pin D12----------SO (SPI Data out) -/// \endcode -/// With these connections, you can then use the default constructor RH_RF95(). -/// you must also set the transmitter power with useRFO: -/// driver.setTxPower(13, true); -/// -/// Note that if you are using Modtronix inAir4 or inAir9,or any other module which uses the -/// transmitter RFO pins and not the PA_BOOST pins -/// that you must configure the power transmitter power for -1 to 14 dBm and with useRFO true. -/// Failure to do that will result in extremely low transmit powers. -/// -/// If you have an Arduino M0 Pro from arduino.org, -/// you should note that you cannot use Pin 2 for the interrupt line -/// (Pin 2 is for the NMI only). The same comments apply to Pin 4 on Arduino Zero from arduino.cc. -/// Instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this: -/// \code -/// // Slave Select is pin 10, interrupt is Pin 3 -/// RH_RF95 driver(10, 3); -/// \endcode -/// -/// If you have a Rocket Scream Mini Ultra Pro with the RFM95W: -/// - Ensure you have Arduino SAMD board support 1.6.5 or later in Arduino IDE 1.6.8 or later. -/// - The radio SS is hardwired to pin D5 and the DIO0 interrupt to pin D2, -/// so you need to initialise the radio like this: -/// \code -/// RH_RF95 driver(5, 2); -/// \endcode -/// - The name of the serial port on that board is 'SerialUSB', not 'Serial', so this may be helpful at the top of our -/// sample sketches: -/// \code -/// #define Serial SerialUSB -/// \endcode -/// - You also need this in setup before radio initialisation -/// \code -/// // Ensure serial flash is not interfering with radio communication on SPI bus -/// pinMode(4, OUTPUT); -/// digitalWrite(4, HIGH); -/// \endcode -/// - and if you have a 915MHz part, you need this after driver/manager intitalisation: -/// \code -/// rf95.setFrequency(915.0); -/// \endcode -/// which adds up to modifying sample sketches something like: -/// \code -/// #include -/// #include -/// RH_RF95 rf95(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W -/// #define Serial SerialUSB -/// -/// void setup() -/// { -/// // Ensure serial flash is not interfering with radio communication on SPI bus -/// pinMode(4, OUTPUT); -/// digitalWrite(4, HIGH); -/// -/// Serial.begin(9600); -/// while (!Serial) ; // Wait for serial port to be available -/// if (!rf95.init()) -/// Serial.println("init failed"); -/// rf95.setFrequency(915.0); -/// } -/// ... -/// \endcode -/// -/// For Adafruit Feather M0 with RFM95, construct the driver like this: -/// \code -/// RH_RF95 rf95(8, 3); -/// \endcode -/// -/// If you have a talk2 Whisper Node LoRa board with on-board RF95 radio, -/// the example rf95_* sketches work without modification. Initialise the radio like -/// with the default constructor: -/// \code -/// RH_RF95 driver; -/// \endcode -/// -/// It is possible to have 2 or more radios connected to one Arduino, provided -/// each radio has its own SS and interrupt line (SCK, SDI and SDO are common -/// to all radios) -/// -/// Caution: on some Arduinos such as the Mega 2560, if you set the slave -/// select pin to be other than the usual SS pin (D53 on Mega 2560), you may -/// need to set the usual SS pin to be an output to force the Arduino into SPI -/// master mode. -/// -/// Caution: Power supply requirements of the RFM module may be relevant in some circumstances: -/// RFM95/96/97/98 modules are capable of pulling 120mA+ at full power, where Arduino's 3.3V line can -/// give 50mA. You may need to make provision for alternate power supply for -/// the RFM module, especially if you wish to use full transmit power, and/or you have -/// other shields demanding power. Inadequate power for the RFM is likely to cause symptoms such as: -/// - reset's/bootups terminate with "init failed" messages -/// - random termination of communication after 5-30 packets sent/received -/// - "fake ok" state, where initialization passes fluently, but communication doesn't happen -/// - shields hang Arduino boards, especially during the flashing -/// -/// \par Interrupts -/// -/// The RH_RF95 driver uses interrupts to react to events in the RFM module, -/// such as the reception of a new packet, or the completion of transmission -/// of a packet. The RH_RF95 driver interrupt service routine reads status from -/// and writes data to the the RFM module via the SPI interface. It is very -/// important therefore, that if you are using the RH_RF95 driver with another -/// SPI based deviced, that you disable interrupts while you transfer data to -/// and from that other device. Use cli() to disable interrupts and sei() to -/// reenable them. -/// -/// \par Memory -/// -/// The RH_RF95 driver requires non-trivial amounts of memory. The sample -/// programs all compile to about 8kbytes each, which will fit in the -/// flash proram memory of most Arduinos. However, the RAM requirements are -/// more critical. Therefore, you should be vary sparing with RAM use in -/// programs that use the RH_RF95 driver. -/// -/// It is often hard to accurately identify when you are hitting RAM limits on Arduino. -/// The symptoms can include: -/// - Mysterious crashes and restarts -/// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements) -/// - Hanging -/// - Output from Serial.print() not appearing -/// -/// \par Range -/// -/// We have made some simple range tests under the following conditions: -/// - rf95_client base station connected to a VHF discone antenna at 8m height above ground -/// - rf95_server mobile connected to 17.3cm 1/4 wavelength antenna at 1m height, no ground plane. -/// - Both configured for 13dBm, 434MHz, Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range -/// - Minimum reported RSSI seen for successful comms was about -91 -/// - Range over flat ground through heavy trees and vegetation approx 2km. -/// - At 20dBm (100mW) otherwise identical conditions approx 3km. -/// - At 20dBm, along salt water flat sandy beach, 3.2km. -/// -/// It should be noted that at this data rate, a 12 octet message takes 2 seconds to transmit. -/// -/// At 20dBm (100mW) with Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. -/// (Default medium range) in the conditions described above. -/// - Range over flat ground through heavy trees and vegetation approx 2km. -/// -/// Caution: the performance of this radio, especially with narrow bandwidths is strongly dependent on the -/// accuracy and stability of the chip clock. HopeRF and Semtech do not appear to -/// recommend bandwidths of less than 62.5 kHz -/// unless you have the optional Temperature Compensated Crystal Oscillator (TCXO) installed and -/// enabled on your radio module. See the refernece manual for more data. -/// Also https://lowpowerlab.com/forum/rf-range-antennas-rfm69-library/lora-library-experiences-range/15/ -/// and http://www.semtech.com/images/datasheet/an120014-xo-guidance-lora-modulation.pdf -/// -/// \par Transmitter Power -/// -/// You can control the transmitter power on the RF transceiver -/// with the RH_RF95::setTxPower() function. The argument can be any of -/// +5 to +23 (for modules that use PA_BOOST) -/// -1 to +14 (for modules that use RFO transmitter pin) -/// The default is 13. Eg: -/// \code -/// driver.setTxPower(10); // use PA_BOOST transmitter pin -/// driver.setTxPower(10, true); // use PA_RFO pin transmitter pin -/// \endcode -/// -/// We have made some actual power measurements against -/// programmed power for Anarduino MiniWirelessLoRa (which has RFM96W-433Mhz installed) -/// - MiniWirelessLoRa RFM96W-433Mhz, USB power -/// - 30cm RG316 soldered direct to RFM96W module ANT and GND -/// - SMA connector -/// - 12db attenuator -/// - SMA connector -/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) -/// - Tektronix TDS220 scope to measure the Vout from power head -/// \code -/// Program power Measured Power -/// dBm dBm -/// 5 5 -/// 7 7 -/// 9 8 -/// 11 11 -/// 13 13 -/// 15 15 -/// 17 16 -/// 19 18 -/// 20 20 -/// 21 21 -/// 22 22 -/// 23 23 -/// \endcode -/// -/// We have also measured the actual power output from a Modtronix inAir4 http://modtronix.com/inair4.html -/// connected to a Teensy 3.1: -/// Teensy 3.1 this is a 3.3V part, connected directly to: -/// Modtronix inAir4 with SMA antenna connector, connected as above: -/// 10cm SMA-SMA cable -/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set) -/// - Tektronix TDS220 scope to measure the Vout from power head -/// \code -/// Program power Measured Power -/// dBm dBm -/// -1 0 -/// 1 2 -/// 3 4 -/// 5 7 -/// 7 10 -/// 9 13 -/// 11 14.2 -/// 13 15 -/// 14 16 -/// \endcode -/// (Caution: we dont claim laboratory accuracy for these power measurements) -/// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna. -class RH_RF95 : public RHSPIDriver -{ - public: - /// \brief Defines register values for a set of modem configuration registers - /// - /// Defines register values for a set of modem configuration registers - /// that can be passed to setModemRegisters() if none of the choices in - /// ModemConfigChoice suit your need setModemRegisters() writes the - /// register values from this structure to the appropriate registers - /// to set the desired spreading factor, coding rate and bandwidth - typedef struct { - uint8_t reg_1d; ///< Value for register RH_RF95_REG_1D_MODEM_CONFIG1 - uint8_t reg_1e; ///< Value for register RH_RF95_REG_1E_MODEM_CONFIG2 - uint8_t reg_26; ///< Value for register RH_RF95_REG_26_MODEM_CONFIG3 - } ModemConfig; - - /// Choices for setModemConfig() for a selected subset of common - /// data rates. If you need another configuration, - /// determine the necessary settings and call setModemRegisters() with your - /// desired settings. It might be helpful to use the LoRa calculator mentioned in - /// http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf - /// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic - /// definitions and not their integer equivalents: its possible that new values will be - /// introduced in later versions (though we will try to avoid it). - /// Caution: if you are using slow packet rates and long packets with RHReliableDatagram or subclasses - /// you may need to change the RHReliableDatagram timeout for reliable operations. - /// Caution: for some slow rates nad with ReliableDatagrams youi may need to increase the reply timeout - /// with manager.setTimeout() to - /// deal with the long transmission times. - typedef enum { - Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range - Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range - Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range - Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range - } ModemConfigChoice; - - /// Constructor. You can have multiple instances, but each instance must have its own - /// interrupt and slave select pin. After constructing, you must call init() to initialise the interface - /// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient - /// distinct interrupt lines, one for each instance. - /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RH_RF22 before - /// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple) - /// \param[in] interruptPin The interrupt Pin number that is connected to the RFM DIO0 interrupt line. - /// Defaults to pin 2, as required by Anarduino MinWirelessLoRa module. - /// Caution: You must specify an interrupt capable pin. - /// On many Arduino boards, there are limitations as to which pins may be used as interrupts. - /// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin. - /// On Arduino Zero from arduino.cc, any digital pin other than 4. - /// On Arduino M0 Pro from arduino.org, any digital pin other than 2. - /// On other Arduinos pins 2 or 3. - /// See http://arduino.cc/en/Reference/attachInterrupt for more details. - /// On Chipkit Uno32, pins 38, 2, 7, 8, 35. - /// On other boards, any digital pin may be used. - /// \param[in] spi Pointer to the SPI interface object to use. - /// Defaults to the standard Arduino hardware SPI interface - RH_RF95(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI &spi = hardware_spi); - - /// 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(); - - /// The main CPU is about to enter deep sleep, prepare the RF95 so it will be able to wake properly after we reboot - /// i.e. confirm we are in idle or rx mode, set a rtcram flag with state we need to restore after boot. Later in boot - /// we'll need to be careful not to wipe registers and be ready to handle any pending interrupts that occurred while - /// the main CPU was powered down. - void prepareDeepSleep(); - - /// Prints the value of all chip registers - /// to the Serial device if RH_HAVE_SERIAL is defined for the current platform - /// For debugging purposes only. - /// \return true on success - bool printRegisters(); - - /// Sets all the registered required to configure the data modem in the RF95/96/97/98, including the bandwidth, - /// spreading factor etc. You can use this to configure the modem with custom configurations if none of the - /// canned configurations in ModemConfigChoice suit you. - /// \param[in] config A ModemConfig structure containing values for the modem configuration registers. - void setModemRegisters(const ModemConfig *config); - - /// Select one of the predefined modem configurations. If you need a modem configuration not provided - /// here, use setModemRegisters() with your own ModemConfig. - /// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator - /// for reliable operation. - /// \param[in] index The configuration choice. - /// \return true if index is a valid choice. - bool setModemConfig(ModemConfigChoice index); - - /// Tests whether a new message is available - /// from the Driver. - /// On most drivers, this will also put the Driver into RHModeRx mode until - /// a message is actually received by the transport, when it wil be returned to RHModeIdle. - /// This can be called multiple times in a timeout loop - /// \return true if a new, complete, error-free uncollected message is available to be retreived by recv() - virtual bool available(); - - /// Sets the length of the preamble - /// in bytes. - /// Caution: this should be set to the same - /// value on all nodes in your network. Default is 8. - /// Sets the message preamble length in RH_RF95_REG_??_PREAMBLE_?SB - /// \param[in] bytes Preamble length in bytes. - void setPreambleLength(uint16_t bytes); - - /// Returns the maximum message length - /// available in this Driver. - /// \return The maximum legal message length - virtual uint8_t maxMessageLength(); - - /// Sets the transmitter and receiver - /// centre frequency. - /// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several - /// different frequency ranges, and setting a frequency outside that range of your radio will probably not work - /// \return true if the selected frquency centre is within range - bool setFrequency(float centre); - - /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, - /// disables them. - void setModeIdle(); - - /// If current mode is Tx or Idle, changes it to Rx. - /// Starts the receiver in the RF95/96/97/98. - void setModeRx(); - - /// If current mode is Rx or Idle, changes it to Rx. F - /// Starts the transmitter in the RF95/96/97/98. - void setModeTx(); - - /// Sets the transmitter power output level, and configures the transmitter pin. - /// Be a good neighbour and set the lowest power level you need. - /// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98) - /// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC) - /// while some (such as the Modtronix inAir4 and inAir9) - /// use the RFO transmitter pin for lower power but higher efficiency. - /// You must set the appropriate power level and useRFO argument for your module. - /// Check with your module manufacturer which transmtter pin is used on your module - /// to ensure you are setting useRFO correctly. - /// Failure to do so will result in very low - /// transmitter power output. - /// Caution: legal power limits may apply in certain countries. - /// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled). - /// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false, - /// valid values are from +5 to +23. - /// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use), - /// valid values are from -1 to 14. - /// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of - /// the PA_BOOST pin (false). Choose the correct setting for your module. - void setTxPower(int8_t power, bool useRFO = false); - - /// Sets the radio into low-power sleep mode. - /// If successful, the transport will stay in sleep mode until woken by - /// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc) - /// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode. - /// \return true if sleep mode was successfully entered. - virtual bool sleep(); - - // Bent G Christensen (bentor@gmail.com), 08/15/2016 - /// Use the radio's Channel Activity Detect (CAD) function to detect channel activity. - /// Sets the RF95 radio into CAD mode and waits until CAD detection is complete. - /// To be used in a listen-before-talk mechanism (Collision Avoidance) - /// with a reasonable time backoff algorithm. - /// This is called automatically by waitCAD(). - /// \return true if channel is in use. - virtual bool isChannelActive(); - - /// Enable TCXO mode - /// Call this immediately after init(), to force your radio to use an external - /// frequency source, such as a Temperature Compensated Crystal Oscillator (TCXO), if available. - /// See the comments in the main documentation about the sensitivity of this radio to - /// clock frequency especially when using narrow bandwidths. - /// Leaves the module in sleep mode. - /// Caution, this function has not been tested by us. - /// Caution, the TCXO model radios are not low power when in sleep (consuming - /// about ~600 uA, reported by Phang Moh Lim.
- void enableTCXO(); - - /// Returns the last measured frequency error. - /// The LoRa receiver estimates the frequency offset between the receiver centre frequency - /// and that of the received LoRa signal. This function returns the estimates offset (in Hz) - /// of the last received message. Caution: this measurement is not absolute, but is measured - /// relative to the local receiver's oscillator. - /// Apparent errors may be due to the transmitter, the receiver or both. - /// \return The estimated centre frequency offset in Hz of the last received message. - /// If the modem bandwidth selector in - /// register RH_RF95_REG_1D_MODEM_CONFIG1 is invalid, returns 0. - int frequencyError(); - - /// Returns the Signal-to-noise ratio (SNR) of the last received message, as measured - /// by the receiver. - /// \return SNR of the last received message in dB - int lastSNR(); - - /// brian.n.norman@gmail.com 9th Nov 2018 - /// Sets the radio spreading factor. - /// valid values are 6 through 12. - /// Out of range values below 6 are clamped to 6 - /// Out of range values above 12 are clamped to 12 - /// See Semtech DS SX1276/77/78/79 page 27 regarding SF6 configuration. - /// - /// \param[in] uint8_t sf (spreading factor 6..12) - /// \return nothing - void setSpreadingFactor(uint8_t sf); - - /// brian.n.norman@gmail.com 9th Nov 2018 - /// Sets the radio signal bandwidth - /// sbw ranges and resultant settings are as follows:- - /// sbw range actual bw (kHz) - /// 0-7800 7.8 - /// 7801-10400 10.4 - /// 10401-15600 15.6 - /// 15601-20800 20.8 - /// 20801-31250 31.25 - /// 31251-41700 41.7 - /// 41701-62500 62.5 - /// 62501-12500 125.0 - /// 12501-250000 250.0 - /// >250000 500.0 - /// NOTE caution Earlier - Semtech do not recommend BW below 62.5 although, in testing - /// I managed 31.25 with two devices in close proximity. - /// \param[in] sbw long, signal bandwidth e.g. 125000 - void setSignalBandwidth(long sbw); - - /// brian.n.norman@gmail.com 9th Nov 2018 - /// Sets the coding rate to 4/5, 4/6, 4/7 or 4/8. - /// Valid denominator values are 5, 6, 7 or 8. A value of 5 sets the coding rate to 4/5 etc. - /// Values below 5 are clamped at 5 - /// values above 8 are clamped at 8 - /// \param[in] denominator uint8_t range 5..8 - void setCodingRate4(uint8_t denominator); - - /// brian.n.norman@gmail.com 9th Nov 2018 - /// sets the low data rate flag if symbol time exceeds 16ms - /// ref: https://www.thethingsnetwork.org/forum/t/a-point-to-note-lora-low-data-rate-optimisation-flag/12007 - /// called by setBandwidth() and setSpreadingfactor() since these affect the symbol time. - void setLowDatarate(); - - /// brian.n.norman@gmail.com 9th Nov 2018 - /// allows the payload CRC bit to be turned on/off. Normally this should be left on - /// so that packets with a bad CRC are rejected - /// \patam[in] on bool, true turns the payload CRC on, false turns it off - void setPayloadCRC(bool on); - - /// Return true if we are currently receiving a packet - bool isReceiving(); - - void loop(); // Perform idle processing - - protected: - /// This is a low level function to handle the interrupts for one instance of RH_RF95. - /// Called automatically by isr*() - /// Should not need to be called by user code. - virtual void handleInterrupt(); - - /// This is the only code called in ISR context, it just queues up our helper thread to run handleInterrupt(); - void RH_INTERRUPT_ATTR handleInterruptLevel0(); - - /// Examine the revceive buffer to determine whether the message is for this node - void validateRxBuf(); - - /// Clear our local receive buffer - void clearRxBuf(); - - /// Waits until any previous transmit packet is finished being transmitted with waitPacketSent(). - /// Then optionally waits for Channel Activity Detection (CAD) - /// to show the channnel is clear (if the radio supports CAD) by calling waitCAD(). - /// Then loads a message into the transmitter and starts the transmitter. Note that a message length - /// of 0 is permitted. - /// \param[in] data Array of data to be sent - /// \param[in] len Number of bytes of data to send - /// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting. - /// \return true if the message length was valid and it was correctly queued for transmit. Return false - /// if CAD was requested and the CAD timeout timed out before clear channel was detected. - virtual bool send(const uint8_t *data, uint8_t len); - - private: - /// Low level interrupt service routine for device connected to interrupt 0 - static void isr0(); - - /// Low level interrupt service routine for device connected to interrupt 1 - static void isr1(); - - /// Low level interrupt service routine for device connected to interrupt 1 - static void isr2(); - - /// Array of instances connected to interrupts 0 and 1 - static RH_RF95 *_deviceForInterrupt[]; - - /// Index of next interrupt number to use in _deviceForInterrupt - static uint8_t _interruptCount; - - bool enableInterrupt(); // enable our IRQ - void disableInterrupt(); // disable our IRQ - - volatile bool pendingInterrupt = false; - - /// The configured interrupt pin connected to this instance - uint8_t _interruptPin; - - /// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated) - /// else 0xff - uint8_t _myInterruptIndex; - - // True if we are using the HF port (779.0 MHz and above) - bool _usingHFport; - - // Last measured SNR, dB - int8_t _lastSNR; - - protected: - /// Number of octets in the buffer - volatile uint8_t _bufLen; - - /// The receiver/transmitter buffer - uint8_t _buf[RH_RF95_MAX_PAYLOAD_LEN]; - - /// True when there is a valid message in the buffer - volatile bool _rxBufValid; -}; - -/// @example rf95_client.pde -/// @example rf95_server.pde -/// @example rf95_encrypted_client.pde -/// @example rf95_encrypted_server.pde -/// @example rf95_reliable_datagram_client.pde -/// @example rf95_reliable_datagram_server.pde - -#endif diff --git a/src/rf95/RHutil/atomic.h b/src/rf95/RHutil/atomic.h deleted file mode 100644 index 019219827..000000000 --- a/src/rf95/RHutil/atomic.h +++ /dev/null @@ -1,71 +0,0 @@ -/* -* This is port of Dean Camera's ATOMIC_BLOCK macros for AVR to ARM Cortex M3 -* v1.0 -* Mark Pendrith, Nov 27, 2012. -* -* From Mark: -* >When I ported the macros I emailed Dean to ask what attribution would be -* >appropriate, and here is his response: -* > -* >>Mark, -* >>I think it's great that you've ported the macros; consider them -* >>public domain, to do with whatever you wish. I hope you find them >useful . -* >> -* >>Cheers! -* >>- Dean -*/ - -#ifdef __arm__ -#ifndef _CORTEX_M3_ATOMIC_H_ -#define _CORTEX_M3_ATOMIC_H_ - -static __inline__ uint32_t __get_primask(void) \ -{ uint32_t primask = 0; \ - __asm__ volatile ("MRS %[result], PRIMASK\n\t":[result]"=r"(primask)::); \ - return primask; } // returns 0 if interrupts enabled, 1 if disabled - -static __inline__ void __set_primask(uint32_t setval) \ -{ __asm__ volatile ("MSR PRIMASK, %[value]\n\t""dmb\n\t""dsb\n\t""isb\n\t"::[value]"r"(setval):); - __asm__ volatile ("" ::: "memory");} - -static __inline__ uint32_t __iSeiRetVal(void) \ -{ __asm__ volatile ("CPSIE i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ - __asm__ volatile ("" ::: "memory"); return 1; } - -static __inline__ uint32_t __iCliRetVal(void) \ -{ __asm__ volatile ("CPSID i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ - __asm__ volatile ("" ::: "memory"); return 1; } - -static __inline__ void __iSeiParam(const uint32_t *__s) \ -{ __asm__ volatile ("CPSIE i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ - __asm__ volatile ("" ::: "memory"); (void)__s; } - -static __inline__ void __iCliParam(const uint32_t *__s) \ -{ __asm__ volatile ("CPSID i\n\t""dmb\n\t""dsb\n\t""isb\n\t"); \ - __asm__ volatile ("" ::: "memory"); (void)__s; } - -static __inline__ void __iRestore(const uint32_t *__s) \ -{ __set_primask(*__s); __asm__ volatile ("dmb\n\t""dsb\n\t""isb\n\t"); \ - __asm__ volatile ("" ::: "memory"); } - - -#define ATOMIC_BLOCK(type) \ -for ( type, __ToDo = __iCliRetVal(); __ToDo ; __ToDo = 0 ) - -#define ATOMIC_RESTORESTATE \ -uint32_t primask_save __attribute__((__cleanup__(__iRestore))) = __get_primask() - -#define ATOMIC_FORCEON \ -uint32_t primask_save __attribute__((__cleanup__(__iSeiParam))) = 0 - -#define NONATOMIC_BLOCK(type) \ -for ( type, __ToDo = __iSeiRetVal(); __ToDo ; __ToDo = 0 ) - -#define NONATOMIC_RESTORESTATE \ -uint32_t primask_save __attribute__((__cleanup__(__iRestore))) = __get_primask() - -#define NONATOMIC_FORCEOFF \ -uint32_t primask_save __attribute__((__cleanup__(__iCliParam))) = 0 - -#endif -#endif diff --git a/src/rf95/RadioHead.h b/src/rf95/RadioHead.h deleted file mode 100644 index ed1551ff4..000000000 --- a/src/rf95/RadioHead.h +++ /dev/null @@ -1,1595 +0,0 @@ -// RadioHead.h -// Author: Mike McCauley (mikem@airspayce.com) DO NOT CONTACT THE AUTHOR DIRECTLY -// Copyright (C) 2014 Mike McCauley -// $Id: RadioHead.h,v 1.80 2020/01/05 07:02:23 mikem Exp mikem $ - -/*! \mainpage RadioHead Packet Radio library for embedded microprocessors - -This is the RadioHead Packet Radio library for embedded microprocessors. -It provides a complete object-oriented library for sending and receiving packetized messages -via a variety of common data radios and other transports on a range of embedded microprocessors. - -The version of the package that this documentation refers to can be downloaded -from http://www.airspayce.com/mikem/arduino/RadioHead/RadioHead-1.98.zip -You can find the latest version of the documentation at http://www.airspayce.com/mikem/arduino/RadioHead - -You can also find online help and discussion at -http://groups.google.com/group/radiohead-arduino -Please use that group for all questions and discussions on this topic. -Do not contact the author directly, unless it is to discuss commercial licensing. -Before asking a question or reporting a bug, please read -- http://en.wikipedia.org/wiki/Wikipedia:Reference_desk/How_to_ask_a_software_question -- http://www.catb.org/esr/faqs/smart-questions.html -- http://www.chiark.greenend.org.uk/~shgtatham/bugs.html - -Caution: Developing this type of software and using data radios -successfully is challenging and requires a substantial knowledge -base in software and radio and data transmission technologies and -theory. It may not be an appropriate project for beginners. If -you are a beginner, you will need to spend some time gaining -knowledge in these areas first. - -\par Overview - -RadioHead consists of 2 main sets of classes: Drivers and Managers. - -- Drivers provide low level access to a range of different packet radios and other packetized message transports. -- Managers provide high level message sending and receiving facilities for a range of different requirements. - -Every RadioHead program will have an instance of a Driver to -provide access to the data radio or transport, and usually a -Manager that uses that driver to send and receive messages for the -application. The programmer is required to instantiate a Driver -and a Manager, and to initialise the Manager. Thereafter the -facilities of the Manager can be used to send and receive -messages. - -It is also possible to use a Driver on its own, without a Manager, although this only allows unaddressed, -unreliable transport via the Driver's facilities. - -In some specialised use cases, it is possible to instantiate more than one Driver and more than one Manager. - -A range of different common embedded microprocessor platforms are supported, allowing your project to run -on your choice of processor. - -Example programs are included to show the main modes of use. - -\par Drivers - -The following Drivers are provided: - -- RH_RF22 -Works with Hope-RF -RF22B and RF23B based transceivers, and compatible chips and modules, -including the RFM22B transceiver module such as -hthis bare module: http://www.sparkfun.com/products/10153 -and this shield: http://www.sparkfun.com/products/11018 -and this board: http://www.anarduino.com/miniwireless -and RF23BP modules such as: http://www.anarduino.com/details.jsp?pid=130 -Supports GFSK, FSK and OOK. Access to other chip -features such as on-chip temperature measurement, analog-digital -converter, transmitter power control etc is also provided. - -- RH_RF24 -Works with Silicon Labs Si4460/4461/4463/4464 family of transceivers chip, and the equivalent -HopeRF RF24/26/27 family of chips and the HopeRF RFM24W/26W/27W modules. -Supports GFSK, FSK and OOK. Access to other chip -features such as on-chip temperature measurement, analog-digital -converter, transmitter power control etc is also provided. - -- RH_RF69 -Works with Hope-RF -RF69B based radio modules, such as the RFM69 module, (as used on the excellent Moteino and Moteino-USB -boards from LowPowerLab http://lowpowerlab.com/moteino/ ) -and compatible chips and modules such as RFM69W, RFM69HW, RFM69CW, RFM69HCW (Semtech SX1231, SX1231H). -Also works with Anarduino MiniWireless -CW and -HW boards http://www.anarduino.com/miniwireless/ including -the marvellous high powered MinWireless-HW (with 20dBm output for excellent range). -Supports GFSK, FSK. - -- RH_NRF24 -Works with Nordic nRF24 based 2.4GHz radio modules, such as nRF24L01 and others. -Also works with Hope-RF RFM73 -and compatible devices (such as BK2423). nRF24L01 and RFM73 can interoperate -with each other. - -- RH_NRF905 -Works with Nordic nRF905 based 433/868/915 MHz radio modules. - -- RH_NRF51 -Works with Nordic nRF51 compatible 2.4 GHz SoC/devices such as the nRF51822. -Also works with Sparkfun nRF52832 breakout board, with Arduino 1.6.13 and -Sparkfun nRF52 boards manager 0.2.3. Caution: although RadioHead compiles with nRF52832 as at 2019-06-06 -there appears to be a problem with the support of interupt handlers in the Sparkfun support libraries, -and drivers (ie most of the SPI based radio drivers) that require interrupts do not work correctly. - -- RH_RF95 -Works with Semtech SX1276/77/78/79, Modtronix inAir4 and inAir9, -and HopeRF RFM95/96/97/98 and other similar LoRa capable radios. -Supports Long Range (LoRa) with spread spectrum frequency hopping, large payloads etc. -FSK/GFSK/OOK modes are not (yet) supported. - -- RH_MRF89 -Works with Microchip MRF89XA and compatible transceivers. -and modules such as MRF89XAM9A. - -- RH_CC110 -Works with Texas Instruments CC110L transceivers and compatible modules such as -Anaren AIR BoosterPack 430BOOST-CC110L - -- RH_E32 -Works with EBYTE E32-TTL-1W serial radio transceivers (and possibly other transceivers in the same family) - -- RH_ASK -Works with a range of inexpensive ASK (amplitude shift keying) RF transceivers such as RX-B1 -(also known as ST-RX04-ASK) receiver; TX-C1 transmitter and DR3100 transceiver; FS1000A/XY-MK-5V transceiver; -HopeRF RFM83C / RFM85. Supports ASK (OOK). - -- RH_Serial -Works with RS232, RS422, RS485, RS488 and other point-to-point and multidropped serial connections, -or with TTL serial UARTs such as those on Arduino and many other processors, -or with data radios with a -serial port interface. RH_Serial provides packetization and error detection over any hardware or -virtual serial connection. Also builds and runs on Linux and OSX. - -- RH_TCP -For use with simulated sketches compiled and running on Linux. -Works with tools/etherSimulator.pl to pass messages between simulated sketches, allowing -testing of Manager classes on Linux and without need for real radios or other transport hardware. - -- RHEncryptedDriver -Adds encryption and decryption to any RadioHead transport driver, using any encrpytion cipher -supported by ArduinoLibs Cryptographic Library http://rweather.github.io/arduinolibs/crypto.html - -Drivers can be used on their own to provide unaddressed, unreliable datagrams. -All drivers have the same identical API. -Or you can use any Driver with any of the Managers described below. - -We welcome contributions of well tested and well documented code to support other transports. - -If your radio or transciever is not on the list above, there is a good chance it -wont work without modifying RadioHead to suit it. If you wish for -support for another radio or transciever, and you send 2 of them to -AirSpayce Pty Ltd, we will consider adding support for it. - -\par Managers - -The drivers above all provide for unaddressed, unreliable, variable -length messages, but if you need more than that, the following -Managers are provided: - -- RHDatagram -Addressed, unreliable variable length messages, with optional broadcast facilities. - -- RHReliableDatagram -Addressed, reliable, retransmitted, acknowledged variable length messages. - -- RHRouter -Multi-hop delivery of RHReliableDatagrams from source node to destination node via 0 or more -intermediate nodes, with manual, pre-programmed routing. - -- RHMesh -Multi-hop delivery of RHReliableDatagrams with automatic route discovery and rediscovery. - -Any Manager may be used with any Driver. - -\par Platforms - -A range of processors and platforms are supported: - -- Arduino and the Arduino IDE (version 1.0 to 1.8.1 and later) -Including Diecimila, Uno, Mega, Leonardo, Yun, Due, Zero etc. http://arduino.cc/, Also similar boards such as - - Moteino http://lowpowerlab.com/moteino/ - - Anarduino Mini http://www.anarduino.com/mini/ - - RedBearLab Blend V1.0 http://redbearlab.com/blend/ (with Arduino 1.0.5 and RedBearLab Blend Add-On version 20140701) - - MoteinoMEGA https://lowpowerlab.com/shop/moteinomega - (with Arduino 1.0.5 and the MoteinoMEGA Arduino Core - https://github.com/LowPowerLab/Moteino/tree/master/MEGA/Core) - - ESP8266 on Arduino IDE and Boards Manager per https://github.com/esp8266/Arduino - Tested using Arduino 1.6.8 with esp8266 by ESP8266 Community version 2.1.0 - Also Arduino 1.8.1 with esp8266 by SparkFun Electronics 2.5.2 - Examples serial_reliable_datagram_* and ask_* are shown to work. - CAUTION: The GHz radio included in the ESP8266 is - not yet supported. - CAUTION: tests here show that when powered by an FTDI USB-Serial converter, - the ESP8266 can draw so much power when transmitting on its GHz WiFi that VCC will sag - causing random crashes. We strongly recommend a large cap, say 1000uF 10V on VCC if you are also using the WiFi. - - Various Talk2 Whisper boards eg https://wisen.com.au/store/products/whisper-node-lora. - Use Arduino Board Manager to install the Talk2 code support. - - etc. - -- STM32 F4 Discover board, using Arduino 1.8.2 or later and - Roger Clarkes Arduino_STM from https://github.com/rogerclarkmelbourne/Arduino_STM32 - Caution: with this library and board, sending text to Serial causes the board to hang in mysterious ways. - Serial2 emits to PA2. The default SPI pins are SCK: PB3, MOSI PB5, MISO PB4. - We tested with PB0 as slave select and PB1 as interrupt pin for various radios. RH_ASK and RH_Serial also work. - -- ChipKIT Core with Arduino IDE on any ChipKIT Core supported Digilent processor (tested on Uno32) - http://chipkit.net/wiki/index.php?title=ChipKIT_core - -- Maple and Flymaple boards with libmaple and the Maple-IDE development environment - http://leaflabs.com/devices/maple/ and http://www.open-drone.org/flymaple - -- Teensy including Teensy 3.1 and earlier built using Arduino IDE 1.0.5 to 1.6.4 and later with - teensyduino addon 1.18 to 1.23 and later. - http://www.pjrc.com/teensy - -- Particle Photon https://store.particle.io/collections/photon and ARM3 based CPU with built-in - Wi-Fi transceiver and extensive IoT software suport. RadioHead does not support the built-in transceiver - but can be used to control other SPI based radios, Serial ports etc. - See below for details on how to build RadioHead for Photon - -- ATTiny built using Arduino IDE 1.8 and the ATTiny core from - https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json - using the instructions at - https://medium.com/jungletronics/attiny85-easy-flashing-through-arduino-b5f896c48189 - (Caution: these are very small processors and not all RadioHead features may be available, depending on memory requirements) - (Caution: we have not had good success building RH_ASK sketches for ATTiny 85 with SpenceKonde ATTinyCore) - -- AtTiny Mega chips supported by Spencer Konde's megaTinyCore (https://github.com/SpenceKonde/megaTinyCore) - (on Arduino 1.8.9 or later) such as AtTiny 3216, AtTiny 1616 etc. These chips can be easily programmed through their - UPDI pin, using an ordinary Arduino board programmed as a jtag2updi programmer as described in - https://github.com/SpenceKonde/megaTinyCore/blob/master/MakeUPDIProgrammer.md. - Make sure you set the programmer type to jtag2updi in the Arduino Tools->Programmer menu. - See https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/ImportantInfo.md for links to pinouts - and pin numbering information for all the suported chips. - -- nRF51 compatible Arm chips such as nRF51822 with Arduino 1.6.4 and later using the procedures - in http://redbearlab.com/getting-started-nrf51822/ - -- nRF52 compatible Arm chips such as as Adafruit BLE Feather board - https://www.adafruit.com/product/3406 - -- Adafruit Feather. These are excellent boards that are available with a variety of radios. We tested with the - Feather 32u4 with RFM69HCW radio, with Arduino IDE 1.6.8 and the Adafruit AVR Boards board manager version 1.6.10. - https://www.adafruit.com/products/3076 - -- Adafruit Feather M0 boards with Arduino 1.8.1 and later, using the Arduino and Adafruit SAMD board support. - https://learn.adafruit.com/adafruit-feather-m0-basic-proto/using-with-arduino-ide - -- ESP32 built using Arduino IDE 1.8.9 or later using the ESP32 toolchain installed per - https://github.com/espressif/arduino-esp32 - The internal 2.4GHz radio is not yet supported. Tested with RFM22 using SPI interface - -- Raspberry Pi - Uses BCM2835 library for GPIO http://www.airspayce.com/mikem/bcm2835/ - Currently works only with RH_NRF24 driver or other drivers that do not require interrupt support. - Contributed by Mike Poublon. - -- Linux and OSX - Using the RHutil/HardwareSerial class, the RH_Serial driver and any manager will - build and run on Linux and OSX. These can be used to build programs that talk securely and reliably to - Arduino and other processors or to other Linux or OSX hosts on a reliable, error detected (and possibly encrypted) datagram - protocol over various types of serial line. - -- Mongoose OS, courtesy Paul Austen. Mongoose OSis an Internet of Things Firmware Development Framework - available under Apache License Version 2.0. It supports low power, connected microcontrollers such as: - ESP32, ESP8266, TI CC3200, TI CC3220, STM32. - https://mongoose-os.com/ - -Other platforms are partially supported, such as Generic AVR 8 bit processors, MSP430. -We welcome contributions that will expand the range of supported platforms. - -If your processor is not on the list above, there is a good chance it -wont work without modifying RadioHead to suit it. If you wish for -support for another processor, and you send 2 of them to -AirSpayce Pty Ltd, we will consider adding support for it. - -RadioHead is available (through the efforts of others) -for PlatformIO. PlatformIO is a cross-platform code builder and the missing library manager. -http://platformio.org/#!/lib/show/124/RadioHead - -\par History - -RadioHead was created in April 2014, substantially based on code from some of our other earlier Radio libraries: - -- RHMesh, RHRouter, RHReliableDatagram and RHDatagram are derived from the RF22 library version 1.39. -- RH_RF22 is derived from the RF22 library version 1.39. -- RH_RF69 is derived from the RF69 library version 1.2. -- RH_ASK is based on the VirtualWire library version 1.26, after significant conversion to C++. -- RH_Serial was new. -- RH_NRF24 is based on the NRF24 library version 1.12, with some significant changes. - -During this combination and redevelopment, we have tried to retain all the processor dependencies and support from -the libraries that were contributed by other people. However not all platforms can be tested by us, so if you -find that support from some platform has not been successfully migrated, please feel free to fix it and send us a -patch. - -Users of RHMesh, RHRouter, RHReliableDatagram and RHDatagram in the previous RF22 library will find that their -existing code will run mostly without modification. See the RH_RF22 documentation for more details. - -\par Installation - -Install in the usual way: unzip the distribution zip file to the libraries -sub-folder of your sketchbook. -The example sketches will be visible in in your Arduino, mpide, maple-ide or whatever. -http://arduino.cc/en/Guide/Libraries - -\par Building for Particle Photon - -The Photon is not supported by the Arduino IDE, so it takes a little effort to set up a build environment. -Heres what we did to enable building of RadioHead example sketches on Linux, -but there are other ways to skin this cat. -Basic reference for getting started is: http://particle-firmware.readthedocs.org/en/develop/build/ -- Download the ARM gcc cross compiler binaries and unpack it in a suitable place: -\code -cd /tmp -wget https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 -tar xvf gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 -\endcode -- If dfu-util and friends not installed on your platform, download dfu-util and friends to somewhere in your path -\code -cd ~/bin -wget http://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-util -wget http://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-suffix -wget http://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-prefix -\endcode -- Download the Particle firmware (contains headers and libraries require to compile Photon sketches) - to a suitable place: -\code -cd /tmp -wget https://github.com/spark/firmware/archive/develop.zip -unzip develop.zip -\endcode -- Make a working area containing the RadioHead library source code and your RadioHead sketch. You must - rename the sketch from .pde or .ino to application.cpp -\code -cd /tmp -mkdir RadioHead -cd RadioHead -cp /usr/local/projects/arduino/libraries/RadioHead/ *.h . -cp /usr/local/projects/arduino/libraries/RadioHead/ *.cpp . -cp /usr/local/projects/arduino/libraries/RadioHead/examples/cc110/cc110_client/cc110_client.pde application.cpp -\endcode -- Edit application.cpp and comment out any \#include so it looks like: -\code - // #include -\endcode -- Connect your Photon by USB. Put it in DFU mode as descibed in Photon documentation. Light should be flashing yellow -- Compile the RadioHead sketch and install it as the user program (this does not update the rest of the - Photon firmware, just the user part: -\code -cd /tmp/firmware-develop/main -PATH=$PATH:/tmp/gcc-arm-none-eabi-5_2-2015q4/bin make APPDIR=/tmp/RadioHead all PLATFORM=photon program-dfu -\endcode -- You should see RadioHead compile without errors and download the finished sketch into the Photon. - -\par Compatible Hardware Suppliers - -We have had good experiences with the following suppliers of RadioHead compatible hardware: - -- LittleBird http://littlebirdelectronics.com.au in Australia for all manner of Arduinos and radios. -- LowPowerLab http://lowpowerlab.com/moteino in USA for the excellent Moteino and Moteino-USB - boards which include Hope-RF RF69B radios on-board. -- Anarduino and HopeRF USA (http://www.hoperfusa.com and http://www.anarduino.com) who have a wide range - of HopeRF radios and Arduino integrated modules. -- SparkFun https://www.sparkfun.com/ in USA who design and sell a wide range of Arduinos and radio modules. -- Wisen http://wisen.com.au who design and sell a wide range of integrated radio/processor modules including the - excellent Talk2 range. - -\par Coding Style - -RadioHead is designed so it can run on small processors with very -limited resources and strict timing contraints. As a result, we -tend only to use the simplest and least demanding (in terms of memory and CPU) C++ -facilities. In particular we avoid as much as possible dynamic -memory allocation, and the use of complex objects like C++ -strings, IO and buffers. We are happy with this, but we are aware -that some people may think we are leaving useful tools on the -table. You should not use this code as an example of how to do -generalised C++ programming on well resourced processors. - -\par Donations - -This library is offered under a free GPL license for those who want to use it that way. -We try hard to keep it up to date, fix bugs -and to provide free support. If this library has helped you save time or money, please consider donating at -http://www.airspayce.com or here: - -\htmlonly
\endhtmlonly - -\subpage packingdata "Passing Sensor Data Between RadioHead nodes" - -\par Trademarks - -RadioHead is a trademark of AirSpayce Pty Ltd. The RadioHead mark was first used on April 12 2014 for -international trade, and is used only in relation to data communications hardware and software and related services. -It is not to be confused with any other similar marks covering other goods and services. - -\par Copyright - -This software is Copyright (C) 2011-2018 Mike McCauley. Use is subject to license -conditions. The main licensing options available are GPL V2 or Commercial: - -\par 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 https://www.gnu.org/licenses/gpl-2.0.html - -\par 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. To purchase a commercial license, contact info@airspayce.com - -\par Revision History -\version 1.1 2014-04-14
- Initial public release -\version 1.2 2014-04-23
- Fixed various typos.
- Added links to compatible Anarduino products.
- Added RHNRFSPIDriver, RH_NRF24 classes to support Nordic NRF24 based radios. -\version 1.3 2014-04-28
- Various documentation fixups.
- RHDatagram::setThisAddress() did not set the local copy of thisAddress. Reported by Steve Childress.
- Fixed a problem on Teensy with RF22 and RF69, where the interrupt pin needs to be set for input,
- else pin interrupt doesn't work properly. Reported by Steve Childress and patched by - Adrien van den Bossche. Thanks.
- Fixed a problem that prevented RF22 honouring setPromiscuous(true). Reported by Steve Childress.
- Updated documentation to clarify some issues to do with maximum message lengths - reported by Steve Childress.
- Added support for yield() on systems that support it (currently Arduino 1.5.5 and later) - so that spin-loops can suport multitasking. Suggested by Steve Childress.
- Added RH_RF22::setGpioReversed() so the reversal it can be configured at run-time after - radio initialisation. It must now be called _after_ init(). Suggested by Steve Childress.
-\version 1.4 2014-04-29
- Fixed further problems with Teensy compatibility for RH_RF22. Tested on Teensy 3.1. - The example/rf22_* examples now run out of the box with the wiring connections as documented for Teensy - in RH_RF22.
- Added YIELDs to spin-loops in RHRouter, RHMesh and RHReliableDatagram, RH_NRF24.
- Tested RH_Serial examples with Teensy 3.1: they now run out of the box.
- Tested RH_ASK examples with Teensy 3.1: they now run out of the box.
- Reduced default SPI speed for NRF24 from 8MHz to 1MHz on Teensy, to improve reliability when - poor wiring is in use.
- on some devices such as Teensy.
- Tested RH_NRF24 examples with Teensy 3.1: they now run out of the box.
-\version 1.5 2014-04-29
- Added support for Nordic Semiconductor nRF905 transceiver with RH_NRF905 driver. Also - added examples for nRF905 and tested on Teensy 3.1 -\version 1.6 2014-04-30
- NRF905 examples were missing -\version 1.7 2014-05-03
- Added support for Arduino Due. Tested with RH_NRF905, RH_Serial, RH_ASK. - IMPORTANT CHANGE to interrupt pins on Arduino with RH_RF22 and RH_RF69 constructors: - previously, you had to specify the interrupt _number_ not the interrupt _pin_. Arduinos and Uno32 - are now consistent with all other platforms: you must specify the interrupt pin number. Default - changed to pin 2 (a common choice with RF22 shields). - Removed examples/maple/maple_rf22_reliable_datagram_client and - examples/maple/maple_rf22_reliable_datagram_client since the rf22 examples now work out - of the box with Flymaple. - Removed examples/uno32/uno32_rf22_reliable_datagram_client and - examples/uno32/uno32_rf22_reliable_datagram_client since the rf22 examples now work out - of the box with ChipKit Uno32. -\version 1.8 2014-05-08
- Added support for YIELD in Teensy 2 and 3, suggested by Steve Childress.
- Documentation updates. Clarify use of headers and Flags
- Fixed misalignment in RH_RF69 between ModemConfigChoice definitions and the implemented choices - which meant you didnt get the choice you thought and GFSK_Rb55555Fd50 hung the transmitter.
- Preliminary work on Linux simulator. -\version 1.9 2014-05-14
- Added support for using Timer 2 instead of Timer 1 on Arduino in RH_ASK when - RH_ASK_ARDUINO_USE_TIMER2 is defined. With the kind assistance of - Luc Small. Thanks!
- Updated comments in RHReliableDatagram concerning servers, retries, timeouts and delays. - Fixed an error in RHReliableDatagram where recvfrom return value was not checked. - Reported by Steve Childress.
- Added Linux simulator support so simple RadioHead sketches can be compiled and run on Linux.
- Added RH_TCP driver to permit message passing between simulated sketches on Linux.
- Added example simulator sketches.
- Added tools/etherSimulator.pl, a simulator of the 'Luminiferous Ether' that passes - messages between simulated sketches and can simulate random message loss etc.
- Fixed a number of typos and improved some documentation.
-\version 1.10 2014-05-15
- Added support for RFM73 modules to RH_NRF24. These 2 radios are very similar, and can interoperate - with each other. Added new RH_NRF24::TransmitPower enums for the RFM73, which has a different - range of available powers
- reduced the default SPI bus speed for RH_NRF24 to 1MHz, since so many modules and CPU have problems - with 8MHz.
-\version 1.11 2014-05-18
- Testing RH_RF22 with RFM23BP and 3.3V Teensy 3.1 and 5V Arduinos. - Updated documentation with respect to GPIO and antenna - control pins for RFM23. Updated documentation with respect to transmitter power control for RFM23
- Fixed a problem with RH_RF22 driver, where GPIO TX and RX pins were not configured during - initialisation, causing poor transmit power and sensitivity on those RF22/RF23 devices where GPIO controls - the antenna selection pins. -\version 1.12 2014-05-20
- Testing with RF69HW and the RH_RF69 driver. Works well with the Anarduino MiniWireless -CW and -HW - boards http://www.anarduino.com/miniwireless/ including - the marvellous high powered MinWireless-HW (with 20dBm output for excellent range).
- Clarified documentation of RH_RF69::setTxPower values for different models of RF69.
- Added RHReliableDatagram::resetRetransmissions().
- Retransmission count precision increased to uin32_t.
- Added data about actual power measurements from RFM22 module.
-\version 1.13 2014-05-23
- setHeaderFlags(flags) changed to setHeaderFlags(set, clear), enabling any flags to be - individually set and cleared by either RadioHead or application code. Requested by Steve Childress.
- Fixed power output setting for boost power on RF69HW for 18, 19 and 20dBm.
- Added data about actual power measurements from RFM69W and RFM69HW modules.
-\version 1.14 2014-05-26
- RH_RF69::init() now always sets the PA boost back to the default settings, else can get invalid - PA power modes after uploading new sketches without a power cycle. Reported by Bryan.
- Added new macros RH_VERSION_MAJOR RH_VERSION_MINOR, with automatic maintenance in Makefile.
- Improvements to RH_TCP: constructor now honours the server argument in the form "servername:port".
- Added YIELD to RHReliableDatagram::recvfromAckTimeout. Requested by Steve Childress.
- Fixed a problem with RH_RF22 reliable datagram acknowledgements that was introduced in version 1.13. - Reported by Steve Childress.
-\version 1.15 2014-05-27
- Fixed a problem with the RadioHead .zip link. -\version 1.16 2014-05-30
- Fixed RH_RF22 so that lastRssi() returns the signal strength in dBm. Suggested by Steve Childress.
- Added support for getLastPreambleTime() to RH_RF69. Requested by Steve Childress.
- RH_NRF24::init() now checks if there is a device connected and responding, else init() will fail. - Suggested by Steve Brown.
- RHSoftwareSPI now initialises default values for SPI pins MOSI = 12, MISO = 11 and SCK = 13.
- Fixed some problems that prevented RH_NRF24 working with mixed software and hardware SPI - on different devices: a race condition - due to slow SPI transfers and fast acknowledgement.
-\version 1.17 2014-06-02
- Fixed a debug typo in RHReliableDatagram that was introduced in 1.16.
- RH_NRF24 now sets default power, data rate and channel in init(), in case another - app has previously set different values without powerdown.
- Caution: there are still problems with RH_NRF24 and Software SPI. Do not use.
-\version 1.18 2014-06-02
- Improvements to performance of RH_NRF24 statusRead, allowing RH_NRF24 and Software SPI - to operate on slow devices like Arduino Uno.
-\version 1.19 2014-06-19
- Added examples ask_transmitter.pde and ask_receiver.pde.
- Fixed an error in the RH_RF22 doc for connection of Teensy to RF22.
- Improved documentation of start symbol bit patterns in RH_ASK.cpp -\version 1.20 2014-06-24
- Fixed a problem with compiling on platforms such as ATTiny where SS is not defined.
- Added YIELD to RHMesh::recvfromAckTimeout().
-\version 1.21 2014-06-24
- Fixed an issue in RH_Serial where characters might be lost with back-to-back frames. - Suggested by Steve Childress.
- Brought previous RHutil/crc16.h code into mainline RHCRC.cpp to prevent name collisions - with other similarly named code in other libraries. Suggested by Steve Childress.
- Fix SPI bus speed errors on 8MHz Arduinos. -\version 1.22 2014-07-01
- Update RH_ASK documentation for common wiring connections.
- Testing RH_ASK with HopeRF RFM83C/RFM85 courtesy Anarduino http://www.anarduino.com/
- Testing RH_NRF24 with Itead Studio IBoard Pro http://imall.iteadstudio.com/iboard-pro.html - using both hardware SPI on the ITDB02 Parallel LCD Module Interface pins and software SPI - on the nRF24L01+ Module Interface pins. Documented wiring required.
- Added support for AVR 1284 and 1284p, contributed by Peter Scargill. - Added support for Semtech SX1276/77/78 and HopeRF RFM95/96/97/98 and other similar LoRa capable radios - in LoRa mode only. Tested with the excellent MiniWirelessLoRa from - Anarduino http://www.anarduino.com/miniwireless
-\version 1.23 2014-07-03
- Changed the default modulation for RH_RF69 to GFSK_Rb250Fd250, since the previous default - was not very reliable.
- Documented RH_RF95 range tests.
- Improvements to RH_RF22 RSSI readings so that lastRssi correctly returns the last message in dBm.
-\version 1.24 2014-07-18 - Added support for building RadioHead for STM32F4 Discovery boards, using the native STM Firmware libraries, - in order to support Codec2WalkieTalkie (http://www.airspayce.com/mikem/Codec2WalkieTalkie) - and other projects. See STM32ArduinoCompat.
- Default modulation for RH_RF95 was incorrectly set to a very slow Bw125Cr48Sf4096 -\version 1.25 2014-07-25 - The available() function will longer terminate any current transmission, and force receive mode. - Now, if there is no unprocessed incoming message and an outgoing message is currently being transmitted, - available() will return false.
- RHRouter::sendtoWait(uint8_t*, uint8_t, uint8_t, uint8_t) renamed to sendtoFromSourceWait due to conflicts - with new sendtoWait() with optional flags.
- RHMEsh and RHRouter already supported end-to-end application layer flags, but RHMesh::sendtoWait() - and RHRouter::sendToWait have now been extended to expose a way to send optional application layer flags. -\version 1.26 2014-08-12 - Fixed a Teensy 2.0 compile problem due yield() not available on Teensy < 3.0.
- Adjusted the algorithm of RH_RF69::temperatureRead() to more closely reflect reality.
- Added functions to RHGenericDriver to get driver packet statistics: rxBad(), rxGood(), txGood().
- Added RH_RF69::printRegisters().
- RH_RF95::printRegisters() was incorrectly printing the register index instead of the address. - Reported by Phang Moh Lim.
- RH_RF95, added definitions for some more registers that are usable in LoRa mode.
- RH_RF95::setTxPower now uses RH_RF95_PA_DAC_ENABLE to achieve 21, 22 and 23dBm.
- RH_RF95, updated power output measurements.
- Testing RH_RF69 on Teensy 3.1 with RF69 on PJRC breakout board. OK.
- Improvements so RadioHead will build under Arduino where SPI is not supported, such as - ATTiny.
- Improvements so RadioHead will build for ATTiny using Arduino IDE and tinycore arduino-tiny-0100-0018.zip.
- Testing RH_ASK on ATTiny85. Reduced RAM footprint. - Added helpful documentation. Caution: RAM memory is *very* tight on this platform.
- RH_RF22 and RH_RF69, added setIdleMode() function to allow the idle mode radio operating state - to be controlled for lower idle power consumption at the expense of slower transitions to TX and RX.
-\version 1.27 2014-08-13 - All RH_RF69 modulation schemes now have data whitening enabled by default.
- Tested and added a number of OOK modulation schemes to RH_RF69 Modem config table.
- Minor improvements to a number of the faster RH_RF69 modulation schemes, but some slower ones - are still not working correctly.
-\version 1.28 2014-08-20 - Added new RH_RF24 driver to support Si446x, RF24/26/26, RFM24/26/27 family of transceivers. - Tested with the excellent - Anarduino Mini and RFM24W and RFM26W with the generous assistance of the good people at - Anarduino http://www.anarduino.com. -\version 1.29 2014-08-21 - Fixed a compile error in RH_RF24 introduced at the last minute in hte previous release.
- Improvements to RH_RF69 modulation schemes: now include the AFCBW in teh ModemConfig.
- ModemConfig RH_RF69::FSK_Rb2Fd5 and RH_RF69::GFSK_Rb2Fd5 are now working.
-\version 1.30 2014-08-25 - Fixed some compile problems with ATtiny84 on Arduino 1.5.5 reported by Glen Cook.
-\version 1.31 2014-08-27 - Changed RH_RF69 FSK and GFSK modulations from Rb2_4Fd2_4 to Rb2_4Fd4_8 and FSK_Rb4_8Fd4_8 to FSK_Rb4_8Fd9_6 - since the previous ones were unreliable (they had modulation indexes of 1).
-\version 1.32 2014-08-28 - Testing with RedBearLab Blend board http://redbearlab.com/blend/. OK.
- Changed more RH_RF69 FSK and GFSK slowish modulations to have modulation index of 2 instead of 1. - This required chnaging the symbolic names.
-\version 1.33 2014-09-01 - Added support for sleep mode in RHGeneric driver, with new mode - RHModeSleep and new virtual function sleep().
- Added support for sleep to RH_RF69, RH_RF22, RH_NRF24, RH_RF24, RH_RF95 drivers.
-\version 1.34 2014-09-19 - Fixed compile errors in example rf22_router_test.
- Fixed a problem with RH_NRF24::setNetworkAddress, also improvements to RH_NRF24 register printing. - Patched by Yveaux.
- Improvements to RH_NRF24 initialisation for version 2.0 silicon.
- Fixed problem with ambigiguous print call in RH_RFM69 when compiling for Codec2.
- Fixed a problem with RH_NRF24 on RFM73 where the LNA gain was not set properly, reducing the sensitivity - of the receiver. -\version 1.35 2014-09-19 - Fixed a problem with interrupt setup on RH_RF95 with Teensy3.1. Reported by AD.
-\version 1.36 2014-09-22 - Improvements to interrupt pin assignments for __AVR_ATmega1284__ and__AVR_ATmega1284P__, provided by - Peter Scargill.
- Work around a bug in Arduino 1.0.6 where digitalPinToInterrupt is defined but NOT_AN_INTERRUPT is not.
- \version 1.37 2014-10-19 - Updated doc for connecting RH_NRF24 to Arduino Mega.
- Changes to RHGenericDriver::setHeaderFlags(), so that the default for the clear argument - is now RH_FLAGS_APPLICATION_SPECIFIC, which is less surprising to users. - Testing with the excellent MoteinoMEGA from LowPowerLab - https://lowpowerlab.com/shop/moteinomega with on-board RFM69W. - \version 1.38 2014-12-29 - Fixed compile warning on some platforms where RH_RF24::send and RH_RF24::writeTxFifo - did not return a value.
- Fixed some more compiler warnings in RH_RF24 on some platforms.
- Refactored printRegisters for some radios. Printing to Serial - is now controlled by the definition of RH_HAVE_SERIAL.
- Added partial support for ARM M4 w/CMSIS with STM's Hardware Abstraction lib for - Steve Childress.
- \version 1.39 2014-12-30 - Fix some compiler warnings under IAR.
- RH_HAVE_SERIAL and Serial.print calls removed for ATTiny platforms.
- \version 1.40 2015-03-09 - Added notice about availability on PlatformIO, thanks to Ivan Kravets.
- Fixed a problem with RH_NRF24 where short packet lengths would occasionally not be trasmitted - due to a race condition with RH_NRF24_TX_DS. Reported by Mark Fox.
- \version 1.41 2015-03-29 - RH_RF22, RH_RF24, RH_RF69 and RH_RF95 improved to allow driver.init() to be called multiple - times without reallocating a new interrupt, allowing the driver to be reinitialised - after sleeping or powering down. - \version 1.42 2015-05-17 - Added support for RH_NRF24 driver on Raspberry Pi, using BCM2835 - library for GPIO pin IO. Contributed by Mike Poublon.
- Tested RH_NRF24 module with NRF24L01+PA+LNA SMA Antenna Wireless Transceiver modules - similar to: http://www.elecfreaks.com/wiki/index.php?title=2.4G_Wireless_nRF24L01p_with_PA_and_LNA - works with no software changes. Measured max power output 18dBm.
- \version 1.43 2015-08-02 - Added RH_NRF51 driver to support Nordic nRF51 family processor with 2.4GHz radio such - as nRF51822, to be built on Arduino 1.6.4 and later. Tested with RedBearLabs nRF51822 board - and BLE Nano kit
- \version 1.44 2015-08-08 - Fixed errors with compiling on some platforms without serial, such as ATTiny. - Reported by Friedrich Müller.
- \version 1.45 2015-08-13 - Added support for using RH_Serial on Linux and OSX (new class RHutil/HardwareSerial - encapsulates serial ports on those platforms). Example examples/serial upgraded - to build and run on Linux and OSX using the tools/simBuild builder. - RHMesh, RHRouter and RHReliableDatagram updated so they can use RH_Serial without - polling loops on Linux and OSX for CPU efficiency.
- \version 1.46 2015-08-14 - Amplified some doc concerning Linux and OSX RH_Serial. Added support for 230400 - baud rate in HardwareSerial.
- Added sample sketches nrf51_audio_tx and nrf51_audio_rx which show how to - build an audio TX/RX pair with RedBear nRF51822 boards and a SparkFun MCP4725 DAC board. - Uses the built-in ADC of the nRF51822 to sample audio at 5kHz and transmit packets - to the receiver which plays them via the DAC.
-\version 1.47 2015-09-18 - Removed top level Makefile from distribution: its only used by the developer and - its presence confuses some people.
- Fixed a problem with RHReliableDatagram with some versions of Raspberry Pi random() that causes - problems: random(min, max) sometimes exceeds its max limit. -\version 1.48 2015-09-30 - Added support for Arduino Zero. Tested on Arduino Zero Pro. -\version 1.49 2015-10-01 - Fixed problems that prevented interrupts working correctly on Arduino Zero and Due. - Builds and runs with 1.6.5 (with 'Arduino SAMD Boards' for Zero version 1.6.1) from arduino.cc. - Arduino version 1.7.7 from arduino.org is not currently supported. -\version 1.50 2015-10-25 - Verified correct building and operation with Arduino 1.7.7 from arduino.org. - Caution: You must burn the bootloader from 1.7.7 to the Arduino Zero before it will - work with Arduino 1.7.7 from arduino.org. Conversely, you must burn the bootloader from 1.6.5 - to the Arduino Zero before it will - work with Arduino 1.6.5 from arduino.cc. Sigh. - Fixed a problem with RH_NRF905 that prevented the power and frequency ranges being set - properly. Reported by Alan Webber. -\version 1.51 2015-12-11 - Changes to RH_RF6::setTxPower() to be compatible with SX1276/77/78/79 modules that - use RFO transmitter pins instead of PA_BOOST, such as the excellent - Modtronix inAir4 http://modtronix.com/inair4.html - and inAir9 modules http://modtronix.com/inair9.html. With the kind assistance of - David from Modtronix. -\version 1.52 2015-12-17 - Added RH_MRF89 module to suport Microchip MRF89XA and compatible transceivers. - and modules.
-\version 1.53 2016-01-02 - Added RH_CC110 module to support Texas Instruments CC110L and compatible transceivers and modules.
-\version 1.54 2016-01-29 - Added support for ESP8266 processor on Arduino IDE. Examples serial_reliable_datagram_* are shown to work. - CAUTION: SPI not supported yet. Timers used by RH_ASK are not tested. - The GHz radio included in the ESP8266 is not yet supported. -\version 1.55 2016-02-12 - Added macros for htons() and friends to RadioHead.h. - Added example sketch serial_gateway.pde. Acts as a transparent gateway between RH_RF22 and RH_Serial, - and with minor mods acts as a universal gateway between any 2 RadioHead driver networks. - Initial work on supporting STM32 F2 on Particle Photon: new platform type defined. - Fixed many warnings exposed by test building for Photon. - Particle Photon tested support for RH_Serial, RH_ASK, SPI, RH_CC110 etc. - Added notes on how to build RadioHead sketches for Photon. -\version 1.56 2016-02-18 - Implemented timers for RH_ASK on ESP8266, added some doc on IO pin selection. -\version 1.57 2016-02-23 - Fixed an issue reported by S3B, where RH_RF22 would sometimes not clear the rxbufvalid flag. -\version 1.58 2-16-04-04 - Tested RH_RF69 with Arduino Due. OK. Updated doc.
- Added support for all ChipKIT Core supported boards - http://chipkit.net/wiki/index.php?title=ChipKIT_core - Tested on ChipKIT Uno32.
- Digilent Uno32 under the old MPIDE is no longer formally - supported but may continue to work for some time.
-\version 1.59 2016-04-12 - Testing with the excellent Rocket Scream Mini Ultra Pro with the RFM95W and RFM69HCW modules from - http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/ (915MHz versions). Updated - documentation with hints to suit. Caution: requires Arduino 1.6.8 and Arduino SAMD Boards 1.6.5. - See also http://www.rocketscream.com/blog/2016/03/10/radio-range-test-with-rfm69hcw/ - for the vendors tests and range with the RFM69HCW version. They also have an RF95 version equipped with - TCXO temperature controllled oscillator for extra frequency stability and support of very slow and - long range protocols. - These boards are highly recommended. They also include battery charging support. -\version 1.60 2016-06-25 - Tested with the excellent talk2 Whisper Node boards - (https://talk2.wisen.com.au/ and https://bitbucket.org/talk2/), - an Arduino Nano compatible board, which include an on-board RF69 radio, external antenna, - run on 2xAA batteries and support low power operations. RF69 examples work without modification. - Added support for ESP8266 SPI, provided by David Skinner. -\version 1.61 2016-07-07 - Patch to RH_ASK.cpp for ESP8266, to prevent crashes in interrupt handlers. Patch from Alexander Mamchits. -\version 1.62 2016-08-17 - Fixed a problem in RH_ASK where _rxInverted was not properly initialised. Reported by "gno.sun.sop". - Added support for waitCAD() and isChannelActive() and setCADTimeout() to RHGeneric. - Implementation of RH_RF95::isChannelActive() allows the RF95 module to support - Channel Activity Detection (CAD). Based on code contributed by Bent Guldbjerg Christensen. - Implmentations of isChannelActive() plus documentation for other radio modules wil be welcomed. -\version 1.63 2016-10-20 - Testing with Adafruit Feather 32u4 with RFM69HCW. Updated documentation to reflect.
-\version 1.64 2016-12-10 - RHReliableDatagram now initialises _seenids. Fix from Ben Lim.
- In RH_NRF51, added get_temperature().
- In RH_NRF51, added support for AES packet encryption, which required a slight change - to the on-air message format.
-\version 1.65 2017-01-11 - Fixed a race condition with RH_NRF51 that prevented ACKs being reliably received.
- Removed code in RH_NRF51 that enabled the DC-DC converter. This seems not to be a necessary condition - for the radio to work and is now left to the application if that is required.
- Proven interoperation between nRF51822 and nRF52832.
- Modification and testing of RH_NRF51 so it works with nRF52 family processors, - such Sparkfun nRF52832 breakout board, with Arduino 1.6.13 and - Sparkfun nRF52 boards manager 0.2.3 using the procedures outlined in - https://learn.sparkfun.com/tutorials/nrf52832-breakout-board-hookup-guide
- Caution, the Sparkfun development system for Arduino is still immature. We had to - rebuild the nrfutil program since the supplied one was not suitable for - the Linux host we were developing on. See https://forum.sparkfun.com/viewtopic.php?f=32&t=45071 - Also, after downloading a sketch in the nRF52832, the program does not start executing cleanly: - you have to reset the processor again by pressing the reset button. - This appears to be a problem with nrfutil, rather than a bug in RadioHead. -\version 1.66 2017-01-15 - Fixed some errors in (unused) register definitions in RH_RF95.h.
- Fixed a problem that caused compilation errors in RH_NRF51 if the appropriate board - support was not installed. -\version 1.67 2017-01-24 - Added RH_RF95::frequencyError() to return the estimated centre frequency offset in Hz - of the last received message -\version 1.68 2017-01-25 - Fixed arithmetic error in RH_RF95::frequencyError() for some platforms. -\version 1.69 2017-02-02 - Added RH_RF95::lastSNR() and improved lastRssi() calculations per the manual. -\version 1.70 2017-02-03 - Added link to Binpress commercial license purchasing. -\version 1.71 2017-02-07 - Improved support for STM32. Patch from Bent Guldbjerg Christensen. -\version 1.72 2017-03-02 - In RH_RF24, fixed a problem where some important properties were not set by the ModemConfig. - Added properties 2007, 2008, 2009. Also properties 200a was not being set in the chip. - Reported by Shannon Bailey and Alan Adamson. - Fixed corresponding convert.pl and added it to the distribution. -\version 1.73 2017-03-04 - Significant changes to RH_RF24 and its API. It is no longer possible to change the modulation scheme - programatically: it proved impossible to cater for all the possible crystal frequencies, - base frequency and modulation schemes. Instead you can use one of a small set of supplied radio - configuration header files, or generate your own with Silicon Labs WDS application. Changing - modulation scheme required editing RH_RF24.cpp to specify the appropriate header and recompiling. - convert.pl is now redundant and removed from the distribution. -\version 1.74 2017-03-08 - Changed RHReliableDatagram so it would not ACK messages heard addressed to other nodes - in promiscuous mode.
- Added RH_RF24::deviceType() to return the integer value of the connected device.
- Added documentation about how to connect RFM69 to an ESP8266. Tested OK.
- RH_RF24 was not correctly changing state in sleep() and setModeIdle().
- Added example rf24_lowpower_client.pde showing how to put an arduino and radio into a low power - mode between transmissions to save battery power.
- Improvements to RH_RF69::setTxPower so it now takes an optional ishighpowermodule - flag to indicate if the connected module is a high power RFM69HW, and so set the power level - correctly. Based on code contributed by bob. -\version 1.75 2017-06-22 - Fixed broken compiler issues with RH_RF95::frequencyError() reported by Steve Rogerson.
- Testing with the very excellent Rocket Scream boards equipped with RF95 TCXO modules. The - temperature controlled oscillator stabilises the chip enough to be able to use even the slowest - protocol Bw125Cr48Sf4096. Caution, the TCXO model radios are not low power when in sleep (consuming - about ~600 uA, reported by Phang Moh Lim).
- Added support for EBYTE E32-TTL-1W and family serial radio transceivers. These RF95 LoRa based radios - can deliver reliable messages at up to 7km measured. -\version 1.76 2017-06-23 - Fixed a problem with RH_RF95 hanging on transmit under some mysterious circumstances. - Reported by several people at https://forum.pjrc.com/threads/41878-Probable-race-condition-in-Radiohead-library?p=146601#post146601
- Increased the size of rssi variables to 16 bits to permit RSSI less than -128 as reported by RF95. -\version 1.77 2017-06-25 - Fixed a compilation error with lastRssi().
-\version 1.78 2017-07-19 - Fixed a number of unused variable warnings from g++.
- Added new module RHEncryptedDriver and examples, contributed by Philippe Rochat, which - adds encryption and decryption to any RadioHead transport driver, using any encryption cipher - supported by ArduinoLibs Cryptographic Library http://rweather.github.io/arduinolibs/crypto.html - Includes several examples.
-\version 1.79 2017-07-25 - Added documentation about 'Passing Sensor Data Between RadioHead nodes'.
- Changes to RH_CC110 driver to calculate RSSI in dBm, based on a patch from Jurie Pieterse.
- Added missing passthroughmethoids to RHEncryptedDriver, allowing it to be used with RHDatagram, - RHReliableDatagram etc. Tested with RH_Serial. Added examples -\version 1.80 2017-10-04 - Testing with the very fine Talk2 Whisper Node LoRa boards https://wisen.com.au/store/products/whisper-node-lora - an Arduino compatible board, which include an on-board RFM95/96 LoRa Radio (Semtech SX1276), external antenna, - run on 2xAAA batteries and support low power operations. RF95 examples work without modification. - Use Arduino Board Manager to install the Talk2 code support. Upload the code with an FTDI adapter set to 5V.
- Added support for SPI transactions in development environments that support it with SPI_HAS_TRANSACTION. - Tested on ESP32 with RFM-22 and Teensy 3.1 with RF69 - Added support for ESP32, tested with RFM-22 connected by SPI.
-\version 1.81 2017-11-15 - RH_CC110, moved setPaTable() from protected to public.
- RH_RF95 modem config Bw125Cr48Sf4096 altered to enable slow daat rate in register 26 - as suggested by Dieter Kneffel. - Added support for nRF52 compatible Arm chips such as as Adafruit BLE Feather board - https://www.adafruit.com/product/3406, with a patch from Mike Bell.
- Fixed a problem where rev 1.80 broke Adafruit M0 LoRa support by declaring - bitOrder variable always as a unsigned char. Reported by Guilherme Jardim.
- In RH_RF95, all modes now have AGC enabled, as suggested by Dieter Kneffel.
-\version 1.82 2018-01-07 - Added guard code to RH_NRF24::waitPacketSent() so that if the transmit never completes for some - reason, the code will eventually return with FALSE. - Added the low-datarate-optimization bit to config for RH_RF95::Bw125Cr48Sf4096. - Fix from Jurie Pieterse to ensure RH_CC110::sleep always enters sleep mode. - Update ESP32 support to include ASK timers. RH_ASK module is now working on ESP32. -\version 1.83 2018-02-12 - Testing adafruit M0 Feather with E32. Updated RH_E32 documentation to show suggested connections - and contructor initialisation.
- Fixed a problem with RHEncryptedDriver that could cause a crash on some platforms when used - with RHReliableDatagram. Reported by Joachim Baumann.
- Improvments to doxygen doc layout in RadioHead.h -\version 1.84 2018-05-07 - Compiles with Roger Clarkes Arduino_STM32 https://github.com/rogerclarkmelbourne/Arduino_STM32, - to support STM32F103C etc, and STM32 F4 Discovery etc.
- Tested STM32 F4 Discovery board with RH_RF22, RH_ASK and RH_Serial. - -\version 1.85 2018-07-09 - RHGenericDriver methods changed to virtual, to allow overriding by RHEncrypredDriver: - lastRssi(), mode(), setMode(). Reported by Eyal Gal.
- Fixed a problem with compiling RH_E32 on some older IDEs, contributed by Philippe Rochat.
- Improvements to RH_RF95 to improve detection of bad packets, contributed by PiNi.
- Fixed an error in RHEncryptedDriver that caused incorrect message lengths for messages multiples of 16 bytes - when STRICT_CONTENT_LEN is defined.
- Fixed a bug in RHMesh which causes the creation of a route to the address which is the byte - behind the end of the route array. Reported by Pascal Gillès de Pélichy.
-\version 1.86 2018-08-28 - Update commercial licensing, remove binpress. -\version 1.87 2018-10-06 - RH_RF22 now resets all registers to default state before initialisation commences. Suggested by Wothke.
- Added RH_ENABLE_EXPLICIT_RETRY_DEDUP which improves the handling of duplicate detection especiually - in the case where a transmitter periodically wakes up and start tranmitting from the first sequence number. - Patch courtesy Justin Newitter. Thanks. -\version 1.88 2018-11-13 - Updated to support ATTiny using instructions in - https://medium.com/jungletronics/attiny85-easy-flashing-through-arduino-b5f896c48189 - Updated examples ask_transmitter and ask_receiver to compile cleanly on ATTiny. - Tested using ATTiny85 and Arduino 1.8.1.
-\version 1.89 2018-11-15 - Testing with ATTiny core from https://github.com/SpenceKonde/ATTinyCore and RH_ASK, - using example ask_transmitter. This resulted in 'Low Memory, instability may occur', - and the resulting sketch would transmit only one packet. Suggest ATTiny users do not use this core, but use - the one from https://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json - as described in https://medium.com/jungletronics/attiny85-easy-flashing-through-arduino-b5f896c48189
- Added support for RH_RF95::setSpreadingFactor(), RH_RF95::setSignalBandwidth(), RH_RF95::setLowDatarate() and - RH_RF95::setPayloadCRC(). Patch from Brian Norman. Thanks.
- -\version 1.90 2019-05-21 - Fixed a block size error in RhEncryptedDriver for the case when - using STRICT_CONTENT_LEN and sending messages of exactly _blockcipher.blockSize() bytes in length. - Reported and patched by Philippe Rochat. - Patch from Samuel Archibald to prevent compile errors with RH_AAK.cpp fo ATSAMD51. - Fixed a probem in RH_RF69::setSyncWords that prevented setSyncWords(NULL, 0) correctly - disabling sync detection and generation. Reported by Federico Maggi. - RHHardwareSPI::usingInterrupt() was a noop. Fixed to call SPI.usingInterrupt(interrupt);. - -\version 1.91 2019-06-01 - Fixed a problem with new RHHardwareSPI::usingInterrupt() that prevented compilation on ESP8266 - which does not have that call. - -\version 1.92 2019-07-14 - Retested serial_reliable_datagram_client.pde and serial_reliable_datagram_server.pde built on Linux - as described in their headers, and with USB-RS485 adapters. No changes, working correctly. - Testing of nRF5232 with Sparkfun nRF52 board support 0.2.3 shows that there appears to be a problem with - interrupt handlers on this board, and none of the interrupt based radio drivers can be expected to work - with this chip. - Ensured all interrupt routines are flagged with ICACHE_RAM_ATTR when compiled for ESP8266, to prevent crashes. - -\version 1.94 2019-09-02 - Fixed a bug in RHSoftwareSPI where RHGenericSPI::setBitOrder() has no effect for - on RHSoftwareSPI. Reported by Peter.
- Added support in RHRouter for a node to optionally be leaf node, and not participate as a router in the - network. See RHRouter::setNodeTypePatch from Alex Evans.
- Fixed a problem with ESP32 causing compile errors over missing SPI.usingInterrupt().
- -\version 1.95 2019-10-14 - Fixed some typos in RH_RF05.h macro definitions reported by Clayton Smith.
- Patch from Michael Cain from RH_ASK on ESP32, untested by me.
- Added support for RPi Zero and Zero W for the RF95, contributed by Brody Mahoney. - Not tested by me.
- -\version 1.96 2019-10-14 - Added examples for RPi Zero and Zero W to examples/raspi/rf95, contributed by Brody Mahoney - not tested by me.
- -\version 1.97 2019-11-02 - Added support for Mongoose OS, contributed by Paul Austen. - -\version 1.98 2020-01-06 - Rationalised use of RH_PLATFORM_ATTINY to be consistent with other platforms.
- Added support for RH_PLATFORM_ATTINY_MEGA, for use with Spencer Konde's megaTinyCore - https://github.com/SpenceKonde/megaTinyCore on Atmel megaAVR AtTiny chips. - Tested with AtTiny 3217, 3216 and 1614, using - RH_Serial, RH_ASK, and RH_RF22 drivers.
- - -\author Mike McCauley. DO NOT CONTACT THE AUTHOR DIRECTLY. USE THE GOOGLE LIST GIVEN ABOVE -*/ - -/*! \page packingdata -\par Passing Sensor Data Between RadioHead nodes - -People often ask about how to send data (such as numbers, sensor -readings etc) from one RadioHead node to another. Although this issue -is not specific to RadioHead, and more properly lies in the area of -programming for networks, we will try to give some guidance here. - -One reason for the uncertainty and confusion in this area, especially -amongst beginners, is that there is no *best* way to do it. The best -solution for your project may depend on the range of processors and -data that you have to deal with. Also, it gets more difficult if you -need to send several numbers in one packet, and/or deal with floating -point numbers and/or different types of processors. - -The principal cause of difficulty is that different microprocessors of -the kind that run RadioHead may have different ways of representing -binary data such as integers. Some processors are little-endian and -some are big-endian in the way they represent multi-byte integers -(https://en.wikipedia.org/wiki/Endianness). And different processors -and maths libraries may represent floating point numbers in radically -different ways: -(https://en.wikipedia.org/wiki/Floating-point_arithmetic) - -All the RadioHead examples show how to send and receive simple ASCII -strings, and if thats all you want, refer to the examples folder in -your RadioHead distribution. But your needs may be more complicated -than that. - -The essence of all engineering is compromise so it will be up to you to -decide whats best for your particular needs. The main choices are: -- Raw Binary -- Network Order Binary -- ASCII - -\par Raw Binary - -With this technique you just pack the raw binary numbers into the packet: - -\code -// Sending a single 16 bit unsigned integer -// in the transmitter: -... -uint16_t data = getsomevalue(); -if (!driver.send((uint8_t*)&data, sizeof(data))) -{ - ... -\endcode - -\code -// and in the receiver: -... -uint16_t data; -uint8_t datalen = sizeof(data); -if ( driver.recv((uint8_t*)&data, &datalen) - && datalen == sizeof(data)) -{ - // Have the data, so do something with it - uint16_t xyz = data; - ... -\endcode - -If you need to send more than one number at a time, its best to pack -them into a structure - -\code -// Sending several 16 bit unsigned integers in a structure -// in a common header for your project: -typedef struct -{ - uint16_t dataitem1; - uint16_t dataitem2; -} MyDataStruct; -... -\endcode - -\code -// In the transmitter -... -MyDataStruct data; -data.dataitem1 = getsomevalue(); -data.dataitem2 = getsomeothervalue(); -if (!driver.send((uint8_t*)&data, sizeof(data))) -{ - ... -\endcode - -\code -// in the receiver -MyDataStruct data; -uint8_t datalen = sizeof(data); -if ( driver.recv((uint8_t*)&data, &datalen) - && datalen == sizeof(data)) -{ - // Have the data, so do something with it - uint16_t pqr = data.dataitem1; - uint16_t xyz = data.dataitem2; - .... -\endcode - - -The disadvantage with this simple technique becomes apparent if your -transmitter and receiver have different endianness: the integers you -receive will not be the same as the ones you sent (actually they are, -but with the internal bytes swapped around, so they probably wont make -sense to you). Endianness is not a problem if *every* data item you -send is a just single byte (uint8_t or int8_t or char), or if the -transmitter and receiver have the same endianness. - -So you should only adopt this technique if: -- You only send data items of a single byte each, or -- You are absolutely sure (now and forever into the future) that you -will only ever use the same processor endianness in the transmitter and receiver. - -\par Network Order Binary - -One solution to the issue of endianness in your processors is to -always convert your data from the processor's native byte order to -'network byte order' before transmission and then convert it back to -the receiver's native byte order on reception. You do this with the -htons (host to network short) macro and friends. These functions may -be a no-op on big-endian processors. - -With this technique you convert every multi-byte number to and from -network byte order (note that in most Arduino processors an integer is -in fact a short, and is the same as int16_t. We prefer to use types -that explicitly specify their size so we can be sure of applying the -right conversions): - -\code -// Sending a single 16 bit unsigned integer -// in the transmitter: -... -uint16_t data = htons(getsomevalue()); -if (!driver.send((uint8_t*)&data, sizeof(data))) -{ - ... -\endcode -\code -// and in the receiver: -... -uint16_t data; -uint8_t datalen = sizeof(data); -if ( driver.recv((uint8_t*)&data, &datalen) - && datalen == sizeof(data)) -{ - // Have the data, so do something with it - uint16_t xyz = ntohs(data); - ... -\endcode - -If you need to send more than one number at a time, its best to pack -them into a structure - -\code -// Sending several 16 bit unsigned integers in a structure -// in a common header for your project: -typedef struct -{ - uint16_t dataitem1; - uint16_t dataitem2; -} MyDataStruct; -... -\endcode -\code -// In the transmitter -... -MyDataStruct data; -data.dataitem1 = htons(getsomevalue()); -data.dataitem2 = htons(getsomeothervalue()); -if (!driver.send((uint8_t*)&data, sizeof(data))) -{ - ... -\endcode -\code -// in the receiver -MyDataStruct data; -uint8_t datalen = sizeof(data); -if ( driver.recv((uint8_t*)&data, &datalen) - && datalen == sizeof(data)) -{ - // Have the data, so do something with it - uint16_t pqr = ntohs(data.dataitem1); - uint16_t xyz = ntohs(data.dataitem2); - .... -\endcode - -This technique is quite general for integers but may not work if you -want to send floating point number between transmitters and receivers -that have different floating point number representations. - - -\par ASCII - -In this technique, you transmit the printable ASCII equivalent of -each floating point and then convert it back to a float in the receiver: - -\code -// In the transmitter -... -float data = getsomevalue(); -uint8_t buf[15]; // Bigger than the biggest possible ASCII -snprintf(buf, sizeof(buf), "%f", data); -if (!driver.send(buf, strlen(buf) + 1)) // Include the trailing NUL -{ - ... -\endcode -\code - -// In the receiver -... -float data; -uint8_t buf[15]; // Bigger than the biggest possible ASCII -uint8_t buflen = sizeof(buf); -if (driver.recv(buf, &buflen)) -{ - // Have the data, so do something with it - float data = atof(buf); // String to float - ... -\endcode - -\par Conclusion: - -- This is just a basic introduction to the issues. You may need to -extend your study into related C/C++ programming techniques. - -- You can extend these ideas to signed 16 bit (int16_t) and 32 bit -(uint32_t, int32_t) numbers. - -- Things can be simple or complicated depending on the needs of your -project. - -- We are not going to write your code for you: its up to you to take -these examples and explanations and extend them to suit your needs. - -*/ - - - -#ifndef RadioHead_h -#define RadioHead_h - -// Official version numbers are maintained automatically by Makefile: -#define RH_VERSION_MAJOR 1 -#define RH_VERSION_MINOR 98 - -// Symbolic names for currently supported platform types -#define RH_PLATFORM_ARDUINO 1 -#define RH_PLATFORM_MSP430 2 -#define RH_PLATFORM_STM32 3 -#define RH_PLATFORM_GENERIC_AVR8 4 -#define RH_PLATFORM_UNO32 5 -#define RH_PLATFORM_UNIX 6 -#define RH_PLATFORM_STM32STD 7 -#define RH_PLATFORM_STM32F4_HAL 8 -#define RH_PLATFORM_RASPI 9 -#define RH_PLATFORM_NRF51 10 -#define RH_PLATFORM_ESP8266 11 -#define RH_PLATFORM_STM32F2 12 -#define RH_PLATFORM_CHIPKIT_CORE 13 -#define RH_PLATFORM_ESP32 14 -#define RH_PLATFORM_NRF52 15 -#define RH_PLATFORM_MONGOOSE_OS 16 -#define RH_PLATFORM_ATTINY 17 -// Spencer Kondes megaTinyCore: -#define RH_PLATFORM_ATTINY_MEGA 18 - -//////////////////////////////////////////////////// -// Select platform automatically, if possible -#ifndef RH_PLATFORM - #if (defined(MPIDE) && MPIDE>=150 && defined(ARDUINO)) - // Using ChipKIT Core on Arduino IDE - #define RH_PLATFORM RH_PLATFORM_CHIPKIT_CORE - #elif defined(MPIDE) - // Uno32 under old MPIDE, which has been discontinued: - #define RH_PLATFORM RH_PLATFORM_UNO32 - #elif defined(NRF51) - #define RH_PLATFORM RH_PLATFORM_NRF51 - #elif defined(NRF52) - #define RH_PLATFORM RH_PLATFORM_NRF52 - #elif defined(ESP8266) - #define RH_PLATFORM RH_PLATFORM_ESP8266 - #elif defined(ESP32) - #define RH_PLATFORM RH_PLATFORM_ESP32 - #elif defined(MGOS) - #define RH_PLATFORM RH_PLATFORM_MONGOOSE_OS - #elif defined(ARDUINO_attinyxy2) || defined(ARDUINO_attinyxy4) || defined(ARDUINO_attinyxy6) || defined(ARDUINO_attinyxy7) - #define RH_PLATFORM RH_PLATFORM_ATTINY_MEGA - #elif defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtinyX4__) || defined(__AVR_ATtinyX5__) || defined(__AVR_ATtiny2313__) || defined(__AVR_ATtiny4313__) || defined(__AVR_ATtinyX313__) || defined(ARDUINO_attiny) - #define RH_PLATFORM RH_PLATFORM_ATTINY - #elif defined(ARDUINO) - #define RH_PLATFORM RH_PLATFORM_ARDUINO - #elif defined(__MSP430G2452__) || defined(__MSP430G2553__) - #define RH_PLATFORM RH_PLATFORM_MSP430 - #elif defined(MCU_STM32F103RE) - #define RH_PLATFORM RH_PLATFORM_STM32 - #elif defined(STM32F2XX) - #define RH_PLATFORM RH_PLATFORM_STM32F2 - #elif defined(USE_STDPERIPH_DRIVER) - #define RH_PLATFORM RH_PLATFORM_STM32STD - #elif defined(RASPBERRY_PI) - #define RH_PLATFORM RH_PLATFORM_RASPI - #elif defined(__unix__) // Linux - #define RH_PLATFORM RH_PLATFORM_UNIX - #elif defined(__APPLE__) // OSX - #define RH_PLATFORM RH_PLATFORM_UNIX - #else - #error Platform not defined! - #endif -#endif - -//////////////////////////////////////////////////// -// Platform specific headers: -#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) - #if (ARDUINO >= 100) - #include - #else - #include - #endif - #include - #define RH_HAVE_HARDWARE_SPI - #define RH_HAVE_SERIAL - #if defined(ARDUINO_ARCH_STM32F4) - // output to Serial causes hangs on STM32 F4 Discovery board - // There seems to be no way to output text to the USB connection - #define Serial Serial2 - #endif -#elif (RH_PLATFORM == RH_PLATFORM_ATTINY) - #warning Arduino TinyCore does not support hardware SPI. Use software SPI instead. -#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA) - #include - #define RH_HAVE_HARDWARE_SPI - #define RH_HAVE_SERIAL -#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) // ESP8266 processor on Arduino IDE - #include - #include - #define RH_HAVE_HARDWARE_SPI - #define RH_HAVE_SERIAL - #define RH_MISSING_SPIUSINGINTERRUPT - -#elif (RH_PLATFORM == RH_PLATFORM_ESP32) // ESP32 processor on Arduino IDE - #include - #include - #define RH_HAVE_HARDWARE_SPI - #define RH_HAVE_SERIAL - #define RH_MISSING_SPIUSINGINTERRUPT - - #elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) // Mongoose OS platform - #include - #include - #include - #include - #include - #include - #include // We use the floor() math function. - #define RH_HAVE_HARDWARE_SPI - //If a Radio is connected via a serial port then this defines the serial - //port the radio is connected to. - #if defined(RH_SERIAL_PORT) - #if RH_SERIAL_PORT == 0 - #define Serial Serial0 - #elif RH_SERIAL_PORT == 1 - #define Serial Serial1 - #elif RH_SERIAL_PORT == 2 - #define Serial Serial2 - #endif - #else - #warning "RH_SERIAL_PORT not defined. Therefore serial port 0 selected" - #define Serial Serial0 - #endif - #define RH_HAVE_SERIAL - -#elif (RH_PLATFORM == RH_PLATFORM_MSP430) // LaunchPad specific - #include "legacymsp430.h" - #include "Energia.h" - #include - #define RH_HAVE_HARDWARE_SPI - #define RH_HAVE_SERIALg - -#elif (RH_PLATFORM == RH_PLATFORM_UNO32 || RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) - #include - #include - #include - #define RH_HAVE_HARDWARE_SPI - #define memcpy_P memcpy - #define RH_HAVE_SERIAL - -#elif (RH_PLATFORM == RH_PLATFORM_STM32) // Maple, Flymaple etc - #include - #include - #include - #include - #define RH_HAVE_HARDWARE_SPI - // Defines which timer to use on Maple - #define MAPLE_TIMER 1 - #define PROGMEM - #define memcpy_P memcpy - #define Serial SerialUSB - #define RH_HAVE_SERIAL - -#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Particle Photon with firmware-develop - #include - #include - #include // floor - #define RH_HAVE_SERIAL - #define RH_HAVE_HARDWARE_SPI - -#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32 with STM32F4xx_StdPeriph_Driver - #include - #include - #include - #include - #include - #include - #define RH_HAVE_HARDWARE_SPI - #define Serial SerialUSB - #define RH_HAVE_SERIAL - -#elif (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8) - #include - #include - #include - #include - #include - #define RH_HAVE_HARDWARE_SPI - #include - -// For Steve Childress port to ARM M4 w/CMSIS with STM's Hardware Abstraction lib. -// See ArduinoWorkarounds.h (not supplied) -#elif (RH_PLATFORM == RH_PLATFORM_STM32F4_HAL) - #include - #include // Also using ST's CubeMX to generate I/O and CPU setup source code for IAR/EWARM, not GCC ARM. - #include - #include - #include - #define RH_HAVE_HARDWARE_SPI // using HAL (Hardware Abstraction Libraries from ST along with CMSIS, not arduino libs or pins concept. - -#elif (RH_PLATFORM == RH_PLATFORM_RASPI) - #define RH_HAVE_HARDWARE_SPI - #define RH_HAVE_SERIAL - #define PROGMEM - #if (__has_include ()) - #include - #else - #include - #endif - #include - //Define SS for CS0 or pin 24 - #define SS 8 - -#elif (RH_PLATFORM == RH_PLATFORM_NRF51) - #define RH_HAVE_SERIAL - #define PROGMEM - #include - -#elif (RH_PLATFORM == RH_PLATFORM_NRF52) - #include - #define RH_HAVE_HARDWARE_SPI - #define RH_HAVE_SERIAL - #define PROGMEM - #include - -#elif (RH_PLATFORM == RH_PLATFORM_UNIX) - // Simulate the sketch on Linux and OSX - #include - #define RH_HAVE_SERIAL -#include // For htons and friends - -#else - #error Platform unknown! -#endif - -//////////////////////////////////////////////////// -// This is an attempt to make a portable atomic block -#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) -#if defined(__arm__) - #include - #else - #include - #endif - #define ATOMIC_BLOCK_START ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { - #define ATOMIC_BLOCK_END } -#elif (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) - // UsingChipKIT Core on Arduino IDE - #define ATOMIC_BLOCK_START unsigned int __status = disableInterrupts(); { - #define ATOMIC_BLOCK_END } restoreInterrupts(__status); -#elif (RH_PLATFORM == RH_PLATFORM_UNO32) - // Under old MPIDE, which has been discontinued: - #include - #define ATOMIC_BLOCK_START unsigned int __status = INTDisableInterrupts(); { - #define ATOMIC_BLOCK_END } INTRestoreInterrupts(__status); -#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Particle Photon with firmware-develop - #define ATOMIC_BLOCK_START { int __prev = HAL_disable_irq(); - #define ATOMIC_BLOCK_END HAL_enable_irq(__prev); } -#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) -// See hardware/esp8266/2.0.0/cores/esp8266/Arduino.h - #define ATOMIC_BLOCK_START { uint32_t __savedPS = xt_rsil(15); - #define ATOMIC_BLOCK_END xt_wsr_ps(__savedPS);} -#else - // TO BE DONE: - #define ATOMIC_BLOCK_START - #define ATOMIC_BLOCK_END -#endif - -//////////////////////////////////////////////////// -// Try to be compatible with systems that support yield() and multitasking -// instead of spin-loops -// Recent Arduino IDE or Teensy 3 has yield() -#if (RH_PLATFORM == RH_PLATFORM_ARDUINO && ARDUINO >= 155) || (defined(TEENSYDUINO) && defined(__MK20DX128__)) - #define YIELD yield(); -#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) -// ESP8266 also has it - #define YIELD yield(); -#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) - //ESP32 and ESP8266 use freertos so we include calls - //that we would normall exit a function and return to - //the rtos in mgosYield() (E.G flush TX uart buffer - extern "C" { - void mgosYield(void); - } - #define YIELD mgosYield() -#else - #define YIELD -#endif - -//////////////////////////////////////////////////// -// digitalPinToInterrupt is not available prior to Arduino 1.5.6 and 1.0.6 -// See http://arduino.cc/en/Reference/attachInterrupt -#ifndef NOT_AN_INTERRUPT - #define NOT_AN_INTERRUPT -1 -#endif -#ifndef digitalPinToInterrupt - #if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && !defined(__arm__) - - #if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) - // Arduino Mega, Mega ADK, Mega Pro - // 2->0, 3->1, 21->2, 20->3, 19->4, 18->5 - #define digitalPinToInterrupt(p) ((p) == 2 ? 0 : ((p) == 3 ? 1 : ((p) >= 18 && (p) <= 21 ? 23 - (p) : NOT_AN_INTERRUPT))) - - #elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) - // Arduino 1284 and 1284P - See Manicbug and Optiboot - // 10->0, 11->1, 2->2 - #define digitalPinToInterrupt(p) ((p) == 10 ? 0 : ((p) == 11 ? 1 : ((p) == 2 ? 2 : NOT_AN_INTERRUPT))) - - #elif defined(__AVR_ATmega32U4__) - // Leonardo, Yun, Micro, Pro Micro, Flora, Esplora - // 3->0, 2->1, 0->2, 1->3, 7->4 - #define digitalPinToInterrupt(p) ((p) == 0 ? 2 : ((p) == 1 ? 3 : ((p) == 2 ? 1 : ((p) == 3 ? 0 : ((p) == 7 ? 4 : NOT_AN_INTERRUPT))))) - - #else - // All other arduino except Due: - // Serial Arduino, Extreme, NG, BT, Uno, Diecimila, Duemilanove, Nano, Menta, Pro, Mini 04, Fio, LilyPad, Ethernet etc - // 2->0, 3->1 - #define digitalPinToInterrupt(p) ((p) == 2 ? 0 : ((p) == 3 ? 1 : NOT_AN_INTERRUPT)) - - #endif - - #elif (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE) - // Hmmm, this is correct for Uno32, but what about other boards on ChipKIT Core? - #define digitalPinToInterrupt(p) ((p) == 38 ? 0 : ((p) == 2 ? 1 : ((p) == 7 ? 2 : ((p) == 8 ? 3 : ((p) == 735 ? 4 : NOT_AN_INTERRUPT))))) - - #else - // Everything else (including Due and Teensy) interrupt number the same as the interrupt pin number - #define digitalPinToInterrupt(p) (p) - #endif -#endif - -// On some platforms, attachInterrupt() takes a pin number, not an interrupt number -#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_SAM_DUE)) - #define RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER -#endif - -// Slave select pin, some platforms such as ATTiny do not define it. -#ifndef SS - #define SS 10 -#endif - -// Some platforms require specail attributes for interrupt routines -#if (RH_PLATFORM == RH_PLATFORM_ESP8266) - // interrupt handler and related code must be in RAM on ESP8266, - // according to issue #46. - #define RH_INTERRUPT_ATTR ICACHE_RAM_ATTR - -#elif (RH_PLATFORM == RH_PLATFORM_ESP32) - #define RH_INTERRUPT_ATTR IRAM_ATTR -#else - #define RH_INTERRUPT_ATTR -#endif - -// These defs cause trouble on some versions of Arduino -#undef abs -#undef round -#undef double - -// Sigh: there is no widespread adoption of htons and friends in the base code, only in some WiFi headers etc -// that have a lot of excess baggage -#if RH_PLATFORM != RH_PLATFORM_UNIX && !defined(htons) -// #ifndef htons -// These predefined macros available on modern GCC compilers - #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ - // Atmel processors - #define htons(x) ( ((x)<<8) | (((x)>>8)&0xFF) ) - #define ntohs(x) htons(x) - #define htonl(x) ( ((x)<<24 & 0xFF000000UL) | \ - ((x)<< 8 & 0x00FF0000UL) | \ - ((x)>> 8 & 0x0000FF00UL) | \ - ((x)>>24 & 0x000000FFUL) ) - #define ntohl(x) htonl(x) - - #elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - // Others - #define htons(x) (x) - #define ntohs(x) (x) - #define htonl(x) (x) - #define ntohl(x) (x) - - #else - #error "Dont know how to define htons and friends for this processor" - #endif -#endif - -// This is the address that indicates a broadcast -#define RH_BROADCAST_ADDRESS 0xff - -// Uncomment this is to enable Encryption (see RHEncryptedDriver): -// But ensure you have installed the Crypto directory from arduinolibs first: -// http://rweather.github.io/arduinolibs/index.html -//#define RH_ENABLE_ENCRYPTION_MODULE - -#endif diff --git a/src/rf95/RadioInterface.cpp b/src/rf95/RadioInterface.cpp index fa618f1cd..90f865b58 100644 --- a/src/rf95/RadioInterface.cpp +++ b/src/rf95/RadioInterface.cpp @@ -1,4 +1,5 @@ -#include "CustomRF95.h" + +#include "RadioInterface.h" #include "NodeDB.h" #include "assert.h" #include "configuration.h" @@ -46,7 +47,8 @@ size_t RadioInterface::beginSending(MeshPacket *p) // if the sender nodenum is zero, that means uninitialized assert(h->from); - size_t numbytes = pb_encode_to_bytes(radiobuf + sizeof(PacketHeader), sizeof(radiobuf), SubPacket_fields, &p->payload) + sizeof(PacketHeader); + size_t numbytes = pb_encode_to_bytes(radiobuf + sizeof(PacketHeader), sizeof(radiobuf), SubPacket_fields, &p->payload) + + sizeof(PacketHeader); assert(numbytes <= MAX_RHPACKETLEN); diff --git a/src/rf95/RadioInterface.h b/src/rf95/RadioInterface.h index e24f063db..549c280a4 100644 --- a/src/rf95/RadioInterface.h +++ b/src/rf95/RadioInterface.h @@ -4,21 +4,25 @@ #include "MeshTypes.h" #include "PointerQueue.h" #include "mesh.pb.h" -#include #define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission #define MAX_RHPACKETLEN 256 /** - * This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility + * This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility * wtih the old radiohead implementation. */ typedef struct { uint8_t to, from, id, flags; } PacketHeader; - +typedef enum { + Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range +} ModemConfigChoice; /** * Basic operations all radio chipsets must implement. @@ -48,7 +52,7 @@ class RadioInterface public: float freq = 915.0; // FIXME, init all these params from user setings int8_t power = 17; - RH_RF95::ModemConfigChoice modemConfig; + ModemConfigChoice modemConfig; /** pool is the pool we will alloc our rx packets from * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool @@ -81,7 +85,6 @@ class RadioInterface // methods from radiohead - /// Initialise the Driver transport hardware and software. /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. @@ -94,9 +97,10 @@ class RadioInterface protected: /*** - * given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of bytes to send (including the PacketHeader & payload). - * - * Used as the first step of + * given a packet set sendingPacket and decode the protobufs into radiobuf. Returns # of bytes to send (including the + * PacketHeader & payload). + * + * Used as the first step of */ size_t beginSending(MeshPacket *p); }; @@ -112,16 +116,4 @@ class SimRadio : public RadioInterface /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. virtual bool init() { return true; } - - /// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, - /// disables them. - void setModeIdle() {} - - /// If current mode is Tx or Idle, changes it to Rx. - /// Starts the receiver in the RF95/96/97/98. - void setModeRx() {} - - /// Returns the operating mode of the library. - /// \return the current mode, one of RF69_MODE_* - virtual RHGenericDriver::RHMode mode() { return RHGenericDriver::RHModeIdle; } }; diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 6cfb54fee..21dc8271c 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -39,22 +39,22 @@ RadioLibInterface *RadioLibInterface::instance; void RadioLibInterface::applyModemConfig() { switch (modemConfig) { - case RH_RF95::Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range + case Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range bw = 125; cr = 5; sf = 7; break; - case RH_RF95::Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range + case Bw500Cr45Sf128: ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range bw = 500; cr = 5; sf = 7; break; - case RH_RF95::Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range + case Bw31_25Cr48Sf512: ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range bw = 31.25; cr = 8; sf = 9; break; - case RH_RF95::Bw125Cr48Sf4096: + case Bw125Cr48Sf4096: bw = 125; cr = 8; sf = 12; diff --git a/src/rf95/Router.h b/src/rf95/Router.h index 8f2ef6fa1..20378371d 100644 --- a/src/rf95/Router.h +++ b/src/rf95/Router.h @@ -6,7 +6,6 @@ #include "PointerQueue.h" #include "RadioInterface.h" #include "mesh.pb.h" -#include /** * A mesh aware router that supports multiple interfaces. From 5af122b39d89abc0474195f2b60796604e3bc0ac Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 21:49:55 -0700 Subject: [PATCH 126/197] update todo list --- docs/software/nrf52-TODO.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index fb1138c5e..fe4d7a614 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -4,20 +4,10 @@ Minimum items needed to make sure hardware is good. -- DONE select and install a bootloader (adafruit) -- DONE get old radio driver working on NRF52 -- DONE basic test of BLE -- DONE get a debug 'serial' console working via the ICE passthrough feature - add a hard fault handler -- DONE switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib -- change rx95 to radiolib -- track rxbad, rxgood, txgood -- neg 7 error code from receive -- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug -- use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. +- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. keep packet sequence number in **attribute** ((section (".noinit"))) - use "variants" to get all gpio bindings - plug in correct variants for the real board -- remove unused sx1262 lib from github - Use the PMU driver on real hardware - add a NEMA based GPS driver to test GPS - Use new radio driver on real hardware - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ @@ -54,6 +44,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At ## Items to be 'feature complete' +- use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. - turn back on in-radio destaddr checking for RF95 - remove the MeshRadio wrapper - we don't need it anymore, just do everythin in RadioInterface subclasses. - figure out what the correct current limit should be for the sx1262, currently we just use the default 100 @@ -97,6 +88,15 @@ Nice ideas worth considering someday... - DONE add "DFU trigger library" to application load - DONE: using this: Possibly use this bootloader? https://github.com/adafruit/Adafruit_nRF52_Bootloader +- DONE select and install a bootloader (adafruit) +- DONE get old radio driver working on NRF52 +- DONE basic test of BLE +- DONE get a debug 'serial' console working via the ICE passthrough feature +- DONE switch to RadioLab? test it with current radio. https://github.com/jgromes/RadioLib +- DONE change rx95 to radiolib +- DONE track rxbad, rxgood, txgood +- DONE neg 7 error code from receive +- DONE remove unused sx1262 lib from github ``` From 0096f54ae9aa6f29bf0995a99ab0a7af4eeb04dd Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 30 Apr 2020 22:53:21 -0700 Subject: [PATCH 127/197] better debug output --- docs/software/nrf52-TODO.md | 2 +- src/mesh/MeshService.cpp | 3 ++- src/rf95/RF95Interface.cpp | 2 +- src/rf95/RadioLibInterface.cpp | 12 ++++++------ src/rf95/SX1262Interface.cpp | 5 +++-- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index fe4d7a614..323aa1e0e 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -5,7 +5,7 @@ Minimum items needed to make sure hardware is good. - add a hard fault handler -- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. keep packet sequence number in **attribute** ((section (".noinit"))) +- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. - use "variants" to get all gpio bindings - plug in correct variants for the real board - Use the PMU driver on real hardware diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 82d6921e1..6d8457aa0 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -61,7 +61,8 @@ static Periodic sendOwnerPeriod(sendOwnerCb); // FIXME, move this someplace better PacketId generatePacketId() { - static uint32_t i; + static uint32_t i __attribute__(( + section(".noinit"))); // We try to keep this in noinit so that hopefully it keeps increasing even across reboots i++; return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 diff --git a/src/rf95/RF95Interface.cpp b/src/rf95/RF95Interface.cpp index 988a95696..5cd6d6b03 100644 --- a/src/rf95/RF95Interface.cpp +++ b/src/rf95/RF95Interface.cpp @@ -89,8 +89,8 @@ void RF95Interface::setStandby() assert(err == ERR_NONE); isReceiving = false; // If we were receiving, not any more - completeSending(); // If we were sending, not anymore disableInterrupt(); + completeSending(); // If we were sending, not anymore } void RF95Interface::startReceive() diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index 21dc8271c..f067cd9fc 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -101,8 +101,8 @@ bool RadioLibInterface::canSleep() void RadioLibInterface::loop() { - PendingISR wasPending = pending; // atomic read - if (wasPending) { + 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 if (wasPending == ISR_TX) @@ -130,8 +130,8 @@ void RadioLibInterface::startNextWork() void RadioLibInterface::handleTransmitInterrupt() { - DEBUG_MSG("handling lora TX interrupt\n"); - assert(sendingPacket); // Were we sending? + // DEBUG_MSG("handling lora TX interrupt\n"); + assert(sendingPacket); // Were we sending? - FIXME, this was null coming out of light sleep due to RF95 ISR! completeSending(); } @@ -140,6 +140,7 @@ void RadioLibInterface::completeSending() { if (sendingPacket) { txGood++; + DEBUG_MSG("Completed sending to=0x%x, id=%u\n", sendingPacket->to, sendingPacket->id); // We are done sending that packet, release it packetPool.release(sendingPacket); @@ -153,8 +154,6 @@ void RadioLibInterface::handleReceiveInterrupt() assert(isReceiving); isReceiving = false; - DEBUG_MSG("handling lora RX interrupt\n"); - // read the number of actually received bytes size_t length = iface->getPacketLength(); @@ -195,6 +194,7 @@ void RadioLibInterface::handleReceiveInterrupt() // parsing was successful, queue for our recipient mp->has_payload = true; rxGood++; + DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); deliverToReceiver(mp); } diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index af3e4603b..e69074e04 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -75,14 +75,15 @@ void SX1262Interface::setStandby() assert(err == ERR_NONE); isReceiving = false; // If we were receiving, not any more - completeSending(); // If we were sending, not anymore disableInterrupt(); + completeSending(); // If we were sending, not anymore } /** * Add SNR data to received messages */ -void SX1262Interface::addReceiveMetadata(MeshPacket *mp) { +void SX1262Interface::addReceiveMetadata(MeshPacket *mp) +{ mp->rx_snr = lora.getSNR(); } From 49a13bbfd35ef4f0f81957984fa0960c2c79a48e Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 08:31:31 -0700 Subject: [PATCH 128/197] increase gps config timeout, could take up to 2.5 secs --- src/GPS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GPS.cpp b/src/GPS.cpp index 6a2dad509..a6a1e8eef 100644 --- a/src/GPS.cpp +++ b/src/GPS.cpp @@ -74,7 +74,7 @@ void GPS::setup() ok = ublox.powerSaveMode(); // use power save mode assert(ok); } - ok = ublox.saveConfiguration(2000); + ok = ublox.saveConfiguration(3000); assert(ok); } else { // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just From 82c1752d85366e62c162af2320ab8156c1b4540d Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 08:31:52 -0700 Subject: [PATCH 129/197] less logspam --- src/mesh/MeshService.cpp | 2 +- src/rf95/FloodingRouter.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 6d8457aa0..d0e693ada 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -314,7 +314,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) int MeshService::onGPSChanged(void *unused) { - DEBUG_MSG("got gps notify\n"); + // DEBUG_MSG("got gps notify\n"); // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); diff --git a/src/rf95/FloodingRouter.cpp b/src/rf95/FloodingRouter.cpp index 3965dc049..582448da6 100644 --- a/src/rf95/FloodingRouter.cpp +++ b/src/rf95/FloodingRouter.cpp @@ -99,7 +99,7 @@ bool FloodingRouter::wasSeenRecently(const MeshPacket *p) BroadcastRecord &r = recentBroadcasts[i]; if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { - DEBUG_MSG("Deleting old broadcast record %d\n", i); + // DEBUG_MSG("Deleting old broadcast record %d\n", i); recentBroadcasts.erase(recentBroadcasts.begin() + i); // delete old record } else { if (r.id == p->id && r.sender == p->from) { From 31eb2f5337577aa0571da0b71aa37729e10edc21 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 08:32:16 -0700 Subject: [PATCH 130/197] very important: don't allow immediate sends if we have pending ISRs --- src/rf95/RF95Interface.cpp | 13 ++----------- src/rf95/RF95Interface.h | 6 +++--- src/rf95/RadioLibInterface.cpp | 16 ++++++++++++++++ src/rf95/RadioLibInterface.h | 5 ++++- src/rf95/SX1262Interface.cpp | 13 ++----------- src/rf95/SX1262Interface.h | 5 +++-- 6 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/rf95/RF95Interface.cpp b/src/rf95/RF95Interface.cpp index 5cd6d6b03..f8f64c9a5 100644 --- a/src/rf95/RF95Interface.cpp +++ b/src/rf95/RF95Interface.cpp @@ -106,18 +106,9 @@ void RF95Interface::startReceive() } /** Could we send right now (i.e. either not actively receving or transmitting)? */ -bool RF95Interface::canSendImmediately() +bool RF95Interface::isActivelyReceiving() { - // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). - // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, - // we almost certainly guarantee no one outside will like the packet we are sending. - bool busyTx = sendingPacket != NULL; - bool busyRx = isReceiving && lora->isReceiving(); - - if (busyTx || busyRx) - DEBUG_MSG("Can not set now, busyTx=%d, busyRx=%d\n", busyTx, busyRx); - - return !busyTx && !busyRx; + return lora->isReceiving(); } bool RF95Interface::sleep() diff --git a/src/rf95/RF95Interface.h b/src/rf95/RF95Interface.h index 911e81a01..d0b5fd7f2 100644 --- a/src/rf95/RF95Interface.h +++ b/src/rf95/RF95Interface.h @@ -38,9 +38,9 @@ class RF95Interface : public RadioLibInterface */ virtual void enableInterrupt(void (*callback)()) { lora->setDio0Action(callback); } - /** Could we send right now (i.e. either not actively receiving or transmitting)? */ - virtual bool canSendImmediately(); - + /** are we actively receiving a packet (only called during receiving state) */ + virtual bool isActivelyReceiving(); + /** * Start waiting to receive a message */ diff --git a/src/rf95/RadioLibInterface.cpp b/src/rf95/RadioLibInterface.cpp index f067cd9fc..3653e29f9 100644 --- a/src/rf95/RadioLibInterface.cpp +++ b/src/rf95/RadioLibInterface.cpp @@ -64,6 +64,22 @@ void RadioLibInterface::applyModemConfig() } } +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +bool RadioLibInterface::canSendImmediately() +{ + // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). + // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, + // we almost certainly guarantee no one outside will like the packet we are sending. + PendingISR isPending = pending; + bool busyTx = sendingPacket != NULL; + bool busyRx = isReceiving && isActivelyReceiving(); + + if (busyTx || busyRx || isPending) + DEBUG_MSG("Can not send yet, busyTx=%d, busyRx=%d, intPend=%d\n", busyTx, busyRx, isPending); + + return !busyTx && !busyRx && !isPending; +} + /// Send a packet (possibly by enquing in a private fifo). This routine will /// later free() the packet to pool. This routine is not allowed to stall because it is called from /// bluetooth comms code. If the txmit queue is empty it might return an error diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index a71a0a00e..bf5e20f7c 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -101,7 +101,10 @@ class RadioLibInterface : public RadioInterface void applyModemConfig(); /** Could we send right now (i.e. either not actively receiving or transmitting)? */ - virtual bool canSendImmediately() = 0; + virtual bool canSendImmediately(); + + /** are we actively receiving a packet (only called during receiving state) */ + virtual bool isActivelyReceiving() = 0; /** * Start waiting to receive a message diff --git a/src/rf95/SX1262Interface.cpp b/src/rf95/SX1262Interface.cpp index e69074e04..d2842956c 100644 --- a/src/rf95/SX1262Interface.cpp +++ b/src/rf95/SX1262Interface.cpp @@ -100,18 +100,9 @@ void SX1262Interface::startReceive() } /** Could we send right now (i.e. either not actively receving or transmitting)? */ -bool SX1262Interface::canSendImmediately() +bool SX1262Interface::isActivelyReceiving() { - // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). - // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, - // we almost certainly guarantee no one outside will like the packet we are sending. - bool busyTx = sendingPacket != NULL; - bool busyRx = isReceiving && lora.getPacketLength() > 0; - - if (busyTx || busyRx) - DEBUG_MSG("Can not set now, busyTx=%d, busyRx=%d\n", busyTx, busyRx); - - return !busyTx && !busyRx; + return lora.getPacketLength() > 0; } bool SX1262Interface::sleep() diff --git a/src/rf95/SX1262Interface.h b/src/rf95/SX1262Interface.h index 39c6e7c09..18ab8ef75 100644 --- a/src/rf95/SX1262Interface.h +++ b/src/rf95/SX1262Interface.h @@ -36,8 +36,8 @@ class SX1262Interface : public RadioLibInterface */ virtual void enableInterrupt(void (*callback)()) { lora.setDio1Action(callback); } - /** Could we send right now (i.e. either not actively receiving or transmitting)? */ - virtual bool canSendImmediately(); + /** are we actively receiving a packet (only called during receiving state) */ + virtual bool isActivelyReceiving(); /** * Start waiting to receive a message @@ -47,6 +47,7 @@ class SX1262Interface : public RadioLibInterface * Add SNR data to received messages */ virtual void addReceiveMetadata(MeshPacket *mp); + private: void setStandby(); }; \ No newline at end of file From 5a4fab25066e77dec48bb8cac11799edce8be67f Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 08:51:53 -0700 Subject: [PATCH 131/197] start msg sequence numbers with a random number each boot --- docs/software/nrf52-TODO.md | 3 ++- src/esp32/main-esp32.cpp | 2 ++ src/mesh/MeshService.cpp | 10 ++++++++-- src/nrf52/main-nrf52.cpp | 1 + src/rf95/RadioLibInterface.h | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 323aa1e0e..17cf83652 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -5,7 +5,6 @@ Minimum items needed to make sure hardware is good. - add a hard fault handler -- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. - use "variants" to get all gpio bindings - plug in correct variants for the real board - Use the PMU driver on real hardware @@ -57,6 +56,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - currently using soft device SD140, is that ideal? - turn on the watchdog timer, require servicing from key application threads - install a hardfault handler for null ptrs (if one isn't already installed) +- nrf52setup should call randomSeed(tbd) ## Things to do 'someday' @@ -97,6 +97,7 @@ Nice ideas worth considering someday... - DONE track rxbad, rxgood, txgood - DONE neg 7 error code from receive - DONE remove unused sx1262 lib from github +- at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. ``` diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index b15d60732..48af9b998 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -164,6 +164,8 @@ void axp192Init() void esp32Setup() { + randomSeed(esp_random()); // ESP docs say this is fairly random + #ifdef AXP192_SLAVE_ADDRESS axp192Init(); #endif diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index d0e693ada..873d17eca 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -61,8 +61,14 @@ static Periodic sendOwnerPeriod(sendOwnerCb); // FIXME, move this someplace better PacketId generatePacketId() { - static uint32_t i __attribute__(( - section(".noinit"))); // We try to keep this in noinit so that hopefully it keeps increasing even across reboots + static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots + static bool didInit = false; + + if (!didInit) { + didInit = true; + i = random(0, NUM_PACKET_ID + + 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) + } i++; return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 diff --git a/src/nrf52/main-nrf52.cpp b/src/nrf52/main-nrf52.cpp index 255b80101..0fe2c8d96 100644 --- a/src/nrf52/main-nrf52.cpp +++ b/src/nrf52/main-nrf52.cpp @@ -67,4 +67,5 @@ void nrf52Setup() { // Not yet on board // pmu.init(); + DEBUG_MSG("FIXME, need to call randomSeed on nrf52!\n"); } \ No newline at end of file diff --git a/src/rf95/RadioLibInterface.h b/src/rf95/RadioLibInterface.h index bf5e20f7c..1e42aedb0 100644 --- a/src/rf95/RadioLibInterface.h +++ b/src/rf95/RadioLibInterface.h @@ -101,7 +101,7 @@ class RadioLibInterface : public RadioInterface void applyModemConfig(); /** Could we send right now (i.e. either not actively receiving or transmitting)? */ - virtual bool canSendImmediately(); + bool canSendImmediately(); /** are we actively receiving a packet (only called during receiving state) */ virtual bool isActivelyReceiving() = 0; From 71fcdba017db8bb36271c2268426f4d7d81afa15 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 09:04:00 -0700 Subject: [PATCH 132/197] cleanup directory structure --- platformio.ini | 2 +- src/{rf95 => mesh}/FloodingRouter.cpp | 0 src/{rf95 => mesh}/FloodingRouter.h | 0 src/{rf95 => mesh}/MemoryPool.h | 0 src/{rf95 => mesh}/PointerQueue.h | 0 src/{rf95 => mesh}/RF95Interface.cpp | 0 src/{rf95 => mesh}/RF95Interface.h | 0 src/{rf95 => mesh}/RadioInterface.cpp | 0 src/{rf95 => mesh}/RadioInterface.h | 0 src/{rf95 => mesh}/RadioLibInterface.cpp | 0 src/{rf95 => mesh}/RadioLibInterface.h | 0 src/{rf95 => mesh}/RadioLibRF95.cpp | 0 src/{rf95 => mesh}/RadioLibRF95.h | 0 src/{rf95 => mesh}/Router.cpp | 0 src/{rf95 => mesh}/Router.h | 0 src/{rf95 => mesh}/SX1262Interface.cpp | 0 src/{rf95 => mesh}/SX1262Interface.h | 0 src/{rf95 => mesh}/TypedQueue.h | 0 src/rf95/LICENSE | 17 ----------------- src/rf95/README.md | 4 ---- src/rf95/kh-todo.txt | 22 ---------------------- 21 files changed, 1 insertion(+), 44 deletions(-) rename src/{rf95 => mesh}/FloodingRouter.cpp (100%) rename src/{rf95 => mesh}/FloodingRouter.h (100%) rename src/{rf95 => mesh}/MemoryPool.h (100%) rename src/{rf95 => mesh}/PointerQueue.h (100%) rename src/{rf95 => mesh}/RF95Interface.cpp (100%) rename src/{rf95 => mesh}/RF95Interface.h (100%) rename src/{rf95 => mesh}/RadioInterface.cpp (100%) rename src/{rf95 => mesh}/RadioInterface.h (100%) rename src/{rf95 => mesh}/RadioLibInterface.cpp (100%) rename src/{rf95 => mesh}/RadioLibInterface.h (100%) rename src/{rf95 => mesh}/RadioLibRF95.cpp (100%) rename src/{rf95 => mesh}/RadioLibRF95.h (100%) rename src/{rf95 => mesh}/Router.cpp (100%) rename src/{rf95 => mesh}/Router.h (100%) rename src/{rf95 => mesh}/SX1262Interface.cpp (100%) rename src/{rf95 => mesh}/SX1262Interface.h (100%) rename src/{rf95 => mesh}/TypedQueue.h (100%) delete mode 100644 src/rf95/LICENSE delete mode 100644 src/rf95/README.md delete mode 100644 src/rf95/kh-todo.txt 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/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 100% rename from src/rf95/RF95Interface.cpp rename to src/mesh/RF95Interface.cpp 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 100% rename from src/rf95/RadioInterface.cpp rename to src/mesh/RadioInterface.cpp diff --git a/src/rf95/RadioInterface.h b/src/mesh/RadioInterface.h similarity index 100% rename from src/rf95/RadioInterface.h rename to src/mesh/RadioInterface.h diff --git a/src/rf95/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp similarity index 100% rename from src/rf95/RadioLibInterface.cpp rename to src/mesh/RadioLibInterface.cpp diff --git a/src/rf95/RadioLibInterface.h b/src/mesh/RadioLibInterface.h similarity index 100% rename from src/rf95/RadioLibInterface.h rename to src/mesh/RadioLibInterface.h 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 100% rename from src/rf95/Router.cpp rename to src/mesh/Router.cpp 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 100% rename from src/rf95/SX1262Interface.cpp rename to src/mesh/SX1262Interface.cpp 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) From 50213d8323a5443266164ddf5704d5cbf17f6076 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 12:11:04 -0700 Subject: [PATCH 133/197] move packet handling into its own thread --- docs/software/nrf52-TODO.md | 1 + src/WorkerThread.cpp | 44 +++++++++++++++++++ src/WorkerThread.h | 77 ++++++++++++++++++++++++++++++++++ src/mesh/RF95Interface.cpp | 4 +- src/mesh/RadioInterface.cpp | 9 ++++ src/mesh/RadioInterface.h | 9 ++-- src/mesh/RadioLibInterface.cpp | 47 +++++++++++++++------ src/mesh/RadioLibInterface.h | 9 ++-- src/mesh/Router.cpp | 3 -- src/mesh/SX1262Interface.cpp | 2 + 10 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 src/WorkerThread.cpp create mode 100644 src/WorkerThread.h diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 17cf83652..0dbe347a7 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -62,6 +62,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- 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/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/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index f8f64c9a5..c335205b7 100644 --- a/src/mesh/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/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 90f865b58..0a360909d 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -7,11 +7,20 @@ #include #include +#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/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 549c280a4..cdfae79d9 100644 --- a/src/mesh/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/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 3653e29f9..f6fb2de3d 100644 --- a/src/mesh/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/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 1e42aedb0..789df68ab 100644 --- a/src/mesh/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/mesh/Router.cpp b/src/mesh/Router.cpp index dff4b7795..ecc8c8f40 100644 --- a/src/mesh/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/mesh/SX1262Interface.cpp b/src/mesh/SX1262Interface.cpp index d2842956c..ad305a4f1 100644 --- a/src/mesh/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? From e084699704a80418f4c93c8e1f73bc7b234a7576 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 12:31:36 -0700 Subject: [PATCH 134/197] SNR is now a float, fix the screen display --- docs/software/nrf52-TODO.md | 1 + src/mesh/RadioInterface.cpp | 1 + src/screen.cpp | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 0dbe347a7..5d9c75775 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -62,6 +62,7 @@ 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 diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 0a360909d..6e98a3641 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -7,6 +7,7 @@ #include #include +// 1kb was too small #define RADIO_STACK_SIZE 4096 RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE) 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]; From 4176d79ee950074aeab01f2546a641e1776d44b9 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 16:33:26 -0700 Subject: [PATCH 135/197] fix warnings --- src/screen.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/screen.cpp b/src/screen.cpp index 54c57457b..a390520c4 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -21,7 +21,6 @@ along with this program. If not, see . */ #include -#include #include "GPS.h" #include "NodeDB.h" @@ -55,7 +54,6 @@ static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int1 // draw an xbm image. // Please note that everything that should be transitioned // needs to be drawn relative to x and y - display->drawXbm(x + 32, y, icon_width, icon_height, (const uint8_t *)icon_bits); display->setFont(ArialMT_Plain_16); @@ -320,11 +318,11 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ uint32_t agoSecs = sinceLastSeen(node); static char lastStr[20]; if (agoSecs < 120) // last 2 mins? - snprintf(lastStr, sizeof(lastStr), "%lu seconds ago", agoSecs); + snprintf(lastStr, sizeof(lastStr), "%u seconds ago", agoSecs); else if (agoSecs < 120 * 60) // last 2 hrs - snprintf(lastStr, sizeof(lastStr), "%lu minutes ago", agoSecs / 60); + snprintf(lastStr, sizeof(lastStr), "%u minutes ago", agoSecs / 60); else - snprintf(lastStr, sizeof(lastStr), "%lu hours ago", agoSecs / 60 / 60); + snprintf(lastStr, sizeof(lastStr), "%u hours ago", agoSecs / 60 / 60); static float simRadian; simRadian += 0.1; // For testing, have the compass spin unless both @@ -412,6 +410,7 @@ void Screen::handleSetOn(bool on) if (on) { DEBUG_MSG("Turning on screen\n"); dispdev.displayOn(); + dispdev.displayOn(); } else { DEBUG_MSG("Turning off screen\n"); dispdev.displayOff(); @@ -594,7 +593,7 @@ void Screen::handleStartBluetoothPinScreen(uint32_t pin) static FrameCallback btFrames[] = {drawFrameBluetooth}; - snprintf(btPIN, sizeof(btPIN), "%06lu", pin); + snprintf(btPIN, sizeof(btPIN), "%06u", pin); ui.disableAllIndicators(); ui.setFrames(btFrames, 1); From 4f7a25f5622716bdafa722bad36ecc853175b3d7 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 16:33:40 -0700 Subject: [PATCH 136/197] remove unneeded include --- src/sleep.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sleep.cpp b/src/sleep.cpp index 39710ad4c..9693a8fc6 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -8,7 +8,6 @@ #include "main.h" #include "target_specific.h" -#include #ifndef NO_ESP32 #include "esp32/pm.h" From cfd6483ea5f61e6e2e7f09c2fbed411d232dd153 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 16:34:16 -0700 Subject: [PATCH 137/197] oops - platform IO can have stale target specific builds without this --- bin/build-all.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/build-all.sh b/bin/build-all.sh index 2b31a3061..44a7d875c 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -39,6 +39,9 @@ function do_build { cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf } +# Important to pull latest version of libs into all device flavors, otherwise some devices might be stale +platformio lib update + for COUNTRY in $COUNTRIES; do for BOARD in $BOARDS; do do_build $BOARD From 4735b3ff5be2da0ef5d775728fd8ae0b3b73e739 Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 1 May 2020 16:35:32 -0700 Subject: [PATCH 138/197] 0.6.1 hotfix build for busted heltec style devices --- bin/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/version.sh b/bin/version.sh index f0cbb5657..6b0158eb3 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.6.0 \ No newline at end of file +export VERSION=0.6.1 \ No newline at end of file From 2ad314f15041df708c88e8c3b22d1f1cbf8cee56 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 2 May 2020 08:29:51 -0700 Subject: [PATCH 139/197] we now always listen before transmit - even if we have just completed a packet --- src/OSTimer.h | 15 +++++ src/WorkerThread.cpp | 5 +- src/WorkerThread.h | 7 ++ src/mesh/RadioLibInterface.cpp | 120 +++++++++++++++++++++------------ src/mesh/RadioLibInterface.h | 19 ++++-- src/mesh/Router.cpp | 2 +- 6 files changed, 115 insertions(+), 53 deletions(-) create mode 100644 src/OSTimer.h diff --git a/src/OSTimer.h b/src/OSTimer.h new file mode 100644 index 000000000..73a40ddb3 --- /dev/null +++ b/src/OSTimer.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +typedef void (*PendableFunction)(void *pvParameter1, uint32_t ulParameter2); + +/** + * Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR) + * + * @return true if successful, false if the timer fifo is too full. + */ +inline bool scheduleCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) +{ + return xTimerPendFunctionCall(callback, param1, param2, pdMS_TO_TICKS(delayMsec)); +} \ No newline at end of file diff --git a/src/WorkerThread.cpp b/src/WorkerThread.cpp index 0efe79429..ed1103911 100644 --- a/src/WorkerThread.cpp +++ b/src/WorkerThread.cpp @@ -38,7 +38,6 @@ void NotifiedWorkerThread::notifyFromISR(BaseType_t *highPriWoken, uint32_t v, e void NotifiedWorkerThread::block() { - xTaskNotifyWait(0, // don't clear notification on entry - 0, // do not reset notification value on read - ¬ification, portMAX_DELAY); // Wait forever + xTaskNotifyWait(0, // don't clear notification on entry + clearOnRead, ¬ification, portMAX_DELAY); // Wait forever } diff --git a/src/WorkerThread.h b/src/WorkerThread.h index 50d87b965..318f9d803 100644 --- a/src/WorkerThread.h +++ b/src/WorkerThread.h @@ -70,6 +70,13 @@ class NotifiedWorkerThread : public WorkerThread */ uint32_t notification = 0; + /** + * What notification bits should be cleared just after we read and return them in notification? + * + * Defaults to clear all of them. + */ + uint32_t clearOnRead = ULONG_MAX; + /** * A method that should block execution - either waiting ona queue/mutex or a "task notification" */ diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index f6fb2de3d..48a5e8a4d 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -24,13 +24,13 @@ RadioLibInterface::RadioLibInterface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) #endif -void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0() +void INTERRUPT_ATTR RadioLibInterface::isrLevel0Common(PendingISR cause) { instance->disableInterrupt(); - instance->pending = ISR_RX; + instance->pending = cause; BaseType_t xHigherPriorityTaskWoken; - instance->notifyFromISR(&xHigherPriorityTaskWoken); + instance->notifyFromISR(&xHigherPriorityTaskWoken, cause, eSetValueWithOverwrite); /* 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 @@ -38,18 +38,14 @@ void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0() YIELD_FROM_ISR(xHigherPriorityTaskWoken); } +void INTERRUPT_ATTR RadioLibInterface::isrRxLevel0() +{ + isrLevel0Common(ISR_RX); +} + void INTERRUPT_ATTR RadioLibInterface::isrTxLevel0() { - 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); + isrLevel0Common(ISR_TX); } /** Our ISR code currently needs this to find our active instance @@ -108,25 +104,18 @@ bool RadioLibInterface::canSendImmediately() /// bluetooth comms code. If the txmit queue is empty it might return an error ErrorCode RadioLibInterface::send(MeshPacket *p) { - // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). - // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, - // we almost certainly guarantee no one outside will like the packet we are sending. - if (canSendImmediately()) { - // if the radio is idle, we can send right away - DEBUG_MSG("immediate send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, - txGood, rxGood, rxBad); - - startSend(p); - return ERRNO_OK; - } else { - DEBUG_MSG("enqueuing packet for send from=0x%x, to=0x%x\n", p->from, p->to); - ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; - - if (res != ERRNO_OK) // we weren't able to queue it, so we must drop it to prevent leaks - packetPool.release(p); + DEBUG_MSG("enqueuing for send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, + txGood, rxGood, rxBad); + ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; + if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks + packetPool.release(p); return res; } + + startTransmitTimer(false); // We want all sending/receiving to be done by our daemon thread + + return res; } bool RadioLibInterface::canSleep() @@ -138,30 +127,75 @@ bool RadioLibInterface::canSleep() return res; } +/** radio helper thread callback. + +We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and +wait a random delay of 50 to 200 ms to make sure we are not stomping on someone else. The 50ms delay at the beginning ensures all +possible listeners have had time to finish processing the previous packet and now have their radio in RX state. The up to 200ms +random delay gives a chance for all possible senders to have high odds of detecting that someone else started transmitting first +and then they will wait until that packet finishes. + +NOTE: the large flood rebroadcast delay might still be needed even with this approach. Because we might not be able to hear other +transmitters that we are potentially stomping on. Requires further thought. + +FIXME, the 50ms and 200ms values should be tuned via logic analyzer later. +*/ void RadioLibInterface::loop() { - PendingISR wasPending = pending; pending = ISR_NONE; - if (wasPending == ISR_TX) + switch (notification) { + case ISR_TX: handleTransmitInterrupt(); - else if (wasPending == ISR_RX) + startReceive(); + startTransmitTimer(); + break; + case ISR_RX: handleReceiveInterrupt(); - else - assert(0); // We expected to receive a valid notification from the ISR + startReceive(); + startTransmitTimer(); + break; + case TRANSMIT_DELAY_COMPLETED: + // If we are not currently in receive mode, then restart the timer and try again later (this can happen if the main thread + // has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode? + if (!txQueue.isEmpty()) { + if (!canSendImmediately()) { + startTransmitTimer(); // try again in a little while + } else { + DEBUG_MSG("Transmit timer completed!\n"); - startNextWork(); + // Send any outgoing packets we have ready + MeshPacket *txp = txQueue.dequeuePtr(0); + assert(txp); + startSend(txp); + } + } + break; + default: + assert(0); // We expected to receive a valid notification from the ISR + } } -void RadioLibInterface::startNextWork() +#include "OSTimer.h" + +void RadioLibInterface::timerCallback(void *p1, uint32_t p2) { - // First send any outgoing packets we have ready - MeshPacket *txp = txQueue.dequeuePtr(0); - if (txp) - startSend(txp); - else { - // Nothing to send, let's switch back to receive mode - startReceive(); + RadioLibInterface *t = (RadioLibInterface *)p1; + + t->timerRunning = false; + + // We use without overwrite, so that if there is already an interrupt pending to be handled, that gets handle properly (the + // ISR handler will restart our timer) + t->notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite); +} + +void RadioLibInterface::startTransmitTimer(bool withDelay) +{ + // If we have work to do and the timer wasn't already scheduled, schedule it now + if (!timerRunning && !txQueue.isEmpty()) { + timerRunning = true; + uint32_t delay = withDelay ? 0 : random(50, 200); // See documentation for loop() wrt these values + scheduleCallback(timerCallback, this, 0, delay); } } diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index 789df68ab..c6f595c80 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -14,9 +14,10 @@ class RadioLibInterface : public RadioInterface { /// Used as our notification from the ISR - enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX }; + enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED }; volatile PendingISR pending = ISR_NONE; + volatile bool timerRunning = false; /** Our ISR code currently needs this to find our active instance */ @@ -25,7 +26,7 @@ class RadioLibInterface : public RadioInterface /** * Raw ISR handler that just calls our polymorphic method */ - static void isrTxLevel0(); + static void isrTxLevel0(), isrLevel0Common(PendingISR code); /** * Debugging counts @@ -43,8 +44,8 @@ class RadioLibInterface : public RadioInterface */ uint8_t syncWord = SX126X_SYNC_WORD_PRIVATE; - float currentLimit = 100; // FIXME - uint16_t preambleLength = 8; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving + float currentLimit = 100; // FIXME + uint16_t preambleLength = 32; // 8 is default, but FIXME use longer to increase the amount of sleep time when receiving Module module; // The HW interface to the radio @@ -83,12 +84,18 @@ class RadioLibInterface : public RadioInterface /** start an immediate transmit */ void startSend(MeshPacket *txp); - /** start a queued transmit (if we have one), else start receiving */ - void startNextWork(); + /** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing + * the transmit + * + * If the timer was already running, we just wait for that one to occur. + * */ + void startTransmitTimer(bool withDelay = true); void handleTransmitInterrupt(); void handleReceiveInterrupt(); + static void timerCallback(void *p1, uint32_t p2); + protected: /** * Convert our modemConfig enum into wf, sf, etc... diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index ecc8c8f40..4790ff17e 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -49,7 +49,7 @@ void Router::loop() ErrorCode Router::send(MeshPacket *p) { if (iface) { - DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); return iface->send(p); } else { DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); From bb9f595b8b495ed7bf6f87f0cc68adca02a1852f Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 2 May 2020 19:51:25 -0700 Subject: [PATCH 140/197] Fix #11 --- src/OSTimer.cpp | 49 +++++++++++++++++++++++ src/OSTimer.h | 11 ++++-- src/SerialConsole.cpp | 4 +- src/WorkerThread.h | 2 +- src/mesh/RF95Interface.h | 6 +-- src/mesh/RadioInterface.cpp | 3 +- src/mesh/RadioLibInterface.cpp | 71 ++++++++++++++++++++++++++-------- src/mesh/RadioLibInterface.h | 8 ++-- src/mesh/RadioLibRF95.cpp | 9 ++++- src/mesh/RadioLibRF95.h | 3 ++ src/mesh/SX1262Interface.h | 4 +- 11 files changed, 137 insertions(+), 33 deletions(-) create mode 100644 src/OSTimer.cpp diff --git a/src/OSTimer.cpp b/src/OSTimer.cpp new file mode 100644 index 000000000..0da3b7d28 --- /dev/null +++ b/src/OSTimer.cpp @@ -0,0 +1,49 @@ +#include "OSTimer.h" +#include "configuration.h" + +#ifdef NO_ESP32 + +/** + * Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR) + * + * NOTE! xTimerPend... seems to ignore the time passed in on ESP32 - I haven't checked on NRF52 + * + * @return true if successful, false if the timer fifo is too full. + */ +bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) +{ + return xTimerPendFunctionCall(callback, param1, param2, pdMS_TO_TICKS(delayMsec)); +} + +#else + +// Super skanky quick hack to use hardware timers of the ESP32 +static hw_timer_t *timer; +static PendableFunction tCallback; +static void *tParam1; +static uint32_t tParam2; + +static void IRAM_ATTR onTimer() +{ + (*tCallback)(tParam1, tParam2); +} + +bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) +{ + if (!timer) { + timer = timerBegin(0, 80, true); // one usec per tick (main clock is 80MhZ on ESP32) + assert(timer); + timerAttachInterrupt(timer, &onTimer, true); + } + + tCallback = callback; + tParam1 = param1; + tParam2 = param2; + + timerAlarmWrite(timer, delayMsec * 1000L, false); // Do not reload, we want it to be a single shot timer + timerRestart(timer); + timerAlarmEnable(timer); + return true; +} + +#endif \ No newline at end of file diff --git a/src/OSTimer.h b/src/OSTimer.h index 73a40ddb3..cdf2386a6 100644 --- a/src/OSTimer.h +++ b/src/OSTimer.h @@ -7,9 +7,12 @@ typedef void (*PendableFunction)(void *pvParameter1, uint32_t ulParameter2); /** * Schedule a callback to run. The callback must _not_ block, though it is called from regular thread level (not ISR) * + * NOTE! ESP32 implementation is busted - always waits 0 ticks + * * @return true if successful, false if the timer fifo is too full. */ -inline bool scheduleCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec) -{ - return xTimerPendFunctionCall(callback, param1, param2, pdMS_TO_TICKS(delayMsec)); -} \ No newline at end of file + bool scheduleOSCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec); + + +/// Uses a hardware timer, but calls the handler in _interrupt_ context +bool scheduleHWCallback(PendableFunction callback, void *param1, uint32_t param2, uint32_t delayMsec); \ No newline at end of file diff --git a/src/SerialConsole.cpp b/src/SerialConsole.cpp index 861219037..9e84a958f 100644 --- a/src/SerialConsole.cpp +++ b/src/SerialConsole.cpp @@ -21,8 +21,8 @@ void SerialConsole::init() } /** - * we override this to notice when we've received a protobuf over the serial stream. Then we shunt off - * debug serial output. + * we override this to notice when we've received a protobuf over the serial + * stream. Then we shunt off debug serial output. */ void SerialConsole::handleToRadio(const uint8_t *buf, size_t len) { diff --git a/src/WorkerThread.h b/src/WorkerThread.h index 318f9d803..f951da32c 100644 --- a/src/WorkerThread.h +++ b/src/WorkerThread.h @@ -75,7 +75,7 @@ class NotifiedWorkerThread : public WorkerThread * * Defaults to clear all of them. */ - uint32_t clearOnRead = ULONG_MAX; + uint32_t clearOnRead = UINT32_MAX; /** * A method that should block execution - either waiting ona queue/mutex or a "task notification" diff --git a/src/mesh/RF95Interface.h b/src/mesh/RF95Interface.h index d0b5fd7f2..6c5e2ab5c 100644 --- a/src/mesh/RF95Interface.h +++ b/src/mesh/RF95Interface.h @@ -40,7 +40,7 @@ class RF95Interface : public RadioLibInterface /** are we actively receiving a packet (only called during receiving state) */ virtual bool isActivelyReceiving(); - + /** * Start waiting to receive a message */ @@ -50,6 +50,6 @@ class RF95Interface : public RadioLibInterface * Add SNR data to received messages */ virtual void addReceiveMetadata(MeshPacket *mp); - private: - void setStandby(); + + virtual void setStandby(); }; \ No newline at end of file diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 6e98a3641..d4a408425 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -17,7 +17,8 @@ RadioInterface::RadioInterface() : txQueue(MAX_TX_QUEUE) bool RadioInterface::init() { - start("radio", RADIO_STACK_SIZE); // Start our worker thread + // we want this thread to run at very high priority, because it is effectively running as a user space ISR + start("radio", RADIO_STACK_SIZE, configMAX_PRIORITIES - 1); // Start our worker thread return true; } diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 48a5e8a4d..ada0a337a 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -1,5 +1,6 @@ #include "RadioLibInterface.h" #include "MeshTypes.h" +#include "OSTimer.h" #include "mesh-pb-constants.h" #include // FIXME, this class shouldn't need to look into nodedb #include @@ -89,14 +90,17 @@ bool RadioLibInterface::canSendImmediately() // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, // we almost certainly guarantee no one outside will like the packet we are sending. - PendingISR isPending = pending; bool busyTx = sendingPacket != NULL; bool busyRx = isReceiving && isActivelyReceiving(); - if (busyTx || busyRx || isPending) - DEBUG_MSG("Can not send yet, busyTx=%d, busyRx=%d, intPend=%d\n", busyTx, busyRx, isPending); - - return !busyTx && !busyRx && !isPending; + if (busyTx || busyRx) { + if (busyTx) + DEBUG_MSG("Can not send yet, busyTx\n"); + if (busyRx) + DEBUG_MSG("Can not send yet, busyRx\n"); + return false; + } else + return true; } /// Send a packet (possibly by enquing in a private fifo). This routine will @@ -104,8 +108,8 @@ bool RadioLibInterface::canSendImmediately() /// bluetooth comms code. If the txmit queue is empty it might return an error ErrorCode RadioLibInterface::send(MeshPacket *p) { - DEBUG_MSG("enqueuing for send on mesh fr=0x%x,to=0x%x,id=%d\n (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, - txGood, rxGood, rxBad); + DEBUG_MSG("enqueuing for send on mesh fr=0x%x,to=0x%x,id=%d (txGood=%d,rxGood=%d,rxBad=%d)\n", p->from, p->to, p->id, txGood, + rxGood, rxBad); ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN; if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks @@ -113,7 +117,9 @@ ErrorCode RadioLibInterface::send(MeshPacket *p) return res; } - startTransmitTimer(false); // We want all sending/receiving to be done by our daemon thread + // We want all sending/receiving to be done by our daemon thread, We use a delay here because this packet might have been sent + // in response to a packet we just received. So we want to make sure the other side has had a chance to reconfigure its radio + startTransmitTimer(true); return res; } @@ -127,6 +133,19 @@ bool RadioLibInterface::canSleep() return res; } +/** At the low end we want to pick a delay large enough that anyone who just completed sending (some other node) + * has had enough time to switch their radio back into receive mode. + */ +#define MIN_TX_WAIT_MSEC 100 + +/** + * At the high end, this value is used to spread node attempts across time so when they are replying to a packet + * they don't both check that the airwaves are clear at the same moment. As long as they are off by some amount + * one of the two will be first to start transmitting and the other will see that. I bet 500ms is more than enough + * to guarantee this. + */ +#define MAX_TX_WAIT_MSEC 2000 // stress test would still fail occasionally with 1000 + /** radio helper thread callback. We never immediately transmit after any operation (either rx or tx). Instead we should start receiving and @@ -138,7 +157,7 @@ and then they will wait until that packet finishes. NOTE: the large flood rebroadcast delay might still be needed even with this approach. Because we might not be able to hear other transmitters that we are potentially stomping on. Requires further thought. -FIXME, the 50ms and 200ms values should be tuned via logic analyzer later. +FIXME, the MIN_TX_WAIT_MSEC and MAX_TX_WAIT_MSEC values should be tuned via logic analyzer later. */ void RadioLibInterface::loop() { @@ -162,8 +181,6 @@ void RadioLibInterface::loop() if (!canSendImmediately()) { startTransmitTimer(); // try again in a little while } else { - DEBUG_MSG("Transmit timer completed!\n"); - // Send any outgoing packets we have ready MeshPacket *txp = txQueue.dequeuePtr(0); assert(txp); @@ -176,9 +193,11 @@ void RadioLibInterface::loop() } } -#include "OSTimer.h" +#ifndef NO_ESP32 +#define USE_HW_TIMER +#endif -void RadioLibInterface::timerCallback(void *p1, uint32_t p2) +void IRAM_ATTR RadioLibInterface::timerCallback(void *p1, uint32_t p2) { RadioLibInterface *t = (RadioLibInterface *)p1; @@ -186,7 +205,17 @@ void RadioLibInterface::timerCallback(void *p1, uint32_t p2) // We use without overwrite, so that if there is already an interrupt pending to be handled, that gets handle properly (the // ISR handler will restart our timer) +#ifndef USE_HW_TIMER t->notify(TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite); +#else + BaseType_t xHigherPriorityTaskWoken; + instance->notifyFromISR(&xHigherPriorityTaskWoken, TRANSMIT_DELAY_COMPLETED, eSetValueWithoutOverwrite); + + /* 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); +#endif } void RadioLibInterface::startTransmitTimer(bool withDelay) @@ -194,8 +223,15 @@ void RadioLibInterface::startTransmitTimer(bool withDelay) // If we have work to do and the timer wasn't already scheduled, schedule it now if (!timerRunning && !txQueue.isEmpty()) { timerRunning = true; - uint32_t delay = withDelay ? 0 : random(50, 200); // See documentation for loop() wrt these values - scheduleCallback(timerCallback, this, 0, delay); + uint32_t delay = + !withDelay ? 0 : random(MIN_TX_WAIT_MSEC, MAX_TX_WAIT_MSEC); // See documentation for loop() wrt these values + // DEBUG_MSG("xmit timer %d\n", delay); +#ifdef USE_HW_TIMER + bool okay = scheduleHWCallback(timerCallback, this, 0, delay); +#else + bool okay = scheduleOSCallback(timerCallback, this, 0, delay); +#endif + assert(okay); } } @@ -245,6 +281,7 @@ void RadioLibInterface::handleReceiveInterrupt() const PacketHeader *h = (PacketHeader *)radiobuf; uint8_t ourAddr = nodeDB.getNodeNum(); + rxGood++; if (h->to != 255 && h->to != ourAddr) { DEBUG_MSG("ignoring packet not sent to us\n"); } else { @@ -264,7 +301,6 @@ void RadioLibInterface::handleReceiveInterrupt() } else { // parsing was successful, queue for our recipient mp->has_payload = true; - rxGood++; DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); deliverToReceiver(mp); @@ -277,6 +313,9 @@ void RadioLibInterface::handleReceiveInterrupt() /** start an immediate transmit */ void RadioLibInterface::startSend(MeshPacket *txp) { + DEBUG_MSG("Starting low level send from=0x%x, id=%u!\n", txp->from, txp->id); + setStandby(); // Cancel any already in process receives + size_t numbytes = beginSending(txp); int res = iface->startTransmit(radiobuf, numbytes); diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index c6f595c80..e1e0f1a97 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -85,8 +85,8 @@ class RadioLibInterface : public RadioInterface void startSend(MeshPacket *txp); /** if we have something waiting to send, start a short random timer so we can come check for collision before actually doing - * the transmit - * + * the transmit + * * If the timer was already running, we just wait for that one to occur. * */ void startTransmitTimer(bool withDelay = true); @@ -103,7 +103,7 @@ class RadioLibInterface : public RadioInterface void applyModemConfig(); /** Could we send right now (i.e. either not actively receiving or transmitting)? */ - bool canSendImmediately(); + virtual bool canSendImmediately(); /** are we actively receiving a packet (only called during receiving state) */ virtual bool isActivelyReceiving() = 0; @@ -128,4 +128,6 @@ class RadioLibInterface : public RadioInterface virtual void addReceiveMetadata(MeshPacket *mp) = 0; virtual void loop(); // Idle processing + + virtual void setStandby() = 0; }; \ No newline at end of file diff --git a/src/mesh/RadioLibRF95.cpp b/src/mesh/RadioLibRF95.cpp index cd8a3b824..c82c1b0c7 100644 --- a/src/mesh/RadioLibRF95.cpp +++ b/src/mesh/RadioLibRF95.cpp @@ -56,8 +56,13 @@ int16_t RadioLibRF95::setFrequency(float freq) bool RadioLibRF95::isReceiving() { // 0x0b == Look for header info valid, signal synchronized or signal detected - uint8_t reg = _mod->SPIreadRegister(SX127X_REG_MODEM_STAT) & 0x1f; + uint8_t reg = readReg(SX127X_REG_MODEM_STAT); // Serial.printf("reg %x\n", reg); return (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED | RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED | - RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0; + RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0; } + +uint8_t RadioLibRF95::readReg(uint8_t addr) +{ + return _mod->SPIreadRegister(addr); +} \ No newline at end of file diff --git a/src/mesh/RadioLibRF95.h b/src/mesh/RadioLibRF95.h index 1746200dd..3c45a8f44 100644 --- a/src/mesh/RadioLibRF95.h +++ b/src/mesh/RadioLibRF95.h @@ -62,6 +62,9 @@ class RadioLibRF95: public SX1278 { // Return true if we are actively receiving a message currently bool isReceiving(); + /// For debugging + uint8_t readReg(uint8_t addr); + #ifndef RADIOLIB_GODMODE private: #endif diff --git a/src/mesh/SX1262Interface.h b/src/mesh/SX1262Interface.h index 18ab8ef75..c1e1fb1c6 100644 --- a/src/mesh/SX1262Interface.h +++ b/src/mesh/SX1262Interface.h @@ -48,6 +48,8 @@ class SX1262Interface : public RadioLibInterface */ virtual void addReceiveMetadata(MeshPacket *mp); + virtual void setStandby(); + private: - void setStandby(); + }; \ No newline at end of file From 80268ea56a0ade86384d3079f31239b9be7c58bb Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 2 May 2020 19:51:55 -0700 Subject: [PATCH 141/197] send() is supposed to always free buffers, even if it returns an error --- src/mesh/Router.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 4790ff17e..b5a34a801 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -44,7 +44,7 @@ void Router::loop() /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. - * If the txmit queue is full it might return an error + * If the txmit queue is full it might return an error. */ ErrorCode Router::send(MeshPacket *p) { @@ -53,6 +53,7 @@ ErrorCode Router::send(MeshPacket *p) return iface->send(p); } else { DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + packetPool.release(p); return ERRNO_NO_INTERFACES; } } From 79c61cf0e0acda97a17fef2b045e4d738e386fe5 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 2 May 2020 19:52:37 -0700 Subject: [PATCH 142/197] limit max power on rf95 to 17 (rather than 20, because 20 can... burn up parts if you exceed 1% duty cycle) --- src/mesh/RF95Interface.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/mesh/RF95Interface.cpp b/src/mesh/RF95Interface.cpp index c335205b7..0557dd082 100644 --- a/src/mesh/RF95Interface.cpp +++ b/src/mesh/RF95Interface.cpp @@ -3,6 +3,9 @@ #include "RadioLibRF95.h" #include +#define MAX_POWER 17 +// if we use 20 we are limited to 1% duty cycle or hw might overheat. For continuous operation set a limit of 17 + RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, SPIClass &spi) : RadioLibInterface(cs, irq, rst, 0, spi) { @@ -15,10 +18,10 @@ RF95Interface::RF95Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOL bool RF95Interface::init() { RadioLibInterface::init(); - + applyModemConfig(); - if (power > 20) // This chip has lower power limits than some - power = 20; + if (power > MAX_POWER) // This chip has lower power limits than some + power = MAX_POWER; iface = lora = new RadioLibRF95(&module); int res = lora->begin(freq, bw, sf, cr, syncWord, power, currentLimit, preambleLength); @@ -27,7 +30,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; @@ -67,8 +70,8 @@ bool RF95Interface::reconfigure() err = lora->setFrequency(freq); assert(err == ERR_NONE); - if (power > 20) // This chip has lower power limits than some - power = 20; + if (power > MAX_POWER) // This chip has lower power limits than some + power = MAX_POWER; err = lora->setOutputPower(power); assert(err == ERR_NONE); @@ -120,4 +123,4 @@ bool RF95Interface::sleep() lora->sleep(); return true; -} \ No newline at end of file +} From 07b4eea037613da190e805619839ec7426055f2c Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 2 May 2020 19:52:54 -0700 Subject: [PATCH 143/197] fix log msg --- src/mesh/NodeDB.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index b27c49e12..0559754a3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -303,7 +303,8 @@ void NodeDB::updateFrom(const MeshPacket &mp) if (p.has_data) { // Keep a copy of the most recent text message. if (p.data.typ == Data_Type_CLEAR_TEXT) { - DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.data.payload.size, p.data.payload.bytes); + DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.data.payload.size, + p.data.payload.bytes); if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) { // We only store/display messages destined for us. devicestate.rx_text_message = mp; From ad2f63919528702784d09d11fc84268dbfcafca3 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 2 May 2020 19:53:13 -0700 Subject: [PATCH 144/197] don't leak messages if they are handled locally --- src/mesh/MeshService.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 873d17eca..71eb5f37c 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -266,8 +266,10 @@ void MeshService::sendToMesh(MeshPacket *p) } // If the phone sent a packet just to us, don't send it out into the network - if (p->to == nodeDB.getNodeNum()) + if (p->to == nodeDB.getNodeNum()) { DEBUG_MSG("Dropping locally processed message\n"); + releaseToPool(p); + } else { // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it if (router.send(p) != ERRNO_OK) { From 1d9290afc0a656f62af194c3f9839039539f39f5 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 2 May 2020 19:53:58 -0700 Subject: [PATCH 145/197] now that the rfinterfaces are smarter, no need to do backoff in the flood router. the interfaces will handle it. --- src/mesh/FloodingRouter.cpp | 57 +++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 582448da6..da7070172 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -5,6 +5,8 @@ /// We clear our old flood record five minute after we see the last of it #define FLOOD_EXPIRE_TIME (5 * 60 * 1000L) +static bool supportFlooding = true; // Sometimes to simplify debugging we want jusT simple broadcast only + FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) { recentBroadcasts.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation @@ -19,7 +21,8 @@ FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) ErrorCode FloodingRouter::send(MeshPacket *p) { // We update our table of recent broadcasts, even for messages we send - wasSeenRecently(p); + if (supportFlooding) + wasSeenRecently(p); return Router::send(p); } @@ -30,6 +33,12 @@ uint32_t getRandomDelay() return random(200, 10 * 1000L); // between 200ms and 10s } +/** + * Now that our generalized packet send code has a random delay - I don't think we need to wait here + * But I'm leaving this bool until I rip the code out for good. + */ +bool needDelay = false; + /** * Called from loop() * Handle any packet that is received by an interface on this node. @@ -39,28 +48,40 @@ uint32_t getRandomDelay() */ void FloodingRouter::handleReceived(MeshPacket *p) { - if (wasSeenRecently(p)) { - DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n"); - packetPool.release(p); - } else { - if (p->to == NODENUM_BROADCAST) { - if (p->id != 0) { - uint32_t delay = getRandomDelay(); + if (supportFlooding) { + if (wasSeenRecently(p)) { + DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n"); + packetPool.release(p); + } else { + if (p->to == NODENUM_BROADCAST) { + if (p->id != 0) { + MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors in %u msec, fr=0x%x,to=0x%x,id=%d\n", delay, p->from, - p->to, p->id); + if (needDelay) { + uint32_t delay = getRandomDelay(); - MeshPacket *tosend = packetPool.allocCopy(*p); - toResend.enqueue(tosend); - setPeriod(delay); // This will work even if we were already waiting a random delay - } else { - DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors in %u msec, fr=0x%x,to=0x%x,id=%d\n", delay, + p->from, p->to, p->id); + + toResend.enqueue(tosend); + setPeriod(delay); // This will work even if we were already waiting a random delay + } else { + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, + p->id); + // Note: we are careful to resend using the original senders node id + // We are careful not to call our hooked version of send() - because we don't want to check this again + Router::send(tosend); + } + } else { + DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); + } } - } - // handle the packet as normal + // handle the packet as normal + Router::handleReceived(p); + } + } else Router::handleReceived(p); - } } void FloodingRouter::doTask() From 624b95782ddb183ffcedb86cfe826134e7771f6d Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 2 May 2020 20:21:42 -0700 Subject: [PATCH 146/197] fix missing carriage returns. thanks to @gregwalters in #119 --- src/SerialConsole.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/SerialConsole.h b/src/SerialConsole.h index 50efb99af..b39eda23b 100644 --- a/src/SerialConsole.h +++ b/src/SerialConsole.h @@ -19,6 +19,13 @@ class SerialConsole : public StreamAPI, public RedirectablePrint * debug serial output. */ virtual void handleToRadio(const uint8_t *buf, size_t len); + + virtual size_t write(uint8_t c) + { + if (c == '\n') // prefix any newlines with carriage return + RedirectablePrint::write('\r'); + return RedirectablePrint::write(c); + } }; extern SerialConsole console; From 9b309fe0a035b72c3569c0595daab22cf5e077fe Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 4 May 2020 08:09:08 -0700 Subject: [PATCH 147/197] Use int based lat/long from now on in the device code for https://github.com/meshtastic/Meshtastic-device/issues/124 --- docs/software/nrf52-TODO.md | 2 +- proto | 2 +- src/GPS.cpp | 8 ++++---- src/GPS.h | 3 +-- src/mesh/MeshService.cpp | 7 +++---- src/mesh/mesh.pb.c | 8 -------- src/mesh/mesh.pb.h | 28 ++++++++++++++-------------- src/screen.cpp | 11 +++++++---- 8 files changed, 31 insertions(+), 38 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 5d9c75775..6bc5fb0d2 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -9,7 +9,7 @@ Minimum items needed to make sure hardware is good. - plug in correct variants for the real board - Use the PMU driver on real hardware - add a NEMA based GPS driver to test GPS -- Use new radio driver on real hardware - possibly start with https://os.mbed.com/teams/Semtech/code/SX126xLib/ +- Use new radio driver on real hardware - Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI - test the LEDs - test the buttons diff --git a/proto b/proto index bd002e5a1..cabbdf51e 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit bd002e5a144f209e42c97b64fea9a05a2e513b28 +Subproject commit cabbdf51ed365b72ab995ad24b075269627f58ad diff --git a/src/GPS.cpp b/src/GPS.cpp index a6a1e8eef..f78ad906c 100644 --- a/src/GPS.cpp +++ b/src/GPS.cpp @@ -48,7 +48,7 @@ void GPS::setup() isConnected = ublox.begin(_serial_gps); if (isConnected) { - DEBUG_MSG("Connected to GPS successfully\n"); + DEBUG_MSG("Connected to UBLOX GPS successfully\n"); bool factoryReset = false; bool ok; @@ -191,10 +191,10 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only { // we only notify if position has changed - latitude = ublox.getLatitude() * 1e-7; - longitude = ublox.getLongitude() * 1e-7; + latitude = ublox.getLatitude(); + longitude = ublox.getLongitude(); altitude = ublox.getAltitude() / 1000; // in mm convert to meters - DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude, longitude, altitude); + DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 if (hasValidLocation) { diff --git a/src/GPS.h b/src/GPS.h index 912356c42..c3c403278 100644 --- a/src/GPS.h +++ b/src/GPS.h @@ -15,7 +15,7 @@ class GPS : public PeriodicTask, public Observable SFE_UBLOX_GPS ublox; public: - double latitude, longitude; + uint32_t latitude, longitude; // as an int mult by 1e-7 to get value as double uint32_t altitude; bool isConnected; // Do we have a GPS we are talking to @@ -29,7 +29,6 @@ class GPS : public PeriodicTask, public Observable void setup(); - virtual void doTask(); /// If we haven't yet set our RTC this boot, set it from a GPS derived time diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 71eb5f37c..1b05e0c51 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -269,8 +269,7 @@ void MeshService::sendToMesh(MeshPacket *p) if (p->to == nodeDB.getNodeNum()) { DEBUG_MSG("Dropping locally processed message\n"); releaseToPool(p); - } - else { + } else { // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it if (router.send(p) != ERRNO_OK) { DEBUG_MSG("No radio was able to send packet, discarding...\n"); @@ -333,8 +332,8 @@ int MeshService::onGPSChanged(void *unused) if (gps.latitude != 0 || gps.longitude != 0) { if (gps.altitude != 0) pos.altitude = gps.altitude; - pos.latitude = gps.latitude; - pos.longitude = gps.longitude; + pos.latitude_i = gps.latitude; + pos.longitude_i = gps.longitude; pos.time = gps.getValidTime(); } diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index 096f28896..0b2c5b8ce 100644 --- a/src/mesh/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -55,11 +55,3 @@ PB_BIND(ToRadio, ToRadio, 2) -#ifndef PB_CONVERT_DOUBLE_FLOAT -/* On some platforms (such as AVR), double is really float. - * To be able to encode/decode double on these platforms, you need. - * to define PB_CONVERT_DOUBLE_FLOAT in pb.h or compiler command line. - */ -PB_STATIC_ASSERT(sizeof(double) == 8, DOUBLE_MUST_BE_8_BYTES) -#endif - diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index bf79305b8..7f25f1c99 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -66,11 +66,11 @@ typedef struct _MyNodeInfo { } MyNodeInfo; typedef struct _Position { - double latitude; - double longitude; int32_t altitude; int32_t battery_level; uint32_t time; + int32_t latitude_i; + int32_t longitude_i; } Position; typedef struct _RadioConfig_UserPreferences { @@ -237,8 +237,8 @@ typedef struct _ToRadio { #define MyNodeInfo_error_code_tag 7 #define MyNodeInfo_error_address_tag 8 #define MyNodeInfo_error_count_tag 9 -#define Position_latitude_tag 1 -#define Position_longitude_tag 2 +#define Position_latitude_i_tag 7 +#define Position_longitude_i_tag 8 #define Position_altitude_tag 3 #define Position_battery_level_tag 4 #define Position_time_tag 6 @@ -297,11 +297,11 @@ typedef struct _ToRadio { /* Struct field encoding specification for nanopb */ #define Position_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, DOUBLE, latitude, 1) \ -X(a, STATIC, SINGULAR, DOUBLE, longitude, 2) \ X(a, STATIC, SINGULAR, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, INT32, battery_level, 4) \ -X(a, STATIC, SINGULAR, UINT32, time, 6) +X(a, STATIC, SINGULAR, UINT32, time, 6) \ +X(a, STATIC, SINGULAR, INT32, latitude_i, 7) \ +X(a, STATIC, SINGULAR, INT32, longitude_i, 8) #define Position_CALLBACK NULL #define Position_DEFAULT NULL @@ -486,21 +486,21 @@ extern const pb_msgdesc_t ToRadio_msg; #define ToRadio_fields &ToRadio_msg /* Maximum encoded size of messages (where known) */ -#define Position_size 46 +#define Position_size 50 #define Data_size 256 #define User_size 72 /* RouteDiscovery_size depends on runtime parameters */ -#define SubPacket_size 383 -#define MeshPacket_size 425 +#define SubPacket_size 387 +#define MeshPacket_size 429 #define ChannelSettings_size 44 #define RadioConfig_size 120 #define RadioConfig_UserPreferences_size 72 -#define NodeInfo_size 138 +#define NodeInfo_size 142 #define MyNodeInfo_size 85 -#define DeviceState_size 18925 +#define DeviceState_size 19185 #define DebugString_size 258 -#define FromRadio_size 434 -#define ToRadio_size 428 +#define FromRadio_size 438 +#define ToRadio_size 432 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/screen.cpp b/src/screen.cpp index a390520c4..0b3f22fb2 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -280,7 +280,7 @@ static float estimatedHeading(double lat, double lon) /// valid lat/lon static bool hasPosition(NodeInfo *n) { - return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0); + return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0); } /// We will skip one node - the one for us, so we just blindly loop over all @@ -288,6 +288,9 @@ static bool hasPosition(NodeInfo *n) static size_t nodeIndex; static int8_t prevFrame = -1; +/// Convert an integer GPS coords to a floating point +#define DegD(i) (i * 1e-7) + static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { // We only advance our nodeIndex if the frame # has changed - because @@ -334,7 +337,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum()); if (ourNode && hasPosition(ourNode) && hasPosition(node)) { Position &op = ourNode->position, &p = node->position; - float d = latLongToMeter(p.latitude, p.longitude, op.latitude, op.longitude); + float d = latLongToMeter(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); if (d < 2000) snprintf(distStr, sizeof(distStr), "%.0f m", d); else @@ -342,8 +345,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // FIXME, also keep the guess at the operators heading and add/substract // it. currently we don't do this and instead draw north up only. - float bearingToOther = bearing(p.latitude, p.longitude, op.latitude, op.longitude); - float myHeading = estimatedHeading(p.latitude, p.longitude); + float bearingToOther = bearing(DegD(p.latitude_i), DegD(p.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i)); + float myHeading = estimatedHeading(DegD(p.latitude_i), DegD(p.longitude_i)); headingRadian = bearingToOther - myHeading; } else { // Debug info for gps lock errors From ecf528f9b6b1211cc2f92909cb7aaaf2bc6dbbd7 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 4 May 2020 10:23:47 -0700 Subject: [PATCH 148/197] move gps before refactoring --- src/{ => gps}/GPS.cpp | 0 src/{ => gps}/GPS.h | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{ => gps}/GPS.cpp (100%) rename src/{ => gps}/GPS.h (100%) diff --git a/src/GPS.cpp b/src/gps/GPS.cpp similarity index 100% rename from src/GPS.cpp rename to src/gps/GPS.cpp diff --git a/src/GPS.h b/src/gps/GPS.h similarity index 100% rename from src/GPS.h rename to src/gps/GPS.h From 933d5424da90113a152aae44df141915ffaf8401 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 4 May 2020 11:15:05 -0700 Subject: [PATCH 149/197] abstract out the UBlox GPS driver --- .vscode/settings.json | 2 + platformio.ini | 2 +- src/PowerFSM.cpp | 2 +- src/esp32/MeshBluetoothService.cpp | 2 +- src/gps/GPS.cpp | 171 ++--------------------------- src/gps/GPS.h | 62 +++++------ src/gps/UBloxGPS.cpp | 153 ++++++++++++++++++++++++++ src/gps/UBloxGPS.h | 41 +++++++ src/main.cpp | 13 ++- src/mesh/MeshService.cpp | 28 ++--- src/mesh/NodeDB.cpp | 2 +- src/mesh/Router.cpp | 2 +- src/sleep.cpp | 9 +- 13 files changed, 263 insertions(+), 226 deletions(-) create mode 100644 src/gps/UBloxGPS.cpp create mode 100644 src/gps/UBloxGPS.h diff --git a/.vscode/settings.json b/.vscode/settings.json index dfe3b542f..bfef8191b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -50,7 +50,9 @@ "cassert": "cpp" }, "cSpell.words": [ + "Blox", "Meshtastic", + "Ublox", "descs", "protobufs" ] diff --git a/platformio.ini b/platformio.ini index 936a8e6c3..b64bcf002 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/mesh -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map +build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -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/PowerFSM.cpp b/src/PowerFSM.cpp index 4c4944b0f..ed6eaf4ff 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -87,7 +87,7 @@ static void lsIdle() static void lsExit() { // setGPSPower(true); // restore GPS power - gps.startLock(); + gps->startLock(); } static void nbEnter() diff --git a/src/esp32/MeshBluetoothService.cpp b/src/esp32/MeshBluetoothService.cpp index 9b3f14804..ecda8bf40 100644 --- a/src/esp32/MeshBluetoothService.cpp +++ b/src/esp32/MeshBluetoothService.cpp @@ -178,7 +178,7 @@ class MyNodeInfoCharacteristic : public ProtobufCharacteristic void onRead(BLECharacteristic *c) { // update gps connection state - myNodeInfo.has_gps = gps.isConnected; + myNodeInfo.has_gps = gps->isConnected; ProtobufCharacteristic::onRead(c); diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index f78ad906c..9273a6ed2 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -6,89 +6,23 @@ #include #ifdef GPS_RX_PIN -HardwareSerial _serial_gps(GPS_SERIAL_NUM); +HardwareSerial _serial_gps_real(GPS_SERIAL_NUM); +HardwareSerial &GPS::_serial_gps = _serial_gps_real; #else // Assume NRF52 -HardwareSerial &_serial_gps = Serial1; +HardwareSerial &GPS::_serial_gps = Serial1; #endif bool timeSetFromGPS; // We try to set our time from GPS each time we wake from sleep -GPS gps; +GPS *gps; // stuff that really should be in in the instance instead... static uint32_t timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock -static bool wantNewLocation = true; - -GPS::GPS() : PeriodicTask() {} - -void GPS::setup() -{ - PeriodicTask::setup(); - - readFromRTC(); // read the main CPU RTC at first - -#ifdef GPS_RX_PIN - _serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); -#else - _serial_gps.begin(GPS_BAUDRATE); -#endif - // _serial_gps.setRxBufferSize(1024); // the default is 256 - // ublox.enableDebugging(Serial); - - // note: the lib's implementation has the wrong docs for what the return val is - // it is not a bool, it returns zero for success - isConnected = ublox.begin(_serial_gps); - - // try a second time, the ublox lib serial parsing is buggy? - if (!isConnected) - isConnected = ublox.begin(_serial_gps); - - if (isConnected) { - DEBUG_MSG("Connected to UBLOX GPS successfully\n"); - - bool factoryReset = false; - bool ok; - if (factoryReset) { - // It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have - // GPS_TX connected) - ublox.factoryReset(); - delay(2000); - isConnected = ublox.begin(_serial_gps); - DEBUG_MSG("Factory reset success=%d\n", isConnected); - if (isConnected) { - ublox.assumeAutoPVT(true, true); // Just parse NEMA for now - } - } else { - ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API - assert(ok); - ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low - assert(ok); - // ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M - // assert(ok); - // ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds - // assert(ok); - ok = ublox.powerSaveMode(); // use power save mode - assert(ok); - } - ok = ublox.saveConfiguration(3000); - assert(ok); - } else { - // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just - // assume NEMA at 9600 baud. - DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n"); - - // tell lib, we are expecting the module to send PVT messages by itself to our Rx pin - // you can set second parameter to "false" if you want to control the parsing and eviction of the data (need to call - // checkUblox cyclically) - ublox.assumeAutoPVT(true, true); - } -} - -void GPS::readFromRTC() +void readFromRTC() { struct timeval tv; /* btw settimeofday() is helpfull here too*/ @@ -102,7 +36,7 @@ void GPS::readFromRTC() } /// If we haven't yet set our RTC this boot, set it from a GPS derived time -void GPS::perhapsSetRTC(const struct timeval *tv) +void perhapsSetRTC(const struct timeval *tv) { if (!timeSetFromGPS) { timeSetFromGPS = true; @@ -118,101 +52,12 @@ void GPS::perhapsSetRTC(const struct timeval *tv) #include -uint32_t GPS::getTime() +uint32_t getTime() { return ((millis() - timeStartMsec) / 1000) + zeroOffsetSecs; } -uint32_t GPS::getValidTime() +uint32_t getValidTime() { return timeSetFromGPS ? getTime() : 0; } - -/// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock) -bool GPS::canSleep() -{ - return true; // we leave GPS on during sleep now, so sleep is okay !wantNewLocation; -} - -/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs -void GPS::prepareSleep() -{ - if (isConnected) - ublox.powerOff(); -} - -void GPS::doTask() -{ - uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix - - if (isConnected) { - // Consume all characters that have arrived - - // getPVT automatically calls checkUblox - ublox.checkUblox(); // See if new data is available. Process bytes as they come in. - - // If we don't have a fix (a quick check), don't try waiting for a solution) - // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions - // turn off for now - // fixtype = ublox.getFixType(); - DEBUG_MSG("fix type %d\n", fixtype); - } - - // DEBUG_MSG("sec %d\n", ublox.getSecond()); - // DEBUG_MSG("lat %d\n", ublox.getLatitude()); - - // any fix that has time - if (!timeSetFromGPS && ublox.getT()) { - struct timeval tv; - - /* Convert to unix time -The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 -(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). -*/ - struct tm t; - t.tm_sec = ublox.getSecond(); - t.tm_min = ublox.getMinute(); - t.tm_hour = ublox.getHour(); - t.tm_mday = ublox.getDay(); - t.tm_mon = ublox.getMonth() - 1; - t.tm_year = ublox.getYear() - 1900; - t.tm_isdst = false; - time_t res = mktime(&t); - tv.tv_sec = res; - tv.tv_usec = 0; // time.centisecond() * (10 / 1000); - - DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); - if (t.tm_year < 0 || t.tm_year >= 300) - DEBUG_MSG("Ignoring invalid GPS time\n"); - else - perhapsSetRTC(&tv); - } - - if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only - { - // we only notify if position has changed - latitude = ublox.getLatitude(); - longitude = ublox.getLongitude(); - altitude = ublox.getAltitude() / 1000; // in mm convert to meters - DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); - - hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 - if (hasValidLocation) { - wantNewLocation = false; - notifyObservers(NULL); - // ublox.powerOff(); - } - } else // we didn't get a location update, go back to sleep and hope the characters show up - wantNewLocation = true; - - // Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over - // the serial - setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000); -} - -void GPS::startLock() -{ - DEBUG_MSG("Looking for GPS lock\n"); - wantNewLocation = true; - setPeriod(1); -} diff --git a/src/gps/GPS.h b/src/gps/GPS.h index c3c403278..7a1a79a04 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -2,54 +2,50 @@ #include "Observer.h" #include "PeriodicTask.h" -#include "SparkFun_Ublox_Arduino_Library.h" #include "sys/time.h" +/// If we haven't yet set our RTC this boot, set it from a GPS derived time +void perhapsSetRTC(const struct timeval *tv); + +/// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero +uint32_t getTime(); + +/// Return time since 1970 in secs. If we don't have a GPS lock return zero +uint32_t getValidTime(); + +void readFromRTC(); + /** * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading) * * When new data is available it will notify observers. */ -class GPS : public PeriodicTask, public Observable +class GPS : public Observable { - SFE_UBLOX_GPS ublox; + protected: + bool hasValidLocation = false; // default to false, until we complete our first read + + static HardwareSerial &_serial_gps; public: - uint32_t latitude, longitude; // as an int mult by 1e-7 to get value as double - uint32_t altitude; - bool isConnected; // Do we have a GPS we are talking to + uint32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double + uint32_t altitude = 0; + bool isConnected = false; // Do we have a GPS we are talking to - GPS(); + virtual ~GPS() {} - /// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero - uint32_t getTime(); - - /// Return time since 1970 in secs. If we don't have a GPS lock return zero - uint32_t getValidTime(); - - void setup(); - - virtual void doTask(); - - /// If we haven't yet set our RTC this boot, set it from a GPS derived time - void perhapsSetRTC(const struct timeval *tv); - - /// Returns true if we think the board can enter deep or light sleep now (we might be trying to get a GPS lock) - bool canSleep(); - - /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs - void prepareSleep(); - - /// Restart our lock attempt - try to get and broadcast a GPS reading ASAP - void startLock(); + /** + * Returns true if we succeeded + */ + virtual bool setup() = 0; /// Returns ture if we have acquired GPS lock. bool hasLock() const { return hasValidLocation; } - private: - void readFromRTC(); - - bool hasValidLocation = false; // default to false, until we complete our first read + /** + * Restart our lock attempt - try to get and broadcast a GPS reading ASAP + * called after the CPU wakes from light-sleep state */ + virtual void startLock() {} }; -extern GPS gps; +extern GPS *gps; diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp new file mode 100644 index 000000000..881fd2977 --- /dev/null +++ b/src/gps/UBloxGPS.cpp @@ -0,0 +1,153 @@ +#include "UBloxGPS.h" +#include "sleep.h" +#include + +UBloxGPS::UBloxGPS() : PeriodicTask() +{ + notifySleepObserver.observe(¬ifySleep); +} + +bool UBloxGPS::setup() +{ + PeriodicTask::setup(); + +#ifdef GPS_RX_PIN + _serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); +#else + _serial_gps.begin(GPS_BAUDRATE); +#endif + // _serial_gps.setRxBufferSize(1024); // the default is 256 + // ublox.enableDebugging(Serial); + + // note: the lib's implementation has the wrong docs for what the return val is + // it is not a bool, it returns zero for success + isConnected = ublox.begin(_serial_gps); + + // try a second time, the ublox lib serial parsing is buggy? + if (!isConnected) + isConnected = ublox.begin(_serial_gps); + + if (isConnected) { + DEBUG_MSG("Connected to UBLOX GPS successfully\n"); + + bool factoryReset = false; + bool ok; + if (factoryReset) { + // It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have + // GPS_TX connected) + ublox.factoryReset(); + delay(2000); + isConnected = ublox.begin(_serial_gps); + DEBUG_MSG("Factory reset success=%d\n", isConnected); + if (isConnected) { + ublox.assumeAutoPVT(true, true); // Just parse NEMA for now + } + } else { + ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API + assert(ok); + ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low + assert(ok); + // ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M + // assert(ok); + // ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds + // assert(ok); + ok = ublox.powerSaveMode(); // use power save mode + assert(ok); + } + ok = ublox.saveConfiguration(3000); + assert(ok); + + return true; + } else { + // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just + // assume NEMA at 9600 baud. + DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n"); + + return false; + } +} + +/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs +int UBloxGPS::prepareSleep(void *unused) +{ + if (isConnected) + ublox.powerOff(); + + return 0; +} + +void UBloxGPS::doTask() +{ + uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix + + if (isConnected) { + // Consume all characters that have arrived + + // getPVT automatically calls checkUblox + ublox.checkUblox(); // See if new data is available. Process bytes as they come in. + + // If we don't have a fix (a quick check), don't try waiting for a solution) + // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions + // turn off for now + // fixtype = ublox.getFixType(); + DEBUG_MSG("fix type %d\n", fixtype); + } + + // DEBUG_MSG("sec %d\n", ublox.getSecond()); + // DEBUG_MSG("lat %d\n", ublox.getLatitude()); + + // any fix that has time + if (ublox.getT()) { + struct timeval tv; + + /* Convert to unix time +The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 +(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). +*/ + struct tm t; + t.tm_sec = ublox.getSecond(); + t.tm_min = ublox.getMinute(); + t.tm_hour = ublox.getHour(); + t.tm_mday = ublox.getDay(); + t.tm_mon = ublox.getMonth() - 1; + t.tm_year = ublox.getYear() - 1900; + t.tm_isdst = false; + time_t res = mktime(&t); + tv.tv_sec = res; + tv.tv_usec = 0; // time.centisecond() * (10 / 1000); + + DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); + if (t.tm_year < 0 || t.tm_year >= 300) + DEBUG_MSG("Ignoring invalid GPS time\n"); + else + perhapsSetRTC(&tv); + } + + if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only + { + // we only notify if position has changed + latitude = ublox.getLatitude(); + longitude = ublox.getLongitude(); + altitude = ublox.getAltitude() / 1000; // in mm convert to meters + DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); + + hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 + if (hasValidLocation) { + wantNewLocation = false; + notifyObservers(NULL); + // ublox.powerOff(); + } + } else // we didn't get a location update, go back to sleep and hope the characters show up + wantNewLocation = true; + + // Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over + // the serial + setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000); +} + +void UBloxGPS::startLock() +{ + DEBUG_MSG("Looking for GPS lock\n"); + wantNewLocation = true; + setPeriod(1); +} diff --git a/src/gps/UBloxGPS.h b/src/gps/UBloxGPS.h new file mode 100644 index 000000000..39b125981 --- /dev/null +++ b/src/gps/UBloxGPS.h @@ -0,0 +1,41 @@ +#pragma once + +#include "GPS.h" +#include "Observer.h" +#include "PeriodicTask.h" +#include "SparkFun_Ublox_Arduino_Library.h" + +/** + * A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading) + * + * When new data is available it will notify observers. + */ +class UBloxGPS : public GPS, public PeriodicTask +{ + SFE_UBLOX_GPS ublox; + + bool wantNewLocation = true; + + CallbackObserver notifySleepObserver = CallbackObserver(this, &UBloxGPS::prepareSleep); + + public: + UBloxGPS(); + + /** + * Returns true if we succeeded + */ + virtual bool setup(); + + virtual void doTask(); + + /** + * Restart our lock attempt - try to get and broadcast a GPS reading ASAP + * called after the CPU wakes from light-sleep state */ + virtual void startLock(); + + private: + + /// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs + /// always returns 0 to indicate okay to sleep + int prepareSleep(void *unused); +}; diff --git a/src/main.cpp b/src/main.cpp index 7e1b39ab7..b70ddb99f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,13 +21,13 @@ */ -#include "GPS.h" #include "MeshRadio.h" #include "MeshService.h" #include "NodeDB.h" #include "Periodic.h" #include "PowerFSM.h" #include "Router.h" +#include "UBloxGPS.h" #include "configuration.h" #include "error.h" #include "power.h" @@ -188,8 +188,13 @@ void setup() screen.print("Started...\n"); - // Init GPS - gps.setup(); + readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) + + // Init GPS - first try ublox + gps = new UBloxGPS(); + if (!gps->setup()) { + // FIXME - fallback to NEMA + } service.init(); @@ -306,7 +311,7 @@ void loop() screen.debug()->setChannelNameStatus(channelSettings.name); screen.debug()->setPowerStatus(powerStatus); // TODO(#4): use something based on hdop to show GPS "signal" strength. - screen.debug()->setGPSStatus(gps.hasLock() ? "ok" : ":("); + screen.debug()->setGPSStatus(gps->hasLock() ? "good" : "bad"); // No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in) // i.e. don't just keep spinning in loop as fast as we can. diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 1b05e0c51..7e9fc4486 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -84,7 +84,7 @@ void MeshService::init() sendOwnerPeriod.setup(); nodeDB.init(); - gpsObserver.observe(&gps); + gpsObserver.observe(gps); packetReceivedObserver.observe(&router.notifyPacketReceived); } @@ -153,7 +153,7 @@ void MeshService::handleIncomingPosition(const MeshPacket *mp) tv.tv_sec = secs; tv.tv_usec = 0; - gps.perhapsSetRTC(&tv); + perhapsSetRTC(&tv); } } else { DEBUG_MSG("Ignoring incoming packet - not a position\n"); @@ -165,7 +165,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp) powerFSM.trigger(EVENT_RECEIVED_PACKET); // Possibly keep the node from sleeping // If it is a position packet, perhaps set our clock (if we don't have a GPS of our own, otherwise wait for that to work) - if (!gps.isConnected) + if (!gps->isConnected) handleIncomingPosition(mp); else { DEBUG_MSG("Ignoring incoming time, because we have a GPS\n"); @@ -234,8 +234,8 @@ void MeshService::handleToRadio(MeshPacket &p) if (p.id == 0) p.id = generatePacketId(); // If the phone didn't supply one, then pick one - p.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone - // (so we update our nodedb for the local node) + p.rx_time = getValidTime(); // Record the time the packet arrived from the phone + // (so we update our nodedb for the local node) // Send the packet into the mesh @@ -258,7 +258,7 @@ void MeshService::sendToMesh(MeshPacket *p) // nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless // devices can get time. if (p->has_payload && p->payload.has_position) { - if (!gps.isConnected) { + if (!gps->isConnected) { DEBUG_MSG("Stripping time %u from position send\n", p->payload.position.time); p->payload.position.time = 0; } else @@ -286,7 +286,7 @@ MeshPacket *MeshService::allocForSending() p->from = nodeDB.getNodeNum(); p->to = NODENUM_BROADCAST; p->id = generatePacketId(); - p->rx_time = gps.getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp + p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp return p; } @@ -315,7 +315,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) p->payload.has_position = true; p->payload.position = node->position; p->payload.want_response = wantReplies; - p->payload.position.time = gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. + p->payload.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. sendToMesh(p); } @@ -329,12 +329,12 @@ int MeshService::onGPSChanged(void *unused) Position &pos = p->payload.position; // !zero or !zero lat/long means valid - if (gps.latitude != 0 || gps.longitude != 0) { - if (gps.altitude != 0) - pos.altitude = gps.altitude; - pos.latitude_i = gps.latitude; - pos.longitude_i = gps.longitude; - pos.time = gps.getValidTime(); + if (gps->latitude != 0 || gps->longitude != 0) { + if (gps->altitude != 0) + pos.altitude = gps->altitude; + pos.latitude_i = gps->latitude; + pos.longitude_i = gps->longitude; + pos.time = getValidTime(); } // We limit our GPS broadcasts to a max rate diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 0559754a3..73b3391d6 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -246,7 +246,7 @@ const NodeInfo *NodeDB::readNextInfo() /// Given a node, return how many seconds in the past (vs now) that we last heard from it uint32_t sinceLastSeen(const NodeInfo *n) { - uint32_t now = gps.getTime(); + uint32_t now = getTime(); uint32_t last_seen = n->position.time; int delta = (int)(now - last_seen); diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index b5a34a801..6e5734e59 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -68,7 +68,7 @@ void Router::handleReceived(MeshPacket *p) { // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function // Also, we should set the time from the ISR and it should have msec level resolution - p->rx_time = gps.getValidTime(); // store the arrival timestamp for the phone + p->rx_time = getValidTime(); // store the arrival timestamp for the phone DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); notifyPacketReceived.notifyObservers(p); diff --git a/src/sleep.cpp b/src/sleep.cpp index 9693a8fc6..4f5fa2fdc 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -29,6 +29,7 @@ extern AXP20X_Class axp; Observable preflightSleep; /// Called to tell observers we are now entering sleep and you should prepare. Must return 0 +/// notifySleep will be called for light or deep sleep, notifyDeepSleep is only called for deep sleep Observable notifySleep, notifyDeepSleep; // deep sleep support @@ -125,12 +126,6 @@ static bool doPreflightSleep() /// Tell devices we are going to sleep and wait for them to handle things static void waitEnterSleep() { - /* - former hardwired code - now moved into notifySleep callbacks: - // Put radio in sleep mode (will still draw power but only 0.2uA) - service.radio.radioIf.sleep(); - */ - uint32_t now = millis(); while (!doPreflightSleep()) { delay(100); // Kinda yucky - wait until radio says say we can shutdown (finished in process sends/receives) @@ -144,7 +139,6 @@ static void waitEnterSleep() // Code that still needs to be moved into notifyObservers Serial.flush(); // send all our characters before we stop cpu clock setBluetoothEnable(false); // has to be off before calling light sleep - gps.prepareSleep(); // abandon in-process parsing notifySleep.notifyObservers(NULL); } @@ -157,6 +151,7 @@ void doDeepSleep(uint64_t msecToWake) // not using wifi yet, but once we are this is needed to shutoff the radio hw // esp_wifi_stop(); waitEnterSleep(); + notifySleep.notifyObservers(NULL); // also tell the regular sleep handlers notifyDeepSleep.notifyObservers(NULL); screen.setOn(false); // datasheet says this will draw only 10ua From 101eef5495c9cbefdfbc3301f604e73476d2b576 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 4 May 2020 11:21:24 -0700 Subject: [PATCH 150/197] oops lat/lon need to be signed ;-) --- proto | 2 +- src/gps/GPS.h | 4 ++-- src/mesh/mesh.pb.h | 18 +++++++++--------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/proto b/proto index cabbdf51e..b35e7fb17 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit cabbdf51ed365b72ab995ad24b075269627f58ad +Subproject commit b35e7fb17e80a9761145d69a288a9e87af862cab diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 7a1a79a04..1c25ec2f4 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -28,8 +28,8 @@ class GPS : public Observable static HardwareSerial &_serial_gps; public: - uint32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double - uint32_t altitude = 0; + int32_t latitude = 0, longitude = 0; // as an int mult by 1e-7 to get value as double + int32_t altitude = 0; bool isConnected = false; // Do we have a GPS we are talking to virtual ~GPS() {} diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 7f25f1c99..ba2293a0a 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -300,8 +300,8 @@ typedef struct _ToRadio { X(a, STATIC, SINGULAR, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, INT32, battery_level, 4) \ X(a, STATIC, SINGULAR, UINT32, time, 6) \ -X(a, STATIC, SINGULAR, INT32, latitude_i, 7) \ -X(a, STATIC, SINGULAR, INT32, longitude_i, 8) +X(a, STATIC, SINGULAR, SINT32, latitude_i, 7) \ +X(a, STATIC, SINGULAR, SINT32, longitude_i, 8) #define Position_CALLBACK NULL #define Position_DEFAULT NULL @@ -486,21 +486,21 @@ extern const pb_msgdesc_t ToRadio_msg; #define ToRadio_fields &ToRadio_msg /* Maximum encoded size of messages (where known) */ -#define Position_size 50 +#define Position_size 40 #define Data_size 256 #define User_size 72 /* RouteDiscovery_size depends on runtime parameters */ -#define SubPacket_size 387 -#define MeshPacket_size 429 +#define SubPacket_size 377 +#define MeshPacket_size 419 #define ChannelSettings_size 44 #define RadioConfig_size 120 #define RadioConfig_UserPreferences_size 72 -#define NodeInfo_size 142 +#define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 19185 +#define DeviceState_size 18535 #define DebugString_size 258 -#define FromRadio_size 438 -#define ToRadio_size 432 +#define FromRadio_size 428 +#define ToRadio_size 422 #ifdef __cplusplus } /* extern "C" */ From c2be6c4068d474c17f4cf22951101efde93dfd10 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 4 May 2020 17:39:57 -0700 Subject: [PATCH 151/197] WIP on #124 --- .vscode/settings.json | 1 + docs/software/nrf52-TODO.md | 3 +- platformio.ini | 3 +- src/gps/GPS.cpp | 18 ++++++++++++ src/gps/GPS.h | 6 +++- src/gps/NEMAGPS.cpp | 56 +++++++++++++++++++++++++++++++++++++ src/gps/NEMAGPS.h | 19 +++++++++++++ src/gps/UBloxGPS.cpp | 50 ++++++++++++--------------------- src/main.cpp | 9 +++++- 9 files changed, 129 insertions(+), 36 deletions(-) create mode 100644 src/gps/NEMAGPS.cpp create mode 100644 src/gps/NEMAGPS.h diff --git a/.vscode/settings.json b/.vscode/settings.json index bfef8191b..ebed64343 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -52,6 +52,7 @@ "cSpell.words": [ "Blox", "Meshtastic", + "NEMAGPS", "Ublox", "descs", "protobufs" diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 6bc5fb0d2..59a467fef 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -9,7 +9,7 @@ Minimum items needed to make sure hardware is good. - plug in correct variants for the real board - Use the PMU driver on real hardware - add a NEMA based GPS driver to test GPS -- Use new radio driver on real hardware +- Use new radio driver on real hardware - Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI - test the LEDs - test the buttons @@ -24,6 +24,7 @@ Minimum items needed to make sure hardware is good. Needed to be fully functional at least at the same level of the ESP32 boards. At this point users would probably want them. +- stop polling for GPS characters, instead stay blocked on read in a thread - increase preamble length? - will break other clients? so all devices must update - enable BLE DFU somehow - set appversion/hwversion diff --git a/platformio.ini b/platformio.ini index b64bcf002..61b61d677 100644 --- a/platformio.ini +++ b/platformio.ini @@ -74,7 +74,8 @@ lib_deps = https://github.com/meshtastic/arduino-fsm.git https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git https://github.com/meshtastic/RadioLib.git - + https://github.com/meshtastic/TinyGPSPlus.git + ; Common settings for ESP targes, mixin with extends = esp32_base [esp32_base] src_filter = diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 9273a6ed2..d8d73784e 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -50,6 +50,24 @@ void perhapsSetRTC(const struct timeval *tv) } } +void perhapsSetRTC(struct tm &t) +{ + /* Convert to unix time + The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 + (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). + */ + time_t res = mktime(&t); + struct timeval tv; + tv.tv_sec = res; + tv.tv_usec = 0; // time.centisecond() * (10 / 1000); + + DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); + if (t.tm_year < 0 || t.tm_year >= 300) + DEBUG_MSG("Ignoring invalid GPS time\n"); + else + perhapsSetRTC(&tv); +} + #include uint32_t getTime() diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 1c25ec2f4..3eb972843 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -6,6 +6,7 @@ /// If we haven't yet set our RTC this boot, set it from a GPS derived time void perhapsSetRTC(const struct timeval *tv); +void perhapsSetRTC(struct tm &t); /// Return time since 1970 in secs. Until we have a GPS lock we will be returning time based at zero uint32_t getTime(); @@ -37,7 +38,10 @@ class GPS : public Observable /** * Returns true if we succeeded */ - virtual bool setup() = 0; + virtual bool setup() { return true; } + + /// A loop callback for subclasses that need it. FIXME, instead just block on serial reads + virtual void loop() {} /// Returns ture if we have acquired GPS lock. bool hasLock() const { return hasValidLocation; } diff --git a/src/gps/NEMAGPS.cpp b/src/gps/NEMAGPS.cpp new file mode 100644 index 000000000..74cdf7606 --- /dev/null +++ b/src/gps/NEMAGPS.cpp @@ -0,0 +1,56 @@ +#include "NEMAGPS.h" +#include "configuration.h" + +static int32_t toDegInt(RawDegrees d) +{ + int32_t degMult = 10000000; // 1e7 + int32_t r = d.deg * degMult + d.billionths / 100; + if (d.negative) + r *= -1; + return r; +} + +void NEMAGPS::loop() +{ + while (_serial_gps.available() > 0) { + int c = _serial_gps.read(); + Serial.write(c); + reader.encode(c); + } + + auto ti = reader.time; + auto d = reader.date; + if (ti.isUpdated() && ti.isValid() && d.isValid()) { + /* Convert to unix time +The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 +(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). +*/ + struct tm t; + t.tm_sec = ti.second(); + t.tm_min = ti.minute(); + t.tm_hour = ti.hour(); + t.tm_mday = d.day(); + t.tm_mon = d.month() - 1; + t.tm_year = d.year() - 1900; + t.tm_isdst = false; + perhapsSetRTC(t); + } + + if (reader.altitude.isUpdated() || reader.location.isUpdated()) { // probably get updated at the same time + if (reader.altitude.isValid()) + altitude = reader.altitude.meters(); + + auto loc = reader.location; + if (loc.isValid()) { + latitude = toDegInt(loc.rawLat()); + longitude = toDegInt(loc.rawLng()); + } + + // expect gps pos lat=37.520825, lon=-122.309162, alt=158 + DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); + + hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 + if (hasValidLocation) + notifyObservers(NULL); + } +} \ No newline at end of file diff --git a/src/gps/NEMAGPS.h b/src/gps/NEMAGPS.h new file mode 100644 index 000000000..ddaf77ee2 --- /dev/null +++ b/src/gps/NEMAGPS.h @@ -0,0 +1,19 @@ +#pragma once + +#include "GPS.h" +#include "Observer.h" +#include "PeriodicTask.h" +#include "TinyGPS++.h" + +/** + * A gps class thatreads from a NEMA GPS stream (and FIXME - eventually keeps the gps powered down except when reading) + * + * When new data is available it will notify observers. + */ +class NEMAGPS : public GPS +{ + TinyGPSPlus reader; + + public: + virtual void loop(); +}; diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp index 881fd2977..54f7810ee 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -9,8 +9,6 @@ UBloxGPS::UBloxGPS() : PeriodicTask() bool UBloxGPS::setup() { - PeriodicTask::setup(); - #ifdef GPS_RX_PIN _serial_gps.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); #else @@ -30,18 +28,18 @@ bool UBloxGPS::setup() if (isConnected) { DEBUG_MSG("Connected to UBLOX GPS successfully\n"); - bool factoryReset = false; + bool factoryReset = true; bool ok; if (factoryReset) { // It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have // GPS_TX connected) ublox.factoryReset(); - delay(2000); + delay(3000); isConnected = ublox.begin(_serial_gps); DEBUG_MSG("Factory reset success=%d\n", isConnected); - if (isConnected) { - ublox.assumeAutoPVT(true, true); // Just parse NEMA for now - } + ok = ublox.saveConfiguration(3000); + assert(ok); + return false; } else { ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API assert(ok); @@ -57,12 +55,10 @@ bool UBloxGPS::setup() ok = ublox.saveConfiguration(3000); assert(ok); + PeriodicTask::setup(); // We don't start our periodic task unless we actually found the device + return true; } else { - // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just - // assume NEMA at 9600 baud. - DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n"); - return false; } } @@ -80,26 +76,24 @@ void UBloxGPS::doTask() { uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix - if (isConnected) { - // Consume all characters that have arrived + assert(isConnected); - // getPVT automatically calls checkUblox - ublox.checkUblox(); // See if new data is available. Process bytes as they come in. + // Consume all characters that have arrived - // If we don't have a fix (a quick check), don't try waiting for a solution) - // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions - // turn off for now - // fixtype = ublox.getFixType(); - DEBUG_MSG("fix type %d\n", fixtype); - } + // getPVT automatically calls checkUblox + ublox.checkUblox(); // See if new data is available. Process bytes as they come in. + + // If we don't have a fix (a quick check), don't try waiting for a solution) + // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions + // turn off for now + // fixtype = ublox.getFixType(); + DEBUG_MSG("fix type %d\n", fixtype); // DEBUG_MSG("sec %d\n", ublox.getSecond()); // DEBUG_MSG("lat %d\n", ublox.getLatitude()); // any fix that has time if (ublox.getT()) { - struct timeval tv; - /* Convert to unix time The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). @@ -112,15 +106,7 @@ The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of s t.tm_mon = ublox.getMonth() - 1; t.tm_year = ublox.getYear() - 1900; t.tm_isdst = false; - time_t res = mktime(&t); - tv.tv_sec = res; - tv.tv_usec = 0; // time.centisecond() * (10 / 1000); - - DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); - if (t.tm_year < 0 || t.tm_year >= 300) - DEBUG_MSG("Ignoring invalid GPS time\n"); - else - perhapsSetRTC(&tv); + perhapsSetRTC(t); } if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only diff --git a/src/main.cpp b/src/main.cpp index b70ddb99f..3971ae9bf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ #include "MeshRadio.h" #include "MeshService.h" +#include "NEMAGPS.h" #include "NodeDB.h" #include "Periodic.h" #include "PowerFSM.h" @@ -193,7 +194,12 @@ void setup() // Init GPS - first try ublox gps = new UBloxGPS(); if (!gps->setup()) { - // FIXME - fallback to NEMA + // Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just + // assume NEMA at 9600 baud. + DEBUG_MSG("ERROR: No UBLOX GPS found, hoping that NEMA might work\n"); + delete gps; + gps = new NEMAGPS(); + gps->setup(); } service.init(); @@ -263,6 +269,7 @@ void loop() { uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop? + gps->loop(); // FIXME, remove from main, instead block on read router.loop(); powerFSM.run_machine(); service.loop(); From dcd1f7478a510c454f32099ab946c383368ac884 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 4 May 2020 20:02:43 -0700 Subject: [PATCH 152/197] fix 124 - we now fallback to nema if we can't talk ublox protocol to the GPS. Though we are super power inefficient about it so TODO/FIXME someday to decrease our power draw. --- docs/software/nrf52-TODO.md | 2 +- src/gps/GPS.cpp | 2 +- src/gps/NEMAGPS.cpp | 69 +++++++++++++++++++++---------------- src/gps/NEMAGPS.h | 2 ++ src/gps/UBloxGPS.cpp | 2 +- 5 files changed, 44 insertions(+), 33 deletions(-) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 59a467fef..a60cf2af9 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -8,7 +8,6 @@ Minimum items needed to make sure hardware is good. - use "variants" to get all gpio bindings - plug in correct variants for the real board - Use the PMU driver on real hardware -- add a NEMA based GPS driver to test GPS - Use new radio driver on real hardware - Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI - test the LEDs @@ -101,6 +100,7 @@ Nice ideas worth considering someday... - DONE neg 7 error code from receive - DONE remove unused sx1262 lib from github - at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. +- add a NEMA based GPS driver to test GPS ``` diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index d8d73784e..bb2d30b54 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -61,7 +61,7 @@ void perhapsSetRTC(struct tm &t) tv.tv_sec = res; tv.tv_usec = 0; // time.centisecond() * (10 / 1000); - DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); + // DEBUG_MSG("Got time from GPS month=%d, year=%d, unixtime=%ld\n", t.tm_mon, t.tm_year, tv.tv_sec); if (t.tm_year < 0 || t.tm_year >= 300) DEBUG_MSG("Ignoring invalid GPS time\n"); else diff --git a/src/gps/NEMAGPS.cpp b/src/gps/NEMAGPS.cpp index 74cdf7606..7d19f8869 100644 --- a/src/gps/NEMAGPS.cpp +++ b/src/gps/NEMAGPS.cpp @@ -12,45 +12,54 @@ static int32_t toDegInt(RawDegrees d) void NEMAGPS::loop() { + while (_serial_gps.available() > 0) { int c = _serial_gps.read(); - Serial.write(c); + // Serial.write(c); reader.encode(c); } - auto ti = reader.time; - auto d = reader.date; - if (ti.isUpdated() && ti.isValid() && d.isValid()) { - /* Convert to unix time -The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 -(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). -*/ - struct tm t; - t.tm_sec = ti.second(); - t.tm_min = ti.minute(); - t.tm_hour = ti.hour(); - t.tm_mday = d.day(); - t.tm_mon = d.month() - 1; - t.tm_year = d.year() - 1900; - t.tm_isdst = false; - perhapsSetRTC(t); - } + uint32_t now = millis(); + if ((now - lastUpdateMsec) > 20 * 1000) { // Ugly hack for now - limit update checks to once every 20 secs (but still consume + // serial chars at whatever rate) + lastUpdateMsec = now; - if (reader.altitude.isUpdated() || reader.location.isUpdated()) { // probably get updated at the same time - if (reader.altitude.isValid()) - altitude = reader.altitude.meters(); + auto ti = reader.time; + auto d = reader.date; + if (ti.isUpdated() && ti.isValid() && d.isValid()) { + /* Convert to unix time + The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 + (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z). + */ + struct tm t; + t.tm_sec = ti.second(); + t.tm_min = ti.minute(); + t.tm_hour = ti.hour(); + t.tm_mday = d.day(); + t.tm_mon = d.month() - 1; + t.tm_year = d.year() - 1900; + t.tm_isdst = false; + perhapsSetRTC(t); - auto loc = reader.location; - if (loc.isValid()) { - latitude = toDegInt(loc.rawLat()); - longitude = toDegInt(loc.rawLng()); + isConnected = true; // we seem to have a real GPS (but not necessarily a lock) } - // expect gps pos lat=37.520825, lon=-122.309162, alt=158 - DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); + if (reader.location.isUpdated()) { + if (reader.altitude.isValid()) + altitude = reader.altitude.meters(); - hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 - if (hasValidLocation) - notifyObservers(NULL); + if (reader.location.isValid()) { + auto loc = reader.location.value(); + latitude = toDegInt(loc.lat); + longitude = toDegInt(loc.lng); + } + + // expect gps pos lat=37.520825, lon=-122.309162, alt=158 + DEBUG_MSG("new NEMA GPS pos lat=%f, lon=%f, alt=%d\n", latitude * 1e-7, longitude * 1e-7, altitude); + + hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0 + if (hasValidLocation) + notifyObservers(NULL); + } } } \ No newline at end of file diff --git a/src/gps/NEMAGPS.h b/src/gps/NEMAGPS.h index ddaf77ee2..5bea0d41f 100644 --- a/src/gps/NEMAGPS.h +++ b/src/gps/NEMAGPS.h @@ -13,6 +13,8 @@ class NEMAGPS : public GPS { TinyGPSPlus reader; + + uint32_t lastUpdateMsec = 0; public: virtual void loop(); diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp index 54f7810ee..560c52fa8 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -28,7 +28,7 @@ bool UBloxGPS::setup() if (isConnected) { DEBUG_MSG("Connected to UBLOX GPS successfully\n"); - bool factoryReset = true; + bool factoryReset = false; bool ok; if (factoryReset) { // It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have From 95df7dd8dc6e50ab369dbb529cbe28b71494169d Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 4 May 2020 20:04:44 -0700 Subject: [PATCH 153/197] 0.6.2 --- bin/version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/version.sh b/bin/version.sh index 6b0158eb3..585b24030 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.6.1 \ No newline at end of file +export VERSION=0.6.2 \ No newline at end of file From 8bfe9fa8fc3a9435d72ada0c2bedd3b872989855 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 5 May 2020 18:40:17 -0700 Subject: [PATCH 154/197] 0.6.3 - fix the problem of BLE message receiption being busted in 0.6.2 --- bin/version.sh | 2 +- src/mesh/PhoneAPI.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/version.sh b/bin/version.sh index 585b24030..fc9ebf0f0 100644 --- a/bin/version.sh +++ b/bin/version.sh @@ -1,3 +1,3 @@ -export VERSION=0.6.2 \ No newline at end of file +export VERSION=0.6.3 \ No newline at end of file diff --git a/src/mesh/PhoneAPI.h b/src/mesh/PhoneAPI.h index cb4ba1c34..f08c9009f 100644 --- a/src/mesh/PhoneAPI.h +++ b/src/mesh/PhoneAPI.h @@ -88,7 +88,7 @@ class PhoneAPI /** * Subclasses can use this as a hook to provide custom notifications for their transport (i.e. bluetooth notifies) */ - void onNowHasData(uint32_t fromRadioNum) {} + virtual void onNowHasData(uint32_t fromRadioNum) {} private: /** From fc0c9bcfe3faec7ccd40cb75465bd2bc7e3ea0e7 Mon Sep 17 00:00:00 2001 From: Mark Huson Date: Wed, 6 May 2020 19:43:17 -0700 Subject: [PATCH 155/197] add update script and README changes --- README.md | 5 ++++- bin/device-update.sh | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100755 bin/device-update.sh diff --git a/README.md b/README.md index cacf68c91..5e8bc1920 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,10 @@ Hard resetting via RTS pin... ``` 5. cd into the directory where the release zip file was expanded. -6. Install the correct firmware for your board with "device-install.sh firmware-_board_-_country_.bin". For instance "./device-install.sh firmware-HELTEC-US-0.0.3.bin". +6. Install the correct firmware for your board with `device-install.sh firmware-_board_-_country_.bin`. + - Example: `./device-install.sh firmware-HELTEC-US-0.0.3.bin`. +7. To update run `device-update.sh firmware-_board_-_country_.bin` + - Example: `./device-update.sh firmware-HELTEC-US-0.0.3.bin`. Note: If you have previously installed meshtastic, you don't need to run this full script instead just run "esptool.py --baud 921600 write*flash 0x10000 firmware-\_board*-_country_.bin". This will be faster, also all of your current preferences will be preserved. diff --git a/bin/device-update.sh b/bin/device-update.sh new file mode 100755 index 000000000..b8a8dcf9b --- /dev/null +++ b/bin/device-update.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e + +FILENAME=$1 + +echo "Trying to update $FILENAME" +esptool.py --baud 921600 writeflash 0x10000 $FILENAME From 4da5d79e883f0b10a0308a211264effff75c6926 Mon Sep 17 00:00:00 2001 From: Mark Huson Date: Wed, 6 May 2020 19:45:02 -0700 Subject: [PATCH 156/197] add device-update to zip --- bin/build-all.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/build-all.sh b/bin/build-all.sh index 44a7d875c..edea28cd6 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -64,6 +64,6 @@ Generated by bin/buildall.sh --> XML rm -f $ARCHIVEDIR/firmware-$VERSION.zip -zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh +zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/bins/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh bin/device-update.sh -echo BUILT ALL \ No newline at end of file +echo BUILT ALL From c4a1fe0f36b662e5b14bfd335753f7f354f4ca6e Mon Sep 17 00:00:00 2001 From: Dafeman <47490997+Dafeman@users.noreply.github.com> Date: Sat, 9 May 2020 23:09:36 +1200 Subject: [PATCH 157/197] Pad Bluetooth passkey to 6 digits --- lib/BluetoothOTA/src/BluetoothUtil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/BluetoothOTA/src/BluetoothUtil.cpp b/lib/BluetoothOTA/src/BluetoothUtil.cpp index 0da8df85d..ccaf41639 100644 --- a/lib/BluetoothOTA/src/BluetoothUtil.cpp +++ b/lib/BluetoothOTA/src/BluetoothUtil.cpp @@ -184,7 +184,7 @@ class MySecurity : public BLESecurityCallbacks void onPassKeyNotify(uint32_t pass_key) { - Serial.printf("onPassKeyNotify %u\n", pass_key); + Serial.printf("onPassKeyNotify %06u\n", pass_key); startCb(pass_key); } From 28d21ecdcc65f04ced7d4b368cc8706c14cb0fd3 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 16:15:01 -0700 Subject: [PATCH 158/197] begin work on crypto --- docs/software/TODO.md | 1 - docs/software/cypto.md | 38 ++++++++++++++++++++++++++++++++++++++ proto | 2 +- src/mesh/CryptoEngine.cpp | 16 ++++++++++++++++ src/mesh/CryptoEngine.h | 31 +++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 docs/software/cypto.md create mode 100644 src/mesh/CryptoEngine.cpp create mode 100644 src/mesh/CryptoEngine.h diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 2d80da3c1..23efd6ee3 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -29,7 +29,6 @@ fetches the fresh nodedb. - rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms. - assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel" - Note: we do not do address filtering at the chip level, because we might need to route for the mesh -- add basic crypto - https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation - use ECB at first (though it is shit) because it doesn't require us to send 16 bytes of IV with each packet. Then OFB per example. Possibly do this crypto at the data payload level only, so that all of the packet routing metadata is in cleartext (so that nodes will route for other radios that are cryptoed with a key we don't know) - add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting - share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app. diff --git a/docs/software/cypto.md b/docs/software/cypto.md new file mode 100644 index 000000000..d1c0081ad --- /dev/null +++ b/docs/software/cypto.md @@ -0,0 +1,38 @@ +# Encryption in Meshtastic + +Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutions to our implementation. However, +the project developers are not cryptography experts. Therefore we ask two things: + +- If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our + notes below and offering advice? We will happily give as much or as little credit as you wish as our thanks ;-). +- Consider our existing solution 'alpha' and probably fairly secure against an not very aggressive adversary. But until + it is reviewed by someone smarter than us, assume it might have flaws. + +## Notes on implementation + +- We do all crypto at the SubPacket (payload) level only, so that all meshtastic nodes will route for others - even those channels which are encrypted with a different key. +- Mostly based on reading [Wikipedia]() and using the modes the ESP32 provides support for in hardware. +- We use AES256-CTR as a stream cypher (with zero padding on the last BLOCK) because it is well supported with hardware acceleration. + +Parameters for our CTR implementation: + +- Our AES key is 256 bits, shared as part of the 'Channel' specification. +- Each SubPacket will be sent as a series of 16 byte BLOCKS. +- The node number concatenated with the packet number is used as the NONCE. This counter will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero. The packet number is sent + in cleartext with each packet. The node number can be derived from the "from" field of each packet. +- Each BLOCK for a packet has an incrementing COUNTER. COUNTER starts at zero for the first block of each packet. +- The IV for each block is constructed by concatenating the NONCE as the upper 96 bits of the IV and the COUNTER as the bottom 32 bits. Note: since our packets are small counter will really never be higher than 32 (five bits). + +``` +You can encrypt separate messages by dividing the nonce_counter buffer in two areas: the first one used for a per-message nonce, handled by yourself, and the second one updated by this function internally. +For example, you might reserve the first 12 bytes for the per-message nonce, and the last 4 bytes for internal use. In that case, before calling this function on a new message you need to set the first 12 bytes of nonce_counter to your chosen nonce value, the last 4 to 0, and nc_off to 0 (which will cause stream_block to be ignored). That way, you can encrypt at most 2**96 messages of up to 2**32 blocks each with the same key. + +The per-message nonce (or information sufficient to reconstruct it) needs to be communicated with the ciphertext and must be unique. The recommended way to ensure uniqueness is to use a message counter. An alternative is to generate random nonces, but this limits the number of messages that can be securely encrypted: for example, with 96-bit random nonces, you should not encrypt more than 2**32 messages with the same key. + +Note that for both stategies, sizes are measured in blocks and that an AES block is 16 bytes. +``` + +## Example code links + +- Example code for [NRF52](https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.0.0/lib_crypto_aes.html#sub_aes_ctr) +- Example code for [ESP32](https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c) diff --git a/proto b/proto index b35e7fb17..1a5afe8f5 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit b35e7fb17e80a9761145d69a288a9e87af862cab +Subproject commit 1a5afe8f567866981ac0cd0a75a95503064d33c2 diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp new file mode 100644 index 000000000..3399bd9ac --- /dev/null +++ b/src/mesh/CryptoEngine.cpp @@ -0,0 +1,16 @@ +#include "CryptoEngine.h" +#include "configuration.h" + +void CryptoEngine::setKey(size_t numBytes, const uint8_t *bytes) +{ + DEBUG_MSG("WARNING: Using stub crypto - all crypto is sent in plaintext!\n"); +} + +/** + * Encrypt a packet + * + * @param bytes is updated in place + */ +void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) {} + +void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) {} \ No newline at end of file diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h new file mode 100644 index 000000000..0b733a6b9 --- /dev/null +++ b/src/mesh/CryptoEngine.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +/** + * see docs/software/crypto.md for details. + * + * The NONCE is constructed by concatenating: + * a 32 bit sending node number + a 64 bit packet number + a 32 bit block counter (starts at zero) + */ + +class CryptoEngine +{ + public: + /** + * Set the key used for encrypt, decrypt. + * + * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. + * + * @param numBytes must be 32 for now (AES256) + */ + void setKey(size_t numBytes, const uint8_t *bytes); + + /** + * Encrypt a packet + * + * @param bytes is updated in place + */ + void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes); + void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes); +}; \ No newline at end of file From b73dd5b23bc80beab58de275b7dd4fe8e5caeea6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 16:15:16 -0700 Subject: [PATCH 159/197] misc todo --- docs/software/nrf52-TODO.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index a60cf2af9..4f4b6c409 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -62,6 +62,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- use the Jumper simulator to run meshes of simulated hardware: https://docs.jumper.io/docs/install.html - 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 From e6875d559c600046d6576c5dc667f3044244e43e Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 16:32:26 -0700 Subject: [PATCH 160/197] Remove MeshRadio wrapper class - we don't need it anymore. --- src/main.cpp | 8 +-- src/mesh/MeshRadio.cpp | 113 --------------------------------- src/mesh/MeshRadio.h | 47 -------------- src/mesh/RadioInterface.cpp | 65 ++++++++++++++++++- src/mesh/RadioInterface.h | 33 ++++++++++ src/mesh/RadioLibInterface.cpp | 2 + src/mesh/RadioLibInterface.h | 4 +- 7 files changed, 105 insertions(+), 167 deletions(-) delete mode 100644 src/mesh/MeshRadio.cpp diff --git a/src/main.cpp b/src/main.cpp index 3971ae9bf..b6c03846f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -104,8 +104,6 @@ const char *getDeviceName() return name; } -static MeshRadio *radio = NULL; - static uint32_t ledBlinker() { static bool ledOn; @@ -231,10 +229,10 @@ void setup() #else new SimRadio(); #endif - radio = new MeshRadio(rIf); - router.addInterface(&radio->radioIf); - if (radio && !radio->init()) + router.addInterface(rIf); + + if (!rIf->init()) recordCriticalError(ErrNoRadio); // This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values diff --git a/src/mesh/MeshRadio.cpp b/src/mesh/MeshRadio.cpp deleted file mode 100644 index 53c052be5..000000000 --- a/src/mesh/MeshRadio.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include "error.h" -#include -#include - -#include "MeshRadio.h" -#include "MeshService.h" -#include "NodeDB.h" -#include "configuration.h" -#include "sleep.h" -#include -#include - -/** - * ## LoRaWAN for North America - -LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments. - -The maximum output power for North America is +30 dBM. - -The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are -separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. -*/ - -/// Sometimes while debugging it is useful to set this false, to disable rf95 accesses -bool useHardware = true; - -MeshRadio::MeshRadio(RadioInterface *rIf) : radioIf(*rIf) // , manager(radioIf) -{ - myNodeInfo.num_channels = NUM_CHANNELS; - - // Can't print strings this early - serial not setup yet - // DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name); -} - -bool MeshRadio::init() -{ - if (!useHardware) - return true; - - DEBUG_MSG("Starting meshradio init...\n"); - - configChangedObserver.observe(&service.configChanged); - preflightSleepObserver.observe(&preflightSleep); - notifyDeepSleepObserver.observe(¬ifyDeepSleep); - -#ifdef RESET_GPIO - pinMode(RESET_GPIO, OUTPUT); // Deassert reset - digitalWrite(RESET_GPIO, HIGH); - - // pulse reset - digitalWrite(RESET_GPIO, LOW); - delay(10); - digitalWrite(RESET_GPIO, HIGH); - delay(10); -#endif - - // we now expect interfaces to operate in promiscous mode - // radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor - // time. - - applySettings(); - - if (!radioIf.init()) { - DEBUG_MSG("LoRa radio init failed\n"); - return false; - } - - // No need to call this now, init is supposed to do same. reloadConfig(); - - return true; -} - -/** hash a string into an integer - * - * djb2 by Dan Bernstein. - * http://www.cse.yorku.ca/~oz/hash.html - */ -unsigned long hash(char *str) -{ - unsigned long hash = 5381; - int c; - - while ((c = *str++) != 0) - hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */ - - return hash; -} - -/** - * Pull our channel settings etc... from protobufs to the dumb interface settings - */ -void MeshRadio::applySettings() -{ - // Set up default configuration - // No Sync Words in LORA mode. - radioIf.modemConfig = (ModemConfigChoice)channelSettings.modem_config; - - // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM - int channel_num = hash(channelSettings.name) % NUM_CHANNELS; - radioIf.freq = CH0 + CH_SPACING * channel_num; - radioIf.power = channelSettings.tx_power; - - DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config, - channel_num, channelSettings.tx_power); -} - -int MeshRadio::reloadConfig(void *unused) -{ - applySettings(); - radioIf.reconfigure(); - - return 0; -} diff --git a/src/mesh/MeshRadio.h b/src/mesh/MeshRadio.h index 85c3e77fd..3d41d23ca 100644 --- a/src/mesh/MeshRadio.h +++ b/src/mesh/MeshRadio.h @@ -2,9 +2,7 @@ #include "MemoryPool.h" #include "MeshTypes.h" -#include "Observer.h" #include "PointerQueue.h" -#include "RadioInterface.h" #include "configuration.h" #include "mesh.pb.h" @@ -61,48 +59,3 @@ #define NUM_CHANNELS NUM_CHANNELS_US #endif -/** - * A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids) - * FIXME - REMOVE THIS CLASS - */ -class MeshRadio -{ - public: - // Kinda ugly way of selecting different radio implementations, but soon this MeshRadio class will be going away - // entirely. At that point we can make things pretty. - RadioInterface &radioIf; - - /** pool is the pool we will alloc our rx packets from - * rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool - */ - MeshRadio(RadioInterface *rIf); - - bool init(); - - private: - CallbackObserver configChangedObserver = - CallbackObserver(this, &MeshRadio::reloadConfig); - - CallbackObserver preflightSleepObserver = - CallbackObserver(this, &MeshRadio::preflightSleepCb); - - CallbackObserver notifyDeepSleepObserver = - CallbackObserver(this, &MeshRadio::notifyDeepSleepDb); - - /// The radioConfig object just changed, call this to force the hw to change to the new settings - int reloadConfig(void *unused = NULL); - - /// Return 0 if sleep is okay - int preflightSleepCb(void *unused = NULL) { return radioIf.canSleep() ? 0 : 1; } - - int notifyDeepSleepDb(void *unused = NULL) - { - radioIf.sleep(); - return 0; - } - - /** - * Pull our channel settings etc... from protobufs to the dumb interface settings - */ - void applySettings(); -}; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index d4a408425..18d6ec6fe 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -1,28 +1,91 @@ #include "RadioInterface.h" +#include "MeshRadio.h" +#include "MeshService.h" #include "NodeDB.h" #include "assert.h" #include "configuration.h" +#include "sleep.h" #include #include #include +/** + * ## LoRaWAN for North America + +LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments. + +The maximum output power for North America is +30 dBM. + +The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are +separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency. +*/ + // 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 + + myNodeInfo.num_channels = NUM_CHANNELS; + + // Can't print strings this early - serial not setup yet + // DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name); } bool RadioInterface::init() { - // we want this thread to run at very high priority, because it is effectively running as a user space ISR + DEBUG_MSG("Starting meshradio init...\n"); + + configChangedObserver.observe(&service.configChanged); + preflightSleepObserver.observe(&preflightSleep); + notifyDeepSleepObserver.observe(¬ifyDeepSleep); + + // we now expect interfaces to operate in promiscous mode + // radioIf.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor + // time. + + // we want this thread to run at very high priority, because it is effectively running as a user space ISR start("radio", RADIO_STACK_SIZE, configMAX_PRIORITIES - 1); // Start our worker thread return true; } +/** hash a string into an integer + * + * djb2 by Dan Bernstein. + * http://www.cse.yorku.ca/~oz/hash.html + */ +unsigned long hash(char *str) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++) != 0) + hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */ + + return hash; +} + +/** + * Pull our channel settings etc... from protobufs to the dumb interface settings + */ +void RadioInterface::applyModemConfig() +{ + // Set up default configuration + // No Sync Words in LORA mode. + modemConfig = (ModemConfigChoice)channelSettings.modem_config; + + // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM + int channel_num = hash(channelSettings.name) % NUM_CHANNELS; + freq = CH0 + CH_SPACING * channel_num; + power = channelSettings.tx_power; + + DEBUG_MSG("Set radio: name=%s, config=%u, ch=%d, power=%d\n", channelSettings.name, channelSettings.modem_config, channel_num, + channelSettings.tx_power); +} + ErrorCode SimRadio::send(MeshPacket *p) { DEBUG_MSG("SimRadio.send\n"); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index cdfae79d9..6c7dbd79b 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -2,6 +2,7 @@ #include "MemoryPool.h" #include "MeshTypes.h" +#include "Observer.h" #include "PointerQueue.h" #include "WorkerThread.h" #include "mesh.pb.h" @@ -35,6 +36,15 @@ class RadioInterface : protected NotifiedWorkerThread friend class MeshRadio; // for debugging we let that class touch pool PointerQueue *rxDest = NULL; + CallbackObserver configChangedObserver = + CallbackObserver(this, &RadioInterface::reloadConfig); + + CallbackObserver preflightSleepObserver = + CallbackObserver(this, &RadioInterface::preflightSleepCb); + + CallbackObserver notifyDeepSleepObserver = + CallbackObserver(this, &RadioInterface::notifyDeepSleepDb); + protected: MeshPacket *sendingPacket = NULL; // The packet we are currently sending PointerQueue txQueue; @@ -104,6 +114,29 @@ class RadioInterface : protected NotifiedWorkerThread size_t beginSending(MeshPacket *p); virtual void loop() {} // Idle processing + + /** + * Convert our modemConfig enum into wf, sf, etc... + * + * These paramaters will be pull from the channelSettings global + */ + virtual void applyModemConfig(); + + private: + /// Return 0 if sleep is okay + int preflightSleepCb(void *unused = NULL) { return canSleep() ? 0 : 1; } + + int notifyDeepSleepDb(void *unused = NULL) + { + sleep(); + return 0; + } + + int reloadConfig(void *unused) + { + reconfigure(); + return 0; + } }; class SimRadio : public RadioInterface diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index ada0a337a..64e6cd2a1 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -58,6 +58,8 @@ RadioLibInterface *RadioLibInterface::instance; */ void RadioLibInterface::applyModemConfig() { + RadioInterface::applyModemConfig(); + switch (modemConfig) { case Bw125Cr45Sf128: ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range bw = 125; diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index e1e0f1a97..a090d132a 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -99,8 +99,10 @@ class RadioLibInterface : public RadioInterface protected: /** * Convert our modemConfig enum into wf, sf, etc... + * + * These paramaters will be pull from the channelSettings global */ - void applyModemConfig(); + virtual void applyModemConfig(); /** Could we send right now (i.e. either not actively receiving or transmitting)? */ virtual bool canSendImmediately(); From 1cc24de7874cb50af45b0c75127a87c3d64b81dc Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 17:51:20 -0700 Subject: [PATCH 161/197] stub encryptor seems nicely backwards compatible with old devices and apps --- proto | 2 +- src/mesh/CryptoEngine.cpp | 10 +++++-- src/mesh/MeshService.cpp | 44 +++++++++++++++--------------- src/mesh/NodeDB.cpp | 4 +-- src/mesh/RadioInterface.cpp | 9 ++---- src/mesh/RadioLibInterface.cpp | 19 +++++-------- src/mesh/Router.cpp | 50 ++++++++++++++++++++++++++++++---- src/mesh/mesh.pb.h | 20 +++++++++----- src/screen.cpp | 6 ++-- 9 files changed, 104 insertions(+), 60 deletions(-) diff --git a/proto b/proto index 1a5afe8f5..f775ebe36 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 1a5afe8f567866981ac0cd0a75a95503064d33c2 +Subproject commit f775ebe369cd9bd92d954d46f76ea0a4ae62078c diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 3399bd9ac..14774df3f 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -11,6 +11,12 @@ void CryptoEngine::setKey(size_t numBytes, const uint8_t *bytes) * * @param bytes is updated in place */ -void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) {} +void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) +{ + DEBUG_MSG("WARNING: noop encryption!\n"); +} -void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) {} \ No newline at end of file +void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) +{ + DEBUG_MSG("WARNING: noop decryption!\n"); +} \ No newline at end of file diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 7e9fc4486..992144b82 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -92,9 +92,9 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) { MeshPacket *p = allocForSending(); p->to = dest; - p->payload.want_response = wantReplies; - p->payload.has_user = true; - User &u = p->payload.user; + p->decoded.want_response = wantReplies; + p->decoded.has_user = true; + User &u = p->decoded.user; u = owner; DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); @@ -108,7 +108,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) bool isCollision = mp->from == myNodeInfo.my_node_num; // we win if we have a lower macaddr - bool weWin = memcmp(&owner.macaddr, &mp->payload.user.macaddr, sizeof(owner.macaddr)) < 0; + bool weWin = memcmp(&owner.macaddr, &mp->decoded.user.macaddr, sizeof(owner.macaddr)) < 0; if (isCollision) { if (weWin) { @@ -134,7 +134,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) sendOurOwner(mp->from); - String lcd = String("Joined: ") + mp->payload.user.long_name + "\n"; + String lcd = String("Joined: ") + mp->decoded.user.long_name + "\n"; screen.print(lcd.c_str()); } @@ -143,12 +143,12 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) void MeshService::handleIncomingPosition(const MeshPacket *mp) { - if (mp->has_payload && mp->payload.has_position) { - DEBUG_MSG("handled incoming position time=%u\n", mp->payload.position.time); + if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.has_position) { + DEBUG_MSG("handled incoming position time=%u\n", mp->decoded.position.time); - if (mp->payload.position.time) { + if (mp->decoded.position.time) { struct timeval tv; - uint32_t secs = mp->payload.position.time; + uint32_t secs = mp->decoded.position.time; tv.tv_sec = secs; tv.tv_usec = 0; @@ -171,7 +171,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp) DEBUG_MSG("Ignoring incoming time, because we have a GPS\n"); } - if (mp->has_payload && mp->payload.has_user) { + if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.has_user) { mp = handleFromRadioUser(mp); } @@ -192,7 +192,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp) MeshPacket *copied = packetPool.allocCopy(*mp); assert(toPhoneQueue.enqueue(copied, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages - if (mp->payload.want_response) + if (mp->decoded.want_response) sendNetworkPing(mp->from); } else { DEBUG_MSG("Not delivering vetoed User message\n"); @@ -257,12 +257,12 @@ void MeshService::sendToMesh(MeshPacket *p) // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other // nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless // devices can get time. - if (p->has_payload && p->payload.has_position) { + if (p->which_payload == MeshPacket_decoded_tag && p->decoded.has_position) { if (!gps->isConnected) { - DEBUG_MSG("Stripping time %u from position send\n", p->payload.position.time); - p->payload.position.time = 0; + DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time); + p->decoded.position.time = 0; } else - DEBUG_MSG("Providing time to mesh %u\n", p->payload.position.time); + DEBUG_MSG("Providing time to mesh %u\n", p->decoded.position.time); } // If the phone sent a packet just to us, don't send it out into the network @@ -282,7 +282,7 @@ MeshPacket *MeshService::allocForSending() { MeshPacket *p = packetPool.allocZeroed(); - p->has_payload = true; + p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. p->from = nodeDB.getNodeNum(); p->to = NODENUM_BROADCAST; p->id = generatePacketId(); @@ -312,10 +312,10 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); p->to = dest; - p->payload.has_position = true; - p->payload.position = node->position; - p->payload.want_response = wantReplies; - p->payload.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. + p->decoded.has_position = true; + p->decoded.position = node->position; + p->decoded.want_response = wantReplies; + p->decoded.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. sendToMesh(p); } @@ -325,9 +325,9 @@ int MeshService::onGPSChanged(void *unused) // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); - p->payload.has_position = true; + p->decoded.has_position = true; - Position &pos = p->payload.position; + Position &pos = p->decoded.position; // !zero or !zero lat/long means valid if (gps->latitude != 0 || gps->longitude != 0) { if (gps->altitude != 0) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 73b3391d6..50b4ca73c 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -274,8 +274,8 @@ size_t NodeDB::getNumOnlineNodes() /// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw void NodeDB::updateFrom(const MeshPacket &mp) { - if (mp.has_payload) { - const SubPacket &p = mp.payload; + if (mp.which_payload == MeshPacket_decoded_tag) { + const SubPacket &p = mp.decoded; DEBUG_MSG("Update DB node 0x%x, rx_time=%u\n", mp.from, mp.rx_time); int oldNumNodes = *numNodes; diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 18d6ec6fe..3ad60005c 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -107,7 +107,7 @@ size_t RadioInterface::beginSending(MeshPacket *p) assert(!sendingPacket); // DEBUG_MSG("sending queued packet on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", rf95.txGood(), rf95.rxGood(), rf95.rxBad()); - assert(p->has_payload); + assert(p->which_payload == MeshPacket_encrypted_tag); // It should have already been encoded by now lastTxStart = millis(); @@ -121,11 +121,8 @@ size_t RadioInterface::beginSending(MeshPacket *p) // if the sender nodenum is zero, that means uninitialized assert(h->from); - size_t numbytes = pb_encode_to_bytes(radiobuf + sizeof(PacketHeader), sizeof(radiobuf), SubPacket_fields, &p->payload) + - sizeof(PacketHeader); - - assert(numbytes <= MAX_RHPACKETLEN); + memcpy(radiobuf + sizeof(PacketHeader), p->encrypted.bytes, p->encrypted.size); sendingPacket = p; - return numbytes; + return p->encrypted.size + sizeof(PacketHeader); } \ No newline at end of file diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 64e6cd2a1..dee630e0c 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -289,24 +289,19 @@ void RadioLibInterface::handleReceiveInterrupt() } else { MeshPacket *mp = packetPool.allocZeroed(); - SubPacket *p = &mp->payload; - mp->from = h->from; mp->to = h->to; mp->id = h->id; addReceiveMetadata(mp); - if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) { - DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); - packetPool.release(mp); - // rxBad++; not really a hw error - } else { - // parsing was successful, queue for our recipient - mp->has_payload = true; - DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point + assert(payloadLen <= sizeof(mp->encrypted.bytes)); + memcpy(mp->encrypted.bytes, payload, payloadLen); + mp->encrypted.size = payloadLen; - deliverToReceiver(mp); - } + DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + + deliverToReceiver(mp); } } } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 6e5734e59..fe32a68a4 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -1,7 +1,11 @@ #include "Router.h" +#include "CryptoEngine.h" +#include "GPS.h" #include "configuration.h" #include "mesh-pb-constants.h" +CryptoEngine *crypto = new CryptoEngine(); + /** * Router todo * @@ -44,10 +48,30 @@ void Router::loop() /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. - * If the txmit queue is full it might return an error. + * If the txmit queue is full it might return an error. */ ErrorCode Router::send(MeshPacket *p) { + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // First convert from protobufs to raw bytes + if (p->which_payload == MeshPacket_decoded_tag) { + static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union + + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); + + assert(numbytes <= MAX_RHPACKETLEN); + crypto->encrypt(p->from, p->id, numbytes, bytes); + + // Copy back into the packet and set the variant type + memcpy(p->encrypted.bytes, bytes, numbytes); + p->encrypted.size = numbytes; + p->which_payload = MeshPacket_encrypted_tag; + } + if (iface) { // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); return iface->send(p); @@ -58,8 +82,6 @@ ErrorCode Router::send(MeshPacket *p) } } -#include "GPS.h" - /** * Handle any packet that is received by an interface on this node. * Note: some packets may merely being passed through this node and will be forwarded elsewhere. @@ -70,7 +92,25 @@ void Router::handleReceived(MeshPacket *p) // Also, we should set the time from the ISR and it should have msec level resolution p->rx_time = getValidTime(); // store the arrival timestamp for the phone - DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - notifyPacketReceived.notifyObservers(p); + assert(p->which_payload == + MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets + + // Try to decrypt the packet if we can + static uint8_t bytes[MAX_RHPACKETLEN]; + memcpy(bytes, p->encrypted.bytes, + p->encrypted.size); // we have to copy into a scratch buffer, because these bytes are a union with the decoded protobuf + crypto->decrypt(p->from, p->id, p->encrypted.size, bytes); + + // Take those raw bytes and convert them back into a well structured protobuf we can understand + if (!pb_decode_from_bytes(bytes, p->encrypted.size, SubPacket_fields, &p->decoded)) { + DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); + } else { + // parsing was successful, queue for our recipient + p->which_payload = MeshPacket_decoded_tag; + + DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + notifyPacketReceived.notifyObservers(p); + } + packetPool.release(p); } \ No newline at end of file diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index ba2293a0a..da04b00cd 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -122,11 +122,15 @@ typedef struct _SubPacket { bool want_response; } SubPacket; +typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t; typedef struct _MeshPacket { int32_t from; int32_t to; - bool has_payload; - SubPacket payload; + pb_size_t which_payload; + union { + SubPacket decoded; + MeshPacket_encrypted_t encrypted; + }; uint32_t rx_time; uint32_t id; float rx_snr; @@ -193,7 +197,7 @@ typedef struct _ToRadio { #define User_init_default {"", "", "", {0}} #define RouteDiscovery_init_default {{{NULL}, NULL}} #define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0} -#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0, 0, 0} +#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} #define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -208,7 +212,7 @@ typedef struct _ToRadio { #define User_init_zero {"", "", "", {0}} #define RouteDiscovery_init_zero {{{NULL}, NULL}} #define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0} -#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0, 0, 0} +#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} #define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -269,9 +273,10 @@ typedef struct _ToRadio { #define SubPacket_data_tag 3 #define SubPacket_user_tag 4 #define SubPacket_want_response_tag 5 +#define MeshPacket_decoded_tag 3 +#define MeshPacket_encrypted_tag 8 #define MeshPacket_from_tag 1 #define MeshPacket_to_tag 2 -#define MeshPacket_payload_tag 3 #define MeshPacket_rx_time_tag 4 #define MeshPacket_id_tag 6 #define MeshPacket_rx_snr_tag 7 @@ -338,13 +343,14 @@ X(a, STATIC, SINGULAR, BOOL, want_response, 5) #define MeshPacket_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, from, 1) \ X(a, STATIC, SINGULAR, INT32, to, 2) \ -X(a, STATIC, OPTIONAL, MESSAGE, payload, 3) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,decoded,decoded), 3) \ +X(a, STATIC, ONEOF, BYTES, (payload,encrypted,encrypted), 8) \ X(a, STATIC, SINGULAR, UINT32, rx_time, 4) \ X(a, STATIC, SINGULAR, UINT32, id, 6) \ X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL -#define MeshPacket_payload_MSGTYPE SubPacket +#define MeshPacket_payload_decoded_MSGTYPE SubPacket #define ChannelSettings_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, tx_power, 1) \ diff --git a/src/screen.cpp b/src/screen.cpp index 0b3f22fb2..c60907aa8 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -81,7 +81,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state MeshPacket &mp = devicestate.rx_text_message; NodeInfo *node = nodeDB.getNode(mp.from); // DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from, - // mp.payload.variant.data.payload.bytes); + // mp.decoded.variant.data.decoded.bytes); // Demo for drawStringMaxWidth: // with the third parameter you can define the width after which words will @@ -94,8 +94,8 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state // the max length of this buffer is much longer than we can possibly print static char tempBuf[96]; - assert(mp.payload.has_data); - snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.data.payload.bytes); + assert(mp.decoded.has_data); + snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.data.payload.bytes); display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf); } From 3e356e5866b12ba03cfdd5f8e6a7247d347618c2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 19:08:04 -0700 Subject: [PATCH 162/197] Crypto works! --- docs/faq.md | 2 +- proto | 2 +- src/esp32/ESP32CryptoEngine.cpp | 83 +++++++++++++++++++++++++++++++++ src/mesh/CryptoEngine.cpp | 12 ++++- src/mesh/CryptoEngine.h | 33 ++++++++++--- src/mesh/NodeDB.cpp | 9 +++- src/mesh/Router.cpp | 2 - src/mesh/mesh.pb.h | 15 +++--- 8 files changed, 137 insertions(+), 21 deletions(-) create mode 100644 src/esp32/ESP32CryptoEngine.cpp diff --git a/docs/faq.md b/docs/faq.md index ec145fe3d..a015f9fdc 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -4,7 +4,7 @@ This project is still pretty young but moving at a pretty good pace. Not all fea Most of these problems should be solved by the beta release (within three months): - We don't make these devices and they haven't been tested by UL or the FCC. If you use them you are experimenting and we can't promise they won't burn your house down ;-) -- Encryption is turned off for now +- The encryption [implementation](software/crypto.md) has not been reviewed by an expert. (Are you an expert? Please help us) - A number of (straightforward) software work items have to be completed before battery life matches our measurements, currently battery life is about three days. Join us on chat if you want the spreadsheet of power measurements/calculations. - The Android API needs to be documented better - No one has written an iOS app yet. But some good souls [are talking about it](https://github.com/meshtastic/Meshtastic-esp32/issues/14) ;-) diff --git a/proto b/proto index f775ebe36..5e2df6c99 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit f775ebe369cd9bd92d954d46f76ea0a4ae62078c +Subproject commit 5e2df6c9986cd75f0af4eab1ba0d2aacf258aaab diff --git a/src/esp32/ESP32CryptoEngine.cpp b/src/esp32/ESP32CryptoEngine.cpp new file mode 100644 index 000000000..bccfae557 --- /dev/null +++ b/src/esp32/ESP32CryptoEngine.cpp @@ -0,0 +1,83 @@ +#include "CryptoEngine.h" +#include "configuration.h" + +#include "crypto/includes.h" + +#include "crypto/common.h" + +// #include "esp_system.h" + +#include "crypto/aes.h" +#include "crypto/aes_wrap.h" +#include "mbedtls/aes.h" + +#define MAX_BLOCKSIZE 256 + +class ESP32CryptoEngine : public CryptoEngine +{ + + mbedtls_aes_context aes; + + /// How many bytes in our key + uint8_t keySize = 0; + + public: + ESP32CryptoEngine() { mbedtls_aes_init(&aes); } + + ~ESP32CryptoEngine() { mbedtls_aes_free(&aes); } + + /** + * Set the key used for encrypt, decrypt. + * + * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. + * + * @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt) + * @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the + * provided pointer) + */ + virtual void setKey(size_t numBytes, uint8_t *bytes) + { + keySize = numBytes; + DEBUG_MSG("Installing AES%d key!\n", numBytes * 8); + if (numBytes != 0) { + auto res = mbedtls_aes_setkey_enc(&aes, bytes, numBytes * 8); + assert(!res); + } + } + + /** + * Encrypt a packet + * + * @param bytes is updated in place + */ + virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) + { + if (keySize != 0) { + uint8_t stream_block[16]; + static uint8_t scratch[MAX_BLOCKSIZE]; + size_t nc_off = 0; + + // DEBUG_MSG("ESP32 encrypt!\n"); + initNonce(fromNode, packetNum); + assert(numBytes <= MAX_BLOCKSIZE); + memcpy(scratch, bytes, numBytes); + memset(scratch + numBytes, 0, + sizeof(scratch) - numBytes); // Fill rest of buffer with zero (in case cypher looks at it) + + auto res = mbedtls_aes_crypt_ctr(&aes, numBytes, &nc_off, nonce, stream_block, scratch, bytes); + assert(!res); + } + } + + virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) + { + // DEBUG_MSG("ESP32 decrypt!\n"); + + // For CTR, the implementation is the same + encrypt(fromNode, packetNum, numBytes, bytes); + } + + private: +}; + +CryptoEngine *crypto = new ESP32CryptoEngine(); diff --git a/src/mesh/CryptoEngine.cpp b/src/mesh/CryptoEngine.cpp index 14774df3f..d72be1118 100644 --- a/src/mesh/CryptoEngine.cpp +++ b/src/mesh/CryptoEngine.cpp @@ -1,7 +1,7 @@ #include "CryptoEngine.h" #include "configuration.h" -void CryptoEngine::setKey(size_t numBytes, const uint8_t *bytes) +void CryptoEngine::setKey(size_t numBytes, uint8_t *bytes) { DEBUG_MSG("WARNING: Using stub crypto - all crypto is sent in plaintext!\n"); } @@ -19,4 +19,14 @@ void CryptoEngine::encrypt(uint32_t fromNode, uint64_t packetNum, size_t numByte void CryptoEngine::decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes) { DEBUG_MSG("WARNING: noop decryption!\n"); +} + +/** + * Init our 128 bit nonce for a new packet + */ +void CryptoEngine::initNonce(uint32_t fromNode, uint64_t packetNum) +{ + memset(nonce, 0, sizeof(nonce)); + *((uint64_t *)&nonce[0]) = packetNum; + *((uint32_t *)&nonce[8]) = fromNode; } \ No newline at end of file diff --git a/src/mesh/CryptoEngine.h b/src/mesh/CryptoEngine.h index 0b733a6b9..04e592e2c 100644 --- a/src/mesh/CryptoEngine.h +++ b/src/mesh/CryptoEngine.h @@ -5,27 +5,46 @@ /** * see docs/software/crypto.md for details. * - * The NONCE is constructed by concatenating: - * a 32 bit sending node number + a 64 bit packet number + a 32 bit block counter (starts at zero) */ class CryptoEngine { + protected: + /** Our per packet nonce */ + uint8_t nonce[16]; + public: + virtual ~CryptoEngine() {} + /** * Set the key used for encrypt, decrypt. * * As a special case: If all bytes are zero, we assume _no encryption_ and send all data in cleartext. * - * @param numBytes must be 32 for now (AES256) + * @param numBytes must be 16 (AES128), 32 (AES256) or 0 (no crypt) + * @param bytes a _static_ buffer that will remain valid for the life of this crypto instance (i.e. this class will cache the + * provided pointer) */ - void setKey(size_t numBytes, const uint8_t *bytes); + virtual void setKey(size_t numBytes, uint8_t *bytes); /** * Encrypt a packet * * @param bytes is updated in place */ - void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes); - void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes); -}; \ No newline at end of file + virtual void encrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes); + virtual void decrypt(uint32_t fromNode, uint64_t packetNum, size_t numBytes, uint8_t *bytes); + + protected: + /** + * Init our 128 bit nonce for a new packet + * + * The NONCE is constructed by concatenating (from MSB to LSB): + * a 64 bit packet number (stored in little endian order) + * a 32 bit sending node number (stored in little endian order) + * a 32 bit block counter (starts at zero) + */ + void initNonce(uint32_t fromNode, uint64_t packetNum); +}; + +extern CryptoEngine *crypto; diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 50b4ca73c..8fe82b822 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -5,6 +5,7 @@ #include "FS.h" #include "SPIFFS.h" +#include "CryptoEngine.h" #include "GPS.h" #include "NodeDB.h" #include "PowerFSM.h" @@ -51,7 +52,7 @@ NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_cou void NodeDB::resetRadioConfig() { - /// 16 bytes of random PSK for our _public_ default channel that all devices power up on + /// 16 bytes of random PSK for our _public_ default channel that all devices power up on (AES128) static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf}; @@ -75,10 +76,14 @@ void NodeDB::resetRadioConfig() channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range channelSettings.tx_power = 23; - memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk)); + memcpy(&channelSettings.psk.bytes, &defaultpsk, sizeof(channelSettings.psk)); + channelSettings.psk.size = sizeof(defaultpsk); strcpy(channelSettings.name, "Default"); } + // Tell our crypto engine about the psk + crypto->setKey(channelSettings.psk.size, channelSettings.psk.bytes); + // temp hack for quicker testing /* radioConfig.preferences.screen_on_secs = 30; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index fe32a68a4..3752a2fbd 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -4,8 +4,6 @@ #include "configuration.h" #include "mesh-pb-constants.h" -CryptoEngine *crypto = new CryptoEngine(); - /** * Router todo * diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index da04b00cd..9442bd25b 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -36,10 +36,11 @@ typedef struct _RouteDiscovery { pb_callback_t route; } RouteDiscovery; +typedef PB_BYTES_ARRAY_T(32) ChannelSettings_psk_t; typedef struct _ChannelSettings { int32_t tx_power; ChannelSettings_ModemConfig modem_config; - pb_byte_t psk[16]; + ChannelSettings_psk_t psk; char name[12]; } ChannelSettings; @@ -198,7 +199,7 @@ typedef struct _ToRadio { #define RouteDiscovery_init_default {{{NULL}, NULL}} #define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0} #define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0} -#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} +#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} #define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0} @@ -213,7 +214,7 @@ typedef struct _ToRadio { #define RouteDiscovery_init_zero {{{NULL}, NULL}} #define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0} #define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0} -#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""} +#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} #define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} #define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0} @@ -355,7 +356,7 @@ X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) #define ChannelSettings_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, tx_power, 1) \ X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \ -X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, psk, 4) \ +X(a, STATIC, SINGULAR, BYTES, psk, 4) \ X(a, STATIC, SINGULAR, STRING, name, 5) #define ChannelSettings_CALLBACK NULL #define ChannelSettings_DEFAULT NULL @@ -498,12 +499,12 @@ extern const pb_msgdesc_t ToRadio_msg; /* RouteDiscovery_size depends on runtime parameters */ #define SubPacket_size 377 #define MeshPacket_size 419 -#define ChannelSettings_size 44 -#define RadioConfig_size 120 +#define ChannelSettings_size 60 +#define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 #define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 18535 +#define DeviceState_size 18552 #define DebugString_size 258 #define FromRadio_size 428 #define ToRadio_size 422 From 96313ee1c4f05f5638e2c816d71205cdb59cd96a Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 19:09:38 -0700 Subject: [PATCH 163/197] remove stale link --- docs/software/cypto.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/software/cypto.md b/docs/software/cypto.md index d1c0081ad..f5f7d04a6 100644 --- a/docs/software/cypto.md +++ b/docs/software/cypto.md @@ -32,7 +32,7 @@ The per-message nonce (or information sufficient to reconstruct it) needs to be Note that for both stategies, sizes are measured in blocks and that an AES block is 16 bytes. ``` -## Example code links +## Remaining todo -- Example code for [NRF52](https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.0.0/lib_crypto_aes.html#sub_aes_ctr) -- Example code for [ESP32](https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c) +- Make the packet numbers 32 bit +- Implement for NRF52 [NRF52](https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.0.0/lib_crypto_aes.html#sub_aes_ctr) From 190a3c2d6bbc33153f3b3a461f4ca634b2100b3e Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 20:27:08 -0700 Subject: [PATCH 164/197] filename typo --- docs/software/{cypto.md => crypto.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/software/{cypto.md => crypto.md} (100%) diff --git a/docs/software/cypto.md b/docs/software/crypto.md similarity index 100% rename from docs/software/cypto.md rename to docs/software/crypto.md From 2fa595523f53ba4389edc9aacdf582434e323237 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 9 May 2020 21:02:56 -0700 Subject: [PATCH 165/197] minor fixups to get nrf52 building again --- docs/README.md | 2 +- docs/software/crypto.md | 8 +++++--- src/mesh/FloodingRouter.cpp | 2 +- src/mesh/RadioLibInterface.cpp | 3 +++ src/mesh/StreamAPI.cpp | 2 +- src/nrf52/NRF52CryptoEngine.cpp | 5 +++++ 6 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 src/nrf52/NRF52CryptoEngine.cpp diff --git a/docs/README.md b/docs/README.md index a10ff9f6a..47281dbeb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,7 +1,7 @@ # What is Meshtastic? Meshtastic is a project that lets you use -inexpensive (\$30 ish) GPS radios as an extensible, super long battery life mesh GPS communicator. These radios are great for hiking, skiing, paragliding - essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other members and any text messages sent to your group chat. +inexpensive (\$30 ish) GPS radios as an extensible, long battery life, secure, mesh GPS communicator. These radios are great for hiking, skiing, paragliding - essentially any hobby where you don't have reliable internet access. Each member of your private mesh can always see the location and distance of all other members and any text messages sent to your group chat. The radios automatically create a mesh to forward packets as needed, so everyone in the group can receive messages from even the furthest member. The radios will optionally work with your phone, but no phone is required. diff --git a/docs/software/crypto.md b/docs/software/crypto.md index f5f7d04a6..7f5709c48 100644 --- a/docs/software/crypto.md +++ b/docs/software/crypto.md @@ -4,8 +4,8 @@ Cryptography is tricky, so we've tried to 'simply' apply standard crypto solutio the project developers are not cryptography experts. Therefore we ask two things: - If you are a cryptography expert, please review these notes and our questions below. Can you help us by reviewing our - notes below and offering advice? We will happily give as much or as little credit as you wish as our thanks ;-). -- Consider our existing solution 'alpha' and probably fairly secure against an not very aggressive adversary. But until + notes below and offering advice? We will happily give as much or as little credit as you wish ;-). +- Consider our existing solution 'alpha' and probably fairly secure against a not particularly aggressive adversary. But until it is reviewed by someone smarter than us, assume it might have flaws. ## Notes on implementation @@ -16,7 +16,7 @@ the project developers are not cryptography experts. Therefore we ask two things Parameters for our CTR implementation: -- Our AES key is 256 bits, shared as part of the 'Channel' specification. +- Our AES key is 128 or 256 bits, shared as part of the 'Channel' specification. - Each SubPacket will be sent as a series of 16 byte BLOCKS. - The node number concatenated with the packet number is used as the NONCE. This counter will be stored in flash in the device and should essentially never repeat. If the user makes a new 'Channel' (i.e. picking a new random 256 bit key), the packet number will start at zero. The packet number is sent in cleartext with each packet. The node number can be derived from the "from" field of each packet. @@ -35,4 +35,6 @@ Note that for both stategies, sizes are measured in blocks and that an AES block ## Remaining todo - Make the packet numbers 32 bit +- Confirm the packet #s are stored in flash across deep sleep (and otherwise in in RAM) +- Have the app change the crypto key when the user generates a new channel - Implement for NRF52 [NRF52](https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v15.0.0/lib_crypto_aes.html#sub_aes_ctr) diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index da7070172..c1672c77e 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -116,7 +116,7 @@ bool FloodingRouter::wasSeenRecently(const MeshPacket *p) } uint32_t now = millis(); - for (int i = 0; i < recentBroadcasts.size();) { + for (size_t i = 0; i < recentBroadcasts.size();) { BroadcastRecord &r = recentBroadcasts[i]; if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index dee630e0c..e09c4f4a9 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -197,6 +197,9 @@ void RadioLibInterface::loop() #ifndef NO_ESP32 #define USE_HW_TIMER +#else +// Not needed on NRF52 +#define IRAM_ATTR #endif void IRAM_ATTR RadioLibInterface::timerCallback(void *p1, uint32_t p2) diff --git a/src/mesh/StreamAPI.cpp b/src/mesh/StreamAPI.cpp index 00434f90b..49ccb3d6b 100644 --- a/src/mesh/StreamAPI.cpp +++ b/src/mesh/StreamAPI.cpp @@ -31,7 +31,7 @@ void StreamAPI::readStream() if (c != START2) rxPtr = 0; // failed to find framing } else if (ptr >= HEADER_LEN) { // we have at least read our 4 byte framing - uint16_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing + uint32_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing if (ptr == HEADER_LEN) { // we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid diff --git a/src/nrf52/NRF52CryptoEngine.cpp b/src/nrf52/NRF52CryptoEngine.cpp new file mode 100644 index 000000000..ee1650ea2 --- /dev/null +++ b/src/nrf52/NRF52CryptoEngine.cpp @@ -0,0 +1,5 @@ + +#include "CryptoEngine.h" + +// FIXME, do a NRF52 version +CryptoEngine *crypto = new CryptoEngine(); \ No newline at end of file From 8b911aba7ff5f0e5a4d33423ffdeee6cc0c2614f Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 10 May 2020 12:33:17 -0700 Subject: [PATCH 166/197] Cleanup build for NRF52 targets --- boards/nrf52840_dk_modified.json | 46 +++++++++++++++++ boards/ppr.json | 2 +- docs/software/mesh-alg.md | 5 +- docs/software/nrf52-TODO.md | 6 ++- platformio.ini | 26 +++++++--- src/configuration.h | 86 +++++++++++++++++++------------- src/main.cpp | 6 +++ variants/ppr/variant.cpp | 31 +++++------- variants/ppr/variant.h | 85 +++++++++++++++++++------------ 9 files changed, 194 insertions(+), 99 deletions(-) create mode 100644 boards/nrf52840_dk_modified.json diff --git a/boards/nrf52840_dk_modified.json b/boards/nrf52840_dk_modified.json new file mode 100644 index 000000000..ce86e09f5 --- /dev/null +++ b/boards/nrf52840_dk_modified.json @@ -0,0 +1,46 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_PCA10056 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [["0x239A", "0x4404"]], + "usb_product": "SimPPR", + "mcu": "nrf52840", + "variant": "pca10056-rc-clock", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": ["bluetooth"], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": ["jlink"], + "svd_path": "nrf52840.svd" + }, + "frameworks": ["arduino"], + "name": "A modified NRF52840-DK devboard (Adafruit BSP)", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "require_upload_port": true, + "speed": 115200, + "protocol": "jlink", + "protocols": ["jlink", "nrfjprog", "stlink"] + }, + "url": "https://meshtastic.org/", + "vendor": "Nordic Semi" +} diff --git a/boards/ppr.json b/boards/ppr.json index 5050758f7..5283fdc4e 100644 --- a/boards/ppr.json +++ b/boards/ppr.json @@ -10,7 +10,7 @@ "hwids": [["0x239A", "0x4403"]], "usb_product": "PPR", "mcu": "nrf52840", - "variant": "pca10056-rc-clock", + "variant": "ppr", "variants_dir": "variants", "bsp": { "name": "adafruit" diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index d90d85c9c..48bc6721f 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -11,9 +11,9 @@ TODO: - DONE read about mesh routing solutions (DSR and AODV) - DONE read about general mesh flooding solutions (naive, MPR, geo assisted) - DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) -- possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead +- REJECTED - seems dying - possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead - update duty cycle spreadsheet for our typical usecase -- generalize naive flooding on top of radiohead or disaster.radio? (and fix radiohead to use my new driver) +- DONE generalize naive flooding a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept @@ -77,7 +77,6 @@ look into the literature for this idea specifically. FIXME, merge into the above: - good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept interesting paper on lora mesh: https://portal.research.lu.se/portal/files/45735775/paper.pdf diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 4f4b6c409..69afcb946 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -5,8 +5,6 @@ Minimum items needed to make sure hardware is good. - add a hard fault handler -- use "variants" to get all gpio bindings -- plug in correct variants for the real board - Use the PMU driver on real hardware - Use new radio driver on real hardware - Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI @@ -62,6 +60,8 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- Hook Segger RTT to the nordic logging framework. https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/debugging-with-real-time-terminal +- Use nordic logging for DEBUG_MSG - use the Jumper simulator to run meshes of simulated hardware: https://docs.jumper.io/docs/install.html - 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 @@ -102,6 +102,8 @@ Nice ideas worth considering someday... - DONE remove unused sx1262 lib from github - at boot we are starting our message IDs at 1, rather we should start them at a random number. also, seed random based on timer. this could be the cause of our first message not seen bug. - add a NEMA based GPS driver to test GPS +- DONE use "variants" to get all gpio bindings +- DONE plug in correct variants for the real board ``` diff --git a/platformio.ini b/platformio.ini index 61b61d677..644195500 100644 --- a/platformio.ini +++ b/platformio.ini @@ -122,11 +122,9 @@ board = ttgo-lora32-v1 build_flags = ${esp32_base.build_flags} -D TTGO_LORA_V2 - -; The NRF52840-dk development board -[env:nrf52dk] +; Common settings for NRF52 based targets +[nrf52_base] platform = nordicnrf52 -board = ppr framework = arduino debug_tool = jlink build_type = debug ; I'm debugging with ICE a lot now @@ -136,10 +134,6 @@ src_filter = ${env.src_filter} - lib_ignore = BluetoothOTA -lib_deps = - ${env.lib_deps} - UC1701 - https://github.com/meshtastic/BQ25703A.git monitor_port = /dev/ttyACM1 debug_extra_cmds = @@ -150,3 +144,19 @@ debug_init_break = ;debug_init_break = tbreak loop ;debug_init_break = tbreak Reset_Handler +; The NRF52840-dk development board +[env:nrf52dk] +extends = nrf52_base +board = nrf52840_dk_modified + +; The PPR board +[env:ppr] +extends = nrf52_base +board = ppr +lib_deps = + ${env.lib_deps} + UC1701 + https://github.com/meshtastic/BQ25703A.git + + + diff --git a/src/configuration.h b/src/configuration.h index b02a1a00f..ca606c53a 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -51,27 +51,31 @@ along with this program. If not, see . #define xstr(s) str(s) #define str(s) #s -// ----------------------------------------------------------------------------- -// OLED -// ----------------------------------------------------------------------------- +#ifdef NRF52840_XXAA // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be + // board specific -#define SSD1306_ADDRESS 0x3C +// +// Standard definitions for NRF52 targets +// -// Flip the screen upside down by default as it makes more sense on T-BEAM -// devices. Comment this out to not rotate screen 180 degrees. -#define FLIP_SCREEN_VERTICALLY +#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) -// DEBUG LED +// We bind to the GPS using variant.h instead for this platform (Serial1) -#define LED_INVERTED 0 // define as 1 if LED is active low (on) +// FIXME, not yet ready for NRF52 +#define RTC_DATA_ATTR -// ----------------------------------------------------------------------------- -// GPS -// ----------------------------------------------------------------------------- +#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK +#define BUTTON_PIN PIN_BUTTON1 + +// FIXME, use variant.h defs for all of this!!! (even on the ESP32 targets) +#else + +// +// Standard definitions for ESP32 targets +// #define GPS_SERIAL_NUM 1 -#define GPS_BAUDRATE 9600 - #define GPS_RX_PIN 34 #ifdef USE_JTAG #define GPS_TX_PIN -1 @@ -88,6 +92,27 @@ along with this program. If not, see . #define MOSI_GPIO 27 #define NSS_GPIO 18 +#endif + +// ----------------------------------------------------------------------------- +// OLED +// ----------------------------------------------------------------------------- + +#define SSD1306_ADDRESS 0x3C + +// Flip the screen upside down by default as it makes more sense on T-BEAM +// devices. Comment this out to not rotate screen 180 degrees. +#define FLIP_SCREEN_VERTICALLY + +// DEBUG LED +#define LED_INVERTED 0 // define as 1 if LED is active low (on) + +// ----------------------------------------------------------------------------- +// GPS +// ----------------------------------------------------------------------------- + +#define GPS_BAUDRATE 9600 + #if defined(TBEAM_V10) // This string must exactly match the case used in release file names or the android updater won't work #define HW_VENDOR "tbeam" @@ -188,35 +213,22 @@ along with this program. If not, see . 0 // If defined, this will be used for user button presses, if your board doesn't have a physical switch, you can wire one // between this pin and ground -#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio -#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio -#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number -#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number -#elif defined(NRF52840_XXAA) // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be - // board specific +#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio +#define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio +#define DIO1_GPIO 35 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number +#endif -// FIXME, use variant.h defs for all of this!!! +#ifdef ARDUINO_NRF52840_PCA10056 // This string must exactly match the case used in release file names or the android updater won't work -#define HW_VENDOR "nrf52" - -#define NO_ESP32 // Don't use ESP32 libs (mainly bluetooth) - -// We bind to the GPS using variant.h instead for this platform (Serial1) -#undef GPS_RX_PIN -#undef GPS_TX_PIN - -// FIXME, not yet ready for NRF52 -#define RTC_DATA_ATTR - -#define LED_PIN PIN_LED1 // LED1 on nrf52840-DK -#define BUTTON_PIN PIN_BUTTON1 +#define HW_VENDOR "nrf52dk" // This board uses 0 to be mean LED on #undef LED_INVERTED #define LED_INVERTED 1 -// Temporarily testing if we can build the RF95 driver for NRF52 +// Uncomment to confirm if we can build the RF95 driver for NRF52 #if 0 #define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio #define RF95_IRQ_GPIO 26 // IRQ line for the LORA radio @@ -224,6 +236,10 @@ along with this program. If not, see . #define DIO2_GPIO 34 // DIO1 & DIO2 are not currently used, but they must be assigned to a pin number #endif +#elif defined(ARDUINO_NRF52840_PPR) + +#define HW_VENDOR "ppr" + #endif // ----------------------------------------------------------------------------- diff --git a/src/main.cpp b/src/main.cpp index b6c03846f..a3c060b18 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -189,6 +189,8 @@ void setup() readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time) +// If we know we have a L80 GPS, don't try UBLOX +#ifndef L80_RESET // Init GPS - first try ublox gps = new UBloxGPS(); if (!gps->setup()) { @@ -199,6 +201,10 @@ void setup() gps = new NEMAGPS(); gps->setup(); } +#else + gps = new NEMAGPS(); + gps->setup(); +#endif service.init(); diff --git a/variants/ppr/variant.cpp b/variants/ppr/variant.cpp index bd85e9713..f5f219e9b 100644 --- a/variants/ppr/variant.cpp +++ b/variants/ppr/variant.cpp @@ -19,31 +19,24 @@ */ #include "variant.h" +#include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" -#include "nrf.h" -const uint32_t g_ADigitalPinMap[] = -{ - // P0 - 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , - 8 , 9 , 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, - 24, 25, 26, 27, 28, 29, 30, 31, - - // P1 - 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47 -}; +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0xff, 12, 13, 0xff, 15, 0xff, 17, 18, 0xff, 20, 0xff, 22, 0xff, 24, 0xff, 26, 0xff, 28, 29, + 30, 31, + // P1 + 32, 0xff, 34, 0xff, 36, 0xff, 38, 0xff, 0xff, 41, 42, 43, 0xff, 45}; void initVariant() { - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2);; + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); } - diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h index 5033a4ecc..b792172ee 100644 --- a/variants/ppr/variant.h +++ b/variants/ppr/variant.h @@ -39,40 +39,44 @@ extern "C" { #endif // __cplusplus // Number of pins defined in PinDescription array -#define PINS_COUNT (48) -#define NUM_DIGITAL_PINS (48) -#define NUM_ANALOG_INPUTS (6) +#define PINS_COUNT (46) +#define NUM_DIGITAL_PINS (46) +#define NUM_ANALOG_INPUTS (0) #define NUM_ANALOG_OUTPUTS (0) // LEDs -#define PIN_LED1 (13) -#define PIN_LED2 (14) +#define PIN_LED1 (0) +#define PIN_LED2 (1) #define LED_BUILTIN PIN_LED1 #define LED_CONN PIN_LED2 #define LED_RED PIN_LED1 -#define LED_BLUE PIN_LED2 +#define LED_GREEN PIN_LED2 -#define LED_STATE_ON 0 // State when LED is litted +// FIXME, bluefruit automatically blinks this led while connected. call AdafruitBluefruit::autoConnLed to change this. +#define LED_BLUE LED_GREEN + +#define LED_STATE_ON 1 // State when LED is litted /* * Buttons */ -#define PIN_BUTTON1 11 -#define PIN_BUTTON2 12 -#define PIN_BUTTON3 24 -#define PIN_BUTTON4 25 +#define PIN_BUTTON1 4 // center +#define PIN_BUTTON2 2 +#define PIN_BUTTON3 3 +#define PIN_BUTTON4 5 +#define PIN_BUTTON5 6 /* * Analog pins */ -#define PIN_A0 (3) -#define PIN_A1 (4) -#define PIN_A2 (28) -#define PIN_A3 (29) -#define PIN_A4 (30) -#define PIN_A5 (31) +#define PIN_A0 (0xff) +#define PIN_A1 (0xff) +#define PIN_A2 (0xff) +#define PIN_A3 (0xff) +#define PIN_A4 (0xff) +#define PIN_A5 (0xff) #define PIN_A6 (0xff) #define PIN_A7 (0xff) @@ -87,9 +91,9 @@ static const uint8_t A7 = PIN_A7; #define ADC_RESOLUTION 14 // Other pins -#define PIN_AREF (2) -#define PIN_NFC1 (9) -#define PIN_NFC2 (10) +#define PIN_AREF (0xff) +//#define PIN_NFC1 (9) +//#define PIN_NFC2 (10) static const uint8_t AREF = PIN_AREF; @@ -97,24 +101,24 @@ static const uint8_t AREF = PIN_AREF; * Serial interfaces */ -// Arduino Header D0, D1 -#define PIN_SERIAL1_RX (33) // P1.01 -#define PIN_SERIAL1_TX (34) // P1.02 +// GPS is on Serial1 +#define PIN_SERIAL1_RX (8) +#define PIN_SERIAL1_TX (9) // Connected to Jlink CDC -#define PIN_SERIAL2_RX (8) -#define PIN_SERIAL2_TX (6) +//#define PIN_SERIAL2_RX (8) +//#define PIN_SERIAL2_TX (6) /* * SPI Interfaces */ #define SPI_INTERFACES_COUNT 1 -#define PIN_SPI_MISO (46) -#define PIN_SPI_MOSI (45) -#define PIN_SPI_SCK (47) +#define PIN_SPI_MISO (15) +#define PIN_SPI_MOSI (13) +#define PIN_SPI_SCK (12) -static const uint8_t SS = 44; +// static const uint8_t SS = 44; static const uint8_t MOSI = PIN_SPI_MOSI; static const uint8_t MISO = PIN_SPI_MISO; static const uint8_t SCK = PIN_SPI_SCK; @@ -124,8 +128,27 @@ static const uint8_t SCK = PIN_SPI_SCK; */ #define WIRE_INTERFACES_COUNT 1 -#define PIN_WIRE_SDA (26) -#define PIN_WIRE_SCL (27) +#define PIN_WIRE_SDA (32 + 2) +#define PIN_WIRE_SCL (32) + +// CUSTOM GPIOs the SX1262 +#define SX1262_CS (10) +#define SX1262_DIO1 (20) +#define SX1262_DIO2 (26) +#define SX1262_BUSY (18) +#define SX1262_RESET (17) +// #define SX1262_ANT_SW (32 + 10) +#define SX1262_RXEN (22) +#define SX1262_TXEN (24) + +// ERC12864-10 LCD +#define ERC12864_CS (32 + 4) +#define ERC12864_RESET (32 + 6) +#define ERC12864_CD (32 + 9) + +// L80 GPS +#define L80_PPS (28) +#define L80_RESET (29) #ifdef __cplusplus } From c12fb69ca29126602a88dca61734d02676e557c6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 10 May 2020 14:17:05 -0700 Subject: [PATCH 167/197] update protos --- docs/software/TODO.md | 20 +++++++++----------- proto | 2 +- src/mesh/mesh.pb.h | 38 +++++++++++++++++++------------------- variants/ppr/variant.h | 9 ++------- 4 files changed, 31 insertions(+), 38 deletions(-) diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 23efd6ee3..711f31f01 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -5,21 +5,15 @@ Items to complete soon (next couple of alpha releases). - lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch. -- remeasure wake time power draws now that we run CPU down at 80MHz - -# AXP192 tasks - -- figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?" -- "AXP192 interrupt is not firing, remove this temporary polling of battery state" -- make debug info screen show real data (including battery level & charging) - close corresponding github issue - # Medium priority Items to complete before the first beta release. -- Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it -fetches the fresh nodedb. -- Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual. +- Use 32 bits for message IDs +- Use fixed32 for node IDs +- Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it + fetches the fresh nodedb. +- Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual. - possibly switch to https://github.com/SlashDevin/NeoGPS for gps comms - good source of battery/signal/gps icons https://materialdesignicons.com/ - research and implement better mesh algorithm - investigate changing routing to https://github.com/sudomesh/LoRaLayer2 ? @@ -204,3 +198,7 @@ Items after the first final candidate release. - enable fast lock and low power inside the gps chip - Make a FAQ - add a SF12 transmit option for _super_ long range +- figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?" +- "AXP192 interrupt is not firing, remove this temporary polling of battery state" +- make debug info screen show real data (including battery level & charging) - close corresponding github issue +- remeasure wake time power draws now that we run CPU down at 80MHz diff --git a/proto b/proto index 5e2df6c99..484049369 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 5e2df6c9986cd75f0af4eab1ba0d2aacf258aaab +Subproject commit 4840493693d5799ebd451f6857ecbbc5c9157348 diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index 9442bd25b..be286b503 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -69,9 +69,9 @@ typedef struct _MyNodeInfo { typedef struct _Position { int32_t altitude; int32_t battery_level; - uint32_t time; int32_t latitude_i; int32_t longitude_i; + uint32_t time; } Position; typedef struct _RadioConfig_UserPreferences { @@ -125,16 +125,16 @@ typedef struct _SubPacket { typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t; typedef struct _MeshPacket { - int32_t from; - int32_t to; + uint32_t from; + uint32_t to; pb_size_t which_payload; union { SubPacket decoded; MeshPacket_encrypted_t encrypted; }; - uint32_t rx_time; uint32_t id; float rx_snr; + uint32_t rx_time; } MeshPacket; typedef struct _DeviceState { @@ -246,7 +246,7 @@ typedef struct _ToRadio { #define Position_longitude_i_tag 8 #define Position_altitude_tag 3 #define Position_battery_level_tag 4 -#define Position_time_tag 6 +#define Position_time_tag 9 #define RadioConfig_UserPreferences_position_broadcast_secs_tag 1 #define RadioConfig_UserPreferences_send_owner_interval_tag 2 #define RadioConfig_UserPreferences_num_missed_to_fail_tag 3 @@ -278,7 +278,7 @@ typedef struct _ToRadio { #define MeshPacket_encrypted_tag 8 #define MeshPacket_from_tag 1 #define MeshPacket_to_tag 2 -#define MeshPacket_rx_time_tag 4 +#define MeshPacket_rx_time_tag 9 #define MeshPacket_id_tag 6 #define MeshPacket_rx_snr_tag 7 #define DeviceState_radio_tag 1 @@ -305,9 +305,9 @@ typedef struct _ToRadio { #define Position_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, altitude, 3) \ X(a, STATIC, SINGULAR, INT32, battery_level, 4) \ -X(a, STATIC, SINGULAR, UINT32, time, 6) \ X(a, STATIC, SINGULAR, SINT32, latitude_i, 7) \ -X(a, STATIC, SINGULAR, SINT32, longitude_i, 8) +X(a, STATIC, SINGULAR, SINT32, longitude_i, 8) \ +X(a, STATIC, SINGULAR, FIXED32, time, 9) #define Position_CALLBACK NULL #define Position_DEFAULT NULL @@ -342,13 +342,13 @@ X(a, STATIC, SINGULAR, BOOL, want_response, 5) #define SubPacket_user_MSGTYPE User #define MeshPacket_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, INT32, from, 1) \ -X(a, STATIC, SINGULAR, INT32, to, 2) \ +X(a, STATIC, SINGULAR, UINT32, from, 1) \ +X(a, STATIC, SINGULAR, UINT32, to, 2) \ X(a, STATIC, ONEOF, MESSAGE, (payload,decoded,decoded), 3) \ X(a, STATIC, ONEOF, BYTES, (payload,encrypted,encrypted), 8) \ -X(a, STATIC, SINGULAR, UINT32, rx_time, 4) \ X(a, STATIC, SINGULAR, UINT32, id, 6) \ -X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) +X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) \ +X(a, STATIC, SINGULAR, FIXED32, rx_time, 9) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL #define MeshPacket_payload_decoded_MSGTYPE SubPacket @@ -493,21 +493,21 @@ extern const pb_msgdesc_t ToRadio_msg; #define ToRadio_fields &ToRadio_msg /* Maximum encoded size of messages (where known) */ -#define Position_size 40 +#define Position_size 39 #define Data_size 256 #define User_size 72 /* RouteDiscovery_size depends on runtime parameters */ -#define SubPacket_size 377 -#define MeshPacket_size 419 +#define SubPacket_size 376 +#define MeshPacket_size 407 #define ChannelSettings_size 60 #define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 -#define NodeInfo_size 132 +#define NodeInfo_size 131 #define MyNodeInfo_size 85 -#define DeviceState_size 18552 +#define DeviceState_size 18124 #define DebugString_size 258 -#define FromRadio_size 428 -#define ToRadio_size 422 +#define FromRadio_size 416 +#define ToRadio_size 410 #ifdef __cplusplus } /* extern "C" */ diff --git a/variants/ppr/variant.h b/variants/ppr/variant.h index b792172ee..d59e17503 100644 --- a/variants/ppr/variant.h +++ b/variants/ppr/variant.h @@ -16,15 +16,12 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef _VARIANT_PCA10056_ -#define _VARIANT_PCA10056_ +#pragma once /** Master clock frequency */ #define VARIANT_MCK (64000000ul) -// This file is the same as the standard pac10056 variant, except that @geeksville broke the xtal on his devboard so -// he has to use a RC clock. - +// This board does not have a 32khz crystal // #define USE_LFXO // Board uses 32khz crystal for LF #define USE_LFRC // Board uses RC for LF @@ -157,5 +154,3 @@ static const uint8_t SCK = PIN_SPI_SCK; /*---------------------------------------------------------------------------- * Arduino objects - C++ only *----------------------------------------------------------------------------*/ - -#endif From 86ae69d36093b8c2b5461ef0cd5ece6b08c1b8fb Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 11 May 2020 16:14:53 -0700 Subject: [PATCH 168/197] refactor so I can track and ignore recent packets of any type --- docs/software/TODO.md | 1 + docs/software/mesh-alg.md | 52 ++++++++++++++++++++++- proto | 2 +- src/mesh/FloodingRouter.cpp | 49 ---------------------- src/mesh/FloodingRouter.h | 22 +--------- src/mesh/PacketHistory.cpp | 52 +++++++++++++++++++++++ src/mesh/PacketHistory.h | 33 +++++++++++++++ src/mesh/mesh.pb.h | 84 +++++++++++++++++++++++++------------ 8 files changed, 196 insertions(+), 99 deletions(-) create mode 100644 src/mesh/PacketHistory.cpp create mode 100644 src/mesh/PacketHistory.h diff --git a/docs/software/TODO.md b/docs/software/TODO.md index 711f31f01..93ceb20d2 100644 --- a/docs/software/TODO.md +++ b/docs/software/TODO.md @@ -11,6 +11,7 @@ Items to complete before the first beta release. - Use 32 bits for message IDs - Use fixed32 for node IDs +- Remove the "want node" node number arbitration process - Don't store position packets in the to phone fifo if we are disconnected. The phone will get that info for 'free' when it fetches the fresh nodedb. - Use the RFM95 sequencer to stay in idle mode most of the time, then automatically go to receive mode and automatically go from transmit to receive mode. See 4.2.8.2 of manual. diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 48bc6721f..2b55a3a34 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -5,15 +5,63 @@ all else fails could always use the stock Radiohead solution - though super inef great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ +reliable messaging tasks (stage one for DSR): + +- add a 'messagePeek' hook for all messages that pass through our node. +- use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. +- keep possible retries in the list with rebroadcast messages? +- for each message keep a count of # retries (max of three) +- delay some random time for each retry (large enough to allow for acks to come in) +- once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender +- after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) +- add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as two bits in the header. + +dsr tasks + +- do "hop by hop" routing +- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. +- otherwise, use next_hop and start sending a message (with ack request) towards that node. + +when we receive any packet + +- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info. +- if we need to route() that packet, resend it to the next_hop based on our nodedb. +- if it is broadcast or destined for our node, deliver locally +- handle routereply/routeerror/routediscovery messages as described below +- then free it + +routeDiscovery + +- if we've already passed through us (or is from us), then it ignore it +- use the nodes already mentioned in the request to update our routing table +- if they were looking for us, send back a routereply +- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) +- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) +- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route reply towards the requester + +when sending any reliable packet + +- if we get back a nak, send a routeError message back towards the original requester. all nodes eavesdrop on that packet and update their route caches + +when we receive a routereply packet + +- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday use a better heuristic + +when we receive a routeError packet + +- delete the route for that failed recipient, restartRouteDiscovery() +- if we receive routeerror in response to a discovery, +- fixme, eventually keep caches of possible other routes. + TODO: -- DONE reread the radiohead mesh implementation - hop to hop acknoledgement seems VERY expensive but otherwise it seems like DSR +- DONE reread the radiohead mesh implementation - hop to hop acknowledgement seems VERY expensive but otherwise it seems like DSR - DONE read about mesh routing solutions (DSR and AODV) - DONE read about general mesh flooding solutions (naive, MPR, geo assisted) - DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) - REJECTED - seems dying - possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead - update duty cycle spreadsheet for our typical usecase -- DONE generalize naive flooding +- DONE generalize naive flooding a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept diff --git a/proto b/proto index 484049369..3bf195cb2 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 4840493693d5799ebd451f6857ecbbc5c9157348 +Subproject commit 3bf195cb2d60f1d877a89bca87d0c70ea2d01177 diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index c1672c77e..177a92169 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -2,15 +2,10 @@ #include "configuration.h" #include "mesh-pb-constants.h" -/// We clear our old flood record five minute after we see the last of it -#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L) - static bool supportFlooding = true; // Sometimes to simplify debugging we want jusT simple broadcast only FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) { - recentBroadcasts.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation - // setup our periodic task } /** @@ -101,47 +96,3 @@ void FloodingRouter::doTask() setPeriod(getRandomDelay()); } } - -/** - * Update recentBroadcasts and return true if we have already seen this packet - */ -bool FloodingRouter::wasSeenRecently(const MeshPacket *p) -{ - if (p->to != NODENUM_BROADCAST) - return false; // Not a broadcast, so we don't care - - if (p->id == 0) { - DEBUG_MSG("Ignoring message with zero id\n"); - return false; // Not a floodable message ID, so we don't care - } - - uint32_t now = millis(); - for (size_t i = 0; i < recentBroadcasts.size();) { - BroadcastRecord &r = recentBroadcasts[i]; - - if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { - // DEBUG_MSG("Deleting old broadcast record %d\n", i); - recentBroadcasts.erase(recentBroadcasts.begin() + i); // delete old record - } else { - if (r.id == p->id && r.sender == p->from) { - DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - - // Update the time on this record to now - r.rxTimeMsec = now; - return true; - } - - i++; - } - } - - // Didn't find an existing record, make one - BroadcastRecord r; - r.id = p->id; - r.sender = p->from; - r.rxTimeMsec = now; - recentBroadcasts.push_back(r); - DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - - return false; -} \ No newline at end of file diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 4ca759ecc..b8bfa0b9c 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -1,17 +1,9 @@ #pragma once +#include "PacketHistory.h" #include "PeriodicTask.h" #include "Router.h" -#include -/** - * A record of a recent message broadcast - */ -struct BroadcastRecord { - NodeNum sender; - PacketId id; - uint32_t rxTimeMsec; // Unix time in msecs - the time we received it -}; /** * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) @@ -36,13 +28,9 @@ struct BroadcastRecord { Any entries in recentBroadcasts that are older than X seconds (longer than the max time a flood can take) will be discarded. */ -class FloodingRouter : public Router, public PeriodicTask +class FloodingRouter : public Router, public PeriodicTask, private PacketHistory { private: - /** FIXME: really should be a std::unordered_set with the key being sender,id. - * This would make checking packets in wasSeenRecently faster. - */ - std::vector recentBroadcasts; /** * Packets we've received that we need to resend after a short delay @@ -74,10 +62,4 @@ class FloodingRouter : public Router, public PeriodicTask virtual void handleReceived(MeshPacket *p); virtual void doTask(); - - private: - /** - * Update recentBroadcasts and return true if we have already seen this packet - */ - bool wasSeenRecently(const MeshPacket *p); }; diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp new file mode 100644 index 000000000..9a5704f66 --- /dev/null +++ b/src/mesh/PacketHistory.cpp @@ -0,0 +1,52 @@ +#include "PacketHistory.h" +#include "configuration.h" + +/// We clear our old flood record five minute after we see the last of it +#define FLOOD_EXPIRE_TIME (5 * 60 * 1000L) + +PacketHistory::PacketHistory() +{ + recentPackets.reserve(MAX_NUM_NODES); // Prealloc the worst case # of records - to prevent heap fragmentation + // setup our periodic task +} + +/** + * Update recentBroadcasts and return true if we have already seen this packet + */ +bool PacketHistory::wasSeenRecently(const MeshPacket *p) +{ + if (p->id == 0) { + DEBUG_MSG("Ignoring message with zero id\n"); + return false; // Not a floodable message ID, so we don't care + } + + uint32_t now = millis(); + for (size_t i = 0; i < recentPackets.size();) { + PacketRecord &r = recentPackets[i]; + + if ((now - r.rxTimeMsec) >= FLOOD_EXPIRE_TIME) { + // DEBUG_MSG("Deleting old broadcast record %d\n", i); + recentPackets.erase(recentPackets.begin() + i); // delete old record + } else { + if (r.id == p->id && r.sender == p->from) { + DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + + // Update the time on this record to now + r.rxTimeMsec = now; + return true; + } + + i++; + } + } + + // Didn't find an existing record, make one + PacketRecord r; + r.id = p->id; + r.sender = p->from; + r.rxTimeMsec = now; + recentPackets.push_back(r); + DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + + return false; +} \ No newline at end of file diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h new file mode 100644 index 000000000..3a182caeb --- /dev/null +++ b/src/mesh/PacketHistory.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Router.h" +#include + +/** + * A record of a recent message broadcast + */ +struct PacketRecord { + NodeNum sender; + PacketId id; + uint32_t rxTimeMsec; // Unix time in msecs - the time we received it +}; + +/** + * This is a mixin that adds a record of past packets we have seen + */ +class PacketHistory +{ + private: + /** FIXME: really should be a std::unordered_set with the key being sender,id. + * This would make checking packets in wasSeenRecently faster. + */ + std::vector recentPackets; + + public: + PacketHistory(); + + /** + * Update recentBroadcasts and return true if we have already seen this packet + */ + bool wasSeenRecently(const MeshPacket *p); +}; diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index be286b503..d8a2e1fd5 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -32,10 +32,6 @@ typedef enum _ChannelSettings_ModemConfig { } ChannelSettings_ModemConfig; /* Struct definitions */ -typedef struct _RouteDiscovery { - pb_callback_t route; -} RouteDiscovery; - typedef PB_BYTES_ARRAY_T(32) ChannelSettings_psk_t; typedef struct _ChannelSettings { int32_t tx_power; @@ -90,6 +86,11 @@ typedef struct _RadioConfig_UserPreferences { bool promiscuous_mode; } RadioConfig_UserPreferences; +typedef struct _RouteDiscovery { + pb_size_t route_count; + int32_t route[8]; +} RouteDiscovery; + typedef struct _User { char id[16]; char long_name[40]; @@ -98,11 +99,12 @@ typedef struct _User { } User; typedef struct _NodeInfo { - int32_t num; + uint32_t num; bool has_user; User user; bool has_position; Position position; + uint32_t next_hop; float snr; } NodeInfo; @@ -121,6 +123,17 @@ typedef struct _SubPacket { bool has_user; User user; bool want_response; + pb_size_t which_route; + union { + RouteDiscovery request; + RouteDiscovery reply; + } route; + uint32_t dest; + pb_size_t which_ack; + union { + uint32_t success_id; + uint32_t fail_id; + } ack; } SubPacket; typedef PB_BYTES_ARRAY_T(256) MeshPacket_encrypted_t; @@ -135,6 +148,7 @@ typedef struct _MeshPacket { uint32_t id; float rx_snr; uint32_t rx_time; + uint32_t hop_limit; } MeshPacket; typedef struct _DeviceState { @@ -196,13 +210,13 @@ typedef struct _ToRadio { #define Position_init_default {0, 0, 0, 0, 0} #define Data_init_default {_Data_Type_MIN, {0, {0}}} #define User_init_default {"", "", "", {0}} -#define RouteDiscovery_init_default {{{NULL}, NULL}} -#define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0} -#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0} +#define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0, 0, {RouteDiscovery_init_default}, 0, 0, {0}} +#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} #define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0} +#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0} #define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, false, MeshPacket_init_default, 0} #define DebugString_init_default {""} @@ -211,13 +225,13 @@ typedef struct _ToRadio { #define Position_init_zero {0, 0, 0, 0, 0} #define Data_init_zero {_Data_Type_MIN, {0, {0}}} #define User_init_zero {"", "", "", {0}} -#define RouteDiscovery_init_zero {{{NULL}, NULL}} -#define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0} -#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0} +#define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} +#define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0, 0, {RouteDiscovery_init_zero}, 0, 0, {0}} +#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} #define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} -#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0} +#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0} #define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0} #define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, false, MeshPacket_init_zero, 0} #define DebugString_init_zero {""} @@ -225,7 +239,6 @@ typedef struct _ToRadio { #define ToRadio_init_zero {0, {MeshPacket_init_zero}} /* Field tags (for use in manual encoding/decoding) */ -#define RouteDiscovery_route_tag 2 #define ChannelSettings_tx_power_tag 1 #define ChannelSettings_modem_config_tag 3 #define ChannelSettings_psk_tag 4 @@ -260,6 +273,7 @@ typedef struct _ToRadio { #define RadioConfig_UserPreferences_min_wake_secs_tag 11 #define RadioConfig_UserPreferences_keep_all_packets_tag 100 #define RadioConfig_UserPreferences_promiscuous_mode_tag 101 +#define RouteDiscovery_route_tag 2 #define User_id_tag 1 #define User_long_name_tag 2 #define User_short_name_tag 3 @@ -268,19 +282,26 @@ typedef struct _ToRadio { #define NodeInfo_user_tag 2 #define NodeInfo_position_tag 3 #define NodeInfo_snr_tag 7 +#define NodeInfo_next_hop_tag 5 #define RadioConfig_preferences_tag 1 #define RadioConfig_channel_settings_tag 2 +#define SubPacket_success_id_tag 10 +#define SubPacket_fail_id_tag 11 +#define SubPacket_request_tag 6 +#define SubPacket_reply_tag 7 #define SubPacket_position_tag 1 #define SubPacket_data_tag 3 #define SubPacket_user_tag 4 #define SubPacket_want_response_tag 5 +#define SubPacket_dest_tag 9 #define MeshPacket_decoded_tag 3 #define MeshPacket_encrypted_tag 8 #define MeshPacket_from_tag 1 #define MeshPacket_to_tag 2 -#define MeshPacket_rx_time_tag 9 #define MeshPacket_id_tag 6 +#define MeshPacket_rx_time_tag 9 #define MeshPacket_rx_snr_tag 7 +#define MeshPacket_hop_limit_tag 10 #define DeviceState_radio_tag 1 #define DeviceState_my_node_tag 2 #define DeviceState_owner_tag 3 @@ -326,20 +347,27 @@ X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, macaddr, 4) #define User_DEFAULT NULL #define RouteDiscovery_FIELDLIST(X, a) \ -X(a, CALLBACK, REPEATED, INT32, route, 2) -#define RouteDiscovery_CALLBACK pb_default_field_callback +X(a, STATIC, REPEATED, INT32, route, 2) +#define RouteDiscovery_CALLBACK NULL #define RouteDiscovery_DEFAULT NULL #define SubPacket_FIELDLIST(X, a) \ X(a, STATIC, OPTIONAL, MESSAGE, position, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, data, 3) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 4) \ -X(a, STATIC, SINGULAR, BOOL, want_response, 5) +X(a, STATIC, SINGULAR, BOOL, want_response, 5) \ +X(a, STATIC, ONEOF, MESSAGE, (route,request,route.request), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (route,reply,route.reply), 7) \ +X(a, STATIC, SINGULAR, UINT32, dest, 9) \ +X(a, STATIC, ONEOF, UINT32, (ack,success_id,ack.success_id), 10) \ +X(a, STATIC, ONEOF, UINT32, (ack,fail_id,ack.fail_id), 11) #define SubPacket_CALLBACK NULL #define SubPacket_DEFAULT NULL #define SubPacket_position_MSGTYPE Position #define SubPacket_data_MSGTYPE Data #define SubPacket_user_MSGTYPE User +#define SubPacket_route_request_MSGTYPE RouteDiscovery +#define SubPacket_route_reply_MSGTYPE RouteDiscovery #define MeshPacket_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, from, 1) \ @@ -348,7 +376,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,decoded,decoded), 3) \ X(a, STATIC, ONEOF, BYTES, (payload,encrypted,encrypted), 8) \ X(a, STATIC, SINGULAR, UINT32, id, 6) \ X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) \ -X(a, STATIC, SINGULAR, FIXED32, rx_time, 9) +X(a, STATIC, SINGULAR, FIXED32, rx_time, 9) \ +X(a, STATIC, SINGULAR, UINT32, hop_limit, 10) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL #define MeshPacket_payload_decoded_MSGTYPE SubPacket @@ -387,9 +416,10 @@ X(a, STATIC, SINGULAR, BOOL, promiscuous_mode, 101) #define RadioConfig_UserPreferences_DEFAULT NULL #define NodeInfo_FIELDLIST(X, a) \ -X(a, STATIC, SINGULAR, INT32, num, 1) \ +X(a, STATIC, SINGULAR, UINT32, num, 1) \ X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \ X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \ +X(a, STATIC, SINGULAR, UINT32, next_hop, 5) \ X(a, STATIC, SINGULAR, FLOAT, snr, 7) #define NodeInfo_CALLBACK NULL #define NodeInfo_DEFAULT NULL @@ -496,18 +526,18 @@ extern const pb_msgdesc_t ToRadio_msg; #define Position_size 39 #define Data_size 256 #define User_size 72 -/* RouteDiscovery_size depends on runtime parameters */ -#define SubPacket_size 376 -#define MeshPacket_size 407 +#define RouteDiscovery_size 88 +#define SubPacket_size 478 +#define MeshPacket_size 515 #define ChannelSettings_size 60 #define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 -#define NodeInfo_size 131 +#define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 18124 +#define DeviceState_size 21720 #define DebugString_size 258 -#define FromRadio_size 416 -#define ToRadio_size 410 +#define FromRadio_size 524 +#define ToRadio_size 518 #ifdef __cplusplus } /* extern "C" */ From 9f05ad29270b84a370b7089b59d25e765388c32f Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 11 May 2020 16:19:44 -0700 Subject: [PATCH 169/197] remove random delay hack from broadcast, since we now do that for all transmits --- src/main.cpp | 2 -- src/mesh/FloodingRouter.cpp | 52 ++++--------------------------------- src/mesh/FloodingRouter.h | 6 +---- 3 files changed, 6 insertions(+), 54 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index a3c060b18..9ba569947 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -208,8 +208,6 @@ void setup() service.init(); - realRouter.setup(); // required for our periodic task (kinda skanky FIXME) - #ifdef SX1262_ANT_SW // make analog PA vs not PA switch on SX1262 eval board work properly pinMode(SX1262_ANT_SW, OUTPUT); diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 177a92169..6db03dd4f 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -4,9 +4,7 @@ static bool supportFlooding = true; // Sometimes to simplify debugging we want jusT simple broadcast only -FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) -{ -} +FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} /** * Send a packet on a suitable interface. This routine will @@ -22,18 +20,6 @@ ErrorCode FloodingRouter::send(MeshPacket *p) return Router::send(p); } -// Return a delay in msec before sending the next packet -uint32_t getRandomDelay() -{ - return random(200, 10 * 1000L); // between 200ms and 10s -} - -/** - * Now that our generalized packet send code has a random delay - I don't think we need to wait here - * But I'm leaving this bool until I rip the code out for good. - */ -bool needDelay = false; - /** * Called from loop() * Handle any packet that is received by an interface on this node. @@ -52,21 +38,11 @@ void FloodingRouter::handleReceived(MeshPacket *p) if (p->id != 0) { MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it - if (needDelay) { - uint32_t delay = getRandomDelay(); + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // Note: we are careful to resend using the original senders node id + // We are careful not to call our hooked version of send() - because we don't want to check this again + Router::send(tosend); - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors in %u msec, fr=0x%x,to=0x%x,id=%d\n", delay, - p->from, p->to, p->id); - - toResend.enqueue(tosend); - setPeriod(delay); // This will work even if we were already waiting a random delay - } else { - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, - p->id); - // Note: we are careful to resend using the original senders node id - // We are careful not to call our hooked version of send() - because we don't want to check this again - Router::send(tosend); - } } else { DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); } @@ -78,21 +54,3 @@ void FloodingRouter::handleReceived(MeshPacket *p) } else Router::handleReceived(p); } - -void FloodingRouter::doTask() -{ - MeshPacket *p = toResend.dequeuePtr(0); - - if (p) { - DEBUG_MSG("Sending delayed message!\n"); - // Note: we are careful to resend using the original senders node id - // We are careful not to call our hooked version of send() - because we don't want to check this again - Router::send(p); - } - - if (toResend.isEmpty()) - disable(); // no more work right now - else { - setPeriod(getRandomDelay()); - } -} diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index b8bfa0b9c..996e9f7ae 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -4,7 +4,6 @@ #include "PeriodicTask.h" #include "Router.h" - /** * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) * @@ -28,10 +27,9 @@ Any entries in recentBroadcasts that are older than X seconds (longer than the max time a flood can take) will be discarded. */ -class FloodingRouter : public Router, public PeriodicTask, private PacketHistory +class FloodingRouter : public Router, private PacketHistory { private: - /** * Packets we've received that we need to resend after a short delay */ @@ -60,6 +58,4 @@ class FloodingRouter : public Router, public PeriodicTask, private PacketHistory * Note: this method will free the provided packet */ virtual void handleReceived(MeshPacket *p); - - virtual void doTask(); }; From b6a202d68ee8cfaf7266daddcea8a8337f280a96 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 12 May 2020 13:35:22 -0700 Subject: [PATCH 170/197] runs again with new protobufs --- platformio.ini | 1 + proto | 2 +- src/mesh/MeshService.cpp | 12 ++++----- src/mesh/NodeDB.cpp | 11 +++++--- src/mesh/PacketHistory.h | 37 ++++++++++++++++++++++++-- src/mesh/mesh.pb.h | 57 +++++++++++++++++++--------------------- src/screen.cpp | 2 +- 7 files changed, 79 insertions(+), 43 deletions(-) diff --git a/platformio.ini b/platformio.ini index 644195500..aead63d98 100644 --- a/platformio.ini +++ b/platformio.ini @@ -51,6 +51,7 @@ build_flags = -Wno-missing-field-initializers -Isrc -Isrc/mesh -Isrc/gps -Ilib/n ; the default is esptool ; upload_protocol = esp-prog +; monitor_speed = 115200 monitor_speed = 921600 # debug_tool = esp-prog diff --git a/proto b/proto index 3bf195cb2..bc3ecd97e 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 3bf195cb2d60f1d877a89bca87d0c70ea2d01177 +Subproject commit bc3ecd97e381b724c1a28acce0d12c688de73ba3 diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 992144b82..5f4b51bab 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -93,7 +93,7 @@ void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) MeshPacket *p = allocForSending(); p->to = dest; p->decoded.want_response = wantReplies; - p->decoded.has_user = true; + p->decoded.which_payload = SubPacket_user_tag; User &u = p->decoded.user; u = owner; DEBUG_MSG("sending owner %s/%s/%s\n", u.id, u.long_name, u.short_name); @@ -143,7 +143,7 @@ const MeshPacket *MeshService::handleFromRadioUser(const MeshPacket *mp) void MeshService::handleIncomingPosition(const MeshPacket *mp) { - if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.has_position) { + if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_position_tag) { DEBUG_MSG("handled incoming position time=%u\n", mp->decoded.position.time); if (mp->decoded.position.time) { @@ -171,7 +171,7 @@ int MeshService::handleFromRadio(const MeshPacket *mp) DEBUG_MSG("Ignoring incoming time, because we have a GPS\n"); } - if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.has_user) { + if (mp->which_payload == MeshPacket_decoded_tag && mp->decoded.which_payload == SubPacket_user_tag) { mp = handleFromRadioUser(mp); } @@ -257,7 +257,7 @@ void MeshService::sendToMesh(MeshPacket *p) // Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other // nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless // devices can get time. - if (p->which_payload == MeshPacket_decoded_tag && p->decoded.has_position) { + if (p->which_payload == MeshPacket_decoded_tag && p->decoded.which_payload == SubPacket_position_tag) { if (!gps->isConnected) { DEBUG_MSG("Stripping time %u from position send\n", p->decoded.position.time); p->decoded.position.time = 0; @@ -312,7 +312,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); p->to = dest; - p->decoded.has_position = true; + p->decoded.which_payload = SubPacket_position_tag; p->decoded.position = node->position; p->decoded.want_response = wantReplies; p->decoded.position.time = getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid. @@ -325,7 +325,7 @@ int MeshService::onGPSChanged(void *unused) // Update our local node info with our position (even if we don't decide to update anyone else) MeshPacket *p = allocForSending(); - p->decoded.has_position = true; + p->decoded.which_payload = SubPacket_position_tag; Position &pos = p->decoded.position; // !zero or !zero lat/long means valid diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 8fe82b822..5d2e8ecd3 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -296,16 +296,18 @@ void NodeDB::updateFrom(const MeshPacket &mp) info->snr = mp.rx_snr; // keep the most recent SNR we received for this node. - if (p.has_position) { + switch (p.which_payload) { + case SubPacket_position_tag: { // we carefully preserve the old time, because we always trust our local timestamps more uint32_t oldtime = info->position.time; info->position = p.position; info->position.time = oldtime; info->has_position = true; updateGUIforNode = info; + break; } - if (p.has_data) { + case SubPacket_data_tag: { // Keep a copy of the most recent text message. if (p.data.typ == Data_Type_CLEAR_TEXT) { DEBUG_MSG("Received text msg from=0x%0x, id=%d, msg=%.*s\n", mp.from, mp.id, p.data.payload.size, @@ -318,9 +320,10 @@ void NodeDB::updateFrom(const MeshPacket &mp) powerFSM.trigger(EVENT_RECEIVED_TEXT_MSG); } } + break; } - if (p.has_user) { + case SubPacket_user_tag: { DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name); bool changed = memcmp(&info->user, &p.user, @@ -338,6 +341,8 @@ void NodeDB::updateFrom(const MeshPacket &mp) // We just changed something important about the user, store our DB // saveToDisk(); } + break; + } } } } diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index 3a182caeb..22470f4fc 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -1,7 +1,10 @@ #pragma once #include "Router.h" -#include +#include +#include + +using namespace std; /** * A record of a recent message broadcast @@ -10,6 +13,34 @@ struct PacketRecord { NodeNum sender; PacketId id; uint32_t rxTimeMsec; // Unix time in msecs - the time we received it + + bool operator==(const PacketRecord &p) const { return sender == p.sender && id == p.id; } +}; + +class PacketRecordHashFunction +{ + public: + size_t operator()(const PacketRecord &p) const { return (hash()(p.sender)) ^ (hash()(p.id)); } +}; + +/// Order packet records by arrival time, we want the oldest packets to be in the front of our heap +class PacketRecordOrderFunction +{ + public: + size_t operator()(const PacketRecord &p1, const PacketRecord &p2) const + { + // If the timer ticks have rolled over the difference between times will be _enormous_. Handle that case specially + uint32_t t1 = p1.rxTimeMsec, t2 = p2.rxTimeMsec; + + if (abs(t1 - t2) > + UINT32_MAX / + 2) { // time must have rolled over, swap them because the new little number is 'bigger' than the old big number + t1 = t2; + t2 = p1.rxTimeMsec; + } + + return t1 > t2; + } }; /** @@ -21,7 +52,9 @@ class PacketHistory /** FIXME: really should be a std::unordered_set with the key being sender,id. * This would make checking packets in wasSeenRecently faster. */ - std::vector recentPackets; + vector recentPackets; + // priority_queue, PacketRecordOrderFunction> arrivalTimes; + // unordered_set recentPackets; public: PacketHistory(); diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index d8a2e1fd5..d193d38ee 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -116,18 +116,15 @@ typedef struct _RadioConfig { } RadioConfig; typedef struct _SubPacket { - bool has_position; - Position position; - bool has_data; - Data data; - bool has_user; - User user; - bool want_response; - pb_size_t which_route; + pb_size_t which_payload; union { + Position position; + Data data; + User user; RouteDiscovery request; RouteDiscovery reply; - } route; + }; + bool want_response; uint32_t dest; pb_size_t which_ack; union { @@ -211,7 +208,7 @@ typedef struct _ToRadio { #define Data_init_default {_Data_Type_MIN, {0, {0}}} #define User_init_default {"", "", "", {0}} #define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} -#define SubPacket_init_default {false, Position_init_default, false, Data_init_default, false, User_init_default, 0, 0, {RouteDiscovery_init_default}, 0, 0, {0}} +#define SubPacket_init_default {0, {Position_init_default}, 0, 0, 0, {0}} #define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} @@ -226,7 +223,7 @@ typedef struct _ToRadio { #define Data_init_zero {_Data_Type_MIN, {0, {0}}} #define User_init_zero {"", "", "", {0}} #define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} -#define SubPacket_init_zero {false, Position_init_zero, false, Data_init_zero, false, User_init_zero, 0, 0, {RouteDiscovery_init_zero}, 0, 0, {0}} +#define SubPacket_init_zero {0, {Position_init_zero}, 0, 0, 0, {0}} #define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} @@ -285,13 +282,13 @@ typedef struct _ToRadio { #define NodeInfo_next_hop_tag 5 #define RadioConfig_preferences_tag 1 #define RadioConfig_channel_settings_tag 2 -#define SubPacket_success_id_tag 10 -#define SubPacket_fail_id_tag 11 -#define SubPacket_request_tag 6 -#define SubPacket_reply_tag 7 #define SubPacket_position_tag 1 #define SubPacket_data_tag 3 #define SubPacket_user_tag 4 +#define SubPacket_request_tag 6 +#define SubPacket_reply_tag 7 +#define SubPacket_success_id_tag 10 +#define SubPacket_fail_id_tag 11 #define SubPacket_want_response_tag 5 #define SubPacket_dest_tag 9 #define MeshPacket_decoded_tag 3 @@ -352,22 +349,22 @@ X(a, STATIC, REPEATED, INT32, route, 2) #define RouteDiscovery_DEFAULT NULL #define SubPacket_FIELDLIST(X, a) \ -X(a, STATIC, OPTIONAL, MESSAGE, position, 1) \ -X(a, STATIC, OPTIONAL, MESSAGE, data, 3) \ -X(a, STATIC, OPTIONAL, MESSAGE, user, 4) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,position,position), 1) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,data,data), 3) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,user,user), 4) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,request,request), 6) \ +X(a, STATIC, ONEOF, MESSAGE, (payload,reply,reply), 7) \ X(a, STATIC, SINGULAR, BOOL, want_response, 5) \ -X(a, STATIC, ONEOF, MESSAGE, (route,request,route.request), 6) \ -X(a, STATIC, ONEOF, MESSAGE, (route,reply,route.reply), 7) \ X(a, STATIC, SINGULAR, UINT32, dest, 9) \ X(a, STATIC, ONEOF, UINT32, (ack,success_id,ack.success_id), 10) \ X(a, STATIC, ONEOF, UINT32, (ack,fail_id,ack.fail_id), 11) #define SubPacket_CALLBACK NULL #define SubPacket_DEFAULT NULL -#define SubPacket_position_MSGTYPE Position -#define SubPacket_data_MSGTYPE Data -#define SubPacket_user_MSGTYPE User -#define SubPacket_route_request_MSGTYPE RouteDiscovery -#define SubPacket_route_reply_MSGTYPE RouteDiscovery +#define SubPacket_payload_position_MSGTYPE Position +#define SubPacket_payload_data_MSGTYPE Data +#define SubPacket_payload_user_MSGTYPE User +#define SubPacket_payload_request_MSGTYPE RouteDiscovery +#define SubPacket_payload_reply_MSGTYPE RouteDiscovery #define MeshPacket_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, UINT32, from, 1) \ @@ -527,17 +524,17 @@ extern const pb_msgdesc_t ToRadio_msg; #define Data_size 256 #define User_size 72 #define RouteDiscovery_size 88 -#define SubPacket_size 478 -#define MeshPacket_size 515 +#define SubPacket_size 273 +#define MeshPacket_size 310 #define ChannelSettings_size 60 #define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 #define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 21720 +#define DeviceState_size 14955 #define DebugString_size 258 -#define FromRadio_size 524 -#define ToRadio_size 518 +#define FromRadio_size 319 +#define ToRadio_size 313 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/screen.cpp b/src/screen.cpp index c60907aa8..dd2f99c07 100644 --- a/src/screen.cpp +++ b/src/screen.cpp @@ -94,7 +94,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state // the max length of this buffer is much longer than we can possibly print static char tempBuf[96]; - assert(mp.decoded.has_data); + assert(mp.decoded.which_payload == SubPacket_data_tag); snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.data.payload.bytes); display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf); From a0b43b9a95da35f95159f034963ad9726788f4c2 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 12 May 2020 17:57:51 -0700 Subject: [PATCH 171/197] Send "unset" for hwver and swver if they were unset --- src/configuration.h | 4 ++++ src/esp32/main-esp32.cpp | 4 ++-- src/main.cpp | 2 +- src/mesh/NodeDB.cpp | 11 +++++++---- src/sleep.cpp | 3 --- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/configuration.h b/src/configuration.h index ca606c53a..04f9d26a1 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -48,9 +48,13 @@ along with this program. If not, see . #define REQUIRE_RADIO true // If true, we will fail to start if the radio is not found +/// Convert a preprocessor name into a quoted string #define xstr(s) str(s) #define str(s) #s +/// Convert a preprocessor name into a quoted string and if that string is empty use "unset" +#define optstr(s) (xstr(s)[0] ? xstr(s) : "unset") + #ifdef NRF52840_XXAA // All of the NRF52 targets are configured using variant.h, so this section shouldn't need to be // board specific diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 48af9b998..b0e1406b5 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -22,8 +22,8 @@ void reinitBluetooth() powerFSM.trigger(EVENT_BLUETOOTH_PAIR); screen.startBluetoothPinScreen(pin); }, - []() { screen.stopBluetoothPinScreen(); }, getDeviceName(), HW_VENDOR, xstr(APP_VERSION), - xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr + []() { screen.stopBluetoothPinScreen(); }, getDeviceName(), HW_VENDOR, optstr(APP_VERSION), + optstr(HW_VERSION)); // FIXME, use a real name based on the macaddr createMeshBluetoothService(serve); // Start advertising - this must be done _after_ creating all services diff --git a/src/main.cpp b/src/main.cpp index 9ba569947..2378458a6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -167,7 +167,7 @@ void setup() ledPeriodic.setup(); // Hello - DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_VERSION)); + DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", optstr(APP_VERSION), optstr(HW_VERSION)); #ifndef NO_ESP32 // Don't init display if we don't have one or we are waking headless due to a timer event diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 5d2e8ecd3..cc83f2f47 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -109,10 +109,6 @@ void NodeDB::init() // default to no GPS, until one has been found by probing myNodeInfo.has_gps = false; - strncpy(myNodeInfo.region, xstr(HW_VERSION), sizeof(myNodeInfo.region)); - strncpy(myNodeInfo.firmware_version, xstr(APP_VERSION), sizeof(myNodeInfo.firmware_version)); - strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model)); - // Init our blank owner info to reasonable defaults getMacAddr(ourMacAddr); sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], @@ -135,6 +131,13 @@ void NodeDB::init() // saveToDisk(); loadFromDisk(); + + // We set these _after_ loading from disk - because they come from the build and are more trusted than + // what is stored in flash + strncpy(myNodeInfo.region, optstr(HW_VERSION), sizeof(myNodeInfo.region)); + strncpy(myNodeInfo.firmware_version, optstr(APP_VERSION), sizeof(myNodeInfo.firmware_version)); + strncpy(myNodeInfo.hw_model, HW_VENDOR, sizeof(myNodeInfo.hw_model)); + resetRadioConfig(); // If bogus settings got saved, then fix them DEBUG_MSG("NODENUM=0x%x, dbsize=%d\n", myNodeInfo.my_node_num, *numNodes); diff --git a/src/sleep.cpp b/src/sleep.cpp index 4f5fa2fdc..4b8db06b0 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -35,9 +35,6 @@ Observable notifySleep, notifyDeepSleep; // deep sleep support RTC_DATA_ATTR int bootCount = 0; -#define xstr(s) str(s) -#define str(s) #s - // ----------------------------------------------------------------------------- // Application // ----------------------------------------------------------------------------- From 27db0e27e8480eb5da6751504a00d15a920bfb66 Mon Sep 17 00:00:00 2001 From: Mark Huson Date: Tue, 12 May 2020 19:11:16 -0700 Subject: [PATCH 172/197] Update supported hardware --- README.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e8bc1920..1844471f8 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,25 @@ This software is 100% open source and developed by a group of hobbyist experimen ## Supported hardware -We currently support three models of radios. The [TTGO T-Beam](https://www.aliexpress.com/item/4000119152086.html), [TTGO LORA32](https://www.banggood.com/LILYGO-TTGO-LORA32-868Mhz-SX1276-ESP32-Oled-Display-bluetooth-WIFI-Lora-Development-Module-Board-p-1248652.html?cur_warehouse=UK) and the [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/). Most users should buy the T-Beam and an 18650 battery (total cost less than \$35). Make sure to buy the frequency range which is legal for your country (915MHz for US/JP/AU/NZ, 470MHz for CN, 433MHz and 870MHz for EU). Getting a version that includes a screen is optional, but highly recommended. +We currently support three models of radios. +- TTGO T-Beam + - [T-Beam V1.0 w/ NEO-M8N](https://www.aliexpress.com/item/33047631119.html) (Recommended) + - [T-Beam V1.0 w/ NEO-6M](https://www.aliexpress.com/item/33050391850.html) + - 3D printable cases + - [T-Beam V0](https://www.thingiverse.com/thing:3773717) + - [T-Beam V1](https://www.thingiverse.com/thing:3830711) -See (meshtastic.org) for 3D printable cases. +- [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS + +- [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS + - [3D Printable case](https://www.thingiverse.com/thing:3125854) + +**Make sure to get the frequency for your country** + - US/JP/AU/NZ - 915MHz + - CN - 470MHz + - EU - 870MHz + +Getting a version that includes a screen is optional, but highly recommended. ## Firmware Installation From 7339abbab5feab125391b086d52bb71414c47008 Mon Sep 17 00:00:00 2001 From: Mark Huson Date: Tue, 12 May 2020 19:14:12 -0700 Subject: [PATCH 173/197] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1844471f8..352a8e79a 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ We currently support three models of radios. - [TTGO LORA32](https://www.aliexpress.com/item/4000211331316.html) - No GPS - [Heltec LoRa 32](https://heltec.org/project/wifi-lora-32/) - No GPS - - [3D Printable case](https://www.thingiverse.com/thing:3125854) + - [3D Printable case](https://www.thingiverse.com/thing:3125854) **Make sure to get the frequency for your country** - US/JP/AU/NZ - 915MHz From 140e29840a982d84927f01186eabf2ecbddb83fd Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 14 May 2020 12:46:29 -0700 Subject: [PATCH 174/197] fix rare gurumeditation if we are unlucky and some ISR code is in serial flash --- src/WorkerThread.cpp | 8 +------- src/WorkerThread.h | 7 ++++++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/WorkerThread.cpp b/src/WorkerThread.cpp index ed1103911..f84d83be2 100644 --- a/src/WorkerThread.cpp +++ b/src/WorkerThread.cpp @@ -28,13 +28,7 @@ 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() { diff --git a/src/WorkerThread.h b/src/WorkerThread.h index f951da32c..86ec08e13 100644 --- a/src/WorkerThread.h +++ b/src/WorkerThread.h @@ -61,8 +61,13 @@ class NotifiedWorkerThread : public WorkerThread /** * Notify from an ISR + * + * This must be inline or IRAM_ATTR on ESP32 */ - void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction); + inline void notifyFromISR(BaseType_t *highPriWoken, uint32_t v = 0, eNotifyAction action = eNoAction) + { + xTaskNotifyFromISR(taskHandle, v, action, highPriWoken); + } protected: /** From 14fdd339724fb8470c97abecb72becb5fb2c744e Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 14 May 2020 14:20:05 -0700 Subject: [PATCH 175/197] move bluetooth OTA back into main tree for now --- .../src/BluetoothSoftwareUpdate.cpp | 103 +++++++++++------- .../src/BluetoothSoftwareUpdate.h | 5 +- src/mesh/RadioLibInterface.h | 21 ++-- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp index 62bdd499d..f2fa7c973 100644 --- a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp +++ b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp @@ -1,21 +1,30 @@ -#include "BluetoothUtil.h" #include "BluetoothSoftwareUpdate.h" -#include "configuration.h" -#include -#include -#include -#include -#include +#include "BluetoothUtil.h" #include "CallbackCharacteristic.h" +#include "RadioLibInterface.h" +#include "configuration.h" +#include "lock.h" +#include +#include +#include +#include +#include + +using namespace meshtastic; CRC32 crc; uint32_t rebootAtMsec = 0; // If not zero we will reboot at this time (used to reboot shortly after the update completes) +uint32_t updateExpectedSize, updateActualSize; + +Lock *updateLock; + class TotalSizeCharacteristic : public CallbackCharacteristic { -public: + public: TotalSizeCharacteristic() - : CallbackCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) + : CallbackCharacteristic("e74dd9c0-a301-4a6f-95a1-f0e1dbea8e1e", + BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ) { } @@ -23,8 +32,11 @@ public: { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); // Check if there is enough to OTA Update uint32_t len = getValue32(c, 0); + updateExpectedSize = len; + updateActualSize = 0; crc.reset(); bool canBegin = Update.begin(len); DEBUG_MSG("Setting update size %u, result %d\n", len, canBegin); @@ -34,32 +46,39 @@ public: else { // This totally breaks abstraction to up up into the app layer for this, but quick hack to make sure we only // talk to one service during the sw update. - //DEBUG_MSG("FIXME, crufty shutdown of mesh bluetooth for sw update."); - //void stopMeshBluetoothService(); - //stopMeshBluetoothService(); + // DEBUG_MSG("FIXME, crufty shutdown of mesh bluetooth for sw update."); + // void stopMeshBluetoothService(); + // stopMeshBluetoothService(); + + if (RadioLibInterface::instance) + RadioLibInterface::instance->sleep(); // FIXME, nasty hack - the RF95 ISR/SPI code on ESP32 can fail while we are + // writing flash - shut the radio off during updates } } }; +#define MAX_BLOCKSIZE 512 + class DataCharacteristic : public CallbackCharacteristic { -public: - DataCharacteristic() - : CallbackCharacteristic( - "e272ebac-d463-4b98-bc84-5cc1a39ee517", BLECharacteristic::PROPERTY_WRITE) - { - } + public: + DataCharacteristic() : CallbackCharacteristic("e272ebac-d463-4b98-bc84-5cc1a39ee517", BLECharacteristic::PROPERTY_WRITE) {} void onWrite(BLECharacteristic *c) { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); std::string value = c->getValue(); uint32_t len = value.length(); - uint8_t *data = c->getData(); + assert(len <= MAX_BLOCKSIZE); + static uint8_t + data[MAX_BLOCKSIZE]; // we temporarily copy here because I'm worried that a fast sender might be able overwrite srcbuf + memcpy(data, c->getData(), len); // DEBUG_MSG("Writing %u\n", len); crc.update(data, len); Update.write(data, len); + updateActualSize += len; } }; @@ -67,38 +86,36 @@ static BLECharacteristic *resultC; class CRC32Characteristic : public CallbackCharacteristic { -public: - CRC32Characteristic() - : CallbackCharacteristic( - "4826129c-c22a-43a3-b066-ce8f0d5bacc6", BLECharacteristic::PROPERTY_WRITE) - { - } + public: + CRC32Characteristic() : CallbackCharacteristic("4826129c-c22a-43a3-b066-ce8f0d5bacc6", BLECharacteristic::PROPERTY_WRITE) {} void onWrite(BLECharacteristic *c) { BLEKeepAliveCallbacks::onWrite(c); + LockGuard g(updateLock); uint32_t expectedCRC = getValue32(c, 0); + uint32_t actualCRC = crc.finalize(); DEBUG_MSG("expected CRC %u\n", expectedCRC); uint8_t result = 0xff; - // Check the CRC before asking the update to happen. - if (crc.finalize() != expectedCRC) + if (updateActualSize != updateExpectedSize) { + DEBUG_MSG("Expected %u bytes, but received %u bytes!\n", updateExpectedSize, updateActualSize); + result = 0xe1; // FIXME, use real error codes + } else if (actualCRC != expectedCRC) // Check the CRC before asking the update to happen. { - DEBUG_MSG("Invalid CRC!\n"); + DEBUG_MSG("Invalid CRC! expected=%u, actual=%u\n", expectedCRC, actualCRC); result = 0xe0; // FIXME, use real error codes - } - else - { - if (Update.end()) - { + } else { + if (Update.end()) { DEBUG_MSG("OTA done, rebooting in 5 seconds!\n"); rebootAtMsec = millis() + 5000; - } - else - { + } else { DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError()); + + if (RadioLibInterface::instance) + RadioLibInterface::instance->startReceive(); // Resume radio } result = Update.getError(); } @@ -108,8 +125,6 @@ public: } }; - - void bluetoothRebootCheck() { if (rebootAtMsec && millis() > rebootAtMsec) @@ -122,11 +137,15 @@ See bluetooth-api.md */ BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion) { + if (!updateLock) + updateLock = new Lock(); + // Create the BLE Service BLEService *service = server->createService(BLEUUID("cb0b9a0b-a84c-4c0d-bdbb-442e3144ee30"), 25, 0); assert(!resultC); - resultC = new BLECharacteristic("5e134862-7411-4424-ac4a-210937432c77", BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); + resultC = new BLECharacteristic("5e134862-7411-4424-ac4a-210937432c77", + BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY); addWithDesc(service, new TotalSizeCharacteristic, "total image size"); addWithDesc(service, new DataCharacteristic, "data"); @@ -135,7 +154,8 @@ BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::st resultC->addDescriptor(addBLEDescriptor(new BLE2902())); // Needed so clients can request notification - BLECharacteristic *swC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *swC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_SW_VERSION_STR), BLECharacteristic::PROPERTY_READ); swC->setValue(swVersion); service->addCharacteristic(addBLECharacteristic(swC)); @@ -143,7 +163,8 @@ BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::st mfC->setValue(hwVendor); service->addCharacteristic(addBLECharacteristic(mfC)); - BLECharacteristic *hwvC = new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); + BLECharacteristic *hwvC = + new BLECharacteristic(BLEUUID((uint16_t)ESP_GATT_UUID_HW_VERSION_STR), BLECharacteristic::PROPERTY_READ); hwvC->setValue(hwVersion); service->addCharacteristic(addBLECharacteristic(hwvC)); diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h index 60b1f6696..60517a7f2 100644 --- a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h +++ b/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h @@ -1,8 +1,11 @@ #pragma once #include +#include +#include +#include -BLEService *createUpdateService(BLEServer* server, std::string hwVendor, std::string swVersion, std::string hwVersion); +BLEService *createUpdateService(BLEServer *server, std::string hwVendor, std::string swVersion, std::string hwVersion); void destroyUpdateService(); void bluetoothRebootCheck(); \ No newline at end of file diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index a090d132a..5f9149d71 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -19,10 +19,6 @@ class RadioLibInterface : public RadioInterface volatile PendingISR pending = ISR_NONE; volatile bool timerRunning = false; - /** Our ISR code currently needs this to find our active instance - */ - static RadioLibInterface *instance; - /** * Raw ISR handler that just calls our polymorphic method */ @@ -57,6 +53,11 @@ class RadioLibInterface : public RadioInterface /// are _trying_ to receive a packet currently (note - we might just be waiting for one) bool isReceiving; + public: + /** Our ISR code currently needs this to find our active instance + */ + static RadioLibInterface *instance; + /** * Glue functions called from ISR land */ @@ -80,6 +81,13 @@ class RadioLibInterface : public RadioInterface */ virtual bool canSleep(); + /** + * Start waiting to receive a message + * + * External functions can call this method to wake the device from sleep. + */ + virtual void startReceive() = 0; + private: /** start an immediate transmit */ void startSend(MeshPacket *txp); @@ -110,11 +118,6 @@ class RadioLibInterface : public RadioInterface /** are we actively receiving a packet (only called during receiving state) */ virtual bool isActivelyReceiving() = 0; - /** - * Start waiting to receive a message - */ - virtual void startReceive() = 0; - /** * Raw ISR handler that just calls our polymorphic method */ From 5ec5248fe45f784736264ed0f9fdd01d268e0e3d Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 14 May 2020 14:22:11 -0700 Subject: [PATCH 176/197] complete ble ota move --- platformio.ini | 2 +- {lib/BluetoothOTA/src => src/esp32}/BluetoothSoftwareUpdate.cpp | 0 {lib/BluetoothOTA/src => src/esp32}/BluetoothSoftwareUpdate.h | 0 {lib/BluetoothOTA/src => src/esp32}/BluetoothUtil.cpp | 0 {lib/BluetoothOTA/src => src/esp32}/BluetoothUtil.h | 0 {lib/BluetoothOTA/src => src/esp32}/CallbackCharacteristic.h | 0 {lib/BluetoothOTA/src => src/esp32}/SimpleAllocator.cpp | 0 {lib/BluetoothOTA/src => src/esp32}/SimpleAllocator.h | 0 8 files changed, 1 insertion(+), 1 deletion(-) rename {lib/BluetoothOTA/src => src/esp32}/BluetoothSoftwareUpdate.cpp (100%) rename {lib/BluetoothOTA/src => src/esp32}/BluetoothSoftwareUpdate.h (100%) rename {lib/BluetoothOTA/src => src/esp32}/BluetoothUtil.cpp (100%) rename {lib/BluetoothOTA/src => src/esp32}/BluetoothUtil.h (100%) rename {lib/BluetoothOTA/src => src/esp32}/CallbackCharacteristic.h (100%) rename {lib/BluetoothOTA/src => src/esp32}/SimpleAllocator.cpp (100%) rename {lib/BluetoothOTA/src => src/esp32}/SimpleAllocator.h (100%) diff --git a/platformio.ini b/platformio.ini index aead63d98..ad27e2944 100644 --- a/platformio.ini +++ b/platformio.ini @@ -84,7 +84,7 @@ src_filter = upload_speed = 921600 debug_init_break = tbreak setup build_flags = - ${env.build_flags} -Wall -Wextra + ${env.build_flags} -Wall -Wextra -Isrc/esp32 lib_ignore = segger_rtt ; The 1.0 release of the TBEAM board diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp b/src/esp32/BluetoothSoftwareUpdate.cpp similarity index 100% rename from lib/BluetoothOTA/src/BluetoothSoftwareUpdate.cpp rename to src/esp32/BluetoothSoftwareUpdate.cpp diff --git a/lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h b/src/esp32/BluetoothSoftwareUpdate.h similarity index 100% rename from lib/BluetoothOTA/src/BluetoothSoftwareUpdate.h rename to src/esp32/BluetoothSoftwareUpdate.h diff --git a/lib/BluetoothOTA/src/BluetoothUtil.cpp b/src/esp32/BluetoothUtil.cpp similarity index 100% rename from lib/BluetoothOTA/src/BluetoothUtil.cpp rename to src/esp32/BluetoothUtil.cpp diff --git a/lib/BluetoothOTA/src/BluetoothUtil.h b/src/esp32/BluetoothUtil.h similarity index 100% rename from lib/BluetoothOTA/src/BluetoothUtil.h rename to src/esp32/BluetoothUtil.h diff --git a/lib/BluetoothOTA/src/CallbackCharacteristic.h b/src/esp32/CallbackCharacteristic.h similarity index 100% rename from lib/BluetoothOTA/src/CallbackCharacteristic.h rename to src/esp32/CallbackCharacteristic.h diff --git a/lib/BluetoothOTA/src/SimpleAllocator.cpp b/src/esp32/SimpleAllocator.cpp similarity index 100% rename from lib/BluetoothOTA/src/SimpleAllocator.cpp rename to src/esp32/SimpleAllocator.cpp diff --git a/lib/BluetoothOTA/src/SimpleAllocator.h b/src/esp32/SimpleAllocator.h similarity index 100% rename from lib/BluetoothOTA/src/SimpleAllocator.h rename to src/esp32/SimpleAllocator.h From 6961853ed792c45346b51728b01eb0d8fb8dd4be Mon Sep 17 00:00:00 2001 From: geeksville Date: Fri, 15 May 2020 10:16:10 -0700 Subject: [PATCH 177/197] ble software update fixes --- src/esp32/BluetoothSoftwareUpdate.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/esp32/BluetoothSoftwareUpdate.cpp b/src/esp32/BluetoothSoftwareUpdate.cpp index f2fa7c973..0f56cecaa 100644 --- a/src/esp32/BluetoothSoftwareUpdate.cpp +++ b/src/esp32/BluetoothSoftwareUpdate.cpp @@ -40,10 +40,11 @@ class TotalSizeCharacteristic : public CallbackCharacteristic crc.reset(); bool canBegin = Update.begin(len); DEBUG_MSG("Setting update size %u, result %d\n", len, canBegin); - if (!canBegin) + if (!canBegin) { // Indicate failure by forcing the size to 0 - c->setValue(0UL); - else { + uint32_t zero = 0; + c->setValue(zero); + } else { // This totally breaks abstraction to up up into the app layer for this, but quick hack to make sure we only // talk to one service during the sw update. // DEBUG_MSG("FIXME, crufty shutdown of mesh bluetooth for sw update."); @@ -113,12 +114,13 @@ class CRC32Characteristic : public CallbackCharacteristic rebootAtMsec = millis() + 5000; } else { DEBUG_MSG("Error Occurred. Error #: %d\n", Update.getError()); - - if (RadioLibInterface::instance) - RadioLibInterface::instance->startReceive(); // Resume radio } result = Update.getError(); } + + if (RadioLibInterface::instance) + RadioLibInterface::instance->startReceive(); // Resume radio + assert(resultC); resultC->setValue(&result, 1); resultC->notify(); From 95e952b896c39108a353606895d40dec831bf87e Mon Sep 17 00:00:00 2001 From: geeksville Date: Sat, 16 May 2020 16:09:06 -0700 Subject: [PATCH 178/197] todo update --- bin/build-all.sh | 3 +++ docs/software/nrf52-TODO.md | 25 ++++++++++++++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/bin/build-all.sh b/bin/build-all.sh index edea28cd6..55d72fa87 100755 --- a/bin/build-all.sh +++ b/bin/build-all.sh @@ -39,6 +39,9 @@ function do_build { cp $SRCELF $OUTDIR/elfs/firmware-$ENV_NAME-$COUNTRY-$VERSION.elf } +# Make sure our submodules are current +git submodule update + # Important to pull latest version of libs into all device flavors, otherwise some devices might be stale platformio lib update diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 69afcb946..47a520c8d 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -1,21 +1,20 @@ # NRF52 TODO +## Misc work items + +* on node 0x1c transmit complete interrupt never comes in - though other nodes receive the packet + ## Initial work items Minimum items needed to make sure hardware is good. +- test my hackedup bootloader on the real hardware - add a hard fault handler - Use the PMU driver on real hardware - Use new radio driver on real hardware - Use UC1701 LCD driver on real hardware. Still need to create at startup and probe on SPI - test the LEDs - test the buttons -- make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): - #define PIN_SPI_MISO (46) - #define PIN_SPI_MOSI (45) - #define PIN_SPI_SCK (47) - #define PIN_WIRE_SDA (26) - #define PIN_WIRE_SCL (27) ## Secondary work items @@ -60,6 +59,7 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- make a Mfg Controller and device under test classes as examples of custom app code for third party devs. Make a post about this. Use a custom payload type code. Have device under test send a broadcast with max hopcount of 0 for the 'mfgcontroller' payload type. mfg controller will read SNR and reply. DOT will declare failure/success and switch to the regular app screen. - Hook Segger RTT to the nordic logging framework. https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/debugging-with-real-time-terminal - Use nordic logging for DEBUG_MSG - use the Jumper simulator to run meshes of simulated hardware: https://docs.jumper.io/docs/install.html @@ -72,11 +72,14 @@ Nice ideas worth considering someday... - in addition to the main CPU watchdog, use the PMU watchdog as a really big emergency hammer - turn on 'shipping mode' in the PMU when device is 'off' - to cut battery draw to essentially zero - make Lorro_BQ25703A read/write operations atomic, current version could let other threads sneak in (once we start using threads) -- turn on DFU assistance in the appload using the nordic DFU helper lib call - make the segger logbuffer larger, move it to RAM that is preserved across reboots and support reading it out at runtime (to allow full log messages to be included in crash reports). Share this code with ESP32 (use gcc noinit attribute) - convert hardfaults/panics/asserts/wd exceptions into fault codes sent to phone - stop enumerating all i2c devices at boot, it wastes power & time - consider using "SYSTEMOFF" deep sleep mode, without RAM retension. Only useful for 'truly off - wake only by button press' only saves 1.5uA vs SYSTEMON. (SYSTEMON only costs 1.5uA). Possibly put PMU into shipping mode? +- change the BLE protocol to be more symmetric. Have the phone _also_ host a GATT service which receives writes to + 'fromradio'. This would allow removing the 'fromnum' mailbox/notify scheme of the current approach and decrease the number of packet handoffs when a packet is received. +- Using the preceeding, make a generalized 'nrf52/esp32 ble to internet' bridge service. To let nrf52 apps do MQTT/UDP/HTTP POST/HTTP GET operations to web services. +- lower advertise interval to save power, lower ble transmit power to save power ## Old unorganized notes @@ -104,6 +107,14 @@ Nice ideas worth considering someday... - add a NEMA based GPS driver to test GPS - DONE use "variants" to get all gpio bindings - DONE plug in correct variants for the real board +- turn on DFU assistance in the appload using the nordic DFU helper lib call +- make a new boarddef with a variant.h file. Fix pins in that file. In particular (at least): + #define PIN_SPI_MISO (46) + #define PIN_SPI_MOSI (45) + #define PIN_SPI_SCK (47) + #define PIN_WIRE_SDA (26) + #define PIN_WIRE_SCL (27) +- customize the bootloader to use proper button bindings ``` From ef1463a6a916e26bc0756cf3124187151d7ed0f6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 17 May 2020 04:44:48 -0700 Subject: [PATCH 179/197] have tbeam charge at max rate (450mA) --- platformio.ini | 2 +- src/esp32/main-esp32.cpp | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index ad27e2944..3706fa20c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -93,7 +93,7 @@ extends = esp32_base board = ttgo-t-beam lib_deps = ${env.lib_deps} - AXP202X_Library + https://github.com/meshtastic/AXP202X_Library.git build_flags = ${esp32_base.build_flags} -D TBEAM_V10 diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index b0e1406b5..03535b385 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -119,16 +119,8 @@ void axp192Init() DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE"); DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE"); + axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA #if 0 - // cribbing from https://github.com/m5stack/M5StickC/blob/master/src/AXP192.cpp to fix charger to be more like 300ms. - // I finally found an english datasheet. Will look at this later - but suffice it to say the default code from TTGO has 'issues' - - axp.adc1Enable(0xff, 1); // turn on all adcs - uint8_t val = 0xc2; - axp._writeByte(0x33, 1, &val); // Bat charge voltage to 4.2, Current 280mA - val = 0b11110010; - // Set ADC sample rate to 200hz - // axp._writeByte(0x84, 1, &val); // Not connected //val = 0xfc; @@ -191,6 +183,8 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif +#define MIN_BAT_MILLIVOLTS 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ + /// loop code specific to ESP32 targets void esp32Loop() { @@ -231,5 +225,11 @@ void esp32Loop() readPowerStatus(); axp.clearIRQ(); } + + float v = axp.getBattVoltage(); + DEBUG_MSG("Bat volt %f\n", v); + //if(v >= MIN_BAT_MILLIVOLTS / 2 && v < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep + // powerFSM.trigger(EVENT_LOW_BATTERY); + #endif // T_BEAM_V10 } \ No newline at end of file From efc239533ca741790786cd6c9b639aeb21671222 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 17 May 2020 04:51:36 -0700 Subject: [PATCH 180/197] Fix #133 - force deep sleep if battery reaches 10% --- src/PowerFSM.cpp | 7 +++++++ src/PowerFSM.h | 1 + src/esp32/main-esp32.cpp | 9 ++++----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index ed6eaf4ff..b60204fd5 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -153,6 +153,13 @@ void PowerFSM_setup() powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press"); powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers + // Handle critically low power battery by forcing deep sleep + powerFSM.add_transition(&stateBOOT, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateLS, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateNB, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateDARK, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateON, &stateSDS, EVENT_LOW_BATTERY, NULL, "LowBat"); + powerFSM.add_transition(&stateDARK, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); powerFSM.add_transition(&stateON, &stateON, EVENT_BLUETOOTH_PAIR, NULL, "Bluetooth pairing"); diff --git a/src/PowerFSM.h b/src/PowerFSM.h index c94ffabf9..ecaea70ac 100644 --- a/src/PowerFSM.h +++ b/src/PowerFSM.h @@ -13,6 +13,7 @@ #define EVENT_BLUETOOTH_PAIR 7 #define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen #define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth +#define EVENT_LOW_BATTERY 10 // Battery is critically low, go to sleep extern Fsm powerFSM; diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 03535b385..904c921e8 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -183,7 +183,7 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif -#define MIN_BAT_MILLIVOLTS 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ +#define MIN_BAT_MILLIVOLTS 5000 // 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ /// loop code specific to ESP32 targets void esp32Loop() @@ -226,10 +226,9 @@ void esp32Loop() axp.clearIRQ(); } - float v = axp.getBattVoltage(); - DEBUG_MSG("Bat volt %f\n", v); - //if(v >= MIN_BAT_MILLIVOLTS / 2 && v < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep - // powerFSM.trigger(EVENT_LOW_BATTERY); + if (powerStatus.haveBattery && !powerStatus.usb && + axp.getBattVoltage() < MIN_BAT_MILLIVOLTS) // If we have a battery at all and it is less than 10% full, force deep sleep + powerFSM.trigger(EVENT_LOW_BATTERY); #endif // T_BEAM_V10 } \ No newline at end of file From ef831a0b4d1a05298f01a55ebbe0e939c10ef5a6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 17 May 2020 05:11:32 -0700 Subject: [PATCH 181/197] Fix leaving display on in deep sleep. We shutoff screen immediately, rather than waiting for our loop call() --- src/screen.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/screen.h b/src/screen.h index 634cdd10c..be5444c1e 100644 --- a/src/screen.h +++ b/src/screen.h @@ -100,7 +100,14 @@ class Screen : public PeriodicTask void setup(); /// Turns the screen on/off. - void setOn(bool on) { enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); } + void setOn(bool on) + { + if (!on) + handleSetOn( + false); // We handle off commands immediately, because they might be called because the CPU is shutting down + else + enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); + } /// Handles a button press. void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); } From 19f5a5ef79acdc29805e1b5edd4356e5f12e3c54 Mon Sep 17 00:00:00 2001 From: geeksville Date: Sun, 17 May 2020 05:12:16 -0700 Subject: [PATCH 182/197] oops - use correct battery shutoff voltage --- src/esp32/main-esp32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/esp32/main-esp32.cpp b/src/esp32/main-esp32.cpp index 904c921e8..7f9780862 100644 --- a/src/esp32/main-esp32.cpp +++ b/src/esp32/main-esp32.cpp @@ -183,7 +183,7 @@ uint32_t axpDebugRead() Periodic axpDebugOutput(axpDebugRead); #endif -#define MIN_BAT_MILLIVOLTS 5000 // 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ +#define MIN_BAT_MILLIVOLTS 3690 // millivolts. 10% per https://blog.ampow.com/lipo-voltage-chart/ /// loop code specific to ESP32 targets void esp32Loop() From 5440cbec6abc3e5963400f9f614c2ac6a21cc099 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 18 May 2020 08:08:57 -0700 Subject: [PATCH 183/197] Update CNAME --- docs/CNAME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CNAME b/docs/CNAME index 42401b5e8..c9a8efa68 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -www.meshtastic.org \ No newline at end of file +meshtastic.org \ No newline at end of file From 2a6858fa346c7007aaf8919d39547c20d7798da9 Mon Sep 17 00:00:00 2001 From: Kevin Hester Date: Mon, 18 May 2020 08:10:51 -0700 Subject: [PATCH 184/197] Update CNAME --- docs/CNAME | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CNAME b/docs/CNAME index c9a8efa68..42401b5e8 100644 --- a/docs/CNAME +++ b/docs/CNAME @@ -1 +1 @@ -meshtastic.org \ No newline at end of file +www.meshtastic.org \ No newline at end of file From 53c3d9baa2be13efbc4af366d58661b4c227ae19 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 18 May 2020 17:02:51 -0700 Subject: [PATCH 185/197] doc updates --- docs/software/mesh-alg.md | 6 +++--- docs/software/nrf52-TODO.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 2b55a3a34..393abce67 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -8,13 +8,13 @@ great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ reliable messaging tasks (stage one for DSR): - add a 'messagePeek' hook for all messages that pass through our node. -- use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. -- keep possible retries in the list with rebroadcast messages? +- DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. +- keep possible retries in the list with to be rebroadcast messages? - for each message keep a count of # retries (max of three) - delay some random time for each retry (large enough to allow for acks to come in) - once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) -- add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as two bits in the header. +- add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. dsr tasks diff --git a/docs/software/nrf52-TODO.md b/docs/software/nrf52-TODO.md index 47a520c8d..ae89750dd 100644 --- a/docs/software/nrf52-TODO.md +++ b/docs/software/nrf52-TODO.md @@ -2,8 +2,6 @@ ## Misc work items -* on node 0x1c transmit complete interrupt never comes in - though other nodes receive the packet - ## Initial work items Minimum items needed to make sure hardware is good. @@ -42,7 +40,6 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At - use SX126x::startReceiveDutyCycleAuto to save power by sleeping and briefly waking to check for preamble bits. Change xmit rules to have more preamble bits. - turn back on in-radio destaddr checking for RF95 -- remove the MeshRadio wrapper - we don't need it anymore, just do everythin in RadioInterface subclasses. - figure out what the correct current limit should be for the sx1262, currently we just use the default 100 - put sx1262 in sleepmode when processor gets shutdown (or rebooted), ideally even for critical faults (to keep power draw low). repurpose deepsleep state for this. - good power management tips: https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/optimizing-power-on-nrf52-designs @@ -59,6 +56,8 @@ Needed to be fully functional at least at the same level of the ESP32 boards. At Nice ideas worth considering someday... +- Use flego to me an iOS/linux app? https://felgo.com/doc/qt/qtbluetooth-index/ or +- Use flutter to make an iOS/linux app? https://github.com/Polidea/FlutterBleLib - make a Mfg Controller and device under test classes as examples of custom app code for third party devs. Make a post about this. Use a custom payload type code. Have device under test send a broadcast with max hopcount of 0 for the 'mfgcontroller' payload type. mfg controller will read SNR and reply. DOT will declare failure/success and switch to the regular app screen. - Hook Segger RTT to the nordic logging framework. https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/debugging-with-real-time-terminal - Use nordic logging for DEBUG_MSG @@ -115,6 +114,7 @@ Nice ideas worth considering someday... #define PIN_WIRE_SDA (26) #define PIN_WIRE_SCL (27) - customize the bootloader to use proper button bindings +- remove the MeshRadio wrapper - we don't need it anymore, just do everything in RadioInterface subclasses. ``` From 26d3ef529e67cb7e354664d155659e59a9839e58 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 18 May 2020 17:35:23 -0700 Subject: [PATCH 186/197] Use the hop_limit field of MeshPacket to limit max delivery depth in the mesh. --- docs/software/mesh-alg.md | 8 +++---- proto | 2 +- src/mesh/FloodingRouter.cpp | 44 ++++++++++++++++------------------ src/mesh/MeshService.cpp | 1 + src/mesh/MeshTypes.h | 9 +++++++ src/mesh/RadioInterface.cpp | 3 ++- src/mesh/RadioInterface.h | 9 ++++++- src/mesh/RadioLibInterface.cpp | 5 +++- src/mesh/Router.h | 3 ++- 9 files changed, 51 insertions(+), 33 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 393abce67..19e7e506c 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -1,12 +1,10 @@ # Mesh broadcast algorithm -FIXME - instead look for standard solutions. this approach seems really suboptimal, because too many nodes will try to rebroast. If -all else fails could always use the stock Radiohead solution - though super inefficient. - great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ reliable messaging tasks (stage one for DSR): +- fix FIXME - should snoop packet not sent to us - add a 'messagePeek' hook for all messages that pass through our node. - DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. - keep possible retries in the list with to be rebroadcast messages? @@ -14,7 +12,6 @@ reliable messaging tasks (stage one for DSR): - delay some random time for each retry (large enough to allow for acks to come in) - once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) -- add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. dsr tasks @@ -55,6 +52,8 @@ when we receive a routeError packet TODO: +- optimize our generalized flooding with heuristics, possibly have particular nodes self mark as 'router' nodes. + - DONE reread the radiohead mesh implementation - hop to hop acknowledgement seems VERY expensive but otherwise it seems like DSR - DONE read about mesh routing solutions (DSR and AODV) - DONE read about general mesh flooding solutions (naive, MPR, geo assisted) @@ -62,6 +61,7 @@ TODO: - REJECTED - seems dying - possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead - update duty cycle spreadsheet for our typical usecase - DONE generalize naive flooding +- DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept diff --git a/proto b/proto index bc3ecd97e..5799cb10b 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit bc3ecd97e381b724c1a28acce0d12c688de73ba3 +Subproject commit 5799cb10b8f3cf353e7791d0609002cc93d9d13d diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 6db03dd4f..e9941fb12 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -2,8 +2,6 @@ #include "configuration.h" #include "mesh-pb-constants.h" -static bool supportFlooding = true; // Sometimes to simplify debugging we want jusT simple broadcast only - FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} /** @@ -13,9 +11,7 @@ FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} */ ErrorCode FloodingRouter::send(MeshPacket *p) { - // We update our table of recent broadcasts, even for messages we send - if (supportFlooding) - wasSeenRecently(p); + wasSeenRecently(p); return Router::send(p); } @@ -29,28 +25,28 @@ ErrorCode FloodingRouter::send(MeshPacket *p) */ void FloodingRouter::handleReceived(MeshPacket *p) { - if (supportFlooding) { - if (wasSeenRecently(p)) { - DEBUG_MSG("Ignoring incoming floodmsg, because we've already seen it\n"); - packetPool.release(p); - } else { - if (p->to == NODENUM_BROADCAST) { - if (p->id != 0) { - MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it + if (wasSeenRecently(p)) { + DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n"); + packetPool.release(p); + } else { + if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) { + if (p->id != 0) { + MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it - DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - // Note: we are careful to resend using the original senders node id - // We are careful not to call our hooked version of send() - because we don't want to check this again - Router::send(tosend); + tosend->hop_limit--; // bump down the hop count - } else { - DEBUG_MSG("Ignoring a simple (0 hop) broadcast\n"); - } + DEBUG_MSG("Rebroadcasting received floodmsg to neighbors, fr=0x%x,to=0x%x,id=%d,hop_limit=%d\n", p->from, p->to, + p->id, tosend->hop_limit); + // Note: we are careful to resend using the original senders node id + // We are careful not to call our hooked version of send() - because we don't want to check this again + Router::send(tosend); + + } else { + DEBUG_MSG("Ignoring a simple (0 id) broadcast\n"); } - - // handle the packet as normal - Router::handleReceived(p); } - } else + + // handle the packet as normal Router::handleReceived(p); + } } diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 5f4b51bab..28aba7fe7 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -285,6 +285,7 @@ MeshPacket *MeshService::allocForSending() p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. p->from = nodeDB.getNodeNum(); p->to = NODENUM_BROADCAST; + p->hop_limit = HOP_MAX; p->id = generatePacketId(); p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 0d9783e14..04bb13ad6 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -14,6 +14,15 @@ typedef uint8_t PacketId; // A packet sequence number #define ERRNO_NO_INTERFACES 33 #define ERRNO_UNKNOWN 32 // pick something that doesn't conflict with RH_ROUTER_ERROR_UNABLE_TO_DELIVER +/** + * the max number of hops a message can pass through, used as the default max for hop_limit in MeshPacket. + * + * We reserve 3 bits in the header so this could be up to 7, but given the high range of lora and typical usecases, keeping + * maxhops to 3 should be fine for a while. This also serves to prevent routing/flooding attempts to be attempted for + * too long. + **/ +#define HOP_MAX 3 + typedef int ErrorCode; /// Alloc and free packets to our global, ISR safe pool diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 3ad60005c..123e128a5 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -115,8 +115,9 @@ size_t RadioInterface::beginSending(MeshPacket *p) h->from = p->from; h->to = p->to; - h->flags = 0; h->id = p->id; + assert(p->hop_limit <= HOP_MAX); + h->flags = p->hop_limit; // if the sender nodenum is zero, that means uninitialized assert(h->from); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 6c7dbd79b..806617590 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -16,7 +16,14 @@ * wtih the old radiohead implementation. */ typedef struct { - uint8_t to, from, id, flags; + uint8_t to, from, id; + + /** + * Usage of flags: + * + * The bottom three bits of flags are use to store hop_limit when sent over the wire. + **/ + uint8_t flags; } PacketHeader; typedef enum { diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index e09c4f4a9..78b9f661c 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -288,13 +288,16 @@ void RadioLibInterface::handleReceiveInterrupt() rxGood++; if (h->to != 255 && h->to != ourAddr) { - DEBUG_MSG("ignoring packet not sent to us\n"); + DEBUG_MSG("FIXME - should snoop packet not sent to us\n"); } else { MeshPacket *mp = packetPool.allocZeroed(); mp->from = h->from; mp->to = h->to; mp->id = h->id; + assert(HOP_MAX <= 0x07); // If hopmax changes, carefully check this code + mp->hop_limit = h->flags & 0x07; + addReceiveMetadata(mp); mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 20378371d..77538a0bd 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -59,7 +59,8 @@ class Router * Handle any packet that is received by an interface on this node. * Note: some packets may merely being passed through this node and will be forwarded elsewhere. * - * Note: this method will free the provided packet + * Note: this packet will never be called for messages sent/generated by this node. + * Note: this method will free the provided packet. */ virtual void handleReceived(MeshPacket *p); }; From 976bdad067825f59fb7eff40c43cd1e85bba2278 Mon Sep 17 00:00:00 2001 From: geeksville Date: Mon, 18 May 2020 17:57:58 -0700 Subject: [PATCH 187/197] sniffReceived now allows router to inspect packets not destined for this node --- docs/software/mesh-alg.md | 10 +++++----- src/mesh/FloodingRouter.cpp | 1 + src/mesh/MeshService.cpp | 2 +- src/mesh/MeshTypes.h | 5 ++++- src/mesh/RadioLibInterface.cpp | 36 ++++++++++++++++------------------ src/mesh/Router.cpp | 20 +++++++++++++++++-- src/mesh/Router.h | 6 ++++++ 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 19e7e506c..b809692a4 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -4,11 +4,13 @@ great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ reliable messaging tasks (stage one for DSR): -- fix FIXME - should snoop packet not sent to us -- add a 'messagePeek' hook for all messages that pass through our node. +- DONE generalize naive flooding +- DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. +- DONE add a 'snoopReceived' hook for all messages that pass through our node. - DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. - keep possible retries in the list with to be rebroadcast messages? -- for each message keep a count of # retries (max of three) +- for each message keep a count of # retries (max of three). allow this to _also_ work for broadcasts. +- Don't use broadcasts for the network pings (close open github issue) - delay some random time for each retry (large enough to allow for acks to come in) - once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) @@ -60,8 +62,6 @@ TODO: - DONE reread the disaster radio protocol docs - seems based on Babel (which is AODVish) - REJECTED - seems dying - possibly dash7? https://www.slideshare.net/MaartenWeyn1/dash7-alliance-protocol-technical-presentation https://github.com/MOSAIC-LoPoW/dash7-ap-open-source-stack - does the opensource stack implement multihop routing? flooding? their discussion mailing list looks dead-dead - update duty cycle spreadsheet for our typical usecase -- DONE generalize naive flooding -- DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. a description of DSR: https://tools.ietf.org/html/rfc4728 good slides here: https://www.slideshare.net/ashrafmath/dynamic-source-routing good description of batman protocol: https://www.open-mesh.org/projects/open-mesh/wiki/BATMANConcept diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index e9941fb12..c40211a8f 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -29,6 +29,7 @@ void FloodingRouter::handleReceived(MeshPacket *p) DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n"); packetPool.release(p); } else { + // If a broadcast, possibly _also_ send copies out into the mesh. (FIXME, do something smarter than naive flooding here) if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) { if (p->id != 0) { MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 28aba7fe7..986deb3fd 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -285,7 +285,7 @@ MeshPacket *MeshService::allocForSending() p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. p->from = nodeDB.getNodeNum(); p->to = NODENUM_BROADCAST; - p->hop_limit = HOP_MAX; + p->hop_limit = HOP_RELIABLE; p->id = generatePacketId(); p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp diff --git a/src/mesh/MeshTypes.h b/src/mesh/MeshTypes.h index 04bb13ad6..f491ce508 100644 --- a/src/mesh/MeshTypes.h +++ b/src/mesh/MeshTypes.h @@ -21,7 +21,10 @@ typedef uint8_t PacketId; // A packet sequence number * maxhops to 3 should be fine for a while. This also serves to prevent routing/flooding attempts to be attempted for * too long. **/ -#define HOP_MAX 3 +#define HOP_MAX 7 + +/// We normally just use max 3 hops for sending reliable messages +#define HOP_RELIABLE 3 typedef int ErrorCode; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 78b9f661c..ba0c32f05 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -2,7 +2,6 @@ #include "MeshTypes.h" #include "OSTimer.h" #include "mesh-pb-constants.h" -#include // FIXME, this class shouldn't need to look into nodedb #include #include #include @@ -284,31 +283,30 @@ void RadioLibInterface::handleReceiveInterrupt() rxBad++; } else { const PacketHeader *h = (PacketHeader *)radiobuf; - uint8_t ourAddr = nodeDB.getNodeNum(); rxGood++; - if (h->to != 255 && h->to != ourAddr) { - DEBUG_MSG("FIXME - should snoop packet not sent to us\n"); - } else { - MeshPacket *mp = packetPool.allocZeroed(); - mp->from = h->from; - mp->to = h->to; - mp->id = h->id; - assert(HOP_MAX <= 0x07); // If hopmax changes, carefully check this code - mp->hop_limit = h->flags & 0x07; + // Note: we deliver _all_ packets to our router (i.e. our interface is intentionally promiscuous). + // This allows the router and other apps on our node to sniff packets (usually routing) between other + // nodes. + MeshPacket *mp = packetPool.allocZeroed(); - addReceiveMetadata(mp); + mp->from = h->from; + mp->to = h->to; + mp->id = h->id; + assert(HOP_MAX <= 0x07); // If hopmax changes, carefully check this code + mp->hop_limit = h->flags & 0x07; - mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point - assert(payloadLen <= sizeof(mp->encrypted.bytes)); - memcpy(mp->encrypted.bytes, payload, payloadLen); - mp->encrypted.size = payloadLen; + addReceiveMetadata(mp); - DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + mp->which_payload = MeshPacket_encrypted_tag; // Mark that the payload is still encrypted at this point + assert(payloadLen <= sizeof(mp->encrypted.bytes)); + memcpy(mp->encrypted.bytes, payload, payloadLen); + mp->encrypted.size = payloadLen; - deliverToReceiver(mp); - } + DEBUG_MSG("Lora RX interrupt from=0x%x, id=%u\n", mp->from, mp->id); + + deliverToReceiver(mp); } } } diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 3752a2fbd..21b928f22 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -3,6 +3,7 @@ #include "GPS.h" #include "configuration.h" #include "mesh-pb-constants.h" +#include /** * Router todo @@ -80,6 +81,15 @@ ErrorCode Router::send(MeshPacket *p) } } +/** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ +void Router::sniffReceived(MeshPacket *p) +{ + DEBUG_MSG("Sniffing packet not sent to us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); +} + /** * Handle any packet that is received by an interface on this node. * Note: some packets may merely being passed through this node and will be forwarded elsewhere. @@ -93,6 +103,8 @@ void Router::handleReceived(MeshPacket *p) assert(p->which_payload == MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets + // FIXME - someday don't send routing packets encrypted. That would allow us to route for other channels without + // being able to decrypt their data. // Try to decrypt the packet if we can static uint8_t bytes[MAX_RHPACKETLEN]; memcpy(bytes, p->encrypted.bytes, @@ -106,8 +118,12 @@ void Router::handleReceived(MeshPacket *p) // parsing was successful, queue for our recipient p->which_payload = MeshPacket_decoded_tag; - DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - notifyPacketReceived.notifyObservers(p); + sniffReceived(p); + uint8_t ourAddr = nodeDB.getNodeNum(); + if (p->to == NODENUM_BROADCAST || p->to == ourAddr) { + DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + notifyPacketReceived.notifyObservers(p); + } } packetPool.release(p); diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 77538a0bd..03d75d33d 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -63,6 +63,12 @@ class Router * Note: this method will free the provided packet. */ virtual void handleReceived(MeshPacket *p); + + /** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ + virtual void sniffReceived(MeshPacket *p); }; extern Router &router; \ No newline at end of file From 6b020149f3a3067f9a220e89813185eb4c3b2c92 Mon Sep 17 00:00:00 2001 From: Dafeman <47490997+Dafeman@users.noreply.github.com> Date: Tue, 19 May 2020 13:50:07 +1200 Subject: [PATCH 188/197] Update GUI Install --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 352a8e79a..5fa2c871d 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,8 @@ Please post comments on our [group chat](https://meshtastic.discourse.group/) if 6. In ESPHome Flasher, refresh the serial ports and select your board. 7. Browse to the previously downloaded firmware and select the correct firmware based on the board type, country and frequency. 8. Select Flash ESP. -9. Once finished, the terminal should start displaying debug messages including the Bluetooth passphrase when you try connect from your phone (handy if you don’t have a screen). +9. Once complete, “Done! Flashing is complete!” will be shown. +10. Debug messages sent from the Meshtastic device can be viewed with a terminal program such as [PuTTY](https://www.putty.org/) (Windows only). Within PuTTY, click “Serial”, enter the “Serial line” com port (can be found at step 4), enter “Speed” as 921600, then click “Open”. ### Installing from a commandline From cca4867987cdc2d3767ec1169f65bb36cb4cbe89 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 10:27:28 -0700 Subject: [PATCH 189/197] want_ack flag added --- docs/software/mesh-alg.md | 11 ++++++--- proto | 2 +- src/mesh/FloodingRouter.cpp | 8 ++++--- src/mesh/FloodingRouter.h | 4 ---- src/mesh/RadioInterface.cpp | 2 +- src/mesh/RadioInterface.h | 3 +++ src/mesh/RadioLibInterface.cpp | 5 +++-- src/mesh/mesh.pb.c | 3 +++ src/mesh/mesh.pb.h | 41 ++++++++++++++++++++++++++++------ 9 files changed, 58 insertions(+), 21 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index b809692a4..4bf2afa30 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -8,9 +8,9 @@ reliable messaging tasks (stage one for DSR): - DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. - DONE add a 'snoopReceived' hook for all messages that pass through our node. - DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. -- keep possible retries in the list with to be rebroadcast messages? -- for each message keep a count of # retries (max of three). allow this to _also_ work for broadcasts. -- Don't use broadcasts for the network pings (close open github issue) +- in the router receive path?, send an ack packet if want_ack was set and we are the final destination. FIXME, for now don't handle multihop or merging of data replies with these acks. +- keep a list of packets waiting for acks +- for each message keep a count of # retries (max of three). Local to the node, only for the most immediate hop, ignorant of multihop routing. - delay some random time for each retry (large enough to allow for acks to come in) - once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) @@ -20,6 +20,11 @@ dsr tasks - do "hop by hop" routing - when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. - otherwise, use next_hop and start sending a message (with ack request) towards that node. +- Don't use broadcasts for the network pings (close open github issue) + +optimizations: + +- use a priority queue for the messages waiting to send. Send acks first, then routing messages, then data messages, then broadcasts? when we receive any packet diff --git a/proto b/proto index 5799cb10b..e095ea92e 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit 5799cb10b8f3cf353e7791d0609002cc93d9d13d +Subproject commit e095ea92e62edc3f5dd6864c3d08d113fd8842e2 diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index c40211a8f..f16405e46 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -2,7 +2,7 @@ #include "configuration.h" #include "mesh-pb-constants.h" -FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} +FloodingRouter::FloodingRouter() {} /** * Send a packet on a suitable interface. This routine will @@ -11,7 +11,8 @@ FloodingRouter::FloodingRouter() : toResend(MAX_NUM_NODES) {} */ ErrorCode FloodingRouter::send(MeshPacket *p) { - wasSeenRecently(p); + // Add any messages _we_ send to the seen message list + wasSeenRecently(p); // FIXME, move this to a sniffSent method return Router::send(p); } @@ -29,7 +30,8 @@ void FloodingRouter::handleReceived(MeshPacket *p) DEBUG_MSG("Ignoring incoming msg, because we've already seen it\n"); packetPool.release(p); } else { - // If a broadcast, possibly _also_ send copies out into the mesh. (FIXME, do something smarter than naive flooding here) + // If a broadcast, possibly _also_ send copies out into the mesh. + // (FIXME, do something smarter than naive flooding here) if (p->to == NODENUM_BROADCAST && p->hop_limit > 0) { if (p->id != 0) { MeshPacket *tosend = packetPool.allocCopy(*p); // keep a copy because we will be sending it diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 996e9f7ae..e7e1b9610 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -30,10 +30,6 @@ class FloodingRouter : public Router, private PacketHistory { private: - /** - * Packets we've received that we need to resend after a short delay - */ - PointerQueue toResend; public: /** diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 123e128a5..45ecc42a8 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -117,7 +117,7 @@ size_t RadioInterface::beginSending(MeshPacket *p) h->to = p->to; h->id = p->id; assert(p->hop_limit <= HOP_MAX); - h->flags = p->hop_limit; + h->flags = p->hop_limit | (p->want_ack ? PACKET_FLAGS_WANT_ACK_MASK : 0); // if the sender nodenum is zero, that means uninitialized assert(h->from); diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 806617590..419763750 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -11,6 +11,9 @@ #define MAX_RHPACKETLEN 256 +#define PACKET_FLAGS_HOP_MASK 0x07 +#define PACKET_FLAGS_WANT_ACK_MASK 0x08 + /** * This structure has to exactly match the wire layout when sent over the radio link. Used to keep compatibility * wtih the old radiohead implementation. diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index ba0c32f05..5bf3b5eea 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -294,8 +294,9 @@ void RadioLibInterface::handleReceiveInterrupt() mp->from = h->from; mp->to = h->to; mp->id = h->id; - assert(HOP_MAX <= 0x07); // If hopmax changes, carefully check this code - mp->hop_limit = h->flags & 0x07; + assert(HOP_MAX <= PACKET_FLAGS_HOP_MASK); // If hopmax changes, carefully check this code + mp->hop_limit = h->flags & PACKET_FLAGS_HOP_MASK; + mp->want_ack = !!(h->flags & PACKET_FLAGS_WANT_ACK_MASK); addReceiveMetadata(mp); diff --git a/src/mesh/mesh.pb.c b/src/mesh/mesh.pb.c index 0b2c5b8ce..321576556 100644 --- a/src/mesh/mesh.pb.c +++ b/src/mesh/mesh.pb.c @@ -51,6 +51,9 @@ PB_BIND(FromRadio, FromRadio, 2) PB_BIND(ToRadio, ToRadio, 2) +PB_BIND(ManufacturingData, ManufacturingData, AUTO) + + diff --git a/src/mesh/mesh.pb.h b/src/mesh/mesh.pb.h index d193d38ee..ce9bc4e36 100644 --- a/src/mesh/mesh.pb.h +++ b/src/mesh/mesh.pb.h @@ -50,6 +50,13 @@ typedef struct _DebugString { char message[256]; } DebugString; +typedef struct _ManufacturingData { + uint32_t fradioFreq; + pb_callback_t hw_model; + pb_callback_t hw_version; + int32_t selftest_result; +} ManufacturingData; + typedef struct _MyNodeInfo { int32_t my_node_num; bool has_gps; @@ -146,6 +153,7 @@ typedef struct _MeshPacket { float rx_snr; uint32_t rx_time; uint32_t hop_limit; + bool want_ack; } MeshPacket; typedef struct _DeviceState { @@ -209,7 +217,7 @@ typedef struct _ToRadio { #define User_init_default {"", "", "", {0}} #define RouteDiscovery_init_default {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define SubPacket_init_default {0, {Position_init_default}, 0, 0, 0, {0}} -#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0} +#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0} #define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default} #define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -219,12 +227,13 @@ typedef struct _ToRadio { #define DebugString_init_default {""} #define FromRadio_init_default {0, 0, {MeshPacket_init_default}} #define ToRadio_init_default {0, {MeshPacket_init_default}} +#define ManufacturingData_init_default {0, {{NULL}, NULL}, {{NULL}, NULL}, 0} #define Position_init_zero {0, 0, 0, 0, 0} #define Data_init_zero {_Data_Type_MIN, {0, {0}}} #define User_init_zero {"", "", "", {0}} #define RouteDiscovery_init_zero {0, {0, 0, 0, 0, 0, 0, 0, 0}} #define SubPacket_init_zero {0, {Position_init_zero}, 0, 0, 0, {0}} -#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0} +#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0} #define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, ""} #define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero} #define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} @@ -234,6 +243,7 @@ typedef struct _ToRadio { #define DebugString_init_zero {""} #define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}} #define ToRadio_init_zero {0, {MeshPacket_init_zero}} +#define ManufacturingData_init_zero {0, {{NULL}, NULL}, {{NULL}, NULL}, 0} /* Field tags (for use in manual encoding/decoding) */ #define ChannelSettings_tx_power_tag 1 @@ -243,6 +253,10 @@ typedef struct _ToRadio { #define Data_typ_tag 1 #define Data_payload_tag 2 #define DebugString_message_tag 1 +#define ManufacturingData_fradioFreq_tag 1 +#define ManufacturingData_hw_model_tag 2 +#define ManufacturingData_hw_version_tag 3 +#define ManufacturingData_selftest_result_tag 4 #define MyNodeInfo_my_node_num_tag 1 #define MyNodeInfo_has_gps_tag 2 #define MyNodeInfo_num_channels_tag 3 @@ -299,6 +313,7 @@ typedef struct _ToRadio { #define MeshPacket_rx_time_tag 9 #define MeshPacket_rx_snr_tag 7 #define MeshPacket_hop_limit_tag 10 +#define MeshPacket_want_ack_tag 11 #define DeviceState_radio_tag 1 #define DeviceState_my_node_tag 2 #define DeviceState_owner_tag 3 @@ -374,7 +389,8 @@ X(a, STATIC, ONEOF, BYTES, (payload,encrypted,encrypted), 8) \ X(a, STATIC, SINGULAR, UINT32, id, 6) \ X(a, STATIC, SINGULAR, FLOAT, rx_snr, 7) \ X(a, STATIC, SINGULAR, FIXED32, rx_time, 9) \ -X(a, STATIC, SINGULAR, UINT32, hop_limit, 10) +X(a, STATIC, SINGULAR, UINT32, hop_limit, 10) \ +X(a, STATIC, SINGULAR, BOOL, want_ack, 11) #define MeshPacket_CALLBACK NULL #define MeshPacket_DEFAULT NULL #define MeshPacket_payload_decoded_MSGTYPE SubPacket @@ -486,6 +502,14 @@ X(a, STATIC, ONEOF, MESSAGE, (variant,set_owner,variant.set_owner), 102) #define ToRadio_variant_set_radio_MSGTYPE RadioConfig #define ToRadio_variant_set_owner_MSGTYPE User +#define ManufacturingData_FIELDLIST(X, a) \ +X(a, STATIC, SINGULAR, UINT32, fradioFreq, 1) \ +X(a, CALLBACK, SINGULAR, STRING, hw_model, 2) \ +X(a, CALLBACK, SINGULAR, STRING, hw_version, 3) \ +X(a, STATIC, SINGULAR, SINT32, selftest_result, 4) +#define ManufacturingData_CALLBACK pb_default_field_callback +#define ManufacturingData_DEFAULT NULL + extern const pb_msgdesc_t Position_msg; extern const pb_msgdesc_t Data_msg; extern const pb_msgdesc_t User_msg; @@ -501,6 +525,7 @@ extern const pb_msgdesc_t DeviceState_msg; extern const pb_msgdesc_t DebugString_msg; extern const pb_msgdesc_t FromRadio_msg; extern const pb_msgdesc_t ToRadio_msg; +extern const pb_msgdesc_t ManufacturingData_msg; /* Defines for backwards compatibility with code written before nanopb-0.4.0 */ #define Position_fields &Position_msg @@ -518,6 +543,7 @@ extern const pb_msgdesc_t ToRadio_msg; #define DebugString_fields &DebugString_msg #define FromRadio_fields &FromRadio_msg #define ToRadio_fields &ToRadio_msg +#define ManufacturingData_fields &ManufacturingData_msg /* Maximum encoded size of messages (where known) */ #define Position_size 39 @@ -525,16 +551,17 @@ extern const pb_msgdesc_t ToRadio_msg; #define User_size 72 #define RouteDiscovery_size 88 #define SubPacket_size 273 -#define MeshPacket_size 310 +#define MeshPacket_size 312 #define ChannelSettings_size 60 #define RadioConfig_size 136 #define RadioConfig_UserPreferences_size 72 #define NodeInfo_size 132 #define MyNodeInfo_size 85 -#define DeviceState_size 14955 +#define DeviceState_size 15021 #define DebugString_size 258 -#define FromRadio_size 319 -#define ToRadio_size 313 +#define FromRadio_size 321 +#define ToRadio_size 315 +/* ManufacturingData_size depends on runtime parameters */ #ifdef __cplusplus } /* extern "C" */ From 8bf4919576e62df8454e435d3abd644ad8a462e1 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 11:56:17 -0700 Subject: [PATCH 190/197] wip reliable unicast (1 hop) --- src/main.cpp | 4 +- src/mesh/FloodingRouter.h | 3 +- src/mesh/MeshService.cpp | 51 ++------------ src/mesh/MeshService.h | 3 - src/mesh/ReliableRouter.cpp | 99 ++++++++++++++++++++++++++ src/mesh/ReliableRouter.h | 63 +++++++++++++++++ src/mesh/Router.cpp | 137 +++++++++++++++++++++++++----------- src/mesh/Router.h | 24 ++++++- 8 files changed, 289 insertions(+), 95 deletions(-) create mode 100644 src/mesh/ReliableRouter.cpp create mode 100644 src/mesh/ReliableRouter.h diff --git a/src/main.cpp b/src/main.cpp index 2378458a6..3103335b5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,7 +33,7 @@ #include "error.h" #include "power.h" // #include "rom/rtc.h" -#include "FloodingRouter.h" +#include "ReliableRouter.h" #include "main.h" #include "screen.h" #include "sleep.h" @@ -53,7 +53,7 @@ meshtastic::PowerStatus powerStatus; bool ssd1306_found; bool axp192_found; -FloodingRouter realRouter; +ReliableRouter realRouter; Router &router = realRouter; // Users of router don't care what sort of subclass implements that API // ----------------------------------------------------------------------------- diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index e7e1b9610..48a8f0bc7 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -27,10 +27,9 @@ Any entries in recentBroadcasts that are older than X seconds (longer than the max time a flood can take) will be discarded. */ -class FloodingRouter : public Router, private PacketHistory +class FloodingRouter : public Router, protected PacketHistory { private: - public: /** * Constructor diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 986deb3fd..ee2905ad4 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -46,8 +46,6 @@ MeshService service; #include "Router.h" -#define NUM_PACKET_ID 255 // 0 is consider invalid - static uint32_t sendOwnerCb() { service.sendOurOwner(); @@ -57,23 +55,6 @@ static uint32_t sendOwnerCb() static Periodic sendOwnerPeriod(sendOwnerCb); -/// Generate a unique packet id -// FIXME, move this someplace better -PacketId generatePacketId() -{ - static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots - static bool didInit = false; - - if (!didInit) { - didInit = true; - i = random(0, NUM_PACKET_ID + - 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) - } - - i++; - return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 -} - MeshService::MeshService() : toPhoneQueue(MAX_RX_TOPHONE) { // assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro @@ -90,7 +71,7 @@ void MeshService::init() void MeshService::sendOurOwner(NodeNum dest, bool wantReplies) { - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->to = dest; p->decoded.want_response = wantReplies; p->decoded.which_payload = SubPacket_user_tag; @@ -265,33 +246,13 @@ void MeshService::sendToMesh(MeshPacket *p) DEBUG_MSG("Providing time to mesh %u\n", p->decoded.position.time); } - // If the phone sent a packet just to us, don't send it out into the network - if (p->to == nodeDB.getNodeNum()) { - DEBUG_MSG("Dropping locally processed message\n"); + // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it + if (router.send(p) != ERRNO_OK) { + DEBUG_MSG("No radio was able to send packet, discarding...\n"); releaseToPool(p); - } else { - // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - if (router.send(p) != ERRNO_OK) { - DEBUG_MSG("No radio was able to send packet, discarding...\n"); - releaseToPool(p); - } } } -MeshPacket *MeshService::allocForSending() -{ - MeshPacket *p = packetPool.allocZeroed(); - - p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. - p->from = nodeDB.getNodeNum(); - p->to = NODENUM_BROADCAST; - p->hop_limit = HOP_RELIABLE; - p->id = generatePacketId(); - p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp - - return p; -} - void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies) { NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum()); @@ -311,7 +272,7 @@ void MeshService::sendOurPosition(NodeNum dest, bool wantReplies) assert(node->has_position); // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->to = dest; p->decoded.which_payload = SubPacket_position_tag; p->decoded.position = node->position; @@ -325,7 +286,7 @@ int MeshService::onGPSChanged(void *unused) // DEBUG_MSG("got gps notify\n"); // Update our local node info with our position (even if we don't decide to update anyone else) - MeshPacket *p = allocForSending(); + MeshPacket *p = router.allocForSending(); p->decoded.which_payload = SubPacket_position_tag; Position &pos = p->decoded.position; diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index f3328225b..f6e688e19 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -67,9 +67,6 @@ class MeshService /// The owner User record just got updated, update our node DB and broadcast the info into the mesh void reloadOwner() { sendOurOwner(); } - /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. - MeshPacket *allocForSending(); - /// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least /// sends our owner void sendNetworkPing(NodeNum dest, bool wantReplies = false); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp new file mode 100644 index 000000000..6ea884ca2 --- /dev/null +++ b/src/mesh/ReliableRouter.cpp @@ -0,0 +1,99 @@ +#include "ReliableRouter.h" +#include "MeshTypes.h" +#include "configuration.h" +#include "mesh-pb-constants.h" + +// ReliableRouter::ReliableRouter() {} + +/** + * If the message is want_ack, then add it to a list of packets to retransmit. + * If we run out of retransmissions, send a nak packet towards the original client to indicate failure. + */ +ErrorCode ReliableRouter::send(MeshPacket *p) +{ + if (p->want_ack) { + auto copy = packetPool.allocCopy(*p); + startRetransmission(copy); + } + + return FloodingRouter::send(p); +} + +/** + * If we receive a want_ack packet (do not check for wasSeenRecently), send back an ack (this might generate multiple ack sends in + * case the our first ack gets lost) + * + * If we receive an ack packet (do check wasSeenRecently), clear out any retransmissions and + * forward the ack to the application layer. + * + * If we receive a nak packet (do check wasSeenRecently), clear out any retransmissions + * and forward the nak to the application layer. + * + * Otherwise, let superclass handle it. + */ +void ReliableRouter::handleReceived(MeshPacket *p) +{ + if (p->to == getNodeNum()) { // ignore ack/nak/want_ack packets that are not address to us (for now) + if (p->want_ack) { + sendAckNak(true, p->from, p->id); + } + + if (perhapsDecode(p)) { + // If the payload is valid, look for ack/nak + + PacketId ackId = p->decoded.which_ack == SubPacket_success_id_tag ? p->decoded.ack.success_id : 0; + PacketId nakId = p->decoded.which_ack == SubPacket_fail_id_tag ? p->decoded.ack.fail_id : 0; + + // we are careful to only read/update wasSeenRecently _after_ confirming this is an ack (to not mess + // up broadcasts) + if ((ackId || nakId) && !wasSeenRecently(p)) { + if (ackId) { + DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); + stopRetransmission(p->to, ackId); + } else { + DEBUG_MSG("Received a nak=%d, stopping retransmissions\n", nakId); + stopRetransmission(p->to, nakId); + } + } + } + } + + // handle the packet as normal + FloodingRouter::handleReceived(p); +} + +/** + * Send an ack or a nak packet back towards whoever sent idFrom + */ +void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) +{ + DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d", isAck, to, idFrom); + auto p = allocForSending(); + p->hop_limit = 0; // Assume just immediate neighbors for now + p->to = to; + + if (isAck) { + p->decoded.ack.success_id = idFrom; + p->decoded.which_ack = SubPacket_success_id_tag; + } else { + p->decoded.ack.fail_id = idFrom; + p->decoded.which_ack = SubPacket_fail_id_tag; + } + + send(p); +} + +/** + * Stop any retransmissions we are doing of the specified node/packet ID pair + */ +void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) {} + +/** + * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. + */ +void ReliableRouter::startRetransmission(MeshPacket *p) {} + +/** + * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) + */ +void ReliableRouter::doRetransmissions() {} \ No newline at end of file diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h new file mode 100644 index 000000000..75bedfb64 --- /dev/null +++ b/src/mesh/ReliableRouter.h @@ -0,0 +1,63 @@ +#pragma once + +#include "FloodingRouter.h" +#include "PeriodicTask.h" + +/** + * This is a mixin that extends Router with the ability to do (one hop only) reliable message sends. + */ +class ReliableRouter : public FloodingRouter +{ + private: + public: + /** + * Constructor + * + */ + // ReliableRouter(); + + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + + /** Do our retransmission handling */ + virtual void loop() + { + doRetransmissions(); + FloodingRouter::loop(); + } + + protected: + /** + * Called from loop() + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + * + * Note: this method will free the provided packet + */ + virtual void handleReceived(MeshPacket *p); + + private: + /** + * Send an ack or a nak packet back towards whoever sent idFrom + */ + void sendAckNak(bool isAck, NodeNum to, PacketId idFrom); + + /** + * Stop any retransmissions we are doing of the specified node/packet ID pair + */ + void stopRetransmission(NodeNum from, PacketId id); + + /** + * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. + */ + void startRetransmission(MeshPacket *p); + + /** + * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) + */ + void doRetransmissions(); +}; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 21b928f22..a7d570781 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -44,6 +44,39 @@ void Router::loop() } } +#define NUM_PACKET_ID 255 // 0 is consider invalid + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId() +{ + static uint32_t i; // Note: trying to keep this in noinit didn't help for working across reboots + static bool didInit = false; + + if (!didInit) { + didInit = true; + i = random(0, NUM_PACKET_ID + + 1); // pick a random initial sequence number at boot (to prevent repeated reboots always starting at 0) + } + + i++; + return (i % NUM_PACKET_ID) + 1; // return number between 1 and 255 +} + +MeshPacket *Router::allocForSending() +{ + MeshPacket *p = packetPool.allocZeroed(); + + p->which_payload = MeshPacket_decoded_tag; // Assume payload is decoded at start. + p->from = nodeDB.getNodeNum(); + p->to = NODENUM_BROADCAST; + p->hop_limit = HOP_RELIABLE; + p->id = generatePacketId(); + p->rx_time = getValidTime(); // Just in case we process the packet locally - make sure it has a valid timestamp + + return p; +} + /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. @@ -51,33 +84,40 @@ void Router::loop() */ ErrorCode Router::send(MeshPacket *p) { - // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - - assert(p->which_payload == MeshPacket_encrypted_tag || - p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now - - // First convert from protobufs to raw bytes - if (p->which_payload == MeshPacket_decoded_tag) { - static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union - - size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); - - assert(numbytes <= MAX_RHPACKETLEN); - crypto->encrypt(p->from, p->id, numbytes, bytes); - - // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); - p->encrypted.size = numbytes; - p->which_payload = MeshPacket_encrypted_tag; - } - - if (iface) { - // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - return iface->send(p); - } else { - DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // If this packet was destined only to apps on our node, don't send it out into the network + if (p->to == nodeDB.getNodeNum()) { + DEBUG_MSG("Dropping locally processed message\n"); packetPool.release(p); - return ERRNO_NO_INTERFACES; + return ERRNO_OK; + } else { + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // First convert from protobufs to raw bytes + if (p->which_payload == MeshPacket_decoded_tag) { + static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union + + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); + + assert(numbytes <= MAX_RHPACKETLEN); + crypto->encrypt(p->from, p->id, numbytes, bytes); + + // Copy back into the packet and set the variant type + memcpy(p->encrypted.bytes, bytes, numbytes); + p->encrypted.size = numbytes; + p->which_payload = MeshPacket_encrypted_tag; + } + + if (iface) { + // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return iface->send(p); + } else { + DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + packetPool.release(p); + return ERRNO_NO_INTERFACES; + } } } @@ -90,18 +130,12 @@ void Router::sniffReceived(MeshPacket *p) DEBUG_MSG("Sniffing packet not sent to us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); } -/** - * Handle any packet that is received by an interface on this node. - * Note: some packets may merely being passed through this node and will be forwarded elsewhere. - */ -void Router::handleReceived(MeshPacket *p) +bool Router::perhapsDecode(MeshPacket *p) { - // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function - // Also, we should set the time from the ISR and it should have msec level resolution - p->rx_time = getValidTime(); // store the arrival timestamp for the phone + if (p->which_payload == MeshPacket_decoded_tag) + return true; // If packet was already decoded just return - assert(p->which_payload == - MeshPacket_encrypted_tag); // I _think_ the only thing that pushes to us is raw devices that just received packets + assert(p->which_payload == MeshPacket_encrypted_tag); // FIXME - someday don't send routing packets encrypted. That would allow us to route for other channels without // being able to decrypt their data. @@ -113,14 +147,37 @@ void Router::handleReceived(MeshPacket *p) // Take those raw bytes and convert them back into a well structured protobuf we can understand if (!pb_decode_from_bytes(bytes, p->encrypted.size, SubPacket_fields, &p->decoded)) { - DEBUG_MSG("Invalid protobufs in received mesh packet, discarding.\n"); + DEBUG_MSG("Invalid protobufs in received mesh packet!\n"); + return false; } else { - // parsing was successful, queue for our recipient + // parsing was successful p->which_payload = MeshPacket_decoded_tag; + return true; + } +} + +NodeNum Router::getNodeNum() +{ + return nodeDB.getNodeNum(); +} + +/** + * Handle any packet that is received by an interface on this node. + * Note: some packets may merely being passed through this node and will be forwarded elsewhere. + */ +void Router::handleReceived(MeshPacket *p) +{ + // FIXME, this class shouldn't EVER need to know about the GPS, move getValidTime() into a non gps dependent function + // Also, we should set the time from the ISR and it should have msec level resolution + p->rx_time = getValidTime(); // store the arrival timestamp for the phone + + // Take those raw bytes and convert them back into a well structured protobuf we can understand + if (perhapsDecode(p)) { + // parsing was successful, queue for our recipient sniffReceived(p); - uint8_t ourAddr = nodeDB.getNodeNum(); - if (p->to == NODENUM_BROADCAST || p->to == ourAddr) { + + if (p->to == NODENUM_BROADCAST || p->to == getNodeNum()) { DEBUG_MSG("Notifying observers of received packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); notifyPacketReceived.notifyObservers(p); } diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 03d75d33d..d0a8e029c 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -44,7 +44,7 @@ class Router * do idle processing * Mostly looking in our incoming rxPacket queue and calling handleReceived. */ - void loop(); + virtual void loop(); /** * Send a packet on a suitable interface. This routine will @@ -53,6 +53,13 @@ class Router */ virtual ErrorCode send(MeshPacket *p); + /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. + MeshPacket *allocForSending(); + + /** + * @return our local nodenum */ + NodeNum getNodeNum(); + protected: /** * Called from loop() @@ -65,10 +72,21 @@ class Router virtual void handleReceived(MeshPacket *p); /** - * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to * update routing tables etc... based on what we overhear (even for messages not destined to our node) */ virtual void sniffReceived(MeshPacket *p); + + /** + * Remove any encryption and decode the protobufs inside this packet (if necessary). + * + * @return true for success, false for corrupt packet. + */ + bool perhapsDecode(MeshPacket *p); }; -extern Router &router; \ No newline at end of file +extern Router &router; + +/// Generate a unique packet id +// FIXME, move this someplace better +PacketId generatePacketId(); \ No newline at end of file From 6ba960ce47229a1d34235cd718a0c66d6073f7f6 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 14:54:47 -0700 Subject: [PATCH 191/197] one hop reliable ready for testing --- docs/software/mesh-alg.md | 21 ++++++++----- src/mesh/ReliableRouter.cpp | 61 +++++++++++++++++++++++++++++++++++-- src/mesh/ReliableRouter.h | 51 +++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 10 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 4bf2afa30..fe5d9752e 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -8,12 +8,13 @@ reliable messaging tasks (stage one for DSR): - DONE add a max hops parameter, use it for broadcast as well (0 means adjacent only, 1 is one forward etc...). Store as three bits in the header. - DONE add a 'snoopReceived' hook for all messages that pass through our node. - DONE use the same 'recentmessages' array used for broadcast msgs to detect duplicate retransmitted messages. -- in the router receive path?, send an ack packet if want_ack was set and we are the final destination. FIXME, for now don't handle multihop or merging of data replies with these acks. -- keep a list of packets waiting for acks -- for each message keep a count of # retries (max of three). Local to the node, only for the most immediate hop, ignorant of multihop routing. -- delay some random time for each retry (large enough to allow for acks to come in) -- once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender -- after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) +- DONE in the router receive path?, send an ack packet if want_ack was set and we are the final destination. FIXME, for now don't handle multihop or merging of data replies with these acks. +- DONE keep a list of packets waiting for acks +- DONE for each message keep a count of # retries (max of three). Local to the node, only for the most immediate hop, ignorant of multihop routing. +- DONE delay some random time for each retry (large enough to allow for acks to come in) +- DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender +- DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) +- test one hop ack/nak with the python framework dsr tasks @@ -21,9 +22,15 @@ dsr tasks - when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. - otherwise, use next_hop and start sending a message (with ack request) towards that node. - Don't use broadcasts for the network pings (close open github issue) +- add ignoreSenders to myNodeInfo to allow testing different mesh topologies by refusing to see certain senders +- test multihop delivery with the python framework -optimizations: +optimizations / low priority: +- low priority: think more careful about reliable retransmit intervals +- make ReliableRouter.pending threadsafe +- bump up PacketPool size for all the new ack/nak/routing packets +- handle 51 day rollover in doRetransmissions - use a priority queue for the messages waiting to send. Send acks first, then routing messages, then data messages, then broadcasts? when we receive any packet diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 6ea884ca2..02833f8ce 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -83,17 +83,72 @@ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) send(p); } +#define NUM_RETRANSMISSIONS 3 + +PendingPacket::PendingPacket(MeshPacket *p) +{ + packet = p; + numRetransmissions = NUM_RETRANSMISSIONS - 1; // We subtract one, because we assume the user just did the first send + setNextTx(); +} + /** * Stop any retransmissions we are doing of the specified node/packet ID pair */ -void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) {} +void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) +{ + auto key = GlobalPacketId(from, id); + stopRetransmission(key); +} +void ReliableRouter::stopRetransmission(GlobalPacketId key) +{ + auto old = pending.find(key); // If we have an old record, someone messed up because id got reused + if (old != pending.end()) { + auto numErased = pending.erase(key); + assert(numErased == 1); + packetPool.release(old->second.packet); + } +} /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. */ -void ReliableRouter::startRetransmission(MeshPacket *p) {} +void ReliableRouter::startRetransmission(MeshPacket *p) +{ + auto id = GlobalPacketId(p); + auto rec = PendingPacket(p); + + stopRetransmission(p->from, p->id); + pending[id] = rec; +} /** * Do any retransmissions that are scheduled (FIXME - for the time being called from loop) */ -void ReliableRouter::doRetransmissions() {} \ No newline at end of file +void ReliableRouter::doRetransmissions() +{ + uint32_t now = millis(); + + // FIXME, we should use a better datastructure rather than walking through this map. + // for(auto el: pending) { + for (auto it = pending.begin(), nextIt = it; it != pending.end(); it = nextIt) { + ++nextIt; // we use this odd pattern because we might be deleting it... + auto &p = it->second; + + // FIXME, handle 51 day rolloever here!!! + if (p.nextTxMsec <= now) { + if (p.numRetransmissions == 0) { + DEBUG_MSG("Reliable send failed, returning a nak\n"); + sendAckNak(false, p.packet->from, p.packet->id); + stopRetransmission(it->first); + } else { + DEBUG_MSG("Sending reliable retransmission\n"); + send(packetPool.allocCopy(*p.packet)); + + // Queue again + --p.numRetransmissions; + p.setNextTx(); + } + } + } +} \ No newline at end of file diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 75bedfb64..3798d9d68 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -2,6 +2,54 @@ #include "FloodingRouter.h" #include "PeriodicTask.h" +#include + +/** + * An identifier for a globalally unique message - a pair of the sending nodenum and the packet id assigned + * to that message + */ +struct GlobalPacketId { + NodeNum node; + PacketId id; + + bool operator==(const GlobalPacketId &p) const { return node == p.node && id == p.id; } + + GlobalPacketId(const MeshPacket *p) + { + node = p->from; + id = p->id; + } + + GlobalPacketId(NodeNum _from, PacketId _id) + { + node = _from; + id = _id; + } +}; + +/** + * A packet queued for retransmission + */ +struct PendingPacket { + MeshPacket *packet; + + /** The next time we should try to retransmit this packet */ + uint32_t nextTxMsec; + + /** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */ + uint8_t numRetransmissions; + + PendingPacket() {} + PendingPacket(MeshPacket *p); + + void setNextTx() { nextTxMsec = millis() + random(10 * 1000, 12 * 1000); } +}; + +class GlobalPacketIdHashFunction +{ + public: + size_t operator()(const GlobalPacketId &p) const { return (hash()(p.node)) ^ (hash()(p.id)); } +}; /** * This is a mixin that extends Router with the ability to do (one hop only) reliable message sends. @@ -9,6 +57,8 @@ class ReliableRouter : public FloodingRouter { private: + unordered_map pending; + public: /** * Constructor @@ -50,6 +100,7 @@ class ReliableRouter : public FloodingRouter * Stop any retransmissions we are doing of the specified node/packet ID pair */ void stopRetransmission(NodeNum from, PacketId id); + void stopRetransmission(GlobalPacketId p); /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. From c65b518432a091367f93ced26e63a630cc3b7e0c Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 14:54:58 -0700 Subject: [PATCH 192/197] less logspam --- src/gps/UBloxGPS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gps/UBloxGPS.cpp b/src/gps/UBloxGPS.cpp index 560c52fa8..ca8f955c5 100644 --- a/src/gps/UBloxGPS.cpp +++ b/src/gps/UBloxGPS.cpp @@ -87,7 +87,7 @@ void UBloxGPS::doTask() // Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions // turn off for now // fixtype = ublox.getFixType(); - DEBUG_MSG("fix type %d\n", fixtype); + // DEBUG_MSG("fix type %d\n", fixtype); // DEBUG_MSG("sec %d\n", ublox.getSecond()); // DEBUG_MSG("lat %d\n", ublox.getLatitude()); From 71041e86742baf93b363620e15334db0c3ff897b Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 19 May 2020 15:51:07 -0700 Subject: [PATCH 193/197] reliable unicast 1 hop works! --- docs/software/mesh-alg.md | 5 +++-- src/mesh/PacketHistory.cpp | 19 +++++++++++-------- src/mesh/PacketHistory.h | 4 +++- src/mesh/RadioLibInterface.cpp | 2 +- src/mesh/ReliableRouter.cpp | 2 +- 5 files changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index fe5d9752e..78f5eba86 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -14,7 +14,8 @@ reliable messaging tasks (stage one for DSR): - DONE delay some random time for each retry (large enough to allow for acks to come in) - DONE once an ack comes in, remove the packet from the retry list and deliver the ack to the original sender - DONE after three retries, deliver a no-ack packet to the original sender (i.e. the phone app or mesh router service) -- test one hop ack/nak with the python framework +- DONE test one hop ack/nak with the python framework +- Do stress test with acks dsr tasks @@ -22,7 +23,7 @@ dsr tasks - when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. - otherwise, use next_hop and start sending a message (with ack request) towards that node. - Don't use broadcasts for the network pings (close open github issue) -- add ignoreSenders to myNodeInfo to allow testing different mesh topologies by refusing to see certain senders +- add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders - test multihop delivery with the python framework optimizations / low priority: diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 9a5704f66..30a448f97 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -13,7 +13,7 @@ PacketHistory::PacketHistory() /** * Update recentBroadcasts and return true if we have already seen this packet */ -bool PacketHistory::wasSeenRecently(const MeshPacket *p) +bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) { if (p->id == 0) { DEBUG_MSG("Ignoring message with zero id\n"); @@ -32,7 +32,8 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p) DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); // Update the time on this record to now - r.rxTimeMsec = now; + if (withUpdate) + r.rxTimeMsec = now; return true; } @@ -41,12 +42,14 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p) } // Didn't find an existing record, make one - PacketRecord r; - r.id = p->id; - r.sender = p->from; - r.rxTimeMsec = now; - recentPackets.push_back(r); - DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + if (withUpdate) { + PacketRecord r; + r.id = p->id; + r.sender = p->from; + r.rxTimeMsec = now; + recentPackets.push_back(r); + DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + } return false; } \ No newline at end of file diff --git a/src/mesh/PacketHistory.h b/src/mesh/PacketHistory.h index 22470f4fc..38edf7d77 100644 --- a/src/mesh/PacketHistory.h +++ b/src/mesh/PacketHistory.h @@ -61,6 +61,8 @@ class PacketHistory /** * Update recentBroadcasts and return true if we have already seen this packet + * + * @param withUpdate if true and not found we add an entry to recentPackets */ - bool wasSeenRecently(const MeshPacket *p); + bool wasSeenRecently(const MeshPacket *p, bool withUpdate = true); }; diff --git a/src/mesh/RadioLibInterface.cpp b/src/mesh/RadioLibInterface.cpp index 5bf3b5eea..9d2710716 100644 --- a/src/mesh/RadioLibInterface.cpp +++ b/src/mesh/RadioLibInterface.cpp @@ -315,7 +315,7 @@ void RadioLibInterface::handleReceiveInterrupt() /** start an immediate transmit */ void RadioLibInterface::startSend(MeshPacket *txp) { - DEBUG_MSG("Starting low level send from=0x%x, id=%u!\n", txp->from, txp->id); + DEBUG_MSG("Starting low level send from=0x%x, id=%u, want_ack=%d\n", txp->from, txp->id, txp->want_ack); setStandby(); // Cancel any already in process receives size_t numbytes = beginSending(txp); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 02833f8ce..eb6fc248a 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -46,7 +46,7 @@ void ReliableRouter::handleReceived(MeshPacket *p) // we are careful to only read/update wasSeenRecently _after_ confirming this is an ack (to not mess // up broadcasts) - if ((ackId || nakId) && !wasSeenRecently(p)) { + if ((ackId || nakId) && !wasSeenRecently(p, false)) { if (ackId) { DEBUG_MSG("Received a ack=%d, stopping retransmissions\n", ackId); stopRetransmission(p->to, ackId); From 0271df0657ca28195644fd1306130c33aa13c41a Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 12:47:08 -0700 Subject: [PATCH 194/197] add beginnings of full DSR routing --- src/mesh/DSRRouter.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++ src/mesh/DSRRouter.h | 39 ++++++++++++++++++++ src/mesh/Router.cpp | 9 +++-- src/mesh/Router.h | 2 +- 4 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 src/mesh/DSRRouter.cpp create mode 100644 src/mesh/DSRRouter.h diff --git a/src/mesh/DSRRouter.cpp b/src/mesh/DSRRouter.cpp new file mode 100644 index 000000000..00c2a80d3 --- /dev/null +++ b/src/mesh/DSRRouter.cpp @@ -0,0 +1,80 @@ +#include "DSRRouter.h" +#include "configuration.h" + +/* when we receive any packet + +- sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info. +- if we need to route() that packet, resend it to the next_hop based on our nodedb. +- if it is broadcast or destined for our node, deliver locally +- handle routereply/routeerror/routediscovery messages as described below +- then free it + +routeDiscovery + +- if we've already passed through us (or is from us), then it ignore it +- use the nodes already mentioned in the request to update our routing table +- if they were looking for us, send back a routereply +- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) +- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) +- if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route +reply towards the requester + +when sending any reliable packet + +- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that +packet and update their route caches. + +when we receive a routereply packet + +- update next_hop on the node, if the new reply needs fewer hops than the existing one (we prefer shorter paths). fixme, someday +use a better heuristic + +when we receive a routeError packet + +- delete the route for that failed recipient, restartRouteDiscovery() +- if we receive routeerror in response to a discovery, +- fixme, eventually keep caches of possible other routes. +*/ + +void DSRRouter::sniffReceived(const MeshPacket *p) +{ + + // FIXME, update nodedb + + // Handle route discovery packets (will be a broadcast message) + if (p->decoded.which_payload == SubPacket_request_tag) { + // FIXME - always start request with the senders nodenum + + if (weAreInRoute(p->decoded.request)) { + DEBUG_MSG("Ignoring a route request that contains us\n"); + } else { + updateRoutes(p->decoded.request, false); // Update our routing tables based on the route that came in so far on this request + + if (p->decoded.dest == getNodeNum()) { + // They were looking for us, send back a route reply (the sender address will be first in the list) + sendRouteReply(p->decoded.request); + } else { + // They were looking for someone else, forward it along (as a zero hop broadcast) + NodeNum nextHop = getNextHop(p->decoded.dest); + if (nextHop) { + // in our route cache, reply to the requester (the sender address will be first in the list) + sendRouteReply(p->decoded.request, nextHop); + } else { + // Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + resendRouteRequest(p); + } + } + } + } + + // Handle regular packets + if (p->to == getNodeNum()) { // Destined for us (at least for this hop) + + // We need to route this packet + if (p->decoded.dest != p->to) { + // FIXME + } + } + + return ReliableRouter::sniffReceived(p); +} \ No newline at end of file diff --git a/src/mesh/DSRRouter.h b/src/mesh/DSRRouter.h new file mode 100644 index 000000000..5ecbdc8e5 --- /dev/null +++ b/src/mesh/DSRRouter.h @@ -0,0 +1,39 @@ +#include "ReliableRouter.h" + +class DSRRouter : public ReliableRouter +{ + + protected: + /** + * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to + * update routing tables etc... based on what we overhear (even for messages not destined to our node) + */ + virtual void sniffReceived(const MeshPacket *p); + + private: + /** + * Does our node appear in the specified route + */ + bool weAreInRoute(const RouteDiscovery &route); + + /** + * Given a DSR route, use that route to update our DB of possible routes + **/ + void updateRoutes(const RouteDiscovery &route, bool reverse); + + /** + * send back a route reply (the sender address will be first in the list) + */ + void sendRouteReply(const RouteDiscovery &route, NodeNum toAppend = 0); + + /** + * Given a nodenum return the next node we should forward to if we want to reach that node. + * + * @return 0 if no route found + */ + NodeNum getNextHop(NodeNum dest); + + /** Not in our route cache, rebroadcast on their behalf (after adding ourselves to the request route) + */ + void resendRouteRequest(const MeshPacket *p); +}; \ No newline at end of file diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index a7d570781..428f19fee 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -90,6 +90,10 @@ ErrorCode Router::send(MeshPacket *p) packetPool.release(p); return ERRNO_OK; } else { + // Never set the want_ack flag on broadcast packets sent over the air. + if (p->to == NODENUM_BROADCAST) + p->want_ack = false; + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) assert(p->which_payload == MeshPacket_encrypted_tag || @@ -125,9 +129,10 @@ ErrorCode Router::send(MeshPacket *p) * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to * update routing tables etc... based on what we overhear (even for messages not destined to our node) */ -void Router::sniffReceived(MeshPacket *p) +void Router::sniffReceived(const MeshPacket *p) { - DEBUG_MSG("Sniffing packet not sent to us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + } bool Router::perhapsDecode(MeshPacket *p) diff --git a/src/mesh/Router.h b/src/mesh/Router.h index d0a8e029c..0f06ce3e9 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -75,7 +75,7 @@ class Router * Every (non duplicate) packet this node receives will be passed through this method. This allows subclasses to * update routing tables etc... based on what we overhear (even for messages not destined to our node) */ - virtual void sniffReceived(MeshPacket *p); + virtual void sniffReceived(const MeshPacket *p); /** * Remove any encryption and decode the protobufs inside this packet (if necessary). From e2cbccb1336dee6c850ef73d8938e58b4dea96ac Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 12:47:41 -0700 Subject: [PATCH 195/197] add want_ack support for broadcast packets --- docs/software/mesh-alg.md | 19 +++++++++++++------ proto | 2 +- src/mesh/FloodingRouter.cpp | 2 +- src/mesh/ReliableRouter.cpp | 20 ++++++++++++++++---- src/mesh/ReliableRouter.h | 12 ++++++++++-- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/docs/software/mesh-alg.md b/docs/software/mesh-alg.md index 78f5eba86..d4ae71215 100644 --- a/docs/software/mesh-alg.md +++ b/docs/software/mesh-alg.md @@ -2,6 +2,10 @@ great source of papers and class notes: http://www.cs.jhu.edu/~cs647/ +flood routing improvements + +- DONE if we don't see anyone rebroadcast our want_ack=true broadcasts, retry as needed. + reliable messaging tasks (stage one for DSR): - DONE generalize naive flooding @@ -19,9 +23,6 @@ reliable messaging tasks (stage one for DSR): dsr tasks -- do "hop by hop" routing -- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. -- otherwise, use next_hop and start sending a message (with ack request) towards that node. - Don't use broadcasts for the network pings (close open github issue) - add ignoreSenders to radioconfig to allow testing different mesh topologies by refusing to see certain senders - test multihop delivery with the python framework @@ -34,6 +35,12 @@ optimizations / low priority: - handle 51 day rollover in doRetransmissions - use a priority queue for the messages waiting to send. Send acks first, then routing messages, then data messages, then broadcasts? +when we send a packet + +- do "hop by hop" routing +- when sending, if destnodeinfo.next_hop is zero (and no message is already waiting for an arp for that node), startRouteDiscovery() for that node. Queue the message in the 'waiting for arp queue' so we can send it later when then the arp completes. +- otherwise, use next_hop and start sending a message (with ack request) towards that node (starting with next_hop). + when we receive any packet - sniff and update tables (especially useful to find adjacent nodes). Update user, network and position info. @@ -47,13 +54,13 @@ routeDiscovery - if we've already passed through us (or is from us), then it ignore it - use the nodes already mentioned in the request to update our routing table - if they were looking for us, send back a routereply -- if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) -- if we receive a discovery packet, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) +- NOT DOING FOR NOW -if max_hops is zero and they weren't looking for us, drop (FIXME, send back error - I think not though?) +- if we receive a discovery packet, and we don't have next_hop set in our nodedb, we use it to populate next_hop (if needed) towards the requester (after decrementing max_hops) - if we receive a discovery packet, and we have a next_hop in our nodedb for that destination we send a (reliable) we send a route reply towards the requester when sending any reliable packet -- if we get back a nak, send a routeError message back towards the original requester. all nodes eavesdrop on that packet and update their route caches +- if timeout doing retries, send a routeError (nak) message back towards the original requester. all nodes eavesdrop on that packet and update their route caches. when we receive a routereply packet diff --git a/proto b/proto index e095ea92e..bfae47bdc 160000 --- a/proto +++ b/proto @@ -1 +1 @@ -Subproject commit e095ea92e62edc3f5dd6864c3d08d113fd8842e2 +Subproject commit bfae47bdc0da23bb1e53fed054d3de2d161389bc diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index f16405e46..d3cc5cbde 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -11,7 +11,7 @@ FloodingRouter::FloodingRouter() {} */ ErrorCode FloodingRouter::send(MeshPacket *p) { - // Add any messages _we_ send to the seen message list + // Add any messages _we_ send to the seen message list (so we will ignore all retransmissions we see) wasSeenRecently(p); // FIXME, move this to a sniffSent method return Router::send(p); diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index eb6fc248a..e14355ece 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -33,7 +33,17 @@ ErrorCode ReliableRouter::send(MeshPacket *p) */ void ReliableRouter::handleReceived(MeshPacket *p) { - if (p->to == getNodeNum()) { // ignore ack/nak/want_ack packets that are not address to us (for now) + NodeNum ourNode = getNodeNum(); + + if (p->from == ourNode && p->to == NODENUM_BROADCAST) { + // We are seeing someone rebroadcast one of our broadcast attempts. + // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for + // the original sending process. + if (stopRetransmission(p->from, p->id)) { + DEBUG_MSG("Someone is retransmitting for us, generate implicit ack"); + sendAckNak(true, p->from, p->id); + } + } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (for now) if (p->want_ack) { sendAckNak(true, p->from, p->id); } @@ -95,20 +105,22 @@ PendingPacket::PendingPacket(MeshPacket *p) /** * Stop any retransmissions we are doing of the specified node/packet ID pair */ -void ReliableRouter::stopRetransmission(NodeNum from, PacketId id) +bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id) { auto key = GlobalPacketId(from, id); stopRetransmission(key); } -void ReliableRouter::stopRetransmission(GlobalPacketId key) +bool ReliableRouter::stopRetransmission(GlobalPacketId key) { auto old = pending.find(key); // If we have an old record, someone messed up because id got reused if (old != pending.end()) { auto numErased = pending.erase(key); assert(numErased == 1); packetPool.release(old->second.packet); - } + return true; + } else + return false; } /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 3798d9d68..2217c92c0 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -39,6 +39,12 @@ struct PendingPacket { /** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */ uint8_t numRetransmissions; + /** True if we have started trying to find a route - for DSR usage + * While trying to find a route we don't actually send the data packet. We just leave it here pending until + * we have a route or we've failed to find one. + */ + bool wantRoute = false; + PendingPacket() {} PendingPacket(MeshPacket *p); @@ -98,9 +104,11 @@ class ReliableRouter : public FloodingRouter /** * Stop any retransmissions we are doing of the specified node/packet ID pair + * + * @return true if we found and removed a transmission with this ID */ - void stopRetransmission(NodeNum from, PacketId id); - void stopRetransmission(GlobalPacketId p); + bool stopRetransmission(NodeNum from, PacketId id); + bool stopRetransmission(GlobalPacketId p); /** * Add p to the list of packets to retransmit occasionally. We will free it once we stop retransmitting. From e75561016b5f94850abbae90b0498aeac061c5d8 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 15:55:57 -0700 Subject: [PATCH 196/197] retransmissions work again --- src/mesh/PacketHistory.cpp | 4 ++-- src/mesh/ReliableRouter.cpp | 17 ++++++++++++----- src/mesh/ReliableRouter.h | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/mesh/PacketHistory.cpp b/src/mesh/PacketHistory.cpp index 30a448f97..7361daad8 100644 --- a/src/mesh/PacketHistory.cpp +++ b/src/mesh/PacketHistory.cpp @@ -29,7 +29,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) recentPackets.erase(recentPackets.begin() + i); // delete old record } else { if (r.id == p->id && r.sender == p->from) { - DEBUG_MSG("Found existing broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + DEBUG_MSG("Found existing packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); // Update the time on this record to now if (withUpdate) @@ -48,7 +48,7 @@ bool PacketHistory::wasSeenRecently(const MeshPacket *p, bool withUpdate) r.sender = p->from; r.rxTimeMsec = now; recentPackets.push_back(r); - DEBUG_MSG("Adding broadcast record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + DEBUG_MSG("Adding packet record for fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); } return false; diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index e14355ece..78c6b93a0 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -36,6 +36,8 @@ void ReliableRouter::handleReceived(MeshPacket *p) NodeNum ourNode = getNodeNum(); if (p->from == ourNode && p->to == NODENUM_BROADCAST) { + DEBUG_MSG("Received someone rebroadcasting for us fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + // We are seeing someone rebroadcast one of our broadcast attempts. // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for // the original sending process. @@ -77,7 +79,7 @@ void ReliableRouter::handleReceived(MeshPacket *p) */ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) { - DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d", isAck, to, idFrom); + DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d\n", isAck, to, idFrom); auto p = allocForSending(); p->hop_limit = 0; // Assume just immediate neighbors for now p->to = to; @@ -108,7 +110,7 @@ PendingPacket::PendingPacket(MeshPacket *p) bool ReliableRouter::stopRetransmission(NodeNum from, PacketId id) { auto key = GlobalPacketId(from, id); - stopRetransmission(key); + return stopRetransmission(key); } bool ReliableRouter::stopRetransmission(GlobalPacketId key) @@ -150,12 +152,17 @@ void ReliableRouter::doRetransmissions() // FIXME, handle 51 day rolloever here!!! if (p.nextTxMsec <= now) { if (p.numRetransmissions == 0) { - DEBUG_MSG("Reliable send failed, returning a nak\n"); + DEBUG_MSG("Reliable send failed, returning a nak fr=0x%x,to=0x%x,id=%d\n", p.packet->from, p.packet->to, + p.packet->id); sendAckNak(false, p.packet->from, p.packet->id); stopRetransmission(it->first); } else { - DEBUG_MSG("Sending reliable retransmission\n"); - send(packetPool.allocCopy(*p.packet)); + DEBUG_MSG("Sending reliable retransmission fr=0x%x,to=0x%x,id=%d, tries left=%d\n", p.packet->from, p.packet->to, + p.packet->id, p.numRetransmissions); + + // Note: we call the superclass version because we don't want to have our version of send() add a new + // retransmission record + FloodingRouter::send(packetPool.allocCopy(*p.packet)); // Queue again --p.numRetransmissions; diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 2217c92c0..e63806af5 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -48,7 +48,7 @@ struct PendingPacket { PendingPacket() {} PendingPacket(MeshPacket *p); - void setNextTx() { nextTxMsec = millis() + random(10 * 1000, 12 * 1000); } + void setNextTx() { nextTxMsec = millis() + random(30 * 1000, 22 * 1000); } }; class GlobalPacketIdHashFunction From 9dd88281afb4bd7da721ee72b03fe5ba4fdf7d32 Mon Sep 17 00:00:00 2001 From: geeksville Date: Thu, 21 May 2020 16:34:16 -0700 Subject: [PATCH 197/197] reliable broadcast now works --- src/mesh/MeshService.cpp | 2 +- src/mesh/ReliableRouter.cpp | 11 +++-- src/mesh/ReliableRouter.h | 2 +- src/mesh/Router.cpp | 80 +++++++++++++++++++------------------ src/mesh/Router.h | 13 ++++-- 5 files changed, 61 insertions(+), 47 deletions(-) diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index ee2905ad4..540ca7cb1 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -247,7 +247,7 @@ void MeshService::sendToMesh(MeshPacket *p) } // Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it - if (router.send(p) != ERRNO_OK) { + if (router.sendLocal(p) != ERRNO_OK) { DEBUG_MSG("No radio was able to send packet, discarding...\n"); releaseToPool(p); } diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 78c6b93a0..0500f2799 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -12,6 +12,11 @@ ErrorCode ReliableRouter::send(MeshPacket *p) { if (p->want_ack) { + // If someone asks for acks on broadcast, we need the hop limit to be at least one, so that first node that receives our + // message will rebroadcast + if (p->to == NODENUM_BROADCAST && p->hop_limit == 0) + p->hop_limit = 1; + auto copy = packetPool.allocCopy(*p); startRetransmission(copy); } @@ -42,7 +47,7 @@ void ReliableRouter::handleReceived(MeshPacket *p) // If this is the first time we saw this, cancel any retransmissions we have queued up and generate an internal ack for // the original sending process. if (stopRetransmission(p->from, p->id)) { - DEBUG_MSG("Someone is retransmitting for us, generate implicit ack"); + DEBUG_MSG("Someone is retransmitting for us, generate implicit ack\n"); sendAckNak(true, p->from, p->id); } } else if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (for now) @@ -79,10 +84,10 @@ void ReliableRouter::handleReceived(MeshPacket *p) */ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) { - DEBUG_MSG("Sending an ack=%d,to=%d,idFrom=%d\n", isAck, to, idFrom); auto p = allocForSending(); p->hop_limit = 0; // Assume just immediate neighbors for now p->to = to; + DEBUG_MSG("Sending an ack=0x%x,to=0x%x,idFrom=%d,id=%d\n", isAck, to, idFrom, p->id); if (isAck) { p->decoded.ack.success_id = idFrom; @@ -92,7 +97,7 @@ void ReliableRouter::sendAckNak(bool isAck, NodeNum to, PacketId idFrom) p->decoded.which_ack = SubPacket_fail_id_tag; } - send(p); + sendLocal(p); // we sometimes send directly to the local node } #define NUM_RETRANSMISSIONS 3 diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index e63806af5..7030793ae 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -48,7 +48,7 @@ struct PendingPacket { PendingPacket() {} PendingPacket(MeshPacket *p); - void setNextTx() { nextTxMsec = millis() + random(30 * 1000, 22 * 1000); } + void setNextTx() { nextTxMsec = millis() + random(20 * 1000, 22 * 1000); } }; class GlobalPacketIdHashFunction diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 428f19fee..0ef7b8333 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -77,6 +77,16 @@ MeshPacket *Router::allocForSending() return p; } +ErrorCode Router::sendLocal(MeshPacket *p) +{ + if (p->to == nodeDB.getNodeNum()) { + DEBUG_MSG("Enqueuing internal message for the receive queue\n"); + fromRadioQueue.enqueue(p); + return ERRNO_OK; + } else + return send(p); +} + /** * Send a packet on a suitable interface. This routine will * later free() the packet to pool. This routine is not allowed to stall. @@ -84,44 +94,39 @@ MeshPacket *Router::allocForSending() */ ErrorCode Router::send(MeshPacket *p) { - // If this packet was destined only to apps on our node, don't send it out into the network - if (p->to == nodeDB.getNodeNum()) { - DEBUG_MSG("Dropping locally processed message\n"); - packetPool.release(p); - return ERRNO_OK; + assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal + + // Never set the want_ack flag on broadcast packets sent over the air. + if (p->to == NODENUM_BROADCAST) + p->want_ack = false; + + // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) + + assert(p->which_payload == MeshPacket_encrypted_tag || + p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now + + // First convert from protobufs to raw bytes + if (p->which_payload == MeshPacket_decoded_tag) { + static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union + + size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); + + assert(numbytes <= MAX_RHPACKETLEN); + crypto->encrypt(p->from, p->id, numbytes, bytes); + + // Copy back into the packet and set the variant type + memcpy(p->encrypted.bytes, bytes, numbytes); + p->encrypted.size = numbytes; + p->which_payload = MeshPacket_encrypted_tag; + } + + if (iface) { + // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + return iface->send(p); } else { - // Never set the want_ack flag on broadcast packets sent over the air. - if (p->to == NODENUM_BROADCAST) - p->want_ack = false; - - // If the packet hasn't yet been encrypted, do so now (it might already be encrypted if we are just forwarding it) - - assert(p->which_payload == MeshPacket_encrypted_tag || - p->which_payload == MeshPacket_decoded_tag); // I _think_ all packets should have a payload by now - - // First convert from protobufs to raw bytes - if (p->which_payload == MeshPacket_decoded_tag) { - static uint8_t bytes[MAX_RHPACKETLEN]; // we have to use a scratch buffer because a union - - size_t numbytes = pb_encode_to_bytes(bytes, sizeof(bytes), SubPacket_fields, &p->decoded); - - assert(numbytes <= MAX_RHPACKETLEN); - crypto->encrypt(p->from, p->id, numbytes, bytes); - - // Copy back into the packet and set the variant type - memcpy(p->encrypted.bytes, bytes, numbytes); - p->encrypted.size = numbytes; - p->which_payload = MeshPacket_encrypted_tag; - } - - if (iface) { - // DEBUG_MSG("Sending packet via interface fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - return iface->send(p); - } else { - DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - packetPool.release(p); - return ERRNO_NO_INTERFACES; - } + DEBUG_MSG("Dropping packet - no interfaces - fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); + packetPool.release(p); + return ERRNO_NO_INTERFACES; } } @@ -132,7 +137,6 @@ ErrorCode Router::send(MeshPacket *p) void Router::sniffReceived(const MeshPacket *p) { DEBUG_MSG("FIXME-update-db Sniffing packet fr=0x%x,to=0x%x,id=%d\n", p->from, p->to, p->id); - } bool Router::perhapsDecode(MeshPacket *p) diff --git a/src/mesh/Router.h b/src/mesh/Router.h index 0f06ce3e9..8c811667e 100644 --- a/src/mesh/Router.h +++ b/src/mesh/Router.h @@ -47,11 +47,9 @@ class Router virtual void loop(); /** - * Send a packet on a suitable interface. This routine will - * later free() the packet to pool. This routine is not allowed to stall. - * If the txmit queue is full it might return an error + * Works like send, but if we are sending to the local node, we directly put the message in the receive queue */ - virtual ErrorCode send(MeshPacket *p); + ErrorCode sendLocal(MeshPacket *p); /// Allocate and return a meshpacket which defaults as send to broadcast from the current node. MeshPacket *allocForSending(); @@ -61,6 +59,13 @@ class Router NodeNum getNodeNum(); protected: + /** + * Send a packet on a suitable interface. This routine will + * later free() the packet to pool. This routine is not allowed to stall. + * If the txmit queue is full it might return an error + */ + virtual ErrorCode send(MeshPacket *p); + /** * Called from loop() * Handle any packet that is received by an interface on this node.