From fd17193d5ef1d90b344381f147be41199cd4f061 Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 14 Apr 2020 12:31:29 -0700 Subject: [PATCH] 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