first commit
This commit is contained in:
5
README.md
Normal file
5
README.md
Normal 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
28
mos.yml
Normal 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
2
mos_esp32.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
cdefs:
|
||||
ESP32: 1
|
||||
2
mos_esp8266.yml
Normal file
2
mos_esp8266.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
cdefs:
|
||||
ESP8266: 1
|
||||
17
src/LICENSE
Normal file
17
src/LICENSE
Normal file
@@ -0,0 +1,17 @@
|
||||
This software is Copyright (C) 2008 Mike McCauley. Use is subject to license
|
||||
conditions. The main licensing options available are GPL V2 or Commercial:
|
||||
|
||||
Open Source Licensing GPL V2
|
||||
|
||||
This is the appropriate option if you want to share the source code of your
|
||||
application with everyone you distribute it to, and you also want to give them
|
||||
the right to share who uses it. If you wish to use this software under Open
|
||||
Source Licensing, you must contribute all your source code to the open source
|
||||
community in accordance with the GPL Version 2 when your application is
|
||||
distributed. See http://www.gnu.org/copyleft/gpl.html
|
||||
|
||||
Commercial Licensing
|
||||
|
||||
This is the appropriate option if you are creating proprietary applications
|
||||
and you are not prepared to distribute and share the source code of your
|
||||
application. Contact info@open.com.au for details.
|
||||
125
src/MANIFEST
Normal file
125
src/MANIFEST
Normal 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
104
src/RHCRC.cpp
Normal 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
19
src/RHCRC.h
Normal 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
123
src/RHDatagram.cpp
Normal 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
162
src/RHDatagram.h
Normal 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
184
src/RHGenericDriver.cpp
Normal 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
265
src/RHGenericDriver.h
Normal 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
31
src/RHGenericSPI.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
// RHGenericSPI.cpp
|
||||
// Author: Mike McCauley (mikem@airspayce.com)
|
||||
// Copyright (C) 2011 Mike McCauley
|
||||
// Contributed by Joanna Rutkowska
|
||||
// $Id: RHGenericSPI.cpp,v 1.2 2014/04/12 05:26:05 mikem Exp $
|
||||
|
||||
#include <RHGenericSPI.h>
|
||||
|
||||
RHGenericSPI::RHGenericSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode)
|
||||
:
|
||||
_frequency(frequency),
|
||||
_bitOrder(bitOrder),
|
||||
_dataMode(dataMode)
|
||||
{
|
||||
}
|
||||
|
||||
void RHGenericSPI::setBitOrder(BitOrder bitOrder)
|
||||
{
|
||||
_bitOrder = bitOrder;
|
||||
}
|
||||
|
||||
void RHGenericSPI::setDataMode(DataMode dataMode)
|
||||
{
|
||||
_dataMode = dataMode;
|
||||
}
|
||||
|
||||
void RHGenericSPI::setFrequency(Frequency frequency)
|
||||
{
|
||||
_frequency = frequency;
|
||||
}
|
||||
|
||||
146
src/RHGenericSPI.h
Normal file
146
src/RHGenericSPI.h
Normal 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
167
src/RHHardwareSP12.cpp
Normal 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
167
src/RHHardwareSP1I.cpp
Normal 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
412
src/RHHardwareSPI.cpp
Normal 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
73
src/RHHardwareSPI.h
Normal 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
76
src/RHHardwareSPI1.h
Normal 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
76
src/RHHardwareSPI2.h
Normal 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
244
src/RHMesh.cpp
Normal 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
262
src/RHMesh.h
Normal 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
113
src/RHNRFSPIDriver.cpp
Normal 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
95
src/RHNRFSPIDriver.h
Normal 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
187
src/RHReliableDatagram.cpp
Normal 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
203
src/RHReliableDatagram.h
Normal 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
306
src/RHRouter.cpp
Normal 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
328
src/RHRouter.h
Normal 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
91
src/RHSPIDriver.cpp
Normal 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
94
src/RHSPIDriver.h
Normal 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
166
src/RHSoftwareSPI.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
// SoftwareSPI.cpp
|
||||
// Author: Chris Lapa (chris@lapa.com.au)
|
||||
// Copyright (C) 2014 Chris Lapa
|
||||
// Contributed by Chris Lapa
|
||||
|
||||
#include <RHSoftwareSPI.h>
|
||||
|
||||
RHSoftwareSPI::RHSoftwareSPI(Frequency frequency, BitOrder bitOrder, DataMode dataMode)
|
||||
:
|
||||
RHGenericSPI(frequency, bitOrder, dataMode)
|
||||
{
|
||||
setPins(12, 11, 13);
|
||||
}
|
||||
|
||||
// Caution: on Arduino Uno and many other CPUs, digitalWrite is quite slow, taking about 4us
|
||||
// digitalWrite is also slow, taking about 3.5us
|
||||
// resulting in very slow SPI bus speeds using this technique, up to about 120us per octet of transfer
|
||||
uint8_t RHSoftwareSPI::transfer(uint8_t data)
|
||||
{
|
||||
uint8_t readData;
|
||||
uint8_t writeData;
|
||||
uint8_t builtReturn;
|
||||
uint8_t mask;
|
||||
|
||||
if (_bitOrder == BitOrderMSBFirst)
|
||||
{
|
||||
mask = 0x80;
|
||||
}
|
||||
else
|
||||
{
|
||||
mask = 0x01;
|
||||
}
|
||||
builtReturn = 0;
|
||||
readData = 0;
|
||||
|
||||
for (uint8_t count=0; count<8; count++)
|
||||
{
|
||||
if (data & mask)
|
||||
{
|
||||
writeData = HIGH;
|
||||
}
|
||||
else
|
||||
{
|
||||
writeData = LOW;
|
||||
}
|
||||
|
||||
if (_clockPhase == 1)
|
||||
{
|
||||
// CPHA=1, miso/mosi changing state now
|
||||
digitalWrite(_mosi, writeData);
|
||||
digitalWrite(_sck, ~_clockPolarity);
|
||||
delayPeriod();
|
||||
|
||||
// CPHA=1, miso/mosi stable now
|
||||
readData = digitalRead(_miso);
|
||||
digitalWrite(_sck, _clockPolarity);
|
||||
delayPeriod();
|
||||
}
|
||||
else
|
||||
{
|
||||
// CPHA=0, miso/mosi changing state now
|
||||
digitalWrite(_mosi, writeData);
|
||||
digitalWrite(_sck, _clockPolarity);
|
||||
delayPeriod();
|
||||
|
||||
// CPHA=0, miso/mosi stable now
|
||||
readData = digitalRead(_miso);
|
||||
digitalWrite(_sck, ~_clockPolarity);
|
||||
delayPeriod();
|
||||
}
|
||||
|
||||
if (_bitOrder == BitOrderMSBFirst)
|
||||
{
|
||||
mask >>= 1;
|
||||
builtReturn |= (readData << (7 - count));
|
||||
}
|
||||
else
|
||||
{
|
||||
mask <<= 1;
|
||||
builtReturn |= (readData << count);
|
||||
}
|
||||
}
|
||||
|
||||
digitalWrite(_sck, _clockPolarity);
|
||||
|
||||
return builtReturn;
|
||||
}
|
||||
|
||||
/// Initialise the SPI library
|
||||
void RHSoftwareSPI::begin()
|
||||
{
|
||||
if (_dataMode == DataMode0 ||
|
||||
_dataMode == DataMode1)
|
||||
{
|
||||
_clockPolarity = LOW;
|
||||
}
|
||||
else
|
||||
{
|
||||
_clockPolarity = HIGH;
|
||||
}
|
||||
|
||||
if (_dataMode == DataMode0 ||
|
||||
_dataMode == DataMode2)
|
||||
{
|
||||
_clockPhase = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_clockPhase = 1;
|
||||
}
|
||||
digitalWrite(_sck, _clockPolarity);
|
||||
|
||||
// Caution: these counts assume that digitalWrite is very fast, which is usually not true
|
||||
switch (_frequency)
|
||||
{
|
||||
case Frequency1MHz:
|
||||
_delayCounts = 8;
|
||||
break;
|
||||
|
||||
case Frequency2MHz:
|
||||
_delayCounts = 4;
|
||||
break;
|
||||
|
||||
case Frequency4MHz:
|
||||
_delayCounts = 2;
|
||||
break;
|
||||
|
||||
case Frequency8MHz:
|
||||
_delayCounts = 1;
|
||||
break;
|
||||
|
||||
case Frequency16MHz:
|
||||
_delayCounts = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Disables the SPI bus usually, in this case
|
||||
/// there is no hardware controller to disable.
|
||||
void RHSoftwareSPI::end() { }
|
||||
|
||||
/// Sets the pins used by this SoftwareSPIClass instance.
|
||||
/// \param[in] miso master in slave out pin used
|
||||
/// \param[in] mosi master out slave in pin used
|
||||
/// \param[in] sck clock pin used
|
||||
void RHSoftwareSPI::setPins(uint8_t miso, uint8_t mosi, uint8_t sck)
|
||||
{
|
||||
_miso = miso;
|
||||
_mosi = mosi;
|
||||
_sck = sck;
|
||||
|
||||
pinMode(_miso, INPUT);
|
||||
pinMode(_mosi, OUTPUT);
|
||||
pinMode(_sck, OUTPUT);
|
||||
digitalWrite(_sck, _clockPolarity);
|
||||
}
|
||||
|
||||
|
||||
void RHSoftwareSPI::delayPeriod()
|
||||
{
|
||||
for (uint8_t count = 0; count < _delayCounts; count++)
|
||||
{
|
||||
__asm__ __volatile__ ("nop");
|
||||
}
|
||||
}
|
||||
|
||||
89
src/RHSoftwareSPI.h
Normal file
89
src/RHSoftwareSPI.h
Normal 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
66
src/RHTcpProtocol.h
Normal 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
845
src/RH_ASK.cpp
Normal 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
422
src/RH_ASK.h
Normal 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
464
src/RH_CC110.cpp
Normal 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
889
src/RH_CC110.h
Normal 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
564
src/RH_MRF89.cpp
Normal 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
628
src/RH_MRF89.h
Normal 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
|
||||