first commit

This commit is contained in:
2018-11-05 10:00:59 +01:00
commit 0bb240e86d
131 changed files with 27996 additions and 0 deletions

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# Library to support RadioHead based packet radio for RF modules like nRF24 and others.
[Library Documentation](https://github.com/PaulStoffregen/RadioHead)
This library has been made available to mongoose-os by Dirk Jahnke.

28
mos.yml Normal file
View File

@@ -0,0 +1,28 @@
author: Dirk Jahnke
description: RadioHead library for Arduino made available for mongoose-os
type: lib
version: 1.0
platforms: [ esp32, esp8266 ]
sources:
- src
includes:
- include
cdefs:
ARDUINO: 150
tags:
- arduino
- c
- core
manifest_version: 2017-09-29
libs:
- origin: https://github.com/mongoose-os-libs/mongoose
- origin: https://github.com/mongoose-os-libs/arduino-compat
- origin: https://github.com/mongoose-os-libs/arduino-wire
- origin: https://github.com/mongoose-os-libs/arduino-spi

2
mos_esp32.yml Normal file
View File

@@ -0,0 +1,2 @@
cdefs:
ESP32: 1

2
mos_esp8266.yml Normal file
View File

@@ -0,0 +1,2 @@
cdefs:
ESP8266: 1

17
src/LICENSE Normal file
View 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.

125
src/MANIFEST Normal file
View File

@@ -0,0 +1,125 @@
RadioHead/LICENSE
RadioHead/MANIFEST
RadioHead/project.cfg
RadioHead/RadioHead.h
RadioHead/RH_ASK.cpp
RadioHead/RH_ASK.h
RadioHead/RHCRC.cpp
RadioHead/RHCRC.h
RadioHead/RHDatagram.cpp
RadioHead/RHDatagram.h
RadioHead/RHGenericDriver.cpp
RadioHead/RHGenericDriver.h
RadioHead/RHGenericSPI.cpp
RadioHead/RHGenericSPI.h
RadioHead/RHHardwareSPI.cpp
RadioHead/RHHardwareSPI.h
RadioHead/RHMesh.cpp
RadioHead/RHMesh.h
RadioHead/RHReliableDatagram.cpp
RadioHead/RHReliableDatagram.h
RadioHead/RH_CC110.cpp
RadioHead/RH_CC110.h
RadioHead/RH_NRF24.cpp
RadioHead/RH_NRF24.h
RadioHead/RH_NRF51.cpp
RadioHead/RH_NRF51.h
RadioHead/RH_NRF905.cpp
RadioHead/RH_NRF905.h
RadioHead/RH_RF22.cpp
RadioHead/RH_RF22.h
RadioHead/RH_RF24.cpp
RadioHead/RH_RF24.h
RadioHead/radio_config_Si4460.h
RadioHead/RH_RF69.cpp
RadioHead/RH_RF69.h
RadioHead/RH_MRF89.cpp
RadioHead/RH_MRF89.h
RadioHead/RH_RF95.cpp
RadioHead/RH_RF95.h
RadioHead/RH_TCP.cpp
RadioHead/RH_TCP.h
RadioHead/RHRouter.cpp
RadioHead/RHRouter.h
RadioHead/RH_Serial.cpp
RadioHead/RH_Serial.h
RadioHead/RHSoftwareSPI.cpp
RadioHead/RHSoftwareSPI.h
RadioHead/RHSPIDriver.cpp
RadioHead/RHSPIDriver.h
RadioHead/RHTcpProtocol.h
RadioHead/RHNRFSPIDriver.cpp
RadioHead/RHNRFSPIDriver.h
RadioHead/RHutil
RadioHead/RHutil/atomic.h
RadioHead/RHutil/simulator.h
RadioHead/RHutil/HardwareSerial.h
RadioHead/RHutil/HardwareSerial.cpp
RadioHead/RHutil/RasPi.cpp
RadioHead/RHutil/RasPi.h
RadioHead/examples/ask/ask_reliable_datagram_client/ask_reliable_datagram_client.pde
RadioHead/examples/ask/ask_reliable_datagram_server/ask_reliable_datagram_server.pde
RadioHead/examples/ask/ask_transmitter/ask_transmitter.pde
RadioHead/examples/ask/ask_receiver/ask_receiver.pde
RadioHead/examples/cc110/cc110_client/cc110_client.pde
RadioHead/examples/cc110/cc110_server/cc110_server.pde
RadioHead/examples/rf95/rf95_client/rf95_client.pde
RadioHead/examples/rf95/rf95_reliable_datagram_client/rf95_reliable_datagram_client.pde
RadioHead/examples/rf95/rf95_reliable_datagram_server/rf95_reliable_datagram_server.pde
RadioHead/examples/rf95/rf95_server/rf95_server.pde
RadioHead/examples/rf22/rf22_client/rf22_client.pde
RadioHead/examples/rf22/rf22_mesh_client/rf22_mesh_client.pde
RadioHead/examples/rf22/rf22_mesh_server1/rf22_mesh_server1.pde
RadioHead/examples/rf22/rf22_mesh_server2/rf22_mesh_server2.pde
RadioHead/examples/rf22/rf22_mesh_server3/rf22_mesh_server3.pde
RadioHead/examples/rf22/rf22_reliable_datagram_client/rf22_reliable_datagram_client.pde
RadioHead/examples/rf22/rf22_reliable_datagram_server/rf22_reliable_datagram_server.pde
RadioHead/examples/rf22/rf22_router_client/rf22_router_client.pde
RadioHead/examples/rf22/rf22_router_server1/rf22_router_server1.pde
RadioHead/examples/rf22/rf22_router_server2/rf22_router_server2.pde
RadioHead/examples/rf22/rf22_router_server3/rf22_router_server3.pde
RadioHead/examples/rf22/rf22_router_test/rf22_router_test.pde
RadioHead/examples/rf22/rf22_server/rf22_server.pde
RadioHead/examples/rf24/rf24_client/rf24_client.pde
RadioHead/examples/rf24/rf24_reliable_datagram_client/rf24_reliable_datagram_client.pde
RadioHead/examples/rf24/rf24_reliable_datagram_server/rf24_reliable_datagram_server.pde
RadioHead/examples/rf24/rf24_server/rf24_server.pde
RadioHead/examples/rf69/rf69_client/rf69_client.pde
RadioHead/examples/rf69/rf69_reliable_datagram_client/rf69_reliable_datagram_client.pde
RadioHead/examples/rf69/rf69_reliable_datagram_server/rf69_reliable_datagram_server.pde
RadioHead/examples/rf69/rf69_server/rf69_server.pde
RadioHead/examples/mrf89/mrf89_client/mrf89_client.pde
RadioHead/examples/mrf89/mrf89_server/mrf89_server.pde
RadioHead/examples/nrf24/nrf24_client/nrf24_client.pde
RadioHead/examples/nrf24/nrf24_reliable_datagram_client/nrf24_reliable_datagram_client.pde
RadioHead/examples/nrf24/nrf24_reliable_datagram_server/nrf24_reliable_datagram_server.pde
RadioHead/examples/nrf24/nrf24_server/nrf24_server.pde
RadioHead/examples/nrf51/nrf51_client/nrf51_client.pde
RadioHead/examples/nrf51/nrf51_reliable_datagram_client/nrf51_reliable_datagram_client.pde
RadioHead/examples/nrf51/nrf51_reliable_datagram_server/nrf51_reliable_datagram_server.pde
RadioHead/examples/nrf51/nrf51_server/nrf51_server.pde
RadioHead/examples/nrf51/nrf51_audio_tx/nrf51_audio_tx.pde
RadioHead/examples/nrf51/nrf51_audio_tx/nrf51_audio.pdf
RadioHead/examples/nrf51/nrf51_audio_rx/nrf51_audio_rx.pde
RadioHead/examples/nrf905/nrf905_client/nrf905_client.pde
RadioHead/examples/nrf905/nrf905_reliable_datagram_client/nrf905_reliable_datagram_client.pde
RadioHead/examples/nrf905/nrf905_reliable_datagram_server/nrf905_reliable_datagram_server.pde
RadioHead/examples/nrf905/nrf905_server/nrf905_server.pde
RadioHead/examples/serial/serial_reliable_datagram_client/serial_reliable_datagram_client.pde
RadioHead/examples/serial/serial_reliable_datagram_server/serial_reliable_datagram_server.pde
RadioHead/examples/simulator/simulator_reliable_datagram_client/simulator_reliable_datagram_client.pde
RadioHead/examples/simulator/simulator_reliable_datagram_server/simulator_reliable_datagram_server.pde
RadioHead/examples/raspi/RasPiRH.cpp
RadioHead/examples/raspi/Makefile
RadioHead/tools/etherSimulator.pl
RadioHead/tools/chain.conf
RadioHead/tools/simMain.cpp
RadioHead/tools/simBuild
RadioHead/doc
RadioHead/STM32ArduinoCompat/HardwareSerial.cpp
RadioHead/STM32ArduinoCompat/HardwareSerial.h
RadioHead/STM32ArduinoCompat/HardwareSPI.cpp
RadioHead/STM32ArduinoCompat/HardwareSPI.h
RadioHead/STM32ArduinoCompat/wirish.cpp
RadioHead/STM32ArduinoCompat/wirish.h
RadioHead/STM32ArduinoCompat/README

104
src/RHCRC.cpp Normal file
View File

@@ -0,0 +1,104 @@
/* Copyright (c) 2002, 2003, 2004 Marek Michalkiewicz
Copyright (c) 2005, 2007 Joerg Wunsch
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the copyright holders nor the names of
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. */
// Port to Energia / MPS430 by Yannick DEVOS XV4Y - (c) 2013
// http://xv4y.radioclub.asia/
//
// Adapted to RadioHead use by Mike McCauley 2014
// This is to prevent name collisions with other similar library functions
// and to provide a consistent API amonng all processors
//
/* $Id: RHCRC.cpp,v 1.1 2014/06/24 02:40:12 mikem Exp $ */
#include <RHCRC.h>
#define lo8(x) ((x)&0xff)
#define hi8(x) ((x)>>8)
uint16_t RHcrc16_update(uint16_t crc, uint8_t a)
{
int i;
crc ^= a;
for (i = 0; i < 8; ++i)
{
if (crc & 1)
crc = (crc >> 1) ^ 0xA001;
else
crc = (crc >> 1);
}
return crc;
}
uint16_t RHcrc_xmodem_update (uint16_t crc, uint8_t data)
{
int i;
crc = crc ^ ((uint16_t)data << 8);
for (i=0; i<8; i++)
{
if (crc & 0x8000)
crc = (crc << 1) ^ 0x1021;
else
crc <<= 1;
}
return crc;
}
uint16_t RHcrc_ccitt_update (uint16_t crc, uint8_t data)
{
data ^= lo8 (crc);
data ^= data << 4;
return ((((uint16_t)data << 8) | hi8 (crc)) ^ (uint8_t)(data >> 4)
^ ((uint16_t)data << 3));
}
uint8_t RHcrc_ibutton_update(uint8_t crc, uint8_t data)
{
uint8_t i;
crc = crc ^ data;
for (i = 0; i < 8; i++)
{
if (crc & 0x01)
crc = (crc >> 1) ^ 0x8C;
else
crc >>= 1;
}
return crc;
}

19
src/RHCRC.h Normal file
View File

@@ -0,0 +1,19 @@
// RHCRC.h
//
// Definitions for RadioHead compatible CRC outines.
//
// These routines originally derived from Arduino source code. See RHCRC.cpp
// for copyright information
// $Id: RHCRC.h,v 1.1 2014/06/24 02:40:12 mikem Exp $
#ifndef RHCRC_h
#define RHCRC_h
#include <RadioHead.h>
extern uint16_t RHcrc16_update(uint16_t crc, uint8_t a);
extern uint16_t RHcrc_xmodem_update (uint16_t crc, uint8_t data);
extern uint16_t RHcrc_ccitt_update (uint16_t crc, uint8_t data);
extern uint8_t RHcrc_ibutton_update(uint8_t crc, uint8_t data);
#endif

123
src/RHDatagram.cpp Normal file
View File

@@ -0,0 +1,123 @@
// RHDatagram.cpp
//
// Copyright (C) 2011 Mike McCauley
// $Id: RHDatagram.cpp,v 1.6 2014/05/23 02:20:17 mikem Exp $
#include <RHDatagram.h>
RHDatagram::RHDatagram(RHGenericDriver& driver, uint8_t thisAddress)
:
_driver(driver),
_thisAddress(thisAddress)
{
}
////////////////////////////////////////////////////////////////////
// Public methods
bool RHDatagram::init()
{
bool ret = _driver.init();
if (ret)
setThisAddress(_thisAddress);
return ret;
}
void RHDatagram::setThisAddress(uint8_t thisAddress)
{
_driver.setThisAddress(thisAddress);
// Use this address in the transmitted FROM header
setHeaderFrom(thisAddress);
_thisAddress = thisAddress;
}
bool RHDatagram::sendto(uint8_t* buf, uint8_t len, uint8_t address)
{
setHeaderTo(address);
return _driver.send(buf, len);
}
bool RHDatagram::recvfrom(uint8_t* buf, uint8_t* len, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags)
{
if (_driver.recv(buf, len))
{
if (from) *from = headerFrom();
if (to) *to = headerTo();
if (id) *id = headerId();
if (flags) *flags = headerFlags();
return true;
}
return false;
}
bool RHDatagram::available()
{
return _driver.available();
}
void RHDatagram::waitAvailable()
{
_driver.waitAvailable();
}
bool RHDatagram::waitPacketSent()
{
return _driver.waitPacketSent();
}
bool RHDatagram::waitPacketSent(uint16_t timeout)
{
return _driver.waitPacketSent(timeout);
}
bool RHDatagram::waitAvailableTimeout(uint16_t timeout)
{
return _driver.waitAvailableTimeout(timeout);
}
uint8_t RHDatagram::thisAddress()
{
return _thisAddress;
}
void RHDatagram::setHeaderTo(uint8_t to)
{
_driver.setHeaderTo(to);
}
void RHDatagram::setHeaderFrom(uint8_t from)
{
_driver.setHeaderFrom(from);
}
void RHDatagram::setHeaderId(uint8_t id)
{
_driver.setHeaderId(id);
}
void RHDatagram::setHeaderFlags(uint8_t set, uint8_t clear)
{
_driver.setHeaderFlags(set, clear);
}
uint8_t RHDatagram::headerTo()
{
return _driver.headerTo();
}
uint8_t RHDatagram::headerFrom()
{
return _driver.headerFrom();
}
uint8_t RHDatagram::headerId()
{
return _driver.headerId();
}
uint8_t RHDatagram::headerFlags()
{
return _driver.headerFlags();
}

162
src/RHDatagram.h Normal file
View File

@@ -0,0 +1,162 @@
// RHDatagram.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// $Id: RHDatagram.h,v 1.14 2015/08/12 23:18:51 mikem Exp $
#ifndef RHDatagram_h
#define RHDatagram_h
#include <RHGenericDriver.h>
// This is the maximum possible message size for radios supported by RadioHead.
// Not all radios support this length, and many are much smaller
#define RH_MAX_MESSAGE_LEN 255
/////////////////////////////////////////////////////////////////////
/// \class RHDatagram RHDatagram.h <RHDatagram.h>
/// \brief Manager class for addressed, unreliable messages
///
/// Every RHDatagram node has an 8 bit address (defaults to 0).
/// Addresses (DEST and SRC) are 8 bit integers with an address of RH_BROADCAST_ADDRESS (0xff)
/// reserved for broadcast.
///
/// \par Media Access Strategy
///
/// RHDatagram and the underlying drivers always transmit as soon as sendto() is called.
///
/// \par Message Lengths
///
/// Not all Radio drivers supported by RadioHead can handle the same message lengths. Some radios can handle
/// up to 255 octets, and some as few as 28. If you attempt to send a message that is too long for
/// the underlying driver, sendTo() will return false and will not transmit the message.
/// It is the programmers responsibility to make
/// sure that messages passed to sendto() do not exceed the capability of the radio. You can use the
/// *_MAX_MESSAGE_LENGTH definitions or driver->maxMessageLength() to help.
///
/// \par Headers
///
/// Each message sent and received by a RadioHead driver includes 4 headers:<br>
/// \b TO The node address that the message is being sent to (broadcast RH_BROADCAST_ADDRESS (255) is permitted)<br>
/// \b FROM The node address of the sending node<br>
/// \b ID A message ID, distinct (over short time scales) for each message sent by a particilar node<br>
/// \b FLAGS A bitmask of flags. The most significant 4 bits are reserved for use by RadioHead. The least
/// significant 4 bits are reserved for applications.<br>
///
class RHDatagram
{
public:
/// Constructor.
/// \param[in] driver The RadioHead driver to use to transport messages.
/// \param[in] thisAddress The address to assign to this node. Defaults to 0
RHDatagram(RHGenericDriver& driver, uint8_t thisAddress = 0);
/// Initialise this instance and the
/// driver connected to it.
bool init();
/// Sets the address of this node. Defaults to 0.
/// This will be used to set the FROM address of all messages sent by this node.
/// In a conventional multinode system, all nodes will have a unique address
/// (which you could store in EEPROM).
/// \param[in] thisAddress The address of this node
void setThisAddress(uint8_t thisAddress);
/// Sends a message to the node(s) with the given address
/// RH_BROADCAST_ADDRESS is a valid address which will cause the message
/// to be accepted by all RHDatagram nodes within range.
/// \param[in] buf Pointer to the binary message to send
/// \param[in] len Number of octets to send (> 0)
/// \param[in] address The address to send the message to.
/// \return true if the message not too loing fot eh driver, and the message was transmitted.
bool sendto(uint8_t* buf, uint8_t len, uint8_t address);
/// Turns the receiver on if it not already on.
/// If there is a valid message available for this node, copy it to buf and return true
/// The SRC address is placed in *from if present and not NULL.
/// The DEST address is placed in *to if present and not NULL.
/// If a message is copied, *len is set to the length.
/// You should be sure to call this function frequently enough to not miss any messages
/// It is recommended that you call it in your main loop.
/// \param[in] buf Location to copy the received message
/// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied.
/// \param[in] from If present and not NULL, the referenced uint8_t will be set to the FROM address
/// \param[in] to If present and not NULL, the referenced uint8_t will be set to the TO address
/// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID
/// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS
/// (not just those addressed to this node).
/// \return true if a valid message was copied to buf
bool recvfrom(uint8_t* buf, uint8_t* len, uint8_t* from = NULL, uint8_t* to = NULL, uint8_t* id = NULL, uint8_t* flags = NULL);
/// Tests whether a new message is available
/// from the Driver.
/// On most drivers, this will also put the Driver into RHModeRx mode until
/// a message is actually received bythe transport, when it will be returned to RHModeIdle.
/// This can be called multiple times in a timeout loop.
/// \return true if a new, complete, error-free uncollected message is available to be retreived by recv()
bool available();
/// Starts the Driver receiver and blocks until a valid received
/// message is available.
void waitAvailable();
/// Blocks until the transmitter
/// is no longer transmitting.
bool waitPacketSent();
/// Blocks until the transmitter is no longer transmitting.
/// or until the timeout occuers, whichever happens first
/// \param[in] timeout Maximum time to wait in milliseconds.
/// \return true if the radio completed transmission within the timeout period. False if it timed out.
bool waitPacketSent(uint16_t timeout);
/// Starts the Driver receiver and blocks until a received message is available or a timeout
/// \param[in] timeout Maximum time to wait in milliseconds.
/// \return true if a message is available
bool waitAvailableTimeout(uint16_t timeout);
/// Sets the TO header to be sent in all subsequent messages
/// \param[in] to The new TO header value
void setHeaderTo(uint8_t to);
/// Sets the FROM header to be sent in all subsequent messages
/// \param[in] from The new FROM header value
void setHeaderFrom(uint8_t from);
/// Sets the ID header to be sent in all subsequent messages
/// \param[in] id The new ID header value
void setHeaderId(uint8_t id);
/// Sets and clears bits in the FLAGS header to be sent in all subsequent messages
/// \param[in] set bitmask of bits to be set
/// \param[in] clear bitmask of flags to clear
void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_NONE);
/// Returns the TO header of the last received message
/// \return The TO header of the most recently received message.
uint8_t headerTo();
/// Returns the FROM header of the last received message
/// \return The FROM header of the most recently received message.
uint8_t headerFrom();
/// Returns the ID header of the last received message
/// \return The ID header of the most recently received message.
uint8_t headerId();
/// Returns the FLAGS header of the last received message
/// \return The FLAGS header of the most recently received message.
uint8_t headerFlags();
/// Returns the address of this node.
/// \return The address of this node
uint8_t thisAddress();
protected:
/// The Driver we are to use
RHGenericDriver& _driver;
/// The address of this node
uint8_t _thisAddress;
};
#endif

184
src/RHGenericDriver.cpp Normal file
View File

@@ -0,0 +1,184 @@
// RHGenericDriver.cpp
//
// Copyright (C) 2014 Mike McCauley
// $Id: RHGenericDriver.cpp,v 1.19 2015/12/11 01:10:24 mikem Exp $
#include <RHGenericDriver.h>
RHGenericDriver::RHGenericDriver()
:
_mode(RHModeInitialising),
_thisAddress(RH_BROADCAST_ADDRESS),
_txHeaderTo(RH_BROADCAST_ADDRESS),
_txHeaderFrom(RH_BROADCAST_ADDRESS),
_txHeaderId(0),
_txHeaderFlags(0),
_rxBad(0),
_rxGood(0),
_txGood(0)
{
}
bool RHGenericDriver::init()
{
return true;
}
// Blocks until a valid message is received
void RHGenericDriver::waitAvailable()
{
while (!available())
YIELD;
}
// Blocks until a valid message is received or timeout expires
// Return true if there is a message available
// Works correctly even on millis() rollover
bool RHGenericDriver::waitAvailableTimeout(uint16_t timeout)
{
unsigned long starttime = millis();
while ((millis() - starttime) < timeout)
{
if (available())
{
return true;
}
YIELD;
}
return false;
}
bool RHGenericDriver::waitPacketSent()
{
while (_mode == RHModeTx)
YIELD; // Wait for any previous transmit to finish
return true;
}
bool RHGenericDriver::waitPacketSent(uint16_t timeout)
{
unsigned long starttime = millis();
while ((millis() - starttime) < timeout)
{
if (_mode != RHModeTx) // Any previous transmit finished?
return true;
YIELD;
}
return false;
}
void RHGenericDriver::setPromiscuous(bool promiscuous)
{
_promiscuous = promiscuous;
}
void RHGenericDriver::setThisAddress(uint8_t address)
{
_thisAddress = address;
}
void RHGenericDriver::setHeaderTo(uint8_t to)
{
_txHeaderTo = to;
}
void RHGenericDriver::setHeaderFrom(uint8_t from)
{
_txHeaderFrom = from;
}
void RHGenericDriver::setHeaderId(uint8_t id)
{
_txHeaderId = id;
}
void RHGenericDriver::setHeaderFlags(uint8_t set, uint8_t clear)
{
_txHeaderFlags &= ~clear;
_txHeaderFlags |= set;
}
uint8_t RHGenericDriver::headerTo()
{
return _rxHeaderTo;
}
uint8_t RHGenericDriver::headerFrom()
{
return _rxHeaderFrom;
}
uint8_t RHGenericDriver::headerId()
{
return _rxHeaderId;
}
uint8_t RHGenericDriver::headerFlags()
{
return _rxHeaderFlags;
}
int8_t RHGenericDriver::lastRssi()
{
return _lastRssi;
}
RHGenericDriver::RHMode RHGenericDriver::mode()
{
return _mode;
}
void RHGenericDriver::setMode(RHMode mode)
{
_mode = mode;
}
bool RHGenericDriver::sleep()
{
return false;
}
// Diagnostic help
void RHGenericDriver::printBuffer(const char* prompt, const uint8_t* buf, uint8_t len)
{
uint8_t i;
#ifdef RH_HAVE_SERIAL
Serial.println(prompt);
for (i = 0; i < len; i++)
{
if (i % 16 == 15)
Serial.println(buf[i], HEX);
else
{
Serial.print(buf[i], HEX);
Serial.print(' ');
}
}
Serial.println("");
#endif
}
uint16_t RHGenericDriver::rxBad()
{
return _rxBad;
}
uint16_t RHGenericDriver::rxGood()
{
return _rxGood;
}
uint16_t RHGenericDriver::txGood()
{
return _txGood;
}
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(RH_PLATFORM_ATTINY)
// Tinycore does not have __cxa_pure_virtual, so without this we
// get linking complaints from the default code generated for pure virtual functions
extern "C" void __cxa_pure_virtual()
{
while (1);
}
#endif

265
src/RHGenericDriver.h Normal file
View File

@@ -0,0 +1,265 @@
// RHGenericDriver.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2014 Mike McCauley
// $Id: RHGenericDriver.h,v 1.17 2016/04/04 01:40:12 mikem Exp $
#ifndef RHGenericDriver_h
#define RHGenericDriver_h
#include <RadioHead.h>
// Defines bits of the FLAGS header reserved for use by the RadioHead library and
// the flags available for use by applications
#define RH_FLAGS_RESERVED 0xf0
#define RH_FLAGS_APPLICATION_SPECIFIC 0x0f
#define RH_FLAGS_NONE 0
/////////////////////////////////////////////////////////////////////
/// \class RHGenericDriver RHGenericDriver.h <RHGenericDriver.h>
/// \brief Abstract base class for a RadioHead driver.
///
/// This class defines the functions that must be provided by any RadioHead driver.
/// Different types of driver will implement all the abstract functions, and will perhaps override
/// other functions in this subclass, or perhaps add new functions specifically required by that driver.
/// Do not directly instantiate this class: it is only to be subclassed by driver classes.
///
/// Subclasses are expected to implement a half-duplex, unreliable, error checked, unaddressed packet transport.
/// They are expected to carry a message payload with an appropriate maximum length for the transport hardware
/// and to also carry unaltered 4 message headers: TO, FROM, ID, FLAGS
///
/// \par Headers
///
/// Each message sent and received by a RadioHead driver includes 4 headers:
/// -TO The node address that the message is being sent to (broadcast RH_BROADCAST_ADDRESS (255) is permitted)
/// -FROM The node address of the sending node
/// -ID A message ID, distinct (over short time scales) for each message sent by a particilar node
/// -FLAGS A bitmask of flags. The most significant 4 bits are reserved for use by RadioHead. The least
/// significant 4 bits are reserved for applications.
class RHGenericDriver
{
public:
/// \brief Defines different operating modes for the transport hardware
///
/// These are the different values that can be adopted by the _mode variable and
/// returned by the mode() member function,
typedef enum
{
RHModeInitialising = 0, ///< Transport is initialising. Initial default value until init() is called..
RHModeSleep, ///< Transport hardware is in low power sleep mode (if supported)
RHModeIdle, ///< Transport is idle.
RHModeTx, ///< Transport is in the process of transmitting a message.
RHModeRx ///< Transport is in the process of receiving a message.
} RHMode;
/// Constructor
RHGenericDriver();
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
virtual bool init();
/// Tests whether a new message is available
/// from the Driver.
/// On most drivers, if there is an uncollected received message, and there is no message
/// currently bing transmitted, this will also put the Driver into RHModeRx mode until
/// a message is actually received by the transport, when it will be returned to RHModeIdle.
/// This can be called multiple times in a timeout loop.
/// \return true if a new, complete, error-free uncollected message is available to be retreived by recv().
virtual bool available() = 0;
/// Turns the receiver on if it not already on.
/// If there is a valid message available, copy it to buf and return true
/// else return false.
/// If a message is copied, *len is set to the length (Caution, 0 length messages are permitted).
/// You should be sure to call this function frequently enough to not miss any messages
/// It is recommended that you call it in your main loop.
/// \param[in] buf Location to copy the received message
/// \param[in,out] len Pointer to available space in buf. Set to the actual number of octets copied.
/// \return true if a valid message was copied to buf
virtual bool recv(uint8_t* buf, uint8_t* len) = 0;
/// Waits until any previous transmit packet is finished being transmitted with waitPacketSent().
/// Then loads a message into the transmitter and starts the transmitter. Note that a message length
/// of 0 is NOT permitted. If the message is too long for the underlying radio technology, send() will
/// return false and will not send the message.
/// \param[in] data Array of data to be sent
/// \param[in] len Number of bytes of data to send (> 0)
/// \return true if the message length was valid and it was correctly queued for transmit
virtual bool send(const uint8_t* data, uint8_t len) = 0;
/// Returns the maximum message length
/// available in this Driver.
/// \return The maximum legal message length
virtual uint8_t maxMessageLength() = 0;
/// Starts the receiver and blocks until a valid received
/// message is available.
virtual void waitAvailable();
/// Blocks until the transmitter
/// is no longer transmitting.
virtual bool waitPacketSent();
/// Blocks until the transmitter is no longer transmitting.
/// or until the timeout occuers, whichever happens first
/// \param[in] timeout Maximum time to wait in milliseconds.
/// \return true if the RF22 completed transmission within the timeout period. False if it timed out.
virtual bool waitPacketSent(uint16_t timeout);
/// Starts the receiver and blocks until a received message is available or a timeout
/// \param[in] timeout Maximum time to wait in milliseconds.
/// \return true if a message is available
virtual bool waitAvailableTimeout(uint16_t timeout);
/// Sets the address of this node. Defaults to 0xFF. Subclasses or the user may want to change this.
/// This will be used to test the adddress in incoming messages. In non-promiscuous mode,
/// only messages with a TO header the same as thisAddress or the broadcast addess (0xFF) will be accepted.
/// In promiscuous mode, all messages will be accepted regardless of the TO header.
/// In a conventional multinode system, all nodes will have a unique address
/// (which you could store in EEPROM).
/// You would normally set the header FROM address to be the same as thisAddress (though you dont have to,
/// allowing the possibilty of address spoofing).
/// \param[in] thisAddress The address of this node.
virtual void setThisAddress(uint8_t thisAddress);
/// Sets the TO header to be sent in all subsequent messages
/// \param[in] to The new TO header value
virtual void setHeaderTo(uint8_t to);
/// Sets the FROM header to be sent in all subsequent messages
/// \param[in] from The new FROM header value
virtual void setHeaderFrom(uint8_t from);
/// Sets the ID header to be sent in all subsequent messages
/// \param[in] id The new ID header value
virtual void setHeaderId(uint8_t id);
/// Sets and clears bits in the FLAGS header to be sent in all subsequent messages
/// First it clears he FLAGS according to the clear argument, then sets the flags according to the
/// set argument. The default for clear always clears the application specific flags.
/// \param[in] set bitmask of bits to be set. Flags are cleared with the clear mask before being set.
/// \param[in] clear bitmask of flags to clear. Defaults to RH_FLAGS_APPLICATION_SPECIFIC
/// which clears the application specific flags, resulting in new application specific flags
/// identical to the set.
virtual void setHeaderFlags(uint8_t set, uint8_t clear = RH_FLAGS_APPLICATION_SPECIFIC);
/// Tells the receiver to accept messages with any TO address, not just messages
/// addressed to thisAddress or the broadcast address
/// \param[in] promiscuous true if you wish to receive messages with any TO address
virtual void setPromiscuous(bool promiscuous);
/// Returns the TO header of the last received message
/// \return The TO header
virtual uint8_t headerTo();
/// Returns the FROM header of the last received message
/// \return The FROM header
virtual uint8_t headerFrom();
/// Returns the ID header of the last received message
/// \return The ID header
virtual uint8_t headerId();
/// Returns the FLAGS header of the last received message
/// \return The FLAGS header
virtual uint8_t headerFlags();
/// Returns the most recent RSSI (Receiver Signal Strength Indicator).
/// Usually it is the RSSI of the last received message, which is measured when the preamble is received.
/// If you called readRssi() more recently, it will return that more recent value.
/// \return The most recent RSSI measurement in dBm.
int8_t lastRssi();
/// Returns the operating mode of the library.
/// \return the current mode, one of RF69_MODE_*
RHMode mode();
/// Sets the operating mode of the transport.
void setMode(RHMode mode);
/// Sets the transport hardware into low-power sleep mode
/// (if supported). May be overridden by specific drivers to initialte sleep mode.
/// If successful, the transport will stay in sleep mode until woken by
/// changing mode it idle, transmit or receive (eg by calling send(), recv(), available() etc)
/// \return true if sleep mode is supported by transport hardware and the RadioHead driver, and if sleep mode
/// was successfully entered. If sleep mode is not suported, return false.
virtual bool sleep();
/// Prints a data buffer in HEX.
/// For diagnostic use
/// \param[in] prompt string to preface the print
/// \param[in] buf Location of the buffer to print
/// \param[in] len Length of the buffer in octets.
static void printBuffer(const char* prompt, const uint8_t* buf, uint8_t len);
/// Returns the count of the number of bad received packets (ie packets with bad lengths, checksum etc)
/// which were rejected and not delivered to the application.
/// Caution: not all drivers can correctly report this count. Some underlying hardware only report
/// good packets.
/// \return The number of bad packets received.
uint16_t rxBad();
/// Returns the count of the number of
/// good received packets
/// \return The number of good packets received.
uint16_t rxGood();
/// Returns the count of the number of
/// packets successfully transmitted (though not necessarily received by the destination)
/// \return The number of packets successfully transmitted
uint16_t txGood();
protected:
/// The current transport operating mode
volatile RHMode _mode;
/// This node id
uint8_t _thisAddress;
/// Whether the transport is in promiscuous mode
bool _promiscuous;
/// TO header in the last received mesasge
volatile uint8_t _rxHeaderTo;
/// FROM header in the last received mesasge
volatile uint8_t _rxHeaderFrom;
/// ID header in the last received mesasge
volatile uint8_t _rxHeaderId;
/// FLAGS header in the last received mesasge
volatile uint8_t _rxHeaderFlags;
/// TO header to send in all messages
uint8_t _txHeaderTo;
/// FROM header to send in all messages
uint8_t _txHeaderFrom;
/// ID header to send in all messages
uint8_t _txHeaderId;
/// FLAGS header to send in all messages
uint8_t _txHeaderFlags;
/// The value of the last received RSSI value, in some transport specific units
volatile int8_t _lastRssi;
/// Count of the number of bad messages (eg bad checksum etc) received
volatile uint16_t _rxBad;
/// Count of the number of successfully transmitted messaged
volatile uint16_t _rxGood;
/// Count of the number of bad messages (correct checksum etc) received
volatile uint16_t _txGood;
private:
};
#endif

31
src/RHGenericSPI.cpp Normal file
View 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;
}

146
src/RHGenericSPI.h Normal file
View File

@@ -0,0 +1,146 @@
// RHGenericSPI.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// Contributed by Joanna Rutkowska
// $Id: RHGenericSPI.h,v 1.7 2014/04/14 08:37:11 mikem Exp $
#ifndef RHGenericSPI_h
#define RHGenericSPI_h
#include <RadioHead.h>
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO)
#include <SPI.h> // for SPI_HAS_TRANSACTION and SPISettings
#endif
/////////////////////////////////////////////////////////////////////
/// \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;
/// SPI Configuration methods
/// Enable SPI interrupts (if supported)
/// This can be used in an SPI slave to indicate when an SPI message has been received
virtual void attachInterrupt() {};
/// Disable SPI interrupts (if supported)
/// This can be used to diable the SPI interrupt in slaves where that is supported.
virtual void detachInterrupt() {};
/// Initialise the SPI library.
/// Call this after configuring and before using the SPI library
virtual void begin() = 0;
/// Disables the SPI bus (leaving pin modes unchanged).
/// Call this after you have finished using the SPI interface
virtual void end() = 0;
/// Sets the bit order the SPI interface will use
/// Sets the order of the bits shifted out of and into the SPI bus, either
/// LSBFIRST (least-significant bit first) or MSBFIRST (most-significant bit first).
/// \param[in] bitOrder Bit order to be used: one of RHGenericSPI::BitOrder
virtual void setBitOrder(BitOrder bitOrder);
/// Sets the SPI data mode: that is, clock polarity and phase.
/// See the Wikipedia article on SPI for details.
/// \param[in] dataMode The mode to use: one of RHGenericSPI::DataMode
virtual void setDataMode(DataMode dataMode);
/// Sets the SPI clock divider relative to the system clock.
/// On AVR based boards, the dividers available are 2, 4, 8, 16, 32, 64 or 128.
/// The default setting is SPI_CLOCK_DIV4, which sets the SPI clock to one-quarter
/// the frequency of the system clock (4 Mhz for the boards at 16 MHz).
/// \param[in] frequency The data rate to use: one of RHGenericSPI::Frequency
virtual void setFrequency(Frequency frequency);
// Try to add SPI Transaction support
// Note: Maybe add some way to set SPISettings?
virtual void beginTransaction() {};
virtual void endTransaction() {};
protected:
/// The configure SPI Bus frequency, one of RHGenericSPI::Frequency
Frequency _frequency; // Bus frequency, one of RHGenericSPI::Frequency
/// Bit order, one of RHGenericSPI::BitOrder
BitOrder _bitOrder;
/// SPI bus mode, one of RHGenericSPI::DataMode
DataMode _dataMode;
};
#endif

167
src/RHHardwareSP12.cpp Normal file
View File

@@ -0,0 +1,167 @@
// RHHardwareSPI2.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// Contributed by Joanna Rutkowska
// $Id: RHHardwareSPI2.cpp,v 1.16 2016/07/07 00:02:53 mikem Exp mikem $
// This is a copy of the standard SPI node, that is hopefully setup to work on those processors
// who have SPI2. Currently I only have it setup for Teensy 3.5/3.6
#if defined(__arm__) && defined(TEENSYDUINO) && (defined(__MK64FX512__) || defined(__MK66FX1M0__) )
#include <RHHardwareSPI2.h>
// Declare a single default instance of the hardware SPI interface class
RHHardwareSPI2 hardware_spi2;
#ifdef RH_HAVE_HARDWARE_SPI
RHHardwareSPI2::RHHardwareSPI2(Frequency frequency, BitOrder bitOrder, DataMode dataMode)
:
RHGenericSPI(frequency, bitOrder, dataMode)
{
}
uint8_t RHHardwareSPI2::transfer(uint8_t data)
{
return SPI2.transfer(data);
}
void RHHardwareSPI2::attachInterrupt()
{
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO)
SPI2.attachInterrupt();
#endif
}
void RHHardwareSPI2::detachInterrupt()
{
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO)
SPI2.detachInterrupt();
#endif
}
void RHHardwareSPI2::begin()
{
// Sigh: there are no common symbols for some of these SPI options across all platforms
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE)
uint8_t dataMode;
if (_dataMode == DataMode0)
dataMode = SPI_MODE0;
else if (_dataMode == DataMode1)
dataMode = SPI_MODE1;
else if (_dataMode == DataMode2)
dataMode = SPI_MODE2;
else if (_dataMode == DataMode3)
dataMode = SPI_MODE3;
else
dataMode = SPI_MODE0;
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY)
// Temporary work-around due to problem where avr_emulation.h does not work properly for the setDataMode() cal
SPCR &= ~SPI_MODE_MASK;
#else
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && defined(ARDUINO_ARCH_SAMD)
// Zero requires begin() before anything else :-)
SPI2.begin();
#endif
SPI2.setDataMode(dataMode);
#endif
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION)
uint32_t frequency32;
if (_frequency == Frequency16MHz) {
frequency32 = 16000000;
} else if (_frequency == Frequency8MHz) {
frequency32 = 8000000;
} else if (_frequency == Frequency4MHz) {
frequency32 = 4000000;
} else if (_frequency == Frequency2MHz) {
frequency32 = 2000000;
} else {
frequency32 = 1000000;
}
_settings = SPISettings(frequency32,
(_bitOrder == BitOrderLSBFirst) ? LSBFIRST : MSBFIRST,
dataMode);
#endif
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))
// Arduino Due in 1.5.5 has its own BitOrder :-(
// So too does Arduino Zero
::BitOrder bitOrder;
#else
uint8_t bitOrder;
#endif
if (_bitOrder == BitOrderLSBFirst)
bitOrder = LSBFIRST;
else
bitOrder = MSBFIRST;
SPI2.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;
}
SPI2.setClockDivider(divider);
SPI2.begin();
// Teensy requires it to be set _after_ begin()
SPI2.setClockDivider(divider);
#else
#warning RHHardwareSPI does not support this platform yet. Consider adding it and contributing a patch.
#endif
}
void RHHardwareSPI2::end()
{
return SPI2.end();
}
// If our platform is arduino and we support transactions then lets use the begin/end transaction
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION)
void RHHardwareSPI2::beginTransaction()
{
SPI2.beginTransaction(_settings);
}
void RHHardwareSPI2::endTransaction()
{
SPI2.endTransaction();
}
#endif
#endif
#endif

167
src/RHHardwareSP1I.cpp Normal file
View File

@@ -0,0 +1,167 @@
// RHHardwareSPI1.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// Contributed by Joanna Rutkowska
// $Id: RHHardwareSPI1.cpp,v 1.16 2016/07/07 00:02:53 mikem Exp mikem $
// This is a copy of the standard SPI node, that is hopefully setup to work on those processors
// who have SPI1. Currently I only have it setup for Teensy 3.5/3.6 and LC
#if defined(__arm__) && defined(TEENSYDUINO) && (defined(KINETISL) || defined(__MK64FX512__) || defined(__MK66FX1M0__) )
#include <RHHardwareSPI1.h>
// Declare a single default instance of the hardware SPI interface class
RHHardwareSPI1 hardware_spi1;
#ifdef RH_HAVE_HARDWARE_SPI
RHHardwareSPI1::RHHardwareSPI1(Frequency frequency, BitOrder bitOrder, DataMode dataMode)
:
RHGenericSPI(frequency, bitOrder, dataMode)
{
}
uint8_t RHHardwareSPI1::transfer(uint8_t data)
{
return SPI1.transfer(data);
}
void RHHardwareSPI1::attachInterrupt()
{
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO)
SPI1.attachInterrupt();
#endif
}
void RHHardwareSPI1::detachInterrupt()
{
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO)
SPI1.detachInterrupt();
#endif
}
void RHHardwareSPI1::begin()
{
// Sigh: there are no common symbols for some of these SPI options across all platforms
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE)
uint8_t dataMode;
if (_dataMode == DataMode0)
dataMode = SPI_MODE0;
else if (_dataMode == DataMode1)
dataMode = SPI_MODE1;
else if (_dataMode == DataMode2)
dataMode = SPI_MODE2;
else if (_dataMode == DataMode3)
dataMode = SPI_MODE3;
else
dataMode = SPI_MODE0;
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY)
// Temporary work-around due to problem where avr_emulation.h does not work properly for the setDataMode() cal
SPCR &= ~SPI_MODE_MASK;
#else
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && defined(ARDUINO_ARCH_SAMD)
// Zero requires begin() before anything else :-)
SPI1.begin();
#endif
SPI1.setDataMode(dataMode);
#endif
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION)
uint32_t frequency32;
if (_frequency == Frequency16MHz) {
frequency32 = 16000000;
} else if (_frequency == Frequency8MHz) {
frequency32 = 8000000;
} else if (_frequency == Frequency4MHz) {
frequency32 = 4000000;
} else if (_frequency == Frequency2MHz) {
frequency32 = 2000000;
} else {
frequency32 = 1000000;
}
_settings = SPISettings(frequency32,
(_bitOrder == BitOrderLSBFirst) ? LSBFIRST : MSBFIRST,
dataMode);
#endif
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))
// Arduino Due in 1.5.5 has its own BitOrder :-(
// So too does Arduino Zero
::BitOrder bitOrder;
#else
uint8_t bitOrder;
#endif
if (_bitOrder == BitOrderLSBFirst)
bitOrder = LSBFIRST;
else
bitOrder = MSBFIRST;
SPI1.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;
}
SPI1.setClockDivider(divider);
SPI1.begin();
// Teensy requires it to be set _after_ begin()
SPI1.setClockDivider(divider);
#else
#warning RHHardwareSPI does not support this platform yet. Consider adding it and contributing a patch.
#endif
}
void RHHardwareSPI1::end()
{
return SPI1.end();
}
// If our platform is arduino and we support transactions then lets use the begin/end transaction
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION)
void RHHardwareSPI1::beginTransaction()
{
SPI1.beginTransaction(_settings);
}
void RHHardwareSPI1::endTransaction()
{
SPI1.endTransaction();
}
#endif
#endif
#endif

412
src/RHHardwareSPI.cpp Normal file
View File

@@ -0,0 +1,412 @@
// RHHardwareSPI.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// Contributed by Joanna Rutkowska
// $Id: RHHardwareSPI.cpp,v 1.16 2016/07/07 00:02:53 mikem Exp mikem $
#include <RHHardwareSPI.h>
// Declare a single default instance of the hardware SPI interface class
RHHardwareSPI hardware_spi;
#ifdef RH_HAVE_HARDWARE_SPI
#if (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc
// Declare an SPI interface to use
HardwareSPI SPI(1);
#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 Discovery
// Declare an SPI interface to use
HardwareSPI SPI(1);
#endif
// Arduino Due has default SPI pins on central SPI headers, and not on 10, 11, 12, 13
// as per other Arduinos
// http://21stdigitalhome.blogspot.com.au/2013/02/arduino-due-hardware-spi.html
#if defined (__arm__) && !defined(CORE_TEENSY) && !defined(SPI_CLOCK_DIV16)
// Arduino Due in 1.5.5 has no definitions for SPI dividers
// SPI clock divider is based on MCK of 84MHz
#define SPI_CLOCK_DIV16 (VARIANT_MCK/84000000) // 1MHz
#define SPI_CLOCK_DIV8 (VARIANT_MCK/42000000) // 2MHz
#define SPI_CLOCK_DIV4 (VARIANT_MCK/21000000) // 4MHz
#define SPI_CLOCK_DIV2 (VARIANT_MCK/10500000) // 8MHz
#define SPI_CLOCK_DIV1 (VARIANT_MCK/5250000) // 16MHz
#endif
RHHardwareSPI::RHHardwareSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode)
:
RHGenericSPI(frequency, bitOrder, dataMode)
{
}
uint8_t RHHardwareSPI::transfer(uint8_t data)
{
return SPI.transfer(data);
}
void RHHardwareSPI::attachInterrupt()
{
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO)
SPI.attachInterrupt();
#endif
}
void RHHardwareSPI::detachInterrupt()
{
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO)
SPI.detachInterrupt();
#endif
}
void RHHardwareSPI::begin()
{
// Sigh: there are no common symbols for some of these SPI options across all platforms
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_UNO32) || (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE)
uint8_t dataMode;
if (_dataMode == DataMode0)
dataMode = SPI_MODE0;
else if (_dataMode == DataMode1)
dataMode = SPI_MODE1;
else if (_dataMode == DataMode2)
dataMode = SPI_MODE2;
else if (_dataMode == DataMode3)
dataMode = SPI_MODE3;
else
dataMode = SPI_MODE0;
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY)
// Temporary work-around due to problem where avr_emulation.h does not work properly for the setDataMode() cal
SPCR &= ~SPI_MODE_MASK;
#else
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && defined(ARDUINO_ARCH_SAMD)
// Zero requires begin() before anything else :-)
SPI.begin();
#endif
SPI.setDataMode(dataMode);
#endif
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION)
uint32_t frequency32;
if (_frequency == Frequency16MHz) {
frequency32 = 16000000;
} else if (_frequency == Frequency8MHz) {
frequency32 = 8000000;
} else if (_frequency == Frequency4MHz) {
frequency32 = 4000000;
} else if (_frequency == Frequency2MHz) {
frequency32 = 2000000;
} else {
frequency32 = 1000000;
}
_settings = SPISettings(frequency32,
(_bitOrder == BitOrderLSBFirst) ? LSBFIRST : MSBFIRST,
dataMode);
//Serial.print("SPISettings: "); Serial.println(frequency32, DEC);
#endif
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && (defined(ARDUINO_SAM_DUE) || defined(ARDUINO_ARCH_SAMD))
// Arduino Due in 1.5.5 has its own BitOrder :-(
// So too does Arduino Zero
::BitOrder bitOrder;
#else
uint8_t bitOrder;
#endif
if (_bitOrder == BitOrderLSBFirst)
bitOrder = LSBFIRST;
else
bitOrder = MSBFIRST;
SPI.setBitOrder(bitOrder);
uint8_t divider;
switch (_frequency)
{
case Frequency1MHz:
default:
#if F_CPU == 8000000
divider = SPI_CLOCK_DIV8;
#else
divider = SPI_CLOCK_DIV16;
#endif
break;
case Frequency2MHz:
#if F_CPU == 8000000
divider = SPI_CLOCK_DIV4;
#else
divider = SPI_CLOCK_DIV8;
#endif
break;
case Frequency4MHz:
#if F_CPU == 8000000
divider = SPI_CLOCK_DIV2;
#else
divider = SPI_CLOCK_DIV4;
#endif
break;
case Frequency8MHz:
divider = SPI_CLOCK_DIV2; // 4MHz on an 8MHz Arduino
break;
case Frequency16MHz:
divider = SPI_CLOCK_DIV2; // Not really 16MHz, only 8MHz. 4MHz on an 8MHz Arduino
break;
}
SPI.setClockDivider(divider);
SPI.begin();
// Teensy requires it to be set _after_ begin()
SPI.setClockDivider(divider);
#elif (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc
spi_mode dataMode;
// Hmmm, if we do this as a switch, GCC on maple gets v confused!
if (_dataMode == DataMode0)
dataMode = SPI_MODE_0;
else if (_dataMode == DataMode1)
dataMode = SPI_MODE_1;
else if (_dataMode == DataMode2)
dataMode = SPI_MODE_2;
else if (_dataMode == DataMode3)
dataMode = SPI_MODE_3;
else
dataMode = SPI_MODE_0;
uint32 bitOrder;
if (_bitOrder == BitOrderLSBFirst)
bitOrder = LSBFIRST;
else
bitOrder = MSBFIRST;
SPIFrequency frequency; // Yes, I know these are not exact equivalents.
switch (_frequency)
{
case Frequency1MHz:
default:
frequency = SPI_1_125MHZ;
break;
case Frequency2MHz:
frequency = SPI_2_25MHZ;
break;
case Frequency4MHz:
frequency = SPI_4_5MHZ;
break;
case Frequency8MHz:
frequency = SPI_9MHZ;
break;
case Frequency16MHz:
frequency = SPI_18MHZ;
break;
}
SPI.begin(frequency, bitOrder, dataMode);
#elif (RH_PLATFORM == RH_PLATFORM_STM32STD) // STM32F4 discovery
uint8_t dataMode;
if (_dataMode == DataMode0)
dataMode = SPI_MODE0;
else if (_dataMode == DataMode1)
dataMode = SPI_MODE1;
else if (_dataMode == DataMode2)
dataMode = SPI_MODE2;
else if (_dataMode == DataMode3)
dataMode = SPI_MODE3;
else
dataMode = SPI_MODE0;
uint32_t bitOrder;
if (_bitOrder == BitOrderLSBFirst)
bitOrder = LSBFIRST;
else
bitOrder = MSBFIRST;
SPIFrequency frequency; // Yes, I know these are not exact equivalents.
switch (_frequency)
{
case Frequency1MHz:
default:
frequency = SPI_1_3125MHZ;
break;
case Frequency2MHz:
frequency = SPI_2_625MHZ;
break;
case Frequency4MHz:
frequency = SPI_5_25MHZ;
break;
case Frequency8MHz:
frequency = SPI_10_5MHZ;
break;
case Frequency16MHz:
frequency = SPI_21_0MHZ;
break;
}
SPI.begin(frequency, bitOrder, dataMode);
#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Photon
Serial.println("HERE");
uint8_t dataMode;
if (_dataMode == DataMode0)
dataMode = SPI_MODE0;
else if (_dataMode == DataMode1)
dataMode = SPI_MODE1;
else if (_dataMode == DataMode2)
dataMode = SPI_MODE2;
else if (_dataMode == DataMode3)
dataMode = SPI_MODE3;
else
dataMode = SPI_MODE0;
SPI.setDataMode(dataMode);
if (_bitOrder == BitOrderLSBFirst)
SPI.setBitOrder(LSBFIRST);
else
SPI.setBitOrder(MSBFIRST);
switch (_frequency)
{
case Frequency1MHz:
default:
SPI.setClockSpeed(1, MHZ);
break;
case Frequency2MHz:
SPI.setClockSpeed(2, MHZ);
break;
case Frequency4MHz:
SPI.setClockSpeed(4, MHZ);
break;
case Frequency8MHz:
SPI.setClockSpeed(8, MHZ);
break;
case Frequency16MHz:
SPI.setClockSpeed(16, MHZ);
break;
}
// SPI.setClockDivider(SPI_CLOCK_DIV4); // 72MHz / 4MHz = 18MHz
// SPI.setClockSpeed(1, MHZ);
SPI.begin();
#elif (RH_PLATFORM == RH_PLATFORM_ESP8266)
// Requires SPI driver for ESP8266 from https://github.com/esp8266/Arduino/tree/master/libraries/SPI
// Which ppears to be in Arduino Board Manager ESP8266 Community version 2.1.0
// Contributed by David Skinner
// begin comes first
SPI.begin();
// datamode
switch ( _dataMode )
{
case DataMode1:
SPI.setDataMode ( SPI_MODE1 );
break;
case DataMode2:
SPI.setDataMode ( SPI_MODE2 );
break;
case DataMode3:
SPI.setDataMode ( SPI_MODE3 );
break;
case DataMode0:
default:
SPI.setDataMode ( SPI_MODE0 );
break;
}
// bitorder
SPI.setBitOrder(_bitOrder == BitOrderLSBFirst ? LSBFIRST : MSBFIRST);
// frequency (this sets the divider)
switch (_frequency)
{
case Frequency1MHz:
default:
SPI.setFrequency(1000000);
break;
case Frequency2MHz:
SPI.setFrequency(2000000);
break;
case Frequency4MHz:
SPI.setFrequency(4000000);
break;
case Frequency8MHz:
SPI.setFrequency(8000000);
break;
case Frequency16MHz:
SPI.setFrequency(16000000);
break;
}
#elif (RH_PLATFORM == RH_PLATFORM_RASPI) // Raspberry PI
uint8_t dataMode;
if (_dataMode == DataMode0)
dataMode = BCM2835_SPI_MODE0;
else if (_dataMode == DataMode1)
dataMode = BCM2835_SPI_MODE1;
else if (_dataMode == DataMode2)
dataMode = BCM2835_SPI_MODE2;
else if (_dataMode == DataMode3)
dataMode = BCM2835_SPI_MODE3;
uint8_t bitOrder;
if (_bitOrder == BitOrderLSBFirst)
bitOrder = BCM2835_SPI_BIT_ORDER_LSBFIRST;
else
bitOrder = BCM2835_SPI_BIT_ORDER_MSBFIRST;
uint32_t divider;
switch (_frequency)
{
case Frequency1MHz:
default:
divider = BCM2835_SPI_CLOCK_DIVIDER_256;
break;
case Frequency2MHz:
divider = BCM2835_SPI_CLOCK_DIVIDER_128;
break;
case Frequency4MHz:
divider = BCM2835_SPI_CLOCK_DIVIDER_64;
break;
case Frequency8MHz:
divider = BCM2835_SPI_CLOCK_DIVIDER_32;
break;
case Frequency16MHz:
divider = BCM2835_SPI_CLOCK_DIVIDER_16;
break;
}
SPI.begin(divider, bitOrder, dataMode);
#else
#warning RHHardwareSPI does not support this platform yet. Consider adding it and contributing a patch.
#endif
}
void RHHardwareSPI::end()
{
return SPI.end();
}
// If our platform is arduino and we support transactions then lets use the begin/end transaction
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION)
void RHHardwareSPI::beginTransaction()
{
SPI.beginTransaction(_settings);
}
void RHHardwareSPI::endTransaction()
{
SPI.endTransaction();
}
#endif
#endif

73
src/RHHardwareSPI.h Normal file
View File

@@ -0,0 +1,73 @@
// RHHardwareSPI.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// Contributed by Joanna Rutkowska
// $Id: RHHardwareSPI.h,v 1.9 2014/08/12 00:54:52 mikem Exp $
#ifndef RHHardwareSPI_h
#define RHHardwareSPI_h
#include <RHGenericSPI.h>
/////////////////////////////////////////////////////////////////////
/// \class RHHardwareSPI RHHardwareSPI.h <RHHardwareSPI.h>
/// \brief Encapsulate a hardware SPI bus interface
///
/// This concrete subclass of GenericSPIClass encapsulates the standard Arduino hardware and other
/// hardware SPI interfaces.
class RHHardwareSPI : public RHGenericSPI
{
#ifdef RH_HAVE_HARDWARE_SPI
public:
/// Constructor
/// Creates an instance of a hardware SPI interface, using whatever SPI hardware is available on
/// your processor platform. On Arduino and Uno32, uses SPI. On Maple, uses HardwareSPI.
/// \param[in] frequency One of RHGenericSPI::Frequency to select the SPI bus frequency. The frequency
/// is mapped to the closest available bus frequency on the platform.
/// \param[in] bitOrder Select the SPI bus bit order, one of RHGenericSPI::BitOrderMSBFirst or
/// RHGenericSPI::BitOrderLSBFirst.
/// \param[in] dataMode Selects the SPI bus data mode. One of RHGenericSPI::DataMode
RHHardwareSPI(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0);
/// Transfer a single octet to and from the SPI interface
/// \param[in] data The octet to send
/// \return The octet read from SPI while the data octet was sent
uint8_t transfer(uint8_t data);
// SPI Configuration methods
/// Enable SPI interrupts
/// This can be used in an SPI slave to indicate when an SPI message has been received
/// It will cause the SPI_STC_vect interrupt vectr to be executed
void attachInterrupt();
/// Disable SPI interrupts
/// This can be used to diable the SPI interrupt in slaves where that is supported.
void detachInterrupt();
/// Initialise the SPI library
/// Call this after configuring the SPI interface and before using it to transfer data.
/// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high.
void begin();
/// Disables the SPI bus (leaving pin modes unchanged).
/// Call this after you have finished using the SPI interface.
void end();
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION)
public:
void beginTransaction();
void endTransaction();
SPISettings _settings;
#endif
#else
// not supported on ATTiny etc
uint8_t transfer(uint8_t data) {return 0;}
void begin(){}
void end(){}
#endif
};
// Built in default instance
extern RHHardwareSPI hardware_spi;
#endif

76
src/RHHardwareSPI1.h Normal file
View File

@@ -0,0 +1,76 @@
// RHHardwareSPI.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// Contributed by Joanna Rutkowska
// $Id: RHHardwareSPI.h,v 1.9 2014/08/12 00:54:52 mikem Exp $
#ifndef RHHardwareSPI1_h
#define RHHardwareSPI1_h
#if defined(__arm__) && defined(TEENSYDUINO) && (defined(KINETISL) || defined(__MK64FX512__) || defined(__MK66FX1M0__) )
#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.
class RHHardwareSPI1 : 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
RHHardwareSPI1(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0);
/// Transfer a single octet to and from the SPI interface
/// \param[in] data The octet to send
/// \return The octet read from SPI while the data octet was sent
uint8_t transfer(uint8_t data);
// SPI Configuration methods
/// Enable SPI interrupts
/// This can be used in an SPI slave to indicate when an SPI message has been received
/// It will cause the SPI_STC_vect interrupt vectr to be executed
void attachInterrupt();
/// Disable SPI interrupts
/// This can be used to diable the SPI interrupt in slaves where that is supported.
void detachInterrupt();
/// Initialise the SPI library
/// Call this after configuring the SPI interface and before using it to transfer data.
/// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high.
void begin();
/// Disables the SPI bus (leaving pin modes unchanged).
/// Call this after you have finished using the SPI interface.
void end();
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION)
public:
void beginTransaction();
void endTransaction();
SPISettings _settings;
#endif
#else
// not supported on ATTiny etc
uint8_t transfer(uint8_t data) {return 0;}
void begin(){}
void end(){}
#endif
};
// Built in default instance
extern RHHardwareSPI1 hardware_spi1;
#else
#error ("RadioHead SPI1 only supported on Teensy 3.5, 3.6 and LC")
#endif
#endif

76
src/RHHardwareSPI2.h Normal file
View File

@@ -0,0 +1,76 @@
// RHHardwareSPI.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// Contributed by Joanna Rutkowska
// $Id: RHHardwareSPI.h,v 1.9 2014/08/12 00:54:52 mikem Exp $
#ifndef RHHardwareSPI2_h
#define RHHardwareSPI2_h
#if defined(__arm__) && defined(TEENSYDUINO) && (defined(__MK64FX512__) || defined(__MK66FX1M0__) )
#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.
class RHHardwareSPI2 : 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
RHHardwareSPI2(Frequency frequency = Frequency1MHz, BitOrder bitOrder = BitOrderMSBFirst, DataMode dataMode = DataMode0);
/// Transfer a single octet to and from the SPI interface
/// \param[in] data The octet to send
/// \return The octet read from SPI while the data octet was sent
uint8_t transfer(uint8_t data);
// SPI Configuration methods
/// Enable SPI interrupts
/// This can be used in an SPI slave to indicate when an SPI message has been received
/// It will cause the SPI_STC_vect interrupt vectr to be executed
void attachInterrupt();
/// Disable SPI interrupts
/// This can be used to diable the SPI interrupt in slaves where that is supported.
void detachInterrupt();
/// Initialise the SPI library
/// Call this after configuring the SPI interface and before using it to transfer data.
/// Initializes the SPI bus by setting SCK, MOSI, and SS to outputs, pulling SCK and MOSI low, and SS high.
void begin();
/// Disables the SPI bus (leaving pin modes unchanged).
/// Call this after you have finished using the SPI interface.
void end();
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(SPI_HAS_TRANSACTION)
public:
void beginTransaction();
void endTransaction();
SPISettings _settings;
#endif
#else
// not supported on ATTiny etc
uint8_t transfer(uint8_t data) {return 0;}
void begin(){}
void end(){}
#endif
};
// Built in default instance
extern RHHardwareSPI2 hardware_spi2;
#else
#error ("RadioHead SPI2 only supported on Teensy 3.5, 3.6")
#endif
#endif

244
src/RHMesh.cpp Normal file
View File

@@ -0,0 +1,244 @@
// RHMesh.cpp
//
// Define addressed datagram
//
// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers
// (see http://www.hoperf.com)
// RHDatagram will be received only by the addressed node or all nodes within range if the
// to address is RH_BROADCAST_ADDRESS
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// $Id: RHMesh.cpp,v 1.9 2015/08/13 02:45:47 mikem Exp $
#include <RHMesh.h>
uint8_t RHMesh::_tmpMessage[RH_ROUTER_MAX_MESSAGE_LEN];
////////////////////////////////////////////////////////////////////
// Constructors
RHMesh::RHMesh(RHGenericDriver& driver, uint8_t thisAddress)
: RHRouter(driver, thisAddress)
{
}
////////////////////////////////////////////////////////////////////
// Public methods
////////////////////////////////////////////////////////////////////
// Discovers a route to the destination (if necessary), sends and
// waits for delivery to the next hop (but not for delivery to the final destination)
uint8_t RHMesh::sendtoWait(uint8_t* buf, uint8_t len, uint8_t address, uint8_t flags)
{
if (len > RH_MESH_MAX_MESSAGE_LEN)
return RH_ROUTER_ERROR_INVALID_LENGTH;
if (address != RH_BROADCAST_ADDRESS)
{
RoutingTableEntry* route = getRouteTo(address);
if (!route && !doArp(address))
return RH_ROUTER_ERROR_NO_ROUTE;
}
// Now have a route. Contruct an application layer message and send it via that route
MeshApplicationMessage* a = (MeshApplicationMessage*)&_tmpMessage;
a->header.msgType = RH_MESH_MESSAGE_TYPE_APPLICATION;
memcpy(a->data, buf, len);
return RHRouter::sendtoWait(_tmpMessage, sizeof(RHMesh::MeshMessageHeader) + len, address, flags);
}
////////////////////////////////////////////////////////////////////
bool RHMesh::doArp(uint8_t address)
{
// Need to discover a route
// Broadcast a route discovery message with nothing in it
MeshRouteDiscoveryMessage* p = (MeshRouteDiscoveryMessage*)&_tmpMessage;
p->header.msgType = RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST;
p->destlen = 1;
p->dest = address; // Who we are looking for
uint8_t error = RHRouter::sendtoWait((uint8_t*)p, sizeof(RHMesh::MeshMessageHeader) + 2, RH_BROADCAST_ADDRESS);
if (error != RH_ROUTER_ERROR_NONE)
return false;
// Wait for a reply, which will be unicast back to us
// It will contain the complete route to the destination
uint8_t messageLen = sizeof(_tmpMessage);
// FIXME: timeout should be configurable
unsigned long starttime = millis();
int32_t timeLeft;
while ((timeLeft = RH_MESH_ARP_TIMEOUT - (millis() - starttime)) > 0)
{
if (waitAvailableTimeout(timeLeft))
{
if (RHRouter::recvfromAck(_tmpMessage, &messageLen))
{
if ( messageLen > 1
&& p->header.msgType == RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE)
{
// Got a reply, now add the next hop to the dest to the routing table
// The first hop taken is the first octet
addRouteTo(address, headerFrom());
return true;
}
}
}
YIELD;
}
return false;
}
////////////////////////////////////////////////////////////////////
// Called by RHRouter::recvfromAck whenever a message goes past
void RHMesh::peekAtMessage(RoutedMessage* message, uint8_t messageLen)
{
MeshMessageHeader* m = (MeshMessageHeader*)message->data;
if ( messageLen > 1
&& m->msgType == RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE)
{
// This is a unicast RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE messages
// being routed back to the originator here. Want to scrape some routing data out of the response
// We can find the routes to all the nodes between here and the responding node
MeshRouteDiscoveryMessage* d = (MeshRouteDiscoveryMessage*)message->data;
addRouteTo(d->dest, headerFrom());
uint8_t numRoutes = messageLen - sizeof(RoutedMessageHeader) - sizeof(MeshMessageHeader) - 2;
uint8_t i;
// Find us in the list of nodes that were traversed to get to the responding node
for (i = 0; i < numRoutes; i++)
if (d->route[i] == _thisAddress)
break;
i++;
while (i++ < numRoutes)
addRouteTo(d->route[i], headerFrom());
}
else if ( messageLen > 1
&& m->msgType == RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE)
{
MeshRouteFailureMessage* d = (MeshRouteFailureMessage*)message->data;
deleteRouteTo(d->dest);
}
}
////////////////////////////////////////////////////////////////////
// This is called when a message is to be delivered to the next hop
uint8_t RHMesh::route(RoutedMessage* message, uint8_t messageLen)
{
uint8_t from = headerFrom(); // Might get clobbered during call to superclass route()
uint8_t ret = RHRouter::route(message, messageLen);
if ( ret == RH_ROUTER_ERROR_NO_ROUTE
|| ret == RH_ROUTER_ERROR_UNABLE_TO_DELIVER)
{
// Cant deliver to the next hop. Delete the route
deleteRouteTo(message->header.dest);
if (message->header.source != _thisAddress)
{
// This is being proxied, so tell the originator about it
MeshRouteFailureMessage* p = (MeshRouteFailureMessage*)&_tmpMessage;
p->header.msgType = RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE;
p->dest = message->header.dest; // Who you were trying to deliver to
// Make sure there is a route back towards whoever sent the original message
addRouteTo(message->header.source, from);
ret = RHRouter::sendtoWait((uint8_t*)p, sizeof(RHMesh::MeshMessageHeader) + 1, message->header.source);
}
}
return ret;
}
////////////////////////////////////////////////////////////////////
// Subclasses may want to override
bool RHMesh::isPhysicalAddress(uint8_t* address, uint8_t addresslen)
{
// Can only handle physical addresses 1 octet long, which is the physical node address
return addresslen == 1 && address[0] == _thisAddress;
}
////////////////////////////////////////////////////////////////////
bool RHMesh::recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source, uint8_t* dest, uint8_t* id, uint8_t* flags)
{
uint8_t tmpMessageLen = sizeof(_tmpMessage);
uint8_t _source;
uint8_t _dest;
uint8_t _id;
uint8_t _flags;
if (RHRouter::recvfromAck(_tmpMessage, &tmpMessageLen, &_source, &_dest, &_id, &_flags))
{
MeshMessageHeader* p = (MeshMessageHeader*)&_tmpMessage;
if ( tmpMessageLen >= 1
&& p->msgType == RH_MESH_MESSAGE_TYPE_APPLICATION)
{
MeshApplicationMessage* a = (MeshApplicationMessage*)p;
// Handle application layer messages, presumably for our caller
if (source) *source = _source;
if (dest) *dest = _dest;
if (id) *id = _id;
if (flags) *flags = _flags;
uint8_t msgLen = tmpMessageLen - sizeof(MeshMessageHeader);
if (*len > msgLen)
*len = msgLen;
memcpy(buf, a->data, *len);
return true;
}
else if ( _dest == RH_BROADCAST_ADDRESS
&& tmpMessageLen > 1
&& p->msgType == RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST)
{
MeshRouteDiscoveryMessage* d = (MeshRouteDiscoveryMessage*)p;
// Handle Route discovery requests
// Message is an array of node addresses the route request has already passed through
// If it originally came from us, ignore it
if (_source == _thisAddress)
return false;
uint8_t numRoutes = tmpMessageLen - sizeof(MeshMessageHeader) - 2;
uint8_t i;
// Are we already mentioned?
for (i = 0; i < numRoutes; i++)
if (d->route[i] == _thisAddress)
return false; // Already been through us. Discard
// Hasnt been past us yet, record routes back to the earlier nodes
addRouteTo(_source, headerFrom()); // The originator
for (i = 0; i < numRoutes; i++)
addRouteTo(d->route[i], headerFrom());
if (isPhysicalAddress(&d->dest, d->destlen))
{
// This route discovery is for us. Unicast the whole route back to the originator
// as a RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE
// We are certain to have a route there, because we just got it
d->header.msgType = RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE;
RHRouter::sendtoWait((uint8_t*)d, tmpMessageLen, _source);
}
else if (i < _max_hops)
{
// Its for someone else, rebroadcast it, after adding ourselves to the list
d->route[numRoutes] = _thisAddress;
tmpMessageLen++;
// Have to impersonate the source
// REVISIT: if this fails what can we do?
RHRouter::sendtoFromSourceWait(_tmpMessage, tmpMessageLen, RH_BROADCAST_ADDRESS, _source);
}
}
}
return false;
}
////////////////////////////////////////////////////////////////////
bool RHMesh::recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags)
{
unsigned long starttime = millis();
int32_t timeLeft;
while ((timeLeft = timeout - (millis() - starttime)) > 0)
{
if (waitAvailableTimeout(timeLeft))
{
if (recvfromAck(buf, len, from, to, id, flags))
return true;
YIELD;
}
}
return false;
}

262
src/RHMesh.h Normal file
View File

@@ -0,0 +1,262 @@
// RHMesh.h
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// $Id: RHMesh.h,v 1.15 2015/08/13 02:45:47 mikem Exp $
#ifndef RHMesh_h
#define RHMesh_h
#include <RHRouter.h>
// Types of RHMesh message, used to set msgType in the RHMeshHeader
#define RH_MESH_MESSAGE_TYPE_APPLICATION 0
#define RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST 1
#define RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE 2
#define RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE 3
// Timeout for address resolution in milliecs
#define RH_MESH_ARP_TIMEOUT 4000
/////////////////////////////////////////////////////////////////////
/// \class RHMesh RHMesh.h <RHMesh.h>
/// \brief RHRouter subclass for sending addressed, optionally acknowledged datagrams
/// multi-hop routed across a network, with automatic route discovery
///
/// Manager class that extends RHRouter to add automatic route discovery within a mesh of adjacent nodes,
/// and route signalling.
///
/// Unlike RHRouter, RHMesh can be used in networks where the network topology is fluid, or unknown,
/// or if nodes can mode around or go in or out of service. When a node wants to send a
/// message to another node, it will automatically discover a route to the destination node and use it.
/// If the route becomes unavailable, a new route will be discovered.
///
/// \par Route Discovery
///
/// When a RHMesh mesh node is initialised, it doe not know any routes to any other nodes
/// (see RHRouter for details on route and the routing table).
/// When you attempt to send a message with sendtoWait, will first check to see if there is a route to the
/// destinastion node in the routing tabl;e. If not, it wil initialite 'Route Discovery'.
/// When a node needs to discover a route to another node, it broadcasts MeshRouteDiscoveryMessage
/// with a message type of RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST.
/// Any node that receives such a request checks to see if it is a request for a route to itself
/// (in which case it makes a unicast reply to the originating node with a
/// MeshRouteDiscoveryMessage
/// with a message type of RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE)
/// otherwise it rebroadcasts the request, after adding itself to the list of nodes visited so
/// far by the request.
///
/// If a node receives a RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST that already has itself
/// listed in the visited nodes, it knows it has already seen and rebroadcast this request,
/// and threfore ignores it. This prevents broadcast storms.
/// When a node receives a RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST it can use the list of
/// nodes aready visited to deduce routes back towards the originating (requesting node).
/// This also means that when the destination node of the request is reached, it (and all
/// the previous nodes the request visited) will have a route back to the originating node.
/// This means the unicast RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE
/// reply will be routed successfully back to the original route requester.
///
/// The RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE sent back by the destination node contains
/// the full list of nodes that were visited on the way to the destination.
/// Therefore, intermediate nodes that route the reply back towards the originating node can use the
/// node list in the reply to deduce routes to all the nodes between it and the destination node.
///
/// Therefore, RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST and
/// RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE together ensure the original requester and all
/// the intermediate nodes know how to route to the source and destination nodes and every node along the path.
///
/// Note that there is a race condition here that can effect routing on multipath routes. For example,
/// if the route to the destination can traverse several paths, last reply from the destination
/// will be the one used.
///
/// \par Route Failure
///
/// RHRouter (and therefore RHMesh) use reliable hop-to-hop delivery of messages using
/// hop-to-hop acknowledgements, but not end-to-end acknowledgements. When sendtoWait() returns,
/// you know that the message has been delivered to the next hop, but not if it is (or even if it can be)
/// delivered to the destination node. If during the course of hop-to-hop routing of a message,
/// one of the intermediate RHMesh nodes finds it cannot deliver to the next hop
/// (say due to a lost route or no acknwledgement from the next hop), it replies to the
/// originator with a unicast MeshRouteFailureMessage RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE message.
/// Intermediate nodes (on the way beack to the originator)
/// and the originating node use this message to delete the route to the destination
/// node of the original message. This means that if a route to a destination becomes unusable
/// (either because an intermediate node is off the air, or has moved out of range) a new route
/// will be established the next time a message is to be sent.
///
/// \par Message Format
///
/// RHMesh uses a number of message formats layered on top of RHRouter:
/// - MeshApplicationMessage (message type RH_MESH_MESSAGE_TYPE_APPLICATION).
/// Carries an application layer message for the caller of RHMesh
/// - MeshRouteDiscoveryMessage (message types RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST
/// and RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_RESPONSE). Carries Route Discovery messages
/// (broadcast) and replies (unicast).
/// - MeshRouteFailureMessage (message type RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE) Informs nodes of
/// route failures.
///
/// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers
/// (see http://www.hoperf.com)
///
/// \par Memory
///
/// RHMesh programs require significant amount of SRAM, often approaching 2kbytes,
/// which is beyond or at the limits of some Arduinos and other processors. Programs
/// with additional software besides basic RHMesh programs may well require even more. If you have insufficient
/// SRAM for your program, it may result in failure to run, or wierd crashes and other hard to trace behaviour.
/// In this event you should consider a processor with more SRAM, such as the MotienoMEGA with 16k
/// (https://lowpowerlab.com/shop/moteinomega) or others.
///
/// \par Performance
/// This class (in the interests of simple implemtenation and low memory use) does not have
/// message queueing. This means that only one message at a time can be handled. Message transmission
/// failures can have a severe impact on network performance.
/// If you need high performance mesh networking under all conditions consider XBee or similar.
class RHMesh : public RHRouter
{
public:
/// The maximum length permitted for the application payload data in a RHMesh message
#define RH_MESH_MAX_MESSAGE_LEN (RH_ROUTER_MAX_MESSAGE_LEN - sizeof(RHMesh::MeshMessageHeader))
/// Structure of the basic RHMesh header.
typedef struct
{
uint8_t msgType; ///< Type of RHMesh message, one of RH_MESH_MESSAGE_TYPE_*
} MeshMessageHeader;
/// Signals an application layer message for the caller of RHMesh
typedef struct
{
MeshMessageHeader header; ///< msgType = RH_MESH_MESSAGE_TYPE_APPLICATION
uint8_t data[RH_MESH_MAX_MESSAGE_LEN]; ///< Application layer payload data
} MeshApplicationMessage;
/// Signals a route discovery request or reply (At present only supports physical dest addresses of length 1 octet)
typedef struct
{
MeshMessageHeader header; ///< msgType = RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_*
uint8_t destlen; ///< Reserved. Must be 1.g
uint8_t dest; ///< The address of the destination node whose route is being sought
uint8_t route[RH_MESH_MAX_MESSAGE_LEN - 1]; ///< List of node addresses visited so far. Length is implcit
} MeshRouteDiscoveryMessage;
/// Signals a route failure
typedef struct
{
MeshMessageHeader header; ///< msgType = RH_MESH_MESSAGE_TYPE_ROUTE_FAILURE
uint8_t dest; ///< The address of the destination towards which the route failed
} MeshRouteFailureMessage;
/// Constructor.
/// \param[in] driver The RadioHead driver to use to transport messages.
/// \param[in] thisAddress The address to assign to this node. Defaults to 0
RHMesh(RHGenericDriver& driver, uint8_t thisAddress = 0);
/// Sends a message to the destination node. Initialises the RHRouter message header
/// (the SOURCE address is set to the address of this node, HOPS to 0) and calls
/// route() which looks up in the routing table the next hop to deliver to.
/// If no route is known, initiates route discovery and waits for a reply.
/// Then sends the message to the next hop
/// Then waits for an acknowledgement from the next hop
/// (but not from the destination node (if that is different).
/// \param [in] buf The application message data
/// \param [in] len Number of octets in the application message data. 0 is permitted
/// \param [in] dest The destination node address. If the address is RH_BROADCAST_ADDRESS (255)
/// the message will be broadcast to all the nearby nodes, but not routed or relayed.
/// \param [in] flags Optional flags for use by subclasses or application layer,
/// delivered end-to-end to the dest address. The receiver can recover the flags with recvFromAck().
/// \return The result code:
/// - RH_ROUTER_ERROR_NONE Message was routed and delivered to the next hop
/// (not necessarily to the final dest address)
/// - RH_ROUTER_ERROR_NO_ROUTE There was no route for dest in the local routing table
/// - RH_ROUTER_ERROR_UNABLE_TO_DELIVER Not able to deliver to the next hop
/// (usually because it dod not acknowledge due to being off the air or out of range
uint8_t sendtoWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t flags = 0);
/// Starts the receiver if it is not running already, processes and possibly routes any received messages
/// addressed to other nodes
/// and delivers any messages addressed to this node.
/// If there is a valid application layer message available for this node (or RH_BROADCAST_ADDRESS),
/// send an acknowledgement to the last hop
/// address (blocking until this is complete), then copy the application message payload data
/// to buf and return true
/// else return false.
/// If a message is copied, *len is set to the length..
/// If from is not NULL, the originator SOURCE address is placed in *source.
/// If to is not NULL, the DEST address is placed in *dest. This might be this nodes address or
/// RH_BROADCAST_ADDRESS.
/// This is the preferred function for getting messages addressed to this node.
/// If the message is not a broadcast, acknowledge to the sender before returning.
/// \param[in] buf Location to copy the received message
/// \param[in,out] len Available space in buf. Set to the actual number of octets copied.
/// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address
/// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address
/// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID
/// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS
/// (not just those addressed to this node).
/// \return true if a valid message was received for this node and copied to buf
bool recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL);
/// Starts the receiver if it is not running already.
/// Similar to recvfromAck(), this will block until either a valid application layer
/// message available for this node
/// or the timeout expires.
/// \param[in] buf Location to copy the received message
/// \param[in,out] len Available space in buf. Set to the actual number of octets copied.
/// \param[in] timeout Maximum time to wait in milliseconds
/// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address
/// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address
/// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID
/// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS
/// (not just those addressed to this node).
/// \return true if a valid message was copied to buf
bool recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL);
protected:
/// Internal function that inspects messages being received and adjusts the routing table if necessary.
/// Called by recvfromAck() immediately after it gets the message from RHReliableDatagram
/// \param [in] message Pointer to the RHRouter message that was received.
/// \param [in] messageLen Length of message in octets
virtual void peekAtMessage(RoutedMessage* message, uint8_t messageLen);
/// Internal function that inspects messages being received and adjusts the routing table if necessary.
/// This is virtual, which lets subclasses override or intercept the route() function.
/// Called by sendtoWait after the message header has been filled in.
/// \param [in] message Pointer to the RHRouter message to be sent.
/// \param [in] messageLen Length of message in octets
virtual uint8_t route(RoutedMessage* message, uint8_t messageLen);
/// Try to resolve a route for the given address. Blocks while discovering the route
/// which may take up to 4000 msec.
/// Virtual so subclasses can override.
/// \param [in] address The physical address to resolve
/// \return true if the address was resolved and added to the local routing table
virtual bool doArp(uint8_t address);
/// Tests if the given address of length addresslen is indentical to the
/// physical address of this node.
/// RHMesh always implements physical addresses as the 1 octet address of the node
/// given by _thisAddress
/// Called by recvfromAck() to test whether a RH_MESH_MESSAGE_TYPE_ROUTE_DISCOVERY_REQUEST
/// is for this node.
/// Subclasses may want to override to implement more complicated or longer physical addresses
/// \param [in] address Address of the pyysical addres being tested
/// \param [in] addresslen Lengthof the address in bytes
/// \return true if the physical address of this node is identical to address
virtual bool isPhysicalAddress(uint8_t* address, uint8_t addresslen);
private:
/// Temporary message buffer
static uint8_t _tmpMessage[RH_ROUTER_MAX_MESSAGE_LEN];
};
/// @example rf22_mesh_client.pde
/// @example rf22_mesh_server1.pde
/// @example rf22_mesh_server2.pde
/// @example rf22_mesh_server3.pde
#endif

113
src/RHNRFSPIDriver.cpp Normal file
View File

@@ -0,0 +1,113 @@
// RHNRFSPIDriver.cpp
//
// Copyright (C) 2014 Mike McCauley
// $Id: RHNRFSPIDriver.cpp,v 1.3 2015/12/16 04:55:33 mikem Exp $
#include <RHNRFSPIDriver.h>
RHNRFSPIDriver::RHNRFSPIDriver(uint8_t slaveSelectPin, RHGenericSPI& spi)
:
_spi(spi),
_slaveSelectPin(slaveSelectPin)
{
}
bool RHNRFSPIDriver::init()
{
// start the SPI library with the default speeds etc:
// On Arduino Due this defaults to SPI1 on the central group of 6 SPI pins
_spi.begin();
// Initialise the slave select pin
// On Maple, this must be _after_ spi.begin
pinMode(_slaveSelectPin, OUTPUT);
digitalWrite(_slaveSelectPin, HIGH);
delay(100);
return true;
}
// Low level commands for interfacing with the device
uint8_t RHNRFSPIDriver::spiCommand(uint8_t command)
{
uint8_t status;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
status = _spi.transfer(command);
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return status;
}
uint8_t RHNRFSPIDriver::spiRead(uint8_t reg)
{
uint8_t val;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
_spi.transfer(reg); // Send the address, discard the status
val = _spi.transfer(0); // The written value is ignored, reg value is read
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return val;
}
uint8_t RHNRFSPIDriver::spiWrite(uint8_t reg, uint8_t val)
{
uint8_t status = 0;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
status = _spi.transfer(reg); // Send the address
_spi.transfer(val); // New value follows
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY)
// Sigh: some devices, such as MRF89XA dont work properly on Teensy 3.1:
// At 1MHz, the clock returns low _after_ slave select goes high, which prevents SPI
// write working. This delay gixes time for the clock to return low.
delayMicroseconds(5);
#endif
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return status;
}
uint8_t RHNRFSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len)
{
uint8_t status = 0;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
status = _spi.transfer(reg); // Send the start address
while (len--)
*dest++ = _spi.transfer(0);
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return status;
}
uint8_t RHNRFSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len)
{
uint8_t status = 0;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
status = _spi.transfer(reg); // Send the start address
while (len--)
_spi.transfer(*src++);
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return status;
}
void RHNRFSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin)
{
_slaveSelectPin = slaveSelectPin;
}

95
src/RHNRFSPIDriver.h Normal file
View File

@@ -0,0 +1,95 @@
// RHNRFSPIDriver.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2014 Mike McCauley
// $Id: RHNRFSPIDriver.h,v 1.3 2015/12/16 04:55:33 mikem Exp $
#ifndef RHNRFSPIDriver_h
#define RHNRFSPIDriver_h
#include <RHGenericDriver.h>
#include <RHHardwareSPI.h>
class RHGenericSPI;
/////////////////////////////////////////////////////////////////////
/// \class RHNRFSPIDriver RHNRFSPIDriver.h <RHNRFSPIDriver.h>
/// \brief Base class for a RadioHead driver that use the SPI bus
/// to communicate with its transport hardware.
///
/// This class can be subclassed by Drivers that require to use the SPI bus.
/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform)
/// of the bitbanged RHSoftwareSPI class. The dfault behaviour is to use a pre-instantiated built-in RHHardwareSPI
/// interface.
///
/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts
/// are disabled during access.
///
/// The read and write routines use SPI conventions as used by Nordic NRF radios and otehr devices,
/// but these can be overriden
/// in subclasses if necessary.
///
/// Application developers are not expected to instantiate this class directly:
/// it is for the use of Driver developers.
class RHNRFSPIDriver : public RHGenericDriver
{
public:
/// Constructor
/// \param[in] slaveSelectPin The controller pin to use to select the desired SPI device. This pin will be driven LOW
/// during SPI communications with the SPI device that uis iused by this Driver.
/// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface.
RHNRFSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi);
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
bool init();
/// Sends a single command to the device
/// \param[in] command The command code to send to the device.
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
/// it may or may not be meaningfule depending on the the type of device being accessed.
uint8_t spiCommand(uint8_t command);
/// Reads a single register from the SPI device
/// \param[in] reg Register number
/// \return The value of the register
uint8_t spiRead(uint8_t reg);
/// Writes a single byte to the SPI device
/// \param[in] reg Register number
/// \param[in] val The value to write
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
/// it may or may not be meaningfule depending on the the type of device being accessed.
uint8_t spiWrite(uint8_t reg, uint8_t val);
/// Reads a number of consecutive registers from the SPI device using burst read mode
/// \param[in] reg Register number of the first register
/// \param[in] dest Array to write the register values to. Must be at least len bytes
/// \param[in] len Number of bytes to read
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
/// it may or may not be meaningfule depending on the the type of device being accessed.
uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len);
/// Write a number of consecutive registers using burst write mode
/// \param[in] reg Register number of the first register
/// \param[in] src Array of new register values to write. Must be at least len bytes
/// \param[in] len Number of bytes to write
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
/// it may or may not be meaningfule depending on the the type of device being accessed.
uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len);
/// Set or change the pin to be used for SPI slave select.
/// This can be called at any time to change the
/// pin that will be used for slave select in subsquent SPI operations.
/// \param[in] slaveSelectPin The pin to use
void setSlaveSelectPin(uint8_t slaveSelectPin);
protected:
/// Reference to the RHGenericSPI instance to use to trasnfer data with teh SPI device
RHGenericSPI& _spi;
/// The pin number of the Slave Select pin that is used to select the desired device.
uint8_t _slaveSelectPin;
};
#endif

187
src/RHReliableDatagram.cpp Normal file
View File

@@ -0,0 +1,187 @@
// RHReliableDatagram.cpp
//
// Define addressed datagram
//
// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers
// (see http://www.hoperf.com)
// RHDatagram will be received only by the addressed node or all nodes within range if the
// to address is RH_BROADCAST_ADDRESS
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// $Id: RHReliableDatagram.cpp,v 1.15 2015/12/11 01:10:24 mikem Exp $
#include <RHReliableDatagram.h>
////////////////////////////////////////////////////////////////////
// Constructors
RHReliableDatagram::RHReliableDatagram(RHGenericDriver& driver, uint8_t thisAddress)
: RHDatagram(driver, thisAddress)
{
_retransmissions = 0;
_lastSequenceNumber = 0;
_timeout = RH_DEFAULT_TIMEOUT;
_retries = RH_DEFAULT_RETRIES;
}
////////////////////////////////////////////////////////////////////
// Public methods
void RHReliableDatagram::setTimeout(uint16_t timeout)
{
_timeout = timeout;
}
////////////////////////////////////////////////////////////////////
void RHReliableDatagram::setRetries(uint8_t retries)
{
_retries = retries;
}
////////////////////////////////////////////////////////////////////
uint8_t RHReliableDatagram::retries()
{
return _retries;
}
////////////////////////////////////////////////////////////////////
bool RHReliableDatagram::sendtoWait(uint8_t* buf, uint8_t len, uint8_t address)
{
// Assemble the message
uint8_t thisSequenceNumber = ++_lastSequenceNumber;
uint8_t retries = 0;
while (retries++ <= _retries)
{
setHeaderId(thisSequenceNumber);
setHeaderFlags(RH_FLAGS_NONE, RH_FLAGS_ACK); // Clear the ACK flag
sendto(buf, len, address);
waitPacketSent();
// Never wait for ACKS to broadcasts:
if (address == RH_BROADCAST_ADDRESS)
return true;
if (retries > 1)
_retransmissions++;
unsigned long thisSendTime = millis(); // Timeout does not include original transmit time
// Compute a new timeout, random between _timeout and _timeout*2
// This is to prevent collisions on every retransmit
// if 2 nodes try to transmit at the same time
#if (RH_PLATFORM == RH_PLATFORM_RASPI) // use standard library random(), bugs in random(min, max)
uint16_t timeout = _timeout + (_timeout * (random() & 0xFF) / 256);
#else
uint16_t timeout = _timeout + (_timeout * random(0, 256) / 256);
#endif
int32_t timeLeft;
while ((timeLeft = timeout - (millis() - thisSendTime)) > 0)
{
if (waitAvailableTimeout(timeLeft))
{
uint8_t from, to, id, flags;
if (recvfrom(0, 0, &from, &to, &id, &flags)) // Discards the message
{
// Now have a message: is it our ACK?
if ( from == address
&& to == _thisAddress
&& (flags & RH_FLAGS_ACK)
&& (id == thisSequenceNumber))
{
// Its the ACK we are waiting for
return true;
}
else if ( !(flags & RH_FLAGS_ACK)
&& (id == _seenIds[from]))
{
// This is a request we have already received. ACK it again
acknowledge(id, from);
}
// Else discard it
}
}
// Not the one we are waiting for, maybe keep waiting until timeout exhausted
YIELD;
}
// Timeout exhausted, maybe retry
YIELD;
}
// Retries exhausted
return false;
}
////////////////////////////////////////////////////////////////////
bool RHReliableDatagram::recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags)
{
uint8_t _from;
uint8_t _to;
uint8_t _id;
uint8_t _flags;
// Get the message before its clobbered by the ACK (shared rx and tx buffer in some drivers
if (available() && recvfrom(buf, len, &_from, &_to, &_id, &_flags))
{
// Never ACK an ACK
if (!(_flags & RH_FLAGS_ACK))
{
// Its a normal message for this node, not an ACK
if (_to != RH_BROADCAST_ADDRESS)
{
// Its not a broadcast, so ACK it
// Acknowledge message with ACK set in flags and ID set to received ID
acknowledge(_id, _from);
}
// If we have not seen this message before, then we are interested in it
if (_id != _seenIds[_from])
{
if (from) *from = _from;
if (to) *to = _to;
if (id) *id = _id;
if (flags) *flags = _flags;
_seenIds[_from] = _id;
return true;
}
// Else just re-ack it and wait for a new one
}
}
// No message for us available
return false;
}
bool RHReliableDatagram::recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* from, uint8_t* to, uint8_t* id, uint8_t* flags)
{
unsigned long starttime = millis();
int32_t timeLeft;
while ((timeLeft = timeout - (millis() - starttime)) > 0)
{
if (waitAvailableTimeout(timeLeft))
{
if (recvfromAck(buf, len, from, to, id, flags))
return true;
}
YIELD;
}
return false;
}
uint32_t RHReliableDatagram::retransmissions()
{
return _retransmissions;
}
void RHReliableDatagram::resetRetransmissions()
{
_retransmissions = 0;
}
void RHReliableDatagram::acknowledge(uint8_t id, uint8_t from)
{
setHeaderId(id);
setHeaderFlags(RH_FLAGS_ACK);
// We would prefer to send a zero length ACK,
// but if an RH_RF22 receives a 0 length message with a CRC error, it will never receive
// a 0 length message again, until its reset, which makes everything hang :-(
// So we send an ACK of 1 octet
// REVISIT: should we send the RSSI for the information of the sender?
uint8_t ack = '!';
sendto(&ack, sizeof(ack), from);
waitPacketSent();
}

203
src/RHReliableDatagram.h Normal file
View File

@@ -0,0 +1,203 @@
// RHReliableDatagram.h
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// $Id: RHReliableDatagram.h,v 1.17 2016/04/04 01:40:12 mikem Exp $
#ifndef RHReliableDatagram_h
#define RHReliableDatagram_h
#include <RHDatagram.h>
// The acknowledgement bit in the FLAGS
// The top 4 bits of the flags are reserved for RadioHead. The lower 4 bits are reserved
// for application layer use.
#define RH_FLAGS_ACK 0x80
/// the default retry timeout in milliseconds
#define RH_DEFAULT_TIMEOUT 200
/// The default number of retries
#define RH_DEFAULT_RETRIES 3
/////////////////////////////////////////////////////////////////////
/// \class RHReliableDatagram RHReliableDatagram.h <RHReliableDatagram.h>
/// \brief RHDatagram subclass for sending addressed, acknowledged, retransmitted datagrams.
///
/// Manager class that extends RHDatagram to define addressed, reliable datagrams with acknowledgement and retransmission.
/// Based on RHDatagram, adds flags and sequence numbers. RHReliableDatagram is reliable in the sense
/// that messages are acknowledged by the recipient, and unacknowledged messages are retransmitted until acknowledged or the
/// retries are exhausted.
/// When addressed messages are sent (by sendtoWait()), it will wait for an ack, and retransmit
/// after timeout until an ack is received or retries are exhausted.
/// When addressed messages are collected by the application (by recvfromAck()),
/// an acknowledgement is automatically sent to the sender.
///
/// You can use RHReliableDatagram to send broadcast messages, with a TO address of RH_BROADCAST_ADDRESS,
/// however broadcasts are not acknowledged or retransmitted and are therefore NOT actually reliable.
///
/// The retransmit timeout is randomly varied between timeout and timeout*2 to prevent collisions on all
/// retries when 2 nodes happen to start sending at the same time .
///
/// Each new message sent by sendtoWait() has its ID incremented.
///
/// An ack consists of a message with:
/// - TO set to the from address of the original message
/// - FROM set to this node address
/// - ID set to the ID of the original message
/// - FLAGS with the RH_FLAGS_ACK bit set
/// - 1 octet of payload containing ASCII '!' (since some drivers cannot handle 0 length payloads)
///
/// \par Media Access Strategy
///
/// RHReliableDatagram and the underlying drivers always transmit as soon as
/// sendtoWait() is called. RHReliableDatagram waits for an acknowledgement,
/// and if one is not received after a timeout period the message is
/// transmitted again. If no acknowledgement is received after several
/// retries, the transmissions is deemed to have failed.
/// No contention for media is detected.
/// This will be recognised as "pure ALOHA".
/// The addition of Clear Channel Assessment (CCA) is desirable and planned.
///
/// There is no message queuing or threading in RHReliableDatagram.
/// sendtoWait() waits until an acknowledgement is received, retransmitting
/// up to (by default) 3 retries time with a default 200ms timeout.
/// During this transmit-acknowledge phase, any received message (other than the expected
/// acknowledgement) will be ignored. Your sketch will be unresponsive to new messages
/// until an acknowledgement is received or the retries are exhausted.
/// Central server-type sketches should be very cautious about their
/// retransmit strategy and configuration lest they hang for a long time
/// trying to reply to clients that are unreachable.
///
/// Caution: if you have a radio network with a mixture of slow and fast
/// processors and ReliableDatagrams, you may be affected by race conditions
/// where the fast processor acknowledges a message before the sender is ready
/// to process the acknowledgement. Best practice is to use the same processors (and
/// radios) throughout your network.
///
class RHReliableDatagram : public RHDatagram
{
public:
/// Constructor.
/// \param[in] driver The RadioHead driver to use to transport messages.
/// \param[in] thisAddress The address to assign to this node. Defaults to 0
RHReliableDatagram(RHGenericDriver& driver, uint8_t thisAddress = 0);
/// Sets the minimum retransmit timeout. If sendtoWait is waiting for an ack
/// longer than this time (in milliseconds),
/// it will retransmit the message. Defaults to 200ms. The timeout is measured from the end of
/// transmission of the message. It must be at least longer than the the transmit
/// time of the acknowledgement (preamble+6 octets) plus the latency/poll time of the receiver.
/// For fast modulation schemes you can considerably shorten this time.
/// Caution: if you are using slow packet rates and long packets
/// you may need to change the timeout for reliable operations.
/// The actual timeout is randomly varied between timeout and timeout*2.
/// \param[in] timeout The new timeout period in milliseconds
void setTimeout(uint16_t timeout);
/// Sets the maximum number of retries. Defaults to 3 at construction time.
/// If set to 0, each message will only ever be sent once.
/// sendtoWait will give up and return false if there is no ack received after all transmissions time out
/// and the retries count is exhausted.
/// param[in] retries The maximum number a retries.
void setRetries(uint8_t retries);
/// Returns the currently configured maximum retries count.
/// Can be changed with setRetries().
/// \return The currently configured maximum number of retries.
uint8_t retries();
/// Send the message (with retries) and waits for an ack. Returns true if an acknowledgement is received.
/// Synchronous: any message other than the desired ACK received while waiting is discarded.
/// Blocks until an ACK is received or all retries are exhausted (ie up to retries*timeout milliseconds).
/// If the destination address is the broadcast address RH_BROADCAST_ADDRESS (255), the message will
/// be sent as a broadcast, but receiving nodes do not acknowledge, and sendtoWait() returns true immediately
/// without waiting for any acknowledgements.
/// \param[in] address The address to send the message to.
/// \param[in] buf Pointer to the binary message to send
/// \param[in] len Number of octets to send
/// \return true if the message was transmitted and an acknowledgement was received.
bool sendtoWait(uint8_t* buf, uint8_t len, uint8_t address);
/// If there is a valid message available for this node, send an acknowledgement to the SRC
/// address (blocking until this is complete), then copy the message to buf and return true
/// else return false.
/// If a message is copied, *len is set to the length..
/// If from is not NULL, the SRC address is placed in *from.
/// If to is not NULL, the DEST address is placed in *to.
/// This is the preferred function for getting messages addressed to this node.
/// If the message is not a broadcast, acknowledge to the sender before returning.
/// You should be sure to call this function frequently enough to not miss any messages
/// It is recommended that you call it in your main loop.
/// \param[in] buf Location to copy the received message
/// \param[in,out] len Available space in buf. Set to the actual number of octets copied.
/// \param[in] from If present and not NULL, the referenced uint8_t will be set to the SRC address
/// \param[in] to If present and not NULL, the referenced uint8_t will be set to the DEST address
/// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID
/// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS
/// (not just those addressed to this node).
/// \return true if a valid message was copied to buf
bool recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* from = NULL, uint8_t* to = NULL, uint8_t* id = NULL, uint8_t* flags = NULL);
/// Similar to recvfromAck(), this will block until either a valid message available for this node
/// or the timeout expires. Starts the receiver automatically.
/// You should be sure to call this function frequently enough to not miss any messages
/// It is recommended that you call it in your main loop.
/// \param[in] buf Location to copy the received message
/// \param[in,out] len Available space in buf. Set to the actual number of octets copied.
/// \param[in] timeout Maximum time to wait in milliseconds
/// \param[in] from If present and not NULL, the referenced uint8_t will be set to the SRC address
/// \param[in] to If present and not NULL, the referenced uint8_t will be set to the DEST address
/// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID
/// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS
/// (not just those addressed to this node).
/// \return true if a valid message was copied to buf
bool recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* from = NULL, uint8_t* to = NULL, uint8_t* id = NULL, uint8_t* flags = NULL);
/// Returns the number of retransmissions
/// we have had to send since starting or since the last call to resetRetransmissions().
/// \return The number of retransmissions since initialisation.
uint32_t retransmissions();
/// Resets the count of the number of retransmissions
/// to 0.
void resetRetransmissions();
protected:
/// Send an ACK for the message id to the given from address
/// Blocks until the ACK has been sent
void acknowledge(uint8_t id, uint8_t from);
/// Checks whether the message currently in the Rx buffer is a new message, not previously received
/// based on the from address and the sequence. If it is new, it is acknowledged and returns true
/// \return true if there is a message received and it is a new message
bool haveNewMessage();
private:
/// Count of retransmissions we have had to send
uint32_t _retransmissions;
/// The last sequence number to be used
/// Defaults to 0
uint8_t _lastSequenceNumber;
// Retransmit timeout (milliseconds)
/// Defaults to 200
uint16_t _timeout;
// Retries (0 means one try only)
/// Defaults to 3
uint8_t _retries;
/// Array of the last seen sequence number indexed by node address that sent it
/// It is used for duplicate detection. Duplicated messages are re-acknowledged when received
/// (this is generally due to lost ACKs, causing the sender to retransmit, even though we have already
/// received that message)
uint8_t _seenIds[256];
};
/// @example rf22_reliable_datagram_client.pde
/// @example rf22_reliable_datagram_server.pde
#endif

306
src/RHRouter.cpp Normal file
View File

@@ -0,0 +1,306 @@
// RHRouter.cpp
//
// Define addressed datagram
//
// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers
// (see http://www.hoperf.com)
// RHDatagram will be received only by the addressed node or all nodes within range if the
// to address is RH_BROADCAST_ADDRESS
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// $Id: RHRouter.cpp,v 1.7 2015/08/13 02:45:47 mikem Exp $
#include <RHRouter.h>
RHRouter::RoutedMessage RHRouter::_tmpMessage;
////////////////////////////////////////////////////////////////////
// Constructors
RHRouter::RHRouter(RHGenericDriver& driver, uint8_t thisAddress)
: RHReliableDatagram(driver, thisAddress)
{
_max_hops = RH_DEFAULT_MAX_HOPS;
clearRoutingTable();
}
////////////////////////////////////////////////////////////////////
// Public methods
bool RHRouter::init()
{
bool ret = RHReliableDatagram::init();
if (ret)
_max_hops = RH_DEFAULT_MAX_HOPS;
return ret;
}
////////////////////////////////////////////////////////////////////
void RHRouter::setMaxHops(uint8_t max_hops)
{
_max_hops = max_hops;
}
////////////////////////////////////////////////////////////////////
void RHRouter::addRouteTo(uint8_t dest, uint8_t next_hop, uint8_t state)
{
uint8_t i;
// First look for an existing entry we can update
for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++)
{
if (_routes[i].dest == dest)
{
_routes[i].dest = dest;
_routes[i].next_hop = next_hop;
_routes[i].state = state;
return;
}
}
// Look for an invalid entry we can use
for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++)
{
if (_routes[i].state == Invalid)
{
_routes[i].dest = dest;
_routes[i].next_hop = next_hop;
_routes[i].state = state;
return;
}
}
// Need to make room for a new one
retireOldestRoute();
// Should be an invalid slot now
for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++)
{
if (_routes[i].state == Invalid)
{
_routes[i].dest = dest;
_routes[i].next_hop = next_hop;
_routes[i].state = state;
}
}
}
////////////////////////////////////////////////////////////////////
RHRouter::RoutingTableEntry* RHRouter::getRouteTo(uint8_t dest)
{
uint8_t i;
for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++)
if (_routes[i].dest == dest && _routes[i].state != Invalid)
return &_routes[i];
return NULL;
}
////////////////////////////////////////////////////////////////////
void RHRouter::deleteRoute(uint8_t index)
{
// Delete a route by copying following routes on top of it
memcpy(&_routes[index], &_routes[index+1],
sizeof(RoutingTableEntry) * (RH_ROUTING_TABLE_SIZE - index - 1));
_routes[RH_ROUTING_TABLE_SIZE - 1].state = Invalid;
}
////////////////////////////////////////////////////////////////////
void RHRouter::printRoutingTable()
{
#ifdef RH_HAVE_SERIAL
uint8_t i;
for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++)
{
Serial.print(i, DEC);
Serial.print(" Dest: ");
Serial.print(_routes[i].dest, DEC);
Serial.print(" Next Hop: ");
Serial.print(_routes[i].next_hop, DEC);
Serial.print(" State: ");
Serial.println(_routes[i].state, DEC);
}
#endif
}
////////////////////////////////////////////////////////////////////
bool RHRouter::deleteRouteTo(uint8_t dest)
{
uint8_t i;
for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++)
{
if (_routes[i].dest == dest)
{
deleteRoute(i);
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////
void RHRouter::retireOldestRoute()
{
// We just obliterate the first in the table and clear the last
deleteRoute(0);
}
////////////////////////////////////////////////////////////////////
void RHRouter::clearRoutingTable()
{
uint8_t i;
for (i = 0; i < RH_ROUTING_TABLE_SIZE; i++)
_routes[i].state = Invalid;
}
uint8_t RHRouter::sendtoWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t flags)
{
return sendtoFromSourceWait(buf, len, dest, _thisAddress, flags);
}
////////////////////////////////////////////////////////////////////
// Waits for delivery to the next hop (but not for delivery to the final destination)
uint8_t RHRouter::sendtoFromSourceWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t source, uint8_t flags)
{
if (((uint16_t)len + sizeof(RoutedMessageHeader)) > _driver.maxMessageLength())
return RH_ROUTER_ERROR_INVALID_LENGTH;
// Construct a RH RouterMessage message
_tmpMessage.header.source = source;
_tmpMessage.header.dest = dest;
_tmpMessage.header.hops = 0;
_tmpMessage.header.id = _lastE2ESequenceNumber++;
_tmpMessage.header.flags = flags;
memcpy(_tmpMessage.data, buf, len);
return route(&_tmpMessage, sizeof(RoutedMessageHeader)+len);
}
////////////////////////////////////////////////////////////////////
uint8_t RHRouter::route(RoutedMessage* message, uint8_t messageLen)
{
// Reliably deliver it if possible. See if we have a route:
uint8_t next_hop = RH_BROADCAST_ADDRESS;
if (message->header.dest != RH_BROADCAST_ADDRESS)
{
RoutingTableEntry* route = getRouteTo(message->header.dest);
if (!route)
return RH_ROUTER_ERROR_NO_ROUTE;
next_hop = route->next_hop;
}
if (!RHReliableDatagram::sendtoWait((uint8_t*)message, messageLen, next_hop))
return RH_ROUTER_ERROR_UNABLE_TO_DELIVER;
return RH_ROUTER_ERROR_NONE;
}
////////////////////////////////////////////////////////////////////
// Subclasses may want to override this to peek at messages going past
void RHRouter::peekAtMessage(RoutedMessage* message, uint8_t messageLen)
{
// Default does nothing
}
////////////////////////////////////////////////////////////////////
bool RHRouter::recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source, uint8_t* dest, uint8_t* id, uint8_t* flags)
{
uint8_t tmpMessageLen = sizeof(_tmpMessage);
uint8_t _from;
uint8_t _to;
uint8_t _id;
uint8_t _flags;
if (RHReliableDatagram::recvfromAck((uint8_t*)&_tmpMessage, &tmpMessageLen, &_from, &_to, &_id, &_flags))
{
// Here we simulate networks with limited visibility between nodes
// so we can test routing
#ifdef RH_TEST_NETWORK
if (
#if RH_TEST_NETWORK==1
// This network looks like 1-2-3-4
(_thisAddress == 1 && _from == 2)
|| (_thisAddress == 2 && (_from == 1 || _from == 3))
|| (_thisAddress == 3 && (_from == 2 || _from == 4))
|| (_thisAddress == 4 && _from == 3)
#elif RH_TEST_NETWORK==2
// This network looks like 1-2-4
// | | |
// --3--
(_thisAddress == 1 && (_from == 2 || _from == 3))
|| _thisAddress == 2
|| _thisAddress == 3
|| (_thisAddress == 4 && (_from == 2 || _from == 3))
#elif RH_TEST_NETWORK==3
// This network looks like 1-2-4
// | |
// --3--
(_thisAddress == 1 && (_from == 2 || _from == 3))
|| (_thisAddress == 2 && (_from == 1 || _from == 4))
|| (_thisAddress == 3 && (_from == 1 || _from == 4))
|| (_thisAddress == 4 && (_from == 2 || _from == 3))
#elif RH_TEST_NETWORK==4
// This network looks like 1-2-3
// |
// 4
(_thisAddress == 1 && _from == 2)
|| _thisAddress == 2
|| (_thisAddress == 3 && _from == 2)
|| (_thisAddress == 4 && _from == 2)
#endif
)
{
// OK
}
else
{
return false; // Pretend we got nothing
}
#endif
peekAtMessage(&_tmpMessage, tmpMessageLen);
// See if its for us or has to be routed
if (_tmpMessage.header.dest == _thisAddress || _tmpMessage.header.dest == RH_BROADCAST_ADDRESS)
{
// Deliver it here
if (source) *source = _tmpMessage.header.source;
if (dest) *dest = _tmpMessage.header.dest;
if (id) *id = _tmpMessage.header.id;
if (flags) *flags = _tmpMessage.header.flags;
uint8_t msgLen = tmpMessageLen - sizeof(RoutedMessageHeader);
if (*len > msgLen)
*len = msgLen;
memcpy(buf, _tmpMessage.data, *len);
return true; // Its for you!
}
else if ( _tmpMessage.header.dest != RH_BROADCAST_ADDRESS
&& _tmpMessage.header.hops++ < _max_hops)
{
// Maybe it has to be routed to the next hop
// REVISIT: if it fails due to no route or unable to deliver to the next hop,
// tell the originator. BUT HOW?
route(&_tmpMessage, tmpMessageLen);
}
// Discard it and maybe wait for another
}
return false;
}
////////////////////////////////////////////////////////////////////
bool RHRouter::recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* source, uint8_t* dest, uint8_t* id, uint8_t* flags)
{
unsigned long starttime = millis();
int32_t timeLeft;
while ((timeLeft = timeout - (millis() - starttime)) > 0)
{
if (waitAvailableTimeout(timeLeft))
{
if (recvfromAck(buf, len, source, dest, id, flags))
return true;
}
YIELD;
}
return false;
}

328
src/RHRouter.h Normal file
View File

@@ -0,0 +1,328 @@
// RHRouter.h
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2011 Mike McCauley
// $Id: RHRouter.h,v 1.9 2014/08/10 20:55:17 mikem Exp $
#ifndef RHRouter_h
#define RHRouter_h
#include <RHReliableDatagram.h>
// Default max number of hops we will route
#define RH_DEFAULT_MAX_HOPS 30
// The default size of the routing table we keep
#define RH_ROUTING_TABLE_SIZE 10
// Error codes
#define RH_ROUTER_ERROR_NONE 0
#define RH_ROUTER_ERROR_INVALID_LENGTH 1
#define RH_ROUTER_ERROR_NO_ROUTE 2
#define RH_ROUTER_ERROR_TIMEOUT 3
#define RH_ROUTER_ERROR_NO_REPLY 4
#define RH_ROUTER_ERROR_UNABLE_TO_DELIVER 5
// This size of RH_ROUTER_MAX_MESSAGE_LEN is OK for Arduino Mega, but too big for
// Duemilanova. Size of 50 works with the sample router programs on Duemilanova.
#define RH_ROUTER_MAX_MESSAGE_LEN (RH_MAX_MESSAGE_LEN - sizeof(RHRouter::RoutedMessageHeader))
//#define RH_ROUTER_MAX_MESSAGE_LEN 50
// These allow us to define a simulated network topology for testing purposes
// See RHRouter.cpp for details
//#define RH_TEST_NETWORK 1
//#define RH_TEST_NETWORK 2
//#define RH_TEST_NETWORK 3
//#define RH_TEST_NETWORK 4
/////////////////////////////////////////////////////////////////////
/// \class RHRouter RHRouter.h <RHRouter.h>
/// \brief RHReliableDatagram subclass for sending addressed, optionally acknowledged datagrams
/// multi-hop routed across a network.
///
/// Manager class that extends RHReliableDatagram to define addressed messages
/// That are reliably transmitted and routed across a network. Each message is transmitted reliably
/// between each hop in order to get from the source node to the destination node.
///
/// With RHRouter, routes are hard wired. This means that each node must have programmed
/// in it how to reach each of the other nodes it will be trying to communicate with.
/// This means you must specify the next-hop node address for each of the destination nodes,
/// using the addRouteTo() function.
///
/// When sendtoWait() is called with a new message to deliver, and the destination address,
/// RHRouter looks up the next hop node for the destination node. It then uses
/// RHReliableDatagram to (reliably) deliver the message to the next hop
/// (which is expected also to be running an RHRouter). If that next-hop node is not
/// the final destination, it will also look up the next hop for the destination node and
/// (reliably) deliver the message to the next hop. By this method, messages can be delivered
/// across a network of nodes, even if each node cannot hear all of the others in the network.
/// Each time a message is received for another node and retransmitted to the next hop,
/// the HOPS filed in teh header is incremented. If a message is received for routing to another node
/// which has exceed the routers max_hops, the message wioll be dropped and ignored.
/// This helps prevent infinite routing loops.
///
/// RHRouter supports messages with a dest of RH_BROADCAST_ADDRESS. Such messages are not routed,
/// and are broadcast (once) to all nodes within range.
///
/// The recvfromAck() function is responsible not just for receiving and delivering
/// messages addressed to this node (or RH_BROADCAST_ADDRESS), but
/// it is also responsible for routing other message to their next hop. This means that it is important to
/// call recvfromAck() or recvfromAckTimeout() frequently in your main loop. recvfromAck() will return
/// false if it receives a message but it is not for this node.
///
/// RHRouter does not provide reliable end-to-end delivery, but uses reliable hop-to-hop delivery.
/// If a message is unable to be delivered to an end node during to a delivery failure between 2 hops,
/// the source node will not be told about it.
///
/// Note: This class is most useful for networks of nodes that are essentially static
/// (i.e. the nodes dont move around), and for which the
/// routing never changes. If that is not the case for your proposed network, see RHMesh instead.
///
/// \par The Routing Table
///
/// The routing table is a local table in RHRouter that holds the information about the next hop node
/// address for each destination address you may want to send a message to. It is your responsibility
/// to make sure every node in an RHRouter network has been configured with a unique address and the
/// routing information so that messages are correctly routed across the network from source node to
/// destination node. This is usually done once in setup() by calling addRouteTo().
/// The hardwired routing will in general be different on each node, and will depend on the physical
/// topololgy of the network.
/// You can also use addRouteTo() to change a route and
/// deleteRouteTo() to delete a route at run time. Youcan also clear the entire routing table
///
/// The Routing Table has limited capacity for entries (defined by RH_ROUTING_TABLE_SIZE, which is 10)
/// if more than RH_ROUTING_TABLE_SIZE are added, the oldest (first) one will be removed by calling
/// retireOldestRoute()
///
/// \par Message Format
///
/// RHRouter add to the lower level RHReliableDatagram (and even lower level RH) class message formats.
/// In those lower level classes, the hop-to-hop message headers are in the RH message headers,
/// and are handled automcatically by tyhe RH hardware.
/// RHRouter and its subclasses add an end-to-end addressing header in the payload of the RH message,
/// and before the RHRouter application data.
/// - 1 octet DEST, the destination node address (ie the address of the final
/// destination node for this message)
/// - 1 octet SOURCE, the source node address (ie the address of the originating node that first sent
/// the message).
/// - 1 octet HOPS, the number of hops this message has traversed so far.
/// - 1 octet ID, an incrementing message ID for end-to-end message tracking for use by subclasses.
/// Not used by RHRouter.
/// - 1 octet FLAGS, a bitmask for use by subclasses. Not used by RHRouter.
/// - 0 or more octets DATA, the application payload data. The length of this data is implicit
/// in the length of the entire message.
///
/// You should be careful to note that there are ID and FLAGS fields in the low level per-hop
/// message header too. These are used only for hop-to-hop, and in general will be different to
/// the ones at the RHRouter level.
///
/// \par Testing
///
/// Bench testing of such networks is notoriously difficult, especially simulating limited radio
/// connectivity between some nodes.
/// To assist testing (both during RH development and for your own networks)
/// RHRouter.cpp has the ability to
/// simulate a number of different small network topologies. Each simulated network supports 4 nodes with
/// addresses 1 to 4. It operates by pretending to not hear RH messages from certain other nodes.
/// You can enable testing with a \#define TEST_NETWORK in RHRouter.h
/// The sample programs rf22_mesh_* rely on this feature.
///
/// Part of the Arduino RH library for operating with HopeRF RH compatible transceivers
/// (see http://www.hoperf.com)
class RHRouter : public RHReliableDatagram
{
public:
/// Defines the structure of the RHRouter message header, used to keep track of end-to-end delivery parameters
typedef struct
{
uint8_t dest; ///< Destination node address
uint8_t source; ///< Originator node address
uint8_t hops; ///< Hops traversed so far
uint8_t id; ///< Originator sequence number
uint8_t flags; ///< Originator flags
// Data follows, Length is implicit in the overall message length
} RoutedMessageHeader;
/// Defines the structure of a RHRouter message
typedef struct
{
RoutedMessageHeader header; ///< end-to-end delivery header
uint8_t data[RH_ROUTER_MAX_MESSAGE_LEN]; ///< Application payload data
} RoutedMessage;
/// Values for the possible states for routes
typedef enum
{
Invalid = 0, ///< No valid route is known
Discovering, ///< Discovering a route (not currently used)
Valid ///< Route is valid
} RouteState;
/// Defines an entry in the routing table
typedef struct
{
uint8_t dest; ///< Destination node address
uint8_t next_hop; ///< Send via this next hop address
uint8_t state; ///< State of this route, one of RouteState
} RoutingTableEntry;
/// Constructor.
/// \param[in] driver The RadioHead driver to use to transport messages.
/// \param[in] thisAddress The address to assign to this node. Defaults to 0
RHRouter(RHGenericDriver& driver, uint8_t thisAddress = 0);
/// Initialises this instance and the radio module connected to it.
/// Overrides the init() function in RH.
/// Sets max_hops to the default of RH_DEFAULT_MAX_HOPS (30)
bool init();
/// Sets the max_hops to the given value
/// This controls the maximum number of hops allowed between source and destination nodes
/// Messages that are not delivered by the time their HOPS field exceeds max_hops on a
/// routing node will be dropped and ignored.
/// \param [in] max_hops The new value for max_hops
void setMaxHops(uint8_t max_hops);
/// Adds a route to the local routing table, or updates it if already present.
/// If there is not enough room the oldest (first) route will be deleted by calling retireOldestRoute().
/// \param [in] dest The destination node address. RH_BROADCAST_ADDRESS is permitted.
/// \param [in] next_hop The address of the next hop to send messages destined for dest
/// \param [in] state The satte of the route. Defaults to Valid
void addRouteTo(uint8_t dest, uint8_t next_hop, uint8_t state = Valid);
/// Finds and returns a RoutingTableEntry for the given destination node
/// \param [in] dest The desired destination node address.
/// \return pointer to a RoutingTableEntry for dest
RoutingTableEntry* getRouteTo(uint8_t dest);
/// Deletes from the local routing table any route for the destination node.
/// \param [in] dest The destination node address
/// \return true if the route was present
bool deleteRouteTo(uint8_t dest);
/// Deletes the oldest (first) route from the
/// local routing table
void retireOldestRoute();
/// Clears all entries from the
/// local routing table
void clearRoutingTable();
/// If RH_HAVE_SERIAL is defined, this will print out the contents of the local
/// routing table using Serial
void printRoutingTable();
/// Sends a message to the destination node. Initialises the RHRouter message header
/// (the SOURCE address is set to the address of this node, HOPS to 0) and calls
/// route() which looks up in the routing table the next hop to deliver to and sends the
/// message to the next hop. Waits for an acknowledgement from the next hop
/// (but not from the destination node (if that is different).
/// \param [in] buf The application message data
/// \param [in] len Number of octets in the application message data. 0 is permitted
/// \param [in] dest The destination node address
/// \param [in] flags Optional flags for use by subclasses or application layer,
/// delivered end-to-end to the dest address. The receiver can recover the flags with recvFromAck().
/// \return The result code:
/// - RH_ROUTER_ERROR_NONE Message was routed and delivered to the next hop
/// (not necessarily to the final dest address)
/// - RH_ROUTER_ERROR_NO_ROUTE There was no route for dest in the local routing table
/// - RH_ROUTER_ERROR_UNABLE_TO_DELIVER Not able to deliver to the next hop
/// (usually because it dod not acknowledge due to being off the air or out of range
uint8_t sendtoWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t flags = 0);
/// Similar to sendtoWait() above, but spoofs the source address.
/// For internal use only during routing
/// \param [in] buf The application message data.
/// \param [in] len Number of octets in the application message data. 0 is permitted.
/// \param [in] dest The destination node address.
/// \param [in] source The (fake) originating node address.
/// \param [in] flags Optional flags for use by subclasses or application layer,
/// delivered end-to-end to the dest address. The receiver can recover the flags with recvFromAck().
/// \return The result code:
/// - RH_ROUTER_ERROR_NONE Message was routed and deliverd to the next hop
/// (not necessarily to the final dest address)
/// - RH_ROUTER_ERROR_NO_ROUTE There was no route for dest in the local routing table
/// - RH_ROUTER_ERROR_UNABLE_TO_DELIVER Noyt able to deliver to the next hop
/// (usually because it dod not acknowledge due to being off the air or out of range
uint8_t sendtoFromSourceWait(uint8_t* buf, uint8_t len, uint8_t dest, uint8_t source, uint8_t flags = 0);
/// Starts the receiver if it is not running already.
/// If there is a valid message available for this node (or RH_BROADCAST_ADDRESS),
/// send an acknowledgement to the last hop
/// address (blocking until this is complete), then copy the application message payload data
/// to buf and return true
/// else return false.
/// If a message is copied, *len is set to the length..
/// If from is not NULL, the originator SOURCE address is placed in *source.
/// If to is not NULL, the DEST address is placed in *dest. This might be this nodes address or
/// RH_BROADCAST_ADDRESS.
/// This is the preferred function for getting messages addressed to this node.
/// If the message is not a broadcast, acknowledge to the sender before returning.
/// \param[in] buf Location to copy the received message
/// \param[in,out] len Available space in buf. Set to the actual number of octets copied.
/// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address
/// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address
/// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID
/// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS
/// (not just those addressed to this node).
/// \return true if a valid message was recvived for this node copied to buf
bool recvfromAck(uint8_t* buf, uint8_t* len, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL);
/// Starts the receiver if it is not running already.
/// Similar to recvfromAck(), this will block until either a valid message available for this node
/// or the timeout expires.
/// \param[in] buf Location to copy the received message
/// \param[in,out] len Available space in buf. Set to the actual number of octets copied.
/// \param[in] timeout Maximum time to wait in milliseconds
/// \param[in] source If present and not NULL, the referenced uint8_t will be set to the SOURCE address
/// \param[in] dest If present and not NULL, the referenced uint8_t will be set to the DEST address
/// \param[in] id If present and not NULL, the referenced uint8_t will be set to the ID
/// \param[in] flags If present and not NULL, the referenced uint8_t will be set to the FLAGS
/// (not just those addressed to this node).
/// \return true if a valid message was copied to buf
bool recvfromAckTimeout(uint8_t* buf, uint8_t* len, uint16_t timeout, uint8_t* source = NULL, uint8_t* dest = NULL, uint8_t* id = NULL, uint8_t* flags = NULL);
protected:
/// Lets sublasses peek at messages going
/// past before routing or local delivery.
/// Called by recvfromAck() immediately after it gets the message from RHReliableDatagram
/// \param [in] message Pointer to the RHRouter message that was received.
/// \param [in] messageLen Length of message in octets
virtual void peekAtMessage(RoutedMessage* message, uint8_t messageLen);
/// Finds the next-hop route and sends the message via RHReliableDatagram::sendtoWait().
/// This is virtual, which lets subclasses override or intercept the route() function.
/// Called by sendtoWait after the message header has been filled in.
/// \param [in] message Pointer to the RHRouter message to be sent.
/// \param [in] messageLen Length of message in octets
virtual uint8_t route(RoutedMessage* message, uint8_t messageLen);
/// Deletes a specific rout entry from therouting table
/// \param [in] index The 0 based index of the routing table entry to delete
void deleteRoute(uint8_t index);
/// The last end-to-end sequence number to be used
/// Defaults to 0
uint8_t _lastE2ESequenceNumber;
/// The maximum number of hops permitted in routed messages.
/// If a routed message would exceed this number of hops it is dropped and ignored.
uint8_t _max_hops;
private:
/// Temporary mesage buffer
static RoutedMessage _tmpMessage;
/// Local routing table
RoutingTableEntry _routes[RH_ROUTING_TABLE_SIZE];
};
/// @example rf22_router_client.pde
/// @example rf22_router_server1.pde
/// @example rf22_router_server2.pde
/// @example rf22_router_server3.pde
#endif

91
src/RHSPIDriver.cpp Normal file
View File

@@ -0,0 +1,91 @@
// RHSPIDriver.cpp
//
// Copyright (C) 2014 Mike McCauley
// $Id: RHSPIDriver.cpp,v 1.10 2015/12/16 04:55:33 mikem Exp $
#include <RHSPIDriver.h>
RHSPIDriver::RHSPIDriver(uint8_t slaveSelectPin, RHGenericSPI& spi)
:
_spi(spi),
_slaveSelectPin(slaveSelectPin)
{
}
bool RHSPIDriver::init()
{
// start the SPI library with the default speeds etc:
// On Arduino Due this defaults to SPI1 on the central group of 6 SPI pins
_spi.begin();
// Initialise the slave select pin
// On Maple, this must be _after_ spi.begin
pinMode(_slaveSelectPin, OUTPUT);
digitalWrite(_slaveSelectPin, HIGH);
delay(100);
return true;
}
uint8_t RHSPIDriver::spiRead(uint8_t reg)
{
uint8_t val;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
_spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the address with the write mask off
val = _spi.transfer(0); // The written value is ignored, reg value is read
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return val;
}
uint8_t RHSPIDriver::spiWrite(uint8_t reg, uint8_t val)
{
uint8_t status = 0;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the address with the write mask on
_spi.transfer(val); // New value follows
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return status;
}
uint8_t RHSPIDriver::spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len)
{
uint8_t status = 0;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
status = _spi.transfer(reg & ~RH_SPI_WRITE_MASK); // Send the start address with the write mask off
while (len--)
*dest++ = _spi.transfer(0);
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return status;
}
uint8_t RHSPIDriver::spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len)
{
uint8_t status = 0;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
status = _spi.transfer(reg | RH_SPI_WRITE_MASK); // Send the start address with the write mask on
while (len--)
_spi.transfer(*src++);
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return status;
}
void RHSPIDriver::setSlaveSelectPin(uint8_t slaveSelectPin)
{
_slaveSelectPin = slaveSelectPin;
}

94
src/RHSPIDriver.h Normal file
View File

@@ -0,0 +1,94 @@
// RHSPIDriver.h
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2014 Mike McCauley
// $Id: RHSPIDriver.h,v 1.10 2015/12/16 04:55:33 mikem Exp $
#ifndef RHSPIDriver_h
#define RHSPIDriver_h
#include <RHGenericDriver.h>
#include <RHHardwareSPI.h>
// This is the bit in the SPI address that marks it as a write
#define RH_SPI_WRITE_MASK 0x80
class RHGenericSPI;
/////////////////////////////////////////////////////////////////////
/// \class RHSPIDriver RHSPIDriver.h <RHSPIDriver.h>
/// \brief Base class for a RadioHead drivers that use the SPI bus
/// to communicate with its transport hardware.
///
/// This class can be subclassed by Drivers that require to use the SPI bus.
/// It can be configured to use either the RHHardwareSPI class (if there is one available on the platform)
/// of the bitbanged RHSoftwareSPI class. The default behaviour is to use a pre-instantiated built-in RHHardwareSPI
/// interface.
///
/// SPI bus access is protected by ATOMIC_BLOCK_START and ATOMIC_BLOCK_END, which will ensure interrupts
/// are disabled during access.
///
/// The read and write routines implement commonly used SPI conventions: specifically that the MSB
/// of the first byte transmitted indicates that it is a write and the remaining bits indicate the rehgister to access)
/// This can be overriden
/// in subclasses if necessaryor an alternative class, RHNRFSPIDriver can be used to access devices like
/// Nordic NRF series radios, which have different requirements.
///
/// Application developers are not expected to instantiate this class directly:
/// it is for the use of Driver developers.
class RHSPIDriver : public RHGenericDriver
{
public:
/// Constructor
/// \param[in] slaveSelectPin The controler pin to use to select the desired SPI device. This pin will be driven LOW
/// during SPI communications with the SPI device that uis iused by this Driver.
/// \param[in] spi Reference to the SPI interface to use. The default is to use a default built-in Hardware interface.
RHSPIDriver(uint8_t slaveSelectPin = SS, RHGenericSPI& spi = hardware_spi);
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// \return true if initialisation succeeded.
bool init();
/// Reads a single register from the SPI device
/// \param[in] reg Register number
/// \return The value of the register
uint8_t spiRead(uint8_t reg);
/// Writes a single byte to the SPI device
/// \param[in] reg Register number
/// \param[in] val The value to write
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
/// it may or may not be meaningfule depending on the the type of device being accessed.
uint8_t spiWrite(uint8_t reg, uint8_t val);
/// Reads a number of consecutive registers from the SPI device using burst read mode
/// \param[in] reg Register number of the first register
/// \param[in] dest Array to write the register values to. Must be at least len bytes
/// \param[in] len Number of bytes to read
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
/// it may or may not be meaningfule depending on the the type of device being accessed.
uint8_t spiBurstRead(uint8_t reg, uint8_t* dest, uint8_t len);
/// Write a number of consecutive registers using burst write mode
/// \param[in] reg Register number of the first register
/// \param[in] src Array of new register values to write. Must be at least len bytes
/// \param[in] len Number of bytes to write
/// \return Some devices return a status byte during the first data transfer. This byte is returned.
/// it may or may not be meaningfule depending on the the type of device being accessed.
uint8_t spiBurstWrite(uint8_t reg, const uint8_t* src, uint8_t len);
/// Set or change the pin to be used for SPI slave select.
/// This can be called at any time to change the
/// pin that will be used for slave select in subsquent SPI operations.
/// \param[in] slaveSelectPin The pin to use
void setSlaveSelectPin(uint8_t slaveSelectPin);
protected:
/// Reference to the RHGenericSPI instance to use to transfer data with teh SPI device
RHGenericSPI& _spi;
/// The pin number of the Slave Select pin that is used to select the desired device.
uint8_t _slaveSelectPin;
};
#endif

166
src/RHSoftwareSPI.cpp Normal file
View 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");
}
}

89
src/RHSoftwareSPI.h Normal file
View File

@@ -0,0 +1,89 @@
// SoftwareSPI.h
// Author: Chris Lapa (chris@lapa.com.au)
// Copyright (C) 2014 Chris Lapa
// Contributed by Chris Lapa
#ifndef RHSoftwareSPI_h
#define RHSoftwareSPI_h
#include <RHGenericSPI.h>
/////////////////////////////////////////////////////////////////////
/// \class RHSoftwareSPI RHSoftwareSPI.h <RHSoftwareSPI.h>
/// \brief Encapsulate a software SPI interface
///
/// This concrete subclass of RHGenericSPI enapsulates a bit-banged software SPI interface.
/// Caution: this software SPI interface will be much slower than hardware SPI on most
/// platforms.
///
/// \par Usage
///
/// Usage varies slightly depending on what driver you are using.
///
/// For RF22, for example:
/// \code
/// #include <RHSoftwareSPI.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 _bitOrder;
uint8_t _delayCounts;
uint8_t _clockPolarity;
uint8_t _clockPhase;
};
#endif

66
src/RHTcpProtocol.h Normal file
View File

@@ -0,0 +1,66 @@
// RH_TcpProtocol.h
// Author: Mike McCauley (mikem@aierspayce.com)
// Definition of protocol messages sent and received by RH_TCP
// Copyright (C) 2014 Mike McCauley
// $Id: RHTcpProtocol.h,v 1.3 2014/05/22 06:07:09 mikem Exp $
/// This file contains the definitions of message structures passed between
/// RH_TCP and the etherSimulator
#ifndef RH_TcpProtocol_h
#define RH_TcpProtocol_h
#define RH_TCP_MESSAGE_TYPE_NOP 0
#define RH_TCP_MESSAGE_TYPE_THISADDRESS 1
#define RH_TCP_MESSAGE_TYPE_PACKET 2
// Maximum message length (including the headers) we are willing to support
#define RH_TCP_MAX_PAYLOAD_LEN 255
// The length of the headers we add.
// The headers are inside the RF69's payload and are therefore encrypted if encryption is enabled
#define RH_TCP_HEADER_LEN 4
// This is the maximum message length that can be supported by this protocol.
#define RH_TCP_MAX_MESSAGE_LEN (RH_TCP_MAX_PAYLOAD_LEN - RH_TCP_HEADER_LEN)
#pragma pack(push, 1) // No padding
/// \brief Generic RH_TCP simulator message structure
typedef struct
{
uint32_t length; ///< Number of octets following, in network byte order
uint8_t payload[RH_TCP_MAX_PAYLOAD_LEN + 1]; ///< Payload
} RHTcpMessage;
/// \brief Generic RH_TCP message structure with message type
typedef struct
{
uint32_t length; ///< Number of octets following, in network byte order
uint8_t type; ///< One of RH_TCP_MESSAGE_TYPE_*
uint8_t payload[RH_TCP_MAX_PAYLOAD_LEN]; ///< Payload
} RHTcpTypeMessage;
/// \brief RH_TCP message Notifies the server of thisAddress of this client
typedef struct
{
uint32_t length; ///< Number of octets following, in network byte order
uint8_t type; ///< == RH_TCP_MESSAGE_TYPE_THISADDRESS
uint8_t thisAddress; ///< Node address
} RHTcpThisAddress;
/// \brief RH_TCP radio message passed to or from the simulator
typedef struct
{
uint32_t length; ///< Number of octets following, in network byte order
uint8_t type; ///< == RH_TCP_MESSAGE_TYPE_PACKET
uint8_t to; ///< Node address of the recipient
uint8_t from; ///< Node address of the sender
uint8_t id; ///< Message sequence number
uint8_t flags; ///< Message flags
uint8_t payload[RH_TCP_MAX_MESSAGE_LEN]; ///< 0 or more, length deduced from length above
} RHTcpPacket;
#pragma pack(pop)
#endif

845
src/RH_ASK.cpp Normal file
View File

@@ -0,0 +1,845 @@
// RH_ASK.cpp
//
// Copyright (C) 2014 Mike McCauley
// $Id: RH_ASK.cpp,v 1.18 2016/07/07 00:02:53 mikem Exp mikem $
#include <RH_ASK.h>
#include <RHCRC.h>
#if (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc
HardwareTimer timer(MAPLE_TIMER);
#endif
#if (RH_PLATFORM == RH_PLATFORM_ESP8266)
// interrupt handler and related code must be in RAM on ESP8266,
// according to issue #46.
#define INTERRUPT_ATTR ICACHE_RAM_ATTR
#else
#define INTERRUPT_ATTR
#endif
// RH_ASK on Arduino uses Timer 1 to generate interrupts 8 times per bit interval
// Define RH_ASK_ARDUINO_USE_TIMER2 if you want to use Timer 2 instead of Timer 1 on Arduino
// You may need this to work around other librraies that insist on using timer 1
// Should be moved to header file
//#define RH_ASK_ARDUINO_USE_TIMER2
// Interrupt handler uses this to find the most recently initialised instance of this driver
static RH_ASK* thisASKDriver;
// 4 bit to 6 bit symbol converter table
// Used to convert the high and low nybbles of the transmitted data
// into 6 bit symbols for transmission. Each 6-bit symbol has 3 1s and 3 0s
// with at most 3 consecutive identical bits
static uint8_t symbols[] =
{
0xd, 0xe, 0x13, 0x15, 0x16, 0x19, 0x1a, 0x1c,
0x23, 0x25, 0x26, 0x29, 0x2a, 0x2c, 0x32, 0x34
};
// This is the value of the start symbol after 6-bit conversion and nybble swapping
#define RH_ASK_START_SYMBOL 0xb38
RH_ASK::RH_ASK(uint16_t speed, uint8_t rxPin, uint8_t txPin, uint8_t pttPin, bool pttInverted)
:
_speed(speed),
_rxPin(rxPin),
_txPin(txPin),
_pttPin(pttPin),
_pttInverted(pttInverted)
{
// Initialise the first 8 nibbles of the tx buffer to be the standard
// preamble. We will append messages after that. 0x38, 0x2c is the start symbol before
// 6-bit conversion to RH_ASK_START_SYMBOL
uint8_t preamble[RH_ASK_PREAMBLE_LEN] = {0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x38, 0x2c};
memcpy(_txBuf, preamble, sizeof(preamble));
}
bool RH_ASK::init()
{
if (!RHGenericDriver::init())
return false;
thisASKDriver = this;
#if (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8)
#ifdef RH_ASK_PTT_PIN
RH_ASK_PTT_DDR |= (1<<RH_ASK_PTT_PIN);
RH_ASK_TX_DDR |= (1<<RH_ASK_TX_PIN);
RH_ASK_RX_DDR &= ~(1<<RH_ASK_RX_PIN);
#else
RH_ASK_TX_DDR |= (1<<RH_ASK_TX_PIN);
RH_ASK_RX_DDR &= ~(1<<RH_ASK_RX_PIN);
#endif
#else
// Set up digital IO pins for arduino
pinMode(_txPin, OUTPUT);
pinMode(_rxPin, INPUT);
pinMode(_pttPin, OUTPUT);
#endif
// Ready to go
setModeIdle();
timerSetup();
return true;
}
// Put these prescaler structs in PROGMEM, not on the stack
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8)
#if defined(RH_ASK_ARDUINO_USE_TIMER2)
// Timer 2 has different prescalers
PROGMEM static const uint16_t prescalers[] = {0, 1, 8, 32, 64, 128, 256, 3333};
#else
PROGMEM static const uint16_t prescalers[] = {0, 1, 8, 64, 256, 1024, 3333};
#endif
#define NUM_PRESCALERS (sizeof(prescalers) / sizeof( uint16_t))
#endif
// Common function for setting timer ticks @ prescaler values for speed
// Returns prescaler index into {0, 1, 8, 64, 256, 1024} array
// and sets nticks to compare-match value if lower than max_ticks
// returns 0 & nticks = 0 on fault
uint8_t RH_ASK::timerCalc(uint16_t speed, uint16_t max_ticks, uint16_t *nticks)
{
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8)
// Clock divider (prescaler) values - 0/3333: error flag
uint8_t prescaler; // index into array & return bit value
unsigned long ulticks; // calculate by ntick overflow
// Div-by-zero protection
if (speed == 0)
{
// signal fault
*nticks = 0;
return 0;
}
// test increasing prescaler (divisor), decreasing ulticks until no overflow
// 1/Fraction of second needed to xmit one bit
unsigned long inv_bit_time = ((unsigned long)speed) * 8;
for (prescaler=1; prescaler < NUM_PRESCALERS; prescaler += 1)
{
// Integer arithmetic courtesy Jim Remington
// 1/Amount of time per CPU clock tick (in seconds)
uint16_t prescalerValue;
memcpy_P(&prescalerValue, &prescalers[prescaler], sizeof(uint16_t));
unsigned long inv_clock_time = F_CPU / ((unsigned long)prescalerValue);
// number of prescaled ticks needed to handle bit time @ speed
ulticks = inv_clock_time / inv_bit_time;
// Test if ulticks fits in nticks bitwidth (with 1-tick safety margin)
if ((ulticks > 1) && (ulticks < max_ticks))
break; // found prescaler
// Won't fit, check with next prescaler value
}
// Check for error
if ((prescaler == 6) || (ulticks < 2) || (ulticks > max_ticks))
{
// signal fault
*nticks = 0;
return 0;
}
*nticks = ulticks;
return prescaler;
#else
return 0; // not implemented or needed on other platforms
#endif
}
// The idea here is to get 8 timer interrupts per bit period
void RH_ASK::timerSetup()
{
#if (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8)
uint16_t nticks;
uint8_t prescaler = timerCalc(_speed, (uint16_t)-1, &nticks);
if (!prescaler) return;
_COMB(TCCR,RH_ASK_TIMER_INDEX,A)= 0;
_COMB(TCCR,RH_ASK_TIMER_INDEX,B)= _BV(WGM12);
_COMB(TCCR,RH_ASK_TIMER_INDEX,B)|= prescaler;
_COMB(OCR,RH_ASK_TIMER_INDEX,A)= nticks;
_COMB(TI,MSK,RH_ASK_TIMER_INDEX)|= _BV(_COMB(OCIE,RH_ASK_TIMER_INDEX,A));
#elif (RH_PLATFORM == RH_PLATFORM_MSP430) // LaunchPad specific
// Calculate the counter overflow count based on the required bit speed
// and CPU clock rate
uint16_t ocr1a = (F_CPU / 8UL) / _speed;
// This code is for Energia/MSP430
TA0CCR0 = ocr1a; // Ticks for 62,5 us
TA0CTL = TASSEL_2 + MC_1; // SMCLK, up mode
TA0CCTL0 |= CCIE; // CCR0 interrupt enabled
#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) // Arduino specific
#if !(defined(__arm__) && defined(CORE_TEENSY) )
uint16_t nticks; // number of prescaled ticks needed
uint8_t prescaler; // Bit values for CS0[2:0]
#endif
#ifdef RH_PLATFORM_ATTINY
// figure out prescaler value and counter match value
// REVISIT: does not correctly handle 1MHz clock speeds, only works with 8MHz clocks
// At 1MHz clock, get 1/8 of the expected baud rate
prescaler = timerCalc(_speed, (uint8_t)-1, &nticks);
if (!prescaler)
return; // fault
TCCR0A = 0;
TCCR0A = _BV(WGM01); // Turn on CTC mode / Output Compare pins disconnected
// convert prescaler index to TCCRnB prescaler bits CS00, CS01, CS02
TCCR0B = 0;
TCCR0B = prescaler; // set CS00, CS01, CS02 (other bits not needed)
// Number of ticks to count before firing interrupt
OCR0A = uint8_t(nticks);
// Set mask to fire interrupt when OCF0A bit is set in TIFR0
#ifdef TIMSK0
// ATtiny84
TIMSK0 |= _BV(OCIE0A);
#else
// ATtiny85
TIMSK |= _BV(OCIE0A);
#endif
#elif defined(__arm__) && defined(CORE_TEENSY)
// on Teensy 3.0 (32 bit ARM), use an interval timer
IntervalTimer *t = new IntervalTimer();
void TIMER1_COMPA_vect(void);
t->begin(TIMER1_COMPA_vect, 125000 / _speed);
#elif defined (__arm__) && defined(ARDUINO_ARCH_SAMD)
// Arduino Zero
#define RH_ASK_ZERO_TIMER TC3
// Clock speed is 48MHz, prescaler of 64 gives a good range of available speeds vs precision
#define RH_ASK_ZERO_PRESCALER 64
#define RH_ASK_ZERO_TIMER_IRQ TC3_IRQn
// Enable clock for TC
REG_GCLK_CLKCTRL = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TCC2_TC3)) ;
while ( GCLK->STATUS.bit.SYNCBUSY == 1 ); // wait for sync
// The type cast must fit with the selected timer mode
TcCount16* TC = (TcCount16*)RH_ASK_ZERO_TIMER; // get timer struct
TC->CTRLA.reg &= ~TC_CTRLA_ENABLE; // Disable TC
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_MODE_COUNT16; // Set Timer counter Mode to 16 bits
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ; // Set TC as Match Frequency
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
// Compute the count required to achieve the requested baud (with 8 interrupts per bit)
uint32_t rc = (VARIANT_MCK / _speed) / RH_ASK_ZERO_PRESCALER / 8;
TC->CTRLA.reg |= TC_CTRLA_PRESCALER_DIV64; // Set prescaler to agree with RH_ASK_ZERO_PRESCALER
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
TC->CC[0].reg = rc; // FIXME
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
// Interrupts
TC->INTENSET.reg = 0; // disable all interrupts
TC->INTENSET.bit.MC0 = 1; // enable compare match to CC0
// Enable InterruptVector
NVIC_ClearPendingIRQ(RH_ASK_ZERO_TIMER_IRQ);
NVIC_EnableIRQ(RH_ASK_ZERO_TIMER_IRQ);
// Enable TC
TC->CTRLA.reg |= TC_CTRLA_ENABLE;
while (TC->STATUS.bit.SYNCBUSY == 1); // wait for sync
#elif defined(__arm__) && defined(ARDUINO_SAM_DUE)
// Arduino Due
// Clock speed is 84MHz
// Due has 9 timers in 3 blocks of 3.
// We use timer 1 TC1_IRQn on TC0 channel 1, since timers 0, 2, 3, 4, 5 are used by the Servo library
#define RH_ASK_DUE_TIMER TC0
#define RH_ASK_DUE_TIMER_CHANNEL 1
#define RH_ASK_DUE_TIMER_IRQ TC1_IRQn
pmc_set_writeprotect(false);
pmc_enable_periph_clk(RH_ASK_DUE_TIMER_IRQ);
// Clock speed 4 can handle all reasonable _speeds we might ask for. Its divisor is 128
// and we want 8 interrupts per bit
uint32_t rc = (VARIANT_MCK / _speed) / 128 / 8;
TC_Configure(RH_ASK_DUE_TIMER, RH_ASK_DUE_TIMER_CHANNEL,
TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK4);
TC_SetRC(RH_ASK_DUE_TIMER, RH_ASK_DUE_TIMER_CHANNEL, rc);
// Enable the RC Compare Interrupt
RH_ASK_DUE_TIMER->TC_CHANNEL[RH_ASK_DUE_TIMER_CHANNEL].TC_IER = TC_IER_CPCS;
NVIC_ClearPendingIRQ(RH_ASK_DUE_TIMER_IRQ);
NVIC_EnableIRQ(RH_ASK_DUE_TIMER_IRQ);
TC_Start(RH_ASK_DUE_TIMER, RH_ASK_DUE_TIMER_CHANNEL);
#else
// This is the path for most Arduinos
// figure out prescaler value and counter match value
#if defined(RH_ASK_ARDUINO_USE_TIMER2)
prescaler = timerCalc(_speed, (uint8_t)-1, &nticks);
if (!prescaler)
return; // fault
// Use timer 2
TCCR2A = _BV(WGM21); // Turn on CTC mode)
// convert prescaler index to TCCRnB prescaler bits CS10, CS11, CS12
TCCR2B = prescaler;
// Caution: special procedures for setting 16 bit regs
// is handled by the compiler
OCR2A = nticks;
// Enable interrupt
#ifdef TIMSK2
// atmega168
TIMSK2 |= _BV(OCIE2A);
#else
// others
TIMSK |= _BV(OCIE2A);
#endif // TIMSK2
#else
// Use timer 1
prescaler = timerCalc(_speed, (uint16_t)-1, &nticks);
if (!prescaler)
return; // fault
TCCR1A = 0; // Output Compare pins disconnected
TCCR1B = _BV(WGM12); // Turn on CTC mode
// convert prescaler index to TCCRnB prescaler bits CS10, CS11, CS12
TCCR1B |= prescaler;
// Caution: special procedures for setting 16 bit regs
// is handled by the compiler
OCR1A = nticks;
// Enable interrupt
#ifdef TIMSK1
// atmega168
TIMSK1 |= _BV(OCIE1A);
#else
// others
TIMSK |= _BV(OCIE1A);
#endif // TIMSK1
#endif
#endif
#elif (RH_PLATFORM == RH_PLATFORM_STM32) // Maple etc
// Pause the timer while we're configuring it
timer.pause();
timer.setPeriod((1000000/8)/_speed);
// Set up an interrupt on channel 1
timer.setChannel1Mode(TIMER_OUTPUT_COMPARE);
timer.setCompare(TIMER_CH1, 1); // Interrupt 1 count after each update
void interrupt(); // defined below
timer.attachCompare1Interrupt(interrupt);
// Refresh the timer's count, prescale, and overflow
timer.refresh();
// Start the timer counting
timer.resume();
#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Photon
// Inspired by SparkIntervalTimer
// We use Timer 6
void TimerInterruptHandler(); // Forward declaration for interrupt handler
#define SYSCORECLOCK 60000000UL // Timer clock tree uses core clock / 2
TIM_TimeBaseInitTypeDef timerInitStructure;
NVIC_InitTypeDef nvicStructure;
TIM_TypeDef* TIMx;
uint32_t period = (1000000 / 8) / _speed; // In microseconds
uint16_t prescaler = (uint16_t)(SYSCORECLOCK / 1000000UL) - 1; //To get TIM counter clock = 1MHz
attachSystemInterrupt(SysInterrupt_TIM6_Update, TimerInterruptHandler);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
nvicStructure.NVIC_IRQChannel = TIM6_DAC_IRQn;
TIMx = TIM6;
nvicStructure.NVIC_IRQChannelPreemptionPriority = 10;
nvicStructure.NVIC_IRQChannelSubPriority = 1;
nvicStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvicStructure);
timerInitStructure.TIM_Prescaler = prescaler;
timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
timerInitStructure.TIM_Period = period;
timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
timerInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIMx, &timerInitStructure);
TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE);
TIM_Cmd(TIMx, ENABLE);
#elif (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE)
// UsingChipKIT Core on Arduino IDE
uint32_t chipkit_timer_interrupt_handler(uint32_t currentTime); // Forward declaration
attachCoreTimerService(chipkit_timer_interrupt_handler);
#elif (RH_PLATFORM == RH_PLATFORM_UNO32)
// Under old MPIDE, which has been discontinued:
// ON Uno32 we use timer1
OpenTimer1(T1_ON | T1_PS_1_1 | T1_SOURCE_INT, (F_CPU / 8) / _speed);
ConfigIntTimer1(T1_INT_ON | T1_INT_PRIOR_1);
#elif (RH_PLATFORM == RH_PLATFORM_ESP8266)
void INTERRUPT_ATTR esp8266_timer_interrupt_handler(); // Forward declarat
// The - 120 is a heuristic to correct for interrupt handling overheads
_timerIncrement = (clockCyclesPerMicrosecond() * 1000000 / 8 / _speed) - 120;
timer0_isr_init();
timer0_attachInterrupt(esp8266_timer_interrupt_handler);
timer0_write(ESP.getCycleCount() + _timerIncrement);
// timer0_write(ESP.getCycleCount() + 41660000);
#endif
}
void INTERRUPT_ATTR RH_ASK::setModeIdle()
{
if (_mode != RHModeIdle)
{
// Disable the transmitter hardware
writePtt(LOW);
writeTx(LOW);
_mode = RHModeIdle;
}
}
void RH_ASK::setModeRx()
{
if (_mode != RHModeRx)
{
// Disable the transmitter hardware
writePtt(LOW);
writeTx(LOW);
_mode = RHModeRx;
}
}
void RH_ASK::setModeTx()
{
if (_mode != RHModeTx)
{
// PRepare state varibles for a new transmission
_txIndex = 0;
_txBit = 0;
_txSample = 0;
// Enable the transmitter hardware
writePtt(HIGH);
_mode = RHModeTx;
}
}
// Call this often
bool RH_ASK::available()
{
if (_mode == RHModeTx)
return false;
setModeRx();
if (_rxBufFull)
{
validateRxBuf();
_rxBufFull= false;
}
return _rxBufValid;
}
bool RH_ASK::recv(uint8_t* buf, uint8_t* len)
{
if (!available())
return false;
if (buf && len)
{
// Skip the length and 4 headers that are at the beginning of the rxBuf
// and drop the trailing 2 bytes of FCS
uint8_t message_len = _rxBufLen-RH_ASK_HEADER_LEN - 3;
if (*len > message_len)
*len = message_len;
memcpy(buf, _rxBuf+RH_ASK_HEADER_LEN+1, *len);
}
_rxBufValid = false; // Got the most recent message, delete it
// printBuffer("recv:", buf, *len);
return true;
}
// Caution: this may block
bool RH_ASK::send(const uint8_t* data, uint8_t len)
{
uint8_t i;
uint16_t index = 0;
uint16_t crc = 0xffff;
uint8_t *p = _txBuf + RH_ASK_PREAMBLE_LEN; // start of the message area
uint8_t count = len + 3 + RH_ASK_HEADER_LEN; // Added byte count and FCS and headers to get total number of bytes
if (len > RH_ASK_MAX_MESSAGE_LEN)
return false;
// Wait for transmitter to become available
waitPacketSent();
// Encode the message length
crc = RHcrc_ccitt_update(crc, count);
p[index++] = symbols[count >> 4];
p[index++] = symbols[count & 0xf];
// Encode the headers
crc = RHcrc_ccitt_update(crc, _txHeaderTo);
p[index++] = symbols[_txHeaderTo >> 4];
p[index++] = symbols[_txHeaderTo & 0xf];
crc = RHcrc_ccitt_update(crc, _txHeaderFrom);
p[index++] = symbols[_txHeaderFrom >> 4];
p[index++] = symbols[_txHeaderFrom & 0xf];
crc = RHcrc_ccitt_update(crc, _txHeaderId);
p[index++] = symbols[_txHeaderId >> 4];
p[index++] = symbols[_txHeaderId & 0xf];
crc = RHcrc_ccitt_update(crc, _txHeaderFlags);
p[index++] = symbols[_txHeaderFlags >> 4];
p[index++] = symbols[_txHeaderFlags & 0xf];
// Encode the message into 6 bit symbols. Each byte is converted into
// 2 6-bit symbols, high nybble first, low nybble second
for (i = 0; i < len; i++)
{
crc = RHcrc_ccitt_update(crc, data[i]);
p[index++] = symbols[data[i] >> 4];
p[index++] = symbols[data[i] & 0xf];
}
// Append the fcs, 16 bits before encoding (4 6-bit symbols after encoding)
// Caution: VW expects the _ones_complement_ of the CCITT CRC-16 as the FCS
// VW sends FCS as low byte then hi byte
crc = ~crc;
p[index++] = symbols[(crc >> 4) & 0xf];
p[index++] = symbols[crc & 0xf];
p[index++] = symbols[(crc >> 12) & 0xf];
p[index++] = symbols[(crc >> 8) & 0xf];
// Total number of 6-bit symbols to send
_txBufLen = index + RH_ASK_PREAMBLE_LEN;
// Start the low level interrupt handler sending symbols
setModeTx();
return true;
}
// Read the RX data input pin, taking into account platform type and inversion.
bool INTERRUPT_ATTR RH_ASK::readRx()
{
bool value;
#if (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8)
value = ((RH_ASK_RX_PORT & (1<<RH_ASK_RX_PIN)) ? 1 : 0);
#else
value = digitalRead(_rxPin);
#endif
return value ^ _rxInverted;
}
// Write the TX output pin, taking into account platform type.
void INTERRUPT_ATTR RH_ASK::writeTx(bool value)
{
#if (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8)
((value) ? (RH_ASK_TX_PORT |= (1<<RH_ASK_TX_PIN)) : (RH_ASK_TX_PORT &= ~(1<<RH_ASK_TX_PIN)));
#else
digitalWrite(_txPin, value);
#endif
}
// Write the PTT output pin, taking into account platform type and inversion.
void INTERRUPT_ATTR RH_ASK::writePtt(bool value)
{
#if (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8)
#if RH_ASK_PTT_PIN
((value) ? (RH_ASK_PTT_PORT |= (1<<RH_ASK_PTT_PIN)) : (RH_ASK_PTT_PORT &= ~(1<<RH_ASK_PTT_PIN)));
#else
((value) ? (RH_ASK_TX_PORT |= (1<<RH_ASK_TX_PIN)) : (RH_ASK_TX_PORT &= ~(1<<RH_ASK_TX_PIN)));
#endif
#else
digitalWrite(_pttPin, value ^ _pttInverted);
#endif
}
uint8_t RH_ASK::maxMessageLength()
{
return RH_ASK_MAX_MESSAGE_LEN;
}
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO)
#if defined(RH_PLATFORM_ATTINY)
#define RH_ASK_TIMER_VECTOR TIM0_COMPA_vect
#else // Assume Arduino Uno (328p or similar)
#if defined(RH_ASK_ARDUINO_USE_TIMER2)
#define RH_ASK_TIMER_VECTOR TIMER2_COMPA_vect
#else
#define RH_ASK_TIMER_VECTOR TIMER1_COMPA_vect
#endif
#endif
#elif (RH_ASK_PLATFORM == RH_ASK_PLATFORM_GENERIC_AVR8)
#define __COMB(a,b,c) (a##b##c)
#define _COMB(a,b,c) __COMB(a,b,c)
#define RH_ASK_TIMER_VECTOR _COMB(TIMER,RH_ASK_TIMER_INDEX,_COMPA_vect)
#endif
#if (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(CORE_TEENSY)
void TIMER1_COMPA_vect(void)
{
thisASKDriver->handleTimerInterrupt();
}
#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined (__arm__) && defined(ARDUINO_ARCH_SAMD)
// Arduino Zero
void TC3_Handler()
{
// The type cast must fit with the selected timer mode
TcCount16* TC = (TcCount16*)RH_ASK_ZERO_TIMER; // get timer struct
TC->INTFLAG.bit.MC0 = 1;
thisASKDriver->handleTimerInterrupt();
}
#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) && defined(__arm__) && defined(ARDUINO_SAM_DUE)
// Arduino Due
void TC1_Handler()
{
TC_GetStatus(RH_ASK_DUE_TIMER, 1);
thisASKDriver->handleTimerInterrupt();
}
#elif (RH_PLATFORM == RH_PLATFORM_ARDUINO) || (RH_PLATFORM == RH_PLATFORM_GENERIC_AVR8)
// This is the interrupt service routine called when timer1 overflows
// Its job is to output the next bit from the transmitter (every 8 calls)
// and to call the PLL code if the receiver is enabled
//ISR(SIG_OUTPUT_COMPARE1A)
ISR(RH_ASK_TIMER_VECTOR)
{
thisASKDriver->handleTimerInterrupt();
}
#elif (RH_PLATFORM == RH_PLATFORM_MSP430) || (RH_PLATFORM == RH_PLATFORM_STM32)
// LaunchPad, Maple
void interrupt()
{
thisASKDriver->handleTimerInterrupt();
}
#elif (RH_PLATFORM == RH_PLATFORM_STM32F2) // Photon
void TimerInterruptHandler()
{
thisASKDriver->handleTimerInterrupt();
}
#elif (RH_PLATFORM == RH_PLATFORM_MSP430)
interrupt(TIMER0_A0_VECTOR) Timer_A_int(void)
{
thisASKDriver->handleTimerInterrupt();
};
#elif (RH_PLATFORM == RH_PLATFORM_CHIPKIT_CORE)
// Using ChipKIT Core on Arduino IDE
uint32_t chipkit_timer_interrupt_handler(uint32_t currentTime)
{
thisASKDriver->handleTimerInterrupt();
return (currentTime + ((CORE_TICK_RATE * 1000)/8)/thisASKDriver->speed());
}
#elif (RH_PLATFORM == RH_PLATFORM_UNO32)
// Under old MPIDE, which has been discontinued:
extern "C"
{
void __ISR(_TIMER_1_VECTOR, ipl1) timerInterrupt(void)
{
thisASKDriver->handleTimerInterrupt();
mT1ClearIntFlag(); // Clear timer 1 interrupt flag
}
}
#elif (RH_PLATFORM == RH_PLATFORM_ESP8266)
void INTERRUPT_ATTR esp8266_timer_interrupt_handler()
{
// timer0_write(ESP.getCycleCount() + 41660000);
// timer0_write(ESP.getCycleCount() + (clockCyclesPerMicrosecond() * 100) - 120 );
timer0_write(ESP.getCycleCount() + thisASKDriver->_timerIncrement);
// static int toggle = 0;
// toggle = (toggle == 1) ? 0 : 1;
// digitalWrite(4, toggle);
thisASKDriver->handleTimerInterrupt();
}
#endif
// Convert a 6 bit encoded symbol into its 4 bit decoded equivalent
uint8_t INTERRUPT_ATTR RH_ASK::symbol_6to4(uint8_t symbol)
{
uint8_t i;
uint8_t count;
// Linear search :-( Could have a 64 byte reverse lookup table?
// There is a little speedup here courtesy Ralph Doncaster:
// The shortcut works because bit 5 of the symbol is 1 for the last 8
// symbols, and it is 0 for the first 8.
// So we only have to search half the table
for (i = (symbol>>2) & 8, count=8; count-- ; i++)
if (symbol == symbols[i]) return i;
return 0; // Not found
}
// Check whether the latest received message is complete and uncorrupted
// We should always check the FCS at user level, not interrupt level
// since it is slow
void RH_ASK::validateRxBuf()
{
uint16_t crc = 0xffff;
// The CRC covers the byte count, headers and user data
for (uint8_t i = 0; i < _rxBufLen; i++)
crc = RHcrc_ccitt_update(crc, _rxBuf[i]);
if (crc != 0xf0b8) // CRC when buffer and expected CRC are CRC'd
{
// Reject and drop the message
_rxBad++;
_rxBufValid = false;
return;
}
// Extract the 4 headers that follow the message length
_rxHeaderTo = _rxBuf[1];
_rxHeaderFrom = _rxBuf[2];
_rxHeaderId = _rxBuf[3];
_rxHeaderFlags = _rxBuf[4];
if (_promiscuous ||
_rxHeaderTo == _thisAddress ||
_rxHeaderTo == RH_BROADCAST_ADDRESS)
{
_rxGood++;
_rxBufValid = true;
}
}
void INTERRUPT_ATTR RH_ASK::receiveTimer()
{
bool rxSample = readRx();
// Integrate each sample
if (rxSample)
_rxIntegrator++;
if (rxSample != _rxLastSample)
{
// Transition, advance if ramp > 80, retard if < 80
_rxPllRamp += ((_rxPllRamp < RH_ASK_RAMP_TRANSITION)
? RH_ASK_RAMP_INC_RETARD
: RH_ASK_RAMP_INC_ADVANCE);
_rxLastSample = rxSample;
}
else
{
// No transition
// Advance ramp by standard 20 (== 160/8 samples)
_rxPllRamp += RH_ASK_RAMP_INC;
}
if (_rxPllRamp >= RH_ASK_RX_RAMP_LEN)
{
// Add this to the 12th bit of _rxBits, LSB first
// The last 12 bits are kept
_rxBits >>= 1;
// Check the integrator to see how many samples in this cycle were high.
// If < 5 out of 8, then its declared a 0 bit, else a 1;
if (_rxIntegrator >= 5)
_rxBits |= 0x800;
_rxPllRamp -= RH_ASK_RX_RAMP_LEN;
_rxIntegrator = 0; // Clear the integral for the next cycle
if (_rxActive)
{
// We have the start symbol and now we are collecting message bits,
// 6 per symbol, each which has to be decoded to 4 bits
if (++_rxBitCount >= 12)
{
// Have 12 bits of encoded message == 1 byte encoded
// Decode as 2 lots of 6 bits into 2 lots of 4 bits
// The 6 lsbits are the high nybble
uint8_t this_byte =
(symbol_6to4(_rxBits & 0x3f)) << 4
| symbol_6to4(_rxBits >> 6);
// The first decoded byte is the byte count of the following message
// the count includes the byte count and the 2 trailing FCS bytes
// REVISIT: may also include the ACK flag at 0x40
if (_rxBufLen == 0)
{
// The first byte is the byte count
// Check it for sensibility. It cant be less than 7, since it
// includes the byte count itself, the 4 byte header and the 2 byte FCS
_rxCount = this_byte;
if (_rxCount < 7 || _rxCount > RH_ASK_MAX_PAYLOAD_LEN)
{
// Stupid message length, drop the whole thing
_rxActive = false;
_rxBad++;
return;
}
}
_rxBuf[_rxBufLen++] = this_byte;
if (_rxBufLen >= _rxCount)
{
// Got all the bytes now
_rxActive = false;
_rxBufFull = true;
setModeIdle();
}
_rxBitCount = 0;
}
}
// Not in a message, see if we have a start symbol
else if (_rxBits == RH_ASK_START_SYMBOL)
{
// Have start symbol, start collecting message
_rxActive = true;
_rxBitCount = 0;
_rxBufLen = 0;
}
}
}
void INTERRUPT_ATTR RH_ASK::transmitTimer()
{
if (_txSample++ == 0)
{
// Send next bit
// Symbols are sent LSB first
// Finished sending the whole message? (after waiting one bit period
// since the last bit)
if (_txIndex >= _txBufLen)
{
setModeIdle();
_txGood++;
}
else
{
writeTx(_txBuf[_txIndex] & (1 << _txBit++));
if (_txBit >= 6)
{
_txBit = 0;
_txIndex++;
}
}
}
if (_txSample > 7)
_txSample = 0;
}
void INTERRUPT_ATTR RH_ASK::handleTimerInterrupt()
{
if (_mode == RHModeRx)
receiveTimer(); // Receiving
else if (_mode == RHModeTx)
transmitTimer(); // Transmitting
}

422
src/RH_ASK.h Normal file
View File

@@ -0,0 +1,422 @@
// RH_ASK.h
//
// Copyright (C) 2014 Mike McCauley
// $Id: RH_ASK.h,v 1.16 2016/07/07 00:02:53 mikem Exp mikem $
#ifndef RH_ASK_h
#define RH_ASK_h
#include <RHGenericDriver.h>
// Maximum message length (including the headers, byte count and FCS) we are willing to support
// This is pretty arbitrary
#define RH_ASK_MAX_PAYLOAD_LEN 67
// The length of the headers we add (To, From, Id, Flags)
// The headers are inside the payload and are therefore protected by the FCS
#define RH_ASK_HEADER_LEN 4
// This is the maximum message length that can be supported by this library.
// 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_ASK_MAX_MESSAGE_LEN
#define RH_ASK_MAX_MESSAGE_LEN (RH_ASK_MAX_PAYLOAD_LEN - RH_ASK_HEADER_LEN - 3)
#endif
#if !defined(RH_ASK_RX_SAMPLES_PER_BIT)
/// Number of samples per bit
#define RH_ASK_RX_SAMPLES_PER_BIT 8
#endif //RH_ASK_RX_SAMPLES_PER_BIT
/// The size of the receiver ramp. Ramp wraps modulo this number
#define RH_ASK_RX_RAMP_LEN 160
// Ramp adjustment parameters
// Standard is if a transition occurs before RH_ASK_RAMP_TRANSITION (80) in the ramp,
// the ramp is retarded by adding RH_ASK_RAMP_INC_RETARD (11)
// else by adding RH_ASK_RAMP_INC_ADVANCE (29)
// If there is no transition it is adjusted by RH_ASK_RAMP_INC (20)
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_INC (RH_ASK_RX_RAMP_LEN/RH_ASK_RX_SAMPLES_PER_BIT)
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_TRANSITION RH_ASK_RX_RAMP_LEN/2
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_ADJUST 9
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_INC_RETARD (RH_ASK_RAMP_INC-RH_ASK_RAMP_ADJUST)
/// Internal ramp adjustment parameter
#define RH_ASK_RAMP_INC_ADVANCE (RH_ASK_RAMP_INC+RH_ASK_RAMP_ADJUST)
/// Outgoing message bits grouped as 6-bit words
/// 36 alternating 1/0 bits, followed by 12 bits of start symbol (together called the preamble)
/// Followed immediately by the 4-6 bit encoded byte count,
/// message buffer and 2 byte FCS
/// Each byte from the byte count on is translated into 2x6-bit words
/// Caution, each symbol is transmitted LSBit first,
/// but each byte is transmitted high nybble first
/// This is the number of 6 bit nibbles in the preamble
#define RH_ASK_PREAMBLE_LEN 8
/////////////////////////////////////////////////////////////////////
/// \class RH_ASK RH_ASK.h <RH_ASK.h>
/// \brief Driver to send and receive unaddressed, unreliable datagrams via inexpensive ASK (Amplitude Shift Keying) or
/// OOK (On Off Keying) RF transceivers.
///
/// The message format and software technology is based on our earlier VirtualWire library
/// (http://www.airspayce.com/mikem/arduino/VirtualWire), with which it is compatible.
/// See http://www.airspayce.com/mikem/arduino/VirtualWire.pdf for more details.
/// VirtualWire is now obsolete and unsupported and is replaced by this library.
///
/// RH_ASK is a Driver for Arduino, Maple and others that provides features to send short
/// messages, without addressing, retransmit or acknowledgment, a bit like UDP
/// over wireless, using ASK (amplitude shift keying). Supports a number of
/// inexpensive radio transmitters and receivers. All that is required is
/// transmit data, receive data and (for transmitters, optionally) a PTT
/// transmitter enable. Can also be used over various analog connections (not just a data radio),
/// such as the audio channel of an A/V sender, or long TTL lines.
///
/// It is intended to be compatible with the RF Monolithics (www.rfm.com)
/// Virtual Wire protocol, but this has not been tested.
///
/// Does not use the Arduino UART. Messages are sent with a training preamble,
/// message length and checksum. Messages are sent with 4-to-6 bit encoding
/// for good DC balance, and a CRC checksum for message integrity.
///
/// But why not just use a UART connected directly to the
/// transmitter/receiver? As discussed in the RFM documentation, ASK receivers
/// require a burst of training pulses to synchronize the transmitter and
/// receiver, and also requires good balance between 0s and 1s in the message
/// stream in order to maintain the DC balance of the message. UARTs do not
/// provide these. They work a bit with ASK wireless, but not as well as this
/// code.
///
/// \par Theory of operation
///
/// See ASH Transceiver Software Designer's Guide of 2002.08.07
/// http://wireless.murata.com/media/products/apnotes/tr_swg05.pdf?ref=rfm.com
///
/// http://web.engr.oregonstate.edu/~moon/research/files/cas2_mar_07_dpll.pdf while not directly relevant
/// is also interesting.
///
/// \par Implementation Details
///
/// Messages of up to RH_ASK_MAX_PAYLOAD_LEN (67) bytes can be sent
/// Each message is transmitted as:
///
/// - 36 bit training preamble consisting of 0-1 bit pairs
/// - 12 bit start symbol 0xb38
/// - 1 byte of message length byte count (4 to 30), count includes byte count and FCS bytes
/// - n message bytes (uincluding 4 bytes of header), maximum n is RH_ASK_MAX_MESSAGE_LEN + 4 (64)
/// - 2 bytes FCS, sent low byte-hi byte
///
/// Everything after the start symbol is encoded 4 to 6 bits, Therefore a byte in the message
/// is encoded as 2x6 bit symbols, sent hi nybble, low nybble. Each symbol is sent LSBit
/// first. The message may consist of any binary digits.
///
/// The Arduino Diecimila clock rate is 16MHz => 62.5ns/cycle.
/// For an RF bit rate of 2000 bps, need 500microsec bit period.
/// The ramp requires 8 samples per bit period, so need 62.5microsec per sample => interrupt tick is 62.5microsec.
///
/// The maximum packet length consists of
/// (6 + 2 + RH_ASK_MAX_MESSAGE_LEN*2) * 6 = 768 bits = 0.384 secs (at 2000 bps).
/// where RH_ASK_MAX_MESSAGE_LEN is RH_ASK_MAX_PAYLOAD_LEN - 7 (= 60).
/// The code consists of an ISR interrupt handler. Most of the work is done in the interrupt
/// handler for both transmit and receive, but some is done from the user level. Expensive
/// functions like CRC computations are always done in the user level.
///
/// \par Supported Hardware
///
/// A range of communications
/// hardware is supported. The ones listed below are available in common retail
/// outlets in Australia and other countries for under $10 per unit. Many
/// other modules may also work with this software.
///
/// Runs on a wide range of Arduino processors using Arduino IDE 1.0 or later.
/// Also runs on on Energia,
/// with MSP430G2553 / G2452 and Arduino with ATMega328 (courtesy Yannick DEVOS - XV4Y),
/// but untested by us. It also runs on Teensy 3.0 (courtesy of Paul
/// Stoffregen), but untested by us. Also compiles and runs on ATtiny85 in
/// Arduino environment, courtesy r4z0r7o3. Also compiles on maple-ide-v0.0.12,
/// and runs on Maple, flymaple 1.1 etc. Runs on ATmega8/168 (Arduino Diecimila,
/// Uno etc), ATmega328 and can run on almost any other AVR8 platform,
/// without relying on the Arduino framework, by properly configuring the
/// library editing the RH_ASK.h header file for describing the access
/// to IO pins and for setting up the timer.
/// Runs on ChipKIT Core supported processors such as Uno32 etc.
///
/// - Receivers
/// - RX-B1 (433.92MHz) (also known as ST-RX04-ASK)
/// - RFM83C from HopeRF http://www.hoperfusa.com/details.jsp?pid=126
/// - Transmitters:
/// - TX-C1 (433.92MHz)
/// - RFM85 from HopeRF http://www.hoperfusa.com/details.jsp?pid=127
/// - Transceivers
/// - DR3100 (433.92MHz)
///
/// \par Connecting to Arduino
///
/// Most transmitters can be connected to Arduino like this:
/// \code
/// Arduino Transmitter
/// GND------------------------------GND
/// D12------------------------------Data
/// 5V-------------------------------VCC
/// \endcode
///
/// Most receivers can be connected to Arduino like this:
/// \code
/// Arduino Receiver
/// GND------------------------------GND
/// D11------------------------------Data
/// 5V-------------------------------VCC
/// SHUT (not connected)
/// WAKEB (not connected)
/// GND |
/// ANT |- connect to your antenna syetem
/// \endcode
///
/// RH_ASK works with ATTiny85, using Arduino 1.0.5 and tinycore from
/// https://code.google.com/p/arduino-tiny/downloads/detail?name=arduino-tiny-0100-0018.zip
/// Tested with the examples ask_transmitter and ask_receiver on ATTiny85.
/// Caution: The RAM memory requirements on an ATTiny85 are *very* tight. Even the bare bones
/// ask_transmitter sketch barely fits in eh RAM available on the ATTiny85. Its unlikely to work on
/// smaller ATTinys such as the ATTiny45 etc. If you have wierd behaviour, consider
/// reducing the size of RH_ASK_MAX_PAYLOAD_LEN to the minimum you can work with.
/// Caution: the default internal clock speed on an ATTiny85 is 1MHz. You MUST set the internal clock speed
/// to 8MHz. You can do this with Arduino IDE, tineycore and ArduinoISP by setting the board type to "ATtiny85@8MHz',
/// setting theProgrammer to 'Arduino as ISP' and selecting Tools->Burn Bootloader. This does not actually burn a
/// bootloader into the tiny, it just changes the fuses so the chip runs at 8MHz.
/// If you run the chip at 1MHz, you will get RK_ASK speeds 1/8th of the expected.
///
/// Initialise RH_ASK for ATTiny85 like this:
/// // #include <SPI.h> // comment this out, not needed
/// RH_ASK driver(2000, 4, 3); // 200bps, TX on D3 (pin 2), RX on D4 (pin 3)
/// then:
/// Connect D3 (pin 2) as the output to the transmitter
/// Connect D4 (pin 3) as the input from the receiver.
///
///
/// For testing purposes you can connect 2 Arduino RH_ASK instances directly, by
/// connecting pin 12 of one to 11 of the other and vice versa, like this for a duplex connection:
///
/// \code
/// Arduino 1 wires Arduino 1
/// D11-----------------------------D12
/// D12-----------------------------D11
/// GND-----------------------------GND
/// \endcode
///
/// You can also connect 2 RH_ASK instances over a suitable analog
/// transmitter/receiver, such as the audio channel of an A/V transmitter/receiver. You may need
/// buffers at each end of the connection to convert the 0-5V digital output to a suitable analog voltage.
///
/// Measured power output from RFM85 at 5V was 18dBm.
///
/// \par ESP8266
/// This module has been tested with the ESP8266 using an ESP-12 on a breakout board
/// ESP-12E SMD Adaptor Board with Power Regulator from tronixlabs
/// http://tronixlabs.com.au/wireless/esp8266/esp8266-esp-12e-smd-adaptor-board-with-power-regulator-australia/
/// compiled on Arduino 1.6.5 and the ESP8266 support 2.0 installed with Board Manager.
/// CAUTION: do not use pin 11 for IO with this chip: it will cause the sketch to hang. Instead
/// use constructor arguments to configure different pins, eg:
/// \code
/// RH_ASK driver(2000, 2, 4, 5);
/// \endcode
/// Which will initialise the driver at 2000 bps, recieve on GPIO2, transmit on GPIO4, PTT on GPIO5.
/// Caution: on the tronixlabs breakout board, pins 4 and 5 may be labelled vice-versa.
///
/// \par Timers
/// The RH_ASK driver uses a timer-driven interrupt to generate 8 interrupts per bit period. RH_ASK
/// takes over a timer on Arduino-like platforms. By default it takes over Timer 1. You can force it
/// to use Timer 2 instead by enabling the define RH_ASK_ARDUINO_USE_TIMER2 near the top of RH_ASK.cpp
/// On Arduino Zero it takes over timer TC3. On Arduino Due it takes over timer
/// TC0. On ESP8266, takes over timer0 (which conflicts with ServoTimer0).
///
/// Caution: ATTiny85 has only 2 timers, one (timer 0) usually used for
/// millis() and one (timer 1) for PWM analog outputs. The RH_ASK Driver
/// library, when built for ATTiny85, takes over timer 0, which prevents use
/// of millis() etc but does permit analog outputs. This will affect the accuracy of millis() and time
/// measurement.
class RH_ASK : public RHGenericDriver
{
public:
/// Constructor.
/// At present only one instance of RH_ASK per sketch is supported.
/// \param[in] speed The desired bit rate in bits per second
/// \param[in] rxPin The pin that is used to get data from the receiver
/// \param[in] txPin The pin that is used to send data to the transmitter
/// \param[in] pttPin The pin that is connected to the transmitter controller. It will be set HIGH to enable the transmitter (unless pttInverted is true).
/// \param[in] pttInverted true if you desire the pttin to be inverted so that LOW wil enable the transmitter.
RH_ASK(uint16_t speed = 2000, uint8_t rxPin = 11, uint8_t txPin = 12, uint8_t pttPin = 10, bool pttInverted = false);
/// 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, this will also put the Driver into RHModeRx mode until
/// a message is actually received bythe 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 loads a message into the transmitter and starts the transmitter. Note that a message length
/// of 0 is NOT permitted.
/// \param[in] data Array of data to be sent
/// \param[in] len Number of bytes of data to send (> 0)
/// \return true if the message length was valid and it was correctly queued for transmit
virtual bool send(const uint8_t* data, uint8_t len);
/// Returns the maximum message length
/// available in this Driver.
/// \return The maximum legal message length
virtual uint8_t maxMessageLength();
/// 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 RF69.
void setModeRx();
/// If current mode is Rx or Idle, changes it to Rx. F
/// Starts the transmitter in the RF69.
void setModeTx();
/// dont call this it used by the interrupt handler
void handleTimerInterrupt();
/// Returns the current speed in bits per second
/// \return The current speed in bits per second
uint16_t speed() { return _speed;}
#if (RH_PLATFORM == RH_PLATFORM_ESP8266)
/// ESP8266 timer0 increment value
uint32_t _timerIncrement;
#endif
protected:
/// Helper function for calculating timer ticks
uint8_t timerCalc(uint16_t speed, uint16_t max_ticks, uint16_t *nticks);
/// Set up the timer and its interrutps so the interrupt handler is called at the right frequency
void timerSetup();
/// Read the rxPin in a platform dependent way, taking into account whether it is inverted or not
bool readRx();
/// Write the txPin in a platform dependent way
void writeTx(bool value);
/// Write the txPin in a platform dependent way, taking into account whether it is inverted or not
void writePtt(bool value);
/// Translates a 6 bit symbol to its 4 bit plaintext equivalent
uint8_t symbol_6to4(uint8_t symbol);
/// The receiver handler function, called a 8 times the bit rate
void receiveTimer();
/// The transmitter handler function, called a 8 times the bit rate
void transmitTimer();
/// Check whether the latest received message is complete and uncorrupted
/// We should always check the FCS at user level, not interrupt level
/// since it is slow
void validateRxBuf();
/// Configure bit rate in bits per second
uint16_t _speed;
/// The configure receiver pin
uint8_t _rxPin;
/// The configure transmitter pin
uint8_t _txPin;
/// The configured transmitter enable pin
uint8_t _pttPin;
/// True of the sense of the rxPin is to be inverted
bool _rxInverted;
/// True of the sense of the pttPin is to be inverted
bool _pttInverted;
// Used in the interrupt handlers
/// Buf is filled but not validated
volatile bool _rxBufFull;
/// Buf is full and valid
volatile bool _rxBufValid;
/// Last digital input from the rx data pin
volatile bool _rxLastSample;
/// This is the integrate and dump integral. If there are <5 0 samples in the PLL cycle
/// the bit is declared a 0, else a 1
volatile uint8_t _rxIntegrator;
/// PLL ramp, varies between 0 and RH_ASK_RX_RAMP_LEN-1 (159) over
/// RH_ASK_RX_SAMPLES_PER_BIT (8) samples per nominal bit time.
/// When the PLL is synchronised, bit transitions happen at about the
/// 0 mark.
volatile uint8_t _rxPllRamp;
/// Flag indicates if we have seen the start symbol of a new message and are
/// in the processes of reading and decoding it
volatile uint8_t _rxActive;
/// Last 12 bits received, so we can look for the start symbol
volatile uint16_t _rxBits;
/// How many bits of message we have received. Ranges from 0 to 12
volatile uint8_t _rxBitCount;
/// The incoming message buffer
uint8_t _rxBuf[RH_ASK_MAX_PAYLOAD_LEN];
/// The incoming message expected length
volatile uint8_t _rxCount;
/// The incoming message buffer length received so far
volatile uint8_t _rxBufLen;
/// Index of the next symbol to send. Ranges from 0 to vw_tx_len
uint8_t _txIndex;
/// Bit number of next bit to send
uint8_t _txBit;
/// Sample number for the transmitter. Runs 0 to 7 during one bit interval
uint8_t _txSample;
/// The transmitter buffer in _symbols_ not data octets
uint8_t _txBuf[(RH_ASK_MAX_PAYLOAD_LEN * 2) + RH_ASK_PREAMBLE_LEN];
/// Number of symbols in _txBuf to be sent;
uint8_t _txBufLen;
};
/// @example ask_reliable_datagram_client.pde
/// @example ask_reliable_datagram_server.pde
/// @example ask_transmitter.pde
/// @example ask_receiver.pde
#endif

464
src/RH_CC110.cpp Normal file
View File

@@ -0,0 +1,464 @@
// RH_CC110.cpp
//
// Driver for Texas Instruments CC110L transceiver.
//
// Copyright (C) 2016 Mike McCauley
// $Id: RH_CC110.cpp,v 1.4 2016/01/02 01:46:34 mikem Exp $
#include <RH_CC110.h>
// Interrupt vectors for the 3 Arduino interrupt pins
// Each interrupt can be handled by a different instance of RH_CC110, allowing you to have
// 2 or more LORAs per Arduino
RH_CC110* RH_CC110::_deviceForInterrupt[RH_CC110_NUM_INTERRUPTS] = {0, 0, 0};
uint8_t RH_CC110::_interruptCount = 0; // Index into _deviceForInterrupt for next device
// We need 2 tables of modem configuration registers, since some values change depending on the Xtal frequency
// These are indexed by the values of ModemConfigChoice
// Canned modem configurations generated with the TI SmartRF Studio v7 version 2.3.0 on boodgie
// based on the sample 'Typical settings'
// Stored in flash (program) memory to save SRAM
// For 26MHz crystals
PROGMEM static const RH_CC110::ModemConfig MODEM_CONFIG_TABLE_26MHZ[] =
{
// 0B 0C 10 11 12 15 19 1A 1B 1C 1D 21 22 23 24 25 26 2C 2D 2E
{0x06, 0x00, 0xf5, 0x83, 0x13, 0x15, 0x16, 0x6c, 0x03, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb1_2Fd5_2
{0x06, 0x00, 0xf6, 0x83, 0x13, 0x15, 0x16, 0x6c, 0x03, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb2_4Fd5_2
{0x06, 0x00, 0xc7, 0x83, 0x13, 0x40, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb4_8Fd25_4
{0x06, 0x00, 0xc8, 0x93, 0x13, 0x34, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb10Fd19
{0x06, 0x00, 0xca, 0x83, 0x13, 0x35, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb38_4Fd20
{0x08, 0x00, 0x7b, 0x83, 0x13, 0x42, 0x1d, 0x1c, 0xc7, 0x00, 0xb2, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb76_8Fd32
{0x08, 0x00, 0x5b, 0xf8, 0x13, 0x47, 0x1d, 0x1c, 0xc7, 0x00, 0xb2, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x81, 0x31, 0x09}, // GFSK_Rb100Fd47
{0x0c, 0x00, 0x2d, 0x3b, 0x13, 0x62, 0x1d, 0x1c, 0xc7, 0x00, 0xb0, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x88, 0x31, 0x09}, // GFSK_Rb250Fd127
};
// For 27MHz crystals
PROGMEM static const RH_CC110::ModemConfig MODEM_CONFIG_TABLE_27MHZ[] =
{
// 0B 0C 10 11 12 15 19 1A 1B 1C 1D 21 22 23 24 25 26 2C 2D 2E
{0x06, 0x00, 0xf5, 0x75, 0x13, 0x14, 0x16, 0x6c, 0x03, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb1_2Fd5_2
{0x06, 0x00, 0xf6, 0x75, 0x13, 0x14, 0x16, 0x6c, 0x03, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb2_4Fd5_2
{0x06, 0x00, 0xc7, 0x75, 0x13, 0x37, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb4_8Fd25_4
{0x06, 0x00, 0xc8, 0x84, 0x13, 0x33, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb10Fd19
{0x06, 0x00, 0xca, 0x75, 0x13, 0x34, 0x16, 0x6c, 0x43, 0x40, 0x91, 0x56, 0x10, 0xe9, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb38_4Fd20
{0x08, 0x00, 0x7b, 0x75, 0x13, 0x42, 0x1d, 0x1c, 0xc7, 0x00, 0xb2, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x81, 0x35, 0x09}, // GFSK_Rb76_8Fd32
{0x08, 0x00, 0x5b, 0xf8, 0x13, 0x47, 0x1d, 0x1c, 0xc7, 0x00, 0xb2, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x81, 0x31, 0x09}, // GFSK_Rb100Fd47
{0x0c, 0x00, 0x2d, 0x2f, 0x13, 0x62, 0x1d, 0x1c, 0xc7, 0x00, 0xb0, 0xb6, 0x10, 0xea, 0x2a, 0x00, 0x1f, 0x88, 0x31, 0x09}, // GFSK_Rb250Fd127
};
// These power outputs are based on the suggested optimum values for
// multilayer inductors in the 915MHz frequency band. Per table 5-15.
// Yes these are not linear.
// Caution: this table is indexed by the values of enum TransmitPower
// Do not change one without changing the other.
// If you do not like these values, use setPaTable() directly.
PROGMEM static const uint8_t paPowerValues[] =
{
0x03, // -30dBm
0x0e, // -20dBm
0x1e, // -15dBm
0x27, // -10dBm
0x8e, // 0dBm
0xcd, // 5dBm
0xc7, // 7dBm
0xc0, // 10dBm
};
RH_CC110::RH_CC110(uint8_t slaveSelectPin, uint8_t interruptPin, bool is27MHz, RHGenericSPI& spi)
:
RHNRFSPIDriver(slaveSelectPin, spi),
_rxBufValid(false),
_is27MHz(is27MHz)
{
_interruptPin = interruptPin;
_myInterruptIndex = 0xff; // Not allocated yet
}
bool RH_CC110::init()
{
if (!RHNRFSPIDriver::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
// Reset the chip
// Strobe the reset
uint8_t val = spiCommand(RH_CC110_STROBE_30_SRES); // Reset
delay(100);
val = spiCommand(RH_CC110_STROBE_36_SIDLE); // IDLE
if (val != 0x0f)
return false; // No chip there or reset failed.
// 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);
// 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_CC110_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
spiWriteRegister(RH_CC110_REG_02_IOCFG0, RH_CC110_GDO_CFG_CRC_OK_AUTORESET); // gdo0 interrupt on CRC_OK
spiWriteRegister(RH_CC110_REG_06_PKTLEN, RH_CC110_MAX_PAYLOAD_LEN); // max packet length
spiWriteRegister(RH_CC110_REG_07_PKTCTRL1, RH_CC110_CRC_AUTOFLUSH); // no append status, crc autoflush, no addr check
spiWriteRegister(RH_CC110_REG_08_PKTCTRL0, RH_CC110_PKT_FORMAT_NORMAL | RH_CC110_CRC_EN | RH_CC110_LENGTH_CONFIG_VARIABLE);
spiWriteRegister(RH_CC110_REG_13_MDMCFG1, RH_CC110_NUM_PREAMBLE_4); // 4 preamble bytes, chan spacing not used
spiWriteRegister(RH_CC110_REG_17_MCSM1, RH_CC110_CCA_MODE_RSSI_PACKET | RH_CC110_RXOFF_MODE_RX | RH_CC110_TXOFF_MODE_IDLE);
spiWriteRegister(RH_CC110_REG_18_MCSM0, RH_CC110_FS_AUTOCAL_FROM_IDLE | RH_CC110_PO_TIMEOUT_64); // cal when going to tx or rx
spiWriteRegister(RH_CC110_REG_20_WORCTRL, 0xfb); // from smartrf
spiWriteRegister(RH_CC110_REG_29_FSTEST, 0x59); // from smartrf
spiWriteRegister(RH_CC110_REG_2A_PTEST, 0x7f); // from smartrf
spiWriteRegister(RH_CC110_REG_2B_AGCTEST, 0x3f); // from smartrf
// Set some reasonable default values
uint8_t syncWords[] = { 0xd3, 0x91 };
setSyncWords(syncWords, sizeof(syncWords));
setTxPower(TransmitPower5dBm);
setFrequency(915.0);
setModemConfig(GFSK_Rb1_2Fd5_2);
return true;
}
void RH_CC110::setIs27MHz(bool is27MHz)
{
_is27MHz = is27MHz;
}
// C++ level interrupt handler for this instance
// We use this to get RxDone and TxDone interrupts
void RH_CC110::handleInterrupt()
{
// Serial.println("I");
if (_mode == RHModeRx)
{
// Radio is confgigured to stay in RX until we move it to IDLE after a CRC_OK message for us
// We only get interrupts in RX mode, on CRC_OK
// CRC OK
_lastRssi = spiBurstReadRegister(RH_CC110_REG_34_RSSI); // Was set when sync word was detected
_bufLen = spiReadRegister(RH_CC110_REG_3F_FIFO);
if (_bufLen < 4)
{
// Something wrong there, flush the FIFO
spiCommand(RH_CC110_STROBE_3A_SFRX);
clearRxBuf();
return;
}
spiBurstRead(RH_CC110_REG_3F_FIFO | RH_CC110_SPI_BURST_MASK | RH_CC110_SPI_READ_MASK, _buf, _bufLen);
// All good so far. See if its for us
validateRxBuf();
if (_rxBufValid)
setModeIdle(); // Done
}
}
// These are low level functions that call the interrupt handler for the correct
// instance of RH_CC110.
// 3 interrupts allows us to have 3 different devices
void RH_CC110::isr0()
{
if (_deviceForInterrupt[0])
_deviceForInterrupt[0]->handleInterrupt();
}
void RH_CC110::isr1()
{
if (_deviceForInterrupt[1])
_deviceForInterrupt[1]->handleInterrupt();
}
void RH_CC110::isr2()
{
if (_deviceForInterrupt[2])
_deviceForInterrupt[2]->handleInterrupt();
}
uint8_t RH_CC110::spiReadRegister(uint8_t reg)
{
return spiRead((reg & 0x3f) | RH_CC110_SPI_READ_MASK);
}
uint8_t RH_CC110::spiBurstReadRegister(uint8_t reg)
{
return spiRead((reg & 0x3f) | RH_CC110_SPI_READ_MASK | RH_CC110_SPI_BURST_MASK);
}
uint8_t RH_CC110::spiWriteRegister(uint8_t reg, uint8_t val)
{
return spiWrite((reg & 0x3f), val);
}
uint8_t RH_CC110::spiBurstWriteRegister(uint8_t reg, const uint8_t* src, uint8_t len)
{
return spiBurstWrite((reg & 0x3f) | RH_CC110_SPI_BURST_MASK, src, len);
}
bool RH_CC110::printRegisters()
{
#ifdef RH_HAVE_SERIAL
uint8_t i;
for (i = 0; i <= 0x2f; i++)
{
Serial.print(i, HEX);
Serial.print(": ");
Serial.println(spiReadRegister(i), HEX);
}
// Burst registers
for (i = 0x30; i <= 0x3e; i++)
{
Serial.print(i, HEX);
Serial.print(": ");
Serial.println(spiBurstReadRegister(i), HEX);
}
#endif
return true;
}
// Check whether the latest received message is complete and uncorrupted
void RH_CC110::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_CC110::available()
{
if (_mode == RHModeTx)
return false;
if (_rxBufValid) // Will be set by the interrupt handler when a good message is received
return true;
setModeRx(); // Make sure we are receiving
return false; // Nothing yet
}
void RH_CC110::clearRxBuf()
{
ATOMIC_BLOCK_START;
_rxBufValid = false;
_bufLen = 0;
ATOMIC_BLOCK_END;
}
bool RH_CC110::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_CC110_HEADER_LEN)
*len = _bufLen - RH_CC110_HEADER_LEN;
memcpy(buf, _buf + RH_CC110_HEADER_LEN, *len);
ATOMIC_BLOCK_END;
}
clearRxBuf(); // This message accepted and cleared
return true;
}
bool RH_CC110::send(const uint8_t* data, uint8_t len)
{
if (len > RH_CC110_MAX_MESSAGE_LEN)
return false;
waitPacketSent(); // Make sure we dont interrupt an outgoing message
setModeIdle();
spiWriteRegister(RH_CC110_REG_3F_FIFO, len + RH_CC110_HEADER_LEN);
spiWriteRegister(RH_CC110_REG_3F_FIFO,_txHeaderTo);
spiWriteRegister(RH_CC110_REG_3F_FIFO,_txHeaderFrom);
spiWriteRegister(RH_CC110_REG_3F_FIFO,_txHeaderId);
spiWriteRegister(RH_CC110_REG_3F_FIFO,_txHeaderFlags);
spiBurstWriteRegister(RH_CC110_REG_3F_FIFO, data, len);
// Radio returns to Idle when TX is finished
// need waitPacketSent() to detect change of _mode and TX completion
setModeTx();
return true;
}
uint8_t RH_CC110::maxMessageLength()
{
return RH_CC110_MAX_MESSAGE_LEN;
}
void RH_CC110::setModeIdle()
{
if (_mode != RHModeIdle)
{
spiCommand(RH_CC110_STROBE_36_SIDLE);
_mode = RHModeIdle;
}
}
bool RH_CC110::sleep()
{
if (_mode != RHModeSleep)
{
spiCommand(RH_CC110_STROBE_39_SPWD);
_mode = RHModeSleep;
}
return true;
}
void RH_CC110::setModeRx()
{
if (_mode != RHModeRx)
{
// Radio is configuewd to stay in RX mode
// only receipt of a CRC_OK wil cause us to return it to IDLE
spiCommand(RH_CC110_STROBE_34_SRX);
_mode = RHModeRx;
}
}
void RH_CC110::setModeTx()
{
if (_mode != RHModeTx)
{
spiCommand(RH_CC110_STROBE_35_STX);
_mode = RHModeTx;
}
}
uint8_t RH_CC110::statusRead()
{
return spiCommand(RH_CC110_STROBE_3D_SNOP);
}
// Sigh, this chip has no TXDONE type interrupt, so we have to poll
bool RH_CC110::waitPacketSent()
{
// If we are not currently in transmit mode, there is no packet to wait for
if (_mode != RHModeTx)
return false;
// Caution: may transition through CALIBRATE
while ((statusRead() & RH_CC110_STATUS_STATE) != RH_CC110_STATUS_IDLE)
YIELD;
_mode = RHModeIdle;
return true;
}
bool RH_CC110::setTxPower(TransmitPower power)
{
if (power > sizeof(paPowerValues))
return false;
uint8_t patable[2];
memcpy_P(&patable[0], (void*)&paPowerValues[power], sizeof(uint8_t));
patable[1] = 0x00;
setPaTable(patable, sizeof(patable));
return true;
}
void RH_CC110::setPaTable(uint8_t* patable, uint8_t patablesize)
{
spiBurstWriteRegister(RH_CC110_REG_3E_PATABLE, patable, patablesize);
}
bool RH_CC110::setFrequency(float centre)
{
// From section 5.21: fcarrier = fxosc / 2^16 * FREQ
uint32_t FREQ;
float fxosc = _is27MHz ? 27.0 : 26.0;
FREQ = (uint32_t)(centre * 65536 / fxosc);
// Some trivial checks
if (FREQ & 0xff000000)
return false;
spiWriteRegister(RH_CC110_REG_0D_FREQ2, (FREQ >> 16) & 0xff);
spiWriteRegister(RH_CC110_REG_0E_FREQ1, (FREQ >> 8) & 0xff);
spiWriteRegister(RH_CC110_REG_0F_FREQ0, FREQ & 0xff);
// Radio is configured to calibrate automatically whenever it enters RX or TX mode
// so no need to check for PLL lock here
return true;
}
// Sets registers from a canned modem configuration structure
void RH_CC110::setModemRegisters(const ModemConfig* config)
{
spiWriteRegister(RH_CC110_REG_0B_FSCTRL1, config->reg_0b);
spiWriteRegister(RH_CC110_REG_0C_FSCTRL0, config->reg_0c);
spiWriteRegister(RH_CC110_REG_10_MDMCFG4, config->reg_10);
spiWriteRegister(RH_CC110_REG_11_MDMCFG3, config->reg_11);
spiWriteRegister(RH_CC110_REG_12_MDMCFG2, config->reg_12);
spiWriteRegister(RH_CC110_REG_15_DEVIATN, config->reg_15);
spiWriteRegister(RH_CC110_REG_19_FOCCFG, config->reg_19);
spiWriteRegister(RH_CC110_REG_1A_BSCFG, config->reg_1a);
spiWriteRegister(RH_CC110_REG_1B_AGCCTRL2, config->reg_1b);
spiWriteRegister(RH_CC110_REG_1C_AGCCTRL1, config->reg_1c);
spiWriteRegister(RH_CC110_REG_1D_AGCCTRL0, config->reg_1d);
spiWriteRegister(RH_CC110_REG_21_FREND1, config->reg_21);
spiWriteRegister(RH_CC110_REG_22_FREND0, config->reg_22);
spiWriteRegister(RH_CC110_REG_23_FSCAL3, config->reg_23);
spiWriteRegister(RH_CC110_REG_24_FSCAL2, config->reg_24);
spiWriteRegister(RH_CC110_REG_25_FSCAL1, config->reg_25);
spiWriteRegister(RH_CC110_REG_26_FSCAL0, config->reg_26);
spiWriteRegister(RH_CC110_REG_2C_TEST2, config->reg_2c);
spiWriteRegister(RH_CC110_REG_2D_TEST1, config->reg_2d);
spiWriteRegister(RH_CC110_REG_2E_TEST0, config->reg_2e);
}
// Set one of the canned Modem configs
// Returns true if its a valid choice
bool RH_CC110::setModemConfig(ModemConfigChoice index)
{
if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE_27MHZ) / sizeof(ModemConfig)))
return false;
const RH_CC110::ModemConfig *p = _is27MHz ? MODEM_CONFIG_TABLE_27MHZ : MODEM_CONFIG_TABLE_26MHZ ;
RH_CC110::ModemConfig cfg;
memcpy_P(&cfg, p + index, sizeof(RH_CC110::ModemConfig));
setModemRegisters(&cfg);
return true;
}
void RH_CC110::setSyncWords(const uint8_t* syncWords, uint8_t len)
{
if (!syncWords || len != 2)
return; // Only 2 byte sync words are supported
spiWriteRegister(RH_CC110_REG_04_SYNC1, syncWords[0]);
spiWriteRegister(RH_CC110_REG_05_SYNC0, syncWords[1]);
}

889
src/RH_CC110.h Normal file
View File

@@ -0,0 +1,889 @@
// RH_CC110.h
//
// Definitions for Texas Instruments CC110L transceiver.
// http://www.ti.com/lit/ds/symlink/cc110l.pdf
// As used in Anaren CC110L Air Module BoosterPack
// https://www.anaren.com/air/cc110l-air-module-boosterpack-embedded-antenna-module-anaren
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2016 Mike McCauley
// $Id: RH_CC110.h,v 1.5 2016/04/04 01:40:12 mikem Exp $
//
#ifndef RH_CC110_h
#define RH_CC110_h
#include <RHNRFSPIDriver.h>
// This is the maximum number of interrupts the driver can support
// Most Arduinos can handle 2, Megas can handle more
#define RH_CC110_NUM_INTERRUPTS 3
// Max number of octets the FIFO can hold
#define RH_CC110_FIFO_SIZE 64
// This is the maximum number of bytes that can be carried by the chip
// We use some for headers, keeping fewer for RadioHead messages
#define RH_CC110_MAX_PAYLOAD_LEN RH_CC110_FIFO_SIZE
// The length of the headers we add.
// The headers are inside the chip payload
#define RH_CC110_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
#ifndef RH_CC110_MAX_MESSAGE_LEN
#define RH_CC110_MAX_MESSAGE_LEN (RH_CC110_MAX_PAYLOAD_LEN - RH_CC110_HEADER_LEN - 1)
#endif
#define RH_CC110_SPI_READ_MASK 0x80
#define RH_CC110_SPI_BURST_MASK 0x40
// Register definitions from Table 5-22
#define RH_CC110_REG_00_IOCFG2 0x00
#define RH_CC110_REG_01_IOCFG1 0x01
#define RH_CC110_REG_02_IOCFG0 0x02
#define RH_CC110_REG_03_FIFOTHR 0x03
#define RH_CC110_REG_04_SYNC1 0x04
#define RH_CC110_REG_05_SYNC0 0x05
#define RH_CC110_REG_06_PKTLEN 0x06
#define RH_CC110_REG_07_PKTCTRL1 0x07
#define RH_CC110_REG_08_PKTCTRL0 0x08
#define RH_CC110_REG_09_ADDR 0x09
#define RH_CC110_REG_0A_CHANNR 0x0a
#define RH_CC110_REG_0B_FSCTRL1 0x0b
#define RH_CC110_REG_0C_FSCTRL0 0x0c
#define RH_CC110_REG_0D_FREQ2 0x0d
#define RH_CC110_REG_0E_FREQ1 0x0e
#define RH_CC110_REG_0F_FREQ0 0x0f
#define RH_CC110_REG_10_MDMCFG4 0x10
#define RH_CC110_REG_11_MDMCFG3 0x11
#define RH_CC110_REG_12_MDMCFG2 0x12
#define RH_CC110_REG_13_MDMCFG1 0x13
#define RH_CC110_REG_14_MDMCFG0 0x14
#define RH_CC110_REG_15_DEVIATN 0x15
#define RH_CC110_REG_16_MCSM2 0x16
#define RH_CC110_REG_17_MCSM1 0x17
#define RH_CC110_REG_18_MCSM0 0x18
#define RH_CC110_REG_19_FOCCFG 0x19
#define RH_CC110_REG_1A_BSCFG 0x1a
#define RH_CC110_REG_1B_AGCCTRL2 0x1b
#define RH_CC110_REG_1C_AGCCTRL1 0x1c
#define RH_CC110_REG_1D_AGCCTRL0 0x1d
#define RH_CC110_REG_1E_WOREVT1 0x1e
#define RH_CC110_REG_1F_WOREVT0 0x1f
#define RH_CC110_REG_20_WORCTRL 0x20
#define RH_CC110_REG_21_FREND1 0x21
#define RH_CC110_REG_22_FREND0 0x22
#define RH_CC110_REG_23_FSCAL3 0x23
#define RH_CC110_REG_24_FSCAL2 0x24
#define RH_CC110_REG_25_FSCAL1 0x25
#define RH_CC110_REG_26_FSCAL0 0x26
#define RH_CC110_REG_27_RCCTRL1 0x28
#define RH_CC110_REG_28_RCCTRL0 0x29
#define RH_CC110_REG_29_FSTEST 0x2a
#define RH_CC110_REG_2A_PTEST 0x2b
#define RH_CC110_REG_2B_AGCTEST 0x2c
#define RH_CC110_REG_2C_TEST2 0x2c
#define RH_CC110_REG_2D_TEST1 0x2d
#define RH_CC110_REG_2E_TEST0 0x2e
// Single byte read and write version of registers 0x30 to 0x3f. Strobes
// use spiCommand()
#define RH_CC110_STROBE_30_SRES 0x30
#define RH_CC110_STROBE_31_SFSTXON 0x31
#define RH_CC110_STROBE_32_SXOFF 0x32
#define RH_CC110_STROBE_33_SCAL 0x33
#define RH_CC110_STROBE_34_SRX 0x34
#define RH_CC110_STROBE_35_STX 0x35
#define RH_CC110_STROBE_36_SIDLE 0x36
#define RH_CC110_STROBE_39_SPWD 0x39
#define RH_CC110_STROBE_3A_SFRX 0x3a
#define RH_CC110_STROBE_3B_SFTX 0x3b
#define RH_CC110_STROBE_3D_SNOP 0x3d
// Burst read from these registers gives more data:
// use spiBurstReadRegister()
#define RH_CC110_REG_30_PARTNUM 0x30
#define RH_CC110_REG_31_VERSION 0x31
#define RH_CC110_REG_32_FREQEST 0x32
#define RH_CC110_REG_33_CRC_REG 0x33
#define RH_CC110_REG_34_RSSI 0x34
#define RH_CC110_REG_35_MARCSTATE 0x35
#define RH_CC110_REG_38_PKTSTATUS 0x38
#define RH_CC110_REG_3A_TXBYTES 0x3a
#define RH_CC110_REG_3B_RXBYTES 0x3b
// PATABLE, TXFIFO, RXFIFO also support burst
#define RH_CC110_REG_3E_PATABLE 0x3e
#define RH_CC110_REG_3F_FIFO 0x3f
// Status Byte
#define RH_CC110_STATUS_CHIP_RDY 0x80
#define RH_CC110_STATUS_STATE 0x70
#define RH_CC110_STATUS_IDLE 0x00
#define RH_CC110_STATUS_RX 0x10
#define RH_CC110_STATUS_TX 0x20
#define RH_CC110_STATUS_FSTXON 0x30
#define RH_CC110_STATUS_CALIBRATE 0x40
#define RH_CC110_STATUS_SETTLING 0x50
#define RH_CC110_STATUS_RXFIFO_OVERFLOW 0x60
#define RH_CC110_STATUS_TXFIFO_UNDERFLOW 0x70
#define RH_CC110_STATUS_FIFOBYTES_AVAILABLE 0x0f
// Register contents
// Chip Status Byte, read from header, data or command strobe
#define RH_CC110_CHIP_RDY 0x80
#define RH_CC110_STATE 0x70
#define RH_CC110_FIFO_BYTES_AVAILABLE 0x0f
// Register bit field definitions
// #define RH_CC110_REG_00_IOCFG2 0x00
// #define RH_CC110_REG_01_IOCFG1 0x01
// #define RH_CC110_REG_02_IOCFG0 0x02
#define RH_CC110_GDO_CFG_RX_FIFO_THR 0x00
#define RH_CC110_GDO_CFG_RX_FIFO_FULL 0x01
#define RH_CC110_GDO_CFG_TX_FIFO_THR 0x02
#define RH_CC110_GDO_CFG_TX_FIFO_EMPTY 0x03
#define RH_CC110_GDO_CFG_RX_FIFO_OVERFLOW 0x04
#define RH_CC110_GDO_CFG_TX_FIFO_UNDEFLOOW 0x05
#define RH_CC110_GDO_CFG_SYNC 0x06
#define RH_CC110_GDO_CFG_CRC_OK_AUTORESET 0x07
#define RH_CC110_GDO_CFG_CCA 0x09
#define RH_CC110_GDO_CFG_LOCK_DETECT 0x0a
#define RH_CC110_GDO_CFG_SERIAL_CLOCK 0x0b
#define RH_CC110_GDO_CFG_SYNCHRONOUS_SDO 0x0c
#define RH_CC110_GDO_CFG_SDO 0x0d
#define RH_CC110_GDO_CFG_CARRIER 0x0e
#define RH_CC110_GDO_CFG_CRC_OK 0x0f
#define RH_CC110_GDO_CFG_PA_PD 0x1b
#define RH_CC110_GDO_CFG_LNA_PD 0x1c
#define RH_CC110_GDO_CFG_CLK_32K 0x27
#define RH_CC110_GDO_CFG_CHIP_RDYN 0x29
#define RH_CC110_GDO_CFG_XOSC_STABLE 0x2b
#define RH_CC110_GDO_CFG_HIGH_IMPEDANCE 0x2e
#define RH_CC110_GDO_CFG_0 0x2f
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_1 0x30
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_1_5 0x31
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_2 0x32
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_3 0x33
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_4 0x34
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_6 0x35
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_8 0x36
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_12 0x37
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_16 0x38
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_24 0x39
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_32 0x3a
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_48 0x3b
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_64 0x3c
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_96 0x3d
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_128 0x3e
#define RH_CC110_GDO_CFG_CLK_XOSC_DIV_192 0x3f
// #define RH_CC110_REG_03_FIFOTHR 0x03
#define RH_CC110_ADC_RETENTION 0x80
#define RH_CC110_CLOSE_IN_RX 0x30
#define RH_CC110_CLOSE_IN_RX_0DB 0x00
#define RH_CC110_CLOSE_IN_RX_6DB 0x10
#define RH_CC110_CLOSE_IN_RX_12DB 0x20
#define RH_CC110_CLOSE_IN_RX_18DB 0x30
#define RH_CC110_FIFO_THR 0x0f
// #define RH_CC110_REG_04_SYNC1 0x04
// #define RH_CC110_REG_05_SYNC0 0x05
// #define RH_CC110_REG_06_PKTLEN 0x06
// #define RH_CC110_REG_07_PKTCTRL1 0x07
#define RH_CC110_CRC_AUTOFLUSH 0x08
#define RH_CC110_APPEND_STATUS 0x04
#define RH_CC110_ADDR_CHK 0x03
// can or the next 2:
#define RH_CC110_ADDR_CHK_ADDRESS 0x01
#define RH_CC110_ADDR_CHK_BROADCAST 0x02
// #define RH_CC110_REG_08_PKTCTRL0 0x08
#define RH_CC110_PKT_FORMAT 0x30
#define RH_CC110_PKT_FORMAT_NORMAL 0x00
#define RH_CC110_PKT_FORMAT_SYNC_SERIAL 0x10
#define RH_CC110_PKT_FORMAT_RANDOM_TX 0x20
#define RH_CC110_PKT_FORMAT_ASYNC_SERIAL 0x30
#define RH_CC110_CRC_EN 0x04
#define RH_CC110_LENGTH_CONFIG 0x03
#define RH_CC110_LENGTH_CONFIG_FIXED 0x00
#define RH_CC110_LENGTH_CONFIG_VARIABLE 0x01
#define RH_CC110_LENGTH_CONFIG_INFINITE 0x02
// #define RH_CC110_REG_09_ADDR 0x09
// #define RH_CC110_REG_0A_CHANNR 0x0a
// #define RH_CC110_REG_0B_FSCTRL1 0x0b
// #define RH_CC110_REG_0C_FSCTRL0 0x0c
// #define RH_CC110_REG_0D_FREQ2 0x0d
// #define RH_CC110_REG_0E_FREQ1 0x0e
// #define RH_CC110_REG_0F_FREQ0 0x0f
// #define RH_CC110_REG_10_MDMCFG4 0x10
#define RH_CC110_CHANBW_E 0xc0
#define RH_CC110_CHANBW_M 0x30
#define RH_CC110_DRATE_E 0x0f
// #define RH_CC110_REG_11_MDMCFG3 0x11
// #define RH_CC110_REG_12_MDMCFG2 0x12
#define RH_CC110_DEM_DCFILT_OFF 0x80
#define RH_CC110_MOD_FORMAT 0x70
#define RH_CC110_MOD_FORMAT_2FSK 0x00
#define RH_CC110_MOD_FORMAT_GFSK 0x10
#define RH_CC110_MOD_FORMAT_OOK 0x30
#define RH_CC110_MOD_FORMAT_4FSK 0x40
#define RH_CC110_MANCHESTER_EN 0x08
#define RH_CC110_SYNC_MODE 0x07
#define RH_CC110_SYNC_MODE_NONE 0x00
#define RH_CC110_SYNC_MODE_15_16 0x01
#define RH_CC110_SYNC_MODE_16_16 0x02
#define RH_CC110_SYNC_MODE_30_32 0x03
#define RH_CC110_SYNC_MODE_NONE_CARRIER 0x04
#define RH_CC110_SYNC_MODE_15_16_CARRIER 0x05
#define RH_CC110_SYNC_MODE_16_16_CARRIER 0x06
#define RH_CC110_SYNC_MODE_30_32_CARRIER 0x07
// #define RH_CC110_REG_13_MDMCFG1 0x13
#define RH_CC110_NUM_PREAMBLE 0x70
#define RH_CC110_NUM_PREAMBLE_2 0x00
#define RH_CC110_NUM_PREAMBLE_3 0x10
#define RH_CC110_NUM_PREAMBLE_4 0x20
#define RH_CC110_NUM_PREAMBLE_6 0x30
#define RH_CC110_NUM_PREAMBLE_8 0x40
#define RH_CC110_NUM_PREAMBLE_12 0x50
#define RH_CC110_NUM_PREAMBLE_16 0x60
#define RH_CC110_NUM_PREAMBLE_24 0x70
#define RH_CC110_CHANSPC_E 0x03
// #define RH_CC110_REG_14_MDMCFG0 0x14
// #define RH_CC110_REG_15_DEVIATN 0x15
#define RH_CC110_DEVIATION_E 0x70
#define RH_CC110_DEVIATION_M 0x07
// #define RH_CC110_REG_16_MCSM2 0x16
#define RH_CC110_RX_TIME_RSSI 0x10
// #define RH_CC110_REG_17_MCSM1 0x17
#define RH_CC110_CCA_MODE 0x30
#define RH_CC110_CCA_MODE_ALWAYS 0x00
#define RH_CC110_CCA_MODE_RSSI 0x10
#define RH_CC110_CCA_MODE_PACKET 0x20
#define RH_CC110_CCA_MODE_RSSI_PACKET 0x30
#define RH_CC110_RXOFF_MODE 0x0c
#define RH_CC110_RXOFF_MODE_IDLE 0x00
#define RH_CC110_RXOFF_MODE_FSTXON 0x04
#define RH_CC110_RXOFF_MODE_TX 0x08
#define RH_CC110_RXOFF_MODE_RX 0x0c
#define RH_CC110_TXOFF_MODE 0x03
#define RH_CC110_TXOFF_MODE_IDLE 0x00
#define RH_CC110_TXOFF_MODE_FSTXON 0x01
#define RH_CC110_TXOFF_MODE_TX 0x02
#define RH_CC110_TXOFF_MODE_RX 0x03
// #define RH_CC110_REG_18_MCSM0 0x18
#define RH_CC110_FS_AUTOCAL 0x30
#define RH_CC110_FS_AUTOCAL_NEVER 0x00
#define RH_CC110_FS_AUTOCAL_FROM_IDLE 0x10
#define RH_CC110_FS_AUTOCAL_TO_IDLE 0x20
#define RH_CC110_FS_AUTOCAL_TO_IDLE_4 0x30
#define RH_CC110_PO_TIMEOUT 0x0c
#define RH_CC110_PO_TIMEOUT_1 0x00
#define RH_CC110_PO_TIMEOUT_16 0x04
#define RH_CC110_PO_TIMEOUT_64 0x08
#define RH_CC110_PO_TIMEOUT_256 0x0c
#define RH_CC110_XOSC_FORCE_ON 0x01
// #define RH_CC110_REG_19_FOCCFG 0x19
#define RH_CC110_FOC_BS_CS_GATE 0x20
#define RH_CC110_FOC_PRE_K 0x18
#define RH_CC110_FOC_PRE_K_0 0x00
#define RH_CC110_FOC_PRE_K_1 0x08
#define RH_CC110_FOC_PRE_K_2 0x10
#define RH_CC110_FOC_PRE_K_3 0x18
#define RH_CC110_FOC_POST_K 0x04
#define RH_CC110_FOC_LIMIT 0x03
#define RH_CC110_FOC_LIMIT_0 0x00
#define RH_CC110_FOC_LIMIT_8 0x01
#define RH_CC110_FOC_LIMIT_4 0x02
#define RH_CC110_FOC_LIMIT_2 0x03
// #define RH_CC110_REG_1A_BSCFG 0x1a
#define RH_CC110_BS_PRE_K 0xc0
#define RH_CC110_BS_PRE_K_1 0x00
#define RH_CC110_BS_PRE_K_2 0x40
#define RH_CC110_BS_PRE_K_3 0x80
#define RH_CC110_BS_PRE_K_4 0xc0
#define RH_CC110_BS_PRE_KP 0x30
#define RH_CC110_BS_PRE_KP_1 0x00
#define RH_CC110_BS_PRE_KP_2 0x10
#define RH_CC110_BS_PRE_KP_3 0x20
#define RH_CC110_BS_PRE_KP_4 0x30
#define RH_CC110_BS_POST_KI 0x08
#define RH_CC110_BS_POST_KP 0x04
#define RH_CC110_BS_LIMIT 0x03
#define RH_CC110_BS_LIMIT_0 0x00
#define RH_CC110_BS_LIMIT_3 0x01
#define RH_CC110_BS_LIMIT_6 0x02
#define RH_CC110_BS_LIMIT_12 0x03
// #define RH_CC110_REG_1B_AGCCTRL2 0x1b
#define RH_CC110_MAX_DVA_GAIN 0xc0
#define RH_CC110_MAX_DVA_GAIN_ALL 0x00
#define RH_CC110_MAX_DVA_GAIN_ALL_LESS_1 0x40
#define RH_CC110_MAX_DVA_GAIN_ALL_LESS_2 0x80
#define RH_CC110_MAX_DVA_GAIN_ALL_LESS_3 0xc0
#define RH_CC110_MAX_LNA_GAIN 0x38
#define RH_CC110_MAGN_TARGET 0x07
#define RH_CC110_MAGN_TARGET_24DB 0x00
#define RH_CC110_MAGN_TARGET_27DB 0x01
#define RH_CC110_MAGN_TARGET_30DB 0x02
#define RH_CC110_MAGN_TARGET_33DB 0x03
#define RH_CC110_MAGN_TARGET_36DB 0x04
#define RH_CC110_MAGN_TARGET_38DB 0x05
#define RH_CC110_MAGN_TARGET_40DB 0x06
#define RH_CC110_MAGN_TARGET_42DB 0x07
// #define RH_CC110_REG_1C_AGCCTRL1 0x1c
#define RH_CC110_AGC_LNA_PRIORITY 0x40
#define RH_CC110_CARRIER_SENSE_REL_THR 0x30
#define RH_CC110_CARRIER_SENSE_REL_THR_0DB 0x00
#define RH_CC110_CARRIER_SENSE_REL_THR_6DB 0x10
#define RH_CC110_CARRIER_SENSE_REL_THR_10DB 0x20
#define RH_CC110_CARRIER_SENSE_REL_THR_14DB 0x30
#define RH_CC110_CARRIER_SENSE_ABS_THR 0x0f
// #define RH_CC110_REG_1D_AGCCTRL0 0x1d
#define RH_CC110_HYST_LEVEL 0xc0
#define RH_CC110_HYST_LEVEL_NONE 0x00
#define RH_CC110_HYST_LEVEL_LOW 0x40
#define RH_CC110_HYST_LEVEL_MEDIUM 0x80
#define RH_CC110_HYST_LEVEL_HIGH 0xc0
#define RH_CC110_WAIT_TIME 0x30
#define RH_CC110_WAIT_TIME_8 0x00
#define RH_CC110_WAIT_TIME_16 0x10
#define RH_CC110_WAIT_TIME_24 0x20
#define RH_CC110_WAIT_TIME_32 0x30
#define RH_CC110_AGC_FREEZE 0x0c
#define RH_CC110_AGC_FILTER_LENGTH 0x03
#define RH_CC110_AGC_FILTER_LENGTH_8 0x00
#define RH_CC110_AGC_FILTER_LENGTH_16 0x01
#define RH_CC110_AGC_FILTER_LENGTH_32 0x02
#define RH_CC110_AGC_FILTER_LENGTH_64 0x03
// #define RH_CC110_REG_1E_WOREVT1 0x1e
// #define RH_CC110_REG_1F_WOREVT0 0x1f
// #define RH_CC110_REG_20_WORCTRL 0x20
// #define RH_CC110_REG_21_FREND1 0x21
#define RH_CC110_LNA_CURRENT 0xc0
#define RH_CC110_LNA2MIX_CURRENT 0x30
#define RH_CC110_LODIV_BUF_CURRENT_RX 0x0c
#define RH_CC110_MIX_CURRENT 0x03
// #define RH_CC110_REG_22_FREND0 0x22
#define RH_CC110_LODIV_BUF_CURRENT_TX 0x30
#define RH_CC110_PA_POWER 0x07
// #define RH_CC110_REG_23_FSCAL3 0x23
#define RH_CC110_FSCAL3_7_6 0xc0
#define RH_CC110_CHP_CURR_CAL_EN 0x30
#define RH_CC110_FSCAL3_3_0 0x0f
// #define RH_CC110_REG_24_FSCAL2 0x24
#define RH_CC110_VCO_CORE_H_EN 0x20
#define RH_CC110_FSCAL2 0x1f
// #define RH_CC110_REG_25_FSCAL1 0x25
#define RH_CC110_FSCAL1 0x3f
// #define RH_CC110_REG_26_FSCAL0 0x26
#define RH_CC110_FSCAL0 0x7f
// #define RH_CC110_REG_27_RCCTRL1 0x28
// #define RH_CC110_REG_28_RCCTRL0 0x29
// #define RH_CC110_REG_29_FSTEST 0x2a
// #define RH_CC110_REG_2A_PTEST 0x2b
// #define RH_CC110_REG_2B_AGCTEST 0x2c
// #define RH_CC110_REG_2C_TEST2 0x2c
// #define RH_CC110_REG_2D_TEST1 0x2d
// #define RH_CC110_REG_2E_TEST0 0x2e
#define RH_CC110_TEST0_7_2 0xfc
#define RH_CC110_VCO_SEL_CAL_EN 0x02
#define RH_CC110_TEST0_0 0x01
// #define RH_CC110_REG_30_PARTNUM 0x30
// #define RH_CC110_REG_31_VERSION 0x31
// #define RH_CC110_REG_32_FREQEST 0x32
// #define RH_CC110_REG_33_CRC_REG 0x33
#define RH_CC110_CRC_REG_CRC_OK 0x80
// #define RH_CC110_REG_34_RSSI 0x34
// #define RH_CC110_REG_35_MARCSTATE 0x35
#define RH_CC110_MARC_STATE 0x1f
#define RH_CC110_MARC_STATE_SLEEP 0x00
#define RH_CC110_MARC_STATE_IDLE 0x01
#define RH_CC110_MARC_STATE_XOFF 0x02
#define RH_CC110_MARC_STATE_VCOON_MC 0x03
#define RH_CC110_MARC_STATE_REGON_MC 0x04
#define RH_CC110_MARC_STATE_MANCAL 0x05
#define RH_CC110_MARC_STATE_VCOON 0x06
#define RH_CC110_MARC_STATE_REGON 0x07
#define RH_CC110_MARC_STATE_STARTCAL 0x08
#define RH_CC110_MARC_STATE_BWBOOST 0x09
#define RH_CC110_MARC_STATE_FS_LOCK 0x0a
#define RH_CC110_MARC_STATE_IFADCON 0x0b
#define RH_CC110_MARC_STATE_ENDCAL 0x0c
#define RH_CC110_MARC_STATE_RX 0x0d
#define RH_CC110_MARC_STATE_RX_END 0x0e
#define RH_CC110_MARC_STATE_RX_RST 0x0f
#define RH_CC110_MARC_STATE_TXRX_SWITCH 0x10
#define RH_CC110_MARC_STATE_RXFIFO_OVERFLOW 0x11
#define RH_CC110_MARC_STATE_FSTXON 0x12
#define RH_CC110_MARC_STATE_TX 0x13
#define RH_CC110_MARC_STATE_TX_END 0x14
#define RH_CC110_MARC_STATE_RXTX_SWITCH 0x15
#define RH_CC110_MARC_STATE_TXFIFO_UNDERFLOW 0x16
// #define RH_CC110_REG_38_PKTSTATUS 0x38
#define RH_CC110_PKTSTATUS_CRC_OK 0x80
#define RH_CC110_PKTSTATUS_CS 0x40
#define RH_CC110_PKTSTATUS_CCA 0x10
#define RH_CC110_PKTSTATUS_SFD 0x08
#define RH_CC110_PKTSTATUS_GDO2 0x04
#define RH_CC110_PKTSTATUS_GDO0 0x01
// #define RH_CC110_REG_3A_TXBYTES 0x3a
#define RH_CC110_TXFIFO_UNDERFLOW 0x80
#define RH_CC110_NUM_TXBYTES 0x7f
// #define RH_CC110_REG_3B_RXBYTES 0x3b
#define RH_CC110_RXFIFO_UNDERFLOW 0x80
#define RH_CC110_NUM_RXBYTES 0x7f
/////////////////////////////////////////////////////////////////////
/// \class RH_CC110 RH_CC110.h <RH_CC110.h>
/// \brief Send and receive addressed, reliable, acknowledged datagrams by Texas Instruments CC110L and compatible transceivers and modules.
///
/// The TI CC110L is a low cost tranceiver chip capable of 300 to 928MHz and with a wide range of modulation types and speeds.
/// The chip is typically provided on a module that also includes the antenna and coupling hardware
/// and is therefore capable of a more restricted frequency range.
///
/// Supported modules include:
/// - Anaren AIR BoosterPack 430BOOST-CC110L
///
/// This base class provides basic functions for sending and receiving unaddressed, unreliable datagrams
/// of arbitrary length to 59 octets per packet at a selected data rate and modulation type.
/// Use one of the Manager classes to get addressing and
/// acknowledgement reliability, routing, meshes etc.
///
/// Naturally, for any 2 radios to communicate that must be configured to use the same frequency and
/// data rate, and with identical network addresses.
///
/// Several CC110L modules can be connected to an Arduino, permitting the construction of translators
/// and frequency changers, etc.
///
/// Several GFSK modulation schemes are provided and may be selected by calling setModemConfig(). No FSK or OOK
/// modulation schemes are provided though the implementor may configure the mnodem characteristics directly
/// by calling setModemRegisters().
///
/// Implementation based on:
/// http://www.ti.com/lit/ds/symlink/cc110l.pdf
/// and
/// https://www.anaren.com/air/cc110l-air-module-boosterpack-embedded-antenna-module-anaren
///
/// \par Crystal Frequency
///
/// Modules based on the CC110L may contain a crystal oscillator with one of 2 possible frequencies: 26MHz or 27MHz.
/// A number of radio configuration parameters (including carrier frequency and data rates) depend on the
/// crystal oscillator frequency. The chip has no knowledge of the frequency, so it is up to the implementer
/// to tell the driver the oscillator frequency by passing in the appropriate value of is27MHz to the constructor (default 26MHz)
/// or by calling setIs27MHz() before calling init().
/// Failure to correctly set this flag will cause incorrect frequency and modulation
/// characteristics to be used.
///
/// Caution: it is not easy to determine what the actual crystal frequency is on some modules. For example,
/// the documentation for the Anaren BoosterPack indictes a 26MHz crystal, but measurements on the devices delivered here
/// indicate a 27MHz crystal is actually installed. TI recommend 27MHz for
///
/// \par Packet Format
///
/// - 2 octets sync (a configurable network address)
/// - 1 octet message length
/// - 4 to 63 octets of payload consisting of:
/// - 1 octet TO header
/// - 1 octet FROM header
/// - 1 octet ID header
/// - 1 octet FLAGS header
/// - 0 to 59 octets of user message
/// - 2 octets CRC
///
/// \par Connecting CC110L to Arduino
///
/// Warning: the CC110L is a 3.3V part, and exposing it to 5V on any pin will damage it. Ensure you are using a 3.3V
/// MCU or use level shifters. We tested with Teensy 3.1.
///
/// The electrical connection between a CC110L module and the Arduino or other processor
/// require 3.3V, the 3 x SPI pins (SCK, SDI, SDO),
/// a Chip Select pin and an Interrupt pin.
/// Examples below assume the Anaren BoosterPack. Caution: the pin numbering on the Anaren BoosterPack
/// is a bit counter-intuitive: the direction of number on J1 is the reverse of J2. Check the pin numbers
/// stencilied on the front of the board to be sure.
///
/// \code
/// Teensy 3.1 CC110L pin name Anaren BoosterPack pin
/// 3.3V---------VDD (3.3V in) J1-1
/// SS pin D10----------CSn (chip select in) J2-8
/// SCK pin D13----------SCLK (SPI clock in) J1-7
/// MOSI pin D11----------MOSI (SPI data in) J2-5
/// MISO pin D12----------MISO (SPI data out) J2-4
/// D2-----------GDO0 (Interrupt output) J2-9
/// GND----------GND (ground in) J2-10
/// \endcode
/// and use the default RH_CC110 constructor. You can use other pins by passing the appropriate arguments
/// to the RH_CC110 constructor, depending on what your MCU supports.
///
/// For the Particle Photon:
/// \code
/// Photon CC110L pin name Anaren BoosterPack pin
/// 3.3V---------VDD (3.3V in) J1-1
/// SS pin A2-----------CSn (chip select in) J2-8
/// SCK pin A3-----------SCLK (SPI clock in) J1-7
/// MOSI pin A5-----------MOSI (SPI data in) J2-5
/// MISO pin A4-----------MISO (SPI data out) J2-4
/// D2-----------GDO0 (Interrupt output) J2-9
/// GND----------GND (ground in) J2-10
/// \endcode
/// and use the default RH_CC110 constructor. You can use other pins by passing the appropriate arguments
/// to the RH_CC110 constructor, depending on what your MCU supports.
///
/// \par Example programs
///
/// Several example programs are provided.
///
/// \par Radio operating strategy and defaults
///
/// The radio is enabled at all times and switched between RX, TX and IDLE modes.
/// When RX is enabled (by calling available() or setModeRx()) the radio will stay in RX mode until a
/// valid CRC correct message addressed to thiis node is received, when it will transition to IDLE.
/// When TX is enabled (by calling send()) it will stay in TX mode until the message has ben sent
/// and waitPacketSent() is called when it wil transition to IDLE
///(this radio has no 'packet sent' interrupt that could be used, so polling
/// with waitPacketSent() is required
///
/// The modulation schemes supported include the GFSK schemes provided by default in the TI SmartRF Suite.
/// This software allows you to get the correct register values for diferent modulation schemes. All the modulation
/// schemes prvided in the driver are based on the recommended register values given by SmartRF.
/// Other schemes such a 2-FSK, 4-FSK and OOK are suported by the chip, but canned configurations are not provided with this driver.
/// The implementer may choose to create their own modem configurations and pass them to setModemRegisters().
///
class RH_CC110 : public RHNRFSPIDriver
{
public:
/// \brief Defines register configuration values for a desired modulation
///
/// Defines values for various configuration fields and registers to
/// achieve a desired modulation speed and frequency deviation.
typedef struct
{
uint8_t reg_0b; ///< RH_CC110_REG_0B_FSCTRL1
uint8_t reg_0c; ///< RH_CC110_REG_0C_FSCTRL0
uint8_t reg_10; ///< RH_CC110_REG_10_MDMCFG4
uint8_t reg_11; ///< RH_CC110_REG_11_MDMCFG3
uint8_t reg_12; ///< RH_CC110_REG_12_MDMCFG2
uint8_t reg_15; ///< RH_CC110_REG_15_DEVIATN
uint8_t reg_19; ///< RH_CC110_REG_19_FOCCFG
uint8_t reg_1a; ///< RH_CC110_REG_1A_BSCFG
uint8_t reg_1b; ///< RH_CC110_REG_1B_AGCCTRL2
uint8_t reg_1c; ///< RH_CC110_REG_1C_AGCCTRL1
uint8_t reg_1d; ///< RH_CC110_REG_1D_AGCCTRL0
uint8_t reg_21; ///< RH_CC110_REG_21_FREND1
uint8_t reg_22; ///< RH_CC110_REG_22_FREND0
uint8_t reg_23; ///< RH_CC110_REG_23_FSCAL3
uint8_t reg_24; ///< RH_CC110_REG_24_FSCAL2
uint8_t reg_25; ///< RH_CC110_REG_25_FSCAL1
uint8_t reg_26; ///< RH_CC110_REG_26_FSCAL0
uint8_t reg_2c; ///< RH_CC110_REG_2C_TEST2
uint8_t reg_2d; ///< RH_CC110_REG_2D_TEST1
uint8_t reg_2e; ///< RH_CC110_REG_2E_TEST0
} ModemConfig;
/// Choices for setModemConfig() for a selected subset of common modulation types,
/// and data rates. If you need another configuration, use the register calculator.
/// and call setModemRegisters() with your desired settings.
/// 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).
/// All configs use SYNC_MODE = RH_CC110_SYNC_MODE_16_16 (2 byte sync)
typedef enum
{
GFSK_Rb1_2Fd5_2 = 0, ///< GFSK, Data Rate: 1.2kBaud, Dev: 5.2kHz, RX BW 58kHz, optimised for sensitivity
GFSK_Rb2_4Fd5_2, ///< GFSK, Data Rate: 2.4kBaud, Dev: 5.2kHz, RX BW 58kHz, optimised for sensitivity
GFSK_Rb4_8Fd25_4, ///< GFSK, Data Rate: 4.8kBaud, Dev: 25.4kHz, RX BW 100kHz, optimised for sensitivity
GFSK_Rb10Fd19, ///< GFSK, Data Rate: 10kBaud, Dev: 19kHz, RX BW 100kHz, optimised for sensitivity
GFSK_Rb38_4Fd20, ///< GFSK, Data Rate: 38.4kBaud, Dev: 20kHz, RX BW 100kHz, optimised for sensitivity
GFSK_Rb76_8Fd32, ///< GFSK, Data Rate: 76.8kBaud, Dev: 32kHz, RX BW 232kHz, optimised for sensitivity
GFSK_Rb100Fd47, ///< GFSK, Data Rate: 100kBaud, Dev: 47kHz, RX BW 325kHz, optimised for sensitivity
GFSK_Rb250Fd127, ///< GFSK, Data Rate: 250kBaud, Dev: 127kHz, RX BW 540kHz, optimised for sensitivity
} ModemConfigChoice;
/// These power outputs are based on the suggested optimum values for
/// multilayer inductors in the 915MHz frequency band. Per table 5-15.
/// Caution: these enum values are indexes into PaPowerValues.
/// Do not change one without changing the other. Use the symbolic names, not the integer values
typedef enum
{
TransmitPowerM30dBm = 0, ///< -30dBm
TransmitPowerM20dBm, ///< -20dBm
TransmitPowerM15dBm, ///< -15dBm
TransmitPowerM10dBm, ///< -10dBm
TransmitPower0dBm, ///< 0dBm
TransmitPower5dBm, ///< 5dBm
TransmitPower7dBm, ///< 7dBm
TransmitPower10dBm, ///< 10dBm
} TransmitPower;
/// 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 CC110L 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 CC110L GDO0 interrupt line.
/// Defaults to pin 2.
/// 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 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] is27MHz Set to true if your CC110 is equipped with a 27MHz crystal oscillator. Defaults to false.
/// \param[in] spi Pointer to the SPI interface object to use.
/// Defaults to the standard Arduino hardware SPI interface
RH_CC110(uint8_t slaveSelectPin = SS, uint8_t interruptPin = 2, bool is27MHz = false, RHGenericSPI& spi = hardware_spi);
/// Initialise the Driver transport hardware and software.
/// Make sure the Driver is properly configured before calling init().
/// In particular, ensure you have called setIs27MHz(true) if your module has a 27MHz crystal oscillator.
/// After init(), the following default characteristics are set:
/// TxPower: TransmitPower5dBm
/// Frequency: 915.0
/// Modulation: GFSK_Rb1_2Fd5_2 (GFSK, Data Rate: 1.2kBaud, Dev: 5.2kHz, RX BW 58kHz, optimised for sensitivity)
/// Sync Words: 0xd3, 0x91
/// \return true if initialisation succeeded.
virtual bool init();
/// 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();
/// Blocks until the current message (if any)
/// has been transmitted
/// \return true on success, false if the chip is not in transmit mode or other transmit failure
virtual bool waitPacketSent();
/// 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 will be returned to RHModeIdle
/// and available() will return true.
/// 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 (after wiaint gor any currenly transmitting message to complete).
/// 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. The message cannot be retreived again.
virtual bool recv(uint8_t* buf, uint8_t* len);
/// Waits until any previous transmit packet is finished being transmitted with waitPacketSent().
/// Then loads a message into the transmitter and starts the transmitter. Note that a message length
/// of 0 is permitted.
/// \param[in] data Array of data to be sent
/// \param[in] len Number of bytes of data to send
/// \return true if the message length was valid and it was correctly queued for transmit
virtual bool send(const uint8_t* data, uint8_t len);
/// Returns the maximum message length
/// available in this Driver.
/// \return The maximum legal message length
virtual uint8_t maxMessageLength();
/// If current mode is Sleep, 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. The radio will stay in Rx mode until a CRC correct message addressed to this node
/// is received, or the ode is changed to Tx, Idle or Sleep.
void setModeRx();
/// If current mode is Rx or Idle, changes it to Tx.
/// Starts the transmitter sending the current message.
void setModeTx();
/// Sets the radio into low-power sleep mode.
/// If successful, the transport will stay in sleep mode until woken by
/// changing mode to 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.
/// Caution: waking up from sleep loses values from registers 0x29 through 0x2e
/// \return true if sleep mode was successfully entered.
virtual bool sleep();
/// Set the Power Amplifier power setting.
/// The PaTable settings are based on are based on the suggested optimum values for
/// multilayer inductors in the 915MHz frequency band. Per table 5-15.
/// If these values are not suitable, use setPaTable() directly.
/// Caution: be a good neighbour and use the lowest power setting compatible with your application.
/// Caution: Permissable power settings for your area may depend on frequency and modulation characteristics:
/// consult local authorities.
/// param[in] power One of TransmitPower enum values
bool setTxPower(TransmitPower power);
/// Indicates the presence of 27MHz crystal oscillator.
/// You must indicate to the driver if your CC110L is equipped with a 27MHz crystal oscillator (26MHz is the default
/// in the constructor).
/// This should be called before calling init() if you have a 27MHz crystal.
/// It can be called after calling init() but you must reset the frequency (with setFrequency()) and modulation
/// (with setModemConfig()) afterwards.
/// \param[in] is27MHz Pass true if the CC110L has a 27MHz crystal (default is true).
void setIs27MHz(bool is27MHz = true);
/// Sets the transmitter and receiver
/// centre frequency.
/// Caution: permissable frequency bands will depend on you country and area: consult local authorities.
/// \param[in] centre Frequency in MHz. 300.0 to 928.0
/// \return true if the selected frquency centre is within range
bool setFrequency(float centre);
/// Sets all the registers required to configure the data modem in the CC110, including the data rate,
/// bandwidths etc. You cas use this to configure the modem with custom configuraitons 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.
/// \param[in] index The configuration choice.
/// \return true if index is a valid choice.
bool setModemConfig(ModemConfigChoice index);
/// Sets the sync words for transmit and receive in registers RH_CC110_REG_04_SYNC1 and RH_CC110_REG_05_SYNC0.
/// Caution: SyncWords should be set to the same
/// value on all nodes in your network. Nodes with different SyncWords set will never receive
/// each others messages, so different SyncWords can be used to isolate different
/// networks from each other. Default is { 0xd3, 0x91 }.
/// \param[in] syncWords Array of sync words, 2 octets long
/// \param[in] len Number of sync words to set. MUST be 2.
void setSyncWords(const uint8_t* syncWords, uint8_t len);
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.
void handleInterrupt();
/// Reads a single register from the CC110L
/// \param[in] reg Register number, one of RH_CC110_REG
/// \return The value of the register
uint8_t spiReadRegister(uint8_t reg);
/// Reads a single register in burst mode.
/// On the CC110L, some registers yield different data when read in burst mode
/// as opposed to single byte mode.
/// \param[in] reg Register number, one of RH_CC110_REG (burst mode readable)
/// \return The value of the register after a burst read
uint8_t spiBurstReadRegister(uint8_t reg);
/// Writes to a single single register on the CC110L
/// \param[in] reg Register number, one of RH_CC110L_REG_*
/// \param[in] val The value to write
/// \return returns the chip status byte per table 5.2
uint8_t spiWriteRegister(uint8_t reg, uint8_t val);
/// Write a number of bytes to a burst capable register
/// \param[in] reg Register number of the first register, one of RH_CC110L_REG_*
/// \param[in] src Array of new register values to write. Must be at least len bytes
/// \param[in] len Number of bytes to write
/// \return the chip status byte per table 5.2
uint8_t spiBurstWriteRegister(uint8_t reg, const uint8_t* src, uint8_t len);
/// Examine the receive buffer to determine whether the message is for this node
/// Sets _rxBufValid.
void validateRxBuf();
/// Clear our local receive buffer
void clearRxBuf();
/// Reads and returns the status byte by issuing the SNOP strobe
/// \return The value of the status byte per Table 5-2
uint8_t statusRead();
/// Sets the PaTable registers directly.
/// Ensure you use suitable PATABLE values per Tbale 5-15 or 5-16
/// You may need to do this to implement an OOK modulation scheme.
void setPaTable(uint8_t* patable, uint8_t patablesize);
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_CC110* _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;
/// Number of octets in the buffer
volatile uint8_t _bufLen;
/// The receiver/transmitter buffer
uint8_t _buf[RH_CC110_MAX_PAYLOAD_LEN];
/// True when there is a valid message in the buffer
volatile bool _rxBufValid;
/// True if crystal oscillator is 26 MHz, not 26MHz.
bool _is27MHz;
};
/// @example cc110_client.pde
/// @example cc110_server.pde
#endif

564
src/RH_MRF89.cpp Normal file
View File

@@ -0,0 +1,564 @@
// RH_MRF89.cpp
//
// Copyright (C) 2015 Mike McCauley
// $Id: RH_MRF89.cpp,v 1.7 2015/12/31 04:23:12 mikem Exp $
#include <RH_MRF89.h>
#define BAND_915
#define DATA_RATE_200
#define LNA_GAIN LNA_GAIN_0_DB
#define TX_POWER TX_POWER_13_DB
// Interrupt vectors for the 3 Arduino interrupt pins
// Each interrupt can be handled by a different instance of RH_MRF89, allowing you to have
// 2 or more LORAs per Arduino
RH_MRF89* RH_MRF89::_deviceForInterrupt[RH_MRF89_NUM_INTERRUPTS] = {0, 0, 0};
uint8_t RH_MRF89::_interruptCount = 0; // Index into _deviceForInterrupt for next device
// These are indexed by the values of ModemConfigChoice
// Values based on sample modulation values from MRF89XA.h
// TXIPOLFV set to be more than Fd
PROGMEM static const RH_MRF89::ModemConfig MODEM_CONFIG_TABLE[] =
{
// MODSEL, FDVAL, BRVAL, FILCREG=(PASFILV|BUTFILV), TXIPOLFV
// FSK, No Manchester, Whitening
{ RH_MRF89_MODSEL_FSK, 0x0B, 0x63, 0x40 | 0x01, 0x20 }, // FSK_Rb2Fd33
{ RH_MRF89_MODSEL_FSK, 0x0B, 0x27, 0x40 | 0x01, 0x20 }, // FSK_Rb5Fd33
{ RH_MRF89_MODSEL_FSK, 0x0B, 0x13, 0x40 | 0x01, 0x20 }, // FSK_Rb10Fd33
{ RH_MRF89_MODSEL_FSK, 0x09, 0x09, 0x70 | 0x02, 0x20 }, // FSK_Rb20Fd40
{ RH_MRF89_MODSEL_FSK, 0x04, 0x04, 0xB0 | 0x05, 0x40 }, // FSK_Rb40Fd80
{ RH_MRF89_MODSEL_FSK, 0x03, 0x03, 0xD0 | 0x06, 0x40 }, // FSK_Rb50Fd100
{ RH_MRF89_MODSEL_FSK, 0x02, 0x02, 0xE0 | 0x09, 0x60 }, // FSK_Rb66Fd133
{ RH_MRF89_MODSEL_FSK, 0x01, 0x01, 0xF0 | 0x0F, 0x80 }, // FSK_Rb100Fd200
{ RH_MRF89_MODSEL_FSK, 0x01, 0x00, 0xF0 | 0x0F, 0x80 } // FSK_Rb200Fd200
};
RH_MRF89::RH_MRF89(uint8_t csconPin, uint8_t csdatPin, uint8_t interruptPin, RHGenericSPI& spi)
:
RHNRFSPIDriver(csconPin, spi),
_csconPin(csconPin),
_csdatPin(csdatPin),
_interruptPin(interruptPin)
{
_myInterruptIndex = 0xff; // Not allocated yet
}
bool RH_MRF89::init()
{
// MRF89 data cant handle SPI greater than 1MHz.
// Sigh on teensy at 1MHz, need special delay after writes, see RHNRFSPIDriver::spiWrite
_spi.setFrequency(RHGenericSPI::Frequency1MHz);
if (!RHNRFSPIDriver::init())
return false;
// Initialise the chip select pins
pinMode(_csconPin, OUTPUT);
digitalWrite(_csconPin, HIGH);
pinMode(_csdatPin, OUTPUT);
digitalWrite(_csdatPin, HIGH);
// 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
// Make sure we are not in some unexpected mode from a previous run
setOpMode(RH_MRF89_CMOD_STANDBY);
// No way to check the device type but lets trivially check there is something there
// by trying to change a register:
spiWriteRegister(RH_MRF89_REG_02_FDEVREG, 0xaa);
if (spiReadRegister(RH_MRF89_REG_02_FDEVREG) != 0xaa)
return false;
spiWriteRegister(RH_MRF89_REG_02_FDEVREG, 0x3); // Back to the default for FDEV
if (spiReadRegister(RH_MRF89_REG_02_FDEVREG) != 0x3)
return false;
// 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);
// 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 actually the
// interrupt number. You have to figure out the interruptnumber-to-interruptpin mapping
// yourself based on knowledge of what Arduino board you are running on.
if (_myInterruptIndex == 0xff)
{
// First run, no interrupt allocated yet
if (_interruptCount <= RH_MRF89_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
// When used with the MRF89XAM9A module, per 75017B.pdf section 1.3, need:
// crystal freq = 12.8MHz
// clock output disabled
// frequency bands 902-915 or 915-928
// VCOT 60mV
// OOK max 28kbps
// Based on 70622C.pdf, section 3.12:
spiWriteRegister(RH_MRF89_REG_00_GCONREG, RH_MRF89_CMOD_STANDBY | RH_MRF89_FBS_950_960 | RH_MRF89_VCOT_60MV);
spiWriteRegister(RH_MRF89_REG_01_DMODREG, RH_MRF89_MODSEL_FSK | RH_MRF89_OPMODE_PACKET); // FSK, Packet mode, LNA 0dB
spiWriteRegister(RH_MRF89_REG_02_FDEVREG, 0); // Set by setModemConfig
spiWriteRegister(RH_MRF89_REG_03_BRSREG, 0); // Set by setModemConfig
spiWriteRegister(RH_MRF89_REG_04_FLTHREG, 0); // Set by setModemConfig (OOK only)
spiWriteRegister(RH_MRF89_REG_05_FIFOCREG, RH_MRF89_FSIZE_64);
spiWriteRegister(RH_MRF89_REG_06_R1CREG, 0); // Set by setFrequency
spiWriteRegister(RH_MRF89_REG_07_P1CREG, 0); // Set by setFrequency
spiWriteRegister(RH_MRF89_REG_08_S1CREG, 0); // Set by setFrequency
spiWriteRegister(RH_MRF89_REG_09_R2CREG, 0); // Frequency set 2 not used
spiWriteRegister(RH_MRF89_REG_0A_P2CREG, 0); // Frequency set 2 not used
spiWriteRegister(RH_MRF89_REG_0B_S2CREG, 0); // Frequency set 2 not used
spiWriteRegister(RH_MRF89_REG_0C_PACREG, RH_MRF89_PARC_23);
// IRQ0 rx mode: SYNC (not used)
// IRQ1 rx mode: CRCOK
// IRQ1 tx mode: TXDONE
spiWriteRegister(RH_MRF89_REG_0D_FTXRXIREG, RH_MRF89_IRQ0RXS_PACKET_SYNC | RH_MRF89_IRQ1RXS_PACKET_CRCOK | RH_MRF89_IRQ1TX);
spiWriteRegister(RH_MRF89_REG_0E_FTPRIREG, RH_MRF89_LENPLL);
spiWriteRegister(RH_MRF89_REG_0F_RSTHIREG, 0x00); // default not used if no RSSI interrupts
spiWriteRegister(RH_MRF89_REG_10_FILCREG, 0); // Set by setModemConfig
spiWriteRegister(RH_MRF89_REG_11_PFCREG, 0x38);// 100kHz, recommended, but not used, see RH_MRF89_REG_12_SYNCREG OOK only?
spiWriteRegister(RH_MRF89_REG_12_SYNCREG, RH_MRF89_SYNCREN | RH_MRF89_SYNCWSZ_32); // No polyphase, no bsync, sync, 0 errors
spiWriteRegister(RH_MRF89_REG_13_RSVREG, 0x07);//default
// spiWriteRegister(RH_MRF89_REG_14_RSTSREG, 0x00); // NO, read only
spiWriteRegister(RH_MRF89_REG_15_OOKCREG, 0x00); // Set by setModemConfig OOK only
spiWriteRegister(RH_MRF89_REG_16_SYNCV31REG, 0x69); // Set by setSyncWords
spiWriteRegister(RH_MRF89_REG_17_SYNCV23REG, 0x81); // Set by setSyncWords
spiWriteRegister(RH_MRF89_REG_18_SYNCV15REG, 0x7E); // Set by setSyncWords
spiWriteRegister(RH_MRF89_REG_19_SYNCV07REG, 0x96); // Set by setSyncWords
// TXIPOLFV set by setModemConfig. power set by setTxPower
spiWriteRegister(RH_MRF89_REG_1A_TXCONREG, 0xf0 | RH_MRF89_TXOPVAL_13DBM); // TX cutoff freq=375kHz,
spiWriteRegister(RH_MRF89_REG_1B_CLKOREG, 0x00); // Disable clock output to save power
spiWriteRegister(RH_MRF89_REG_1C_PLOADREG, 0x40); // payload=64bytes (no RX-filtering on packet length)
spiWriteRegister(RH_MRF89_REG_1D_NADDSREG, 0x00); // Node Address (0=default) Not used
spiWriteRegister(RH_MRF89_REG_1E_PKTCREG, RH_MRF89_PKTLENF | RH_MRF89_PRESIZE_4 | RH_MRF89_WHITEON | RH_MRF89_CHKCRCEN | RH_MRF89_ADDFIL_OFF);
spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, 0x00); // default (FIFO access in standby=write, clear FIFO on CRC mismatch)
// Looking OK now
// Set some suitable defaults:
setPreambleLength(3); // The default
uint8_t syncwords[] = { 0x69, 0x81, 0x7e, 0x96 }; // Same as RH_MRF89XA
setSyncWords(syncwords, sizeof(syncwords));
setTxPower(RH_MRF89_TXOPVAL_1DBM);
if (!setFrequency(915.4))
return false;
// Some slow, reliable default speed and modulation
if (!setModemConfig(FSK_Rb20Fd40))
return false;
return true;
}
bool RH_MRF89::printRegisters()
{
#ifdef RH_HAVE_SERIAL
uint8_t i;
for (i = 0; i <= 0x1f; i++)
{
Serial.print(i, HEX);
Serial.print(": ");
Serial.println(spiReadRegister(i), HEX);
}
#endif
return true;
}
// C++ level interrupt handler for this instance
// MRF89XA is unusual in that it has 2 interrupt lines, and not a single, combined one.
// Only one of the several interrupt lines (IRQ1) from the RFM95 needs to be
// connnected to the processor.
// We use this to get CRCOK and TXDONE interrupts
void RH_MRF89::handleInterrupt()
{
// Serial.println("I");
if (_mode == RHModeTx)
{
// Serial.println("T");
// TXDONE
// Transmit is complete
_txGood++;
setModeIdle();
}
else if (_mode == RHModeRx)
{
// Serial.println("R");
// CRCOK
// We have received a packet.
// First byte in FIFO is packet length
// REVISIT: Capture last rssi from RSTSREG
// based roughly on Figure 3-9
_lastRssi = (spiReadRegister(RH_MRF89_REG_14_RSTSREG) >> 1) - 120;
_bufLen = spiReadData();
if (_bufLen < 4)
{
// Drain the FIFO
uint8_t i;
for (i = 0; spiReadRegister(RH_MRF89_REG_0D_FTXRXIREG) & RH_MRF89_FIFOEMPTY; i++)
spiReadData();
clearRxBuf();
return;
}
// Now drain all the data from the FIFO into _buf
uint8_t i;
for (i = 0; spiReadRegister(RH_MRF89_REG_0D_FTXRXIREG) & RH_MRF89_FIFOEMPTY; i++)
_buf[i] = spiReadData();
// All good. See if its for us
validateRxBuf();
if (_rxBufValid)
setModeIdle(); // Got one
}
}
// These are low level functions that call the interrupt handler for the correct
// instance of RH_MRF89.
// 3 interrupts allows us to have 3 different devices
void RH_MRF89::isr0()
{
if (_deviceForInterrupt[0])
_deviceForInterrupt[0]->handleInterrupt();
}
void RH_MRF89::isr1()
{
if (_deviceForInterrupt[1])
_deviceForInterrupt[1]->handleInterrupt();
}
void RH_MRF89::isr2()
{
if (_deviceForInterrupt[2])
_deviceForInterrupt[2]->handleInterrupt();
}
uint8_t RH_MRF89::spiReadRegister(uint8_t reg)
{
// Tell the chip we want to talk to the configuration registers
setSlaveSelectPin(_csconPin);
digitalWrite(_csdatPin, HIGH);
return spiRead(((reg & 0x1f) << 1) | RH_MRF89_SPI_READ_MASK);
}
uint8_t RH_MRF89::spiWriteRegister(uint8_t reg, uint8_t val)
{
// Tell the chip we want to talk to the configuration registers
setSlaveSelectPin(_csconPin);
digitalWrite(_csdatPin, HIGH);
// Hmmm, on teensy 3.1, needed some special behaviour in RHNRFSPIDriver::spiWrite
// because otherwise, CSCON returns high before the final clock goes low,
// which prevents the MRF89XA spi write succeeding. Clock must be low when CSCON goes high.
return spiWrite(((reg & 0x1f) << 1), val);
}
uint8_t RH_MRF89::spiWriteData(uint8_t data)
{
spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, RH_MRF89_ACFCRC); // Write to FIFO
setSlaveSelectPin(_csdatPin);
digitalWrite(_csconPin, HIGH);
return spiCommand(data);
}
uint8_t RH_MRF89::spiWriteData(const uint8_t* data, uint8_t len)
{
spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, RH_MRF89_ACFCRC); // Write to FIFO
setSlaveSelectPin(_csdatPin);
digitalWrite(_csconPin, HIGH);
uint8_t status = 0;
ATOMIC_BLOCK_START;
_spi.beginTransaction();
digitalWrite(_slaveSelectPin, LOW);
while (len--)
_spi.transfer(*data++);
digitalWrite(_slaveSelectPin, HIGH);
_spi.endTransaction();
ATOMIC_BLOCK_END;
return status;
}
uint8_t RH_MRF89::spiReadData()
{
spiWriteRegister(RH_MRF89_REG_1F_FCRCREG, RH_MRF89_ACFCRC | RH_MRF89_FRWAXS); // Read from FIFO
setSlaveSelectPin(_csdatPin);
digitalWrite(_csconPin, HIGH);
return spiCommand(0);
}
void RH_MRF89::setOpMode(uint8_t mode)
{
// REVISIT: do we need to have time delays when switching between modes?
uint8_t val = spiReadRegister(RH_MRF89_REG_00_GCONREG);
val = (val & ~RH_MRF89_CMOD) | (mode & RH_MRF89_CMOD);
spiWriteRegister(RH_MRF89_REG_00_GCONREG, val);
}
void RH_MRF89::setModeIdle()
{
if (_mode != RHModeIdle)
{
setOpMode(RH_MRF89_CMOD_STANDBY);
_mode = RHModeIdle;
}
}
bool RH_MRF89::sleep()
{
if (_mode != RHModeSleep)
{
setOpMode(RH_MRF89_CMOD_SLEEP);
_mode = RHModeSleep;
}
return true;
}
void RH_MRF89::setModeRx()
{
if (_mode != RHModeRx)
{
setOpMode(RH_MRF89_CMOD_RECEIVE);
_mode = RHModeRx;
}
}
void RH_MRF89::setModeTx()
{
if (_mode != RHModeTx)
{
setOpMode(RH_MRF89_CMOD_TRANSMIT);
_mode = RHModeTx;
}
}
void RH_MRF89::setTxPower(uint8_t power)
{
uint8_t txconreg = spiReadRegister(RH_MRF89_REG_1A_TXCONREG);
txconreg |= (power & RH_MRF89_TXOPVAL);
spiWriteRegister(RH_MRF89_REG_1A_TXCONREG, txconreg);
}
bool RH_MRF89::available()
{
if (_mode == RHModeTx)
return false;
setModeRx();
return _rxBufValid; // Will be set by the interrupt handler when a good message is received
}
bool RH_MRF89::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_MRF89_HEADER_LEN)
*len = _bufLen - RH_MRF89_HEADER_LEN;
memcpy(buf, _buf + RH_MRF89_HEADER_LEN, *len);
ATOMIC_BLOCK_END;
}
clearRxBuf(); // This message accepted and cleared
return true;
}
bool RH_MRF89::send(const uint8_t* data, uint8_t len)
{
if (len > RH_MRF89_MAX_MESSAGE_LEN)
return false;
waitPacketSent(); // Make sure we dont interrupt an outgoing message
setModeIdle();
// First octet is the length of the chip payload
// 0 length messages are transmitted but never trigger a receive!
spiWriteData(len + RH_MRF89_HEADER_LEN);
spiWriteData(_txHeaderTo);
spiWriteData(_txHeaderFrom);
spiWriteData(_txHeaderId);
spiWriteData(_txHeaderFlags);
spiWriteData(data, len);
setModeTx(); // Start transmitting
return true;
}
uint8_t RH_MRF89::maxMessageLength()
{
return RH_MRF89_MAX_MESSAGE_LEN;
}
// Check whether the latest received message is complete and uncorrupted
void RH_MRF89::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;
}
}
void RH_MRF89::clearRxBuf()
{
ATOMIC_BLOCK_START;
_rxBufValid = false;
_bufLen = 0;
ATOMIC_BLOCK_END;
}
bool RH_MRF89::verifyPLLLock()
{
// Verify PLL-lock per instructions in Note 1 section 3.12
// Need to do this after changing frequency.
uint8_t ftpriVal = spiReadRegister(RH_MRF89_REG_0E_FTPRIREG);
spiWriteRegister(RH_MRF89_REG_0E_FTPRIREG, ftpriVal | RH_MRF89_LSTSPLL); // Clear PLL lock bit
setOpMode(RH_MRF89_CMOD_FS);
unsigned long ulStartTime = millis();
while ((millis() - ulStartTime < 1000))
{
ftpriVal = spiReadRegister(RH_MRF89_REG_0E_FTPRIREG);
if ((ftpriVal & RH_MRF89_LSTSPLL) != 0)
break;
}
setOpMode(RH_MRF89_CMOD_STANDBY);
return ((ftpriVal & RH_MRF89_LSTSPLL) != 0);
}
bool RH_MRF89::setFrequency(float centre)
{
// REVISIT: FSK only: its different for OOK :-(
uint8_t FBS;
if (centre >= 902.0 && centre < 915.0)
{
FBS = RH_MRF89_FBS_902_915;
}
else if (centre >= 915.0 && centre <= 928.0)
{
FBS = RH_MRF89_FBS_915_928;
}
else if (centre >= 950.0 && centre <= 960.0)
{
// Not all modules support this frequency band:
// The MRF98XAM9A does not
FBS = RH_MRF89_FBS_950_960;
}
// else if (centre >= 863.0 && centre <= 870.0)
// {
// // Not all modules support this frequency band:
// // The MRF98XAM9A does not
// FBS = RH_MRF89_FBS_950_960; // Yes same as above
// }
else
{
// Cant do this freq
return false;
}
// Based on frequency calcs done in MRF89XA.h
// uint8_t R = 100; // Recommended
uint8_t R = 119; // Also recommended :-(
uint32_t centre_kHz = centre * 1000;
uint32_t xtal_kHz = (RH_MRF89_XTAL_FREQ * 1000);
uint32_t compare = (centre_kHz * 8 * (R + 1)) / (9 * xtal_kHz);
uint8_t P = ((compare - 75) / 76) + 1;
uint8_t S = compare - (75 * (P + 1));
// Now set the new register values:
uint8_t val = spiReadRegister(RH_MRF89_REG_00_GCONREG);
val = (val & ~RH_MRF89_FBS) | (FBS & RH_MRF89_FBS);
spiWriteRegister(RH_MRF89_REG_00_GCONREG, val);
spiWriteRegister(RH_MRF89_REG_06_R1CREG, R);
spiWriteRegister(RH_MRF89_REG_07_P1CREG, P);
spiWriteRegister(RH_MRF89_REG_08_S1CREG, S);
return verifyPLLLock();
}
// Set one of the canned FSK Modem configs
// Returns true if its a valid choice
bool RH_MRF89::setModemConfig(ModemConfigChoice index)
{
if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig)))
return false;
RH_MRF89::ModemConfig cfg;
memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(cfg));
// Now update the registers
uint8_t val = spiReadRegister(RH_MRF89_REG_01_DMODREG);
val = (val & ~RH_MRF89_MODSEL) | cfg.MODSEL;
spiWriteRegister(RH_MRF89_REG_01_DMODREG, val);
spiWriteRegister(RH_MRF89_REG_02_FDEVREG, cfg.FDVAL);
spiWriteRegister(RH_MRF89_REG_03_BRSREG, cfg.BRVAL);
spiWriteRegister(RH_MRF89_REG_10_FILCREG, cfg.FILCREG);
// The sample configs in MRF89XA.h all use TXIPOLFV = 0xf0 => 375kHz, which is too wide for most modulations
val = spiReadRegister(RH_MRF89_REG_1A_TXCONREG);
val = (val & ~RH_MRF89_TXIPOLFV) | (cfg.TXIPOLFV & RH_MRF89_TXIPOLFV);
spiWriteRegister(RH_MRF89_REG_1A_TXCONREG, val);
return true;
}
void RH_MRF89::setPreambleLength(uint8_t bytes)
{
if (bytes >= 1 && bytes <= 4)
{
bytes--;
uint8_t pktcreg = spiReadRegister(RH_MRF89_REG_1E_PKTCREG);
pktcreg = (pktcreg & ~RH_MRF89_PRESIZE) | ((bytes << 5) & RH_MRF89_PRESIZE);
spiWriteRegister(RH_MRF89_REG_1E_PKTCREG, pktcreg);
}
}
void RH_MRF89::setSyncWords(const uint8_t* syncWords, uint8_t len)
{
if (syncWords && (len > 0 and len <= 4))
{
uint8_t syncreg = spiReadRegister(RH_MRF89_REG_12_SYNCREG);
syncreg = (syncreg & ~RH_MRF89_SYNCWSZ) | (((len - 1) << 3) & RH_MRF89_SYNCWSZ);
spiWriteRegister(RH_MRF89_REG_12_SYNCREG, syncreg);
uint8_t i;
for (i = 0; i < 4; i++)
{
if (len > i)
spiWriteRegister(RH_MRF89_REG_16_SYNCV31REG + i, syncWords[i]);
}
}
}

628
src/RH_MRF89.h Normal file
View File

@@ -0,0 +1,628 @@
// RH_MRF89.h
//
// Definitions for Microchip MRF89XA family radios radios per:
// http://ww1.microchip.com/downloads/en/DeviceDoc/70622C.pdf
// http://ww1.microchip.com/downloads/en/DeviceDoc/75017B.pdf
//
// Author: Mike McCauley (mikem@airspayce.com)
// Copyright (C) 2015 Mike McCauley
// $Id: RH_MRF89.h,v 1.6 2015/12/17 10:58:13 mikem Exp $
//
#ifndef RH_RF95_h
#define RH_RF95_h
#include <RHNRFSPIDriver.h>
// This is the maximum number of interrupts the driver can support
// Most Arduinos can handle 2, Megas can handle more
#define RH_MRF89_NUM_INTERRUPTS 3
// Max number of octets the MRF89XA Rx/Tx FIFO can hold
#define RH_MRF89_FIFO_SIZE 64
// This is the maximum number of bytes that can be carried by the MRF89XA.
// We use some for headers, keeping fewer for RadioHead messages
#define RH_MRF89_MAX_PAYLOAD_LEN RH_MRF89_FIFO_SIZE
// The length of the headers we add.
// The headers are inside the MRF89XA payload
#define RH_MRF89_HEADER_LEN 4
// This is the maximum user 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 4 bytes headers, user data. Message length and CRC are automatically encoded and decoded by
// the MRF89XA
#ifndef RH_MRF89_MAX_MESSAGE_LEN
#define RH_MRF89_MAX_MESSAGE_LEN (RH_MRF89_MAX_PAYLOAD_LEN - RH_MRF89_HEADER_LEN)
#endif
// Bits that must be set to do a SPI read
#define RH_MRF89_SPI_READ_MASK 0x40
// The MRF89XA crystal frequency in MHz
#define RH_MRF89_XTAL_FREQ 12.8
// Register names from Figure 2-18
#define RH_MRF89_REG_00_GCONREG 0x00
#define RH_MRF89_REG_01_DMODREG 0x01
#define RH_MRF89_REG_02_FDEVREG 0x02
#define RH_MRF89_REG_03_BRSREG 0x03
#define RH_MRF89_REG_04_FLTHREG 0x04
#define RH_MRF89_REG_05_FIFOCREG 0x05
#define RH_MRF89_REG_06_R1CREG 0x06
#define RH_MRF89_REG_07_P1CREG 0x07
#define RH_MRF89_REG_08_S1CREG 0x08
#define RH_MRF89_REG_09_R2CREG 0x09
#define RH_MRF89_REG_0A_P2CREG 0x0a
#define RH_MRF89_REG_0B_S2CREG 0x0b
#define RH_MRF89_REG_0C_PACREG 0x0c
#define RH_MRF89_REG_0D_FTXRXIREG 0x0d
#define RH_MRF89_REG_0E_FTPRIREG 0x0e
#define RH_MRF89_REG_0F_RSTHIREG 0x0f
#define RH_MRF89_REG_10_FILCREG 0x10
#define RH_MRF89_REG_11_PFCREG 0x11
#define RH_MRF89_REG_12_SYNCREG 0x12
// Hmm the addresses of the next 2 is ambiguous in the docs
// this seems to agree with whats in the chip:
#define RH_MRF89_REG_13_RSVREG 0x13
#define RH_MRF89_REG_14_RSTSREG 0x14
#define RH_MRF89_REG_15_OOKCREG 0x15
#define RH_MRF89_REG_16_SYNCV31REG 0x16
#define RH_MRF89_REG_17_SYNCV23REG 0x17
#define RH_MRF89_REG_18_SYNCV15REG 0x18
#define RH_MRF89_REG_19_SYNCV07REG 0x19
#define RH_MRF89_REG_1A_TXCONREG 0x1a
#define RH_MRF89_REG_1B_CLKOREG 0x1b
#define RH_MRF89_REG_1C_PLOADREG 0x1c
#define RH_MRF89_REG_1D_NADDSREG 0x1d
#define RH_MRF89_REG_1E_PKTCREG 0x1e
#define RH_MRF89_REG_1F_FCRCREG 0x1f
// Register bitfield definitions
//#define RH_MRF89_REG_00_GCONREG 0x00
#define RH_MRF89_CMOD 0xe0
#define RH_MRF89_CMOD_TRANSMIT 0x80
#define RH_MRF89_CMOD_RECEIVE 0x60
#define RH_MRF89_CMOD_FS 0x40
#define RH_MRF89_CMOD_STANDBY 0x20
#define RH_MRF89_CMOD_SLEEP 0x00
#define RH_MRF89_FBS 0x18
#define RH_MRF89_FBS_950_960 0x10
#define RH_MRF89_FBS_915_928 0x08
#define RH_MRF89_FBS_902_915 0x00
#define RH_MRF89_VCOT 0x06
#define RH_MRF89_VCOT_180MV 0x06
#define RH_MRF89_VCOT_120MV 0x04
#define RH_MRF89_VCOT_60MV 0x02
#define RH_MRF89_VCOT_TANK 0x00
#define RH_MRF89_RPS 0x01
//#define RH_MRF89_REG_01_DMODREG 0x01
#define RH_MRF89_MODSEL 0xc0
#define RH_MRF89_MODSEL_FSK 0x80
#define RH_MRF89_MODSEL_OOK 0x40
#define RH_MRF89_DMODE0 0x20
#define RH_MRF89_OOKTYP 0x18
#define RH_MRF89_OOKTYP_AVERAGE 0x10
#define RH_MRF89_OOKTYP_PEAK 0x08
#define RH_MRF89_OOKTYP_FIXED 0x00
#define RH_MRF89_DMODE1 0x04
#define RH_MRF89_IFGAIN 0x03
#define RH_MRF89_IFGAIN_M13P5 0x03
#define RH_MRF89_IFGAIN_M9 0x02
#define RH_MRF89_IFGAIN_M4P5 0x01
#define RH_MRF89_IFGAIN_0 0x00
// DMODE1 and DMODE1:
#define RH_MRF89_OPMODE_CONTINUOUS 0x00
#define RH_MRF89_OPMODE_BUFFER RH_MRF89_DMODE0
#define RH_MRF89_OPMODE_PACKET RH_MRF89_DMODE1
//#define RH_MRF89_REG_03_BRSREG 0x03
#define RH_MRF89_BRVAL 0x7f
//#define RH_MRF89_REG_05_FIFOCREG 0x05
#define RH_MRF89_FSIZE 0xc0
#define RH_MRF89_FSIZE_64 0xc0
#define RH_MRF89_FSIZE_48 0x80
#define RH_MRF89_FSIZE_32 0x40
#define RH_MRF89_FSIZE_16 0x00
#define RH_MRF89_FTINT 0x3f
//#define RH_MRF89_REG_0C_PACREG 0x0c
#define RH_MRF89_PARC 0x18
#define RH_MRF89_PARC_23 0x18
#define RH_MRF89_PARC_15 0x10
#define RH_MRF89_PARC_8P5 0x08
#define RH_MRF89_PARC_3 0x00
//#define RH_MRF89_REG_0D_FTXRXIREG 0x0d
#define RH_MRF89_IRQ0RXS 0xc0
#define RH_MRF89_IRQ0RXS_CONT_RSSI 0x40
#define RH_MRF89_IRQ0RXS_CONT_SYNC 0x00
#define RH_MRF89_IRQ0RXS_BUFFER_SYNC 0xc0
#define RH_MRF89_IRQ0RXS_BUFFER_FIFOEMPTY 0x80
#define RH_MRF89_IRQ0RXS_BUFFER_WRITEBYTE 0x40
#define RH_MRF89_IRQ0RXS_BUFFER_NONE 0x00
#define RH_MRF89_IRQ0RXS_PACKET_SYNC 0xc0
#define RH_MRF89_IRQ0RXS_PACKET_FIFOEMPTY 0x80
#define RH_MRF89_IRQ0RXS_PACKET_WRITEBYTE 0x40
#define RH_MRF89_IRQ0RXS_PACKET_PLREADY 0x00
#define RH_MRF89_IRQ1RXS 0x30
#define RH_MRF89_IRQ1RXS_CONT_DCLK 0x00
#define RH_MRF89_IRQ1RXS_BUFFER_FIFO_THRESH 0x30
#define RH_MRF89_IRQ1RXS_BUFFER_RSSI 0x20
#define RH_MRF89_IRQ1RXS_BUFFER_FIFOFULL 0x10
#define RH_MRF89_IRQ1RXS_BUFFER_NONE 0x00
#define RH_MRF89_IRQ1RXS_PACKET_FIFO_THRESH 0x30
#define RH_MRF89_IRQ1RXS_PACKET_RSSI 0x20
#define RH_MRF89_IRQ1RXS_PACKET_FIFOFULL 0x10
#define RH_MRF89_IRQ1RXS_PACKET_CRCOK 0x00
#define RH_MRF89_IRQ1TX 0x08
#define RH_MRF89_FIFOFULL 0x04
#define RH_MRF89_FIFOEMPTY 0x02
#define RH_MRF89_FOVRUN 0x01
//#define RH_MRF89_REG_0E_FTPRIREG 0x0e
#define RH_MRF89_FIFOFM 0x80
#define RH_MRF89_FIFOFSC 0x40
#define RH_MRF89_TXDONE 0x20
#define RH_MRF89_IRQ0TXST 0x10
#define RH_MRF89_RIRQS 0x04
#define RH_MRF89_LSTSPLL 0x02
#define RH_MRF89_LENPLL 0x01
//#define RH_MRF89_REG_10_FILCREG 0x10
#define RH_MRF89_PASFILV 0xf0
#define RH_MRF89_PASFILV_987KHZ 0xf0
#define RH_MRF89_PASFILV_676KHZ 0xe0
#define RH_MRF89_PASFILV_514KHZ 0xd0
#define RH_MRF89_PASFILV_458KHZ 0xc0
#define RH_MRF89_PASFILV_414KHZ 0xb0
#define RH_MRF89_PASFILV_378KHZ 0xa0
#define RH_MRF89_PASFILV_321KHZ 0x90
#define RH_MRF89_PASFILV_262KHZ 0x80
#define RH_MRF89_PASFILV_234KHZ 0x70
#define RH_MRF89_PASFILV_211KHZ 0x60
#define RH_MRF89_PASFILV_184KHZ 0x50
#define RH_MRF89_PASFILV_157KHZ 0x40
#define RH_MRF89_PASFILV_137KHZ 0x30
#define RH_MRF89_PASFILV_109KHZ 0x20
#define RH_MRF89_PASFILV_82KHZ 0x10
#define RH_MRF89_PASFILV_65KHZ 0x00
#define RH_MRF89_BUTFILV 0x0f
#define RH_MRF89_BUTFILV_25KHZ 0x00
#define RH_MRF89_BUTFILV_50KHZ 0x01
#define RH_MRF89_BUTFILV_75KHZ 0x02
#define RH_MRF89_BUTFILV_100KHZ 0x03
#define RH_MRF89_BUTFILV_125KHZ 0x04
#define RH_MRF89_BUTFILV_150KHZ 0x05
#define RH_MRF89_BUTFILV_175KHZ 0x06
#define RH_MRF89_BUTFILV_200KHZ 0x07
#define RH_MRF89_BUTFILV_225KHZ 0x08
#define RH_MRF89_BUTFILV_250KHZ 0x09
#define RH_MRF89_BUTFILV_275KHZ 0x0a
#define RH_MRF89_BUTFILV_300KHZ 0x0b
#define RH_MRF89_BUTFILV_325KHZ 0x0c
#define RH_MRF89_BUTFILV_350KHZ 0x0d
#define RH_MRF89_BUTFILV_375KHZ 0x0e
#define RH_MRF89_BUTFILV_400KHZ 0x0f
//#define RH_MRF89_REG_11_PFCREG 0x11
#define RH_MRF89_POLCFV 0xf0
//#define RH_MRF89_REG_12_SYNCREG 0x12
#define RH_MRF89_POLFILEN 0x80
#define RH_MRF89_BSYNCEN 0x40
#define RH_MRF89_SYNCREN 0x20
#define RH_MRF89_SYNCWSZ 0x18
#define RH_MRF89_SYNCWSZ_32 0x18
#define RH_MRF89_SYNCWSZ_24 0x10
#define RH_MRF89_SYNCWSZ_16 0x08
#define RH_MRF89_SYNCWSZ_8 0x00
#define RH_MRF89_SYNCTEN 0x06
#define RH_MRF89_SYNCTEN_3 0x06
#define RH_MRF89_SYNCTEN_2 0x04
#define RH_MRF89_SYNCTEN_1 0x02
#define RH_MRF89_SYNCTEN_0 0x00
//#define RH_MRF89_REG_15_OOKCREG 0x15
#define RH_MRF89_OOTHSV 0xe0
#define RH_MRF89_OOTHSV_6P0DB 0xe0
#define RH_MRF89_OOTHSV_5P0DB 0xc0
#define RH_MRF89_OOTHSV_4P0DB 0xa0
#define RH_MRF89_OOTHSV_3P0DB 0x80
#define RH_MRF89_OOTHSV_2P0DB 0x60
#define RH_MRF89_OOTHSV_1P5DB 0x40
#define RH_MRF89_OOTHSV_1P0DB 0x20
#define RH_MRF89_OOTHSV_0P5DB 0x00
#define RH_MRF89_OOKTHPV 0x1c
#define RH_MRF89_OOKTHPV_16 0x1c
#define RH_MRF89_OOKTHPV_8 0x18
#define RH_MRF89_OOKTHPV_4 0x14
#define RH_MRF89_OOKTHPV_2 0x10
#define RH_MRF89_OOKTHPV_1_IN_8 0x0c
#define RH_MRF89_OOKTHPV_1_IN_4 0x08
#define RH_MRF89_OOKTHPV_1_IN_2 0x04
#define RH_MRF89_OOKTHPV_1_IN_1 0x00
#define RH_MRF89_OOKATHC 0x03
#define RH_MRF89_OOKATHC_32PI 0x03
#define RH_MRF89_OOKATHC_8PI 0x00
//#define RH_MRF89_REG_1A_TXCONREG 0x1a
#define RH_MRF89_TXIPOLFV 0xf0
#define RH_MRF89_TXOPVAL 0x0e
#define RH_MRF89_TXOPVAL_M8DBM 0x0e
#define RH_MRF89_TXOPVAL_M5DBM 0x0c
#define RH_MRF89_TXOPVAL_M2DBM 0x0a
#define RH_MRF89_TXOPVAL_1DBM 0x08
#define RH_MRF89_TXOPVAL_4DBM 0x06
#define RH_MRF89_TXOPVAL_7DBM 0x04
#define RH_MRF89_TXOPVAL_10DBM 0x02
#define RH_MRF89_TXOPVAL_13DBM 0x00
//#define RH_MRF89_REG_1B_CLKOREG 0x1b
#define RH_MRF89_CLKOCNTRL 0x80
#define RH_MRF89_CLKOFREQ 0x7c
//#define RH_MRF89_REG_1C_PLOADREG 0x1c
#define RH_MRF89_MCHSTREN 0x80
#define RH_MRF89_PLDPLEN 0x7f
//#define RH_MRF89_REG_1E_PKTCREG 0x1e
#define RH_MRF89_PKTLENF 0x80
#define RH_MRF89_PRESIZE 0x60
#define RH_MRF89_PRESIZE_4 0x60
#define RH_MRF89_PRESIZE_3 0x40
#define RH_MRF89_PRESIZE_2 0x20
#define RH_MRF89_PRESIZE_1 0x00
#define RH_MRF89_WHITEON 0x10
#define RH_MRF89_CHKCRCEN 0x08
#define RH_MRF89_ADDFIL 0x06