diff --git a/platformio.ini b/platformio.ini index 90e551aab..fb180d397 100644 --- a/platformio.ini +++ b/platformio.ini @@ -98,4 +98,11 @@ build_flags = [env:ttgo-lora32-v2] board = ttgo-lora32-v1 build_flags = - ${env.build_flags} -D TTGO_LORA_V2 \ No newline at end of file + ${env.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 +build_flags = + ${env.build_flags} -D BARE_BOARD \ No newline at end of file diff --git a/src/CustomRF95.cpp b/src/CustomRF95.cpp index f31da68e6..75830fc3d 100644 --- a/src/CustomRF95.cpp +++ b/src/CustomRF95.cpp @@ -5,12 +5,14 @@ #include #include +#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(MemoryPool &_pool, PointerQueue &_rxDest) - : RH_RF95(NSS_GPIO, DIO0_GPIO), pool(_pool), rxDest(_rxDest), txQueue(MAX_TX_QUEUE), sendingPacket(NULL) + : RH_RF95(NSS_GPIO, RF95_IRQ_GPIO), RadioInterface(_pool, _rxDest), txQueue(MAX_TX_QUEUE) { } @@ -173,3 +175,5 @@ void CustomRF95::startSend(MeshPacket *txp) int res = RH_RF95::send(radiobuf, numbytes); assert(res); } + +#endif \ No newline at end of file diff --git a/src/CustomRF95.h b/src/CustomRF95.h index d44e88fd9..77b1ee8e1 100644 --- a/src/CustomRF95.h +++ b/src/CustomRF95.h @@ -1,8 +1,6 @@ #pragma once -#include "MemoryPool.h" -#include "MeshTypes.h" -#include "PointerQueue.h" +#include "RadioInterface.h" #include "mesh.pb.h" #include #include @@ -12,15 +10,12 @@ /** * 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 +class CustomRF95 : public RH_RF95, public RadioInterface { friend class MeshRadio; // for debugging we let that class touch pool - MemoryPool &pool; - PointerQueue &rxDest; PointerQueue txQueue; - MeshPacket *sendingPacket; // The packet we are currently sending 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/MeshRadio.cpp b/src/MeshRadio.cpp index c7cdcb42f..123cbb37d 100644 --- a/src/MeshRadio.cpp +++ b/src/MeshRadio.cpp @@ -24,7 +24,8 @@ 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) : rf95(_pool, _rxDest), manager(rf95) +MeshRadio::MeshRadio(MemoryPool &_pool, PointerQueue &_rxDest) + : radioIf(_pool, _rxDest) // , manager(radioIf) { myNodeInfo.num_channels = NUM_CHANNELS; @@ -50,10 +51,10 @@ bool MeshRadio::init() delay(10); #endif - manager.setThisAddress( + radioIf.setThisAddress( nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time. - if (!manager.init()) { + 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; @@ -85,11 +86,11 @@ unsigned long hash(char *str) void MeshRadio::reloadConfig() { - rf95.setModeIdle(); // Need to be idle before doing init + radioIf.setModeIdle(); // Need to be idle before doing init // Set up default configuration // No Sync Words in LORA mode. - rf95.setModemConfig( + radioIf.setModemConfig( (RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default // setModemConfig(Bw125Cr48Sf4096); // slow and reliable? // rf95.setPreambleLength(8); // Default is 8 @@ -97,7 +98,7 @@ void MeshRadio::reloadConfig() // 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 (!rf95.setFrequency(center_freq)) { + if (!radioIf.setFrequency(center_freq)) { DEBUG_MSG("setFrequency failed\n"); assert(0); // fixme panic } @@ -108,13 +109,13 @@ void MeshRadio::reloadConfig() // 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. - rf95.setTxPower(channelSettings.tx_power, false); + radioIf.setTxPower(channelSettings.tx_power, false); 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 - rf95.setModeRx(); + radioIf.setModeRx(); } ErrorCode MeshRadio::send(MeshPacket *p) @@ -122,9 +123,9 @@ ErrorCode MeshRadio::send(MeshPacket *p) lastTxStart = millis(); if (useHardware) - return rf95.send(p); + return radioIf.send(p); else { - rf95.pool.release(p); + radioIf.pool.release(p); return ERRNO_OK; } } @@ -136,12 +137,12 @@ 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 && rf95.mode() == RHGenericDriver::RHModeTx) { + 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"); - rf95.setModeRx(); - if (rf95.sendingPacket) { // There was probably a packet we were trying to send, free it - rf95.pool.release(rf95.sendingPacket); - rf95.sendingPacket = NULL; + radioIf.setModeRx(); + if (radioIf.sendingPacket) { // There was probably a packet we were trying to send, free it + radioIf.pool.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 293d5852c..efb9e4faa 100644 --- a/src/MeshRadio.h +++ b/src/MeshRadio.h @@ -64,8 +64,14 @@ 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 - rf95; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing + radioIf; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing +#else + SimRadio radioIf; +#endif /** 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 @@ -88,14 +94,8 @@ class MeshRadio private: // RHReliableDatagram manager; // don't use mesh yet - RHMesh manager; + // 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; - - /// low level send, might block for mutiple seconds - ErrorCode sendTo(NodeNum dest, const uint8_t *buf, size_t len); - - /// enqueue a received packet in rxDest - void handleReceive(MeshPacket *p); }; diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 95cfee725..9aae4762d 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -31,7 +31,7 @@ static void lsEnter() screen.setOn(false); uint32_t now = millis(); - while (!service.radio.rf95.canSleep()) { + 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 @@ -82,7 +82,12 @@ static void lsIdle() } else { DEBUG_MSG("wakeCause %d\n", wakeCause); - if (!digitalRead(BUTTON_PIN)) // If we woke because of press, instead generate a PRESS event. +#ifdef BUTTON_PIN + bool pressed = !digitalRead(BUTTON_PIN); +#else + bool pressed = false; +#endif + if (pressed) // If we woke because of press, instead generate a PRESS event. { powerFSM.trigger(EVENT_PRESS); } else { diff --git a/src/RadioInterface.cpp b/src/RadioInterface.cpp new file mode 100644 index 000000000..1406646b8 --- /dev/null +++ b/src/RadioInterface.cpp @@ -0,0 +1,15 @@ +#include "CustomRF95.h" +#include "NodeDB.h" +#include "assert.h" +#include "configuration.h" +#include +#include + +RadioInterface::RadioInterface(MemoryPool &_pool, PointerQueue &_rxDest) : pool(_pool), rxDest(_rxDest) {} + +ErrorCode SimRadio::send(MeshPacket *p) +{ + DEBUG_MSG("SimRadio.send\n"); + pool.release(p); + return ERRNO_OK; +} \ No newline at end of file diff --git a/src/RadioInterface.h b/src/RadioInterface.h new file mode 100644 index 000000000..452b8841c --- /dev/null +++ b/src/RadioInterface.h @@ -0,0 +1,123 @@ +#pragma once + +#include "MemoryPool.h" +#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 + +/** + * Basic operations all radio chipsets must implement. + * + * This defines the SOLE API for talking to radios (because soon we will have alternate radio implementations) + */ +class RadioInterface +{ + friend class MeshRadio; // for debugging we let that class touch pool + + protected: + MemoryPool &pool; + PointerQueue &rxDest; + + MeshPacket *sendingPacket = NULL; // The packet we are currently sending + 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); + + /** + * 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() { return true; } + + /// 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 + 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 + + /// 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() { 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) {} +}; diff --git a/src/configuration.h b/src/configuration.h index 44f627391..a8128882d 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -110,7 +110,7 @@ along with this program. If not, see . #ifndef USE_JTAG #define RESET_GPIO 14 #endif -#define DIO0_GPIO 26 +#define RF95_IRQ_GPIO 26 #define DIO1_GPIO 33 // Note: not really used on this board #define DIO2_GPIO 32 // Note: not really used on this board @@ -131,7 +131,7 @@ along with this program. If not, see . #ifndef USE_JTAG #define RESET_GPIO 23 #endif -#define DIO0_GPIO 26 +#define RF95_IRQ_GPIO 26 #define DIO1_GPIO 33 // Note: not really used on this board #define DIO2_GPIO 32 // Note: not really used on this board @@ -159,7 +159,7 @@ along with this program. If not, see . #ifndef USE_JTAG #define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio #endif -#define DIO0_GPIO 26 +#define RF95_IRQ_GPIO 26 #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(TTGO_LORA_V1) @@ -175,10 +175,10 @@ along with this program. If not, see . #define LED_PIN 2 // If defined we will blink this LED #define BUTTON_PIN 0 // If defined, this will be used for user button presses -#define RESET_GPIO 14 // If defined, this pin will be used to reset the LORA radio -#define DIO0_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 +#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(TTGO_LORA_V2) // This string must exactly match the case used in release file names or the android updater won't work #define HW_VENDOR "ttgo-lora32-v2" @@ -194,10 +194,14 @@ 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 DIO0_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 +#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) +// This string must exactly match the case used in release file names or the android updater won't work +#define HW_VENDOR "bare" + #endif // ----------------------------------------------------------------------------- diff --git a/src/sleep.cpp b/src/sleep.cpp index 8326f572c..e204ea5f5 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -110,7 +110,7 @@ void doDeepSleep(uint64_t msecToWake) 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.rf95.sleep(); + service.radio.radioIf.sleep(); nodeDB.saveToDisk(); @@ -213,8 +213,12 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r gpio_pullup_en((gpio_num_t)BUTTON_PIN); #endif +#ifdef BUTTON_PIN gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low - gpio_wakeup_enable((gpio_num_t)DIO0_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high +#endif +#ifdef RF95_IRQ_GPIO + gpio_wakeup_enable((gpio_num_t)RF95_IRQ_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high +#endif #ifdef PMU_IRQ // FIXME, disable wake due to PMU because it seems to fire all the time? if (axp192_found) @@ -223,7 +227,7 @@ 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(DIO0_GPIO), + // 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(); }