This driver is a stripped down version of the Radiohead 1.45 driver, and covers fewer radios. Threading and an event queue have been added to make the ISR's more stable across architectures. Specifically The STM32L4 parts

Dependents:   Threaded_LoRa_Modem

Revision:
0:ab4e012489ef
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/RH_RF22.cpp	Thu Oct 15 01:27:00 2015 +0000
@@ -0,0 +1,748 @@
+// RH_RF22.cpp
+//
+// Copyright (C) 2011 Mike McCauley
+// $Id: RH_RF22.cpp,v 1.24 2015/05/17 00:11:26 mikem Exp $
+
+#include <RH_RF22.h>
+
+// Interrupt vectors for the 2 Arduino interrupt pins
+// Each interrupt can be handled by a different instance of RH_RF22, allowing you to have
+// 2 RH_RF22s per Arduino
+RH_RF22* RH_RF22::_deviceForInterrupt[RH_RF22_NUM_INTERRUPTS] = {0, 0, 0};
+uint8_t RH_RF22::_interruptCount = 0; // Index into _deviceForInterrupt for next device
+
+// These are indexed by the values of ModemConfigChoice
+// Canned modem configurations generated with 
+// http://www.hoperf.com/upload/rf/RH_RF22B%2023B%2031B%2042B%2043B%20Register%20Settings_RevB1-v5.xls
+// Stored in flash (program) memory to save SRAM
+PROGMEM static const RH_RF22::ModemConfig MODEM_CONFIG_TABLE[] =
+{
+    { 0x2b, 0x03, 0xf4, 0x20, 0x41, 0x89, 0x00, 0x36, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x10, 0x62, 0x2c, 0x00, 0x08 }, // Unmodulated carrier
+    { 0x2b, 0x03, 0xf4, 0x20, 0x41, 0x89, 0x00, 0x36, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x10, 0x62, 0x2c, 0x33, 0x08 }, // FSK, PN9 random modulation, 2, 5
+
+    // All the following enable FIFO with reg 71
+    //  1c,   1f,   20,   21,   22,   23,   24,   25,   2c,   2d,   2e,   58,   69,   6e,   6f,   70,   71,   72
+    // FSK, No Manchester, Max Rb err <1%, Xtal Tol 20ppm
+    { 0x2b, 0x03, 0xf4, 0x20, 0x41, 0x89, 0x00, 0x36, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x10, 0x62, 0x2c, 0x22, 0x08 }, // 2, 5
+    { 0x1b, 0x03, 0x41, 0x60, 0x27, 0x52, 0x00, 0x07, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x13, 0xa9, 0x2c, 0x22, 0x3a }, // 2.4, 36
+    { 0x1d, 0x03, 0xa1, 0x20, 0x4e, 0xa5, 0x00, 0x13, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x27, 0x52, 0x2c, 0x22, 0x48 }, // 4.8, 45
+    { 0x1e, 0x03, 0xd0, 0x00, 0x9d, 0x49, 0x00, 0x45, 0x40, 0x0a, 0x20, 0x80, 0x60, 0x4e, 0xa5, 0x2c, 0x22, 0x48 }, // 9.6, 45
+    { 0x2b, 0x03, 0x34, 0x02, 0x75, 0x25, 0x07, 0xff, 0x40, 0x0a, 0x1b, 0x80, 0x60, 0x9d, 0x49, 0x2c, 0x22, 0x0f }, // 19.2, 9.6
+    { 0x02, 0x03, 0x68, 0x01, 0x3a, 0x93, 0x04, 0xd5, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x09, 0xd5, 0x0c, 0x22, 0x1f }, // 38.4, 19.6
+    { 0x06, 0x03, 0x45, 0x01, 0xd7, 0xdc, 0x07, 0x6e, 0x40, 0x0a, 0x2d, 0x80, 0x60, 0x0e, 0xbf, 0x0c, 0x22, 0x2e }, // 57.6. 28.8
+    { 0x8a, 0x03, 0x60, 0x01, 0x55, 0x55, 0x02, 0xad, 0x40, 0x0a, 0x50, 0x80, 0x60, 0x20, 0x00, 0x0c, 0x22, 0xc8 }, // 125, 125
+
+    { 0x2b, 0x03, 0xa1, 0xe0, 0x10, 0xc7, 0x00, 0x09, 0x40, 0x0a, 0x1d,  0x80, 0x60, 0x04, 0x32, 0x2c, 0x22, 0x04 }, // 512 baud, FSK, 2.5 Khz fd for POCSAG compatibility
+    { 0x27, 0x03, 0xa1, 0xe0, 0x10, 0xc7, 0x00, 0x06, 0x40, 0x0a, 0x1d,  0x80, 0x60, 0x04, 0x32, 0x2c, 0x22, 0x07 }, // 512 baud, FSK, 4.5 Khz fd for POCSAG compatibility
+
+    // GFSK, No Manchester, Max Rb err <1%, Xtal Tol 20ppm
+    // These differ from FSK only in register 71, for the modulation type
+    { 0x2b, 0x03, 0xf4, 0x20, 0x41, 0x89, 0x00, 0x36, 0x40, 0x0a, 0x1d, 0x80, 0x60, 0x10, 0x62, 0x2c, 0x23, 0x08 }, // 2, 5
+    { 0x1b, 0x03, 0x41, 0x60, 0x27, 0x52, 0x00, 0x07, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x13, 0xa9, 0x2c, 0x23, 0x3a }, // 2.4, 36
+    { 0x1d, 0x03, 0xa1, 0x20, 0x4e, 0xa5, 0x00, 0x13, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x27, 0x52, 0x2c, 0x23, 0x48 }, // 4.8, 45
+    { 0x1e, 0x03, 0xd0, 0x00, 0x9d, 0x49, 0x00, 0x45, 0x40, 0x0a, 0x20, 0x80, 0x60, 0x4e, 0xa5, 0x2c, 0x23, 0x48 }, // 9.6, 45
+    { 0x2b, 0x03, 0x34, 0x02, 0x75, 0x25, 0x07, 0xff, 0x40, 0x0a, 0x1b, 0x80, 0x60, 0x9d, 0x49, 0x2c, 0x23, 0x0f }, // 19.2, 9.6
+    { 0x02, 0x03, 0x68, 0x01, 0x3a, 0x93, 0x04, 0xd5, 0x40, 0x0a, 0x1e, 0x80, 0x60, 0x09, 0xd5, 0x0c, 0x23, 0x1f }, // 38.4, 19.6
+    { 0x06, 0x03, 0x45, 0x01, 0xd7, 0xdc, 0x07, 0x6e, 0x40, 0x0a, 0x2d, 0x80, 0x60, 0x0e, 0xbf, 0x0c, 0x23, 0x2e }, // 57.6. 28.8
+    { 0x8a, 0x03, 0x60, 0x01, 0x55, 0x55, 0x02, 0xad, 0x40, 0x0a, 0x50, 0x80, 0x60, 0x20, 0x00, 0x0c, 0x23, 0xc8 }, // 125, 125
+
+    // OOK, No Manchester, Max Rb err <1%, Xtal Tol 20ppm
+    { 0x51, 0x03, 0x68, 0x00, 0x3a, 0x93, 0x01, 0x3d, 0x2c, 0x11, 0x28, 0x80, 0x60, 0x09, 0xd5, 0x2c, 0x21, 0x08 }, // 1.2, 75
+    { 0xc8, 0x03, 0x39, 0x20, 0x68, 0xdc, 0x00, 0x6b, 0x2a, 0x08, 0x2a, 0x80, 0x60, 0x13, 0xa9, 0x2c, 0x21, 0x08 }, // 2.4, 335
+    { 0xc8, 0x03, 0x9c, 0x00, 0xd1, 0xb7, 0x00, 0xd4, 0x29, 0x04, 0x29, 0x80, 0x60, 0x27, 0x52, 0x2c, 0x21, 0x08 }, // 4.8, 335
+    { 0xb8, 0x03, 0x9c, 0x00, 0xd1, 0xb7, 0x00, 0xd4, 0x28, 0x82, 0x29, 0x80, 0x60, 0x4e, 0xa5, 0x2c, 0x21, 0x08 }, // 9.6, 335
+    { 0xa8, 0x03, 0x9c, 0x00, 0xd1, 0xb7, 0x00, 0xd4, 0x28, 0x41, 0x29, 0x80, 0x60, 0x9d, 0x49, 0x2c, 0x21, 0x08 }, // 19.2, 335
+    { 0x98, 0x03, 0x9c, 0x00, 0xd1, 0xb7, 0x00, 0xd4, 0x28, 0x20, 0x29, 0x80, 0x60, 0x09, 0xd5, 0x0c, 0x21, 0x08 }, // 38.4, 335
+    { 0x98, 0x03, 0x96, 0x00, 0xda, 0x74, 0x00, 0xdc, 0x28, 0x1f, 0x29, 0x80, 0x60, 0x0a, 0x3d, 0x0c, 0x21, 0x08 }, // 40, 335
+};
+
+RH_RF22::RH_RF22(PINS slaveSelectPin, PINS interruptPin, RHGenericSPI& spi)
+    :
+    RHSPIDriver(slaveSelectPin, spi),
+    _interruptPin(interruptPin)
+{
+    _idleMode = RH_RF22_XTON; // Default idle state is READY mode
+    _polynomial = CRC_16_IBM; // Historical
+    _myInterruptIndex = 0xff; // Not allocated yet
+}
+
+void RH_RF22::setIdleMode(uint8_t idleMode)
+{
+    _idleMode = idleMode;
+}
+
+bool RH_RF22::init()
+{
+    if (!RHSPIDriver::init())
+	return false;
+
+#if (RH_PLATFORM != RH_PLATFORM_MBED)
+    // Determine the interrupt number that corresponds to the interruptPin
+    int interruptNumber = digitalPinToInterrupt(_interruptPin);
+    if (interruptNumber == NOT_AN_INTERRUPT)
+	return false;
+#endif
+
+    // Software reset the device
+    reset();
+
+    // Get the device type and check it
+    // This also tests whether we are really connected to a device
+    _deviceType = spiRead(RH_RF22_REG_00_DEVICE_TYPE);
+    if (   _deviceType != RH_RF22_DEVICE_TYPE_RX_TRX
+        && _deviceType != RH_RF22_DEVICE_TYPE_TX)
+    {
+	return false;
+    }
+
+
+#if (RH_PLATFORM != RH_PLATFORM_MBED)
+    // 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); 
+#endif
+
+    // Enable interrupt output on the radio. Interrupt line will now go high until
+    // an interrupt occurs
+    spiWrite(RH_RF22_REG_05_INTERRUPT_ENABLE1, RH_RF22_ENTXFFAEM | RH_RF22_ENRXFFAFULL | RH_RF22_ENPKSENT | RH_RF22_ENPKVALID | RH_RF22_ENCRCERROR | RH_RF22_ENFFERR);
+    spiWrite(RH_RF22_REG_06_INTERRUPT_ENABLE2, RH_RF22_ENPREAVAL);
+
+    // 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_RF22_NUM_INTERRUPTS)
+	    _myInterruptIndex = _interruptCount++;
+	else
+	    return false; // Too many devices, not enough interrupt vectors
+    }
+    _deviceForInterrupt[_myInterruptIndex] = this;
+    
+#if (RH_PLATFORM == RH_PLATFORM_MBED)
+    if (_myInterruptIndex == 0)
+		_interruptPin.fall(&isr0);
+    else if (_myInterruptIndex == 1)
+		_interruptPin.fall(&isr1);
+    else if (_myInterruptIndex == 2)
+		_interruptPin.fall(&isr2);
+    else
+	return false; // Too many devices, not enough interrupt vectors
+#else
+    if (_myInterruptIndex == 0)
+	attachInterrupt(interruptNumber, isr0, FALLING);
+    else if (_myInterruptIndex == 1)
+	attachInterrupt(interruptNumber, isr1, FALLING);
+    else if (_myInterruptIndex == 2)
+	attachInterrupt(interruptNumber, isr2, FALLING);
+    else
+	return false; // Too many devices, not enough interrupt vectors
+#endif
+
+    setModeIdle();
+
+    clearTxBuf();
+    clearRxBuf();
+
+    // Most of these are the POR default
+    spiWrite(RH_RF22_REG_7D_TX_FIFO_CONTROL2, RH_RF22_TXFFAEM_THRESHOLD);
+    spiWrite(RH_RF22_REG_7E_RX_FIFO_CONTROL,  RH_RF22_RXFFAFULL_THRESHOLD);
+    spiWrite(RH_RF22_REG_30_DATA_ACCESS_CONTROL, RH_RF22_ENPACRX | RH_RF22_ENPACTX | RH_RF22_ENCRC | (_polynomial & RH_RF22_CRC));
+
+    // Configure the message headers
+    // Here we set up the standard packet format for use by the RH_RF22 library
+    // 8 nibbles preamble
+    // 2 SYNC words 2d, d4
+    // Header length 4 (to, from, id, flags)
+    // 1 octet of data length (0 to 255)
+    // 0 to 255 octets data
+    // 2 CRC octets as CRC16(IBM), computed on the header, length and data
+    // On reception the to address is check for validity against RH_RF22_REG_3F_CHECK_HEADER3
+    // or the broadcast address of 0xff
+    // If no changes are made after this, the transmitted
+    // to address will be 0xff, the from address will be 0xff
+    // and all such messages will be accepted. This permits the out-of the box
+    // RH_RF22 config to act as an unaddresed, unreliable datagram service
+    spiWrite(RH_RF22_REG_32_HEADER_CONTROL1, RH_RF22_BCEN_HEADER3 | RH_RF22_HDCH_HEADER3);
+    spiWrite(RH_RF22_REG_33_HEADER_CONTROL2, RH_RF22_HDLEN_4 | RH_RF22_SYNCLEN_2);
+
+    setPreambleLength(8);
+    uint8_t syncwords[] = { 0x2d, 0xd4 };
+    setSyncWords(syncwords, sizeof(syncwords));
+    setPromiscuous(false); 
+
+    // Set some defaults. An innocuous ISM frequency, and reasonable pull-in
+    setFrequency(434.0, 0.05);
+//    setFrequency(900.0);
+    // Some slow, reliable default speed and modulation
+    setModemConfig(FSK_Rb2_4Fd36);
+//    setModemConfig(FSK_Rb125Fd125);
+    setGpioReversed(false);
+    // Lowish power
+    setTxPower(RH_RF22_TXPOW_8DBM);
+
+    return true;
+}
+
+// C++ level interrupt handler for this instance
+void RH_RF22::handleInterrupt()
+{
+    uint8_t _lastInterruptFlags[2];
+    // Read the interrupt flags which clears the interrupt
+    spiBurstRead(RH_RF22_REG_03_INTERRUPT_STATUS1, _lastInterruptFlags, 2);
+
+#if 0
+    // DEVELOPER TESTING ONLY
+    // Caution: Serial printing in this interrupt routine can cause mysterious crashes
+    Serial.print("interrupt ");
+    Serial.print(_lastInterruptFlags[0], HEX);
+    Serial.print(" ");
+    Serial.println(_lastInterruptFlags[1], HEX);
+    if (_lastInterruptFlags[0] == 0 && _lastInterruptFlags[1] == 0)
+	Serial.println("FUNNY: no interrupt!");
+#endif
+
+#if 0
+    // DEVELOPER TESTING ONLY
+    // TESTING: fake an RH_RF22_IFFERROR
+    static int counter = 0;
+    if (_lastInterruptFlags[0] & RH_RF22_IPKSENT && counter++ == 10)
+    {
+	_lastInterruptFlags[0] = RH_RF22_IFFERROR;
+	counter = 0;
+    }
+#endif
+
+    if (_lastInterruptFlags[0] & RH_RF22_IFFERROR)
+    {
+	resetFifos(); // Clears the interrupt
+	if (_mode == RHModeTx)
+	    restartTransmit();
+	else if (_mode == RHModeRx)
+	    clearRxBuf();
+//	Serial.println("IFFERROR");  
+    }
+    // Caution, any delay here may cause a FF underflow or overflow
+    if (_lastInterruptFlags[0] & RH_RF22_ITXFFAEM)
+    {
+	// See if more data has to be loaded into the Tx FIFO 
+  	sendNextFragment();
+//	Serial.println("ITXFFAEM");  
+    }
+    if (_lastInterruptFlags[0] & RH_RF22_IRXFFAFULL)
+    {
+	// Caution, any delay here may cause a FF overflow
+	// Read some data from the Rx FIFO
+	readNextFragment();
+//	Serial.println("IRXFFAFULL"); 
+    }
+    if (_lastInterruptFlags[0] & RH_RF22_IEXT)
+    {
+	// This is not enabled by the base code, but users may want to enable it
+	handleExternalInterrupt();
+//	Serial.println("IEXT"); 
+    }
+    if (_lastInterruptFlags[1] & RH_RF22_IWUT)
+    {
+	// This is not enabled by the base code, but users may want to enable it
+	handleWakeupTimerInterrupt();
+//	Serial.println("IWUT"); 
+    }
+    if (_lastInterruptFlags[0] & RH_RF22_IPKSENT)
+    {
+//	Serial.println("IPKSENT");   
+	_txGood++; 
+	// Transmission does not automatically clear the tx buffer.
+	// Could retransmit if we wanted
+	// RH_RF22 transitions automatically to Idle
+	_mode = RHModeIdle;
+    }
+    if (_lastInterruptFlags[0] & RH_RF22_IPKVALID)
+    {
+	uint8_t len = spiRead(RH_RF22_REG_4B_RECEIVED_PACKET_LENGTH);
+//	Serial.println("IPKVALID");   
+
+	// May have already read one or more fragments
+	// Get any remaining unread octets, based on the expected length
+	// First make sure we dont overflow the buffer in the case of a stupid length
+	// or partial bad receives
+	if (   len >  RH_RF22_MAX_MESSAGE_LEN
+	    || len < _bufLen)
+	{
+	    _rxBad++;
+	    _mode = RHModeIdle;
+	    clearRxBuf();
+	    return; // Hmmm receiver buffer overflow. 
+	}
+
+	spiBurstRead(RH_RF22_REG_7F_FIFO_ACCESS, _buf + _bufLen, len - _bufLen);
+	_rxHeaderTo = spiRead(RH_RF22_REG_47_RECEIVED_HEADER3);
+	_rxHeaderFrom = spiRead(RH_RF22_REG_48_RECEIVED_HEADER2);
+	_rxHeaderId = spiRead(RH_RF22_REG_49_RECEIVED_HEADER1);
+	_rxHeaderFlags = spiRead(RH_RF22_REG_4A_RECEIVED_HEADER0);
+	_rxGood++;
+	_bufLen = len;
+	_mode = RHModeIdle;
+	_rxBufValid = true;
+    }
+    if (_lastInterruptFlags[0] & RH_RF22_ICRCERROR)
+    {
+//	Serial.println("ICRCERR");  
+	_rxBad++;
+	clearRxBuf();
+	resetRxFifo();
+	_mode = RHModeIdle;
+	setModeRx(); // Keep trying
+    }
+    if (_lastInterruptFlags[1] & RH_RF22_IPREAVAL)
+    {
+//	Serial.println("IPREAVAL");  
+	_lastRssi = (int8_t)(-120 + ((spiRead(RH_RF22_REG_26_RSSI) / 2)));
+	_lastPreambleTime = millis();
+	resetRxFifo();
+	clearRxBuf();
+    }
+}
+
+// These are low level functions that call the interrupt handler for the correct
+// instance of RH_RF22.
+// 3 interrupts allows us to have 3 different devices
+void RH_RF22::isr0()
+{
+    if (_deviceForInterrupt[0])
+	_deviceForInterrupt[0]->handleInterrupt();
+}
+void RH_RF22::isr1()
+{
+    if (_deviceForInterrupt[1])
+	_deviceForInterrupt[1]->handleInterrupt();
+}
+void RH_RF22::isr2()
+{
+    if (_deviceForInterrupt[2])
+	_deviceForInterrupt[2]->handleInterrupt();
+}
+
+void RH_RF22::reset()
+{
+    spiWrite(RH_RF22_REG_07_OPERATING_MODE1, RH_RF22_SWRES);
+    // Wait for it to settle
+    delay(1); // SWReset time is nominally 100usec
+}
+
+uint8_t RH_RF22::statusRead()
+{
+    return spiRead(RH_RF22_REG_02_DEVICE_STATUS);
+}
+
+uint8_t RH_RF22::adcRead(uint8_t adcsel,
+                      uint8_t adcref ,
+                      uint8_t adcgain, 
+                      uint8_t adcoffs)
+{
+    uint8_t configuration = adcsel | adcref | (adcgain & RH_RF22_ADCGAIN);
+    spiWrite(RH_RF22_REG_0F_ADC_CONFIGURATION, configuration | RH_RF22_ADCSTART);
+    spiWrite(RH_RF22_REG_10_ADC_SENSOR_AMP_OFFSET, adcoffs);
+
+    // Conversion time is nominally 305usec
+    // Wait for the DONE bit
+    while (!(spiRead(RH_RF22_REG_0F_ADC_CONFIGURATION) & RH_RF22_ADCDONE))
+	;
+    // Return the value  
+    return spiRead(RH_RF22_REG_11_ADC_VALUE);
+}
+
+uint8_t RH_RF22::temperatureRead(uint8_t tsrange, uint8_t tvoffs)
+{
+    spiWrite(RH_RF22_REG_12_TEMPERATURE_SENSOR_CALIBRATION, tsrange | RH_RF22_ENTSOFFS);
+    spiWrite(RH_RF22_REG_13_TEMPERATURE_VALUE_OFFSET, tvoffs);
+    return adcRead(RH_RF22_ADCSEL_INTERNAL_TEMPERATURE_SENSOR | RH_RF22_ADCREF_BANDGAP_VOLTAGE); 
+}
+
+uint16_t RH_RF22::wutRead()
+{
+    uint8_t buf[2];
+    spiBurstRead(RH_RF22_REG_17_WAKEUP_TIMER_VALUE1, buf, 2);
+    return ((uint16_t)buf[0] << 8) | buf[1]; // Dont rely on byte order
+}
+
+// RFM-22 doc appears to be wrong: WUT for wtm = 10000, r, = 0, d = 0 is about 1 sec
+void RH_RF22::setWutPeriod(uint16_t wtm, uint8_t wtr, uint8_t wtd)
+{
+    uint8_t period[3];
+
+    period[0] = ((wtr & 0xf) << 2) | (wtd & 0x3);
+    period[1] = wtm >> 8;
+    period[2] = wtm & 0xff;
+    spiBurstWrite(RH_RF22_REG_14_WAKEUP_TIMER_PERIOD1, period, sizeof(period));
+}
+
+// Returns true if centre + (fhch * fhs) is within limits
+// Caution, different versions of the RH_RF22 support different max freq
+// so YMMV
+bool RH_RF22::setFrequency(float centre, float afcPullInRange)
+{
+    uint8_t fbsel = RH_RF22_SBSEL;
+    uint8_t afclimiter;
+    if (centre < 240.0 || centre > 960.0) // 930.0 for early silicon
+	return false;
+    if (centre >= 480.0)
+    {
+	if (afcPullInRange < 0.0 || afcPullInRange > 0.318750)
+	    return false;
+	centre /= 2;
+	fbsel |= RH_RF22_HBSEL;
+	afclimiter = afcPullInRange * 1000000.0 / 1250.0;
+    }
+    else
+    {
+	if (afcPullInRange < 0.0 || afcPullInRange > 0.159375)
+	    return false;
+	afclimiter = afcPullInRange * 1000000.0 / 625.0;
+    }
+    centre /= 10.0;
+    float integerPart = floor(centre);
+    float fractionalPart = centre - integerPart;
+
+    uint8_t fb = (uint8_t)integerPart - 24; // Range 0 to 23
+    fbsel |= fb;
+    uint16_t fc = fractionalPart * 64000;
+    spiWrite(RH_RF22_REG_73_FREQUENCY_OFFSET1, 0);  // REVISIT
+    spiWrite(RH_RF22_REG_74_FREQUENCY_OFFSET2, 0);
+    spiWrite(RH_RF22_REG_75_FREQUENCY_BAND_SELECT, fbsel);
+    spiWrite(RH_RF22_REG_76_NOMINAL_CARRIER_FREQUENCY1, fc >> 8);
+    spiWrite(RH_RF22_REG_77_NOMINAL_CARRIER_FREQUENCY0, fc & 0xff);
+    spiWrite(RH_RF22_REG_2A_AFC_LIMITER, afclimiter);
+    return !(statusRead() & RH_RF22_FREQERR);
+}
+
+// Step size in 10kHz increments
+// Returns true if centre + (fhch * fhs) is within limits
+bool RH_RF22::setFHStepSize(uint8_t fhs)
+{
+    spiWrite(RH_RF22_REG_7A_FREQUENCY_HOPPING_STEP_SIZE, fhs);
+    return !(statusRead() & RH_RF22_FREQERR);
+}
+
+// Adds fhch * fhs to centre frequency
+// Returns true if centre + (fhch * fhs) is within limits
+bool RH_RF22::setFHChannel(uint8_t fhch)
+{
+    spiWrite(RH_RF22_REG_79_FREQUENCY_HOPPING_CHANNEL_SELECT, fhch);
+    return !(statusRead() & RH_RF22_FREQERR);
+}
+
+uint8_t RH_RF22::rssiRead()
+{
+    return spiRead(RH_RF22_REG_26_RSSI);
+}
+
+uint8_t RH_RF22::ezmacStatusRead()
+{
+    return spiRead(RH_RF22_REG_31_EZMAC_STATUS);
+}
+
+void RH_RF22::setOpMode(uint8_t mode)
+{
+    spiWrite(RH_RF22_REG_07_OPERATING_MODE1, mode);
+}
+
+void RH_RF22::setModeIdle()
+{
+    if (_mode != RHModeIdle)
+    {
+	setOpMode(_idleMode);
+	_mode = RHModeIdle;
+    }
+}
+
+bool RH_RF22::sleep()
+{
+    if (_mode != RHModeSleep)
+    {
+	setOpMode(0);
+	_mode = RHModeSleep;
+    }
+    return true;
+}
+
+void RH_RF22::setModeRx()
+{
+    if (_mode != RHModeRx)
+    {
+	setOpMode(_idleMode | RH_RF22_RXON);
+	_mode = RHModeRx;
+    }
+}
+
+void RH_RF22::setModeTx()
+{
+    if (_mode != RHModeTx)
+    {
+	setOpMode(_idleMode | RH_RF22_TXON);
+	// Hmmm, if you dont clear the RX FIFO here, then it appears that going
+	// to transmit mode in the middle of a receive can corrupt the
+	// RX FIFO
+	resetRxFifo();
+	_mode = RHModeTx;
+    }
+}
+
+void RH_RF22::setTxPower(uint8_t power)
+{
+    spiWrite(RH_RF22_REG_6D_TX_POWER, power | RH_RF22_LNA_SW); // On RF23, LNA_SW must be set.
+}
+
+// Sets registers from a canned modem configuration structure
+void RH_RF22::setModemRegisters(const ModemConfig* config)
+{
+    spiWrite(RH_RF22_REG_1C_IF_FILTER_BANDWIDTH,                    config->reg_1c);
+    spiWrite(RH_RF22_REG_1F_CLOCK_RECOVERY_GEARSHIFT_OVERRIDE,      config->reg_1f);
+    spiBurstWrite(RH_RF22_REG_20_CLOCK_RECOVERY_OVERSAMPLING_RATE, &config->reg_20, 6);
+    spiBurstWrite(RH_RF22_REG_2C_OOK_COUNTER_VALUE_1,              &config->reg_2c, 3);
+    spiWrite(RH_RF22_REG_58_CHARGE_PUMP_CURRENT_TRIMMING,           config->reg_58);
+    spiWrite(RH_RF22_REG_69_AGC_OVERRIDE1,                          config->reg_69);
+    spiBurstWrite(RH_RF22_REG_6E_TX_DATA_RATE1,                    &config->reg_6e, 5);
+}
+
+// Set one of the canned FSK Modem configs
+// Returns true if its a valid choice
+bool RH_RF22::setModemConfig(ModemConfigChoice index)
+{
+    if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig)))
+        return false;
+
+    RH_RF22::ModemConfig cfg;
+    memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(RH_RF22::ModemConfig));
+    setModemRegisters(&cfg);
+
+    return true;
+}
+
+// REVISIT: top bit is in Header Control 2 0x33
+void RH_RF22::setPreambleLength(uint8_t nibbles)
+{
+    spiWrite(RH_RF22_REG_34_PREAMBLE_LENGTH, nibbles);
+}
+
+// Caution doesnt set sync word len in Header Control 2 0x33
+void RH_RF22::setSyncWords(const uint8_t* syncWords, uint8_t len)
+{
+    spiBurstWrite(RH_RF22_REG_36_SYNC_WORD3, syncWords, len);
+}
+
+void RH_RF22::clearRxBuf()
+{
+    ATOMIC_BLOCK_START;
+    _bufLen = 0;
+    _rxBufValid = false;
+    ATOMIC_BLOCK_END;
+}
+
+bool RH_RF22::available()
+{
+    if (!_rxBufValid)
+    {
+	if (_mode == RHModeTx)
+	    return false;
+	setModeRx(); // Make sure we are receiving
+    }
+    return _rxBufValid;
+}
+
+bool RH_RF22::recv(uint8_t* buf, uint8_t* len)
+{
+    if (!available())
+	return false;
+
+    if (buf && len)
+    {
+	ATOMIC_BLOCK_START;
+	if (*len > _bufLen)
+	    *len = _bufLen;
+	memcpy(buf, _buf, *len);
+	ATOMIC_BLOCK_END;
+    }
+    clearRxBuf();
+//    printBuffer("recv:", buf, *len);
+    return true;
+}
+
+void RH_RF22::clearTxBuf()
+{
+    ATOMIC_BLOCK_START;
+    _bufLen = 0;
+    _txBufSentIndex = 0;
+    ATOMIC_BLOCK_END;
+}
+
+void RH_RF22::startTransmit()
+{
+    sendNextFragment(); // Actually the first fragment
+    spiWrite(RH_RF22_REG_3E_PACKET_LENGTH, _bufLen); // Total length that will be sent
+    setModeTx(); // Start the transmitter, turns off the receiver
+}
+
+// Restart the transmission of a packet that had a problem
+void RH_RF22::restartTransmit()
+{
+    _mode = RHModeIdle;
+    _txBufSentIndex = 0;
+//	    Serial.println("Restart");
+    startTransmit();
+}
+
+bool RH_RF22::send(const uint8_t* data, uint8_t len)
+{
+    bool ret = true;
+    waitPacketSent();
+    ATOMIC_BLOCK_START;
+    spiWrite(RH_RF22_REG_3A_TRANSMIT_HEADER3, _txHeaderTo);
+    spiWrite(RH_RF22_REG_3B_TRANSMIT_HEADER2, _txHeaderFrom);
+    spiWrite(RH_RF22_REG_3C_TRANSMIT_HEADER1, _txHeaderId);
+    spiWrite(RH_RF22_REG_3D_TRANSMIT_HEADER0, _txHeaderFlags);
+    if (!fillTxBuf(data, len))
+	ret = false;
+    else
+	startTransmit();
+    ATOMIC_BLOCK_END;
+//    printBuffer("send:", data, len);
+    return ret;
+}
+
+bool RH_RF22::fillTxBuf(const uint8_t* data, uint8_t len)
+{
+    clearTxBuf();
+    if (!len)
+	return false; 
+    return appendTxBuf(data, len);
+}
+
+bool RH_RF22::appendTxBuf(const uint8_t* data, uint8_t len)
+{
+    if (((uint16_t)_bufLen + len) > RH_RF22_MAX_MESSAGE_LEN)
+	return false;
+    ATOMIC_BLOCK_START;
+    memcpy(_buf + _bufLen, data, len);
+    _bufLen += len;
+    ATOMIC_BLOCK_END;
+//    printBuffer("txbuf:", _buf, _bufLen);
+    return true;
+}
+
+// Assumption: there is currently <= RH_RF22_TXFFAEM_THRESHOLD bytes in the Tx FIFO
+void RH_RF22::sendNextFragment()
+{
+    if (_txBufSentIndex < _bufLen)
+    {
+	// Some left to send?
+	uint8_t len = _bufLen - _txBufSentIndex;
+	// But dont send too much
+	if (len > (RH_RF22_FIFO_SIZE - RH_RF22_TXFFAEM_THRESHOLD - 1))
+	    len = (RH_RF22_FIFO_SIZE - RH_RF22_TXFFAEM_THRESHOLD - 1);
+	spiBurstWrite(RH_RF22_REG_7F_FIFO_ACCESS, _buf + _txBufSentIndex, len);
+//	printBuffer("frag:", _buf  + _txBufSentIndex, len);
+	_txBufSentIndex += len;
+    }
+}
+
+// Assumption: there are at least RH_RF22_RXFFAFULL_THRESHOLD in the RX FIFO
+// That means it should only be called after a RXFFAFULL interrupt
+void RH_RF22::readNextFragment()
+{
+    if (((uint16_t)_bufLen + RH_RF22_RXFFAFULL_THRESHOLD) > RH_RF22_MAX_MESSAGE_LEN)
+	return; // Hmmm receiver overflow. Should never occur
+
+    // Read the RH_RF22_RXFFAFULL_THRESHOLD octets that should be there
+    spiBurstRead(RH_RF22_REG_7F_FIFO_ACCESS, _buf + _bufLen, RH_RF22_RXFFAFULL_THRESHOLD);
+    _bufLen += RH_RF22_RXFFAFULL_THRESHOLD;
+}
+
+// Clear the FIFOs
+void RH_RF22::resetFifos()
+{
+    spiWrite(RH_RF22_REG_08_OPERATING_MODE2, RH_RF22_FFCLRRX | RH_RF22_FFCLRTX);
+    spiWrite(RH_RF22_REG_08_OPERATING_MODE2, 0);
+}
+
+// Clear the Rx FIFO
+void RH_RF22::resetRxFifo()
+{
+    spiWrite(RH_RF22_REG_08_OPERATING_MODE2, RH_RF22_FFCLRRX);
+    spiWrite(RH_RF22_REG_08_OPERATING_MODE2, 0);
+}
+
+// CLear the TX FIFO
+void RH_RF22::resetTxFifo()
+{
+    spiWrite(RH_RF22_REG_08_OPERATING_MODE2, RH_RF22_FFCLRTX);
+    spiWrite(RH_RF22_REG_08_OPERATING_MODE2, 0);
+}
+
+// Default implmentation does nothing. Override if you wish
+void RH_RF22::handleExternalInterrupt()
+{
+}
+
+// Default implmentation does nothing. Override if you wish
+void RH_RF22::handleWakeupTimerInterrupt()
+{
+}
+
+void RH_RF22::setPromiscuous(bool promiscuous)
+{
+    RHSPIDriver::setPromiscuous(promiscuous);
+    spiWrite(RH_RF22_REG_43_HEADER_ENABLE3, promiscuous ? 0x00 : 0xff);
+}
+
+bool RH_RF22::setCRCPolynomial(CRCPolynomial polynomial)
+{
+    if (polynomial >= CRC_CCITT &&
+	polynomial <= CRC_Biacheva)
+    {
+	_polynomial = polynomial;
+	return true;
+    }
+    else
+	return false;
+}
+
+uint8_t RH_RF22::maxMessageLength()
+{
+    return RH_RF22_MAX_MESSAGE_LEN;
+}
+
+void RH_RF22::setThisAddress(uint8_t thisAddress)
+{
+    RHSPIDriver::setThisAddress(thisAddress);
+    spiWrite(RH_RF22_REG_3F_CHECK_HEADER3, thisAddress);
+}
+
+uint32_t RH_RF22::getLastPreambleTime()
+{
+    return _lastPreambleTime;
+}
+
+void RH_RF22::setGpioReversed(bool gpioReversed)
+{
+    // Ensure the antenna can be switched automatically according to transmit and receive
+    // This assumes GPIO0(out) is connected to TX_ANT(in) to enable tx antenna during transmit
+    // This assumes GPIO1(out) is connected to RX_ANT(in) to enable rx antenna during receive
+    if (gpioReversed)
+    {
+	// Reversed for HAB-RFM22B-BOA HAB-RFM22B-BO, also Si4432 sold by Dorji.com via Tindie.com.
+	spiWrite(RH_RF22_REG_0B_GPIO_CONFIGURATION0, 0x15) ; // RX state
+	spiWrite(RH_RF22_REG_0C_GPIO_CONFIGURATION1, 0x12) ; // TX state
+    }
+    else
+    {
+	spiWrite(RH_RF22_REG_0B_GPIO_CONFIGURATION0, 0x12) ; // TX state
+	spiWrite(RH_RF22_REG_0C_GPIO_CONFIGURATION1, 0x15) ; // RX state
+    }
+}
+