Public Member Functions | Protected Member Functions | Protected Attributes | List of all members
RH_ASK Class Reference

Driver to send and receive unaddressed, unreliable datagrams via inexpensive ASK (Amplitude Shift Keying) or OOK (On Off Keying) RF transceivers. More...

#include <RH_ASK.h>

Inheritance diagram for RH_ASK:

Public Member Functions

 RH_ASK (uint16_t speed=2000, uint8_t rxPin=11, uint8_t txPin=12, uint8_t pttPin=10, bool pttInverted=false)
virtual bool init ()
virtual bool available ()
virtual bool recv (uint8_t *buf, uint8_t *len)
virtual bool send (const uint8_t *data, uint8_t len)
virtual uint8_t maxMessageLength ()
void setModeIdle ()
void setModeRx ()
void setModeTx ()
void handleTimerInterrupt ()
 dont call this it used by the interrupt handler
uint16_t speed ()
- Public Member Functions inherited from RHGenericDriver
 RHGenericDriver ()
virtual void waitAvailable ()
virtual bool waitPacketSent ()
virtual bool waitPacketSent (uint16_t timeout)
virtual bool waitAvailableTimeout (uint16_t timeout)
virtual bool waitCAD ()
void setCADTimeout (unsigned long cad_timeout)
virtual bool isChannelActive ()
virtual void setThisAddress (uint8_t thisAddress)
virtual void setHeaderTo (uint8_t to)
virtual void setHeaderFrom (uint8_t from)
virtual void setHeaderId (uint8_t id)
virtual void setHeaderFlags (uint8_t set, uint8_t clear=RH_FLAGS_APPLICATION_SPECIFIC)
virtual void setPromiscuous (bool promiscuous)
virtual uint8_t headerTo ()
virtual uint8_t headerFrom ()
virtual uint8_t headerId ()
virtual uint8_t headerFlags ()
int8_t lastRssi ()
RHMode mode ()
void setMode (RHMode mode)
 Sets the operating mode of the transport.
virtual bool sleep ()
uint16_t rxBad ()
uint16_t rxGood ()
uint16_t txGood ()

Protected Member Functions

uint8_t timerCalc (uint16_t speed, uint16_t max_ticks, uint16_t *nticks)
 Helper function for calculating timer ticks.
void timerSetup ()
 Set up the timer and its interrutps so the interrupt handler is called at the right frequency.
bool readRx ()
 Read the rxPin in a platform dependent way, taking into account whether it is inverted or not.
void writeTx (bool value)
 Write the txPin in a platform dependent way.
void writePtt (bool value)
 Write the txPin in a platform dependent way, taking into account whether it is inverted or not.
uint8_t symbol_6to4 (uint8_t symbol)
 Translates a 6 bit symbol to its 4 bit plaintext equivalent.
void receiveTimer ()
 The receiver handler function, called a 8 times the bit rate.
void transmitTimer ()
 The transmitter handler function, called a 8 times the bit rate.
void validateRxBuf ()

Protected Attributes

uint16_t _speed
 Configure bit rate in bits per second.
uint8_t _rxPin
 The configure receiver pin.
uint8_t _txPin
 The configure transmitter pin.
uint8_t _pttPin
 The configured transmitter enable pin.
bool _rxInverted
 True of the sense of the rxPin is to be inverted.
bool _pttInverted
 True of the sense of the pttPin is to be inverted.
volatile bool _rxBufFull
 Buf is filled but not validated.
volatile bool _rxBufValid
 Buf is full and valid.
volatile bool _rxLastSample
 Last digital input from the rx data pin.
volatile uint8_t _rxIntegrator
volatile uint8_t _rxPllRamp
volatile uint8_t _rxActive
volatile uint16_t _rxBits
 Last 12 bits received, so we can look for the start symbol.
volatile uint8_t _rxBitCount
 How many bits of message we have received. Ranges from 0 to 12.
uint8_t _rxBuf [RH_ASK_MAX_PAYLOAD_LEN]
 The incoming message buffer.
volatile uint8_t _rxCount
 The incoming message expected length.
volatile uint8_t _rxBufLen
 The incoming message buffer length received so far.
uint8_t _txIndex
 Index of the next symbol to send. Ranges from 0 to vw_tx_len.
uint8_t _txBit
 Bit number of next bit to send.
uint8_t _txSample
 Sample number for the transmitter. Runs 0 to 7 during one bit interval.
 The transmitter buffer in symbols not data octets.
uint8_t _txBufLen
 Number of symbols in _txBuf to be sent;.
- Protected Attributes inherited from RHGenericDriver
volatile RHMode _mode
 The current transport operating mode.
uint8_t _thisAddress
 This node id.
bool _promiscuous
 Whether the transport is in promiscuous mode.
volatile uint8_t _rxHeaderTo
 TO header in the last received mesasge.
volatile uint8_t _rxHeaderFrom
 FROM header in the last received mesasge.
volatile uint8_t _rxHeaderId
 ID header in the last received mesasge.
volatile uint8_t _rxHeaderFlags
 FLAGS header in the last received mesasge.
uint8_t _txHeaderTo
 TO header to send in all messages.
uint8_t _txHeaderFrom
 FROM header to send in all messages.
uint8_t _txHeaderId
 ID header to send in all messages.
uint8_t _txHeaderFlags
 FLAGS header to send in all messages.
volatile int8_t _lastRssi
 The value of the last received RSSI value, in some transport specific units.
volatile uint16_t _rxBad
 Count of the number of bad messages (eg bad checksum etc) received.
volatile uint16_t _rxGood
 Count of the number of successfully transmitted messaged.
volatile uint16_t _txGood
 Count of the number of bad messages (correct checksum etc) received.
volatile bool _cad
 Channel activity detected.
unsigned int _cad_timeout

Additional Inherited Members

- Public Types inherited from RHGenericDriver
enum  RHMode {
  RHModeInitialising = 0, RHModeSleep, RHModeIdle, RHModeTx,
  RHModeRx, RHModeCad
 Defines different operating modes for the transport hardware. More...
- Static Public Member Functions inherited from RHGenericDriver
static void printBuffer (const char *prompt, const uint8_t *buf, uint8_t len)

Detailed Description

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 (, with which it is compatible. See 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 ( 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.

Theory of operation

See ASH Transceiver Software Designer's Guide of 2002.08.07 while not directly relevant is also interesting.

Implementation Details

Messages of up to RH_ASK_MAX_PAYLOAD_LEN (67) bytes can be sent Each message is transmitted as:

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.

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.

Connecting to Arduino

Most transmitters can be connected to Arduino like this:

Arduino Transmitter

Most receivers can be connected to Arduino like this:

Arduino Receiver
SHUT (not connected)
WAKEB (not connected)
ANT |- connect to your antenna syetem

RH_ASK works with ATTiny85, using Arduino 1.0.5 and tinycore from 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:

Arduino 1 wires Arduino 1

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.

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 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:
RH_ASK driver(2000, 2, 4, 5);
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.
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.

Constructor & Destructor Documentation

RH_ASK::RH_ASK ( uint16_t  speed = 2000,
uint8_t  rxPin = 11,
uint8_t  txPin = 12,
uint8_t  pttPin = 10,
bool  pttInverted = false 

Constructor. At present only one instance of RH_ASK per sketch is supported.

[in]speedThe desired bit rate in bits per second
[in]rxPinThe pin that is used to get data from the receiver
[in]txPinThe pin that is used to send data to the transmitter
[in]pttPinThe pin that is connected to the transmitter controller. It will be set HIGH to enable the transmitter (unless pttInverted is true).
[in]pttInvertedtrue if you desire the pttin to be inverted so that LOW wil enable the transmitter.

References _txBuf.

Member Function Documentation

bool RH_ASK::available ( )

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

true if a new, complete, error-free uncollected message is available to be retreived by recv()

Implements RHGenericDriver.

References RHGenericDriver::_mode, _rxBufFull, _rxBufValid, RHGenericDriver::RHModeTx, setModeRx(), and validateRxBuf().

Referenced by recv().

bool RH_ASK::init ( )

Initialise the Driver transport hardware and software. Make sure the Driver is properly configured before calling init().

true if initialisation succeeded.

Reimplemented from RHGenericDriver.

References _pttPin, _rxPin, _txPin, RHGenericDriver::init(), setModeIdle(), and timerSetup().

uint8_t RH_ASK::maxMessageLength ( )

Returns the maximum message length available in this Driver.

The maximum legal message length

Implements RHGenericDriver.

References handleTimerInterrupt(), and speed().

bool RH_ASK::recv ( uint8_t *  buf,
uint8_t *  len 

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.

[in]bufLocation to copy the received message
[in,out]lenPointer to available space in buf. Set to the actual number of octets copied.
true if a valid message was copied to buf

Implements RHGenericDriver.

References _rxBuf, _rxBufLen, _rxBufValid, and available().

bool RH_ASK::send ( const uint8_t *  data,
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.

[in]dataArray of data to be sent
[in]lenNumber of bytes of data to send (> 0)
true if the message length was valid and it was correctly queued for transmit

Implements RHGenericDriver.

References _txBuf, _txBufLen, RHGenericDriver::_txHeaderFlags, RHGenericDriver::_txHeaderFrom, RHGenericDriver::_txHeaderId, RHGenericDriver::_txHeaderTo, setModeTx(), RHGenericDriver::waitCAD(), and RHGenericDriver::waitPacketSent().

void INTERRUPT_ATTR RH_ASK::setModeIdle ( )

If current mode is Rx or Tx changes it to Idle. If the transmitter or receiver is running, disables them.

References RHGenericDriver::_mode, RHGenericDriver::RHModeIdle, writePtt(), and writeTx().

Referenced by init(), receiveTimer(), and transmitTimer().

void RH_ASK::setModeRx ( )

If current mode is Tx or Idle, changes it to Rx. Starts the receiver in the RF69.

References RHGenericDriver::_mode, RHGenericDriver::RHModeRx, writePtt(), and writeTx().

Referenced by available().

void RH_ASK::setModeTx ( )

If current mode is Rx or Idle, changes it to Rx. F Starts the transmitter in the RF69.

References RHGenericDriver::_mode, _txBit, _txIndex, _txSample, RHGenericDriver::RHModeTx, and writePtt().

Referenced by send().

uint16_t RH_ASK::speed ( )

Returns the current speed in bits per second

The current speed in bits per second

References _speed, readRx(), receiveTimer(), symbol_6to4(), timerCalc(), timerSetup(), transmitTimer(), validateRxBuf(), writePtt(), and writeTx().

Referenced by maxMessageLength().

void RH_ASK::validateRxBuf ( )

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

References RHGenericDriver::_promiscuous, RHGenericDriver::_rxBad, _rxBuf, _rxBufLen, _rxBufValid, RHGenericDriver::_rxGood, RHGenericDriver::_rxHeaderFlags, RHGenericDriver::_rxHeaderFrom, RHGenericDriver::_rxHeaderId, RHGenericDriver::_rxHeaderTo, and RHGenericDriver::_thisAddress.

Referenced by available(), and speed().

Member Data Documentation

volatile uint8_t RH_ASK::_rxActive

Flag indicates if we have seen the start symbol of a new message and are in the processes of reading and decoding it

Referenced by receiveTimer().

volatile uint8_t RH_ASK::_rxIntegrator

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

Referenced by receiveTimer().

volatile uint8_t RH_ASK::_rxPllRamp

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.

Referenced by receiveTimer().

The documentation for this class was generated from the following files: