commit 873f8befe17b3404f513929bc9987b24cacd9391 Author: Dirk Jahnke Date: Mon Nov 5 16:27:01 2018 +0100 First commit, not compiling (stuck in RadioHead library). diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f988dc6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.tmp +build/ +deps/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..198dfcf --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright 2017 Cesanta Software Limited + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..61764cb --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Arduino Adafruit SSD1306 C/C++ example + +This example shows how to use Adafruit SSD1306 on Mongoose OS in C/C++. diff --git a/fs/index.html b/fs/index.html new file mode 100644 index 0000000..32722d3 --- /dev/null +++ b/fs/index.html @@ -0,0 +1,12 @@ + + +FastClock-Master + + + + + diff --git a/mos.yml b/mos.yml new file mode 100644 index 0000000..647a7cd --- /dev/null +++ b/mos.yml @@ -0,0 +1,67 @@ +author: mongoose-os +description: Fastclock Master Controller +version: 1.0.1 +name: fastclock-master + +libs_version: ${mos.version} +modules_version: ${mos.version} +mongoose_os_version: ${mos.version} + +platform: esp8266 + +sources: + - src + - src/RadioHead + +includes: + - src/RadioHead + +filesystem: + - fs + +cdefs: + MPIDE: 0 + TEENSYDUINO: 0 + RH_PLATFORM: RH_PLATFORM_ESP8266 + +libs: + # common mgos libs + - origin: https://github.com/mongoose-os-libs/boards + - origin: https://github.com/mongoose-os-libs/ca-bundle + - origin: https://github.com/mongoose-os-libs/core + - origin: https://github.com/mongoose-os-libs/dash + - origin: https://github.com/mongoose-os-libs/i2c + - origin: https://github.com/mongoose-os-libs/rpc-service-config + - origin: https://github.com/mongoose-os-libs/rpc-service-fs + - origin: https://github.com/mongoose-os-libs/rpc-uart + - origin: https://github.com/mongoose-os-libs/spi + - origin: https://github.com/mongoose-os-libs/http-server + - origin: https://github.com/mongoose-os-libs/rpc-service-config + - origin: https://github.com/mongoose-os-libs/rpc-service-fs + - origin: https://github.com/mongoose-os-libs/rpc-service-ota + - origin: https://github.com/mongoose-os-libs/rpc-service-wifi + - origin: https://github.com/mongoose-os-libs/rpc-uart + - origin: https://github.com/mongoose-os-libs/ota-http-server + - origin: https://github.com/mongoose-os-libs/ota-shadow + - origin: https://github.com/mongoose-os-libs/wifi + - origin: https://github.com/mongoose-os-libs/arduino-compat + - origin: https://github.com/mongoose-os-libs/arduino-adafruit-ssd1306 + - origin: https://github.com/mongoose-os-libs/arduino-spi + # - origin: https://gitea.pmpark.de/dirk/Mongoose_Arduino_RadioHead.git + +config_schema: + - ["i2c.enable", true] + - ["i2c.scl_gpio", 5] + - ["i2c.sda_gpio", 4] + - ["i2c.freq", 1000000] + - ["wifi.ap.ssid", "Fastclock-Master-??????"] + - ["wifi.ap.pass", "Fastclock-Master"] + - ["wifi.sta.ssid", "Pinguin"] + - ["wifi.sta.pass", "PaulchenAufmKlo34"] + +tags: + - c + - arduino + - hw + +manifest_version: 2017-05-18 diff --git a/src/RadioHead/RHDatagram.cpp b/src/RadioHead/RHDatagram.cpp new file mode 100644 index 0000000..11e93e7 --- /dev/null +++ b/src/RadioHead/RHDatagram.cpp @@ -0,0 +1,120 @@ +// RHDatagram.cpp +// +// Copyright (C) 2011 Mike McCauley +// $Id: RHDatagram.cpp,v 1.6 2014/05/23 02:20:17 mikem Exp $ + +#include "RHDatagram.h" + +RHDatagram::RHDatagram(RHGenericDriver& driver, uint8_t thisAddress) + : + _driver(driver), + _thisAddress(thisAddress) +{ +} + +//////////////////////////////////////////////////////////////////// +// Public methods +bool RHDatagram::init() +{ + bool ret = _driver.init(); + if (ret) + setThisAddress(_thisAddress); + return ret; +} + +void RHDatagram::setThisAddress(uint8_t thisAddress) +{ + _driver.setThisAddress(thisAddress); + // Use this address in the transmitted FROM header + setHeaderFrom(thisAddress); + _thisAddress = thisAddress; +} + +bool RHDatagram::sendto(uint8_t* buf, uint8_t len, uint8_t address) +{ + setHeaderTo(address); + return _driver.send(buf, len); +} + +bool RHDatagram::recvfrom(uint8_t* buf, uint8_t* len, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags) +{ + if (_driver.recv(buf, len)) + { + if (from) *from = headerFrom(); + if (to) *to = headerTo(); + if (id) *id = headerId(); + if (flags) *flags = headerFlags(); + return true; + } + return false; +} + +bool RHDatagram::available() +{ + return _driver.available(); +} + +void RHDatagram::waitAvailable() +{ + _driver.waitAvailable(); +} + +bool RHDatagram::waitPacketSent() +{ + return _driver.waitPacketSent(); +} + +bool RHDatagram::waitPacketSent(uint16_t timeout) +{ + return _driver.waitPacketSent(timeout); +} + +bool RHDatagram::waitAvailableTimeout(uint16_t timeout) +{ + return _driver.waitAvailableTimeout(timeout); +} + +uint8_t RHDatagram::thisAddress() +{ + return _thisAddress; +} + +void RHDatagram::setHeaderTo(uint8_t to) +{ + _driver.setHeaderTo(to); +} + +void RHDatagram::setHeaderFrom(uint8_t from) +{ + _driver.setHeaderFrom(from); +} + +void RHDatagram::setHeaderId(uint8_t id) +{ + _driver.setHeaderId(id); +} + +void RHDatagram::setHeaderFlags(uint8_t set, uint8_t clear) +{ + _driver.setHeaderFlags(set, clear); +} + +uint8_t RHDatagram::headerTo() +{ + return _driver.headerTo(); +} + +uint8_t RHDatagram::headerFrom() +{ + return _driver.headerFrom(); +} + +uint8_t RHDatagram::headerId() +{ + return _driver.headerId(); +} + +uint8_t RHDatagram::headerFlags() +{ + return _driver.headerFlags(); +} diff --git a/src/RadioHead/RHDatagram.h b/src/RadioHead/RHDatagram.h new file mode 100644 index 0000000..adc4b70 --- /dev/null +++ b/src/RadioHead/RHDatagram.h @@ -0,0 +1,162 @@ +// RHDatagram.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHDatagram.h,v 1.14 2015/08/12 23:18:51 mikem Exp $ + +#ifndef RHDatagram_h +#define RHDatagram_h + +#include "RHGenericDriver.h" + +// This is the maximum possible message size for radios supported by RadioHead. +// Not all radios support this length, and many are much smaller +#define RH_MAX_MESSAGE_LEN 255 + +///////////////////////////////////////////////////////////////////// +/// \class RHDatagram RHDatagram.h +/// \brief Manager class for addressed, unreliable messages +/// +/// Every RHDatagram node has an 8 bit address (defaults to 0). +/// Addresses (DEST and SRC) are 8 bit integers with an address of RH_BROADCAST_ADDRESS (0xff) +/// reserved for broadcast. +/// +/// \par Media Access Strategy +/// +/// RHDatagram and the underlying drivers always transmit as soon as sendto() is called. +/// +/// \par Message Lengths +/// +/// Not all Radio drivers supported by RadioHead can handle the same message lengths. Some radios can handle +/// up to 255 octets, and some as few as 28. If you attempt to send a message that is too long for +/// the underlying driver, sendTo() will return false and will not transmit the message. +/// It is the programmers responsibility to make +/// sure that messages passed to sendto() do not exceed the capability of the radio. You can use the +/// *_MAX_MESSAGE_LENGTH definitions or driver->maxMessageLength() to help. +/// +/// \par Headers +/// +/// Each message sent and received by a RadioHead driver includes 4 headers:
+/// \b TO The node address that the message is being sent to (broadcast RH_BROADCAST_ADDRESS (255) is permitted)
+/// \b FROM The node address of the sending node
+/// \b ID A message ID, distinct (over short time scales) for each message sent by a particilar node
+/// \b 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 RHDatagram +{ +public: + /// Constructor. + /// \param[in] driver The RadioHead driver to use to transport messages. + /// \param[in] thisAddress The address to assign to this node. Defaults to 0 + RHDatagram(RHGenericDriver& driver, uint8_t thisAddress = 0); + + /// Initialise this instance and the + /// driver connected to it. + bool init(); + + /// Sets the address of this node. Defaults to 0. + /// This will be used to set the FROM address of all messages sent by this node. + /// In a conventional multinode system, all nodes will have a unique address + /// (which you could store in EEPROM). + /// \param[in] thisAddress The address of this node + void setThisAddress(uint8_t thisAddress); + + /// Sends a message to the node(s) with the given address + /// RH_BROADCAST_ADDRESS is a valid address which will cause the message + /// to be accepted by all RHDatagram nodes within range. + /// \param[in] buf Pointer to the binary message to send + /// \param[in] len Number of octets to send (> 0) + /// \param[in] address The address to send the message to. + /// \return true if the message not too loing fot eh driver, and the message was transmitted. + bool sendto(uint8_t* buf, uint8_t len, uint8_t address); + + /// Turns the receiver on if it not already on. + /// If there is a valid message available for this node, copy it to buf and return true + /// The SRC address is placed in *from if present and not NULL. + /// The DEST address is placed in *to if present and not NULL. + /// If a message is copied, *len is set to the length. + /// 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. + /// \param[in] from If present and not NULL, the referenced uint8_t will be set to the FROM address + /// \param[in] to If present and not NULL, the referenced uint8_t will be set to the TO address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// (not just those addressed to this node). + /// \return true if a valid message was copied to buf + bool recvfrom(uint8_t* buf, uint8_t* len, uint8_t* from = NULL, uint8_t* to = NULL, uint8_t* id = NULL, uint8_t* flags = NULL); + + /// 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 bythe 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() + bool available(); + + /// Starts the Driver receiver and blocks until a valid received + /// message is available. + void waitAvailable(); + + /// Blocks until the transmitter + /// is no longer transmitting. + 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. + bool waitPacketSent(uint16_t timeout); + + /// Starts the Driver 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 + bool waitAvailableTimeout(uint16_t timeout); + + /// Sets the TO header to be sent in all subsequent messages + /// \param[in] to The new TO header value + void setHeaderTo(uint8_t to); + + /// Sets the FROM header to be sent in all subsequent messages + /// \param[in] from The new FROM header value + void setHeaderFrom(uint8_t from); + + /// Sets the ID header to be sent in all subsequent messages + /// \param[in] id The new ID header value + void setHeaderId(uint8_t id); + + /// Sets and clears bits in the FLAGS header to be sent in all subsequent messages + /// \param[in] set bitmask of bits to be set + /// \param[in] clear bitmask of flags to clear + void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_NONE); + + /// Returns the TO header of the last received message + /// \return The TO header of the most recently received message. + uint8_t headerTo(); + + /// Returns the FROM header of the last received message + /// \return The FROM header of the most recently received message. + uint8_t headerFrom(); + + /// Returns the ID header of the last received message + /// \return The ID header of the most recently received message. + uint8_t headerId(); + + /// Returns the FLAGS header of the last received message + /// \return The FLAGS header of the most recently received message. + uint8_t headerFlags(); + + /// Returns the address of this node. + /// \return The address of this node + uint8_t thisAddress(); + +protected: + /// The Driver we are to use + RHGenericDriver& _driver; + + /// The address of this node + uint8_t _thisAddress; +}; + +#endif diff --git a/src/RadioHead/RHGenericDriver.cpp b/src/RadioHead/RHGenericDriver.cpp new file mode 100644 index 0000000..3ca5e74 --- /dev/null +++ b/src/RadioHead/RHGenericDriver.cpp @@ -0,0 +1,184 @@ +// RHGenericDriver.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RHGenericDriver.cpp,v 1.19 2015/12/11 01:10:24 mikem Exp $ + +#include "RHGenericDriver.h" + +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) +{ +} + +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; +} + +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; +} + +int8_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) +{ + uint8_t i; + +#ifdef RH_HAVE_SERIAL + Serial.println(prompt); + 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; +} + +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(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/RadioHead/RHGenericDriver.h b/src/RadioHead/RHGenericDriver.h new file mode 100644 index 0000000..f71baa3 --- /dev/null +++ b/src/RadioHead/RHGenericDriver.h @@ -0,0 +1,265 @@ +// RHGenericDriver.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RHGenericDriver.h,v 1.17 2016/04/04 01:40:12 mikem Exp $ + +#ifndef RHGenericDriver_h +#define RHGenericDriver_h + +#include "RadioHead.h" + +// 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 + +///////////////////////////////////////////////////////////////////// +/// \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. + } 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 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) + /// \return true if the message length was valid and it was correctly queued for transmit + 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 RF22 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); + + /// 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. + int8_t lastRssi(); + + /// Returns the operating mode of the library. + /// \return the current mode, one of RF69_MODE_* + RHMode mode(); + + /// Sets the operating mode of the transport. + 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. + uint16_t rxBad(); + + /// Returns the count of the number of + /// good received packets + /// \return The number of good packets received. + 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 + 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 int8_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; + +private: + +}; + + +#endif diff --git a/src/RadioHead/RHGenericSPI.cpp b/src/RadioHead/RHGenericSPI.cpp new file mode 100644 index 0000000..9c4011f --- /dev/null +++ b/src/RadioHead/RHGenericSPI.cpp @@ -0,0 +1,30 @@ +// 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.h" + +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/RadioHead/RHGenericSPI.h b/src/RadioHead/RHGenericSPI.h new file mode 100644 index 0000000..8a13006 --- /dev/null +++ b/src/RadioHead/RHGenericSPI.h @@ -0,0 +1,146 @@ +// RHGenericSPI.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHGenericSPI.h,v 1.7 2014/04/14 08:37:11 mikem Exp $ + +#ifndef RHGenericSPI_h +#define RHGenericSPI_h + +#include "RadioHead.h" + +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) +#include // for SPI_HAS_TRANSACTION and SPISettings +#endif + +///////////////////////////////////////////////////////////////////// +/// \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; + + /// 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); + + // Try to add SPI Transaction support + // Note: Maybe add some way to set SPISettings? + virtual void beginTransaction() {}; + virtual void endTransaction() {}; +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/RadioHead/RHHardwareSPI.cpp b/src/RadioHead/RHHardwareSPI.cpp new file mode 100644 index 0000000..4b1e064 --- /dev/null +++ b/src/RadioHead/RHHardwareSPI.cpp @@ -0,0 +1,411 @@ +// RHHardwareSPI.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHHardwareSPI.cpp,v 1.16 2016/07/07 00:02:53 mikem Exp mikem $ + +#include "RHHardwareSPI.h" + +// Declare a single default instance of the hardware SPI interface class +RHHardwareSPI hardware_spi; + +#ifdef RH_HAVE_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); +#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) + // 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); +} + +void RHHardwareSPI::attachInterrupt() +{ +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) + SPI.attachInterrupt(); +#endif +} + +void RHHardwareSPI::detachInterrupt() +{ +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) + SPI.detachInterrupt(); +#endif +} + +void RHHardwareSPI::begin() +{ + // 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) + 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) + // Zero requires begin() before anything else :-) + SPI.begin(); + #endif + + SPI.setDataMode(dataMode); +#endif +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION) + uint32_t frequency32; + if (_frequency == Frequency16MHz) { + frequency32 = 16000000; + } else if (_frequency == Frequency8MHz) { + frequency32 = 8000000; + } else if (_frequency == Frequency4MHz) { + frequency32 = 4000000; + } else if (_frequency == Frequency2MHz) { + frequency32 = 2000000; + } else { + frequency32 = 1000000; + } + _settings = SPISettings(frequency32, + (_bitOrder == BitOrderLSBFirst) ? LSBFIRST : MSBFIRST, + dataMode); + //Serial.print("SPISettings: "); Serial.println(frequency32, DEC); +#endif + + +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD)) + // Arduino Due in 1.5.5 has its own BitOrder :-( + // So too does Arduino Zero + ::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 + Serial.println("HERE"); + 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); +#else + #warning RHHardwareSPI does not support this platform yet. Consider adding it and contributing a patch. +#endif +} + +void RHHardwareSPI::end() +{ + return SPI.end(); +} + +// If our platform is arduino and we support transactions then lets use the begin/end transaction +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION) +void RHHardwareSPI::beginTransaction() +{ + SPI.beginTransaction(_settings); +} + +void RHHardwareSPI::endTransaction() +{ + SPI.endTransaction(); +} + #endif + +#endif diff --git a/src/RadioHead/RHHardwareSPI.h b/src/RadioHead/RHHardwareSPI.h new file mode 100644 index 0000000..b17c657 --- /dev/null +++ b/src/RadioHead/RHHardwareSPI.h @@ -0,0 +1,73 @@ +// RHHardwareSPI.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// Contributed by Joanna Rutkowska +// $Id: RHHardwareSPI.h,v 1.9 2014/08/12 00:54:52 mikem Exp $ + +#ifndef RHHardwareSPI_h +#define RHHardwareSPI_h + +#include "RHGenericSPI.h" + +///////////////////////////////////////////////////////////////////// +/// \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. +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); + + // 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(); + #if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION) +public: + void beginTransaction(); + void endTransaction(); + SPISettings _settings; + #endif +#else + // not supported on ATTiny etc + uint8_t transfer(uint8_t data) {return 0;} + void begin(){} + void end(){} + +#endif +}; + +// Built in default instance +extern RHHardwareSPI hardware_spi; + +#endif diff --git a/src/RadioHead/RHNRFSPIDriver.cpp b/src/RadioHead/RHNRFSPIDriver.cpp new file mode 100644 index 0000000..5ffd7b8 --- /dev/null +++ b/src/RadioHead/RHNRFSPIDriver.cpp @@ -0,0 +1,111 @@ +// RHNRFSPIDriver.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RHNRFSPIDriver.cpp,v 1.3 2015/12/16 04:55:33 mikem Exp $ + +#include "RHNRFSPIDriver.h" + +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; + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(command); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + return status; +} + +uint8_t RHNRFSPIDriver::spiRead(uint8_t reg) +{ + uint8_t val; + ATOMIC_BLOCK_START; + _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(); + ATOMIC_BLOCK_END; + return val; +} + +uint8_t RHNRFSPIDriver::spiWrite(uint8_t reg, uint8_t val) +{ + uint8_t status = 0; + ATOMIC_BLOCK_START; + _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(); + 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; + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(reg); // Send the start address + while (len--) + *dest++ = _spi.transfer(0); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + 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; + _spi.beginTransaction(); + digitalWrite(_slaveSelectPin, LOW); + status = _spi.transfer(reg); // Send the start address + while (len--) + _spi.transfer(*src++); + digitalWrite(_slaveSelectPin, HIGH); + _spi.endTransaction(); + ATOMIC_BLOCK_END; + return status; +} + +void RHNRFSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin) +{ + _slaveSelectPin = slaveSelectPin; +} diff --git a/src/RadioHead/RHNRFSPIDriver.h b/src/RadioHead/RHNRFSPIDriver.h new file mode 100644 index 0000000..71dfed9 --- /dev/null +++ b/src/RadioHead/RHNRFSPIDriver.h @@ -0,0 +1,95 @@ +// RHNRFSPIDriver.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RHNRFSPIDriver.h,v 1.3 2015/12/16 04:55:33 mikem Exp $ + +#ifndef RHNRFSPIDriver_h +#define RHNRFSPIDriver_h + +#include "RHGenericDriver.h" +#include "RHHardwareSPI.h" + +class RHGenericSPI; + +///////////////////////////////////////////////////////////////////// +/// \class RHNRFSPIDriver RHNRFSPIDriver.h +/// \brief Base class for a RadioHead driver 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 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); + +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/RadioHead/RHReliableDatagram.cpp b/src/RadioHead/RHReliableDatagram.cpp new file mode 100644 index 0000000..811fdf6 --- /dev/null +++ b/src/RadioHead/RHReliableDatagram.cpp @@ -0,0 +1,186 @@ +// RHReliableDatagram.cpp +// +// Define addressed datagram +// +// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers +// (see http://www.hoperf.com) +// RHDatagram will be received only by the addressed node or all nodes within range if the +// to address is RH_BROADCAST_ADDRESS +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHReliableDatagram.cpp,v 1.15 2015/12/11 01:10:24 mikem Exp $ + +#include "RHReliableDatagram.h" + +//////////////////////////////////////////////////////////////////// +// Constructors +RHReliableDatagram::RHReliableDatagram(RHGenericDriver& driver, uint8_t thisAddress) + : RHDatagram(driver, thisAddress) +{ + _retransmissions = 0; + _lastSequenceNumber = 0; + _timeout = RH_DEFAULT_TIMEOUT; + _retries = RH_DEFAULT_RETRIES; +} + +//////////////////////////////////////////////////////////////////// +// Public methods +void RHReliableDatagram::setTimeout(uint16_t timeout) +{ + _timeout = timeout; +} + +//////////////////////////////////////////////////////////////////// +void RHReliableDatagram::setRetries(uint8_t retries) +{ + _retries = retries; +} + +//////////////////////////////////////////////////////////////////// +uint8_t RHReliableDatagram::retries() +{ + return _retries; +} + +//////////////////////////////////////////////////////////////////// +bool RHReliableDatagram::sendtoWait(uint8_t* buf, uint8_t len, uint8_t address) +{ + // Assemble the message + uint8_t thisSequenceNumber = ++_lastSequenceNumber; + uint8_t retries = 0; + while (retries++ <= _retries) + { + setHeaderId(thisSequenceNumber); + setHeaderFlags(RH_FLAGS_NONE, RH_FLAGS_ACK); // Clear the ACK flag + sendto(buf, len, address); + waitPacketSent(); + + // Never wait for ACKS to broadcasts: + if (address == RH_BROADCAST_ADDRESS) + return true; + + if (retries > 1) + _retransmissions++; + unsigned long thisSendTime = millis(); // Timeout does not include original transmit time + + // Compute a new timeout, random between _timeout and _timeout*2 + // This is to prevent collisions on every retransmit + // if 2 nodes try to transmit at the same time +#if (RH_PLATFORM == RH_PLATFORM_RASPI) // use standard library random(), bugs in random(min, max) + uint16_t timeout = _timeout + (_timeout * (random() & 0xFF) / 256); +#else + uint16_t timeout = _timeout + (_timeout * random(0, 256) / 256); +#endif + int32_t timeLeft; + while ((timeLeft = timeout - (millis() - thisSendTime)) > 0) + { + if (waitAvailableTimeout(timeLeft)) + { + uint8_t from, to, id, flags; + if (recvfrom(0, 0, &from, &to, &id, &flags)) // Discards the message + { + // Now have a message: is it our ACK? + if ( from == address + && to == _thisAddress + && (flags & RH_FLAGS_ACK) + && (id == thisSequenceNumber)) + { + // Its the ACK we are waiting for + return true; + } + else if ( !(flags & RH_FLAGS_ACK) + && (id == _seenIds[from])) + { + // This is a request we have already received. ACK it again + acknowledge(id, from); + } + // Else discard it + } + } + // Not the one we are waiting for, maybe keep waiting until timeout exhausted + YIELD; + } + // Timeout exhausted, maybe retry + YIELD; + } + // Retries exhausted + return false; +} + +//////////////////////////////////////////////////////////////////// +bool RHReliableDatagram::recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags) +{ + uint8_t _from; + uint8_t _to; + uint8_t _id; + uint8_t _flags; + // Get the message before its clobbered by the ACK (shared rx and tx buffer in some drivers + if (available() && recvfrom(buf, len, &_from, &_to, &_id, &_flags)) + { + // Never ACK an ACK + if (!(_flags & RH_FLAGS_ACK)) + { + // Its a normal message for this node, not an ACK + if (_to != RH_BROADCAST_ADDRESS) + { + // Its not a broadcast, so ACK it + // Acknowledge message with ACK set in flags and ID set to received ID + acknowledge(_id, _from); + } + // If we have not seen this message before, then we are interested in it + if (_id != _seenIds[_from]) + { + if (from) *from = _from; + if (to) *to = _to; + if (id) *id = _id; + if (flags) *flags = _flags; + _seenIds[_from] = _id; + return true; + } + // Else just re-ack it and wait for a new one + } + } + // No message for us available + return false; +} + +bool RHReliableDatagram::recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags) +{ + unsigned long starttime = millis(); + int32_t timeLeft; + while ((timeLeft = timeout - (millis() - starttime)) > 0) + { + if (waitAvailableTimeout(timeLeft)) + { + if (recvfromAck(buf, len, from, to, id, flags)) + return true; + } + YIELD; + } + return false; +} + +uint32_t RHReliableDatagram::retransmissions() +{ + return _retransmissions; +} + +void RHReliableDatagram::resetRetransmissions() +{ + _retransmissions = 0; +} + +void RHReliableDatagram::acknowledge(uint8_t id, uint8_t from) +{ + setHeaderId(id); + setHeaderFlags(RH_FLAGS_ACK); + // We would prefer to send a zero length ACK, + // but if an RH_RF22 receives a 0 length message with a CRC error, it will never receive + // a 0 length message again, until its reset, which makes everything hang :-( + // So we send an ACK of 1 octet + // REVISIT: should we send the RSSI for the information of the sender? + uint8_t ack = '!'; + sendto(&ack, sizeof(ack), from); + waitPacketSent(); +} diff --git a/src/RadioHead/RHReliableDatagram.h b/src/RadioHead/RHReliableDatagram.h new file mode 100644 index 0000000..38044e5 --- /dev/null +++ b/src/RadioHead/RHReliableDatagram.h @@ -0,0 +1,202 @@ +// RHReliableDatagram.h +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHReliableDatagram.h,v 1.17 2016/04/04 01:40:12 mikem Exp $ + +#ifndef RHReliableDatagram_h +#define RHReliableDatagram_h + +#include "RHDatagram.h" + +// The acknowledgement bit in the FLAGS +// The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved +// for application layer use. +#define RH_FLAGS_ACK 0x80 + +/// the default retry timeout in milliseconds +#define RH_DEFAULT_TIMEOUT 200 + +/// The default number of retries +#define RH_DEFAULT_RETRIES 3 + +///////////////////////////////////////////////////////////////////// +/// \class RHReliableDatagram RHReliableDatagram.h +/// \brief RHDatagram subclass for sending addressed, acknowledged, retransmitted datagrams. +/// +/// Manager class that extends RHDatagram to define addressed, reliable datagrams with acknowledgement and retransmission. +/// Based on RHDatagram, adds flags and sequence numbers. RHReliableDatagram is reliable in the sense +/// that messages are acknowledged by the recipient, and unacknowledged messages are retransmitted until acknowledged or the +/// retries are exhausted. +/// When addressed messages are sent (by sendtoWait()), it will wait for an ack, and retransmit +/// after timeout until an ack is received or retries are exhausted. +/// When addressed messages are collected by the application (by recvfromAck()), +/// an acknowledgement is automatically sent to the sender. +/// +/// You can use RHReliableDatagram to send broadcast messages, with a TO address of RH_BROADCAST_ADDRESS, +/// however broadcasts are not acknowledged or retransmitted and are therefore NOT actually reliable. +/// +/// The retransmit timeout is randomly varied between timeout and timeout*2 to prevent collisions on all +/// retries when 2 nodes happen to start sending at the same time . +/// +/// Each new message sent by sendtoWait() has its ID incremented. +/// +/// An ack consists of a message with: +/// - TO set to the from address of the original message +/// - FROM set to this node address +/// - ID set to the ID of the original message +/// - FLAGS with the RH_FLAGS_ACK bit set +/// - 1 octet of payload containing ASCII '!' (since some drivers cannot handle 0 length payloads) +/// +/// \par Media Access Strategy +/// +/// RHReliableDatagram and the underlying drivers always transmit as soon as +/// sendtoWait() is called. RHReliableDatagram waits for an acknowledgement, +/// and if one is not received after a timeout period the message is +/// transmitted again. If no acknowledgement is received after several +/// retries, the transmissions is deemed to have failed. +/// No contention for media is detected. +/// This will be recognised as "pure ALOHA". +/// The addition of Clear Channel Assessment (CCA) is desirable and planned. +/// +/// There is no message queuing or threading in RHReliableDatagram. +/// sendtoWait() waits until an acknowledgement is received, retransmitting +/// up to (by default) 3 retries time with a default 200ms timeout. +/// During this transmit-acknowledge phase, any received message (other than the expected +/// acknowledgement) will be ignored. Your sketch will be unresponsive to new messages +/// until an acknowledgement is received or the retries are exhausted. +/// Central server-type sketches should be very cautious about their +/// retransmit strategy and configuration lest they hang for a long time +/// trying to reply to clients that are unreachable. +/// +/// Caution: if you have a radio network with a mixture of slow and fast +/// processors and ReliableDatagrams, you may be affected by race conditions +/// where the fast processor acknowledges a message before the sender is ready +/// to process the acknowledgement. Best practice is to use the same processors (and +/// radios) throughout your network. +/// +class RHReliableDatagram : public RHDatagram +{ +public: + /// Constructor. + /// \param[in] driver The RadioHead driver to use to transport messages. + /// \param[in] thisAddress The address to assign to this node. Defaults to 0 + RHReliableDatagram(RHGenericDriver& driver, uint8_t thisAddress = 0); + + /// Sets the minimum retransmit timeout. If sendtoWait is waiting for an ack + /// longer than this time (in milliseconds), + /// it will retransmit the message. Defaults to 200ms. The timeout is measured from the end of + /// transmission of the message. It must be at least longer than the the transmit + /// time of the acknowledgement (preamble+6 octets) plus the latency/poll time of the receiver. + /// For fast modulation schemes you can considerably shorten this time. + /// Caution: if you are using slow packet rates and long packets + /// you may need to change the timeout for reliable operations. + /// The actual timeout is randomly varied between timeout and timeout*2. + /// \param[in] timeout The new timeout period in milliseconds + void setTimeout(uint16_t timeout); + + /// Sets the maximum number of retries. Defaults to 3 at construction time. + /// If set to 0, each message will only ever be sent once. + /// sendtoWait will give up and return false if there is no ack received after all transmissions time out + /// and the retries count is exhausted. + /// param[in] retries The maximum number a retries. + void setRetries(uint8_t retries); + + /// Returns the currently configured maximum retries count. + /// Can be changed with setRetries(). + /// \return The currently configured maximum number of retries. + uint8_t retries(); + + /// Send the message (with retries) and waits for an ack. Returns true if an acknowledgement is received. + /// Synchronous: any message other than the desired ACK received while waiting is discarded. + /// Blocks until an ACK is received or all retries are exhausted (ie up to retries*timeout milliseconds). + /// If the destination address is the broadcast address RH_BROADCAST_ADDRESS (255), the message will + /// be sent as a broadcast, but receiving nodes do not acknowledge, and sendtoWait() returns true immediately + /// without waiting for any acknowledgements. + /// \param[in] address The address to send the message to. + /// \param[in] buf Pointer to the binary message to send + /// \param[in] len Number of octets to send + /// \return true if the message was transmitted and an acknowledgement was received. + bool sendtoWait(uint8_t* buf, uint8_t len, uint8_t address); + + /// If there is a valid message available for this node, send an acknowledgement to the SRC + /// address (blocking until this is complete), then copy the message to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length.. + /// If from is not NULL, the SRC address is placed in *from. + /// If to is not NULL, the DEST address is placed in *to. + /// This is the preferred function for getting messages addressed to this node. + /// If the message is not a broadcast, acknowledge to the sender before returning. + /// 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 Available space in buf. Set to the actual number of octets copied. + /// \param[in] from If present and not NULL, the referenced uint8_t will be set to the SRC address + /// \param[in] to If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// (not just those addressed to this node). + /// \return true if a valid message was copied to buf + bool recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* from = NULL, uint8_t* to = NULL, uint8_t* id = NULL, uint8_t* flags = NULL); + + /// Similar to recvfromAck(), this will block until either a valid message available for this node + /// or the timeout expires. Starts the receiver automatically. + /// 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 Available space in buf. Set to the actual number of octets copied. + /// \param[in] timeout Maximum time to wait in milliseconds + /// \param[in] from If present and not NULL, the referenced uint8_t will be set to the SRC address + /// \param[in] to If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// (not just those addressed to this node). + /// \return true if a valid message was copied to buf + bool recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* from = NULL, uint8_t* to = NULL, uint8_t* id = NULL, uint8_t* flags = NULL); + + /// Returns the number of retransmissions + /// we have had to send since starting or since the last call to resetRetransmissions(). + /// \return The number of retransmissions since initialisation. + uint32_t retransmissions(); + + /// Resets the count of the number of retransmissions + /// to 0. + void resetRetransmissions(); + +protected: + /// Send an ACK for the message id to the given from address + /// Blocks until the ACK has been sent + void acknowledge(uint8_t id, uint8_t from); + + /// Checks whether the message currently in the Rx buffer is a new message, not previously received + /// based on the from address and the sequence. If it is new, it is acknowledged and returns true + /// \return true if there is a message received and it is a new message + bool haveNewMessage(); + +private: + /// Count of retransmissions we have had to send + uint32_t _retransmissions; + + /// The last sequence number to be used + /// Defaults to 0 + uint8_t _lastSequenceNumber; + + // Retransmit timeout (milliseconds) + /// Defaults to 200 + uint16_t _timeout; + + // Retries (0 means one try only) + /// Defaults to 3 + uint8_t _retries; + + /// Array of the last seen sequence number indexed by node address that sent it + /// It is used for duplicate detection. Duplicated messages are re-acknowledged when received + /// (this is generally due to lost ACKs, causing the sender to retransmit, even though we have already + /// received that message) + uint8_t _seenIds[256]; +}; + +/// @example rf22_reliable_datagram_client.pde +/// @example rf22_reliable_datagram_server.pde + +#endif diff --git a/src/RadioHead/RHRouter.cpp b/src/RadioHead/RHRouter.cpp new file mode 100644 index 0000000..1f7414e --- /dev/null +++ b/src/RadioHead/RHRouter.cpp @@ -0,0 +1,305 @@ +// RHRouter.cpp +// +// Define addressed datagram +// +// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers +// (see http://www.hoperf.com) +// RHDatagram will be received only by the addressed node or all nodes within range if the +// to address is RH_BROADCAST_ADDRESS +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHRouter.cpp,v 1.7 2015/08/13 02:45:47 mikem Exp $ + +#include "RHRouter.h" + +RHRouter::RoutedMessage RHRouter::_tmpMessage; + +//////////////////////////////////////////////////////////////////// +// Constructors +RHRouter::RHRouter(RHGenericDriver& driver, uint8_t thisAddress) + : RHReliableDatagram(driver, thisAddress) +{ + _max_hops = RH_DEFAULT_MAX_HOPS; + clearRoutingTable(); +} + +//////////////////////////////////////////////////////////////////// +// Public methods +bool RHRouter::init() +{ + bool ret = RHReliableDatagram::init(); + if (ret) + _max_hops = RH_DEFAULT_MAX_HOPS; + return ret; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::setMaxHops(uint8_t max_hops) +{ + _max_hops = max_hops; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::addRouteTo(uint8_t dest, uint8_t next_hop, uint8_t state) +{ + uint8_t i; + + // First look for an existing entry we can update + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + if (_routes[i].dest == dest) + { + _routes[i].dest = dest; + _routes[i].next_hop = next_hop; + _routes[i].state = state; + return; + } + } + + // Look for an invalid entry we can use + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + if (_routes[i].state == Invalid) + { + _routes[i].dest = dest; + _routes[i].next_hop = next_hop; + _routes[i].state = state; + return; + } + } + + // Need to make room for a new one + retireOldestRoute(); + // Should be an invalid slot now + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + if (_routes[i].state == Invalid) + { + _routes[i].dest = dest; + _routes[i].next_hop = next_hop; + _routes[i].state = state; + } + } +} + +//////////////////////////////////////////////////////////////////// +RHRouter::RoutingTableEntry* RHRouter::getRouteTo(uint8_t dest) +{ + uint8_t i; + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + if (_routes[i].dest == dest && _routes[i].state != Invalid) + return &_routes[i]; + return NULL; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::deleteRoute(uint8_t index) +{ + // Delete a route by copying following routes on top of it + memcpy(&_routes[index], &_routes[index+1], + sizeof(RoutingTableEntry) * (RH_ROUTING_TABLE_SIZE - index - 1)); + _routes[RH_ROUTING_TABLE_SIZE - 1].state = Invalid; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::printRoutingTable() +{ +#ifdef RH_HAVE_SERIAL + uint8_t i; + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + Serial.print(i, DEC); + Serial.print(" Dest: "); + Serial.print(_routes[i].dest, DEC); + Serial.print(" Next Hop: "); + Serial.print(_routes[i].next_hop, DEC); + Serial.print(" State: "); + Serial.println(_routes[i].state, DEC); + } +#endif +} + +//////////////////////////////////////////////////////////////////// +bool RHRouter::deleteRouteTo(uint8_t dest) +{ + uint8_t i; + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + { + if (_routes[i].dest == dest) + { + deleteRoute(i); + return true; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::retireOldestRoute() +{ + // We just obliterate the first in the table and clear the last + deleteRoute(0); +} + +//////////////////////////////////////////////////////////////////// +void RHRouter::clearRoutingTable() +{ + uint8_t i; + for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++) + _routes[i].state = Invalid; +} + + +uint8_t RHRouter::sendtoWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t flags) +{ + return sendtoFromSourceWait(buf, len, dest, _thisAddress, flags); +} + +//////////////////////////////////////////////////////////////////// +// Waits for delivery to the next hop (but not for delivery to the final destination) +uint8_t RHRouter::sendtoFromSourceWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t source, uint8_t flags) +{ + if (((uint16_t)len + sizeof(RoutedMessageHeader)) > _driver.maxMessageLength()) + return RH_ROUTER_ERROR_INVALID_LENGTH; + + // Construct a RH RouterMessage message + _tmpMessage.header.source = source; + _tmpMessage.header.dest = dest; + _tmpMessage.header.hops = 0; + _tmpMessage.header.id = _lastE2ESequenceNumber++; + _tmpMessage.header.flags = flags; + memcpy(_tmpMessage.data, buf, len); + + return route(&_tmpMessage, sizeof(RoutedMessageHeader)+len); +} + +//////////////////////////////////////////////////////////////////// +uint8_t RHRouter::route(RoutedMessage* message, uint8_t messageLen) +{ + // Reliably deliver it if possible. See if we have a route: + uint8_t next_hop = RH_BROADCAST_ADDRESS; + if (message->header.dest != RH_BROADCAST_ADDRESS) + { + RoutingTableEntry* route = getRouteTo(message->header.dest); + if (!route) + return RH_ROUTER_ERROR_NO_ROUTE; + next_hop = route->next_hop; + } + + if (!RHReliableDatagram::sendtoWait((uint8_t*)message, messageLen, next_hop)) + return RH_ROUTER_ERROR_UNABLE_TO_DELIVER; + + return RH_ROUTER_ERROR_NONE; +} + +//////////////////////////////////////////////////////////////////// +// Subclasses may want to override this to peek at messages going past +void RHRouter::peekAtMessage(RoutedMessage* message, uint8_t messageLen) +{ + // Default does nothing +} + +//////////////////////////////////////////////////////////////////// +bool RHRouter::recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source, uint8_t* dest, uint8_t* id, uint8_t* flags) +{ + uint8_t tmpMessageLen = sizeof(_tmpMessage); + uint8_t _from; + uint8_t _to; + uint8_t _id; + uint8_t _flags; + if (RHReliableDatagram::recvfromAck((uint8_t*)&_tmpMessage, &tmpMessageLen, &_from, &_to, &_id, &_flags)) + { + // Here we simulate networks with limited visibility between nodes + // so we can test routing +#ifdef RH_TEST_NETWORK + if ( +#if RH_TEST_NETWORK==1 + // This network looks like 1-2-3-4 + (_thisAddress == 1 && _from == 2) + || (_thisAddress == 2 && (_from == 1 || _from == 3)) + || (_thisAddress == 3 && (_from == 2 || _from == 4)) + || (_thisAddress == 4 && _from == 3) + +#elif RH_TEST_NETWORK==2 + // This network looks like 1-2-4 + // | | | + // --3-- + (_thisAddress == 1 && (_from == 2 || _from == 3)) + || _thisAddress == 2 + || _thisAddress == 3 + || (_thisAddress == 4 && (_from == 2 || _from == 3)) + +#elif RH_TEST_NETWORK==3 + // This network looks like 1-2-4 + // | | + // --3-- + (_thisAddress == 1 && (_from == 2 || _from == 3)) + || (_thisAddress == 2 && (_from == 1 || _from == 4)) + || (_thisAddress == 3 && (_from == 1 || _from == 4)) + || (_thisAddress == 4 && (_from == 2 || _from == 3)) + +#elif RH_TEST_NETWORK==4 + // This network looks like 1-2-3 + // | + // 4 + (_thisAddress == 1 && _from == 2) + || _thisAddress == 2 + || (_thisAddress == 3 && _from == 2) + || (_thisAddress == 4 && _from == 2) + +#endif +) + { + // OK + } + else + { + return false; // Pretend we got nothing + } +#endif + + peekAtMessage(&_tmpMessage, tmpMessageLen); + // See if its for us or has to be routed + if (_tmpMessage.header.dest == _thisAddress || _tmpMessage.header.dest == RH_BROADCAST_ADDRESS) + { + // Deliver it here + if (source) *source = _tmpMessage.header.source; + if (dest) *dest = _tmpMessage.header.dest; + if (id) *id = _tmpMessage.header.id; + if (flags) *flags = _tmpMessage.header.flags; + uint8_t msgLen = tmpMessageLen - sizeof(RoutedMessageHeader); + if (*len > msgLen) + *len = msgLen; + memcpy(buf, _tmpMessage.data, *len); + return true; // Its for you! + } + else if ( _tmpMessage.header.dest != RH_BROADCAST_ADDRESS + && _tmpMessage.header.hops++ < _max_hops) + { + // Maybe it has to be routed to the next hop + // REVISIT: if it fails due to no route or unable to deliver to the next hop, + // tell the originator. BUT HOW? + route(&_tmpMessage, tmpMessageLen); + } + // Discard it and maybe wait for another + } + return false; +} + +//////////////////////////////////////////////////////////////////// +bool RHRouter::recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* source, uint8_t* dest, uint8_t* id, uint8_t* flags) +{ + unsigned long starttime = millis(); + int32_t timeLeft; + while ((timeLeft = timeout - (millis() - starttime)) > 0) + { + if (waitAvailableTimeout(timeLeft)) + { + if (recvfromAck(buf, len, source, dest, id, flags)) + return true; + } + YIELD; + } + return false; +} diff --git a/src/RadioHead/RHRouter.h b/src/RadioHead/RHRouter.h new file mode 100644 index 0000000..e49f329 --- /dev/null +++ b/src/RadioHead/RHRouter.h @@ -0,0 +1,327 @@ +// RHRouter.h +// +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2011 Mike McCauley +// $Id: RHRouter.h,v 1.9 2014/08/10 20:55:17 mikem Exp $ + +#ifndef RHRouter_h +#define RHRouter_h + +#include "RHReliableDatagram.h" + +// Default max number of hops we will route +#define RH_DEFAULT_MAX_HOPS 30 + +// The default size of the routing table we keep +#define RH_ROUTING_TABLE_SIZE 10 + +// Error codes +#define RH_ROUTER_ERROR_NONE 0 +#define RH_ROUTER_ERROR_INVALID_LENGTH 1 +#define RH_ROUTER_ERROR_NO_ROUTE 2 +#define RH_ROUTER_ERROR_TIMEOUT 3 +#define RH_ROUTER_ERROR_NO_REPLY 4 +#define RH_ROUTER_ERROR_UNABLE_TO_DELIVER 5 + +// This size of RH_ROUTER_MAX_MESSAGE_LEN is OK for Arduino Mega, but too big for +// Duemilanova. Size of 50 works with the sample router programs on Duemilanova. +#define RH_ROUTER_MAX_MESSAGE_LEN (RH_MAX_MESSAGE_LEN - sizeof(RHRouter::RoutedMessageHeader)) +//#define RH_ROUTER_MAX_MESSAGE_LEN 50 + +// These allow us to define a simulated network topology for testing purposes +// See RHRouter.cpp for details +//#define RH_TEST_NETWORK 1 +//#define RH_TEST_NETWORK 2 +//#define RH_TEST_NETWORK 3 +//#define RH_TEST_NETWORK 4 + +///////////////////////////////////////////////////////////////////// +/// \class RHRouter RHRouter.h +/// \brief RHReliableDatagram subclass for sending addressed, optionally acknowledged datagrams +/// multi-hop routed across a network. +/// +/// Manager class that extends RHReliableDatagram to define addressed messages +/// That are reliably transmitted and routed across a network. Each message is transmitted reliably +/// between each hop in order to get from the source node to the destination node. +/// +/// With RHRouter, routes are hard wired. This means that each node must have programmed +/// in it how to reach each of the other nodes it will be trying to communicate with. +/// This means you must specify the next-hop node address for each of the destination nodes, +/// using the addRouteTo() function. +/// +/// When sendtoWait() is called with a new message to deliver, and the destination address, +/// RHRouter looks up the next hop node for the destination node. It then uses +/// RHReliableDatagram to (reliably) deliver the message to the next hop +/// (which is expected also to be running an RHRouter). If that next-hop node is not +/// the final destination, it will also look up the next hop for the destination node and +/// (reliably) deliver the message to the next hop. By this method, messages can be delivered +/// across a network of nodes, even if each node cannot hear all of the others in the network. +/// Each time a message is received for another node and retransmitted to the next hop, +/// the HOPS filed in teh header is incremented. If a message is received for routing to another node +/// which has exceed the routers max_hops, the message wioll be dropped and ignored. +/// This helps prevent infinite routing loops. +/// +/// RHRouter supports messages with a dest of RH_BROADCAST_ADDRESS. Such messages are not routed, +/// and are broadcast (once) to all nodes within range. +/// +/// The recvfromAck() function is responsible not just for receiving and delivering +/// messages addressed to this node (or RH_BROADCAST_ADDRESS), but +/// it is also responsible for routing other message to their next hop. This means that it is important to +/// call recvfromAck() or recvfromAckTimeout() frequently in your main loop. recvfromAck() will return +/// false if it receives a message but it is not for this node. +/// +/// RHRouter does not provide reliable end-to-end delivery, but uses reliable hop-to-hop delivery. +/// If a message is unable to be delivered to an end node during to a delivery failure between 2 hops, +/// the source node will not be told about it. +/// +/// Note: This class is most useful for networks of nodes that are essentially static +/// (i.e. the nodes dont move around), and for which the +/// routing never changes. If that is not the case for your proposed network, see RHMesh instead. +/// +/// \par The Routing Table +/// +/// The routing table is a local table in RHRouter that holds the information about the next hop node +/// address for each destination address you may want to send a message to. It is your responsibility +/// to make sure every node in an RHRouter network has been configured with a unique address and the +/// routing information so that messages are correctly routed across the network from source node to +/// destination node. This is usually done once in setup() by calling addRouteTo(). +/// The hardwired routing will in general be different on each node, and will depend on the physical +/// topololgy of the network. +/// You can also use addRouteTo() to change a route and +/// deleteRouteTo() to delete a route at run time. Youcan also clear the entire routing table +/// +/// The Routing Table has limited capacity for entries (defined by RH_ROUTING_TABLE_SIZE, which is 10) +/// if more than RH_ROUTING_TABLE_SIZE are added, the oldest (first) one will be removed by calling +/// retireOldestRoute() +/// +/// \par Message Format +/// +/// RHRouter add to the lower level RHReliableDatagram (and even lower level RH) class message formats. +/// In those lower level classes, the hop-to-hop message headers are in the RH message headers, +/// and are handled automcatically by tyhe RH hardware. +/// RHRouter and its subclasses add an end-to-end addressing header in the payload of the RH message, +/// and before the RHRouter application data. +/// - 1 octet DEST, the destination node address (ie the address of the final +/// destination node for this message) +/// - 1 octet SOURCE, the source node address (ie the address of the originating node that first sent +/// the message). +/// - 1 octet HOPS, the number of hops this message has traversed so far. +/// - 1 octet ID, an incrementing message ID for end-to-end message tracking for use by subclasses. +/// Not used by RHRouter. +/// - 1 octet FLAGS, a bitmask for use by subclasses. Not used by RHRouter. +/// - 0 or more octets DATA, the application payload data. The length of this data is implicit +/// in the length of the entire message. +/// +/// You should be careful to note that there are ID and FLAGS fields in the low level per-hop +/// message header too. These are used only for hop-to-hop, and in general will be different to +/// the ones at the RHRouter level. +/// +/// \par Testing +/// +/// Bench testing of such networks is notoriously difficult, especially simulating limited radio +/// connectivity between some nodes. +/// To assist testing (both during RH development and for your own networks) +/// RHRouter.cpp has the ability to +/// simulate a number of different small network topologies. Each simulated network supports 4 nodes with +/// addresses 1 to 4. It operates by pretending to not hear RH messages from certain other nodes. +/// You can enable testing with a \#define TEST_NETWORK in RHRouter.h +/// The sample programs rf22_mesh_* rely on this feature. +/// +/// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers +/// (see http://www.hoperf.com) +class RHRouter : public RHReliableDatagram +{ +public: + + /// Defines the structure of the RHRouter message header, used to keep track of end-to-end delivery parameters + typedef struct + { + uint8_t dest; ///< Destination node address + uint8_t source; ///< Originator node address + uint8_t hops; ///< Hops traversed so far + uint8_t id; ///< Originator sequence number + uint8_t flags; ///< Originator flags + // Data follows, Length is implicit in the overall message length + } RoutedMessageHeader; + + /// Defines the structure of a RHRouter message + typedef struct + { + RoutedMessageHeader header; ///< end-to-end delivery header + uint8_t data[RH_ROUTER_MAX_MESSAGE_LEN]; ///< Application payload data + } RoutedMessage; + + /// Values for the possible states for routes + typedef enum + { + Invalid = 0, ///< No valid route is known + Discovering, ///< Discovering a route (not currently used) + Valid ///< Route is valid + } RouteState; + + /// Defines an entry in the routing table + typedef struct + { + uint8_t dest; ///< Destination node address + uint8_t next_hop; ///< Send via this next hop address + uint8_t state; ///< State of this route, one of RouteState + } RoutingTableEntry; + + /// Constructor. + /// \param[in] driver The RadioHead driver to use to transport messages. + /// \param[in] thisAddress The address to assign to this node. Defaults to 0 + RHRouter(RHGenericDriver& driver, uint8_t thisAddress = 0); + + /// Initialises this instance and the radio module connected to it. + /// Overrides the init() function in RH. + /// Sets max_hops to the default of RH_DEFAULT_MAX_HOPS (30) + bool init(); + + /// Sets the max_hops to the given value + /// This controls the maximum number of hops allowed between source and destination nodes + /// Messages that are not delivered by the time their HOPS field exceeds max_hops on a + /// routing node will be dropped and ignored. + /// \param [in] max_hops The new value for max_hops + void setMaxHops(uint8_t max_hops); + + /// Adds a route to the local routing table, or updates it if already present. + /// If there is not enough room the oldest (first) route will be deleted by calling retireOldestRoute(). + /// \param [in] dest The destination node address. RH_BROADCAST_ADDRESS is permitted. + /// \param [in] next_hop The address of the next hop to send messages destined for dest + /// \param [in] state The satte of the route. Defaults to Valid + void addRouteTo(uint8_t dest, uint8_t next_hop, uint8_t state = Valid); + + /// Finds and returns a RoutingTableEntry for the given destination node + /// \param [in] dest The desired destination node address. + /// \return pointer to a RoutingTableEntry for dest + RoutingTableEntry* getRouteTo(uint8_t dest); + + /// Deletes from the local routing table any route for the destination node. + /// \param [in] dest The destination node address + /// \return true if the route was present + bool deleteRouteTo(uint8_t dest); + + /// Deletes the oldest (first) route from the + /// local routing table + void retireOldestRoute(); + + /// Clears all entries from the + /// local routing table + void clearRoutingTable(); + + /// If RH_HAVE_SERIAL is defined, this will print out the contents of the local + /// routing table using Serial + void printRoutingTable(); + + /// Sends a message to the destination node. Initialises the RHRouter message header + /// (the SOURCE address is set to the address of this node, HOPS to 0) and calls + /// route() which looks up in the routing table the next hop to deliver to and sends the + /// message to the next hop. Waits for an acknowledgement from the next hop + /// (but not from the destination node (if that is different). + /// \param [in] buf The application message data + /// \param [in] len Number of octets in the application message data. 0 is permitted + /// \param [in] dest The destination node address + /// \param [in] flags Optional flags for use by subclasses or application layer, + /// delivered end-to-end to the dest address. The receiver can recover the flags with recvFromAck(). + /// \return The result code: + /// - RH_ROUTER_ERROR_NONE Message was routed and delivered to the next hop + /// (not necessarily to the final dest address) + /// - RH_ROUTER_ERROR_NO_ROUTE There was no route for dest in the local routing table + /// - RH_ROUTER_ERROR_UNABLE_TO_DELIVER Not able to deliver to the next hop + /// (usually because it dod not acknowledge due to being off the air or out of range + uint8_t sendtoWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t flags = 0); + + /// Similar to sendtoWait() above, but spoofs the source address. + /// For internal use only during routing + /// \param [in] buf The application message data. + /// \param [in] len Number of octets in the application message data. 0 is permitted. + /// \param [in] dest The destination node address. + /// \param [in] source The (fake) originating node address. + /// \param [in] flags Optional flags for use by subclasses or application layer, + /// delivered end-to-end to the dest address. The receiver can recover the flags with recvFromAck(). + /// \return The result code: + /// - RH_ROUTER_ERROR_NONE Message was routed and deliverd to the next hop + /// (not necessarily to the final dest address) + /// - RH_ROUTER_ERROR_NO_ROUTE There was no route for dest in the local routing table + /// - RH_ROUTER_ERROR_UNABLE_TO_DELIVER Noyt able to deliver to the next hop + /// (usually because it dod not acknowledge due to being off the air or out of range + uint8_t sendtoFromSourceWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t source, uint8_t flags = 0); + + /// Starts the receiver if it is not running already. + /// If there is a valid message available for this node (or RH_BROADCAST_ADDRESS), + /// send an acknowledgement to the last hop + /// address (blocking until this is complete), then copy the application message payload data + /// to buf and return true + /// else return false. + /// If a message is copied, *len is set to the length.. + /// If from is not NULL, the originator SOURCE address is placed in *source. + /// If to is not NULL, the DEST address is placed in *dest. This might be this nodes address or + /// RH_BROADCAST_ADDRESS. + /// This is the preferred function for getting messages addressed to this node. + /// If the message is not a broadcast, acknowledge to the sender before returning. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Available space in buf. Set to the actual number of octets copied. + /// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address + /// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// (not just those addressed to this node). + /// \return true if a valid message was recvived for this node copied to buf + bool recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL); + + /// Starts the receiver if it is not running already. + /// Similar to recvfromAck(), this will block until either a valid message available for this node + /// or the timeout expires. + /// \param[in] buf Location to copy the received message + /// \param[in,out] len Available space in buf. Set to the actual number of octets copied. + /// \param[in] timeout Maximum time to wait in milliseconds + /// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address + /// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address + /// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID + /// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS + /// (not just those addressed to this node). + /// \return true if a valid message was copied to buf + bool recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL); + +protected: + + /// Lets sublasses peek at messages going + /// past before routing or local delivery. + /// Called by recvfromAck() immediately after it gets the message from RHReliableDatagram + /// \param [in] message Pointer to the RHRouter message that was received. + /// \param [in] messageLen Length of message in octets + virtual void peekAtMessage(RoutedMessage* message, uint8_t messageLen); + + /// Finds the next-hop route and sends the message via RHReliableDatagram::sendtoWait(). + /// This is virtual, which lets subclasses override or intercept the route() function. + /// Called by sendtoWait after the message header has been filled in. + /// \param [in] message Pointer to the RHRouter message to be sent. + /// \param [in] messageLen Length of message in octets + virtual uint8_t route(RoutedMessage* message, uint8_t messageLen); + + /// Deletes a specific rout entry from therouting table + /// \param [in] index The 0 based index of the routing table entry to delete + void deleteRoute(uint8_t index); + + /// The last end-to-end sequence number to be used + /// Defaults to 0 + uint8_t _lastE2ESequenceNumber; + + /// The maximum number of hops permitted in routed messages. + /// If a routed message would exceed this number of hops it is dropped and ignored. + uint8_t _max_hops; + +private: + + /// Temporary mesage buffer + static RoutedMessage _tmpMessage; + + /// Local routing table + RoutingTableEntry _routes[RH_ROUTING_TABLE_SIZE]; +}; + +/// @example rf22_router_client.pde +/// @example rf22_router_server1.pde +/// @example rf22_router_server2.pde +/// @example rf22_router_server3.pde +#endif diff --git a/src/RadioHead/RHSPIDriver.cpp b/src/RadioHead/RHSPIDriver.cpp new file mode 100644 index 0000000..47d1092 --- /dev/null +++ b/src/RadioHead/RHSPIDriver.cpp @@ -0,0 +1,91 @@ +// RHSPIDriver.cpp +// +// Copyright (C) 2014 Mike McCauley +// $Id: RHSPIDriver.cpp,v 1.10 2015/12/16 04:55:33 mikem Exp $ + +#include "RHSPIDriver.h" + +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; + _spi.beginTransaction(); + 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); + _spi.endTransaction(); + 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; +} diff --git a/src/RadioHead/RHSPIDriver.h b/src/RadioHead/RHSPIDriver.h new file mode 100644 index 0000000..3873761 --- /dev/null +++ b/src/RadioHead/RHSPIDriver.h @@ -0,0 +1,94 @@ +// RHSPIDriver.h +// Author: Mike McCauley (mikem@airspayce.com) +// Copyright (C) 2014 Mike McCauley +// $Id: RHSPIDriver.h,v 1.10 2015/12/16 04:55:33 mikem Exp $ + +#ifndef RHSPIDriver_h +#define RHSPIDriver_h + +#include "RHGenericDriver.h" +#include "RHHardwareSPI.h" + +// 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 a 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); + +protected: + /// Reference to the RHGenericSPI instance to use to transfer 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/RadioHead/RHSoftwareSPI.cpp b/src/RadioHead/RHSoftwareSPI.cpp new file mode 100644 index 0000000..31b03c4 --- /dev/null +++ b/src/RadioHead/RHSoftwareSPI.cpp @@ -0,0 +1,165 @@ +// SoftwareSPI.cpp +// Author: Chris Lapa (chris@lapa.com.au) +// Copyright (C) 2014 Chris Lapa +// Contributed by Chris Lapa + +#include "RHSoftwareSPI.h" + +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/RadioHead/RHSoftwareSPI.h b/src/RadioHead/RHSoftwareSPI.h new file mode 100644 index 0000000..3b1b10b --- /dev/null +++ b/src/RadioHead/RHSoftwareSPI.h @@ -0,0 +1,89 @@ +// 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 "RHGenericSPI.h" + +///////////////////////////////////////////////////////////////////// +/// \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. +/// +/// \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 _bitOrder; + uint8_t _delayCounts; + uint8_t _clockPolarity; + uint8_t _clockPhase; +}; + +#endif diff --git a/src/RadioHead/RH_NRF24.cpp b/src/RadioHead/RH_NRF24.cpp new file mode 100644 index 0000000..948b96d --- /dev/null +++ b/src/RadioHead/RH_NRF24.cpp @@ -0,0 +1,338 @@ +// NRF24.cpp +// +// Copyright (C) 2012 Mike McCauley +// $Id: RH_NRF24.cpp,v 1.22 2016/04/04 01:40:12 mikem Exp $ + +#include "RH_NRF24.h" + +RH_NRF24::RH_NRF24(uint8_t chipEnablePin, uint8_t slaveSelectPin, RHGenericSPI& spi) + : + RHNRFSPIDriver(slaveSelectPin, spi), + _rxBufValid(0) +{ + _configuration = RH_NRF24_EN_CRC | RH_NRF24_CRCO; // Default: 2 byte CRC enabled + _chipEnablePin = chipEnablePin; +} + +bool RH_NRF24::init() +{ + // Teensy with nRF24 is unreliable at 8MHz: + // so is Arduino with RF73 + _spi.setFrequency(RHGenericSPI::Frequency1MHz); + if (!RHNRFSPIDriver::init()) + return false; + + // Initialise the slave select pin + pinMode(_chipEnablePin, OUTPUT); + digitalWrite(_chipEnablePin, LOW); + + // Clear interrupts + spiWriteRegister(RH_NRF24_REG_07_STATUS, RH_NRF24_RX_DR | RH_NRF24_TX_DS | RH_NRF24_MAX_RT); + // Enable dynamic payload length on all pipes + spiWriteRegister(RH_NRF24_REG_1C_DYNPD, RH_NRF24_DPL_ALL); + // Enable dynamic payload length, disable payload-with-ack, enable noack + spiWriteRegister(RH_NRF24_REG_1D_FEATURE, RH_NRF24_EN_DPL | RH_NRF24_EN_DYN_ACK); + // Test if there is actually a device connected and responding + // CAUTION: RFM73 and version 2.0 silicon may require ACTIVATE + if (spiReadRegister(RH_NRF24_REG_1D_FEATURE) != (RH_NRF24_EN_DPL | RH_NRF24_EN_DYN_ACK)) + { + spiWrite(RH_NRF24_COMMAND_ACTIVATE, 0x73); + // Enable dynamic payload length, disable payload-with-ack, enable noack + spiWriteRegister(RH_NRF24_REG_1D_FEATURE, RH_NRF24_EN_DPL | RH_NRF24_EN_DYN_ACK); + if (spiReadRegister(RH_NRF24_REG_1D_FEATURE) != (RH_NRF24_EN_DPL | RH_NRF24_EN_DYN_ACK)) + return false; + } + + // Make sure we are powered down + setModeIdle(); + + // Flush FIFOs + flushTx(); + flushRx(); + + setChannel(2); // The default, in case it was set by another app without powering down + setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm); + + return true; +} + +// Use the register commands to read and write the registers +uint8_t RH_NRF24::spiReadRegister(uint8_t reg) +{ + return spiRead((reg & RH_NRF24_REGISTER_MASK) | RH_NRF24_COMMAND_R_REGISTER); +} + +uint8_t RH_NRF24::spiWriteRegister(uint8_t reg, uint8_t val) +{ + return spiWrite((reg & RH_NRF24_REGISTER_MASK) | RH_NRF24_COMMAND_W_REGISTER, val); +} + +uint8_t RH_NRF24::spiBurstReadRegister(uint8_t reg, uint8_t* dest, uint8_t len) +{ + return spiBurstRead((reg & RH_NRF24_REGISTER_MASK) | RH_NRF24_COMMAND_R_REGISTER, dest, len); +} + +uint8_t RH_NRF24::spiBurstWriteRegister(uint8_t reg, uint8_t* src, uint8_t len) +{ + return spiBurstWrite((reg & RH_NRF24_REGISTER_MASK) | RH_NRF24_COMMAND_W_REGISTER, src, len); +} + +uint8_t RH_NRF24::statusRead() +{ + // status is a side-effect of NOP, faster than reading reg 07 + return spiCommand(RH_NRF24_COMMAND_NOP); +} + +uint8_t RH_NRF24::flushTx() +{ + return spiCommand(RH_NRF24_COMMAND_FLUSH_TX); +} + +uint8_t RH_NRF24::flushRx() +{ + return spiCommand(RH_NRF24_COMMAND_FLUSH_RX); +} + +bool RH_NRF24::setChannel(uint8_t channel) +{ + spiWriteRegister(RH_NRF24_REG_05_RF_CH, channel & RH_NRF24_RF_CH); + return true; +} + +bool RH_NRF24::setOpMode(uint8_t mode) +{ + _configuration = mode; + return true; +} + +bool RH_NRF24::setNetworkAddress(uint8_t* address, uint8_t len) +{ + if (len < 3 || len > 5) + return false; + + // Set both TX_ADDR and RX_ADDR_P0 for auto-ack with Enhanced shockwave + spiWriteRegister(RH_NRF24_REG_03_SETUP_AW, len-2); // Mapping [3..5] = [1..3] + spiBurstWriteRegister(RH_NRF24_REG_0A_RX_ADDR_P0, address, len); + spiBurstWriteRegister(RH_NRF24_REG_10_TX_ADDR, address, len); + return true; +} + +bool RH_NRF24::setRF(DataRate data_rate, TransmitPower power) +{ + uint8_t value = (power << 1) & RH_NRF24_PWR; + // Ugly mapping of data rates to noncontiguous 2 bits: + if (data_rate == DataRate250kbps) + value |= RH_NRF24_RF_DR_LOW; + else if (data_rate == DataRate2Mbps) + value |= RH_NRF24_RF_DR_HIGH; + // else DataRate1Mbps, 00 + + // RFM73 needs this: + value |= RH_NRF24_LNA_HCURR; + + spiWriteRegister(RH_NRF24_REG_06_RF_SETUP, value); + // If we were using auto-ack, we would have to set the appropriate timeout in reg 4 here + // see NRF24::setRF() + return true; +} + +void RH_NRF24::setModeIdle() +{ + if (_mode != RHModeIdle) + { + spiWriteRegister(RH_NRF24_REG_00_CONFIG, _configuration); + digitalWrite(_chipEnablePin, LOW); + _mode = RHModeIdle; + } +} + +bool RH_NRF24::sleep() +{ + if (_mode != RHModeSleep) + { + spiWriteRegister(RH_NRF24_REG_00_CONFIG, 0); // Power Down mode + digitalWrite(_chipEnablePin, LOW); + _mode = RHModeSleep; + return true; + } + return false; // Already there? +} + +void RH_NRF24::setModeRx() +{ + if (_mode != RHModeRx) + { + spiWriteRegister(RH_NRF24_REG_00_CONFIG, _configuration | RH_NRF24_PWR_UP | RH_NRF24_PRIM_RX); + digitalWrite(_chipEnablePin, HIGH); + _mode = RHModeRx; + } +} + +void RH_NRF24::setModeTx() +{ + if (_mode != RHModeTx) + { + // Its the CE rising edge that puts us into TX mode + // CE staying high makes us go to standby-II when the packet is sent + digitalWrite(_chipEnablePin, LOW); + // Ensure DS is not set + spiWriteRegister(RH_NRF24_REG_07_STATUS, RH_NRF24_TX_DS | RH_NRF24_MAX_RT); + spiWriteRegister(RH_NRF24_REG_00_CONFIG, _configuration | RH_NRF24_PWR_UP); + digitalWrite(_chipEnablePin, HIGH); + _mode = RHModeTx; + } +} + +bool RH_NRF24::send(const uint8_t* data, uint8_t len) +{ + if (len > RH_NRF24_MAX_MESSAGE_LEN) + return false; + // Set up the headers + _buf[0] = _txHeaderTo; + _buf[1] = _txHeaderFrom; + _buf[2] = _txHeaderId; + _buf[3] = _txHeaderFlags; + memcpy(_buf+RH_NRF24_HEADER_LEN, data, len); + spiBurstWrite(RH_NRF24_COMMAND_W_TX_PAYLOAD_NOACK, _buf, len + RH_NRF24_HEADER_LEN); + setModeTx(); + // Radio will return to Standby II mode after transmission is complete + _txGood++; + return true; +} + +bool RH_NRF24::waitPacketSent() +{ + // If we are not currently in transmit mode, there is no packet to wait for + if (_mode != RHModeTx) + return false; + + // Wait for either the Data Sent or Max ReTries flag, signalling the + // end of transmission + // We dont actually use auto-ack, so prob dont expect to see RH_NRF24_MAX_RT + uint8_t status; + while (!((status = statusRead()) & (RH_NRF24_TX_DS | RH_NRF24_MAX_RT))) + YIELD; + + // Must clear RH_NRF24_MAX_RT if it is set, else no further comm + if (status & RH_NRF24_MAX_RT) + flushTx(); + setModeIdle(); + spiWriteRegister(RH_NRF24_REG_07_STATUS, RH_NRF24_TX_DS | RH_NRF24_MAX_RT); + // Return true if data sent, false if MAX_RT + return status & RH_NRF24_TX_DS; +} + +bool RH_NRF24::isSending() +{ + return !(spiReadRegister(RH_NRF24_REG_00_CONFIG) & RH_NRF24_PRIM_RX) && + !(statusRead() & (RH_NRF24_TX_DS | RH_NRF24_MAX_RT)); +} + +bool RH_NRF24::printRegisters() +{ +#ifdef RH_HAVE_SERIAL + // Iterate over register range, but don't process registers not in use. + for (uint8_t r = RH_NRF24_REG_00_CONFIG; r <= RH_NRF24_REG_1D_FEATURE; r++) + { + if ((r <= RH_NRF24_REG_17_FIFO_STATUS) || (r >= RH_NRF24_REG_1C_DYNPD)) + { + Serial.print(r, HEX); + Serial.print(": "); + uint8_t len = 1; + // Address registers are 5 bytes in size + if ( (RH_NRF24_REG_0A_RX_ADDR_P0 == r) + || (RH_NRF24_REG_0B_RX_ADDR_P1 == r) + || (RH_NRF24_REG_10_TX_ADDR == r) ) + { + len = 5; + } + uint8_t buf[5]; + spiBurstReadRegister(r, buf, len); + for (uint8_t j = 0; j < len; ++j) + { + Serial.print(buf[j], HEX); + Serial.print(" "); + } + Serial.println(""); + } + } +#endif + + return true; +} + +// Check whether the latest received message is complete and uncorrupted +void RH_NRF24::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_NRF24::available() +{ + if (!_rxBufValid) + { + if (_mode == RHModeTx) + return false; + setModeRx(); + if (spiReadRegister(RH_NRF24_REG_17_FIFO_STATUS) & RH_NRF24_RX_EMPTY) + return false; + // Manual says that messages > 32 octets should be discarded + uint8_t len = spiRead(RH_NRF24_COMMAND_R_RX_PL_WID); + if (len > 32) + { + flushRx(); + clearRxBuf(); + setModeIdle(); + return false; + } + // Clear read interrupt + spiWriteRegister(RH_NRF24_REG_07_STATUS, RH_NRF24_RX_DR); + // Get the message into the RX buffer, so we can inspect the headers + spiBurstRead(RH_NRF24_COMMAND_R_RX_PAYLOAD, _buf, len); + _bufLen = len; + // 140 microsecs (32 octet payload) + validateRxBuf(); + if (_rxBufValid) + setModeIdle(); // Got one + } + return _rxBufValid; +} + +void RH_NRF24::clearRxBuf() +{ + _rxBufValid = false; + _bufLen = 0; +} + +bool RH_NRF24::recv(uint8_t* buf, uint8_t* len) +{ + if (!available()) + return false; + if (buf && len) + { + // Skip the 4 headers that are at the beginning of the rxBuf + if (*len > _bufLen-RH_NRF24_HEADER_LEN) + *len = _bufLen-RH_NRF24_HEADER_LEN; + memcpy(buf, _buf+RH_NRF24_HEADER_LEN, *len); + } + clearRxBuf(); // This message accepted and cleared + return true; +} + +uint8_t RH_NRF24::maxMessageLength() +{ + return RH_NRF24_MAX_MESSAGE_LEN; +} diff --git a/src/RadioHead/RH_NRF24.h b/src/RadioHead/RH_NRF24.h new file mode 100644 index 0000000..ffdb210 --- /dev/null +++ b/src/RadioHead/RH_NRF24.h @@ -0,0 +1,639 @@ +// RH_NRF24.h +// Author: Mike McCauley +// Copyright (C) 2012 Mike McCauley +// $Id: RH_NRF24.h,v 1.19 2016/07/07 00:02:53 mikem Exp mikem $ +// + +#ifndef RH_NRF24_h +#define RH_NRF24_h + +#include "RHGenericSPI.h" +#include "RHNRFSPIDriver.h" + +// This is the maximum number of bytes that can be carried by the nRF24. +// We use some for headers, keeping fewer for RadioHead messages +#define RH_NRF24_MAX_PAYLOAD_LEN 32 + +// The length of the headers we add. +// The headers are inside the nRF24 payload +#define RH_NRF24_HEADER_LEN 4 + +// This is the maximum RadioHead user message length that can be supported by this library. Limited by +// the supported message lengths in the nRF24 +#define RH_NRF24_MAX_MESSAGE_LEN (RH_NRF24_MAX_PAYLOAD_LEN-RH_NRF24_HEADER_LEN) + +// SPI Command names +#define RH_NRF24_COMMAND_R_REGISTER 0x00 +#define RH_NRF24_COMMAND_W_REGISTER 0x20 +#define RH_NRF24_COMMAND_ACTIVATE 0x50 // only on RFM73 ? +#define RH_NRF24_COMMAND_R_RX_PAYLOAD 0x61 +#define RH_NRF24_COMMAND_W_TX_PAYLOAD 0xa0 +#define RH_NRF24_COMMAND_FLUSH_TX 0xe1 +#define RH_NRF24_COMMAND_FLUSH_RX 0xe2 +#define RH_NRF24_COMMAND_REUSE_TX_PL 0xe3 +#define RH_NRF24_COMMAND_R_RX_PL_WID 0x60 +#define RH_NRF24_COMMAND_W_ACK_PAYLOAD(pipe) (0xa8|(pipe&0x7)) +#define RH_NRF24_COMMAND_W_TX_PAYLOAD_NOACK 0xb0 +#define RH_NRF24_COMMAND_NOP 0xff + +// Register names +#define RH_NRF24_REGISTER_MASK 0x1f +#define RH_NRF24_REG_00_CONFIG 0x00 +#define RH_NRF24_REG_01_EN_AA 0x01 +#define RH_NRF24_REG_02_EN_RXADDR 0x02 +#define RH_NRF24_REG_03_SETUP_AW 0x03 +#define RH_NRF24_REG_04_SETUP_RETR 0x04 +#define RH_NRF24_REG_05_RF_CH 0x05 +#define RH_NRF24_REG_06_RF_SETUP 0x06 +#define RH_NRF24_REG_07_STATUS 0x07 +#define RH_NRF24_REG_08_OBSERVE_TX 0x08 +#define RH_NRF24_REG_09_RPD 0x09 +#define RH_NRF24_REG_0A_RX_ADDR_P0 0x0a +#define RH_NRF24_REG_0B_RX_ADDR_P1 0x0b +#define RH_NRF24_REG_0C_RX_ADDR_P2 0x0c +#define RH_NRF24_REG_0D_RX_ADDR_P3 0x0d +#define RH_NRF24_REG_0E_RX_ADDR_P4 0x0e +#define RH_NRF24_REG_0F_RX_ADDR_P5 0x0f +#define RH_NRF24_REG_10_TX_ADDR 0x10 +#define RH_NRF24_REG_11_RX_PW_P0 0x11 +#define RH_NRF24_REG_12_RX_PW_P1 0x12 +#define RH_NRF24_REG_13_RX_PW_P2 0x13 +#define RH_NRF24_REG_14_RX_PW_P3 0x14 +#define RH_NRF24_REG_15_RX_PW_P4 0x15 +#define RH_NRF24_REG_16_RX_PW_P5 0x16 +#define RH_NRF24_REG_17_FIFO_STATUS 0x17 +#define RH_NRF24_REG_1C_DYNPD 0x1c +#define RH_NRF24_REG_1D_FEATURE 0x1d + +// These register masks etc are named wherever possible +// corresponding to the bit and field names in the nRF24L01 Product Specification +// #define RH_NRF24_REG_00_CONFIG 0x00 +#define RH_NRF24_MASK_RX_DR 0x40 +#define RH_NRF24_MASK_TX_DS 0x20 +#define RH_NRF24_MASK_MAX_RT 0x10 +#define RH_NRF24_EN_CRC 0x08 +#define RH_NRF24_CRCO 0x04 +#define RH_NRF24_PWR_UP 0x02 +#define RH_NRF24_PRIM_RX 0x01 + +// #define RH_NRF24_REG_01_EN_AA 0x01 +#define RH_NRF24_ENAA_P5 0x20 +#define RH_NRF24_ENAA_P4 0x10 +#define RH_NRF24_ENAA_P3 0x08 +#define RH_NRF24_ENAA_P2 0x04 +#define RH_NRF24_ENAA_P1 0x02 +#define RH_NRF24_ENAA_P0 0x01 + +// #define RH_NRF24_REG_02_EN_RXADDR 0x02 +#define RH_NRF24_ERX_P5 0x20 +#define RH_NRF24_ERX_P4 0x10 +#define RH_NRF24_ERX_P3 0x08 +#define RH_NRF24_ERX_P2 0x04 +#define RH_NRF24_ERX_P1 0x02 +#define RH_NRF24_ERX_P0 0x01 + +// #define RH_NRF24_REG_03_SETUP_AW 0x03 +#define RH_NRF24_AW_3_BYTES 0x01 +#define RH_NRF24_AW_4_BYTES 0x02 +#define RH_NRF24_AW_5_BYTES 0x03 + +// #define RH_NRF24_REG_04_SETUP_RETR 0x04 +#define RH_NRF24_ARD 0xf0 +#define RH_NRF24_ARC 0x0f + +// #define RH_NRF24_REG_05_RF_CH 0x05 +#define RH_NRF24_RF_CH 0x7f + +// #define RH_NRF24_REG_06_RF_SETUP 0x06 +#define RH_NRF24_CONT_WAVE 0x80 +#define RH_NRF24_RF_DR_LOW 0x20 +#define RH_NRF24_PLL_LOCK 0x10 +#define RH_NRF24_RF_DR_HIGH 0x08 +#define RH_NRF24_PWR 0x06 +#define RH_NRF24_PWR_m18dBm 0x00 +#define RH_NRF24_PWR_m12dBm 0x02 +#define RH_NRF24_PWR_m6dBm 0x04 +#define RH_NRF24_PWR_0dBm 0x06 +#define RH_NRF24_LNA_HCURR 0x01 + +// #define RH_NRF24_REG_07_STATUS 0x07 +#define RH_NRF24_RX_DR 0x40 +#define RH_NRF24_TX_DS 0x20 +#define RH_NRF24_MAX_RT 0x10 +#define RH_NRF24_RX_P_NO 0x0e +#define RH_NRF24_STATUS_TX_FULL 0x01 + +// #define RH_NRF24_REG_08_OBSERVE_TX 0x08 +#define RH_NRF24_PLOS_CNT 0xf0 +#define RH_NRF24_ARC_CNT 0x0f + +// #define RH_NRF24_REG_09_RPD 0x09 +#define RH_NRF24_RPD 0x01 + +// #define RH_NRF24_REG_17_FIFO_STATUS 0x17 +#define RH_NRF24_TX_REUSE 0x40 +#define RH_NRF24_TX_FULL 0x20 +#define RH_NRF24_TX_EMPTY 0x10 +#define RH_NRF24_RX_FULL 0x02 +#define RH_NRF24_RX_EMPTY 0x01 + +// #define RH_NRF24_REG_1C_DYNPD 0x1c +#define RH_NRF24_DPL_ALL 0x3f +#define RH_NRF24_DPL_P5 0x20 +#define RH_NRF24_DPL_P4 0x10 +#define RH_NRF24_DPL_P3 0x08 +#define RH_NRF24_DPL_P2 0x04 +#define RH_NRF24_DPL_P1 0x02 +#define RH_NRF24_DPL_P0 0x01 + +// #define RH_NRF24_REG_1D_FEATURE 0x1d +#define RH_NRF24_EN_DPL 0x04 +#define RH_NRF24_EN_ACK_PAY 0x02 +#define RH_NRF24_EN_DYN_ACK 0x01 + + +///////////////////////////////////////////////////////////////////// +/// \class RH_NRF24 RH_NRF24.h +/// \brief Send and receive addressed, reliable, acknowledged datagrams by nRF24L01 and compatible transceivers. +/// +/// Supported transceivers include: +/// - Nordic nRF24 based 2.4GHz radio modules, such as nRF24L01 http://www.nordicsemi.com/eng/Products/2.4GHz-RF/nRF24L01 +/// and other compatible transceivers. +/// - nRF24L01p with PA and LNA modules that produce a higher power output similar to this one: +/// http://www.elecfreaks.com/wiki/index.php?title=2.4G_Wireless_nRF24L01p_with_PA_and_LNA +/// - Sparkfun WRL-00691 module with nRF24L01 https://www.sparkfun.com/products/691 +/// or WRL-00705 https://www.sparkfun.com/products/705 etc. +/// - Hope-RF RFM73 http://www.hoperf.com/rf/2.4g_module/RFM73.htm and +/// http://www.anarduino.com/details.jsp?pid=121 +/// and compatible devices (such as BK2423). nRF24L01 and RFM73 can interoperate +/// with each other. +/// +/// This base class provides basic functions for sending and receiving unaddressed, unreliable datagrams +/// of arbitrary length to 28 octets per packet. Use one of the Manager classes to get addressing and +/// acknowledgement reliability, routing, meshes etc. +/// +/// The nRF24L01 (http://www.sparkfun.com/datasheets/Wireless/Nordic/nRF24L01P_Product_Specification_1_0.pdf) +/// is a low-cost 2.4GHz ISM transceiver module. It supports a number of channel frequencies in the 2.4GHz band +/// and a range of data rates. +/// +/// This library provides functions for sending and receiving messages of up to 28 octets on any +/// frequency supported by the nRF24L01, at a selected data rate. +/// +/// Several nRF24L01 modules can be connected to an Arduino, permitting the construction of translators +/// and frequency changers, etc. +/// +/// The nRF24 transceiver is configured to use Enhanced Shockburst with no acknowledgement and no retransmits. +/// TX_ADDR and RX_ADDR_P0 are set to the network address. If you need the low level auto-acknowledgement +/// feature supported by this chip, you can use our original NRF24 library +/// at http://www.airspayce.com/mikem/arduino/NRF24 +/// +/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and +/// data rate, and with identical network addresses. +/// +/// Example Arduino programs are included to show the main modes of use. +/// +/// \par Packet Format +/// +/// All messages sent and received by this class conform to this packet format, as specified by +/// the nRF24L01 product specification: +/// +/// - 1 octets PREAMBLE +/// - 3 to 5 octets NETWORK ADDRESS +/// - 9 bits packet control field +/// - 0 to 32 octets PAYLOAD, consisting of: +/// - 1 octet TO header +/// - 1 octet FROM header +/// - 1 octet ID header +/// - 1 octet FLAGS header +/// - 0 to 28 octets of user message +/// - 2 octets CRC +/// +/// \par Connecting nRF24L01 to Arduino +/// +/// The electrical connection between the nRF24L01 and the Arduino require 3.3V, the 3 x SPI pins (SCK, SDI, SDO), +/// a Chip Enable pin and a Slave Select pin. +/// If you are using the Sparkfun WRL-00691 module, it has a voltage regulator on board and +/// can be should with 5V VCC if possible. +/// The examples below assume the Sparkfun WRL-00691 module +/// +/// Connect the nRF24L01 to most Arduino's like this (Caution, Arduino Mega has different pins for SPI, +/// see below). Use these same connections for Teensy 3.1 (use 3.3V not 5V Vcc). +/// \code +/// Arduino Sparkfun WRL-00691 +/// 5V-----------VCC (3.3V to 7V in) +/// pin D8-----------CE (chip enable in) +/// SS pin D10----------CSN (chip select in) +/// SCK pin D13----------SCK (SPI clock in) +/// MOSI pin D11----------SDI (SPI Data in) +/// MISO pin D12----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND----------GND (ground in) +/// \endcode +/// +/// For an Arduino Leonardo (the SPI pins do not come out on the Digital pins as for normal Arduino, but only +/// appear on the ICSP header) +/// \code +/// Leonardo Sparkfun WRL-00691 +/// 5V-----------VCC (3.3V to 7V in) +/// pin D8-----------CE (chip enable in) +/// SS pin D10----------CSN (chip select in) +/// SCK ICSP pin 3----------SCK (SPI clock in) +/// MOSI ICSP pin 4----------SDI (SPI Data in) +/// MISO ICSP pin 1----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND----------GND (ground in) +/// \endcode +/// and initialise the NRF24 object like this to explicitly set the SS pin +/// NRF24 nrf24(8, 10); +/// +/// For an Arduino Due (the SPI pins do not come out on the Digital pins as for normal Arduino, but only +/// appear on the SPI header). Use the same connections for Yun with 5V or 3.3V. +/// \code +/// Due Sparkfun WRL-00691 +/// 3.3V-----------VCC (3.3V to 7V in) +/// pin D8-----------CE (chip enable in) +/// SS pin D10----------CSN (chip select in) +/// SCK SPI pin 3----------SCK (SPI clock in) +/// MOSI SPI pin 4----------SDI (SPI Data in) +/// MISO SPI pin 1----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND----------GND (ground in) +/// \endcode +/// and initialise the NRF24 object with the default constructor +/// NRF24 nrf24; +/// +/// For an Arduino Mega: +/// \code +/// Mega Sparkfun WRL-00691 +/// 5V-----------VCC (3.3V to 7V in) +/// pin D8-----------CE (chip enable in) +/// SS pin D53----------CSN (chip select in) +/// SCK pin D52----------SCK (SPI clock in) +/// MOSI pin D51----------SDI (SPI Data in) +/// MISO pin D50----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND----------GND (ground in) +/// \endcode +/// and you can then use the constructor RH_NRF24(8, 53). +/// +/// For an Itead Studio IBoard Pro http://imall.iteadstudio.com/iboard-pro.html, connected by hardware SPI to the +/// ITDB02 Parallel LCD Module Interface pins: +/// \code +/// IBoard Signal=ITDB02 pin Sparkfun WRL-00691 +/// 3.3V 37-----------VCC (3.3V to 7V in) +/// D2 28-----------CE (chip enable in) +/// D29 27----------CSN (chip select in) +/// SCK D52 32----------SCK (SPI clock in) +/// MOSI D51 34----------SDI (SPI Data in) +/// MISO D50 30----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND 39----------GND (ground in) +/// \endcode +/// And initialise like this: +/// \code +/// RH_NRF24 nrf24(2, 29); +/// \endcode +/// +/// For an Itead Studio IBoard Pro http://imall.iteadstudio.com/iboard-pro.html, connected by software SPI to the +/// nRF24L01+ Module Interface pins. CAUTION: performance of software SPI is very slow and is not +/// compatible with other modules running hardware SPI. +/// \code +/// IBoard Signal=Module pin Sparkfun WRL-00691 +/// 3.3V 2----------VCC (3.3V to 7V in) +/// D12 3-----------CE (chip enable in) +/// D29 4----------CSN (chip select in) +/// D9 5----------SCK (SPI clock in) +/// D8 6----------SDI (SPI Data in) +/// D7 7----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND 1----------GND (ground in) +/// \endcode +/// And initialise like this: +/// \code +/// #include +/// #include +/// #include +/// Singleton instance of the radio driver +/// RHSoftwareSPI spi; +/// RH_NRF24 nrf24(12, 11, spi); +/// void setup() { +/// spi.setPins(7, 8, 9); +/// .... +/// \endcode +/// +/// +/// For Raspberry Pi with Sparkfun WRL-00691 +/// \code +/// Raspberry Pi P1 pin Sparkfun WRL-00691 +/// 5V 2-----------VCC (3.3V to 7V in) +/// GPIO25 22-----------CE (chip enable in) +/// GPIO8 24----------CSN (chip select in) +/// GPIO11 23----------SCK (SPI clock in) +/// GPIO10 19----------SDI (SPI Data in) +/// GPIO9 21----------SDO (SPI data out) +/// IRQ (Interrupt output, not connected) +/// GND 6----------GND (ground in) +/// \endcode +/// and initialise like this: +/// \code +/// RH_NRF24 nrf24(RPI_V2_GPIO_P1_22, RPI_V2_GPIO_P1_24); +/// \endcode +/// See the example program and Makefile in examples/raspi. Requires bcm2835 library to be previously installed. +/// \code +/// cd examples/raspi +/// make +/// sudo ./RasPiRH +/// \endcode +/// \code +/// +/// You can override the default settings for the CSN and CE pins +/// in the NRF24() constructor if you wish to connect the slave select CSN to other than the normal one for your +/// Arduino (D10 for Diecimila, Uno etc and D53 for Mega) +/// +/// 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: this module has not been proved to work with Leonardo, at least without level +/// shifters between the nRF24 and the Leonardo. Tests seem to indicate that such level shifters would be required +/// with Leonardo to make it work. +/// +/// It is possible to have 2 radios conected to one arduino, provided each radio has its own +/// CSN and CE line (SCK, SDI and SDO are common to both radios) +/// +/// \par SPI Interface +/// +/// You can interface to nRF24L01 with with hardware or software SPI. Use of software SPI with the RHSoftwareSPI +/// class depends on a fast enough processor and digitalOut() functions to achieve a high enough SPI bus frequency. +/// If you observe reliable behaviour with the default hardware SPI RHHardwareSPI, but unreliable behaviour +/// with Software SPI RHSoftwareSPI, it may be due to slow CPU performance. +/// +/// Initialisation example with hardware SPI +/// \code +/// #include +/// RH_NRF24 driver; +/// RHReliableDatagram manager(driver, CLIENT_ADDRESS); +/// \endcode +/// +/// Initialisation example with software SPI +/// \code +/// #include +/// #include +/// RHSoftwareSPI spi; +/// RH_NRF24 driver(8, 10, spi); +/// RHReliableDatagram manager(driver, CLIENT_ADDRESS); +/// \endcode +/// +/// \par Example programs +/// +/// Several example programs are provided. +/// +/// \par Radio Performance +/// +/// Frequency accuracy may be debatable. For nominal frequency of 2401.000 MHz (ie channel 1), +/// my Yaesu VR-5000 receiver indicated the center frequency for my test radios +/// was 2401.121 MHz. Its not clear to me if the Yaesu +/// is the source of the error, but I tend to believe it, which would make the nRF24l01 frequency out by 121kHz. +/// +/// The measured power output for a nRF24L01p with PA and LNA set to 0dBm output is about 18dBm. +/// +/// \par Radio operating strategy and defaults +/// +/// The radio is enabled all the time, and switched between TX and RX modes depending on +/// whether there is any data to send. Sending data sets the radio to TX mode. +/// After data is sent, the radio automatically returns to Standby II mode. Calling waitAvailable() or +/// waitAvailableTimeout() starts the radio in RX mode. +/// +/// The radio is configured by default to Channel 2, 2Mbps, 0dBm power, 5 bytes address, payload width 1, CRC enabled +/// 2 byte CRC, No Auto-Ack mode. Enhanced shockburst is used. +/// TX and P0 are set to the Network address. Node addresses and decoding are handled with the RH_NRF24 module. +/// +/// \par Memory +/// +/// Memory usage of this class is minimal. The compiled client and server sketches are about 6000 bytes on Arduino. +/// The reliable client and server sketches compile to about 8500 bytes on Arduino. +/// RAM requirements are minimal. +/// +class RH_NRF24 : public RHNRFSPIDriver +{ +public: + + /// \brief Defines convenient values for setting data rates in setRF() + typedef enum + { + DataRate1Mbps = 0, ///< 1 Mbps + DataRate2Mbps, ///< 2 Mbps + DataRate250kbps ///< 250 kbps + } DataRate; + + /// \brief Convenient values for setting transmitter power in setRF() + /// These are designed to agree with the values for RF_PWR in RH_NRF24_REG_06_RF_SETUP + /// To be passed to setRF(); + typedef enum + { + // Add 20dBm for nRF24L01p with PA and LNA modules + TransmitPowerm18dBm = 0, ///< On nRF24, -18 dBm + TransmitPowerm12dBm, ///< On nRF24, -12 dBm + TransmitPowerm6dBm, ///< On nRF24, -6 dBm + TransmitPower0dBm, ///< On nRF24, 0 dBm + // Sigh, different power levels for the same bit patterns on RFM73: + // On RFM73P-S, there is a Tx power amp, so expect higher power levels, up to 20dBm. Alas + // there is no clear documentation on the power for different settings :-( + RFM73TransmitPowerm10dBm = 0, ///< On RFM73, -10 dBm + RFM73TransmitPowerm5dBm, ///< On RFM73, -5 dBm + RFM73TransmitPowerm0dBm, ///< On RFM73, 0 dBm + RFM73TransmitPower5dBm ///< On RFM73, 5 dBm. 20dBm on RFM73P-S2 ? + + } TransmitPower; + + /// Constructor. You can have multiple instances, but each instance must have its own + /// chip enable and slave select pin. + /// After constructing, you must call init() to initialise the interface + /// and the radio module + /// \param[in] chipEnablePin the Arduino pin to use to enable the chip for transmit/receive + /// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the NRF24 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] spi Pointer to the SPI interface object to use. + /// Defaults to the standard Arduino hardware SPI interface + RH_NRF24(uint8_t chipEnablePin = 8, uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi); + + /// Initialises this instance and the radio module connected to it. + /// The following steps are taken:g + /// - Set the chip enable and chip select pins to output LOW, HIGH respectively. + /// - Initialise the SPI output pins + /// - Initialise the SPI interface library to 8MHz (Hint, if you want to lower + /// the SPI frequency (perhaps where you have other SPI shields, low voltages etc), + /// call SPI.setClockDivider() after init()). + /// -Flush the receiver and transmitter buffers + /// - Set the radio to receive with powerUpRx(); + /// \return true if everything was successful + bool init(); + + /// Reads a single register from the NRF24 + /// \param[in] reg Register number, one of RH_NRF24_REG_* + /// \return The value of the register + uint8_t spiReadRegister(uint8_t reg); + + /// Writes a single byte to the NRF24, and at the same time reads the current STATUS register + /// \param[in] reg Register number, one of RH_NRF24_REG_* + /// \param[in] val The value to write + /// \return the current STATUS (read while the command is sent) + uint8_t spiWriteRegister(uint8_t reg, uint8_t val); + + /// Reads a number of consecutive registers from the NRF24 using burst read mode + /// \param[in] reg Register number of the first register, one of RH_NRF24_REG_* + /// \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 the current STATUS (read while the command is sent) + uint8_t spiBurstReadRegister(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, one of RH_NRF24_REG_* + /// \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 the current STATUS (read while the command is sent) + uint8_t spiBurstWriteRegister(uint8_t reg, uint8_t* src, uint8_t len); + + /// Reads and returns the device status register NRF24_REG_02_DEVICE_STATUS + /// \return The value of the device status register + uint8_t statusRead(); + + /// Sets the transmit and receive channel number. + /// The frequency used is (2400 + channel) MHz + /// \return true on success + bool setChannel(uint8_t channel); + + /// Sets the chip configuration that will be used to set + /// the NRF24 NRF24_REG_00_CONFIG register when in Idle mode. This allows you to change some + /// chip configuration for compatibility with libraries other than this one. + /// You should not normally need to call this. + /// Defaults to NRF24_EN_CRC| RH_NRF24_CRCO, which is the standard configuration for this library + /// (2 byte CRC enabled). + /// \param[in] mode The chip configuration to be used whe in Idle mode. + /// \return true on success + bool setOpMode(uint8_t mode); + + /// Sets the Network address. + /// Only nodes with the same network address can communicate with each other. You + /// can set different network addresses in different sets of nodes to isolate them from each other. + /// Internally, this sets the nRF24 TX_ADDR and RX_ADDR_P0 to be the given network address. + /// The default network address is 0xE7E7E7E7E7 + /// \param[in] address The new network address. Must match the network address of any receiving node(s). + /// \param[in] len Number of bytes of address to set (3 to 5). + /// \return true on success, false if len is not in the range 3-5 inclusive. + bool setNetworkAddress(uint8_t* address, uint8_t len); + + /// Sets the data rate and transmitter power to use. Note that the nRF24 and the RFM73 have different + /// available power levels, and for convenience, 2 different sets of values are available in the + /// RH_NRF24::TransmitPower enum. The ones with the RFM73 only have meaning on the RFM73 and compatible + /// devces. The others are for the nRF24. + /// \param [in] data_rate The data rate to use for all packets transmitted and received. One of RH_NRF24::DataRate. + /// \param [in] power Transmitter power. One of RH_NRF24::TransmitPower. + /// \return true on success + bool setRF(DataRate data_rate, TransmitPower power); + + /// Sets the radio in power down mode, with the configuration set to the + /// last value from setOpMode(). + /// Sets chip enable to LOW. + void setModeIdle(); + + /// Sets the radio in RX mode. + /// Sets chip enable to HIGH to enable the chip in RX mode. + void setModeRx(); + + /// Sets the radio in TX mode. + /// Pulses the chip enable LOW then HIGH to enable the chip in TX mode. + void setModeTx(); + + /// Sends data to the address set by setTransmitAddress() + /// Sets the radio to TX mode + /// \param [in] data Data bytes to send. + /// \param [in] len Number of data bytes to send + /// \return true on success (which does not necessarily mean the receiver got the message, only that the message was + /// successfully transmitted). + bool send(const uint8_t* data, uint8_t len); + + /// Blocks until the current message (if any) + /// has been transmitted + /// \return true on success, false if the chip is not in transmit mode or other transmit failure + virtual bool waitPacketSent(); + + /// Indicates if the chip is in transmit mode and + /// there is a packet currently being transmitted + /// \return true if the chip is in transmit mode and there is a transmission in progress + bool isSending(); + + /// 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(); + + /// Checks whether a received message is available. + /// This can be called multiple times in a timeout loop + /// \return true if a complete, valid message has been received and is able to be retrieved by + /// recv() + 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 + bool recv(uint8_t* buf, uint8_t* len); + + /// The maximum message length supported by this driver + /// \return The maximum message length supported by this driver + uint8_t maxMessageLength(); + + /// Sets the radio into Power Down mode. + /// If successful, the radio will stay in Power Down 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(); + +protected: + /// Flush the TX FIFOs + /// \return the value of the device status register + uint8_t flushTx(); + + /// Flush the RX FIFOs + /// \return the value of the device status register + uint8_t flushRx(); + + /// Examine the receive buffer to determine whether the message is for this node + void validateRxBuf(); + + /// Clear our local receive buffer + void clearRxBuf(); + +private: + /// This idle mode chip configuration + uint8_t _configuration; + + /// the number of the chip enable pin + uint8_t _chipEnablePin; + + /// Number of octets in the buffer + uint8_t _bufLen; + + /// The receiver/transmitter buffer + uint8_t _buf[RH_NRF24_MAX_PAYLOAD_LEN]; + + /// True when there is a valid message in the buffer + bool _rxBufValid; +}; + +/// @example nrf24_client.pde +/// @example nrf24_server.pde +/// @example nrf24_reliable_datagram_client.pde +/// @example nrf24_reliable_datagram_server.pde +/// @example RasPiRH.cpp + +#endif diff --git a/src/RadioHead/RadioHead.h b/src/RadioHead/RadioHead.h new file mode 100644 index 0000000..8e53d59 --- /dev/null +++ b/src/RadioHead/RadioHead.h @@ -0,0 +1,975 @@ +// RadioHead.h +// Author: Mike McCauley (mikem@airspayce.com) DO NOT CONTACT THE AUTHOR DIRECTLY +// Copyright (C) 2014 Mike McCauley +// $Id: RadioHead.h,v 1.56 2016/07/07 00:02:53 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.61.zip +/// You can find the latest version 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 +/// +/// \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 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 +/// this 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. +/// +/// - 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_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. +/// +/// 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. +/// +/// \par Managers +/// +/// The following Mangers are provided: +/// +/// - RHDatagram +/// Addressed, unreliable variable length messages, with optional broadcast facilities. +/// +/// - RHReliableDatagram +/// Addressed, reliable, retransmitted, acknowledged variable length messages. +/// +/// - RHRouter +/// Multi-hop delivery from source node to destination node via 0 or more intermediate nodes, with manual routing. +/// +/// - RHMesh +/// Multi-hop delivery with automatic route discovery and rediscovery. +/// +/// Any Manager may be used with any Driver. +/// +/// \par Platforms +/// +/// A range of platforms is supported: +/// +/// - Arduino and the Arduino IDE (version 1.0 to 1.6.5 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 +/// Examples serial_reliable_datagram_* and ask_* are shown to work. +/// CAUTION: The GHz radio included in the ESP8266 is +/// not yet supported. +/// - etc. +/// +/// - 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 +/// bt 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.0.5 with the arduino-tiny support from https://code.google.com/p/arduino-tiny/ +/// and Digispark built with Arduino 1.6.5. +/// (Caution: these are very small processors and not all RadioHead features may be available, depending on memory requirements) +/// +/// - nRF51 compatible Arm chips such as nRF51822 with Arduino 1.6.4 and later using the procedures +/// in http://redbearlab.com/getting-started-nrf51822/ +/// +/// - 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 datagram +/// protocol over a serial line. +/// +/// Other platforms are partially supported, such as Generic AVR 8 bit processors, MSP430. +/// We welcome contributions that will expand the range of supported platforms. +/// +/// 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 stated 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. +/// +/// \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 +/// +/// \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-2016 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 http://www.gnu.org/copyleft/gpl.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. Contact info@airspayce.com for details (do not use this address for anything other than +/// commercial license enquiries. For all other queries, using the RadioHead mailing list). +/// +/// \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. +/// 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. +/// +/// \author Mike McCauley. DO NOT CONTACT THE AUTHOR DIRECTLY. USE THE MAILING LIST GIVEN ABOVE + +#ifndef RadioHead_h +#define RadioHead_h + +#include "Arduino.h" + +// Official version numbers are maintained automatically by Makefile: +#define RH_VERSION_MAJOR 1 +#define RH_VERSION_MINOR 61 + +// 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 + +//////////////////////////////////////////////////// +// Select platform automatically, if possible +#ifndef RH_PLATFORM + #if (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(ESP8266) + #define RH_PLATFORM RH_PLATFORM_ESP8266 + #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 + +#if 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__) + #define RH_PLATFORM_ATTINY +#endif + +//////////////////////////////////////////////////// +// Platform specific headers: +#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) + #if (ARDUINO >= 100) + #include + #else + #include + #endif + #ifdef RH_PLATFORM_ATTINY + #warning Arduino TinyCore does not support hardware SPI. Use software SPI instead. + #else + #include + #define RH_HAVE_HARDWARE_SPI + #define RH_HAVE_SERIAL + #endif + +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) // ESP8266 processor on Arduino IDE + #include + #include + #define RH_HAVE_HARDWARE_SPI + #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_SERIAL + +#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 + #include + #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_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(RH_PLATFORM_ATTINY)) || (TEENSYDUINO && defined(__MK20DX128__)) + #define YIELD yield(); +#elif (RH_PLATFORM == RH_PLATFORM_ESP8266) +// ESP8266 also hash it +#if defined(MONGOOSE_OS) +extern void yield(void); +#endif + #define YIELD yield(); +#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 + +// 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 availble 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 + +#endif diff --git a/src/clockMsg.h b/src/clockMsg.h new file mode 100644 index 0000000..817ddad --- /dev/null +++ b/src/clockMsg.h @@ -0,0 +1,14 @@ +#ifndef clockMsg_h_included +#define clockMsg_h_included + +struct clockMsg_s { + uint8_t msgType; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint16_t millisecond; +}; +#define msgType_Clock 'c' + +#endif diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..57eec98 --- /dev/null +++ b/src/config.h @@ -0,0 +1,11 @@ +#ifndef config_h_included +#define config_h_included + +#define PIN_NRF24_CSN 2 +#define PIN_NRF24_CE 15 + +// communication protocol definitions +#define nRF_Channel 1 +#define THIS_ADRESS 0 // uint8_t address of this node + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..74a5532 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2014-2017 Cesanta Software Limited + * All rights reserved + */ + +#include +#include +#include +#include +#include "RadioHead/RH_NRF24.h" +#include "RadioHead/RHDatagram.h" +// #include +#include +#include +#include + +#include "common/cs_dbg.h" +#include "mgos.h" +#include "mgos_app.h" +#include "mgos_timers.h" +#include "mgos_shadow.h" +#include "config.h" +#include "clockMsg.h" + +static Adafruit_SSD1306 *display = nullptr; + +#define LOGO16_GLCD_HEIGHT 16 +#define LOGO16_GLCD_WIDTH 16 +static const unsigned char PROGMEM logo16_glcd_bmp[] = +{ 0x00, 0xc0, // B00000000, B11000000, + 0x01, 0xc0, // B00000001, B11000000, + 0x01, 0xc0, // B00000001, B11000000, + 0x03, 0xe0, // B00000011, B11100000, + 0xf3, 0xe0, // B11110011, B11100000, + 0xfe, 0xf8, // B11111110, B11111000, + 0x7e, 0xff, // B01111110, B11111111, + 0x33, 0x9f, // B00110011, B10011111, + 0x1f, 0xfc, // B00011111, B11111100, + 0x0d, 0x70, // B00001101, B01110000, + 0x1b, 0xa0, // B00011011, B10100000, + 0x3f, 0xe0, // B00111111, B11100000, + 0x3f, 0xf0, // B00111111, B11110000, + 0x7c, 0xf0, // B01111100, B11110000, + 0x70, 0x70, // B01110000, B01110000, + 0x00, 0x30 // B00000000, B00110000 +}; + +#if defined(CHECK_DISPLAY_RESOLUTION) +#if (SSD1306_LCDHEIGHT != 32) +#error("Height incorrect, please fix Adafruit_SSD1306.h!"); +#endif +#endif + +// Singleton instance of the radio driver +RH_NRF24 nrf24(PIN_NRF24_CSN, PIN_NRF24_CE); + +// Address RH_BROADCAST_ADDRESS can be used for broadcasts as destination +RHDatagram Datagram(nrf24, THIS_ADRESS); + + + +static void setSmallTextSize(void) { display->setFont(&TomThumb); } +static uint8_t getSmallTextHeight() { return TomThumb.yAdvance; } +//static uint8_t getSmallTextCharsPerLine() { return 39; } + +static void setNormalTextSize(void) { display->setFont(&Org_01); } +static uint8_t getNormalTextHeight() { return Org_01.yAdvance; } +//static uint8_t getNormalTextCharsPerLine() { return 24; } + +static void setLargeTextSize(void) { display->setFont(&FreeMonoBold9pt7b); } +//static uint8_t getLargeTextHeight() { return FreeMonoBold9pt7b.yAdvance; } +//static uint8_t getLargeTextCharsPerLine() { return 12; } +static void timer_cb(void *arg); +static void fastclockRF_receive_cb(void *arg); +static void fastclockRF_send_cb(void *arg); +static void initFastclockRF_cb(void *arg); + +static void displayBegin(boolean reset) { + //display->begin(SSD1306_SWITCHCAPVCC, 0x3C, reset /* reset */); + display->begin(); (void) reset; +} + +static void initDisplay_cb(void *arg) { + static int step=0; + (void) arg; + + display = new Adafruit_SSD1306(16 /* RST GPIO */, Adafruit_SSD1306::RES_128_32); + + switch (step) { + case 0: + displayBegin(true); + display->display(); + LOG(LL_INFO, ("*** Display initialized, height=%d, width=%d", display->height(), display->width())); + break; + case 1: + displayBegin(false); + display->clearDisplay(); + display->drawPixel(10, 10, WHITE); + display->drawPixel(12, 12, WHITE); + display->drawPixel(14, 14, WHITE); + display->drawPixel(16, 16, WHITE); + display->display(); + LOG(LL_INFO, ("*** Pixel drawn, height=%d, width=%d", display->height(), display->width())); + break; + case 2: + displayBegin(false); + display->drawLine(0, display->height()-1, display->width(), display->height()/2, WHITE); + display->drawCircle(display->width()-20, display->height()-10, 10, WHITE); + display->display(); + LOG(LL_INFO, ("*** Line & Circle drawn")); + break; + case 3: + displayBegin(false); + display->drawBitmap(60, 0, logo16_glcd_bmp, 16, 16, 1); + display->display(); + LOG(LL_INFO, ("*** Icon/Bitmap drawn")); + break; + default: // do nothing + break; + } + ++step; + if (step <= 3) mgos_set_timer(600 /* ms */, false /* repeat */, initDisplay_cb, NULL); +} + +void setup(void) { + LOG(LL_INFO, ("*** Setup started")); + LOG(LL_INFO, ("*** Setting timer")); + mgos_set_timer(2000 /* ms */, false /* repeat */, initDisplay_cb, NULL); + mgos_set_timer(5000 /* ms */, true /* repeat */, timer_cb, NULL); + mgos_set_timer(7000 /* ms */, false /* repeat */, initFastclockRF_cb, NULL); + LOG(LL_INFO, ("*** Setup done")); +} + +static void show_dashboard(int hour, int minute) { + display->clearDisplay(); + display->setTextColor(WHITE, BLACK); + + // ***** clock name ***** + setNormalTextSize(); + display->setCursor(0, getNormalTextHeight()-1); + display->printf("N-RE"); + + // ****** speed ***** + setNormalTextSize(); + display->setCursor(55, 2*getNormalTextHeight()-1); + display->printf("1:3,5"); + + // ***** time ***** + setLargeTextSize(); + display->setCursor(0, display->height()-1); + display->printf("%02d:%02d", hour, minute); + + // ***** halt/go ***** + setSmallTextSize(); + display->setTextColor(BLACK, WHITE); + display->fillRect(55, display->height() - 2*getSmallTextHeight()-3, 5*4+2, getSmallTextHeight(), WHITE); + display->setCursor(57, display->height() - getSmallTextHeight()-3); display->printf("HALT"); + display->setTextColor(WHITE, BLACK); + + // **** weekday ***** + setSmallTextSize(); + display->setCursor(60, display->height()); display->printf("Mo"); + + // ***** # of clients ***** + setSmallTextSize(); + display->setCursor(55, getNormalTextHeight()); display->printf("7 -->"); + + // ***** client list ***** + display->writeFastVLine(79, 0, display->height(), WHITE); + for (int i=0; i<5; ++i) { + display->setTextColor(BLACK, WHITE); + display->fillRect(81, i * getSmallTextHeight(), 3*3+1, getSmallTextHeight(), WHITE); + display->setCursor(82, (i+1) * getSmallTextHeight()); + display->printf("%02d", i); + display->setTextColor(WHITE, BLACK); + display->setCursor(82+3*3+1+1, (i+1) * getSmallTextHeight()); + display->printf("Client-%d", i); + } + display->display(); +} + + +static long lastSentTimeTick = 0; +static long msPerModelSecond = 1000; // 500 = real time +static struct clock_s { + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint16_t millisecond; +} fastclock; + +static void incrementClockByMilliseconds(int amount) { + fastclock.millisecond += amount; + if (fastclock.millisecond >= 1000) { + fastclock.millisecond -= 1000; + fastclock.second++; + if (fastclock.second >= 60) { + fastclock.second -= 60; + fastclock.minute++; + if (fastclock.minute >= 60) { + fastclock.minute -= 60; + fastclock.hour++; + if (fastclock.hour >= 24) { + fastclock.hour -=24; + fastclock.day++; + if (fastclock.day >= 7) { + fastclock.day -= 7; + } + } + } + } + } + LOG(LL_INFO, ("*** new clock: %02d:%02d:%02d.%03d day %d", fastclock.hour, fastclock.minute, fastclock.second, fastclock.millisecond, fastclock.day)); +} + +static void fastclockRF_receive_cb(void *arg) { + (void) arg; + + // check for incoming messages + if (Datagram.available()) + { + // Should be a message for us now + uint8_t buf[RH_MAX_MESSAGE_LEN]; + uint8_t len = sizeof(buf); + uint8_t from, to, id, flags; + + if (Datagram.recvfrom(buf, &len, &from, &to, &id, &flags)) { + LOG(LL_INFO, ("got request: %s", (char*)buf)); + } + else + { + LOG(LL_INFO, ("*** Datagram.recvfrom failed")); + } + } +} + +static struct clockMsg_s clockMsg; + +static void fastclockRF_send_cb(void *arg) { + (void) arg; + + clockMsg.msgType = msgType_Clock; + clockMsg.hour = fastclock.hour; + clockMsg.minute = fastclock.minute; + clockMsg.second = fastclock.second; + + // send clock info as a broadcast message + + LOG(LL_INFO, ("*** Sending clock packet (broadcast)")); + if (Datagram.sendto((uint8_t *) &clockMsg, sizeof(clockMsg), RH_BROADCAST_ADDRESS)) { + LOG(LL_INFO, ("%02d:%02d:%02d - Sent new clock tick", fastclock.hour, fastclock.minute, fastclock.second)); + } +} + +static void timeTick_cb(void *arg) { + (void) arg; + long newTimeTick = millis(); + int fastclockTimeAdvance = (newTimeTick - lastSentTimeTick) / msPerModelSecond; + + incrementClockByMilliseconds(fastclockTimeAdvance); + lastSentTimeTick += fastclockTimeAdvance * msPerModelSecond; +} + +static void initFastclockRF_cb(void *arg) { + (void) arg; + LOG(LL_INFO, ("*** Setting up RF")); + lastSentTimeTick = millis(); + fastclock.day = 0; + fastclock.hour = 0; + fastclock.minute = 0; + fastclock.second = 0; + fastclock.millisecond = 0; + mgos_set_timer(200 /* ms */, true /* repeat */, fastclockRF_receive_cb, NULL); + mgos_set_timer(500 /* ms */, true /* repeat */, timeTick_cb, NULL); + mgos_set_timer(4000 /* ms */, true /* repeat */, fastclockRF_send_cb, NULL); +} + +static void timer_cb(void *arg) { + // static int hour = 0, minute = 0; + show_dashboard(fastclock.hour, fastclock.minute); + LOG(LL_INFO, ("%02d:%02d", fastclock.hour, fastclock.minute)); + /* + minute++; + if (minute >= 60) { hour++; minute=0; } + if (hour >= 24) { hour=0; } + */ + (void) arg; +} + +#if 0 +void loop(void) { + /* do not use loop(), use timers instead; otherwise the watchdog timer reboots your device */ +} +#endif diff --git a/tools/setup_dash.sh b/tools/setup_dash.sh new file mode 100755 index 0000000..34ddd0e --- /dev/null +++ b/tools/setup_dash.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +defaultDEVICE_ID=fad7795eff2d760d27b7bc59 +defaultTOKEN=90a408ad822cce2e194a7cdc +defaultWIFI_SSID=Pinguin +defaultWIFI_PWD=PaulchenAufmKlo34 + +read -p 'Device ID [default=$defaultDEVICE_ID]: ' DEVICE_ID +: ${DEVICE_ID:=$defaultDEVICE_ID} +echo $DEVICE_ID +read -p 'Mongoose Dash Token: [default=$defaultTOKEN]' TOKEN +: ${TOKEN:=$defaultTOKEN} +echo $TOKEN +read -p 'Wifi SSID [default=$defaultSSID]: ' WIFI_SSID +: ${WIFI_SSID:=$defaultWIFI_SSID} +echo $WIFI_SSID +read -sp 'Wifi Password: ' WIFI_PWD +: ${WIFI_PWD:=$defaultWIFI_PWD} +echo "*****" +echo "" +echo "Setting device id to $DEVICE_ID" +mos config-set --no-reboot device.id="$DEVICE_ID" +echo "Setting token to $TOKEN" +mos config-set --no-reboot dash.token="$TOKEN" +echo "Enabling DASH" +mos config-set --no-reboot dash.enable=true +echo "Setting ACLs" +mos config-set --no-reboot conf_acl=wifi.*,device.*,dash.enable +echo "Setting WIFI" +mos wifi "$WIFI_SSID" "$WIFI_PWD" +echo "Making changes permanent" +mos call FS.Rename '{"src": "conf9.json", "dst": "conf5.json"}' +echo "Rebooting device" +mos call Sys.Reboot +