mirror of
https://github.com/meshtastic/firmware.git
synced 2025-06-24 05:53:10 +00:00
Strip out all the parts of Radiohead (most of it) that we don't need
This commit is contained in:
parent
5c379c4a98
commit
fd17193d5e
@ -30,7 +30,7 @@ board_build.partitions = partition-table.csv
|
|||||||
|
|
||||||
; note: we add src to our include search path so that lmic_project_config can override
|
; note: we add src to our include search path so that lmic_project_config can override
|
||||||
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
|
||||||
build_flags = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${common.hw_version}
|
build_flags = -Wall -Wextra -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os -Wl,-Map,.pio/build/output.map -DAXP_DEBUG_PORT=Serial -DHW_VERSION_${common.hw_version}
|
||||||
|
|
||||||
; not needed included in ttgo-t-beam board file
|
; not needed included in ttgo-t-beam board file
|
||||||
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
|
; also to use PSRAM https://docs.platformio.org/en/latest/platforms/espressif32.html#external-ram-psram
|
||||||
@ -65,7 +65,6 @@ debug_tool = jlink
|
|||||||
debug_init_break = tbreak setup
|
debug_init_break = tbreak setup
|
||||||
|
|
||||||
lib_deps =
|
lib_deps =
|
||||||
https://github.com/meshtastic/RadioHead.git#d32df52f8c80eb2525d3774adc1fe36bb4c32952
|
|
||||||
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
https://github.com/meshtastic/esp8266-oled-ssd1306.git ; ESP8266_SSD1306
|
||||||
SPI
|
SPI
|
||||||
; 1260 ; OneButton - not used yet
|
; 1260 ; OneButton - not used yet
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "RadioInterface.h"
|
#include "RadioInterface.h"
|
||||||
#include "mesh.pb.h"
|
#include "mesh.pb.h"
|
||||||
#include <RHMesh.h>
|
|
||||||
#include <RH_RF95.h>
|
#include <RH_RF95.h>
|
||||||
|
|
||||||
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
|
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
#include "RH_RF95.h"
|
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include <RHMesh.h>
|
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@
|
|||||||
#include "PointerQueue.h"
|
#include "PointerQueue.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "mesh.pb.h"
|
#include "mesh.pb.h"
|
||||||
#include <RHMesh.h>
|
|
||||||
|
|
||||||
// US channel settings
|
// US channel settings
|
||||||
#define CH0_US 903.08f // MHz
|
#define CH0_US 903.08f // MHz
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
/// Error codes for critical error
|
/// Error codes for critical error
|
||||||
enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio };
|
enum CriticalErrorCode { NoError, ErrTxWatchdog, ErrSleepEnterWait, ErrNoRadio };
|
||||||
|
|
||||||
|
17
src/rf95/LICENSE
Normal file
17
src/rf95/LICENSE
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
This software is Copyright (C) 2008 Mike McCauley. Use is subject to license
|
||||||
|
conditions. The main licensing options available are GPL V2 or Commercial:
|
||||||
|
|
||||||
|
Open Source Licensing GPL V2
|
||||||
|
|
||||||
|
This is the appropriate option if you want to share the source code of your
|
||||||
|
application with everyone you distribute it to, and you also want to give them
|
||||||
|
the right to share who uses it. If you wish to use this software under Open
|
||||||
|
Source Licensing, you must contribute all your source code to the open source
|
||||||
|
community in accordance with the GPL Version 2 when your application is
|
||||||
|
distributed. See http://www.gnu.org/copyleft/gpl.html
|
||||||
|
|
||||||
|
Commercial Licensing
|
||||||
|
|
||||||
|
This is the appropriate option if you are creating proprietary applications
|
||||||
|
and you are not prepared to distribute and share the source code of your
|
||||||
|
application. Contact info@open.com.au for details.
|
4
src/rf95/README.md
Normal file
4
src/rf95/README.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# RF95
|
||||||
|
|
||||||
|
This is a heavily modified version of the Mike McCauley's RadioHead RF95 driver. We are using it under the GPL V3 License. See the
|
||||||
|
file LICENSE for Mike's license terms (which listed GPL as acceptible).
|
221
src/rf95/RHGenericDriver.cpp
Normal file
221
src/rf95/RHGenericDriver.cpp
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
// RHGenericDriver.cpp
|
||||||
|
//
|
||||||
|
// Copyright (C) 2014 Mike McCauley
|
||||||
|
// $Id: RHGenericDriver.cpp,v 1.23 2018/02/11 23:57:18 mikem Exp $
|
||||||
|
|
||||||
|
#include <RHGenericDriver.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),
|
||||||
|
_cad_timeout(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RHGenericDriver::init()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks until a valid message is received
|
||||||
|
void RHGenericDriver::waitAvailable()
|
||||||
|
{
|
||||||
|
while (!available())
|
||||||
|
YIELD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blocks until a valid message is received or timeout expires
|
||||||
|
// Return true if there is a message available
|
||||||
|
// Works correctly even on millis() rollover
|
||||||
|
bool RHGenericDriver::waitAvailableTimeout(uint16_t timeout)
|
||||||
|
{
|
||||||
|
unsigned long starttime = millis();
|
||||||
|
while ((millis() - starttime) < timeout)
|
||||||
|
{
|
||||||
|
if (available())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
YIELD;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RHGenericDriver::waitPacketSent()
|
||||||
|
{
|
||||||
|
while (_mode == RHModeTx)
|
||||||
|
YIELD; // Wait for any previous transmit to finish
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RHGenericDriver::waitPacketSent(uint16_t timeout)
|
||||||
|
{
|
||||||
|
unsigned long starttime = millis();
|
||||||
|
while ((millis() - starttime) < timeout)
|
||||||
|
{
|
||||||
|
if (_mode != RHModeTx) // Any previous transmit finished?
|
||||||
|
return true;
|
||||||
|
YIELD;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until no channel activity detected or timeout
|
||||||
|
bool RHGenericDriver::waitCAD()
|
||||||
|
{
|
||||||
|
if (!_cad_timeout)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Wait for any channel activity to finish or timeout
|
||||||
|
// Sophisticated DCF function...
|
||||||
|
// DCF : BackoffTime = random() x aSlotTime
|
||||||
|
// 100 - 1000 ms
|
||||||
|
// 10 sec timeout
|
||||||
|
unsigned long t = millis();
|
||||||
|
while (isChannelActive())
|
||||||
|
{
|
||||||
|
if (millis() - t > _cad_timeout)
|
||||||
|
return false;
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_STM32) // stdlib on STMF103 gets confused if random is redefined
|
||||||
|
delay(_random(1, 10) * 100);
|
||||||
|
#else
|
||||||
|
delay(random(1, 10) * 100); // Should these values be configurable? Macros?
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// subclasses are expected to override if CAD is available for that radio
|
||||||
|
bool RHGenericDriver::isChannelActive()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHGenericDriver::setPromiscuous(bool promiscuous)
|
||||||
|
{
|
||||||
|
_promiscuous = promiscuous;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHGenericDriver::setThisAddress(uint8_t address)
|
||||||
|
{
|
||||||
|
_thisAddress = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHGenericDriver::setHeaderTo(uint8_t to)
|
||||||
|
{
|
||||||
|
_txHeaderTo = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHGenericDriver::setHeaderFrom(uint8_t from)
|
||||||
|
{
|
||||||
|
_txHeaderFrom = from;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHGenericDriver::setHeaderId(uint8_t id)
|
||||||
|
{
|
||||||
|
_txHeaderId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHGenericDriver::setHeaderFlags(uint8_t set, uint8_t clear)
|
||||||
|
{
|
||||||
|
_txHeaderFlags &= ~clear;
|
||||||
|
_txHeaderFlags |= set;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHGenericDriver::headerTo()
|
||||||
|
{
|
||||||
|
return _rxHeaderTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHGenericDriver::headerFrom()
|
||||||
|
{
|
||||||
|
return _rxHeaderFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHGenericDriver::headerId()
|
||||||
|
{
|
||||||
|
return _rxHeaderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHGenericDriver::headerFlags()
|
||||||
|
{
|
||||||
|
return _rxHeaderFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t RHGenericDriver::lastRssi()
|
||||||
|
{
|
||||||
|
return _lastRssi;
|
||||||
|
}
|
||||||
|
|
||||||
|
RHGenericDriver::RHMode RHGenericDriver::mode()
|
||||||
|
{
|
||||||
|
return _mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHGenericDriver::setMode(RHMode mode)
|
||||||
|
{
|
||||||
|
_mode = mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RHGenericDriver::sleep()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagnostic help
|
||||||
|
void RHGenericDriver::printBuffer(const char* prompt, const uint8_t* buf, uint8_t len)
|
||||||
|
{
|
||||||
|
#ifdef RH_HAVE_SERIAL
|
||||||
|
Serial.println(prompt);
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
if (i % 16 == 15)
|
||||||
|
Serial.println(buf[i], HEX);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.print(buf[i], HEX);
|
||||||
|
Serial.print(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.println("");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t RHGenericDriver::rxBad()
|
||||||
|
{
|
||||||
|
return _rxBad;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t RHGenericDriver::rxGood()
|
||||||
|
{
|
||||||
|
return _rxGood;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t RHGenericDriver::txGood()
|
||||||
|
{
|
||||||
|
return _txGood;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHGenericDriver::setCADTimeout(unsigned long cad_timeout)
|
||||||
|
{
|
||||||
|
_cad_timeout = cad_timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_ATTINY)
|
||||||
|
// Tinycore does not have __cxa_pure_virtual, so without this we
|
||||||
|
// get linking complaints from the default code generated for pure virtual functions
|
||||||
|
extern "C" void __cxa_pure_virtual()
|
||||||
|
{
|
||||||
|
while (1);
|
||||||
|
}
|
||||||
|
#endif
|
307
src/rf95/RHGenericDriver.h
Normal file
307
src/rf95/RHGenericDriver.h
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
// RHGenericDriver.h
|
||||||
|
// Author: Mike McCauley (mikem@airspayce.com)
|
||||||
|
// Copyright (C) 2014 Mike McCauley
|
||||||
|
// $Id: RHGenericDriver.h,v 1.23 2018/09/23 23:54:01 mikem Exp $
|
||||||
|
|
||||||
|
#ifndef RHGenericDriver_h
|
||||||
|
#define RHGenericDriver_h
|
||||||
|
|
||||||
|
#include <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
|
||||||
|
|
||||||
|
// Default timeout for waitCAD() in ms
|
||||||
|
#define RH_CAD_DEFAULT_TIMEOUT 10000
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
/// \class RHGenericDriver RHGenericDriver.h <RHGenericDriver.h>
|
||||||
|
/// \brief Abstract base class for a RadioHead driver.
|
||||||
|
///
|
||||||
|
/// This class defines the functions that must be provided by any RadioHead driver.
|
||||||
|
/// Different types of driver will implement all the abstract functions, and will perhaps override
|
||||||
|
/// other functions in this subclass, or perhaps add new functions specifically required by that driver.
|
||||||
|
/// Do not directly instantiate this class: it is only to be subclassed by driver classes.
|
||||||
|
///
|
||||||
|
/// Subclasses are expected to implement a half-duplex, unreliable, error checked, unaddressed packet transport.
|
||||||
|
/// They are expected to carry a message payload with an appropriate maximum length for the transport hardware
|
||||||
|
/// and to also carry unaltered 4 message headers: TO, FROM, ID, FLAGS
|
||||||
|
///
|
||||||
|
/// \par Headers
|
||||||
|
///
|
||||||
|
/// Each message sent and received by a RadioHead driver includes 4 headers:
|
||||||
|
/// -TO The node address that the message is being sent to (broadcast RH_BROADCAST_ADDRESS (255) is permitted)
|
||||||
|
/// -FROM The node address of the sending node
|
||||||
|
/// -ID A message ID, distinct (over short time scales) for each message sent by a particilar node
|
||||||
|
/// -FLAGS A bitmask of flags. The most significant 4 bits are reserved for use by RadioHead. The least
|
||||||
|
/// significant 4 bits are reserved for applications.
|
||||||
|
class RHGenericDriver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// \brief Defines different operating modes for the transport hardware
|
||||||
|
///
|
||||||
|
/// These are the different values that can be adopted by the _mode variable and
|
||||||
|
/// returned by the mode() member function,
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
RHModeInitialising = 0, ///< Transport is initialising. Initial default value until init() is called..
|
||||||
|
RHModeSleep, ///< Transport hardware is in low power sleep mode (if supported)
|
||||||
|
RHModeIdle, ///< Transport is idle.
|
||||||
|
RHModeTx, ///< Transport is in the process of transmitting a message.
|
||||||
|
RHModeRx, ///< Transport is in the process of receiving a message.
|
||||||
|
RHModeCad ///< Transport is in the process of detecting channel activity (if supported)
|
||||||
|
} RHMode;
|
||||||
|
|
||||||
|
/// Constructor
|
||||||
|
RHGenericDriver();
|
||||||
|
|
||||||
|
/// Initialise the Driver transport hardware and software.
|
||||||
|
/// Make sure the Driver is properly configured before calling init().
|
||||||
|
/// \return true if initialisation succeeded.
|
||||||
|
virtual bool init();
|
||||||
|
|
||||||
|
/// Tests whether a new message is available
|
||||||
|
/// from the Driver.
|
||||||
|
/// On most drivers, if there is an uncollected received message, and there is no message
|
||||||
|
/// currently bing transmitted, this will also put the Driver into RHModeRx mode until
|
||||||
|
/// a message is actually received by the transport, when it will be returned to RHModeIdle.
|
||||||
|
/// This can be called multiple times in a timeout loop.
|
||||||
|
/// \return true if a new, complete, error-free uncollected message is available to be retreived by recv().
|
||||||
|
virtual bool available() = 0;
|
||||||
|
|
||||||
|
/// Turns the receiver on if it not already on.
|
||||||
|
/// If there is a valid message available, copy it to buf and return true
|
||||||
|
/// else return false.
|
||||||
|
/// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted).
|
||||||
|
/// You should be sure to call this function frequently enough to not miss any messages
|
||||||
|
/// It is recommended that you call it in your main loop.
|
||||||
|
/// \param[in] buf Location to copy the received message
|
||||||
|
/// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied.
|
||||||
|
/// \return true if a valid message was copied to buf
|
||||||
|
virtual bool recv(uint8_t* buf, uint8_t* len) = 0;
|
||||||
|
|
||||||
|
/// Waits until any previous transmit packet is finished being transmitted with waitPacketSent().
|
||||||
|
/// Then optionally waits for Channel Activity Detection (CAD)
|
||||||
|
/// to show the channnel is clear (if the radio supports CAD) by calling waitCAD().
|
||||||
|
/// Then loads a message into the transmitter and starts the transmitter. Note that a message length
|
||||||
|
/// of 0 is NOT permitted. If the message is too long for the underlying radio technology, send() will
|
||||||
|
/// return false and will not send the message.
|
||||||
|
/// \param[in] data Array of data to be sent
|
||||||
|
/// \param[in] len Number of bytes of data to send (> 0)
|
||||||
|
/// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting.
|
||||||
|
/// \return true if the message length was valid and it was correctly queued for transmit. Return false
|
||||||
|
/// if CAD was requested and the CAD timeout timed out before clear channel was detected.
|
||||||
|
virtual bool send(const uint8_t* data, uint8_t len) = 0;
|
||||||
|
|
||||||
|
/// Returns the maximum message length
|
||||||
|
/// available in this Driver.
|
||||||
|
/// \return The maximum legal message length
|
||||||
|
virtual uint8_t maxMessageLength() = 0;
|
||||||
|
|
||||||
|
/// Starts the receiver and blocks until a valid received
|
||||||
|
/// message is available.
|
||||||
|
virtual void waitAvailable();
|
||||||
|
|
||||||
|
/// Blocks until the transmitter
|
||||||
|
/// is no longer transmitting.
|
||||||
|
virtual bool waitPacketSent();
|
||||||
|
|
||||||
|
/// Blocks until the transmitter is no longer transmitting.
|
||||||
|
/// or until the timeout occuers, whichever happens first
|
||||||
|
/// \param[in] timeout Maximum time to wait in milliseconds.
|
||||||
|
/// \return true if the radio completed transmission within the timeout period. False if it timed out.
|
||||||
|
virtual bool waitPacketSent(uint16_t timeout);
|
||||||
|
|
||||||
|
/// Starts the receiver and blocks until a received message is available or a timeout
|
||||||
|
/// \param[in] timeout Maximum time to wait in milliseconds.
|
||||||
|
/// \return true if a message is available
|
||||||
|
virtual bool waitAvailableTimeout(uint16_t timeout);
|
||||||
|
|
||||||
|
// Bent G Christensen (bentor@gmail.com), 08/15/2016
|
||||||
|
/// Channel Activity Detection (CAD).
|
||||||
|
/// Blocks until channel activity is finished or CAD timeout occurs.
|
||||||
|
/// Uses the radio's CAD function (if supported) to detect channel activity.
|
||||||
|
/// Implements random delays of 100 to 1000ms while activity is detected and until timeout.
|
||||||
|
/// Caution: the random() function is not seeded. If you want non-deterministic behaviour, consider
|
||||||
|
/// using something like randomSeed(analogRead(A0)); in your sketch.
|
||||||
|
/// Permits the implementation of listen-before-talk mechanism (Collision Avoidance).
|
||||||
|
/// Calls the isChannelActive() member function for the radio (if supported)
|
||||||
|
/// to determine if the channel is active. If the radio does not support isChannelActive(),
|
||||||
|
/// always returns true immediately
|
||||||
|
/// \return true if the radio-specific CAD (as returned by isChannelActive())
|
||||||
|
/// shows the channel is clear within the timeout period (or the timeout period is 0), else returns false.
|
||||||
|
virtual bool waitCAD();
|
||||||
|
|
||||||
|
/// Sets the Channel Activity Detection timeout in milliseconds to be used by waitCAD().
|
||||||
|
/// The default is 0, which means do not wait for CAD detection.
|
||||||
|
/// CAD detection depends on support for isChannelActive() by your particular radio.
|
||||||
|
void setCADTimeout(unsigned long cad_timeout);
|
||||||
|
|
||||||
|
/// Determine if the currently selected radio channel is active.
|
||||||
|
/// This is expected to be subclassed by specific radios to implement their Channel Activity Detection
|
||||||
|
/// if supported. If the radio does not support CAD, returns true immediately. If a RadioHead radio
|
||||||
|
/// supports isChannelActive() it will be documented in the radio specific documentation.
|
||||||
|
/// This is called automatically by waitCAD().
|
||||||
|
/// \return true if the radio-specific CAD (as returned by override of isChannelActive()) shows the
|
||||||
|
/// current radio channel as active, else false. If there is no radio-specific CAD, returns false.
|
||||||
|
virtual bool isChannelActive();
|
||||||
|
|
||||||
|
/// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this.
|
||||||
|
/// This will be used to test the adddress in incoming messages. In non-promiscuous mode,
|
||||||
|
/// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted.
|
||||||
|
/// In promiscuous mode, all messages will be accepted regardless of the TO header.
|
||||||
|
/// In a conventional multinode system, all nodes will have a unique address
|
||||||
|
/// (which you could store in EEPROM).
|
||||||
|
/// You would normally set the header FROM address to be the same as thisAddress (though you dont have to,
|
||||||
|
/// allowing the possibilty of address spoofing).
|
||||||
|
/// \param[in] thisAddress The address of this node.
|
||||||
|
virtual void setThisAddress(uint8_t thisAddress);
|
||||||
|
|
||||||
|
/// Sets the TO header to be sent in all subsequent messages
|
||||||
|
/// \param[in] to The new TO header value
|
||||||
|
virtual void setHeaderTo(uint8_t to);
|
||||||
|
|
||||||
|
/// Sets the FROM header to be sent in all subsequent messages
|
||||||
|
/// \param[in] from The new FROM header value
|
||||||
|
virtual void setHeaderFrom(uint8_t from);
|
||||||
|
|
||||||
|
/// Sets the ID header to be sent in all subsequent messages
|
||||||
|
/// \param[in] id The new ID header value
|
||||||
|
virtual void setHeaderId(uint8_t id);
|
||||||
|
|
||||||
|
/// Sets and clears bits in the FLAGS header to be sent in all subsequent messages
|
||||||
|
/// First it clears he FLAGS according to the clear argument, then sets the flags according to the
|
||||||
|
/// set argument. The default for clear always clears the application specific flags.
|
||||||
|
/// \param[in] set bitmask of bits to be set. Flags are cleared with the clear mask before being set.
|
||||||
|
/// \param[in] clear bitmask of flags to clear. Defaults to RH_FLAGS_APPLICATION_SPECIFIC
|
||||||
|
/// which clears the application specific flags, resulting in new application specific flags
|
||||||
|
/// identical to the set.
|
||||||
|
virtual void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_APPLICATION_SPECIFIC);
|
||||||
|
|
||||||
|
/// Tells the receiver to accept messages with any TO address, not just messages
|
||||||
|
/// addressed to thisAddress or the broadcast address
|
||||||
|
/// \param[in] promiscuous true if you wish to receive messages with any TO address
|
||||||
|
virtual void setPromiscuous(bool promiscuous);
|
||||||
|
|
||||||
|
/// Returns the TO header of the last received message
|
||||||
|
/// \return The TO header
|
||||||
|
virtual uint8_t headerTo();
|
||||||
|
|
||||||
|
/// Returns the FROM header of the last received message
|
||||||
|
/// \return The FROM header
|
||||||
|
virtual uint8_t headerFrom();
|
||||||
|
|
||||||
|
/// Returns the ID header of the last received message
|
||||||
|
/// \return The ID header
|
||||||
|
virtual uint8_t headerId();
|
||||||
|
|
||||||
|
/// Returns the FLAGS header of the last received message
|
||||||
|
/// \return The FLAGS header
|
||||||
|
virtual uint8_t headerFlags();
|
||||||
|
|
||||||
|
/// Returns the most recent RSSI (Receiver Signal Strength Indicator).
|
||||||
|
/// Usually it is the RSSI of the last received message, which is measured when the preamble is received.
|
||||||
|
/// If you called readRssi() more recently, it will return that more recent value.
|
||||||
|
/// \return The most recent RSSI measurement in dBm.
|
||||||
|
virtual int16_t lastRssi();
|
||||||
|
|
||||||
|
/// Returns the operating mode of the library.
|
||||||
|
/// \return the current mode, one of RF69_MODE_*
|
||||||
|
virtual RHMode mode();
|
||||||
|
|
||||||
|
/// Sets the operating mode of the transport.
|
||||||
|
virtual void setMode(RHMode mode);
|
||||||
|
|
||||||
|
/// Sets the transport hardware into low-power sleep mode
|
||||||
|
/// (if supported). May be overridden by specific drivers to initialte sleep mode.
|
||||||
|
/// If successful, the transport will stay in sleep mode until woken by
|
||||||
|
/// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc)
|
||||||
|
/// \return true if sleep mode is supported by transport hardware and the RadioHead driver, and if sleep mode
|
||||||
|
/// was successfully entered. If sleep mode is not suported, return false.
|
||||||
|
virtual bool sleep();
|
||||||
|
|
||||||
|
/// Prints a data buffer in HEX.
|
||||||
|
/// For diagnostic use
|
||||||
|
/// \param[in] prompt string to preface the print
|
||||||
|
/// \param[in] buf Location of the buffer to print
|
||||||
|
/// \param[in] len Length of the buffer in octets.
|
||||||
|
static void printBuffer(const char* prompt, const uint8_t* buf, uint8_t len);
|
||||||
|
|
||||||
|
/// Returns the count of the number of bad received packets (ie packets with bad lengths, checksum etc)
|
||||||
|
/// which were rejected and not delivered to the application.
|
||||||
|
/// Caution: not all drivers can correctly report this count. Some underlying hardware only report
|
||||||
|
/// good packets.
|
||||||
|
/// \return The number of bad packets received.
|
||||||
|
virtual uint16_t rxBad();
|
||||||
|
|
||||||
|
/// Returns the count of the number of
|
||||||
|
/// good received packets
|
||||||
|
/// \return The number of good packets received.
|
||||||
|
virtual uint16_t rxGood();
|
||||||
|
|
||||||
|
/// Returns the count of the number of
|
||||||
|
/// packets successfully transmitted (though not necessarily received by the destination)
|
||||||
|
/// \return The number of packets successfully transmitted
|
||||||
|
virtual uint16_t txGood();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/// The current transport operating mode
|
||||||
|
volatile RHMode _mode;
|
||||||
|
|
||||||
|
/// This node id
|
||||||
|
uint8_t _thisAddress;
|
||||||
|
|
||||||
|
/// Whether the transport is in promiscuous mode
|
||||||
|
bool _promiscuous;
|
||||||
|
|
||||||
|
/// TO header in the last received mesasge
|
||||||
|
volatile uint8_t _rxHeaderTo;
|
||||||
|
|
||||||
|
/// FROM header in the last received mesasge
|
||||||
|
volatile uint8_t _rxHeaderFrom;
|
||||||
|
|
||||||
|
/// ID header in the last received mesasge
|
||||||
|
volatile uint8_t _rxHeaderId;
|
||||||
|
|
||||||
|
/// FLAGS header in the last received mesasge
|
||||||
|
volatile uint8_t _rxHeaderFlags;
|
||||||
|
|
||||||
|
/// TO header to send in all messages
|
||||||
|
uint8_t _txHeaderTo;
|
||||||
|
|
||||||
|
/// FROM header to send in all messages
|
||||||
|
uint8_t _txHeaderFrom;
|
||||||
|
|
||||||
|
/// ID header to send in all messages
|
||||||
|
uint8_t _txHeaderId;
|
||||||
|
|
||||||
|
/// FLAGS header to send in all messages
|
||||||
|
uint8_t _txHeaderFlags;
|
||||||
|
|
||||||
|
/// The value of the last received RSSI value, in some transport specific units
|
||||||
|
volatile int16_t _lastRssi;
|
||||||
|
|
||||||
|
/// Count of the number of bad messages (eg bad checksum etc) received
|
||||||
|
volatile uint16_t _rxBad;
|
||||||
|
|
||||||
|
/// Count of the number of successfully transmitted messaged
|
||||||
|
volatile uint16_t _rxGood;
|
||||||
|
|
||||||
|
/// Count of the number of bad messages (correct checksum etc) received
|
||||||
|
volatile uint16_t _txGood;
|
||||||
|
|
||||||
|
/// Channel activity detected
|
||||||
|
volatile bool _cad;
|
||||||
|
|
||||||
|
/// Channel activity timeout in ms
|
||||||
|
unsigned int _cad_timeout;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
31
src/rf95/RHGenericSPI.cpp
Normal file
31
src/rf95/RHGenericSPI.cpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// RHGenericSPI.cpp
|
||||||
|
// Author: Mike McCauley (mikem@airspayce.com)
|
||||||
|
// Copyright (C) 2011 Mike McCauley
|
||||||
|
// Contributed by Joanna Rutkowska
|
||||||
|
// $Id: RHGenericSPI.cpp,v 1.2 2014/04/12 05:26:05 mikem Exp $
|
||||||
|
|
||||||
|
#include <RHGenericSPI.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;
|
||||||
|
}
|
||||||
|
|
183
src/rf95/RHGenericSPI.h
Normal file
183
src/rf95/RHGenericSPI.h
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
// RHGenericSPI.h
|
||||||
|
// Author: Mike McCauley (mikem@airspayce.com)
|
||||||
|
// Copyright (C) 2011 Mike McCauley
|
||||||
|
// Contributed by Joanna Rutkowska
|
||||||
|
// $Id: RHGenericSPI.h,v 1.9 2020/01/05 07:02:23 mikem Exp mikem $
|
||||||
|
|
||||||
|
#ifndef RHGenericSPI_h
|
||||||
|
#define RHGenericSPI_h
|
||||||
|
|
||||||
|
#include <RadioHead.h>
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
/// \class RHGenericSPI RHGenericSPI.h <RHGenericSPI.h>
|
||||||
|
/// \brief Base class for SPI interfaces
|
||||||
|
///
|
||||||
|
/// This generic abstract class is used to encapsulate hardware or software SPI interfaces for
|
||||||
|
/// a variety of platforms.
|
||||||
|
/// The intention is so that driver classes can be configured to use hardware or software SPI
|
||||||
|
/// without changing the main code.
|
||||||
|
///
|
||||||
|
/// You must provide a subclass of this class to driver constructors that require SPI.
|
||||||
|
/// A concrete subclass that encapsualates the standard Arduino hardware SPI and a bit-banged
|
||||||
|
/// software implementation is included.
|
||||||
|
///
|
||||||
|
/// Do not directly use this class: it must be subclassed and the following abstract functions at least
|
||||||
|
/// must be implmented:
|
||||||
|
/// - begin()
|
||||||
|
/// - end()
|
||||||
|
/// - transfer()
|
||||||
|
class RHGenericSPI
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// \brief Defines constants for different SPI modes
|
||||||
|
///
|
||||||
|
/// Defines constants for different SPI modes
|
||||||
|
/// that can be passed to the constructor or setMode()
|
||||||
|
/// We need to define these in a device and platform independent way, because the
|
||||||
|
/// SPI implementation is different on each platform.
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
DataMode0 = 0, ///< SPI Mode 0: CPOL = 0, CPHA = 0
|
||||||
|
DataMode1, ///< SPI Mode 1: CPOL = 0, CPHA = 1
|
||||||
|
DataMode2, ///< SPI Mode 2: CPOL = 1, CPHA = 0
|
||||||
|
DataMode3, ///< SPI Mode 3: CPOL = 1, CPHA = 1
|
||||||
|
} DataMode;
|
||||||
|
|
||||||
|
/// \brief Defines constants for different SPI bus frequencies
|
||||||
|
///
|
||||||
|
/// Defines constants for different SPI bus frequencies
|
||||||
|
/// that can be passed to setFrequency().
|
||||||
|
/// The frequency you get may not be exactly the one according to the name.
|
||||||
|
/// We need to define these in a device and platform independent way, because the
|
||||||
|
/// SPI implementation is different on each platform.
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
Frequency1MHz = 0, ///< SPI bus frequency close to 1MHz
|
||||||
|
Frequency2MHz, ///< SPI bus frequency close to 2MHz
|
||||||
|
Frequency4MHz, ///< SPI bus frequency close to 4MHz
|
||||||
|
Frequency8MHz, ///< SPI bus frequency close to 8MHz
|
||||||
|
Frequency16MHz ///< SPI bus frequency close to 16MHz
|
||||||
|
} Frequency;
|
||||||
|
|
||||||
|
/// \brief Defines constants for different SPI endianness
|
||||||
|
///
|
||||||
|
/// Defines constants for different SPI endianness
|
||||||
|
/// that can be passed to setBitOrder()
|
||||||
|
/// We need to define these in a device and platform independent way, because the
|
||||||
|
/// SPI implementation is different on each platform.
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
BitOrderMSBFirst = 0, ///< SPI MSB first
|
||||||
|
BitOrderLSBFirst, ///< SPI LSB first
|
||||||
|
} BitOrder;
|
||||||
|
|
||||||
|
/// Constructor
|
||||||
|
/// Creates an instance of an abstract SPI interface.
|
||||||
|
/// Do not use this contructor directly: you must instead use on of the concrete subclasses provided
|
||||||
|
/// such as RHHardwareSPI or RHSoftwareSPI
|
||||||
|
/// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency
|
||||||
|
/// is mapped to the closest available bus frequency on the platform.
|
||||||
|
/// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or
|
||||||
|
/// RHGenericSPI::BitOrderLSBFirst.
|
||||||
|
/// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode
|
||||||
|
RHGenericSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0);
|
||||||
|
|
||||||
|
/// Transfer a single octet to and from the SPI interface
|
||||||
|
/// \param[in] data The octet to send
|
||||||
|
/// \return The octet read from SPI while the data octet was sent
|
||||||
|
virtual uint8_t transfer(uint8_t data) = 0;
|
||||||
|
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS)
|
||||||
|
/// Transfer up to 2 bytes on the SPI interface
|
||||||
|
/// \param[in] byte0 The first byte to be sent on the SPI interface
|
||||||
|
/// \param[in] byte1 The second byte to be sent on the SPI interface
|
||||||
|
/// \return The second byte clocked in as the second byte is sent.
|
||||||
|
virtual uint8_t transfer2B(uint8_t byte0, uint8_t byte1) = 0;
|
||||||
|
|
||||||
|
/// Read a number of bytes on the SPI interface from an NRF device
|
||||||
|
/// \param[in] reg The NRF device register to read
|
||||||
|
/// \param[out] dest The buffer to hold the bytes read
|
||||||
|
/// \param[in] len The number of bytes to read
|
||||||
|
/// \return The NRF status byte
|
||||||
|
virtual uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len) = 0;
|
||||||
|
|
||||||
|
/// Wrte a number of bytes on the SPI interface to an NRF device
|
||||||
|
/// \param[in] reg The NRF device register to read
|
||||||
|
/// \param[out] src The buffer to hold the bytes write
|
||||||
|
/// \param[in] len The number of bytes to write
|
||||||
|
/// \return The NRF status byte
|
||||||
|
virtual uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len) = 0;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// SPI Configuration methods
|
||||||
|
/// Enable SPI interrupts (if supported)
|
||||||
|
/// This can be used in an SPI slave to indicate when an SPI message has been received
|
||||||
|
virtual void attachInterrupt() {};
|
||||||
|
|
||||||
|
/// Disable SPI interrupts (if supported)
|
||||||
|
/// This can be used to diable the SPI interrupt in slaves where that is supported.
|
||||||
|
virtual void detachInterrupt() {};
|
||||||
|
|
||||||
|
/// Initialise the SPI library.
|
||||||
|
/// Call this after configuring and before using the SPI library
|
||||||
|
virtual void begin() = 0;
|
||||||
|
|
||||||
|
/// Disables the SPI bus (leaving pin modes unchanged).
|
||||||
|
/// Call this after you have finished using the SPI interface
|
||||||
|
virtual void end() = 0;
|
||||||
|
|
||||||
|
/// Sets the bit order the SPI interface will use
|
||||||
|
/// Sets the order of the bits shifted out of and into the SPI bus, either
|
||||||
|
/// LSBFIRST (least-significant bit first) or MSBFIRST (most-significant bit first).
|
||||||
|
/// \param[in] bitOrder Bit order to be used: one of RHGenericSPI::BitOrder
|
||||||
|
virtual void setBitOrder(BitOrder bitOrder);
|
||||||
|
|
||||||
|
/// Sets the SPI data mode: that is, clock polarity and phase.
|
||||||
|
/// See the Wikipedia article on SPI for details.
|
||||||
|
/// \param[in] dataMode The mode to use: one of RHGenericSPI::DataMode
|
||||||
|
virtual void setDataMode(DataMode dataMode);
|
||||||
|
|
||||||
|
/// Sets the SPI clock divider relative to the system clock.
|
||||||
|
/// On AVR based boards, the dividers available are 2, 4, 8, 16, 32, 64 or 128.
|
||||||
|
/// The default setting is SPI_CLOCK_DIV4, which sets the SPI clock to one-quarter
|
||||||
|
/// the frequency of the system clock (4 Mhz for the boards at 16 MHz).
|
||||||
|
/// \param[in] frequency The data rate to use: one of RHGenericSPI::Frequency
|
||||||
|
virtual void setFrequency(Frequency frequency);
|
||||||
|
|
||||||
|
/// Signal the start of an SPI transaction that must not be interrupted by other SPI actions
|
||||||
|
/// In subclasses that support transactions this will ensure that other SPI transactions
|
||||||
|
/// are blocked until this one is completed by endTransaction().
|
||||||
|
/// Base does nothing
|
||||||
|
/// Might be overridden in subclass
|
||||||
|
virtual void beginTransaction(){}
|
||||||
|
|
||||||
|
/// Signal the end of an SPI transaction
|
||||||
|
/// Base does nothing
|
||||||
|
/// Might be overridden in subclass
|
||||||
|
virtual void endTransaction(){}
|
||||||
|
|
||||||
|
/// Specify the interrupt number of the interrupt that will use SPI transactions
|
||||||
|
/// Tells the SPI support software that SPI transactions will occur with the interrupt
|
||||||
|
/// handler assocated with interruptNumber
|
||||||
|
/// Base does nothing
|
||||||
|
/// Might be overridden in subclass
|
||||||
|
/// \param[in] interruptNumber The number of the interrupt
|
||||||
|
virtual void usingInterrupt(uint8_t interruptNumber){
|
||||||
|
(void)interruptNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
/// The configure SPI Bus frequency, one of RHGenericSPI::Frequency
|
||||||
|
Frequency _frequency; // Bus frequency, one of RHGenericSPI::Frequency
|
||||||
|
|
||||||
|
/// Bit order, one of RHGenericSPI::BitOrder
|
||||||
|
BitOrder _bitOrder;
|
||||||
|
|
||||||
|
/// SPI bus mode, one of RHGenericSPI::DataMode
|
||||||
|
DataMode _dataMode;
|
||||||
|
};
|
||||||
|
#endif
|
499
src/rf95/RHHardwareSPI.cpp
Normal file
499
src/rf95/RHHardwareSPI.cpp
Normal file
@ -0,0 +1,499 @@
|
|||||||
|
// RHHardwareSPI.cpp
|
||||||
|
// Author: Mike McCauley (mikem@airspayce.com)
|
||||||
|
// Copyright (C) 2011 Mike McCauley
|
||||||
|
// Contributed by Joanna Rutkowska
|
||||||
|
// $Id: RHHardwareSPI.cpp,v 1.25 2020/01/05 07:02:23 mikem Exp mikem $
|
||||||
|
|
||||||
|
#include <RHHardwareSPI.h>
|
||||||
|
|
||||||
|
#ifdef RH_HAVE_HARDWARE_SPI
|
||||||
|
|
||||||
|
// Declare a single default instance of the hardware SPI interface class
|
||||||
|
RHHardwareSPI hardware_spi;
|
||||||
|
|
||||||
|
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc
|
||||||
|
// Declare an SPI interface to use
|
||||||
|
HardwareSPI SPI(1);
|
||||||
|
#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 Discovery
|
||||||
|
// Declare an SPI interface to use
|
||||||
|
HardwareSPI SPI(1);
|
||||||
|
#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS) // Mongoose OS platform
|
||||||
|
HardwareSPI SPI(1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Arduino Due has default SPI pins on central SPI headers, and not on 10, 11, 12, 13
|
||||||
|
// as per other Arduinos
|
||||||
|
// http://21stdigitalhome.blogspot.com.au/2013/02/arduino-due-hardware-spi.html
|
||||||
|
#if defined (__arm__) && !defined(CORE_TEENSY) && !defined(SPI_CLOCK_DIV16) && !defined(RH_PLATFORM_NRF52)
|
||||||
|
// Arduino Due in 1.5.5 has no definitions for SPI dividers
|
||||||
|
// SPI clock divider is based on MCK of 84MHz
|
||||||
|
#define SPI_CLOCK_DIV16 (VARIANT_MCK/84000000) // 1MHz
|
||||||
|
#define SPI_CLOCK_DIV8 (VARIANT_MCK/42000000) // 2MHz
|
||||||
|
#define SPI_CLOCK_DIV4 (VARIANT_MCK/21000000) // 4MHz
|
||||||
|
#define SPI_CLOCK_DIV2 (VARIANT_MCK/10500000) // 8MHz
|
||||||
|
#define SPI_CLOCK_DIV1 (VARIANT_MCK/5250000) // 16MHz
|
||||||
|
#endif
|
||||||
|
|
||||||
|
RHHardwareSPI::RHHardwareSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode)
|
||||||
|
:
|
||||||
|
RHGenericSPI(frequency, bitOrder, dataMode)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHHardwareSPI::transfer(uint8_t data)
|
||||||
|
{
|
||||||
|
return SPI.transfer(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS)
|
||||||
|
uint8_t RHHardwareSPI::transfer2B(uint8_t byte0, uint8_t byte1)
|
||||||
|
{
|
||||||
|
return SPI.transfer2B(byte0, byte1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHHardwareSPI::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len)
|
||||||
|
{
|
||||||
|
return SPI.spiBurstRead(reg, dest, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHHardwareSPI::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len)
|
||||||
|
{
|
||||||
|
uint8_t status = SPI.spiBurstWrite(reg, src, len);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void RHHardwareSPI::attachInterrupt()
|
||||||
|
{
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO || RH_PLATFORM == RH_PLATFORM_NRF52)
|
||||||
|
SPI.attachInterrupt();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHHardwareSPI::detachInterrupt()
|
||||||
|
{
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO || RH_PLATFORM == RH_PLATFORM_NRF52)
|
||||||
|
SPI.detachInterrupt();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHHardwareSPI::begin()
|
||||||
|
{
|
||||||
|
#if defined(SPI_HAS_TRANSACTION)
|
||||||
|
// Perhaps this is a uniform interface for SPI?
|
||||||
|
// Currently Teensy and ESP32 only
|
||||||
|
uint32_t frequency;
|
||||||
|
if (_frequency == Frequency16MHz)
|
||||||
|
frequency = 16000000;
|
||||||
|
else if (_frequency == Frequency8MHz)
|
||||||
|
frequency = 8000000;
|
||||||
|
else if (_frequency == Frequency4MHz)
|
||||||
|
frequency = 4000000;
|
||||||
|
else if (_frequency == Frequency2MHz)
|
||||||
|
frequency = 2000000;
|
||||||
|
else
|
||||||
|
frequency = 1000000;
|
||||||
|
|
||||||
|
#if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))) || defined(ARDUINO_ARCH_NRF52) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32) || defined(NRF52)
|
||||||
|
// Arduino Due in 1.5.5 has its own BitOrder :-(
|
||||||
|
// So too does Arduino Zero
|
||||||
|
// So too does rogerclarkmelbourne/Arduino_STM32
|
||||||
|
::BitOrder bitOrder;
|
||||||
|
#elif (RH_PLATFORM == RH_PLATFORM_ATTINY_MEGA)
|
||||||
|
::BitOrder bitOrder;
|
||||||
|
#else
|
||||||
|
uint8_t bitOrder;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (_bitOrder == BitOrderLSBFirst)
|
||||||
|
bitOrder = LSBFIRST;
|
||||||
|
else
|
||||||
|
bitOrder = MSBFIRST;
|
||||||
|
|
||||||
|
uint8_t dataMode;
|
||||||
|
if (_dataMode == DataMode0)
|
||||||
|
dataMode = SPI_MODE0;
|
||||||
|
else if (_dataMode == DataMode1)
|
||||||
|
dataMode = SPI_MODE1;
|
||||||
|
else if (_dataMode == DataMode2)
|
||||||
|
dataMode = SPI_MODE2;
|
||||||
|
else if (_dataMode == DataMode3)
|
||||||
|
dataMode = SPI_MODE3;
|
||||||
|
else
|
||||||
|
dataMode = SPI_MODE0;
|
||||||
|
|
||||||
|
// Save the settings for use in transactions
|
||||||
|
_settings = SPISettings(frequency, bitOrder, dataMode);
|
||||||
|
SPI.begin();
|
||||||
|
|
||||||
|
#else // SPI_HAS_TRANSACTION
|
||||||
|
|
||||||
|
// Sigh: there are no common symbols for some of these SPI options across all platforms
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE || RH_PLATFORM == RH_PLATFORM_NRF52)
|
||||||
|
uint8_t dataMode;
|
||||||
|
if (_dataMode == DataMode0)
|
||||||
|
dataMode = SPI_MODE0;
|
||||||
|
else if (_dataMode == DataMode1)
|
||||||
|
dataMode = SPI_MODE1;
|
||||||
|
else if (_dataMode == DataMode2)
|
||||||
|
dataMode = SPI_MODE2;
|
||||||
|
else if (_dataMode == DataMode3)
|
||||||
|
dataMode = SPI_MODE3;
|
||||||
|
else
|
||||||
|
dataMode = SPI_MODE0;
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY)
|
||||||
|
// Temporary work-around due to problem where avr_emulation.h does not work properly for the setDataMode() cal
|
||||||
|
SPCR &= ~SPI_MODE_MASK;
|
||||||
|
#else
|
||||||
|
#if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && defined(ARDUINO_ARCH_SAMD)) || defined(ARDUINO_ARCH_NRF52)
|
||||||
|
// Zero requires begin() before anything else :-)
|
||||||
|
SPI.begin();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SPI.setDataMode(dataMode);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ((RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))) || defined(ARDUINO_ARCH_NRF52) || defined (ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32)
|
||||||
|
// Arduino Due in 1.5.5 has its own BitOrder :-(
|
||||||
|
// So too does Arduino Zero
|
||||||
|
// So too does rogerclarkmelbourne/Arduino_STM32
|
||||||
|
::BitOrder bitOrder;
|
||||||
|
#else
|
||||||
|
uint8_t bitOrder;
|
||||||
|
#endif
|
||||||
|
if (_bitOrder == BitOrderLSBFirst)
|
||||||
|
bitOrder = LSBFIRST;
|
||||||
|
else
|
||||||
|
bitOrder = MSBFIRST;
|
||||||
|
SPI.setBitOrder(bitOrder);
|
||||||
|
uint8_t divider;
|
||||||
|
switch (_frequency)
|
||||||
|
{
|
||||||
|
case Frequency1MHz:
|
||||||
|
default:
|
||||||
|
#if F_CPU == 8000000
|
||||||
|
divider = SPI_CLOCK_DIV8;
|
||||||
|
#else
|
||||||
|
divider = SPI_CLOCK_DIV16;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency2MHz:
|
||||||
|
#if F_CPU == 8000000
|
||||||
|
divider = SPI_CLOCK_DIV4;
|
||||||
|
#else
|
||||||
|
divider = SPI_CLOCK_DIV8;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency4MHz:
|
||||||
|
#if F_CPU == 8000000
|
||||||
|
divider = SPI_CLOCK_DIV2;
|
||||||
|
#else
|
||||||
|
divider = SPI_CLOCK_DIV4;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency8MHz:
|
||||||
|
divider = SPI_CLOCK_DIV2; // 4MHz on an 8MHz Arduino
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency16MHz:
|
||||||
|
divider = SPI_CLOCK_DIV2; // Not really 16MHz, only 8MHz. 4MHz on an 8MHz Arduino
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
SPI.setClockDivider(divider);
|
||||||
|
SPI.begin();
|
||||||
|
// Teensy requires it to be set _after_ begin()
|
||||||
|
SPI.setClockDivider(divider);
|
||||||
|
|
||||||
|
#elif (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc
|
||||||
|
spi_mode dataMode;
|
||||||
|
// Hmmm, if we do this as a switch, GCC on maple gets v confused!
|
||||||
|
if (_dataMode == DataMode0)
|
||||||
|
dataMode = SPI_MODE_0;
|
||||||
|
else if (_dataMode == DataMode1)
|
||||||
|
dataMode = SPI_MODE_1;
|
||||||
|
else if (_dataMode == DataMode2)
|
||||||
|
dataMode = SPI_MODE_2;
|
||||||
|
else if (_dataMode == DataMode3)
|
||||||
|
dataMode = SPI_MODE_3;
|
||||||
|
else
|
||||||
|
dataMode = SPI_MODE_0;
|
||||||
|
|
||||||
|
uint32 bitOrder;
|
||||||
|
if (_bitOrder == BitOrderLSBFirst)
|
||||||
|
bitOrder = LSBFIRST;
|
||||||
|
else
|
||||||
|
bitOrder = MSBFIRST;
|
||||||
|
|
||||||
|
SPIFrequency frequency; // Yes, I know these are not exact equivalents.
|
||||||
|
switch (_frequency)
|
||||||
|
{
|
||||||
|
case Frequency1MHz:
|
||||||
|
default:
|
||||||
|
frequency = SPI_1_125MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency2MHz:
|
||||||
|
frequency = SPI_2_25MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency4MHz:
|
||||||
|
frequency = SPI_4_5MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency8MHz:
|
||||||
|
frequency = SPI_9MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency16MHz:
|
||||||
|
frequency = SPI_18MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
SPI.begin(frequency, bitOrder, dataMode);
|
||||||
|
|
||||||
|
#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 discovery
|
||||||
|
uint8_t dataMode;
|
||||||
|
if (_dataMode == DataMode0)
|
||||||
|
dataMode = SPI_MODE0;
|
||||||
|
else if (_dataMode == DataMode1)
|
||||||
|
dataMode = SPI_MODE1;
|
||||||
|
else if (_dataMode == DataMode2)
|
||||||
|
dataMode = SPI_MODE2;
|
||||||
|
else if (_dataMode == DataMode3)
|
||||||
|
dataMode = SPI_MODE3;
|
||||||
|
else
|
||||||
|
dataMode = SPI_MODE0;
|
||||||
|
|
||||||
|
uint32_t bitOrder;
|
||||||
|
if (_bitOrder == BitOrderLSBFirst)
|
||||||
|
bitOrder = LSBFIRST;
|
||||||
|
else
|
||||||
|
bitOrder = MSBFIRST;
|
||||||
|
|
||||||
|
SPIFrequency frequency; // Yes, I know these are not exact equivalents.
|
||||||
|
switch (_frequency)
|
||||||
|
{
|
||||||
|
case Frequency1MHz:
|
||||||
|
default:
|
||||||
|
frequency = SPI_1_3125MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency2MHz:
|
||||||
|
frequency = SPI_2_625MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency4MHz:
|
||||||
|
frequency = SPI_5_25MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency8MHz:
|
||||||
|
frequency = SPI_10_5MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency16MHz:
|
||||||
|
frequency = SPI_21_0MHZ;
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
SPI.begin(frequency, bitOrder, dataMode);
|
||||||
|
|
||||||
|
#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Photon
|
||||||
|
uint8_t dataMode;
|
||||||
|
if (_dataMode == DataMode0)
|
||||||
|
dataMode = SPI_MODE0;
|
||||||
|
else if (_dataMode == DataMode1)
|
||||||
|
dataMode = SPI_MODE1;
|
||||||
|
else if (_dataMode == DataMode2)
|
||||||
|
dataMode = SPI_MODE2;
|
||||||
|
else if (_dataMode == DataMode3)
|
||||||
|
dataMode = SPI_MODE3;
|
||||||
|
else
|
||||||
|
dataMode = SPI_MODE0;
|
||||||
|
SPI.setDataMode(dataMode);
|
||||||
|
if (_bitOrder == BitOrderLSBFirst)
|
||||||
|
SPI.setBitOrder(LSBFIRST);
|
||||||
|
else
|
||||||
|
SPI.setBitOrder(MSBFIRST);
|
||||||
|
|
||||||
|
switch (_frequency)
|
||||||
|
{
|
||||||
|
case Frequency1MHz:
|
||||||
|
default:
|
||||||
|
SPI.setClockSpeed(1, MHZ);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency2MHz:
|
||||||
|
SPI.setClockSpeed(2, MHZ);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency4MHz:
|
||||||
|
SPI.setClockSpeed(4, MHZ);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency8MHz:
|
||||||
|
SPI.setClockSpeed(8, MHZ);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Frequency16MHz:
|
||||||
|
SPI.setClockSpeed(16, MHZ);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SPI.setClockDivider(SPI_CLOCK_DIV4); // 72MHz / 4MHz = 18MHz
|
||||||
|
// SPI.setClockSpeed(1, MHZ);
|
||||||
|
SPI.begin();
|
||||||
|
|
||||||
|
#elif (RH_PLATFORM == RH_PLATFORM_ESP8266)
|
||||||
|
// Requires SPI driver for ESP8266 from https://github.com/esp8266/Arduino/tree/master/libraries/SPI
|
||||||
|
// Which ppears to be in Arduino Board Manager ESP8266 Community version 2.1.0
|
||||||
|
// Contributed by David Skinner
|
||||||
|
// begin comes first
|
||||||
|
SPI.begin();
|
||||||
|
|
||||||
|
// datamode
|
||||||
|
switch ( _dataMode )
|
||||||
|
{
|
||||||
|
case DataMode1:
|
||||||
|
SPI.setDataMode ( SPI_MODE1 );
|
||||||
|
break;
|
||||||
|
case DataMode2:
|
||||||
|
SPI.setDataMode ( SPI_MODE2 );
|
||||||
|
break;
|
||||||
|
case DataMode3:
|
||||||
|
SPI.setDataMode ( SPI_MODE3 );
|
||||||
|
break;
|
||||||
|
case DataMode0:
|
||||||
|
default:
|
||||||
|
SPI.setDataMode ( SPI_MODE0 );
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bitorder
|
||||||
|
SPI.setBitOrder(_bitOrder == BitOrderLSBFirst ? LSBFIRST : MSBFIRST);
|
||||||
|
|
||||||
|
// frequency (this sets the divider)
|
||||||
|
switch (_frequency)
|
||||||
|
{
|
||||||
|
case Frequency1MHz:
|
||||||
|
default:
|
||||||
|
SPI.setFrequency(1000000);
|
||||||
|
break;
|
||||||
|
case Frequency2MHz:
|
||||||
|
SPI.setFrequency(2000000);
|
||||||
|
break;
|
||||||
|
case Frequency4MHz:
|
||||||
|
SPI.setFrequency(4000000);
|
||||||
|
break;
|
||||||
|
case Frequency8MHz:
|
||||||
|
SPI.setFrequency(8000000);
|
||||||
|
break;
|
||||||
|
case Frequency16MHz:
|
||||||
|
SPI.setFrequency(16000000);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif (RH_PLATFORM == RH_PLATFORM_RASPI) // Raspberry PI
|
||||||
|
uint8_t dataMode;
|
||||||
|
if (_dataMode == DataMode0)
|
||||||
|
dataMode = BCM2835_SPI_MODE0;
|
||||||
|
else if (_dataMode == DataMode1)
|
||||||
|
dataMode = BCM2835_SPI_MODE1;
|
||||||
|
else if (_dataMode == DataMode2)
|
||||||
|
dataMode = BCM2835_SPI_MODE2;
|
||||||
|
else if (_dataMode == DataMode3)
|
||||||
|
dataMode = BCM2835_SPI_MODE3;
|
||||||
|
|
||||||
|
uint8_t bitOrder;
|
||||||
|
if (_bitOrder == BitOrderLSBFirst)
|
||||||
|
bitOrder = BCM2835_SPI_BIT_ORDER_LSBFIRST;
|
||||||
|
else
|
||||||
|
bitOrder = BCM2835_SPI_BIT_ORDER_MSBFIRST;
|
||||||
|
|
||||||
|
uint32_t divider;
|
||||||
|
switch (_frequency)
|
||||||
|
{
|
||||||
|
case Frequency1MHz:
|
||||||
|
default:
|
||||||
|
divider = BCM2835_SPI_CLOCK_DIVIDER_256;
|
||||||
|
break;
|
||||||
|
case Frequency2MHz:
|
||||||
|
divider = BCM2835_SPI_CLOCK_DIVIDER_128;
|
||||||
|
break;
|
||||||
|
case Frequency4MHz:
|
||||||
|
divider = BCM2835_SPI_CLOCK_DIVIDER_64;
|
||||||
|
break;
|
||||||
|
case Frequency8MHz:
|
||||||
|
divider = BCM2835_SPI_CLOCK_DIVIDER_32;
|
||||||
|
break;
|
||||||
|
case Frequency16MHz:
|
||||||
|
divider = BCM2835_SPI_CLOCK_DIVIDER_16;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SPI.begin(divider, bitOrder, dataMode);
|
||||||
|
#elif (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS)
|
||||||
|
uint8_t dataMode = SPI_MODE0;
|
||||||
|
uint32_t frequency = 4000000; //!!! ESP32/NRF902 works ok at 4MHz but not at 8MHz SPI clock.
|
||||||
|
uint32_t bitOrder = MSBFIRST;
|
||||||
|
|
||||||
|
if (_dataMode == DataMode0) {
|
||||||
|
dataMode = SPI_MODE0;
|
||||||
|
} else if (_dataMode == DataMode1) {
|
||||||
|
dataMode = SPI_MODE1;
|
||||||
|
} else if (_dataMode == DataMode2) {
|
||||||
|
dataMode = SPI_MODE2;
|
||||||
|
} else if (_dataMode == DataMode3) {
|
||||||
|
dataMode = SPI_MODE3;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_bitOrder == BitOrderLSBFirst) {
|
||||||
|
bitOrder = LSBFIRST;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_frequency == Frequency4MHz)
|
||||||
|
frequency = 4000000;
|
||||||
|
else if (_frequency == Frequency2MHz)
|
||||||
|
frequency = 2000000;
|
||||||
|
else
|
||||||
|
frequency = 1000000;
|
||||||
|
|
||||||
|
SPI.begin(frequency, bitOrder, dataMode);
|
||||||
|
#else
|
||||||
|
#warning RHHardwareSPI does not support this platform yet. Consider adding it and contributing a patch.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // SPI_HAS_TRANSACTION
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHHardwareSPI::end()
|
||||||
|
{
|
||||||
|
return SPI.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHHardwareSPI::beginTransaction()
|
||||||
|
{
|
||||||
|
#if defined(SPI_HAS_TRANSACTION)
|
||||||
|
SPI.beginTransaction(_settings);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHHardwareSPI::endTransaction()
|
||||||
|
{
|
||||||
|
#if defined(SPI_HAS_TRANSACTION)
|
||||||
|
SPI.endTransaction();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHHardwareSPI::usingInterrupt(uint8_t interrupt)
|
||||||
|
{
|
||||||
|
#if defined(SPI_HAS_TRANSACTION) && !defined(RH_MISSING_SPIUSINGINTERRUPT)
|
||||||
|
SPI.usingInterrupt(interrupt);
|
||||||
|
#endif
|
||||||
|
(void)interrupt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
116
src/rf95/RHHardwareSPI.h
Normal file
116
src/rf95/RHHardwareSPI.h
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// RHHardwareSPI.h
|
||||||
|
// Author: Mike McCauley (mikem@airspayce.com)
|
||||||
|
// Copyright (C) 2011 Mike McCauley
|
||||||
|
// Contributed by Joanna Rutkowska
|
||||||
|
// $Id: RHHardwareSPI.h,v 1.12 2020/01/05 07:02:23 mikem Exp mikem $
|
||||||
|
|
||||||
|
#ifndef RHHardwareSPI_h
|
||||||
|
#define RHHardwareSPI_h
|
||||||
|
|
||||||
|
#include <RHGenericSPI.h>
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
/// \class RHHardwareSPI RHHardwareSPI.h <RHHardwareSPI.h>
|
||||||
|
/// \brief Encapsulate a hardware SPI bus interface
|
||||||
|
///
|
||||||
|
/// This concrete subclass of GenericSPIClass encapsulates the standard Arduino hardware and other
|
||||||
|
/// hardware SPI interfaces.
|
||||||
|
///
|
||||||
|
/// SPI transactions are supported in development environments that support it with SPI_HAS_TRANSACTION.
|
||||||
|
class RHHardwareSPI : public RHGenericSPI
|
||||||
|
{
|
||||||
|
#ifdef RH_HAVE_HARDWARE_SPI
|
||||||
|
public:
|
||||||
|
/// Constructor
|
||||||
|
/// Creates an instance of a hardware SPI interface, using whatever SPI hardware is available on
|
||||||
|
/// your processor platform. On Arduino and Uno32, uses SPI. On Maple, uses HardwareSPI.
|
||||||
|
/// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency
|
||||||
|
/// is mapped to the closest available bus frequency on the platform.
|
||||||
|
/// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or
|
||||||
|
/// RHGenericSPI::BitOrderLSBFirst.
|
||||||
|
/// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode
|
||||||
|
RHHardwareSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0);
|
||||||
|
|
||||||
|
/// Transfer a single octet to and from the SPI interface
|
||||||
|
/// \param[in] data The octet to send
|
||||||
|
/// \return The octet read from SPI while the data octet was sent
|
||||||
|
uint8_t transfer(uint8_t data);
|
||||||
|
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS)
|
||||||
|
/// Transfer (write) 2 bytes on the SPI interface to an NRF device
|
||||||
|
/// \param[in] byte0 The first byte to be sent on the SPI interface
|
||||||
|
/// \param[in] byte1 The second byte to be sent on the SPI interface
|
||||||
|
/// \return The second byte clocked in as the second byte is sent.
|
||||||
|
uint8_t transfer2B(uint8_t byte0, uint8_t byte1);
|
||||||
|
|
||||||
|
/// Read a number of bytes on the SPI interface from an NRF device
|
||||||
|
/// \param[in] reg The NRF device register to read
|
||||||
|
/// \param[out] dest The buffer to hold the bytes read
|
||||||
|
/// \param[in] len The number of bytes to read
|
||||||
|
/// \return The NRF status byte
|
||||||
|
uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len);
|
||||||
|
|
||||||
|
/// Wrte a number of bytes on the SPI interface to an NRF device
|
||||||
|
/// \param[in] reg The NRF device register to read
|
||||||
|
/// \param[out] src The buffer to hold the bytes write
|
||||||
|
/// \param[in] len The number of bytes to write
|
||||||
|
/// \return The NRF status byte
|
||||||
|
uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// SPI Configuration methods
|
||||||
|
/// Enable SPI interrupts
|
||||||
|
/// This can be used in an SPI slave to indicate when an SPI message has been received
|
||||||
|
/// It will cause the SPI_STC_vect interrupt vectr to be executed
|
||||||
|
void attachInterrupt();
|
||||||
|
|
||||||
|
/// Disable SPI interrupts
|
||||||
|
/// This can be used to diable the SPI interrupt in slaves where that is supported.
|
||||||
|
void detachInterrupt();
|
||||||
|
|
||||||
|
/// Initialise the SPI library
|
||||||
|
/// Call this after configuring the SPI interface and before using it to transfer data.
|
||||||
|
/// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high.
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
/// Disables the SPI bus (leaving pin modes unchanged).
|
||||||
|
/// Call this after you have finished using the SPI interface.
|
||||||
|
void end();
|
||||||
|
#else
|
||||||
|
// not supported on ATTiny etc
|
||||||
|
uint8_t transfer(uint8_t /*data*/) {return 0;}
|
||||||
|
void begin(){}
|
||||||
|
void end(){}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// Signal the start of an SPI transaction that must not be interrupted by other SPI actions
|
||||||
|
/// In subclasses that support transactions this will ensure that other SPI transactions
|
||||||
|
/// are blocked until this one is completed by endTransaction().
|
||||||
|
/// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION.
|
||||||
|
virtual void beginTransaction();
|
||||||
|
|
||||||
|
/// Signal the end of an SPI transaction
|
||||||
|
/// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION.
|
||||||
|
virtual void endTransaction();
|
||||||
|
|
||||||
|
/// Specify the interrupt number of the interrupt that will use SPI transactions
|
||||||
|
/// Tells the SPI support software that SPI transactions will occur with the interrupt
|
||||||
|
/// handler assocated with interruptNumber
|
||||||
|
/// Uses the underlying SPI transaction support if available as specified by SPI_HAS_TRANSACTION.
|
||||||
|
/// \param[in] interruptNumber The number of the interrupt
|
||||||
|
virtual void usingInterrupt(uint8_t interruptNumber);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
#if defined(SPI_HAS_TRANSACTION)
|
||||||
|
// Storage for SPI settings used in SPI transactions
|
||||||
|
SPISettings _settings;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Built in default instance
|
||||||
|
extern RHHardwareSPI hardware_spi;
|
||||||
|
|
||||||
|
#endif
|
137
src/rf95/RHNRFSPIDriver.cpp
Normal file
137
src/rf95/RHNRFSPIDriver.cpp
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// RHNRFSPIDriver.cpp
|
||||||
|
//
|
||||||
|
// Copyright (C) 2014 Mike McCauley
|
||||||
|
// $Id: RHNRFSPIDriver.cpp,v 1.5 2020/01/05 07:02:23 mikem Exp mikem $
|
||||||
|
|
||||||
|
#include <RHNRFSPIDriver.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;
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS)
|
||||||
|
status = _spi.transfer(command);
|
||||||
|
#else
|
||||||
|
_spi.beginTransaction();
|
||||||
|
digitalWrite(_slaveSelectPin, LOW);
|
||||||
|
status = _spi.transfer(command);
|
||||||
|
digitalWrite(_slaveSelectPin, HIGH);
|
||||||
|
_spi.endTransaction();
|
||||||
|
#endif
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHNRFSPIDriver::spiRead(uint8_t reg)
|
||||||
|
{
|
||||||
|
uint8_t val;
|
||||||
|
ATOMIC_BLOCK_START;
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS)
|
||||||
|
val = _spi.transfer2B(reg, 0); // Send the address, discard the status, The written value is ignored, reg value is read
|
||||||
|
#else
|
||||||
|
_spi.beginTransaction();
|
||||||
|
digitalWrite(_slaveSelectPin, LOW);
|
||||||
|
_spi.transfer(reg); // Send the address, discard the status
|
||||||
|
val = _spi.transfer(0); // The written value is ignored, reg value is read
|
||||||
|
digitalWrite(_slaveSelectPin, HIGH);
|
||||||
|
_spi.endTransaction();
|
||||||
|
#endif
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHNRFSPIDriver::spiWrite(uint8_t reg, uint8_t val)
|
||||||
|
{
|
||||||
|
uint8_t status = 0;
|
||||||
|
ATOMIC_BLOCK_START;
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS)
|
||||||
|
status = _spi.transfer2B(reg, val);
|
||||||
|
#else
|
||||||
|
_spi.beginTransaction();
|
||||||
|
digitalWrite(_slaveSelectPin, LOW);
|
||||||
|
status = _spi.transfer(reg); // Send the address
|
||||||
|
_spi.transfer(val); // New value follows
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY)
|
||||||
|
// Sigh: some devices, such as MRF89XA dont work properly on Teensy 3.1:
|
||||||
|
// At 1MHz, the clock returns low _after_ slave select goes high, which prevents SPI
|
||||||
|
// write working. This delay gixes time for the clock to return low.
|
||||||
|
delayMicroseconds(5);
|
||||||
|
#endif
|
||||||
|
digitalWrite(_slaveSelectPin, HIGH);
|
||||||
|
_spi.endTransaction();
|
||||||
|
#endif
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHNRFSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len)
|
||||||
|
{
|
||||||
|
uint8_t status = 0;
|
||||||
|
ATOMIC_BLOCK_START;
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS)
|
||||||
|
status = _spi.spiBurstRead(reg, dest, len);
|
||||||
|
#else
|
||||||
|
_spi.beginTransaction();
|
||||||
|
digitalWrite(_slaveSelectPin, LOW);
|
||||||
|
status = _spi.transfer(reg); // Send the start address
|
||||||
|
while (len--)
|
||||||
|
*dest++ = _spi.transfer(0);
|
||||||
|
digitalWrite(_slaveSelectPin, HIGH);
|
||||||
|
_spi.endTransaction();
|
||||||
|
#endif
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHNRFSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len)
|
||||||
|
{
|
||||||
|
uint8_t status = 0;
|
||||||
|
ATOMIC_BLOCK_START;
|
||||||
|
#if (RH_PLATFORM == RH_PLATFORM_MONGOOSE_OS)
|
||||||
|
status = _spi.spiBurstWrite(reg, src, len);
|
||||||
|
#else
|
||||||
|
_spi.beginTransaction();
|
||||||
|
digitalWrite(_slaveSelectPin, LOW);
|
||||||
|
status = _spi.transfer(reg); // Send the start address
|
||||||
|
while (len--)
|
||||||
|
_spi.transfer(*src++);
|
||||||
|
digitalWrite(_slaveSelectPin, HIGH);
|
||||||
|
_spi.endTransaction();
|
||||||
|
#endif
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHNRFSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin)
|
||||||
|
{
|
||||||
|
_slaveSelectPin = slaveSelectPin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHNRFSPIDriver::spiUsingInterrupt(uint8_t interruptNumber)
|
||||||
|
{
|
||||||
|
_spi.usingInterrupt(interruptNumber);
|
||||||
|
}
|
||||||
|
|
101
src/rf95/RHNRFSPIDriver.h
Normal file
101
src/rf95/RHNRFSPIDriver.h
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// RHNRFSPIDriver.h
|
||||||
|
// Author: Mike McCauley (mikem@airspayce.com)
|
||||||
|
// Copyright (C) 2014 Mike McCauley
|
||||||
|
// $Id: RHNRFSPIDriver.h,v 1.5 2017/11/06 00:04:08 mikem Exp $
|
||||||
|
|
||||||
|
#ifndef RHNRFSPIDriver_h
|
||||||
|
#define RHNRFSPIDriver_h
|
||||||
|
|
||||||
|
#include <RHGenericDriver.h>
|
||||||
|
#include <RHHardwareSPI.h>
|
||||||
|
|
||||||
|
class RHGenericSPI;
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
/// \class RHNRFSPIDriver RHNRFSPIDriver.h <RHNRFSPIDriver.h>
|
||||||
|
/// \brief Base class for RadioHead drivers that use the SPI bus
|
||||||
|
/// to communicate with its NRF family transport hardware.
|
||||||
|
///
|
||||||
|
/// This class can be subclassed by Drivers that require to use the SPI bus.
|
||||||
|
/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform)
|
||||||
|
/// of the bitbanged RHSoftwareSPI class. The dfault behaviour is to use a pre-instantiated built-in RHHardwareSPI
|
||||||
|
/// interface.
|
||||||
|
///
|
||||||
|
/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts
|
||||||
|
/// are disabled during access.
|
||||||
|
///
|
||||||
|
/// The read and write routines use SPI conventions as used by Nordic NRF radios and otehr devices,
|
||||||
|
/// but these can be overriden
|
||||||
|
/// in subclasses if necessary.
|
||||||
|
///
|
||||||
|
/// Application developers are not expected to instantiate this class directly:
|
||||||
|
/// it is for the use of Driver developers.
|
||||||
|
class RHNRFSPIDriver : public RHGenericDriver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Constructor
|
||||||
|
/// \param[in] slaveSelectPin The controller pin to use to select the desired SPI device. This pin will be driven LOW
|
||||||
|
/// during SPI communications with the SPI device that uis iused by this Driver.
|
||||||
|
/// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface.
|
||||||
|
RHNRFSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi);
|
||||||
|
|
||||||
|
/// Initialise the Driver transport hardware and software.
|
||||||
|
/// Make sure the Driver is properly configured before calling init().
|
||||||
|
/// \return true if initialisation succeeded.
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/// Sends a single command to the device
|
||||||
|
/// \param[in] command The command code to send to the device.
|
||||||
|
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
|
||||||
|
/// it may or may not be meaningfule depending on the the type of device being accessed.
|
||||||
|
uint8_t spiCommand(uint8_t command);
|
||||||
|
|
||||||
|
/// Reads a single register from the SPI device
|
||||||
|
/// \param[in] reg Register number
|
||||||
|
/// \return The value of the register
|
||||||
|
uint8_t spiRead(uint8_t reg);
|
||||||
|
|
||||||
|
/// Writes a single byte to the SPI device
|
||||||
|
/// \param[in] reg Register number
|
||||||
|
/// \param[in] val The value to write
|
||||||
|
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
|
||||||
|
/// it may or may not be meaningfule depending on the the type of device being accessed.
|
||||||
|
uint8_t spiWrite(uint8_t reg, uint8_t val);
|
||||||
|
|
||||||
|
/// Reads a number of consecutive registers from the SPI device using burst read mode
|
||||||
|
/// \param[in] reg Register number of the first register
|
||||||
|
/// \param[in] dest Array to write the register values to. Must be at least len bytes
|
||||||
|
/// \param[in] len Number of bytes to read
|
||||||
|
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
|
||||||
|
/// it may or may not be meaningfule depending on the the type of device being accessed.
|
||||||
|
uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len);
|
||||||
|
|
||||||
|
/// Write a number of consecutive registers using burst write mode
|
||||||
|
/// \param[in] reg Register number of the first register
|
||||||
|
/// \param[in] src Array of new register values to write. Must be at least len bytes
|
||||||
|
/// \param[in] len Number of bytes to write
|
||||||
|
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
|
||||||
|
/// it may or may not be meaningfule depending on the the type of device being accessed.
|
||||||
|
uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len);
|
||||||
|
|
||||||
|
/// Set or change the pin to be used for SPI slave select.
|
||||||
|
/// This can be called at any time to change the
|
||||||
|
/// pin that will be used for slave select in subsquent SPI operations.
|
||||||
|
/// \param[in] slaveSelectPin The pin to use
|
||||||
|
void setSlaveSelectPin(uint8_t slaveSelectPin);
|
||||||
|
|
||||||
|
/// Set the SPI interrupt number
|
||||||
|
/// If SPI transactions can occur within an interrupt, tell the low level SPI
|
||||||
|
/// interface which interrupt is used
|
||||||
|
/// \param[in] interruptNumber the interrupt number
|
||||||
|
void spiUsingInterrupt(uint8_t interruptNumber);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Reference to the RHGenericSPI instance to use to trasnfer data with teh SPI device
|
||||||
|
RHGenericSPI& _spi;
|
||||||
|
|
||||||
|
/// The pin number of the Slave Select pin that is used to select the desired device.
|
||||||
|
uint8_t _slaveSelectPin;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
95
src/rf95/RHSPIDriver.cpp
Normal file
95
src/rf95/RHSPIDriver.cpp
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// RHSPIDriver.cpp
|
||||||
|
//
|
||||||
|
// Copyright (C) 2014 Mike McCauley
|
||||||
|
// $Id: RHSPIDriver.cpp,v 1.11 2017/11/06 00:04:08 mikem Exp $
|
||||||
|
|
||||||
|
#include <RHSPIDriver.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;
|
||||||
|
digitalWrite(_slaveSelectPin, LOW);
|
||||||
|
_spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the address with the write mask off
|
||||||
|
val = _spi.transfer(0); // The written value is ignored, reg value is read
|
||||||
|
digitalWrite(_slaveSelectPin, HIGH);
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHSPIDriver::spiWrite(uint8_t reg, uint8_t val)
|
||||||
|
{
|
||||||
|
uint8_t status = 0;
|
||||||
|
ATOMIC_BLOCK_START;
|
||||||
|
_spi.beginTransaction();
|
||||||
|
digitalWrite(_slaveSelectPin, LOW);
|
||||||
|
status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the address with the write mask on
|
||||||
|
_spi.transfer(val); // New value follows
|
||||||
|
digitalWrite(_slaveSelectPin, HIGH);
|
||||||
|
_spi.endTransaction();
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len)
|
||||||
|
{
|
||||||
|
uint8_t status = 0;
|
||||||
|
ATOMIC_BLOCK_START;
|
||||||
|
_spi.beginTransaction();
|
||||||
|
digitalWrite(_slaveSelectPin, LOW);
|
||||||
|
status = _spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the start address with the write mask off
|
||||||
|
while (len--)
|
||||||
|
*dest++ = _spi.transfer(0);
|
||||||
|
digitalWrite(_slaveSelectPin, HIGH);
|
||||||
|
_spi.endTransaction();
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RHSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len)
|
||||||
|
{
|
||||||
|
uint8_t status = 0;
|
||||||
|
ATOMIC_BLOCK_START;
|
||||||
|
_spi.beginTransaction();
|
||||||
|
digitalWrite(_slaveSelectPin, LOW);
|
||||||
|
status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the start address with the write mask on
|
||||||
|
while (len--)
|
||||||
|
_spi.transfer(*src++);
|
||||||
|
digitalWrite(_slaveSelectPin, HIGH);
|
||||||
|
_spi.endTransaction();
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin)
|
||||||
|
{
|
||||||
|
_slaveSelectPin = slaveSelectPin;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHSPIDriver::spiUsingInterrupt(uint8_t interruptNumber)
|
||||||
|
{
|
||||||
|
_spi.usingInterrupt(interruptNumber);
|
||||||
|
}
|
||||||
|
|
100
src/rf95/RHSPIDriver.h
Normal file
100
src/rf95/RHSPIDriver.h
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// RHSPIDriver.h
|
||||||
|
// Author: Mike McCauley (mikem@airspayce.com)
|
||||||
|
// Copyright (C) 2014 Mike McCauley
|
||||||
|
// $Id: RHSPIDriver.h,v 1.14 2019/09/06 04:40:40 mikem Exp $
|
||||||
|
|
||||||
|
#ifndef RHSPIDriver_h
|
||||||
|
#define RHSPIDriver_h
|
||||||
|
|
||||||
|
#include <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 <RHSPIDriver.h>
|
||||||
|
/// \brief Base class for RadioHead drivers that use the SPI bus
|
||||||
|
/// to communicate with its transport hardware.
|
||||||
|
///
|
||||||
|
/// This class can be subclassed by Drivers that require to use the SPI bus.
|
||||||
|
/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform)
|
||||||
|
/// of the bitbanged RHSoftwareSPI class. The default behaviour is to use a pre-instantiated built-in RHHardwareSPI
|
||||||
|
/// interface.
|
||||||
|
///
|
||||||
|
/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts
|
||||||
|
/// are disabled during access.
|
||||||
|
///
|
||||||
|
/// The read and write routines implement commonly used SPI conventions: specifically that the MSB
|
||||||
|
/// of the first byte transmitted indicates that it is a write and the remaining bits indicate the rehgister to access)
|
||||||
|
/// This can be overriden
|
||||||
|
/// in subclasses if necessaryor an alternative class, RHNRFSPIDriver can be used to access devices like
|
||||||
|
/// Nordic NRF series radios, which have different requirements.
|
||||||
|
///
|
||||||
|
/// Application developers are not expected to instantiate this class directly:
|
||||||
|
/// it is for the use of Driver developers.
|
||||||
|
class RHSPIDriver : public RHGenericDriver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Constructor
|
||||||
|
/// \param[in] slaveSelectPin The controler pin to use to select the desired SPI device. This pin will be driven LOW
|
||||||
|
/// during SPI communications with the SPI device that uis iused by this Driver.
|
||||||
|
/// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface.
|
||||||
|
RHSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi);
|
||||||
|
|
||||||
|
/// Initialise the Driver transport hardware and software.
|
||||||
|
/// Make sure the Driver is properly configured before calling init().
|
||||||
|
/// \return true if initialisation succeeded.
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/// Reads a single register from the SPI device
|
||||||
|
/// \param[in] reg Register number
|
||||||
|
/// \return The value of the register
|
||||||
|
uint8_t spiRead(uint8_t reg);
|
||||||
|
|
||||||
|
/// Writes a single byte to the SPI device
|
||||||
|
/// \param[in] reg Register number
|
||||||
|
/// \param[in] val The value to write
|
||||||
|
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
|
||||||
|
/// it may or may not be meaningfule depending on the the type of device being accessed.
|
||||||
|
uint8_t spiWrite(uint8_t reg, uint8_t val);
|
||||||
|
|
||||||
|
/// Reads a number of consecutive registers from the SPI device using burst read mode
|
||||||
|
/// \param[in] reg Register number of the first register
|
||||||
|
/// \param[in] dest Array to write the register values to. Must be at least len bytes
|
||||||
|
/// \param[in] len Number of bytes to read
|
||||||
|
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
|
||||||
|
/// it may or may not be meaningfule depending on the the type of device being accessed.
|
||||||
|
uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len);
|
||||||
|
|
||||||
|
/// Write a number of consecutive registers using burst write mode
|
||||||
|
/// \param[in] reg Register number of the first register
|
||||||
|
/// \param[in] src Array of new register values to write. Must be at least len bytes
|
||||||
|
/// \param[in] len Number of bytes to write
|
||||||
|
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
|
||||||
|
/// it may or may not be meaningfule depending on the the type of device being accessed.
|
||||||
|
uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len);
|
||||||
|
|
||||||
|
/// Set or change the pin to be used for SPI slave select.
|
||||||
|
/// This can be called at any time to change the
|
||||||
|
/// pin that will be used for slave select in subsquent SPI operations.
|
||||||
|
/// \param[in] slaveSelectPin The pin to use
|
||||||
|
void setSlaveSelectPin(uint8_t slaveSelectPin);
|
||||||
|
|
||||||
|
/// Set the SPI interrupt number
|
||||||
|
/// If SPI transactions can occur within an interrupt, tell the low level SPI
|
||||||
|
/// interface which interrupt is used
|
||||||
|
/// \param[in] interruptNumber the interrupt number
|
||||||
|
void spiUsingInterrupt(uint8_t interruptNumber);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Reference to the RHGenericSPI instance to use to transfer data with the SPI device
|
||||||
|
RHGenericSPI& _spi;
|
||||||
|
|
||||||
|
/// The pin number of the Slave Select pin that is used to select the desired device.
|
||||||
|
uint8_t _slaveSelectPin;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
166
src/rf95/RHSoftwareSPI.cpp
Normal file
166
src/rf95/RHSoftwareSPI.cpp
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
90
src/rf95/RHSoftwareSPI.h
Normal file
90
src/rf95/RHSoftwareSPI.h
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// SoftwareSPI.h
|
||||||
|
// Author: Chris Lapa (chris@lapa.com.au)
|
||||||
|
// Copyright (C) 2014 Chris Lapa
|
||||||
|
// Contributed by Chris Lapa
|
||||||
|
|
||||||
|
#ifndef RHSoftwareSPI_h
|
||||||
|
#define RHSoftwareSPI_h
|
||||||
|
|
||||||
|
#include <RHGenericSPI.h>
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
/// \class RHSoftwareSPI RHSoftwareSPI.h <RHSoftwareSPI.h>
|
||||||
|
/// \brief Encapsulate a software SPI interface
|
||||||
|
///
|
||||||
|
/// This concrete subclass of RHGenericSPI enapsulates a bit-banged software SPI interface.
|
||||||
|
/// Caution: this software SPI interface will be much slower than hardware SPI on most
|
||||||
|
/// platforms.
|
||||||
|
///
|
||||||
|
/// SPI transactions are not supported, and associated functions do nothing.
|
||||||
|
///
|
||||||
|
/// \par Usage
|
||||||
|
///
|
||||||
|
/// Usage varies slightly depending on what driver you are using.
|
||||||
|
///
|
||||||
|
/// For RF22, for example:
|
||||||
|
/// \code
|
||||||
|
/// #include <RHSoftwareSPI.h>
|
||||||
|
/// RHSoftwareSPI spi;
|
||||||
|
/// RH_RF22 driver(SS, 2, spi);
|
||||||
|
/// RHReliableDatagram(driver, CLIENT_ADDRESS);
|
||||||
|
/// void setup()
|
||||||
|
/// {
|
||||||
|
/// spi.setPins(6, 5, 7); // Or whatever SPI pins you need
|
||||||
|
/// ....
|
||||||
|
/// }
|
||||||
|
/// \endcode
|
||||||
|
class RHSoftwareSPI : public RHGenericSPI
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// Constructor
|
||||||
|
/// Creates an instance of a bit-banged software SPI interface.
|
||||||
|
/// Sets the SPI pins to the defaults of
|
||||||
|
/// MISO = 12, MOSI = 11, SCK = 13. If you need other assigments, call setPins() before
|
||||||
|
/// calling manager.init() or driver.init().
|
||||||
|
/// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency
|
||||||
|
/// is mapped to the closest available bus frequency on the platform. CAUTION: the achieved
|
||||||
|
/// frequency will almost certainly be very much slower on most platforms. eg on Arduino Uno, the
|
||||||
|
/// the clock rate is likely to be at best around 46kHz.
|
||||||
|
/// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or
|
||||||
|
/// RHGenericSPI::BitOrderLSBFirst.
|
||||||
|
/// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode
|
||||||
|
RHSoftwareSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0);
|
||||||
|
|
||||||
|
/// Transfer a single octet to and from the SPI interface
|
||||||
|
/// \param[in] data The octet to send
|
||||||
|
/// \return The octet read from SPI while the data octet was sent.
|
||||||
|
uint8_t transfer(uint8_t data);
|
||||||
|
|
||||||
|
/// Initialise the software SPI library
|
||||||
|
/// Call this after configuring the SPI interface and before using it to transfer data.
|
||||||
|
/// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high.
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
/// Disables the SPI bus usually, in this case
|
||||||
|
/// there is no hardware controller to disable.
|
||||||
|
void end();
|
||||||
|
|
||||||
|
/// Sets the pins used by this SoftwareSPIClass instance.
|
||||||
|
/// The defaults are: MISO = 12, MOSI = 11, SCK = 13.
|
||||||
|
/// \param[in] miso master in slave out pin used
|
||||||
|
/// \param[in] mosi master out slave in pin used
|
||||||
|
/// \param[in] sck clock pin used
|
||||||
|
void setPins(uint8_t miso = 12, uint8_t mosi = 11, uint8_t sck = 13);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/// Delay routine for bus timing.
|
||||||
|
void delayPeriod();
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t _miso;
|
||||||
|
uint8_t _mosi;
|
||||||
|
uint8_t _sck;
|
||||||
|
uint8_t _delayCounts;
|
||||||
|
uint8_t _clockPolarity;
|
||||||
|
uint8_t _clockPhase;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
668
src/rf95/RH_RF95.cpp
Normal file
668
src/rf95/RH_RF95.cpp
Normal file
@ -0,0 +1,668 @@
|
|||||||
|
// RH_RF95.cpp
|
||||||
|
//
|
||||||
|
// Copyright (C) 2011 Mike McCauley
|
||||||
|
// $Id: RH_RF95.cpp,v 1.22 2020/01/05 07:02:23 mikem Exp mikem $
|
||||||
|
|
||||||
|
#include <RH_RF95.h>
|
||||||
|
|
||||||
|
// Interrupt vectors for the 3 Arduino interrupt pins
|
||||||
|
// Each interrupt can be handled by a different instance of RH_RF95, allowing you to have
|
||||||
|
// 2 or more LORAs per Arduino
|
||||||
|
RH_RF95 *RH_RF95::_deviceForInterrupt[RH_RF95_NUM_INTERRUPTS] = {0, 0, 0};
|
||||||
|
uint8_t RH_RF95::_interruptCount = 0; // Index into _deviceForInterrupt for next device
|
||||||
|
|
||||||
|
// These are indexed by the values of ModemConfigChoice
|
||||||
|
// Stored in flash (program) memory to save SRAM
|
||||||
|
PROGMEM static const RH_RF95::ModemConfig MODEM_CONFIG_TABLE[] =
|
||||||
|
{
|
||||||
|
// 1d, 1e, 26
|
||||||
|
{0x72, 0x74, 0x04}, // Bw125Cr45Sf128 (the chip default), AGC enabled
|
||||||
|
{0x92, 0x74, 0x04}, // Bw500Cr45Sf128, AGC enabled
|
||||||
|
{0x48, 0x94, 0x04}, // Bw31_25Cr48Sf512, AGC enabled
|
||||||
|
{0x78, 0xc4, 0x0c}, // Bw125Cr48Sf4096, AGC enabled
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
RH_RF95::RH_RF95(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI &spi)
|
||||||
|
: RHSPIDriver(slaveSelectPin, spi),
|
||||||
|
_rxBufValid(0)
|
||||||
|
{
|
||||||
|
_interruptPin = interruptPin;
|
||||||
|
_myInterruptIndex = 0xff; // Not allocated yet
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RH_RF95::init()
|
||||||
|
{
|
||||||
|
if (!RHSPIDriver::init())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Determine the interrupt number that corresponds to the interruptPin
|
||||||
|
int interruptNumber = digitalPinToInterrupt(_interruptPin);
|
||||||
|
if (interruptNumber == NOT_AN_INTERRUPT)
|
||||||
|
return false;
|
||||||
|
#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER
|
||||||
|
interruptNumber = _interruptPin;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Tell the low level SPI interface we will use SPI within this interrupt
|
||||||
|
spiUsingInterrupt(interruptNumber);
|
||||||
|
|
||||||
|
// No way to check the device type :-(
|
||||||
|
|
||||||
|
// Add by Adrien van den Bossche <vandenbo@univ-tlse2.fr> for Teensy
|
||||||
|
// ARM M4 requires the below. else pin interrupt doesn't work properly.
|
||||||
|
// On all other platforms, its innocuous, belt and braces
|
||||||
|
pinMode(_interruptPin, INPUT);
|
||||||
|
|
||||||
|
bool isWakeFromDeepSleep = false; // true if we think we are waking from deep sleep AND the rf95 seems to have a valid configuration
|
||||||
|
|
||||||
|
if (!isWakeFromDeepSleep)
|
||||||
|
{
|
||||||
|
// Set sleep mode, so we can also set LORA mode:
|
||||||
|
spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE);
|
||||||
|
delay(10); // Wait for sleep mode to take over from say, CAD
|
||||||
|
// Check we are in sleep mode, with LORA set
|
||||||
|
if (spiRead(RH_RF95_REG_01_OP_MODE) != (RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE))
|
||||||
|
{
|
||||||
|
// Serial.println(spiRead(RH_RF95_REG_01_OP_MODE), HEX);
|
||||||
|
return false; // No device present?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up FIFO
|
||||||
|
// We configure so that we can use the entire 256 byte FIFO for either receive
|
||||||
|
// or transmit, but not both at the same time
|
||||||
|
spiWrite(RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0);
|
||||||
|
spiWrite(RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0);
|
||||||
|
|
||||||
|
// Packet format is preamble + explicit-header + payload + crc
|
||||||
|
// Explicit Header Mode
|
||||||
|
// payload is TO + FROM + ID + FLAGS + message data
|
||||||
|
// RX mode is implmented with RXCONTINUOUS
|
||||||
|
// max message data length is 255 - 4 = 251 octets
|
||||||
|
|
||||||
|
setModeIdle();
|
||||||
|
|
||||||
|
// Set up default configuration
|
||||||
|
// No Sync Words in LORA mode.
|
||||||
|
setModemConfig(Bw125Cr45Sf128); // Radio default
|
||||||
|
// setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
|
||||||
|
setPreambleLength(8); // Default is 8
|
||||||
|
// An innocuous ISM frequency, same as RF22's
|
||||||
|
setFrequency(434.0);
|
||||||
|
// Lowish power
|
||||||
|
setTxPower(13);
|
||||||
|
|
||||||
|
Serial.printf("IRQ flag mask 0x%x\n", spiRead(RH_RF95_REG_11_IRQ_FLAGS_MASK));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// FIXME
|
||||||
|
// restore mode base off reading RS95 registers
|
||||||
|
|
||||||
|
// Only let CPU enter deep sleep if RF95 is sitting waiting on a receive or is in idle or sleep.
|
||||||
|
}
|
||||||
|
|
||||||
|
// geeksville: we do this last, because if there is an interrupt pending from during the deep sleep, this attach will cause it to be taken.
|
||||||
|
|
||||||
|
// Set up interrupt handler
|
||||||
|
// Since there are a limited number of interrupt glue functions isr*() available,
|
||||||
|
// we can only support a limited number of devices simultaneously
|
||||||
|
// ON some devices, notably most Arduinos, the interrupt pin passed in is actuallt the
|
||||||
|
// interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping
|
||||||
|
// yourself based on knwledge of what Arduino board you are running on.
|
||||||
|
if (_myInterruptIndex == 0xff)
|
||||||
|
{
|
||||||
|
// First run, no interrupt allocated yet
|
||||||
|
if (_interruptCount <= RH_RF95_NUM_INTERRUPTS)
|
||||||
|
_myInterruptIndex = _interruptCount++;
|
||||||
|
else
|
||||||
|
return false; // Too many devices, not enough interrupt vectors
|
||||||
|
}
|
||||||
|
_deviceForInterrupt[_myInterruptIndex] = this;
|
||||||
|
if (_myInterruptIndex == 0)
|
||||||
|
attachInterrupt(interruptNumber, isr0, RISING);
|
||||||
|
else if (_myInterruptIndex == 1)
|
||||||
|
attachInterrupt(interruptNumber, isr1, RISING);
|
||||||
|
else if (_myInterruptIndex == 2)
|
||||||
|
attachInterrupt(interruptNumber, isr2, RISING);
|
||||||
|
else
|
||||||
|
return false; // Too many devices, not enough interrupt vectors
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::prepareDeepSleep()
|
||||||
|
{
|
||||||
|
// Determine the interrupt number that corresponds to the interruptPin
|
||||||
|
int interruptNumber = digitalPinToInterrupt(_interruptPin);
|
||||||
|
|
||||||
|
detachInterrupt(interruptNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RH_RF95::isReceiving()
|
||||||
|
{
|
||||||
|
// 0x0b == Look for header info valid, signal synchronized or signal detected
|
||||||
|
uint8_t reg = spiRead(RH_RF95_REG_18_MODEM_STAT) & 0x1f;
|
||||||
|
// Serial.printf("reg %x\n", reg);
|
||||||
|
return _mode == RHModeRx && (reg & (RH_RF95_MODEM_STATUS_SIGNAL_DETECTED |
|
||||||
|
RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED |
|
||||||
|
RH_RF95_MODEM_STATUS_HEADER_INFO_VALID)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// C++ level interrupt handler for this instance
|
||||||
|
// LORA is unusual in that it has several interrupt lines, and not a single, combined one.
|
||||||
|
// On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly
|
||||||
|
// connnected to the processor.
|
||||||
|
// We use this to get RxDone and TxDone interrupts
|
||||||
|
void RH_RF95::handleInterrupt()
|
||||||
|
{
|
||||||
|
// Read the interrupt register
|
||||||
|
uint8_t irq_flags = spiRead(RH_RF95_REG_12_IRQ_FLAGS);
|
||||||
|
|
||||||
|
// Note: there can be substantial latency between ISR assertion and this function being run, therefore
|
||||||
|
// multiple flags might be set. Handle them all
|
||||||
|
|
||||||
|
// Note: we are running the chip in continuous receive mode (currently, so RX_TIMEOUT shouldn't ever occur)
|
||||||
|
bool haveRxError = irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR);
|
||||||
|
if (haveRxError)
|
||||||
|
// if (_mode == RHModeRx && irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR))
|
||||||
|
{
|
||||||
|
_rxBad++;
|
||||||
|
clearRxBuf();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((irq_flags & RH_RF95_RX_DONE) && !haveRxError)
|
||||||
|
{
|
||||||
|
// Read the RegHopChannel register to check if CRC presence is signalled
|
||||||
|
// in the header. If not it might be a stray (noise) packet.*
|
||||||
|
uint8_t crc_present = spiRead(RH_RF95_REG_1C_HOP_CHANNEL) & RH_RF95_RX_PAYLOAD_CRC_IS_ON;
|
||||||
|
|
||||||
|
spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags, required before reading fifo (according to datasheet)
|
||||||
|
|
||||||
|
if (!crc_present)
|
||||||
|
{
|
||||||
|
_rxBad++;
|
||||||
|
clearRxBuf();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Have received a packet
|
||||||
|
uint8_t len = spiRead(RH_RF95_REG_13_RX_NB_BYTES);
|
||||||
|
|
||||||
|
// Reset the fifo read ptr to the beginning of the packet
|
||||||
|
spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, spiRead(RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR));
|
||||||
|
spiBurstRead(RH_RF95_REG_00_FIFO, _buf, len);
|
||||||
|
_bufLen = len;
|
||||||
|
|
||||||
|
// Remember the last signal to noise ratio, LORA mode
|
||||||
|
// Per page 111, SX1276/77/78/79 datasheet
|
||||||
|
_lastSNR = (int8_t)spiRead(RH_RF95_REG_19_PKT_SNR_VALUE) / 4;
|
||||||
|
|
||||||
|
// Remember the RSSI of this packet, LORA mode
|
||||||
|
// this is according to the doc, but is it really correct?
|
||||||
|
// weakest receiveable signals are reported RSSI at about -66
|
||||||
|
_lastRssi = spiRead(RH_RF95_REG_1A_PKT_RSSI_VALUE);
|
||||||
|
// Adjust the RSSI, datasheet page 87
|
||||||
|
if (_lastSNR < 0)
|
||||||
|
_lastRssi = _lastRssi + _lastSNR;
|
||||||
|
else
|
||||||
|
_lastRssi = (int)_lastRssi * 16 / 15;
|
||||||
|
if (_usingHFport)
|
||||||
|
_lastRssi -= 157;
|
||||||
|
else
|
||||||
|
_lastRssi -= 164;
|
||||||
|
|
||||||
|
// We have received a message.
|
||||||
|
validateRxBuf();
|
||||||
|
if (_rxBufValid)
|
||||||
|
setModeIdle(); // Got one
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (irq_flags & RH_RF95_TX_DONE)
|
||||||
|
{
|
||||||
|
_txGood++;
|
||||||
|
setModeIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_mode == RHModeCad && (irq_flags & RH_RF95_CAD_DONE))
|
||||||
|
{
|
||||||
|
_cad = irq_flags & RH_RF95_CAD_DETECTED;
|
||||||
|
setModeIdle();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ack all interrupts, note - we did this already in the RX_DONE case above, and we don't want to do it twice
|
||||||
|
if (!(irq_flags & RH_RF95_RX_DONE))
|
||||||
|
{
|
||||||
|
// Sigh: on some processors, for some unknown reason, doing this only once does not actually
|
||||||
|
// clear the radio's interrupt flag. So we do it twice. Why?
|
||||||
|
// kevinh: turn this off until root cause is known, because it can cause missed interrupts!
|
||||||
|
// spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags
|
||||||
|
spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are low level functions that call the interrupt handler for the correct
|
||||||
|
// instance of RH_RF95.
|
||||||
|
// 3 interrupts allows us to have 3 different devices
|
||||||
|
void RH_INTERRUPT_ATTR RH_RF95::isr0()
|
||||||
|
{
|
||||||
|
if (_deviceForInterrupt[0])
|
||||||
|
_deviceForInterrupt[0]->handleInterrupt();
|
||||||
|
}
|
||||||
|
void RH_INTERRUPT_ATTR RH_RF95::isr1()
|
||||||
|
{
|
||||||
|
if (_deviceForInterrupt[1])
|
||||||
|
_deviceForInterrupt[1]->handleInterrupt();
|
||||||
|
}
|
||||||
|
void RH_INTERRUPT_ATTR RH_RF95::isr2()
|
||||||
|
{
|
||||||
|
if (_deviceForInterrupt[2])
|
||||||
|
_deviceForInterrupt[2]->handleInterrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the latest received message is complete and uncorrupted
|
||||||
|
void RH_RF95::validateRxBuf()
|
||||||
|
{
|
||||||
|
if (_bufLen < 4)
|
||||||
|
return; // Too short to be a real message
|
||||||
|
// Extract the 4 headers
|
||||||
|
_rxHeaderTo = _buf[0];
|
||||||
|
_rxHeaderFrom = _buf[1];
|
||||||
|
_rxHeaderId = _buf[2];
|
||||||
|
_rxHeaderFlags = _buf[3];
|
||||||
|
if (_promiscuous ||
|
||||||
|
_rxHeaderTo == _thisAddress ||
|
||||||
|
_rxHeaderTo == RH_BROADCAST_ADDRESS)
|
||||||
|
{
|
||||||
|
_rxGood++;
|
||||||
|
_rxBufValid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RH_RF95::available()
|
||||||
|
{
|
||||||
|
if (_mode == RHModeTx)
|
||||||
|
return false;
|
||||||
|
setModeRx();
|
||||||
|
return _rxBufValid; // Will be set by the interrupt handler when a good message is received
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::clearRxBuf()
|
||||||
|
{
|
||||||
|
ATOMIC_BLOCK_START;
|
||||||
|
_rxBufValid = false;
|
||||||
|
_bufLen = 0;
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RH_RF95::recv(uint8_t *buf, uint8_t *len)
|
||||||
|
{
|
||||||
|
if (!available())
|
||||||
|
return false;
|
||||||
|
if (buf && len)
|
||||||
|
{
|
||||||
|
ATOMIC_BLOCK_START;
|
||||||
|
// Skip the 4 headers that are at the beginning of the rxBuf
|
||||||
|
if (*len > _bufLen - RH_RF95_HEADER_LEN)
|
||||||
|
*len = _bufLen - RH_RF95_HEADER_LEN;
|
||||||
|
memcpy(buf, _buf + RH_RF95_HEADER_LEN, *len);
|
||||||
|
ATOMIC_BLOCK_END;
|
||||||
|
}
|
||||||
|
clearRxBuf(); // This message accepted and cleared
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RH_RF95::send(const uint8_t *data, uint8_t len)
|
||||||
|
{
|
||||||
|
if (len > RH_RF95_MAX_MESSAGE_LEN)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
waitPacketSent(); // Make sure we dont interrupt an outgoing message
|
||||||
|
setModeIdle();
|
||||||
|
|
||||||
|
if (!waitCAD())
|
||||||
|
return false; // Check channel activity
|
||||||
|
|
||||||
|
// Position at the beginning of the FIFO
|
||||||
|
spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, 0);
|
||||||
|
// The headers
|
||||||
|
spiWrite(RH_RF95_REG_00_FIFO, _txHeaderTo);
|
||||||
|
spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFrom);
|
||||||
|
spiWrite(RH_RF95_REG_00_FIFO, _txHeaderId);
|
||||||
|
spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFlags);
|
||||||
|
// The message data
|
||||||
|
spiBurstWrite(RH_RF95_REG_00_FIFO, data, len);
|
||||||
|
spiWrite(RH_RF95_REG_22_PAYLOAD_LENGTH, len + RH_RF95_HEADER_LEN);
|
||||||
|
|
||||||
|
setModeTx(); // Start the transmitter
|
||||||
|
// when Tx is done, interruptHandler will fire and radio mode will return to STANDBY
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RH_RF95::printRegisters()
|
||||||
|
{
|
||||||
|
#ifdef RH_HAVE_SERIAL
|
||||||
|
uint8_t registers[] = {0x01, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x014, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27};
|
||||||
|
|
||||||
|
uint8_t i;
|
||||||
|
for (i = 0; i < sizeof(registers); i++)
|
||||||
|
{
|
||||||
|
Serial.print(registers[i], HEX);
|
||||||
|
Serial.print(": ");
|
||||||
|
Serial.println(spiRead(registers[i]), HEX);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t RH_RF95::maxMessageLength()
|
||||||
|
{
|
||||||
|
return RH_RF95_MAX_MESSAGE_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RH_RF95::setFrequency(float centre)
|
||||||
|
{
|
||||||
|
// Frf = FRF / FSTEP
|
||||||
|
uint32_t frf = (centre * 1000000.0) / RH_RF95_FSTEP;
|
||||||
|
spiWrite(RH_RF95_REG_06_FRF_MSB, (frf >> 16) & 0xff);
|
||||||
|
spiWrite(RH_RF95_REG_07_FRF_MID, (frf >> 8) & 0xff);
|
||||||
|
spiWrite(RH_RF95_REG_08_FRF_LSB, frf & 0xff);
|
||||||
|
_usingHFport = (centre >= 779.0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::setModeIdle()
|
||||||
|
{
|
||||||
|
if (_mode != RHModeIdle)
|
||||||
|
{
|
||||||
|
spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_STDBY);
|
||||||
|
_mode = RHModeIdle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RH_RF95::sleep()
|
||||||
|
{
|
||||||
|
if (_mode != RHModeSleep)
|
||||||
|
{
|
||||||
|
spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP);
|
||||||
|
_mode = RHModeSleep;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::setModeRx()
|
||||||
|
{
|
||||||
|
if (_mode != RHModeRx)
|
||||||
|
{
|
||||||
|
spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_RXCONTINUOUS);
|
||||||
|
spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x00); // Interrupt on RxDone
|
||||||
|
_mode = RHModeRx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::setModeTx()
|
||||||
|
{
|
||||||
|
if (_mode != RHModeTx)
|
||||||
|
{
|
||||||
|
spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_TX);
|
||||||
|
spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone
|
||||||
|
_mode = RHModeTx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::setTxPower(int8_t power, bool useRFO)
|
||||||
|
{
|
||||||
|
// Sigh, different behaviours depending on whther the module use PA_BOOST or the RFO pin
|
||||||
|
// for the transmitter output
|
||||||
|
if (useRFO)
|
||||||
|
{
|
||||||
|
if (power > 14)
|
||||||
|
power = 14;
|
||||||
|
if (power < -1)
|
||||||
|
power = -1;
|
||||||
|
spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_MAX_POWER | (power + 1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (power > 23)
|
||||||
|
power = 23;
|
||||||
|
if (power < 5)
|
||||||
|
power = 5;
|
||||||
|
|
||||||
|
// For RH_RF95_PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf'
|
||||||
|
// RH_RF95_PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will us it
|
||||||
|
// for 21, 22 and 23dBm
|
||||||
|
if (power > 20)
|
||||||
|
{
|
||||||
|
spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_ENABLE);
|
||||||
|
power -= 3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_DISABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFM95/96/97/98 does not have RFO pins connected to anything. Only PA_BOOST
|
||||||
|
// pin is connected, so must use PA_BOOST
|
||||||
|
// Pout = 2 + OutputPower.
|
||||||
|
// The documentation is pretty confusing on this topic: PaSelect says the max power is 20dBm,
|
||||||
|
// but OutputPower claims it would be 17dBm.
|
||||||
|
// My measurements show 20dBm is correct
|
||||||
|
spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_PA_SELECT | (power - 5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets registers from a canned modem configuration structure
|
||||||
|
void RH_RF95::setModemRegisters(const ModemConfig *config)
|
||||||
|
{
|
||||||
|
spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, config->reg_1d);
|
||||||
|
spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, config->reg_1e);
|
||||||
|
spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, config->reg_26);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set one of the canned FSK Modem configs
|
||||||
|
// Returns true if its a valid choice
|
||||||
|
bool RH_RF95::setModemConfig(ModemConfigChoice index)
|
||||||
|
{
|
||||||
|
if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ModemConfig cfg;
|
||||||
|
memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(RH_RF95::ModemConfig));
|
||||||
|
setModemRegisters(&cfg);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::setPreambleLength(uint16_t bytes)
|
||||||
|
{
|
||||||
|
spiWrite(RH_RF95_REG_20_PREAMBLE_MSB, bytes >> 8);
|
||||||
|
spiWrite(RH_RF95_REG_21_PREAMBLE_LSB, bytes & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RH_RF95::isChannelActive()
|
||||||
|
{
|
||||||
|
// Set mode RHModeCad
|
||||||
|
if (_mode != RHModeCad)
|
||||||
|
{
|
||||||
|
spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_CAD);
|
||||||
|
spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x80); // Interrupt on CadDone
|
||||||
|
_mode = RHModeCad;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (_mode == RHModeCad)
|
||||||
|
YIELD;
|
||||||
|
|
||||||
|
return _cad;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::enableTCXO()
|
||||||
|
{
|
||||||
|
while ((spiRead(RH_RF95_REG_4B_TCXO) & RH_RF95_TCXO_TCXO_INPUT_ON) != RH_RF95_TCXO_TCXO_INPUT_ON)
|
||||||
|
{
|
||||||
|
sleep();
|
||||||
|
spiWrite(RH_RF95_REG_4B_TCXO, (spiRead(RH_RF95_REG_4B_TCXO) | RH_RF95_TCXO_TCXO_INPUT_ON));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// From section 4.1.5 of SX1276/77/78/79
|
||||||
|
// Ferror = FreqError * 2**24 * BW / Fxtal / 500
|
||||||
|
int RH_RF95::frequencyError()
|
||||||
|
{
|
||||||
|
int32_t freqerror = 0;
|
||||||
|
|
||||||
|
// Convert 2.5 bytes (5 nibbles, 20 bits) to 32 bit signed int
|
||||||
|
// Caution: some C compilers make errors with eg:
|
||||||
|
// freqerror = spiRead(RH_RF95_REG_28_FEI_MSB) << 16
|
||||||
|
// so we go more carefully.
|
||||||
|
freqerror = spiRead(RH_RF95_REG_28_FEI_MSB);
|
||||||
|
freqerror <<= 8;
|
||||||
|
freqerror |= spiRead(RH_RF95_REG_29_FEI_MID);
|
||||||
|
freqerror <<= 8;
|
||||||
|
freqerror |= spiRead(RH_RF95_REG_2A_FEI_LSB);
|
||||||
|
// Sign extension into top 3 nibbles
|
||||||
|
if (freqerror & 0x80000)
|
||||||
|
freqerror |= 0xfff00000;
|
||||||
|
|
||||||
|
int error = 0; // In hertz
|
||||||
|
float bw_tab[] = {7.8, 10.4, 15.6, 20.8, 31.25, 41.7, 62.5, 125, 250, 500};
|
||||||
|
uint8_t bwindex = spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) >> 4;
|
||||||
|
if (bwindex < (sizeof(bw_tab) / sizeof(float)))
|
||||||
|
error = (float)freqerror * bw_tab[bwindex] * ((float)(1L << 24) / (float)RH_RF95_FXOSC / 500.0);
|
||||||
|
// else not defined
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
int RH_RF95::lastSNR()
|
||||||
|
{
|
||||||
|
return _lastSNR;
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// additions below by Brian Norman 9th Nov 2018
|
||||||
|
// brian.n.norman@gmail.com
|
||||||
|
//
|
||||||
|
// Routines intended to make changing BW, SF and CR
|
||||||
|
// a bit more intuitive
|
||||||
|
//
|
||||||
|
///////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void RH_RF95::setSpreadingFactor(uint8_t sf)
|
||||||
|
{
|
||||||
|
if (sf <= 6)
|
||||||
|
sf = RH_RF95_SPREADING_FACTOR_64CPS;
|
||||||
|
else if (sf == 7)
|
||||||
|
sf = RH_RF95_SPREADING_FACTOR_128CPS;
|
||||||
|
else if (sf == 8)
|
||||||
|
sf = RH_RF95_SPREADING_FACTOR_256CPS;
|
||||||
|
else if (sf == 9)
|
||||||
|
sf = RH_RF95_SPREADING_FACTOR_512CPS;
|
||||||
|
else if (sf == 10)
|
||||||
|
sf = RH_RF95_SPREADING_FACTOR_1024CPS;
|
||||||
|
else if (sf == 11)
|
||||||
|
sf = RH_RF95_SPREADING_FACTOR_2048CPS;
|
||||||
|
else if (sf >= 12)
|
||||||
|
sf = RH_RF95_SPREADING_FACTOR_4096CPS;
|
||||||
|
|
||||||
|
// set the new spreading factor
|
||||||
|
spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, (spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) & ~RH_RF95_SPREADING_FACTOR) | sf);
|
||||||
|
// check if Low data Rate bit should be set or cleared
|
||||||
|
setLowDatarate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::setSignalBandwidth(long sbw)
|
||||||
|
{
|
||||||
|
uint8_t bw; //register bit pattern
|
||||||
|
|
||||||
|
if (sbw <= 7800)
|
||||||
|
bw = RH_RF95_BW_7_8KHZ;
|
||||||
|
else if (sbw <= 10400)
|
||||||
|
bw = RH_RF95_BW_10_4KHZ;
|
||||||
|
else if (sbw <= 15600)
|
||||||
|
bw = RH_RF95_BW_15_6KHZ;
|
||||||
|
else if (sbw <= 20800)
|
||||||
|
bw = RH_RF95_BW_20_8KHZ;
|
||||||
|
else if (sbw <= 31250)
|
||||||
|
bw = RH_RF95_BW_31_25KHZ;
|
||||||
|
else if (sbw <= 41700)
|
||||||
|
bw = RH_RF95_BW_41_7KHZ;
|
||||||
|
else if (sbw <= 62500)
|
||||||
|
bw = RH_RF95_BW_62_5KHZ;
|
||||||
|
else if (sbw <= 125000)
|
||||||
|
bw = RH_RF95_BW_125KHZ;
|
||||||
|
else if (sbw <= 250000)
|
||||||
|
bw = RH_RF95_BW_250KHZ;
|
||||||
|
else
|
||||||
|
bw = RH_RF95_BW_500KHZ;
|
||||||
|
|
||||||
|
// top 4 bits of reg 1D control bandwidth
|
||||||
|
spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, (spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) & ~RH_RF95_BW) | bw);
|
||||||
|
// check if low data rate bit should be set or cleared
|
||||||
|
setLowDatarate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::setCodingRate4(uint8_t denominator)
|
||||||
|
{
|
||||||
|
int cr = RH_RF95_CODING_RATE_4_5;
|
||||||
|
|
||||||
|
// if (denominator <= 5)
|
||||||
|
// cr = RH_RF95_CODING_RATE_4_5;
|
||||||
|
if (denominator == 6)
|
||||||
|
cr = RH_RF95_CODING_RATE_4_6;
|
||||||
|
else if (denominator == 7)
|
||||||
|
cr = RH_RF95_CODING_RATE_4_7;
|
||||||
|
else if (denominator >= 8)
|
||||||
|
cr = RH_RF95_CODING_RATE_4_8;
|
||||||
|
|
||||||
|
// CR is bits 3..1 of RH_RF95_REG_1D_MODEM_CONFIG1
|
||||||
|
spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1, (spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) & ~RH_RF95_CODING_RATE) | cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::setLowDatarate()
|
||||||
|
{
|
||||||
|
// called after changing bandwidth and/or spreading factor
|
||||||
|
// Semtech modem design guide AN1200.13 says
|
||||||
|
// "To avoid issues surrounding drift of the crystal reference oscillator due to either temperature change
|
||||||
|
// or motion,the low data rate optimization bit is used. Specifically for 125 kHz bandwidth and SF = 11 and 12,
|
||||||
|
// this adds a small overhead to increase robustness to reference frequency variations over the timescale of the LoRa packet."
|
||||||
|
|
||||||
|
// read current value for BW and SF
|
||||||
|
uint8_t BW = spiRead(RH_RF95_REG_1D_MODEM_CONFIG1) >> 4; // bw is in bits 7..4
|
||||||
|
uint8_t SF = spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) >> 4; // sf is in bits 7..4
|
||||||
|
|
||||||
|
// calculate symbol time (see Semtech AN1200.22 section 4)
|
||||||
|
float bw_tab[] = {7800, 10400, 15600, 20800, 31250, 41700, 62500, 125000, 250000, 500000};
|
||||||
|
|
||||||
|
float bandwidth = bw_tab[BW];
|
||||||
|
|
||||||
|
float symbolTime = 1000.0 * pow(2, SF) / bandwidth; // ms
|
||||||
|
|
||||||
|
// the symbolTime for SF 11 BW 125 is 16.384ms.
|
||||||
|
// and, according to this :-
|
||||||
|
// https://www.thethingsnetwork.org/forum/t/a-point-to-note-lora-low-data-rate-optimisation-flag/12007
|
||||||
|
// the LDR bit should be set if the Symbol Time is > 16ms
|
||||||
|
// So the threshold used here is 16.0ms
|
||||||
|
|
||||||
|
// the LDR is bit 3 of RH_RF95_REG_26_MODEM_CONFIG3
|
||||||
|
uint8_t current = spiRead(RH_RF95_REG_26_MODEM_CONFIG3) & ~RH_RF95_LOW_DATA_RATE_OPTIMIZE; // mask off the LDR bit
|
||||||
|
if (symbolTime > 16.0)
|
||||||
|
spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, current | RH_RF95_LOW_DATA_RATE_OPTIMIZE);
|
||||||
|
else
|
||||||
|
spiWrite(RH_RF95_REG_26_MODEM_CONFIG3, current);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RH_RF95::setPayloadCRC(bool on)
|
||||||
|
{
|
||||||
|
// Payload CRC is bit 2 of register 1E
|
||||||
|
uint8_t current = spiRead(RH_RF95_REG_1E_MODEM_CONFIG2) & ~RH_RF95_PAYLOAD_CRC_ON; // mask off the CRC
|
||||||
|
|
||||||
|
if (on)
|
||||||
|
spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, current | RH_RF95_PAYLOAD_CRC_ON);
|
||||||
|
else
|
||||||
|
spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2, current);
|
||||||
|
}
|
894
src/rf95/RH_RF95.h
Normal file
894
src/rf95/RH_RF95.h
Normal file
@ -0,0 +1,894 @@
|
|||||||
|
// RH_RF95.h
|
||||||
|
//
|
||||||
|
// Definitions for HopeRF LoRa radios per:
|
||||||
|
// http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf
|
||||||
|
// http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf
|
||||||
|
//
|
||||||
|
// Author: Mike McCauley (mikem@airspayce.com)
|
||||||
|
// Copyright (C) 2014 Mike McCauley
|
||||||
|
// $Id: RH_RF95.h,v 1.23 2019/11/02 02:34:22 mikem Exp $
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef RH_RF95_h
|
||||||
|
#define RH_RF95_h
|
||||||
|
|
||||||
|
#include <RHSPIDriver.h>
|
||||||
|
|
||||||
|
// This is the maximum number of interrupts the driver can support
|
||||||
|
// Most Arduinos can handle 2, Megas can handle more
|
||||||
|
#define RH_RF95_NUM_INTERRUPTS 3
|
||||||
|
|
||||||
|
// Max number of octets the LORA Rx/Tx FIFO can hold
|
||||||
|
#define RH_RF95_FIFO_SIZE 255
|
||||||
|
|
||||||
|
// This is the maximum number of bytes that can be carried by the LORA.
|
||||||
|
// We use some for headers, keeping fewer for RadioHead messages
|
||||||
|
#define RH_RF95_MAX_PAYLOAD_LEN RH_RF95_FIFO_SIZE
|
||||||
|
|
||||||
|
// The length of the headers we add.
|
||||||
|
// The headers are inside the LORA's payload
|
||||||
|
#define RH_RF95_HEADER_LEN 4
|
||||||
|
|
||||||
|
// This is the maximum message length that can be supported by this driver.
|
||||||
|
// Can be pre-defined to a smaller size (to save SRAM) prior to including this header
|
||||||
|
// Here we allow for 1 byte message length, 4 bytes headers, user data and 2 bytes of FCS
|
||||||
|
#ifndef RH_RF95_MAX_MESSAGE_LEN
|
||||||
|
#define RH_RF95_MAX_MESSAGE_LEN (RH_RF95_MAX_PAYLOAD_LEN - RH_RF95_HEADER_LEN)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The crystal oscillator frequency of the module
|
||||||
|
#define RH_RF95_FXOSC 32000000.0
|
||||||
|
|
||||||
|
// The Frequency Synthesizer step = RH_RF95_FXOSC / 2^^19
|
||||||
|
#define RH_RF95_FSTEP (RH_RF95_FXOSC / 524288)
|
||||||
|
|
||||||
|
|
||||||
|
// Register names (LoRa Mode, from table 85)
|
||||||
|
#define RH_RF95_REG_00_FIFO 0x00
|
||||||
|
#define RH_RF95_REG_01_OP_MODE 0x01
|
||||||
|
#define RH_RF95_REG_02_RESERVED 0x02
|
||||||
|
#define RH_RF95_REG_03_RESERVED 0x03
|
||||||
|
#define RH_RF95_REG_04_RESERVED 0x04
|
||||||
|
#define RH_RF95_REG_05_RESERVED 0x05
|
||||||
|
#define RH_RF95_REG_06_FRF_MSB 0x06
|
||||||
|
#define RH_RF95_REG_07_FRF_MID 0x07
|
||||||
|
#define RH_RF95_REG_08_FRF_LSB 0x08
|
||||||
|
#define RH_RF95_REG_09_PA_CONFIG 0x09
|
||||||
|
#define RH_RF95_REG_0A_PA_RAMP 0x0a
|
||||||
|
#define RH_RF95_REG_0B_OCP 0x0b
|
||||||
|
#define RH_RF95_REG_0C_LNA 0x0c
|
||||||
|
#define RH_RF95_REG_0D_FIFO_ADDR_PTR 0x0d
|
||||||
|
#define RH_RF95_REG_0E_FIFO_TX_BASE_ADDR 0x0e
|
||||||
|
#define RH_RF95_REG_0F_FIFO_RX_BASE_ADDR 0x0f
|
||||||
|
#define RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR 0x10
|
||||||
|
#define RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11
|
||||||
|
#define RH_RF95_REG_12_IRQ_FLAGS 0x12
|
||||||
|
#define RH_RF95_REG_13_RX_NB_BYTES 0x13
|
||||||
|
#define RH_RF95_REG_14_RX_HEADER_CNT_VALUE_MSB 0x14
|
||||||
|
#define RH_RF95_REG_15_RX_HEADER_CNT_VALUE_LSB 0x15
|
||||||
|
#define RH_RF95_REG_16_RX_PACKET_CNT_VALUE_MSB 0x16
|
||||||
|
#define RH_RF95_REG_17_RX_PACKET_CNT_VALUE_LSB 0x17
|
||||||
|
#define RH_RF95_REG_18_MODEM_STAT 0x18
|
||||||
|
#define RH_RF95_REG_19_PKT_SNR_VALUE 0x19
|
||||||
|
#define RH_RF95_REG_1A_PKT_RSSI_VALUE 0x1a
|
||||||
|
#define RH_RF95_REG_1B_RSSI_VALUE 0x1b
|
||||||
|
#define RH_RF95_REG_1C_HOP_CHANNEL 0x1c
|
||||||
|
#define RH_RF95_REG_1D_MODEM_CONFIG1 0x1d
|
||||||
|
#define RH_RF95_REG_1E_MODEM_CONFIG2 0x1e
|
||||||
|
#define RH_RF95_REG_1F_SYMB_TIMEOUT_LSB 0x1f
|
||||||
|
#define RH_RF95_REG_20_PREAMBLE_MSB 0x20
|
||||||
|
#define RH_RF95_REG_21_PREAMBLE_LSB 0x21
|
||||||
|
#define RH_RF95_REG_22_PAYLOAD_LENGTH 0x22
|
||||||
|
#define RH_RF95_REG_23_MAX_PAYLOAD_LENGTH 0x23
|
||||||
|
#define RH_RF95_REG_24_HOP_PERIOD 0x24
|
||||||
|
#define RH_RF95_REG_25_FIFO_RX_BYTE_ADDR 0x25
|
||||||
|
#define RH_RF95_REG_26_MODEM_CONFIG3 0x26
|
||||||
|
|
||||||
|
#define RH_RF95_REG_27_PPM_CORRECTION 0x27
|
||||||
|
#define RH_RF95_REG_28_FEI_MSB 0x28
|
||||||
|
#define RH_RF95_REG_29_FEI_MID 0x29
|
||||||
|
#define RH_RF95_REG_2A_FEI_LSB 0x2a
|
||||||
|
#define RH_RF95_REG_2C_RSSI_WIDEBAND 0x2c
|
||||||
|
#define RH_RF95_REG_31_DETECT_OPTIMIZE 0x31
|
||||||
|
#define RH_RF95_REG_33_INVERT_IQ 0x33
|
||||||
|
#define RH_RF95_REG_37_DETECTION_THRESHOLD 0x37
|
||||||
|
#define RH_RF95_REG_39_SYNC_WORD 0x39
|
||||||
|
|
||||||
|
#define RH_RF95_REG_40_DIO_MAPPING1 0x40
|
||||||
|
#define RH_RF95_REG_41_DIO_MAPPING2 0x41
|
||||||
|
#define RH_RF95_REG_42_VERSION 0x42
|
||||||
|
|
||||||
|
#define RH_RF95_REG_4B_TCXO 0x4b
|
||||||
|
#define RH_RF95_REG_4D_PA_DAC 0x4d
|
||||||
|
#define RH_RF95_REG_5B_FORMER_TEMP 0x5b
|
||||||
|
#define RH_RF95_REG_61_AGC_REF 0x61
|
||||||
|
#define RH_RF95_REG_62_AGC_THRESH1 0x62
|
||||||
|
#define RH_RF95_REG_63_AGC_THRESH2 0x63
|
||||||
|
#define RH_RF95_REG_64_AGC_THRESH3 0x64
|
||||||
|
|
||||||
|
// RH_RF95_REG_01_OP_MODE 0x01
|
||||||
|
#define RH_RF95_LONG_RANGE_MODE 0x80
|
||||||
|
#define RH_RF95_ACCESS_SHARED_REG 0x40
|
||||||
|
#define RH_RF95_LOW_FREQUENCY_MODE 0x08
|
||||||
|
#define RH_RF95_MODE 0x07
|
||||||
|
#define RH_RF95_MODE_SLEEP 0x00
|
||||||
|
#define RH_RF95_MODE_STDBY 0x01
|
||||||
|
#define RH_RF95_MODE_FSTX 0x02
|
||||||
|
#define RH_RF95_MODE_TX 0x03
|
||||||
|
#define RH_RF95_MODE_FSRX 0x04
|
||||||
|
#define RH_RF95_MODE_RXCONTINUOUS 0x05
|
||||||
|
#define RH_RF95_MODE_RXSINGLE 0x06
|
||||||
|
#define RH_RF95_MODE_CAD 0x07
|
||||||
|
|
||||||
|
// RH_RF95_REG_09_PA_CONFIG 0x09
|
||||||
|
#define RH_RF95_PA_SELECT 0x80
|
||||||
|
#define RH_RF95_MAX_POWER 0x70
|
||||||
|
#define RH_RF95_OUTPUT_POWER 0x0f
|
||||||
|
|
||||||
|
// RH_RF95_REG_0A_PA_RAMP 0x0a
|
||||||
|
#define RH_RF95_LOW_PN_TX_PLL_OFF 0x10
|
||||||
|
#define RH_RF95_PA_RAMP 0x0f
|
||||||
|
#define RH_RF95_PA_RAMP_3_4MS 0x00
|
||||||
|
#define RH_RF95_PA_RAMP_2MS 0x01
|
||||||
|
#define RH_RF95_PA_RAMP_1MS 0x02
|
||||||
|
#define RH_RF95_PA_RAMP_500US 0x03
|
||||||
|
#define RH_RF95_PA_RAMP_250US 0x04
|
||||||
|
#define RH_RF95_PA_RAMP_125US 0x05
|
||||||
|
#define RH_RF95_PA_RAMP_100US 0x06
|
||||||
|
#define RH_RF95_PA_RAMP_62US 0x07
|
||||||
|
#define RH_RF95_PA_RAMP_50US 0x08
|
||||||
|
#define RH_RF95_PA_RAMP_40US 0x09
|
||||||
|
#define RH_RF95_PA_RAMP_31US 0x0a
|
||||||
|
#define RH_RF95_PA_RAMP_25US 0x0b
|
||||||
|
#define RH_RF95_PA_RAMP_20US 0x0c
|
||||||
|
#define RH_RF95_PA_RAMP_15US 0x0d
|
||||||
|
#define RH_RF95_PA_RAMP_12US 0x0e
|
||||||
|
#define RH_RF95_PA_RAMP_10US 0x0f
|
||||||
|
|
||||||
|
// RH_RF95_REG_0B_OCP 0x0b
|
||||||
|
#define RH_RF95_OCP_ON 0x20
|
||||||
|
#define RH_RF95_OCP_TRIM 0x1f
|
||||||
|
|
||||||
|
// RH_RF95_REG_0C_LNA 0x0c
|
||||||
|
#define RH_RF95_LNA_GAIN 0xe0
|
||||||
|
#define RH_RF95_LNA_GAIN_G1 0x20
|
||||||
|
#define RH_RF95_LNA_GAIN_G2 0x40
|
||||||
|
#define RH_RF95_LNA_GAIN_G3 0x60
|
||||||
|
#define RH_RF95_LNA_GAIN_G4 0x80
|
||||||
|
#define RH_RF95_LNA_GAIN_G5 0xa0
|
||||||
|
#define RH_RF95_LNA_GAIN_G6 0xc0
|
||||||
|
#define RH_RF95_LNA_BOOST_LF 0x18
|
||||||
|
#define RH_RF95_LNA_BOOST_LF_DEFAULT 0x00
|
||||||
|
#define RH_RF95_LNA_BOOST_HF 0x03
|
||||||
|
#define RH_RF95_LNA_BOOST_HF_DEFAULT 0x00
|
||||||
|
#define RH_RF95_LNA_BOOST_HF_150PC 0x03
|
||||||
|
|
||||||
|
// RH_RF95_REG_11_IRQ_FLAGS_MASK 0x11
|
||||||
|
#define RH_RF95_RX_TIMEOUT_MASK 0x80
|
||||||
|
#define RH_RF95_RX_DONE_MASK 0x40
|
||||||
|
#define RH_RF95_PAYLOAD_CRC_ERROR_MASK 0x20
|
||||||
|
#define RH_RF95_VALID_HEADER_MASK 0x10
|
||||||
|
#define RH_RF95_TX_DONE_MASK 0x08
|
||||||
|
#define RH_RF95_CAD_DONE_MASK 0x04
|
||||||
|
#define RH_RF95_FHSS_CHANGE_CHANNEL_MASK 0x02
|
||||||
|
#define RH_RF95_CAD_DETECTED_MASK 0x01
|
||||||
|
|
||||||
|
// RH_RF95_REG_12_IRQ_FLAGS 0x12
|
||||||
|
#define RH_RF95_RX_TIMEOUT 0x80
|
||||||
|
#define RH_RF95_RX_DONE 0x40
|
||||||
|
#define RH_RF95_PAYLOAD_CRC_ERROR 0x20
|
||||||
|
#define RH_RF95_VALID_HEADER 0x10
|
||||||
|
#define RH_RF95_TX_DONE 0x08
|
||||||
|
#define RH_RF95_CAD_DONE 0x04
|
||||||
|
#define RH_RF95_FHSS_CHANGE_CHANNEL 0x02
|
||||||
|
#define RH_RF95_CAD_DETECTED 0x01
|
||||||
|
|
||||||
|
// RH_RF95_REG_18_MODEM_STAT 0x18
|
||||||
|
#define RH_RF95_RX_CODING_RATE 0xe0
|
||||||
|
#define RH_RF95_MODEM_STATUS_CLEAR 0x10
|
||||||
|
#define RH_RF95_MODEM_STATUS_HEADER_INFO_VALID 0x08
|
||||||
|
#define RH_RF95_MODEM_STATUS_RX_ONGOING 0x04
|
||||||
|
#define RH_RF95_MODEM_STATUS_SIGNAL_SYNCHRONIZED 0x02
|
||||||
|
#define RH_RF95_MODEM_STATUS_SIGNAL_DETECTED 0x01
|
||||||
|
|
||||||
|
// RH_RF95_REG_1C_HOP_CHANNEL 0x1c
|
||||||
|
#define RH_RF95_PLL_TIMEOUT 0x80
|
||||||
|
#define RH_RF95_RX_PAYLOAD_CRC_IS_ON 0x40
|
||||||
|
#define RH_RF95_FHSS_PRESENT_CHANNEL 0x3f
|
||||||
|
|
||||||
|
// RH_RF95_REG_1D_MODEM_CONFIG1 0x1d
|
||||||
|
#define RH_RF95_BW 0xf0
|
||||||
|
|
||||||
|
#define RH_RF95_BW_7_8KHZ 0x00
|
||||||
|
#define RH_RF95_BW_10_4KHZ 0x10
|
||||||
|
#define RH_RF95_BW_15_6KHZ 0x20
|
||||||
|
#define RH_RF95_BW_20_8KHZ 0x30
|
||||||
|
#define RH_RF95_BW_31_25KHZ 0x40
|
||||||
|
#define RH_RF95_BW_41_7KHZ 0x50
|
||||||
|
#define RH_RF95_BW_62_5KHZ 0x60
|
||||||
|
#define RH_RF95_BW_125KHZ 0x70
|
||||||
|
#define RH_RF95_BW_250KHZ 0x80
|
||||||
|
#define RH_RF95_BW_500KHZ 0x90
|
||||||
|
#define RH_RF95_CODING_RATE 0x0e
|
||||||
|
#define RH_RF95_CODING_RATE_4_5 0x02
|
||||||
|
#define RH_RF95_CODING_RATE_4_6 0x04
|
||||||
|
#define RH_RF95_CODING_RATE_4_7 0x06
|
||||||
|
#define RH_RF95_CODING_RATE_4_8 0x08
|
||||||
|
#define RH_RF95_IMPLICIT_HEADER_MODE_ON 0x01
|
||||||
|
|
||||||
|
// RH_RF95_REG_1E_MODEM_CONFIG2 0x1e
|
||||||
|
#define RH_RF95_SPREADING_FACTOR 0xf0
|
||||||
|
#define RH_RF95_SPREADING_FACTOR_64CPS 0x60
|
||||||
|
#define RH_RF95_SPREADING_FACTOR_128CPS 0x70
|
||||||
|
#define RH_RF95_SPREADING_FACTOR_256CPS 0x80
|
||||||
|
#define RH_RF95_SPREADING_FACTOR_512CPS 0x90
|
||||||
|
#define RH_RF95_SPREADING_FACTOR_1024CPS 0xa0
|
||||||
|
#define RH_RF95_SPREADING_FACTOR_2048CPS 0xb0
|
||||||
|
#define RH_RF95_SPREADING_FACTOR_4096CPS 0xc0
|
||||||
|
#define RH_RF95_TX_CONTINUOUS_MODE 0x08
|
||||||
|
|
||||||
|
#define RH_RF95_PAYLOAD_CRC_ON 0x04
|
||||||
|
#define RH_RF95_SYM_TIMEOUT_MSB 0x03
|
||||||
|
|
||||||
|
// RH_RF95_REG_26_MODEM_CONFIG3
|
||||||
|
#define RH_RF95_MOBILE_NODE 0x08 // HopeRF term
|
||||||
|
#define RH_RF95_LOW_DATA_RATE_OPTIMIZE 0x08 // Semtechs term
|
||||||
|
#define RH_RF95_AGC_AUTO_ON 0x04
|
||||||
|
|
||||||
|
// RH_RF95_REG_4B_TCXO 0x4b
|
||||||
|
#define RH_RF95_TCXO_TCXO_INPUT_ON 0x10
|
||||||
|
|
||||||
|
// RH_RF95_REG_4D_PA_DAC 0x4d
|
||||||
|
#define RH_RF95_PA_DAC_DISABLE 0x04
|
||||||
|
#define RH_RF95_PA_DAC_ENABLE 0x07
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////
|
||||||
|
/// \class RH_RF95 RH_RF95.h <RH_RF95.h>
|
||||||
|
/// \brief Driver to send and receive unaddressed, unreliable datagrams via a LoRa
|
||||||
|
/// capable radio transceiver.
|
||||||
|
///
|
||||||
|
/// For Semtech SX1276/77/78/79 and HopeRF RF95/96/97/98 and other similar LoRa capable radios.
|
||||||
|
/// Based on http://www.hoperf.com/upload/rf/RFM95_96_97_98W.pdf
|
||||||
|
/// and http://www.hoperf.cn/upload/rfchip/RF96_97_98.pdf
|
||||||
|
/// and http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf
|
||||||
|
/// and http://www.semtech.com/images/datasheet/sx1276.pdf
|
||||||
|
/// and http://www.semtech.com/images/datasheet/sx1276_77_78_79.pdf
|
||||||
|
/// FSK/GFSK/OOK modes are not (yet) supported.
|
||||||
|
///
|
||||||
|
/// Works with
|
||||||
|
/// - the excellent MiniWirelessLoRa from Anarduino http://www.anarduino.com/miniwireless
|
||||||
|
/// - The excellent Modtronix inAir4 http://modtronix.com/inair4.html
|
||||||
|
/// and inAir9 modules http://modtronix.com/inair9.html.
|
||||||
|
/// - the excellent Rocket Scream Mini Ultra Pro with the RFM95W
|
||||||
|
/// http://www.rocketscream.com/blog/product/mini-ultra-pro-with-radio/
|
||||||
|
/// - Lora1276 module from NiceRF http://www.nicerf.com/product_view.aspx?id=99
|
||||||
|
/// - Adafruit Feather M0 with RFM95
|
||||||
|
/// - The very fine Talk2 Whisper Node LoRa boards https://wisen.com.au/store/products/whisper-node-lora
|
||||||
|
/// an Arduino compatible board, which include an on-board RFM95/96 LoRa Radio (Semtech SX1276), external antenna,
|
||||||
|
/// run on 2xAAA batteries and support low power operations. RF95 examples work without modification.
|
||||||
|
/// Use Arduino Board Manager to install the Talk2 code support. Upload the code with an FTDI adapter set to 5V.
|
||||||
|
/// - heltec / TTGO ESP32 LoRa OLED https://www.aliexpress.com/item/Internet-Development-Board-SX1278-ESP32-WIFI-chip-0-96-inch-OLED-Bluetooth-WIFI-Lora-Kit-32/32824535649.html
|
||||||
|
///
|
||||||
|
/// \par Overview
|
||||||
|
///
|
||||||
|
/// This class provides basic functions for sending and receiving unaddressed,
|
||||||
|
/// unreliable datagrams of arbitrary length to 251 octets per packet.
|
||||||
|
///
|
||||||
|
/// Manager classes may use this class to implement reliable, addressed datagrams and streams,
|
||||||
|
/// mesh routers, repeaters, translators etc.
|
||||||
|
///
|
||||||
|
/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and
|
||||||
|
/// modulation scheme.
|
||||||
|
///
|
||||||
|
/// This Driver provides an object-oriented interface for sending and receiving data messages with Hope-RF
|
||||||
|
/// RFM95/96/97/98(W), Semtech SX1276/77/78/79 and compatible radio modules in LoRa mode.
|
||||||
|
///
|
||||||
|
/// The Hope-RF (http://www.hoperf.com) RFM95/96/97/98(W) and Semtech SX1276/77/78/79 is a low-cost ISM transceiver
|
||||||
|
/// chip. It supports FSK, GFSK, OOK over a wide range of frequencies and
|
||||||
|
/// programmable data rates, and it also supports the proprietary LoRA (Long Range) mode, which
|
||||||
|
/// is the only mode supported in this RadioHead driver.
|
||||||
|
///
|
||||||
|
/// This Driver provides functions for sending and receiving messages of up
|
||||||
|
/// to 251 octets on any frequency supported by the radio, in a range of
|
||||||
|
/// predefined Bandwidths, Spreading Factors and Coding Rates. Frequency can be set with
|
||||||
|
/// 61Hz precision to any frequency from 240.0MHz to 960.0MHz. Caution: most modules only support a more limited
|
||||||
|
/// range of frequencies due to antenna tuning.
|
||||||
|
///
|
||||||
|
/// Up to 2 modules can be connected to an Arduino (3 on a Mega),
|
||||||
|
/// permitting the construction of translators and frequency changers, etc.
|
||||||
|
///
|
||||||
|
/// Support for other features such as transmitter power control etc is
|
||||||
|
/// also provided.
|
||||||
|
///
|
||||||
|
/// Tested on MinWirelessLoRa with arduino-1.0.5
|
||||||
|
/// on OpenSuSE 13.1.
|
||||||
|
/// Also tested with Teensy3.1, Modtronix inAir4 and Arduino 1.6.5 on OpenSuSE 13.1
|
||||||
|
///
|
||||||
|
/// \par Packet Format
|
||||||
|
///
|
||||||
|
/// All messages sent and received by this RH_RF95 Driver conform to this packet format:
|
||||||
|
///
|
||||||
|
/// - LoRa mode:
|
||||||
|
/// - 8 symbol PREAMBLE
|
||||||
|
/// - Explicit header with header CRC (handled internally by the radio)
|
||||||
|
/// - 4 octets HEADER: (TO, FROM, ID, FLAGS)
|
||||||
|
/// - 0 to 251 octets DATA
|
||||||
|
/// - CRC (handled internally by the radio)
|
||||||
|
///
|
||||||
|
/// \par Connecting RFM95/96/97/98 and Semtech SX1276/77/78/79 to Arduino
|
||||||
|
///
|
||||||
|
/// We tested with Anarduino MiniWirelessLoRA, which is an Arduino Duemilanove compatible with a RFM96W
|
||||||
|
/// module on-board. Therefore it needs no connections other than the USB
|
||||||
|
/// programming connection and an antenna to make it work.
|
||||||
|
///
|
||||||
|
/// If you have a bare RFM95/96/97/98 that you want to connect to an Arduino, you
|
||||||
|
/// might use these connections (untested): CAUTION: you must use a 3.3V type
|
||||||
|
/// Arduino, otherwise you will also need voltage level shifters between the
|
||||||
|
/// Arduino and the RFM95. CAUTION, you must also ensure you connect an
|
||||||
|
/// antenna.
|
||||||
|
///
|
||||||
|
/// \code
|
||||||
|
/// Arduino RFM95/96/97/98
|
||||||
|
/// GND----------GND (ground in)
|
||||||
|
/// 3V3----------3.3V (3.3V in)
|
||||||
|
/// interrupt 0 pin D2-----------DIO0 (interrupt request out)
|
||||||
|
/// SS pin D10----------NSS (CS chip select in)
|
||||||
|
/// SCK pin D13----------SCK (SPI clock in)
|
||||||
|
/// MOSI pin D11----------MOSI (SPI Data in)
|
||||||
|
/// MISO pin D12----------MISO (SPI Data out)
|
||||||
|
/// \endcode
|
||||||
|
/// With these connections, you can then use the default constructor RH_RF95().
|
||||||
|
/// You can override the default settings for the SS pin and the interrupt in
|
||||||
|
/// the RH_RF95 constructor if you wish to connect the slave select SS to other
|
||||||
|
/// than the normal one for your Arduino (D10 for Diecimila, Uno etc and D53
|
||||||
|
/// for Mega) or the interrupt request to other than pin D2 (Caution,
|
||||||
|
/// different processors have different constraints as to the pins available
|
||||||
|
/// for interrupts).
|
||||||
|
///
|
||||||
|
/// You can connect a Modtronix inAir4 or inAir9 directly to a 3.3V part such as a Teensy 3.1 like
|
||||||
|
/// this (tested).
|
||||||
|
/// \code
|
||||||
|
/// Teensy inAir4 inAir9
|
||||||
|
/// GND----------0V (ground in)
|
||||||
|
/// 3V3----------3.3V (3.3V in)
|
||||||
|
/// interrupt 0 pin D2-----------D0 (interrupt request out)
|
||||||
|
/// SS pin D10----------CS (CS chip select in)
|
||||||
|
/// SCK pin D13----------CK (SPI clock in)
|
||||||
|
/// MOSI pin D11----------SI (SPI Data in)
|
||||||
|
/// MISO pin D12----------SO (SPI Data out)
|
||||||
|
/// \endcode
|
||||||
|
/// With these connections, you can then use the default constructor RH_RF95().
|
||||||
|
/// you must also set the transmitter power with useRFO:
|
||||||
|
/// driver.setTxPower(13, true);
|
||||||
|
///
|
||||||
|
/// Note that if you are using Modtronix inAir4 or inAir9,or any other module which uses the
|
||||||
|
/// transmitter RFO pins and not the PA_BOOST pins
|
||||||
|
/// that you must configure the power transmitter power for -1 to 14 dBm and with useRFO true.
|
||||||
|
/// Failure to do that will result in extremely low transmit powers.
|
||||||
|
///
|
||||||
|
/// If you have an Arduino M0 Pro from arduino.org,
|
||||||
|
/// you should note that you cannot use Pin 2 for the interrupt line
|
||||||
|
/// (Pin 2 is for the NMI only). The same comments apply to Pin 4 on Arduino Zero from arduino.cc.
|
||||||
|
/// Instead you can use any other pin (we use Pin 3) and initialise RH_RF69 like this:
|
||||||
|
/// \code
|
||||||
|
/// // Slave Select is pin 10, interrupt is Pin 3
|
||||||
|
/// RH_RF95 driver(10, 3);
|
||||||
|
/// \endcode
|
||||||
|
///
|
||||||
|
/// If you have a Rocket Scream Mini Ultra Pro with the RFM95W:
|
||||||
|
/// - Ensure you have Arduino SAMD board support 1.6.5 or later in Arduino IDE 1.6.8 or later.
|
||||||
|
/// - The radio SS is hardwired to pin D5 and the DIO0 interrupt to pin D2,
|
||||||
|
/// so you need to initialise the radio like this:
|
||||||
|
/// \code
|
||||||
|
/// RH_RF95 driver(5, 2);
|
||||||
|
/// \endcode
|
||||||
|
/// - The name of the serial port on that board is 'SerialUSB', not 'Serial', so this may be helpful at the top of our
|
||||||
|
/// sample sketches:
|
||||||
|
/// \code
|
||||||
|
/// #define Serial SerialUSB
|
||||||
|
/// \endcode
|
||||||
|
/// - You also need this in setup before radio initialisation
|
||||||
|
/// \code
|
||||||
|
/// // Ensure serial flash is not interfering with radio communication on SPI bus
|
||||||
|
/// pinMode(4, OUTPUT);
|
||||||
|
/// digitalWrite(4, HIGH);
|
||||||
|
/// \endcode
|
||||||
|
/// - and if you have a 915MHz part, you need this after driver/manager intitalisation:
|
||||||
|
/// \code
|
||||||
|
/// rf95.setFrequency(915.0);
|
||||||
|
/// \endcode
|
||||||
|
/// which adds up to modifying sample sketches something like:
|
||||||
|
/// \code
|
||||||
|
/// #include <SPI.h>
|
||||||
|
/// #include <RH_RF95.h>
|
||||||
|
/// RH_RF95 rf95(5, 2); // Rocket Scream Mini Ultra Pro with the RFM95W
|
||||||
|
/// #define Serial SerialUSB
|
||||||
|
///
|
||||||
|
/// void setup()
|
||||||
|
/// {
|
||||||
|
/// // Ensure serial flash is not interfering with radio communication on SPI bus
|
||||||
|
/// pinMode(4, OUTPUT);
|
||||||
|
/// digitalWrite(4, HIGH);
|
||||||
|
///
|
||||||
|
/// Serial.begin(9600);
|
||||||
|
/// while (!Serial) ; // Wait for serial port to be available
|
||||||
|
/// if (!rf95.init())
|
||||||
|
/// Serial.println("init failed");
|
||||||
|
/// rf95.setFrequency(915.0);
|
||||||
|
/// }
|
||||||
|
/// ...
|
||||||
|
/// \endcode
|
||||||
|
///
|
||||||
|
/// For Adafruit Feather M0 with RFM95, construct the driver like this:
|
||||||
|
/// \code
|
||||||
|
/// RH_RF95 rf95(8, 3);
|
||||||
|
/// \endcode
|
||||||
|
///
|
||||||
|
/// If you have a talk2 Whisper Node LoRa board with on-board RF95 radio,
|
||||||
|
/// the example rf95_* sketches work without modification. Initialise the radio like
|
||||||
|
/// with the default constructor:
|
||||||
|
/// \code
|
||||||
|
/// RH_RF95 driver;
|
||||||
|
/// \endcode
|
||||||
|
///
|
||||||
|
/// It is possible to have 2 or more radios connected to one Arduino, provided
|
||||||
|
/// each radio has its own SS and interrupt line (SCK, SDI and SDO are common
|
||||||
|
/// to all radios)
|
||||||
|
///
|
||||||
|
/// Caution: on some Arduinos such as the Mega 2560, if you set the slave
|
||||||
|
/// select pin to be other than the usual SS pin (D53 on Mega 2560), you may
|
||||||
|
/// need to set the usual SS pin to be an output to force the Arduino into SPI
|
||||||
|
/// master mode.
|
||||||
|
///
|
||||||
|
/// Caution: Power supply requirements of the RFM module may be relevant in some circumstances:
|
||||||
|
/// RFM95/96/97/98 modules are capable of pulling 120mA+ at full power, where Arduino's 3.3V line can
|
||||||
|
/// give 50mA. You may need to make provision for alternate power supply for
|
||||||
|
/// the RFM module, especially if you wish to use full transmit power, and/or you have
|
||||||
|
/// other shields demanding power. Inadequate power for the RFM is likely to cause symptoms such as:
|
||||||
|
/// - reset's/bootups terminate with "init failed" messages
|
||||||
|
/// - random termination of communication after 5-30 packets sent/received
|
||||||
|
/// - "fake ok" state, where initialization passes fluently, but communication doesn't happen
|
||||||
|
/// - shields hang Arduino boards, especially during the flashing
|
||||||
|
///
|
||||||
|
/// \par Interrupts
|
||||||
|
///
|
||||||
|
/// The RH_RF95 driver uses interrupts to react to events in the RFM module,
|
||||||
|
/// such as the reception of a new packet, or the completion of transmission
|
||||||
|
/// of a packet. The RH_RF95 driver interrupt service routine reads status from
|
||||||
|
/// and writes data to the the RFM module via the SPI interface. It is very
|
||||||
|
/// important therefore, that if you are using the RH_RF95 driver with another
|
||||||
|
/// SPI based deviced, that you disable interrupts while you transfer data to
|
||||||
|
/// and from that other device. Use cli() to disable interrupts and sei() to
|
||||||
|
/// reenable them.
|
||||||
|
///
|
||||||
|
/// \par Memory
|
||||||
|
///
|
||||||
|
/// The RH_RF95 driver requires non-trivial amounts of memory. The sample
|
||||||
|
/// programs all compile to about 8kbytes each, which will fit in the
|
||||||
|
/// flash proram memory of most Arduinos. However, the RAM requirements are
|
||||||
|
/// more critical. Therefore, you should be vary sparing with RAM use in
|
||||||
|
/// programs that use the RH_RF95 driver.
|
||||||
|
///
|
||||||
|
/// It is often hard to accurately identify when you are hitting RAM limits on Arduino.
|
||||||
|
/// The symptoms can include:
|
||||||
|
/// - Mysterious crashes and restarts
|
||||||
|
/// - Changes in behaviour when seemingly unrelated changes are made (such as adding print() statements)
|
||||||
|
/// - Hanging
|
||||||
|
/// - Output from Serial.print() not appearing
|
||||||
|
///
|
||||||
|
/// \par Range
|
||||||
|
///
|
||||||
|
/// We have made some simple range tests under the following conditions:
|
||||||
|
/// - rf95_client base station connected to a VHF discone antenna at 8m height above ground
|
||||||
|
/// - rf95_server mobile connected to 17.3cm 1/4 wavelength antenna at 1m height, no ground plane.
|
||||||
|
/// - Both configured for 13dBm, 434MHz, Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range
|
||||||
|
/// - Minimum reported RSSI seen for successful comms was about -91
|
||||||
|
/// - Range over flat ground through heavy trees and vegetation approx 2km.
|
||||||
|
/// - At 20dBm (100mW) otherwise identical conditions approx 3km.
|
||||||
|
/// - At 20dBm, along salt water flat sandy beach, 3.2km.
|
||||||
|
///
|
||||||
|
/// It should be noted that at this data rate, a 12 octet message takes 2 seconds to transmit.
|
||||||
|
///
|
||||||
|
/// At 20dBm (100mW) with Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on.
|
||||||
|
/// (Default medium range) in the conditions described above.
|
||||||
|
/// - Range over flat ground through heavy trees and vegetation approx 2km.
|
||||||
|
///
|
||||||
|
/// Caution: the performance of this radio, especially with narrow bandwidths is strongly dependent on the
|
||||||
|
/// accuracy and stability of the chip clock. HopeRF and Semtech do not appear to
|
||||||
|
/// recommend bandwidths of less than 62.5 kHz
|
||||||
|
/// unless you have the optional Temperature Compensated Crystal Oscillator (TCXO) installed and
|
||||||
|
/// enabled on your radio module. See the refernece manual for more data.
|
||||||
|
/// Also https://lowpowerlab.com/forum/rf-range-antennas-rfm69-library/lora-library-experiences-range/15/
|
||||||
|
/// and http://www.semtech.com/images/datasheet/an120014-xo-guidance-lora-modulation.pdf
|
||||||
|
///
|
||||||
|
/// \par Transmitter Power
|
||||||
|
///
|
||||||
|
/// You can control the transmitter power on the RF transceiver
|
||||||
|
/// with the RH_RF95::setTxPower() function. The argument can be any of
|
||||||
|
/// +5 to +23 (for modules that use PA_BOOST)
|
||||||
|
/// -1 to +14 (for modules that use RFO transmitter pin)
|
||||||
|
/// The default is 13. Eg:
|
||||||
|
/// \code
|
||||||
|
/// driver.setTxPower(10); // use PA_BOOST transmitter pin
|
||||||
|
/// driver.setTxPower(10, true); // use PA_RFO pin transmitter pin
|
||||||
|
/// \endcode
|
||||||
|
///
|
||||||
|
/// We have made some actual power measurements against
|
||||||
|
/// programmed power for Anarduino MiniWirelessLoRa (which has RFM96W-433Mhz installed)
|
||||||
|
/// - MiniWirelessLoRa RFM96W-433Mhz, USB power
|
||||||
|
/// - 30cm RG316 soldered direct to RFM96W module ANT and GND
|
||||||
|
/// - SMA connector
|
||||||
|
/// - 12db attenuator
|
||||||
|
/// - SMA connector
|
||||||
|
/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set)
|
||||||
|
/// - Tektronix TDS220 scope to measure the Vout from power head
|
||||||
|
/// \code
|
||||||
|
/// Program power Measured Power
|
||||||
|
/// dBm dBm
|
||||||
|
/// 5 5
|
||||||
|
/// 7 7
|
||||||
|
/// 9 8
|
||||||
|
/// 11 11
|
||||||
|
/// 13 13
|
||||||
|
/// 15 15
|
||||||
|
/// 17 16
|
||||||
|
/// 19 18
|
||||||
|
/// 20 20
|
||||||
|
/// 21 21
|
||||||
|
/// 22 22
|
||||||
|
/// 23 23
|
||||||
|
/// \endcode
|
||||||
|
///
|
||||||
|
/// We have also measured the actual power output from a Modtronix inAir4 http://modtronix.com/inair4.html
|
||||||
|
/// connected to a Teensy 3.1:
|
||||||
|
/// Teensy 3.1 this is a 3.3V part, connected directly to:
|
||||||
|
/// Modtronix inAir4 with SMA antenna connector, connected as above:
|
||||||
|
/// 10cm SMA-SMA cable
|
||||||
|
/// - MiniKits AD8307 HF/VHF Power Head (calibrated against Rohde&Schwartz 806.2020 test set)
|
||||||
|
/// - Tektronix TDS220 scope to measure the Vout from power head
|
||||||
|
/// \code
|
||||||
|
/// Program power Measured Power
|
||||||
|
/// dBm dBm
|
||||||
|
/// -1 0
|
||||||
|
/// 1 2
|
||||||
|
/// 3 4
|
||||||
|
/// 5 7
|
||||||
|
/// 7 10
|
||||||
|
/// 9 13
|
||||||
|
/// 11 14.2
|
||||||
|
/// 13 15
|
||||||
|
/// 14 16
|
||||||
|
/// \endcode
|
||||||
|
/// (Caution: we dont claim laboratory accuracy for these power measurements)
|
||||||
|
/// You would not expect to get anywhere near these powers to air with a simple 1/4 wavelength wire antenna.
|
||||||
|
class RH_RF95 : public RHSPIDriver
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// \brief Defines register values for a set of modem configuration registers
|
||||||
|
///
|
||||||
|
/// Defines register values for a set of modem configuration registers
|
||||||
|
/// that can be passed to setModemRegisters() if none of the choices in
|
||||||
|
/// ModemConfigChoice suit your need setModemRegisters() writes the
|
||||||
|
/// register values from this structure to the appropriate registers
|
||||||
|
/// to set the desired spreading factor, coding rate and bandwidth
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t reg_1d; ///< Value for register RH_RF95_REG_1D_MODEM_CONFIG1
|
||||||
|
uint8_t reg_1e; ///< Value for register RH_RF95_REG_1E_MODEM_CONFIG2
|
||||||
|
uint8_t reg_26; ///< Value for register RH_RF95_REG_26_MODEM_CONFIG3
|
||||||
|
} ModemConfig;
|
||||||
|
|
||||||
|
/// Choices for setModemConfig() for a selected subset of common
|
||||||
|
/// data rates. If you need another configuration,
|
||||||
|
/// determine the necessary settings and call setModemRegisters() with your
|
||||||
|
/// desired settings. It might be helpful to use the LoRa calculator mentioned in
|
||||||
|
/// http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf
|
||||||
|
/// These are indexes into MODEM_CONFIG_TABLE. We strongly recommend you use these symbolic
|
||||||
|
/// definitions and not their integer equivalents: its possible that new values will be
|
||||||
|
/// introduced in later versions (though we will try to avoid it).
|
||||||
|
/// Caution: if you are using slow packet rates and long packets with RHReliableDatagram or subclasses
|
||||||
|
/// you may need to change the RHReliableDatagram timeout for reliable operations.
|
||||||
|
/// Caution: for some slow rates nad with ReliableDatagrams youi may need to increase the reply timeout
|
||||||
|
/// with manager.setTimeout() to
|
||||||
|
/// deal with the long transmission times.
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
Bw125Cr45Sf128 = 0, ///< Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Default medium range
|
||||||
|
Bw500Cr45Sf128, ///< Bw = 500 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on. Fast+short range
|
||||||
|
Bw31_25Cr48Sf512, ///< Bw = 31.25 kHz, Cr = 4/8, Sf = 512chips/symbol, CRC on. Slow+long range
|
||||||
|
Bw125Cr48Sf4096, ///< Bw = 125 kHz, Cr = 4/8, Sf = 4096chips/symbol, CRC on. Slow+long range
|
||||||
|
} ModemConfigChoice;
|
||||||
|
|
||||||
|
/// Constructor. You can have multiple instances, but each instance must have its own
|
||||||
|
/// interrupt and slave select pin. After constructing, you must call init() to initialise the interface
|
||||||
|
/// and the radio module. A maximum of 3 instances can co-exist on one processor, provided there are sufficient
|
||||||
|
/// distinct interrupt lines, one for each instance.
|
||||||
|
/// \param[in] slaveSelectPin the Arduino pin number of the output to use to select the RH_RF22 before
|
||||||
|
/// accessing it. Defaults to the normal SS pin for your Arduino (D10 for Diecimila, Uno etc, D53 for Mega, D10 for Maple)
|
||||||
|
/// \param[in] interruptPin The interrupt Pin number that is connected to the RFM DIO0 interrupt line.
|
||||||
|
/// Defaults to pin 2, as required by Anarduino MinWirelessLoRa module.
|
||||||
|
/// Caution: You must specify an interrupt capable pin.
|
||||||
|
/// On many Arduino boards, there are limitations as to which pins may be used as interrupts.
|
||||||
|
/// On Leonardo pins 0, 1, 2 or 3. On Mega2560 pins 2, 3, 18, 19, 20, 21. On Due and Teensy, any digital pin.
|
||||||
|
/// On Arduino Zero from arduino.cc, any digital pin other than 4.
|
||||||
|
/// On Arduino M0 Pro from arduino.org, any digital pin other than 2.
|
||||||
|
/// On other Arduinos pins 2 or 3.
|
||||||
|
/// See http://arduino.cc/en/Reference/attachInterrupt for more details.
|
||||||
|
/// On Chipkit Uno32, pins 38, 2, 7, 8, 35.
|
||||||
|
/// On other boards, any digital pin may be used.
|
||||||
|
/// \param[in] spi Pointer to the SPI interface object to use.
|
||||||
|
/// Defaults to the standard Arduino hardware SPI interface
|
||||||
|
RH_RF95(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, RHGenericSPI& spi = hardware_spi);
|
||||||
|
|
||||||
|
/// Initialise the Driver transport hardware and software.
|
||||||
|
/// Make sure the Driver is properly configured before calling init().
|
||||||
|
/// \return true if initialisation succeeded.
|
||||||
|
virtual bool init();
|
||||||
|
|
||||||
|
/// The main CPU is about to enter deep sleep, prepare the RF95 so it will be able to wake properly after we reboot
|
||||||
|
/// i.e. confirm we are in idle or rx mode, set a rtcram flag with state we need to restore after boot. Later in boot
|
||||||
|
/// we'll need to be careful not to wipe registers and be ready to handle any pending interrupts that occurred while
|
||||||
|
/// the main CPU was powered down.
|
||||||
|
void prepareDeepSleep();
|
||||||
|
|
||||||
|
/// Prints the value of all chip registers
|
||||||
|
/// to the Serial device if RH_HAVE_SERIAL is defined for the current platform
|
||||||
|
/// For debugging purposes only.
|
||||||
|
/// \return true on success
|
||||||
|
bool printRegisters();
|
||||||
|
|
||||||
|
/// Sets all the registered required to configure the data modem in the RF95/96/97/98, including the bandwidth,
|
||||||
|
/// spreading factor etc. You can use this to configure the modem with custom configurations if none of the
|
||||||
|
/// canned configurations in ModemConfigChoice suit you.
|
||||||
|
/// \param[in] config A ModemConfig structure containing values for the modem configuration registers.
|
||||||
|
void setModemRegisters(const ModemConfig* config);
|
||||||
|
|
||||||
|
/// Select one of the predefined modem configurations. If you need a modem configuration not provided
|
||||||
|
/// here, use setModemRegisters() with your own ModemConfig.
|
||||||
|
/// Caution: the slowest protocols may require a radio module with TCXO temperature controlled oscillator
|
||||||
|
/// for reliable operation.
|
||||||
|
/// \param[in] index The configuration choice.
|
||||||
|
/// \return true if index is a valid choice.
|
||||||
|
bool setModemConfig(ModemConfigChoice index);
|
||||||
|
|
||||||
|
/// Tests whether a new message is available
|
||||||
|
/// from the Driver.
|
||||||
|
/// On most drivers, this will also put the Driver into RHModeRx mode until
|
||||||
|
/// a message is actually received by the transport, when it wil be returned to RHModeIdle.
|
||||||
|
/// This can be called multiple times in a timeout loop
|
||||||
|
/// \return true if a new, complete, error-free uncollected message is available to be retreived by recv()
|
||||||
|
virtual bool available();
|
||||||
|
|
||||||
|
/// Turns the receiver on if it not already on.
|
||||||
|
/// If there is a valid message available, copy it to buf and return true
|
||||||
|
/// else return false.
|
||||||
|
/// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted).
|
||||||
|
/// You should be sure to call this function frequently enough to not miss any messages
|
||||||
|
/// It is recommended that you call it in your main loop.
|
||||||
|
/// \param[in] buf Location to copy the received message
|
||||||
|
/// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied.
|
||||||
|
/// \return true if a valid message was copied to buf
|
||||||
|
virtual bool recv(uint8_t* buf, uint8_t* len);
|
||||||
|
|
||||||
|
/// Waits until any previous transmit packet is finished being transmitted with waitPacketSent().
|
||||||
|
/// Then optionally waits for Channel Activity Detection (CAD)
|
||||||
|
/// to show the channnel is clear (if the radio supports CAD) by calling waitCAD().
|
||||||
|
/// Then loads a message into the transmitter and starts the transmitter. Note that a message length
|
||||||
|
/// of 0 is permitted.
|
||||||
|
/// \param[in] data Array of data to be sent
|
||||||
|
/// \param[in] len Number of bytes of data to send
|
||||||
|
/// specify the maximum time in ms to wait. If 0 (the default) do not wait for CAD before transmitting.
|
||||||
|
/// \return true if the message length was valid and it was correctly queued for transmit. Return false
|
||||||
|
/// if CAD was requested and the CAD timeout timed out before clear channel was detected.
|
||||||
|
virtual bool send(const uint8_t* data, uint8_t len);
|
||||||
|
|
||||||
|
/// Sets the length of the preamble
|
||||||
|
/// in bytes.
|
||||||
|
/// Caution: this should be set to the same
|
||||||
|
/// value on all nodes in your network. Default is 8.
|
||||||
|
/// Sets the message preamble length in RH_RF95_REG_??_PREAMBLE_?SB
|
||||||
|
/// \param[in] bytes Preamble length in bytes.
|
||||||
|
void setPreambleLength(uint16_t bytes);
|
||||||
|
|
||||||
|
/// Returns the maximum message length
|
||||||
|
/// available in this Driver.
|
||||||
|
/// \return The maximum legal message length
|
||||||
|
virtual uint8_t maxMessageLength();
|
||||||
|
|
||||||
|
/// Sets the transmitter and receiver
|
||||||
|
/// centre frequency.
|
||||||
|
/// \param[in] centre Frequency in MHz. 137.0 to 1020.0. Caution: RFM95/96/97/98 comes in several
|
||||||
|
/// different frequency ranges, and setting a frequency outside that range of your radio will probably not work
|
||||||
|
/// \return true if the selected frquency centre is within range
|
||||||
|
bool setFrequency(float centre);
|
||||||
|
|
||||||
|
/// If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running,
|
||||||
|
/// disables them.
|
||||||
|
void setModeIdle();
|
||||||
|
|
||||||
|
/// If current mode is Tx or Idle, changes it to Rx.
|
||||||
|
/// Starts the receiver in the RF95/96/97/98.
|
||||||
|
void setModeRx();
|
||||||
|
|
||||||
|
/// If current mode is Rx or Idle, changes it to Rx. F
|
||||||
|
/// Starts the transmitter in the RF95/96/97/98.
|
||||||
|
void setModeTx();
|
||||||
|
|
||||||
|
/// Sets the transmitter power output level, and configures the transmitter pin.
|
||||||
|
/// Be a good neighbour and set the lowest power level you need.
|
||||||
|
/// Some SX1276/77/78/79 and compatible modules (such as RFM95/96/97/98)
|
||||||
|
/// use the PA_BOOST transmitter pin for high power output (and optionally the PA_DAC)
|
||||||
|
/// while some (such as the Modtronix inAir4 and inAir9)
|
||||||
|
/// use the RFO transmitter pin for lower power but higher efficiency.
|
||||||
|
/// You must set the appropriate power level and useRFO argument for your module.
|
||||||
|
/// Check with your module manufacturer which transmtter pin is used on your module
|
||||||
|
/// to ensure you are setting useRFO correctly.
|
||||||
|
/// Failure to do so will result in very low
|
||||||
|
/// transmitter power output.
|
||||||
|
/// Caution: legal power limits may apply in certain countries.
|
||||||
|
/// After init(), the power will be set to 13dBm, with useRFO false (ie PA_BOOST enabled).
|
||||||
|
/// \param[in] power Transmitter power level in dBm. For RFM95/96/97/98 LORA with useRFO false,
|
||||||
|
/// valid values are from +5 to +23.
|
||||||
|
/// For Modtronix inAir4 and inAir9 with useRFO true (ie RFO pins in use),
|
||||||
|
/// valid values are from -1 to 14.
|
||||||
|
/// \param[in] useRFO If true, enables the use of the RFO transmitter pins instead of
|
||||||
|
/// the PA_BOOST pin (false). Choose the correct setting for your module.
|
||||||
|
void setTxPower(int8_t power, bool useRFO = false);
|
||||||
|
|
||||||
|
/// Sets the radio into low-power sleep mode.
|
||||||
|
/// If successful, the transport will stay in sleep mode until woken by
|
||||||
|
/// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc)
|
||||||
|
/// Caution: there is a time penalty as the radio takes a finite time to wake from sleep mode.
|
||||||
|
/// \return true if sleep mode was successfully entered.
|
||||||
|
virtual bool sleep();
|
||||||
|
|
||||||
|
// Bent G Christensen (bentor@gmail.com), 08/15/2016
|
||||||
|
/// Use the radio's Channel Activity Detect (CAD) function to detect channel activity.
|
||||||
|
/// Sets the RF95 radio into CAD mode and waits until CAD detection is complete.
|
||||||
|
/// To be used in a listen-before-talk mechanism (Collision Avoidance)
|
||||||
|
/// with a reasonable time backoff algorithm.
|
||||||
|
/// This is called automatically by waitCAD().
|
||||||
|
/// \return true if channel is in use.
|
||||||
|
virtual bool isChannelActive();
|
||||||
|
|
||||||
|
/// Enable TCXO mode
|
||||||
|
/// Call this immediately after init(), to force your radio to use an external
|
||||||
|
/// frequency source, such as a Temperature Compensated Crystal Oscillator (TCXO), if available.
|
||||||
|
/// See the comments in the main documentation about the sensitivity of this radio to
|
||||||
|
/// clock frequency especially when using narrow bandwidths.
|
||||||
|
/// Leaves the module in sleep mode.
|
||||||
|
/// Caution, this function has not been tested by us.
|
||||||
|
/// Caution, the TCXO model radios are not low power when in sleep (consuming
|
||||||
|
/// about ~600 uA, reported by Phang Moh Lim.<br>
|
||||||
|
void enableTCXO();
|
||||||
|
|
||||||
|
/// Returns the last measured frequency error.
|
||||||
|
/// The LoRa receiver estimates the frequency offset between the receiver centre frequency
|
||||||
|
/// and that of the received LoRa signal. This function returns the estimates offset (in Hz)
|
||||||
|
/// of the last received message. Caution: this measurement is not absolute, but is measured
|
||||||
|
/// relative to the local receiver's oscillator.
|
||||||
|
/// Apparent errors may be due to the transmitter, the receiver or both.
|
||||||
|
/// \return The estimated centre frequency offset in Hz of the last received message.
|
||||||
|
/// If the modem bandwidth selector in
|
||||||
|
/// register RH_RF95_REG_1D_MODEM_CONFIG1 is invalid, returns 0.
|
||||||
|
int frequencyError();
|
||||||
|
|
||||||
|
/// Returns the Signal-to-noise ratio (SNR) of the last received message, as measured
|
||||||
|
/// by the receiver.
|
||||||
|
/// \return SNR of the last received message in dB
|
||||||
|
int lastSNR();
|
||||||
|
|
||||||
|
/// brian.n.norman@gmail.com 9th Nov 2018
|
||||||
|
/// Sets the radio spreading factor.
|
||||||
|
/// valid values are 6 through 12.
|
||||||
|
/// Out of range values below 6 are clamped to 6
|
||||||
|
/// Out of range values above 12 are clamped to 12
|
||||||
|
/// See Semtech DS SX1276/77/78/79 page 27 regarding SF6 configuration.
|
||||||
|
///
|
||||||
|
/// \param[in] uint8_t sf (spreading factor 6..12)
|
||||||
|
/// \return nothing
|
||||||
|
void setSpreadingFactor(uint8_t sf);
|
||||||
|
|
||||||
|
/// brian.n.norman@gmail.com 9th Nov 2018
|
||||||
|
/// Sets the radio signal bandwidth
|
||||||
|
/// sbw ranges and resultant settings are as follows:-
|
||||||
|
/// sbw range actual bw (kHz)
|
||||||
|
/// 0-7800 7.8
|
||||||
|
/// 7801-10400 10.4
|
||||||
|
/// 10401-15600 15.6
|
||||||
|
/// 15601-20800 20.8
|
||||||
|
/// 20801-31250 31.25
|
||||||
|
/// 31251-41700 41.7
|
||||||
|
/// 41701-62500 62.5
|
||||||
|
/// 62501-12500 125.0
|
||||||
|
/// 12501-250000 250.0
|
||||||
|
/// >250000 500.0
|
||||||
|
/// NOTE caution Earlier - Semtech do not recommend BW below 62.5 although, in testing
|
||||||
|
/// I managed 31.25 with two devices in close proximity.
|
||||||
|
/// \param[in] sbw long, signal bandwidth e.g. 125000
|
||||||
|
void setSignalBandwidth(long sbw);
|
||||||
|
|
||||||
|
/// brian.n.norman@gmail.com 9th Nov 2018
|
||||||
|
/// Sets the coding rate to 4/5, 4/6, 4/7 or 4/8.
|
||||||
|
/// Valid denominator values are 5, 6, 7 or 8. A value of 5 sets the coding rate to 4/5 etc.
|
||||||
|
/// Values below 5 are clamped at 5
|
||||||
|
/// values above 8 are clamped at 8
|
||||||
|
/// \param[in] denominator uint8_t range 5..8
|
||||||
|
void setCodingRate4(uint8_t denominator);
|
||||||
|
|
||||||
|
/// brian.n.norman@gmail.com 9th Nov 2018
|
||||||
|
/// sets the low data rate flag if symbol time exceeds 16ms
|
||||||
|
/// ref: https://www.thethingsnetwork.org/forum/t/a-point-to-note-lora-low-data-rate-optimisation-flag/12007
|
||||||
|
/// called by setBandwidth() and setSpreadingfactor() since these affect the symbol time.
|
||||||
|
void setLowDatarate();
|
||||||
|
|
||||||
|
/// brian.n.norman@gmail.com 9th Nov 2018
|
||||||
|
/// allows the payload CRC bit to be turned on/off. Normally this should be left on
|
||||||
|
/// so that packets with a bad CRC are rejected
|
||||||
|
/// \patam[in] on bool, true turns the payload CRC on, false turns it off
|
||||||
|
void setPayloadCRC(bool on);
|
||||||
|
|
||||||
|
/// Return true if we are currently receiving a packet
|
||||||
|
bool isReceiving();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// This is a low level function to handle the interrupts for one instance of RH_RF95.
|
||||||
|
/// Called automatically by isr*()
|
||||||
|
/// Should not need to be called by user code.
|
||||||
|
virtual void handleInterrupt();
|
||||||
|
|
||||||
|
/// Examine the revceive buffer to determine whether the message is for this node
|
||||||
|
void validateRxBuf();
|
||||||
|
|
||||||
|
/// Clear our local receive buffer
|
||||||
|
void clearRxBuf();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Low level interrupt service routine for device connected to interrupt 0
|
||||||
|
static void isr0();
|
||||||
|
|
||||||
|
/// Low level interrupt service routine for device connected to interrupt 1
|
||||||
|
static void isr1();
|
||||||
|
|
||||||
|
/// Low level interrupt service routine for device connected to interrupt 1
|
||||||
|
static void isr2();
|
||||||
|
|
||||||
|
/// Array of instances connected to interrupts 0 and 1
|
||||||
|
static RH_RF95* _deviceForInterrupt[];
|
||||||
|
|
||||||
|
/// Index of next interrupt number to use in _deviceForInterrupt
|
||||||
|
static uint8_t _interruptCount;
|
||||||
|
|
||||||
|
/// The configured interrupt pin connected to this instance
|
||||||
|
uint8_t _interruptPin;
|
||||||
|
|
||||||
|
/// The index into _deviceForInterrupt[] for this device (if an interrupt is already allocated)
|
||||||
|
/// else 0xff
|
||||||
|
uint8_t _myInterruptIndex;
|
||||||
|
|
||||||
|
// True if we are using the HF port (779.0 MHz and above)
|
||||||
|
bool _usingHFport;
|
||||||
|
|
||||||
|
// Last measured SNR, dB
|
||||||
|
int8_t _lastSNR;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Number of octets in the buffer
|
||||||
|
volatile uint8_t _bufLen;
|
||||||
|
|
||||||
|
/// The receiver/transmitter buffer
|
||||||
|
uint8_t _buf[RH_RF95_MAX_PAYLOAD_LEN];
|
||||||
|
|
||||||
|
/// True when there is a valid message in the buffer
|
||||||
|
volatile bool _rxBufValid;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @example rf95_client.pde
|
||||||
|
/// @example rf95_server.pde
|
||||||
|
/// @example rf95_encrypted_client.pde
|
||||||
|
/// @example rf95_encrypted_server.pde
|
||||||
|
/// @example rf95_reliable_datagram_client.pde
|
||||||
|
/// @example rf95_reliable_datagram_server.pde
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
1595
src/rf95/RadioHead.h
Normal file
1595
src/rf95/RadioHead.h
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user