Particle Photon / Electron + RFN95W Long Range Radio

I have been testing some Hope RFM95W long range wireless radios and I’m seeing about 1 mile range capabilities.

I figure this would be a great way to add sensors to the Photon or Electron which can then push that data out over the Particle Cloud to the web.

I’m building products that could use this radio along with the Photon or Electron so its in my best interested to just embed this radio into the same product PCB that will have the Photon or Electron on board also to keep cost down, no need for a extra micro processor on board.

I see there is a library for a Hope RFM69 in the Build Library now but the chipsets are different so some modifications or major modifications my be needed.

I figure it might be best to just attempt to port the Radio Head library for the RFM95w chip that is documented here:

I have $100 or whatever is reasonable for anybody who can get this ported over properly, I have no idea how hard or easy it may be.

The code below is for the basic TX and RX example modes with no data loss retries which is also avaliable and is really what I want to get working but I figure getting the easiest example up and running would be the best way to start.

The code comes from the RadioHead library below.

http://www.airspayce.com/mikem/arduino/RadioHead/

Here is the test code used for the RX receiver radio + Atmel 32u4:

// Atmel32u4 95_RX
// -*- mode: C++ -*-
// Example sketch showing how to create a simple messaging client (receiver)
// with the RH_RF95 class. RH_RF95 class does not provide for addressing or
// reliability, so you should only use RH_RF95 if you do not need the higher
// level messaging abilities.
// It is designed to work with the other example Feather9x_TX
 
#include <SPI.h>
#include <RH_RF95.h>
 
/* for feather32u4 */
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 7
 
// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 915.0
 
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
 
// Blinky on receipt
#define LED 13
 
void setup() 
{
  pinMode(LED, OUTPUT);     
  pinMode(RFM95_RST, OUTPUT);
  digitalWrite(RFM95_RST, HIGH);
 
  //while (!Serial);
  Serial.begin(9600);
  delay(100);
 
  Serial.println("Feather LoRa RX Test!");
  
  // manual reset
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);
 
  while (!rf95.init()) {
    Serial.println("LoRa radio init failed");
    while (1);
  }
  Serial.println("LoRa radio init OK!");
 
  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
  if (!rf95.setFrequency(RF95_FREQ)) {
    Serial.println("setFrequency failed");
    while (1);
  }
  Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
 
  // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
 
  // The default transmitter power is 13dBm, using PA_BOOST.
  // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then 
  // you can set transmitter powers from 5 to 23 dBm:
  rf95.setTxPower(23, false);
}
 
void loop()
{
  if (rf95.available())
  {
    // Should be a message for us now   
    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
    uint8_t len = sizeof(buf);
    
    if (rf95.recv(buf, &len))
    {
      digitalWrite(LED, HIGH);
      //RH_RF95::printBuffer("Received: ", buf, len);
      Serial.print("Got: ");
      Serial.println((char*)buf);
       Serial.print("RSSI: ");
      Serial.println(rf95.lastRssi(), DEC);
      
      
      // Send a reply
      uint8_t data[] = "And hello back to you";
      rf95.send(data, sizeof(data));
      rf95.waitPacketSent();
      Serial.println("Sent a reply");
      Serial.println("---------");
      digitalWrite(LED, LOW);
    }
    else
    {
      Serial.println("Receive failed");
      Serial.println("");
    }
  }
}

And here is the example code for the TX transmitter module:


 // Atmel32u4 95_TX
// -*- mode: C++ -*-
// Example sketch showing how to create a simple messaging client (transmitter)
// with the RH_RF95 class. RH_RF95 class does not provide for addressing or
// reliability, so you should only use RH_RF95 if you do not need the higher
// level messaging abilities.
// It is designed to work with the other example Feather9x_RX
 
#include <SPI.h>
#include <RH_RF95.h>
 
/* for atmel32u4 */
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 7
*/
 
   
 
// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 915.0
 
// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);
 
void setup() 
{
  pinMode(RFM95_RST, OUTPUT);
  digitalWrite(RFM95_RST, HIGH);
 
  //while (!Serial);
  Serial.begin(9600);
  delay(100);
 
  Serial.println("Feather LoRa TX Test!");
 
  // manual reset
  digitalWrite(RFM95_RST, LOW);
  delay(10);
  digitalWrite(RFM95_RST, HIGH);
  delay(10);
 
  while (!rf95.init()) {
    Serial.println("LoRa radio init failed");
    while (1);
  }
  Serial.println("LoRa radio init OK!");
 
  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
  if (!rf95.setFrequency(RF95_FREQ)) {
    Serial.println("setFrequency failed");
    while (1);
  }
  Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);
  
  // Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
 
  // The default transmitter power is 13dBm, using PA_BOOST.
  // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then 
  // you can set transmitter powers from 5 to 23 dBm:
  rf95.setTxPower(23, false);
}
 
int16_t packetnum = 0;  // packet counter, we increment per xmission
 
void loop()
{
  Serial.println("Sending to rf95_server");
  // Send a message to rf95_server
  
  char radiopacket[20] = "Hello World #      ";
  itoa(packetnum++, radiopacket+13, 10);
  Serial.print("Sending "); Serial.println(radiopacket);
  radiopacket[19] = 0;
  
  Serial.println("Sending..."); delay(10);
  rf95.send((uint8_t *)radiopacket, 20);
 
  Serial.println("Waiting for packet to complete..."); delay(10);
  rf95.waitPacketSent();
  // Now wait for a reply
  uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
  uint8_t len = sizeof(buf);
 
  Serial.println("Waiting for reply..."); delay(10);
  if (rf95.waitAvailableTimeout(1000))
  { 
    // Should be a reply message for us now   
    if (rf95.recv(buf, &len))
   {
      Serial.print("Got reply: ");
      Serial.println((char*)buf);
      Serial.print("RSSI: ");
      Serial.println(rf95.lastRssi(), DEC);    
    }
    else
    {
      Serial.println("Receive failed");
    }
  }
  else
  {
    Serial.println("No reply, is there a listener around?");
  }
  delay(1000);
}

Here is a link to the .H file, its too long to post here: http://www.airspayce.com/mikem/arduino/RadioHead/RH__RF95_8h_source.html

Here is the .CPP file:

// RH_RF95.cpp
//
// Copyright (C) 2011 Mike McCauley
// $Id: RH_RF95.cpp,v 1.11 2016/04/04 01:40:12 mikem Exp mikem $

#include <RH_RF95.h>

// Interrupt vectors for the 3 Arduino interrupt pins
// Each interrupt can be handled by a different instance of RH_RF95, allowing you to have
// 2 or more LORAs per Arduino
RH_RF95* RH_RF95::_deviceForInterrupt[RH_RF95_NUM_INTERRUPTS] = {0, 0, 0};
uint8_t RH_RF95::_interruptCount = 0; // Index into _deviceForInterrupt for next device

// These are indexed by the values of ModemConfigChoice
// Stored in flash (program) memory to save SRAM
PROGMEM static const RH_RF95::ModemConfig MODEM_CONFIG_TABLE[] =
{
    //  1d,     1e,      26
    { 0x72,   0x74,    0x00}, // Bw125Cr45Sf128 (the chip default)
    { 0x92,   0x74,    0x00}, // Bw500Cr45Sf128
    { 0x48,   0x94,    0x00}, // Bw31_25Cr48Sf512
    { 0x78,   0xc4,    0x00}, // Bw125Cr48Sf4096
    
};

RH_RF95::RH_RF95(uint8_t slaveSelectPin, uint8_t interruptPin, RHGenericSPI& spi)
    :
    RHSPIDriver(slaveSelectPin, spi),
    _rxBufValid(0)
{
    _interruptPin = interruptPin;
    _myInterruptIndex = 0xff; // Not allocated yet
}

bool RH_RF95::init()
{
    if (!RHSPIDriver::init())
	return false;

    // Determine the interrupt number that corresponds to the interruptPin
    int interruptNumber = digitalPinToInterrupt(_interruptPin);
    if (interruptNumber == NOT_AN_INTERRUPT)
	return false;
#ifdef RH_ATTACHINTERRUPT_TAKES_PIN_NUMBER
    interruptNumber = _interruptPin;
#endif

    // No way to check the device type :-(
    
    // Set sleep mode, so we can also set LORA mode:
    spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE);
    delay(10); // Wait for sleep mode to take over from say, CAD
    // Check we are in sleep mode, with LORA set
    if (spiRead(RH_RF95_REG_01_OP_MODE) != (RH_RF95_MODE_SLEEP | RH_RF95_LONG_RANGE_MODE))
    {
//	Serial.println(spiRead(RH_RF95_REG_01_OP_MODE), HEX);
	return false; // No device present?
    }

    // 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_RF95_NUM_INTERRUPTS)
	    _myInterruptIndex = _interruptCount++;
	else
	    return false; // Too many devices, not enough interrupt vectors
    }
    _deviceForInterrupt[_myInterruptIndex] = this;
    if (_myInterruptIndex == 0)
	attachInterrupt(interruptNumber, isr0, RISING);
    else if (_myInterruptIndex == 1)
	attachInterrupt(interruptNumber, isr1, RISING);
    else if (_myInterruptIndex == 2)
	attachInterrupt(interruptNumber, isr2, RISING);
    else
	return false; // Too many devices, not enough interrupt vectors

    // Set up FIFO
    // We configure so that we can use the entire 256 byte FIFO for either receive
    // or transmit, but not both at the same time
    spiWrite(RH_RF95_REG_0E_FIFO_TX_BASE_ADDR, 0);
    spiWrite(RH_RF95_REG_0F_FIFO_RX_BASE_ADDR, 0);

    // Packet format is preamble + explicit-header + payload + crc
    // Explicit Header Mode
    // payload is TO + FROM + ID + FLAGS + message data
    // RX mode is implmented with RXCONTINUOUS
    // max message data length is 255 - 4 = 251 octets

    setModeIdle();

    // Set up default configuration
    // No Sync Words in LORA mode.
    setModemConfig(Bw125Cr45Sf128); // Radio default
//    setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
    setPreambleLength(8); // Default is 8
    // An innocuous ISM frequency, same as RF22's
    setFrequency(434.0);
    // Lowish power
    setTxPower(13);

    return true;
}

// C++ level interrupt handler for this instance
// LORA is unusual in that it has several interrupt lines, and not a single, combined one.
// On MiniWirelessLoRa, only one of the several interrupt lines (DI0) from the RFM95 is usefuly 
// connnected to the processor.
// We use this to get RxDone and TxDone interrupts
void RH_RF95::handleInterrupt()
{
    // Read the interrupt register
    uint8_t irq_flags = spiRead(RH_RF95_REG_12_IRQ_FLAGS);
    if (_mode == RHModeRx && irq_flags & (RH_RF95_RX_TIMEOUT | RH_RF95_PAYLOAD_CRC_ERROR))
    {
	_rxBad++;
    }
    else if (_mode == RHModeRx && irq_flags & RH_RF95_RX_DONE)
    {
	// Have received a packet
	uint8_t len = spiRead(RH_RF95_REG_13_RX_NB_BYTES);

	// Reset the fifo read ptr to the beginning of the packet
	spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, spiRead(RH_RF95_REG_10_FIFO_RX_CURRENT_ADDR));
	spiBurstRead(RH_RF95_REG_00_FIFO, _buf, len);
	_bufLen = len;
	spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags

	// Remember the RSSI of this packet
	// this is according to the doc, but is it really correct?
	// weakest receiveable signals are reported RSSI at about -66
	_lastRssi = spiRead(RH_RF95_REG_1A_PKT_RSSI_VALUE) - 137;

	// We have received a message.
	validateRxBuf(); 
	if (_rxBufValid)
	    setModeIdle(); // Got one 
    }
    else if (_mode == RHModeTx && irq_flags & RH_RF95_TX_DONE)
    {
	_txGood++;
	setModeIdle();
    }
    
    spiWrite(RH_RF95_REG_12_IRQ_FLAGS, 0xff); // Clear all IRQ flags
}

// These are low level functions that call the interrupt handler for the correct
// instance of RH_RF95.
// 3 interrupts allows us to have 3 different devices
void RH_RF95::isr0()
{
    if (_deviceForInterrupt[0])
	_deviceForInterrupt[0]->handleInterrupt();
}
void RH_RF95::isr1()
{
    if (_deviceForInterrupt[1])
	_deviceForInterrupt[1]->handleInterrupt();
}
void RH_RF95::isr2()
{
    if (_deviceForInterrupt[2])
	_deviceForInterrupt[2]->handleInterrupt();
}

// Check whether the latest received message is complete and uncorrupted
void RH_RF95::validateRxBuf()
{
    if (_bufLen < 4)
	return; // Too short to be a real message
    // Extract the 4 headers
    _rxHeaderTo    = _buf[0];
    _rxHeaderFrom  = _buf[1];
    _rxHeaderId    = _buf[2];
    _rxHeaderFlags = _buf[3];
    if (_promiscuous ||
	_rxHeaderTo == _thisAddress ||
	_rxHeaderTo == RH_BROADCAST_ADDRESS)
    {
	_rxGood++;
	_rxBufValid = true;
    }
}

bool RH_RF95::available()
{
    if (_mode == RHModeTx)
	return false;
    setModeRx();
    return _rxBufValid; // Will be set by the interrupt handler when a good message is received
}

void RH_RF95::clearRxBuf()
{
    ATOMIC_BLOCK_START;
    _rxBufValid = false;
    _bufLen = 0;
    ATOMIC_BLOCK_END;
}

bool RH_RF95::recv(uint8_t* buf, uint8_t* len)
{
    if (!available())
	return false;
    if (buf && len)
    {
	ATOMIC_BLOCK_START;
	// Skip the 4 headers that are at the beginning of the rxBuf
	if (*len > _bufLen-RH_RF95_HEADER_LEN)
	    *len = _bufLen-RH_RF95_HEADER_LEN;
	memcpy(buf, _buf+RH_RF95_HEADER_LEN, *len);
	ATOMIC_BLOCK_END;
    }
    clearRxBuf(); // This message accepted and cleared
    return true;
}

bool RH_RF95::send(const uint8_t* data, uint8_t len)
{
    if (len > RH_RF95_MAX_MESSAGE_LEN)
	return false;

    waitPacketSent(); // Make sure we dont interrupt an outgoing message
    setModeIdle();

    // Position at the beginning of the FIFO
    spiWrite(RH_RF95_REG_0D_FIFO_ADDR_PTR, 0);
    // The headers
    spiWrite(RH_RF95_REG_00_FIFO, _txHeaderTo);
    spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFrom);
    spiWrite(RH_RF95_REG_00_FIFO, _txHeaderId);
    spiWrite(RH_RF95_REG_00_FIFO, _txHeaderFlags);
    // The message data
    spiBurstWrite(RH_RF95_REG_00_FIFO, data, len);
    spiWrite(RH_RF95_REG_22_PAYLOAD_LENGTH, len + RH_RF95_HEADER_LEN);

    setModeTx(); // Start the transmitter
    // when Tx is done, interruptHandler will fire and radio mode will return to STANDBY
    return true;
}

bool RH_RF95::printRegisters()
{
#ifdef RH_HAVE_SERIAL
    uint8_t registers[] = { 0x01, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x014, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27};

    uint8_t i;
    for (i = 0; i < sizeof(registers); i++)
    {
	Serial.print(registers[i], HEX);
	Serial.print(": ");
	Serial.println(spiRead(registers[i]), HEX);
    }
#endif
    return true;
}

uint8_t RH_RF95::maxMessageLength()
{
    return RH_RF95_MAX_MESSAGE_LEN;
}

bool RH_RF95::setFrequency(float centre)
{
    // Frf = FRF / FSTEP
    uint32_t frf = (centre * 1000000.0) / RH_RF95_FSTEP;
    spiWrite(RH_RF95_REG_06_FRF_MSB, (frf >> 16) & 0xff);
    spiWrite(RH_RF95_REG_07_FRF_MID, (frf >> 8) & 0xff);
    spiWrite(RH_RF95_REG_08_FRF_LSB, frf & 0xff);

    return true;
}

void RH_RF95::setModeIdle()
{
    if (_mode != RHModeIdle)
    {
	spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_STDBY);
	_mode = RHModeIdle;
    }
}

bool RH_RF95::sleep()
{
    if (_mode != RHModeSleep)
    {
	spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_SLEEP);
	_mode = RHModeSleep;
    }
    return true;
}

void RH_RF95::setModeRx()
{
    if (_mode != RHModeRx)
    {
	spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_RXCONTINUOUS);
	spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x00); // Interrupt on RxDone
	_mode = RHModeRx;
    }
}

void RH_RF95::setModeTx()
{
    if (_mode != RHModeTx)
    {
	spiWrite(RH_RF95_REG_01_OP_MODE, RH_RF95_MODE_TX);
	spiWrite(RH_RF95_REG_40_DIO_MAPPING1, 0x40); // Interrupt on TxDone
	_mode = RHModeTx;
    }
}

void RH_RF95::setTxPower(int8_t power, bool useRFO)
{
    // Sigh, different behaviours depending on whther the module use PA_BOOST or the RFO pin
    // for the transmitter output
    if (useRFO)
    {
	if (power > 14)
	    power = 14;
	if (power < -1)
	    power = -1;
	spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_MAX_POWER | (power + 1));
    }
    else
    {
	if (power > 23)
	    power = 23;
	if (power < 5)
	    power = 5;

	// For RH_RF95_PA_DAC_ENABLE, manual says '+20dBm on PA_BOOST when OutputPower=0xf'
	// RH_RF95_PA_DAC_ENABLE actually adds about 3dBm to all power levels. We will us it
	// for 21, 22 and 23dBm
	if (power > 20)
	{
	    spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_ENABLE);
	    power -= 3;
	}
	else
	{
	    spiWrite(RH_RF95_REG_4D_PA_DAC, RH_RF95_PA_DAC_DISABLE);
	}

	// RFM95/96/97/98 does not have RFO pins connected to anything. Only PA_BOOST
	// pin is connected, so must use PA_BOOST
	// Pout = 2 + OutputPower.
	// The documentation is pretty confusing on this topic: PaSelect says the max power is 20dBm,
	// but OutputPower claims it would be 17dBm.
	// My measurements show 20dBm is correct
	spiWrite(RH_RF95_REG_09_PA_CONFIG, RH_RF95_PA_SELECT | (power-5));
    }
}

// Sets registers from a canned modem configuration structure
void RH_RF95::setModemRegisters(const ModemConfig* config)
{
    spiWrite(RH_RF95_REG_1D_MODEM_CONFIG1,       config->reg_1d);
    spiWrite(RH_RF95_REG_1E_MODEM_CONFIG2,       config->reg_1e);
    spiWrite(RH_RF95_REG_26_MODEM_CONFIG3,       config->reg_26);
}

// Set one of the canned FSK Modem configs
// Returns true if its a valid choice
bool RH_RF95::setModemConfig(ModemConfigChoice index)
{
    if (index > (signed int)(sizeof(MODEM_CONFIG_TABLE) / sizeof(ModemConfig)))
        return false;

    ModemConfig cfg;
    memcpy_P(&cfg, &MODEM_CONFIG_TABLE[index], sizeof(RH_RF95::ModemConfig));
    setModemRegisters(&cfg);

    return true;
}

void RH_RF95::setPreambleLength(uint16_t bytes)
{
    spiWrite(RH_RF95_REG_20_PREAMBLE_MSB, bytes >> 8);
    spiWrite(RH_RF95_REG_21_PREAMBLE_LSB, bytes & 0xff);
}

@peekay123 I know your busy but by taking a quick glance at this Arduino library do you think porting for the Photon would much more involved than the changes needed for the INA2331 3 channel current voltage sensor?

The RFM69 is another wireless chip that is from the same company that has a library in the build IDE but I’m not sure how much help it could provide in the porting process.

These radios are some of the best I have ever tested and they sell for $8 so I’m wanting to use them to add on sensors to the web connected Photon and Electron which will be withing a 1 mile radius. These would be great for adding sensors for a home security system to monitor all kinds of stuff inside or way outside the house where WiFi does not reach.

Either way I’m excited about the range and cost so I’m really looking forward to figuring out how to get this working with Particle if possible.

Let me know what you think if you have time :smiley:

@RWB, I’ll be looking at the library soon :wink:

3 Likes

I have done some more testing on the LoRa units and range.

I picked up one of these units because they looked to have better range due to better antennas.

It’s the Mini Ultra Pro + RFM95W radio + 5db Antenna. They claim in their test they have gotten 8km range in a semi urban environment.

From my testing this unit performs almost as good as a RFM95w + 3 inch copper wire antenna which was suprising and really good news because it means you do not need the fancy antenna or SMA connector on the PCB to get 1 mile ranges in the city.

The RadioHead library examples for the RFM95 radios have some errors in them in the Void Setup code where you initialize the radio and set the Pin numbers for your actual microcontroller for the CS and reset lines. I got that worked out and now I can use what they call the reliable code that checks if your transmission was received and retries the transmit multiple times if the receiver does not successfully receive the transmission which is nice if you need to make sure your data is received.

So I don’t think this port is going to be very hard once the code is correct. I’ll post more later when I have some more time to play with these. They are very impressive range wise though.

3 Likes

Sorry.But I use that Library with one is Arduino nano + RFM98W and one is Arduino Uno + RFM98W. Sometime, it works. But later, it is error “init issues”. Can you help me fix it?

Hello folks,
I am interested in use the RFM95/96 (LORA) with the Particle Photon, Not sure if anyone did a radiohead library porting to particle photon ?
I did personally some test using the rfm96 433MHZ and RFM95 ,915MHZ, I posted my results on the LowPowerLab forum.
this is the link if anyone interested .
https://lowpowerlab.com/forum/moteino/lora-rfm96-433mhz-range-test-gt5-2-miles-8-3km-!!!/msg12479/#msg12479

regards

@luisgcu What I did get going was some code to get the Photon + RFM95 915Mhz Adafruit Feather with Atmel 32u4 chip to talk together over the TX & RX lines.

I figured this was easier than trying to get an individual RFM95 chip working directly with the Photon because I am no expert at modifying libraries :smile: Plus the library for the Adafruit RFM95 Feather boards is already working.

Here is the code for the Photon which is connected to the Adafruit RFM95 Feather Board via the TX & RX lines:

#include "Particle.h"

// Constants
const size_t READ_BUF_SIZE = 256;

// Structures
typedef struct {
int temperature;
int humidity;
int pressure;
int luminosity;
int winddir;
int windspeed;
int rainfall;
} WeatherData;

// Forward declarations
void processBuffer();
void handleWeatherData(const WeatherData &data);

// Global variables
int counter = 0;
unsigned long lastSend = 0;

char readBuf[READ_BUF_SIZE];
size_t readBufOffset = 0;

void setup() {
Serial.begin(19200);

// Serial1 RX is connected to Arduino TX (1)
// Serial2 TX is connected to Arduino RX (0)
// Photon GND is connected to Arduino GND
Serial1.begin(19200);
}

void loop() {

// Read data from serial
while(Serial1.available()) {
if (readBufOffset < READ_BUF_SIZE) {
char c = Serial1.read();
if (c != '\n') {
// Add character to buffer
readBuf[readBufOffset++] = c;
}
else {
// End of line character found, process line
readBuf[readBufOffset] = 0;
processBuffer();
readBufOffset = 0;
}
}
else {
Serial.println("readBuf overflow, emptying buffer");
readBufOffset = 0;
}
}

}

void processBuffer() {
// Serial.printlnf("Received from Arduino: %s", readBuf);
WeatherData data;

if (sscanf(readBuf, "%d,%d,%d,%d,%d,%d,%d", &data.temperature, &data.humidity, &data.pressure,
&data.luminosity, &data.winddir, &data.windspeed, &data.rainfall) == 7) {

handleWeatherData(data);
}
else {
Serial.printlnf("invalid data %s", readBuf);
}
}

void handleWeatherData(const WeatherData &data) {
Serial.printlnf("got temperature=%d humidity=%d pressure=%d luminosity=%d winddir=%d windspeed=%d rainfall=%d",
data.temperature, data.humidity, data.pressure, data.luminosity, data.winddir, data.windspeed, data.rainfall);
}

Here is the code I’m running on the RFM95 Adafruit Feather board:

// Feather9x_RX
// -*- mode: C++ -*-
// Example sketch showing how to create a simple messaging client (receiver)
// with the RH_RF95 class. RH_RF95 class does not provide for addressing or
// reliability, so you should only use RH_RF95 if you do not need the higher
// level messaging abilities.
// It is designed to work with the other example Feather9x_TX

#include <SPI.h>
#include <RH_RF95.h>

/* for feather32u4 */
#define RFM95_CS 8
#define RFM95_RST 4
#define RFM95_INT 7

// Constants

// Structures
typedef struct {
  int temperature;
  int humidity;
  int pressure;
  int luminosity;
  int winddir;
  int windspeed;
  int rainfall;
} WeatherData;

// Forward declarations
void getWeatherData(WeatherData &data);
void sendWeatherData(const WeatherData &data);

// Global variables
char sendBuf[256];



// Change to 434.0 or other frequency, must match RX's freq!
#define RF95_FREQ 915.0

// Singleton instance of the radio driver
RH_RF95 rf95(RFM95_CS, RFM95_INT);

// Blinky on receipt
#define LED 13

void setup() 
{
  pinMode(LED, OUTPUT);     
  pinMode(RFM95_RST, OUTPUT);
  digitalWrite(RFM95_RST, HIGH);

  // Serial TX (1) is connected to Photon RX
  // Serial RX (0) is connected to Photon TX
  // Ardiuno GND is connected to Photon GND
  Serial.begin(19200);
  Serial1.begin(19200);
  
  delay(100);

  Serial.println("Feather LoRa RX Test!");
  
  // manual reset
   digitalWrite(RFM95_RST, LOW);
   delay(10);
   digitalWrite(RFM95_RST, HIGH);
   delay(10);

   while (!rf95.init()) {
   Serial.println("LoRa radio init failed");
   while (1);
   }
   Serial.println("LoRa radio init OK!");

  // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
   if (!rf95.setFrequency(RF95_FREQ)) {
      Serial.println("setFrequency failed");
      while (1);
   }
   Serial.print("Set Freq to: "); Serial.println(RF95_FREQ);


  // If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then 
  // you can set transmitter powers from 5 to 23 dBm:
   rf95.setTxPower(23, false);
}

void loop()
{

  WeatherData data;
  getWeatherData(data);
  sendWeatherData(data);
  

  /*
  if (rf95.available())
   {
    // Should be a message for us now   
    uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
    uint8_t len = sizeof(buf);
    
    if (rf95.recv(buf, &len))
    {
      digitalWrite(LED, HIGH);
      RH_RF95::printBuffer("Received: ", buf, len);
      Serial.print("Got: ");
      Serial.println((char*)buf);
       Serial.print("RSSI: ");
      Serial.println(rf95.lastRssi(), DEC);
      delay(10);
      // Send a reply
      uint8_t data[] = "And hello back to you";
      rf95.send(data, sizeof(data));
      rf95.waitPacketSent();
      Serial.println("Sent a reply");
      digitalWrite(LED, LOW);
    }
    else
    {
      Serial.println("Receive failed");
    }
  }*/
  delay(1000);
}

void getWeatherData(WeatherData &data) {
  // This just generates random data for testing
  data.temperature = rand();
  data.humidity = rand();
  data.pressure = rand();
  data.luminosity = rand();
  data.winddir = rand();
  data.windspeed = rand();
  data.rainfall = rand();
}

void sendWeatherData(const WeatherData &data) {

  snprintf(sendBuf, sizeof(sendBuf), "%d,%d,%d,%d,%d,%d,%d\n",
      data.temperature, data.humidity, data.pressure, data.luminosity, data.winddir, data.windspeed, data.rainfall);
  Serial1.print(sendBuf);
}

Part of this code comes from this LORA HAM project: https://github.com/travisgoodspeed/loraham

And the Serial to Serial transfer part came from this tutorial: https://github.com/rickkas7/serial_tutorial/blob/master/example1.md

1 Like

@RWB thanks for sharing your code… interesting approach… we are in the same page. I am not an expert either to reconvert an entire library…
What I did is to import the entire radiohead library, it compile most of the code… but the I get stuck at point when calling the spi.h
I am interesting in using the photon +RFM95 to implement a simple Lora Wan gateway.
So will continue digging.

seems like here is the way to do that…

Building for Particle Photon
The Photon is not supported by the Arduino IDE, so it takes a little effort to set up a build environment. Heres what we did to enable building of RadioHead example sketches on Linux, but there are other ways to skin this cat. Basic reference for getting stated is: http://particle-firmware.readthedocs.org/en/develop/build/

Download the ARM gcc cross compiler binaries and unpack it in a suitable place:
cd /tmp
wget https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2
tar xvf gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2
If dfu-util and friends not installed on your platform, download dfu-util and friends to somewhere in your path
cd ~/bin
wget http://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-util
wget http://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-suffix
wget http://dfu-util.sourceforge.net/releases/dfu-util-0.8-binaries/linux-i386/dfu-prefix
Download the Particle firmware (contains headers and libraries require to compile Photon sketches) to a suitable place:
cd /tmp
wget https://github.com/spark/firmware/archive/develop.zip
unzip develop.zip
Make a working area containing the RadioHead library source code and your RadioHead sketch. You must rename the sketch from .pde or .ino to application.cpp
cd /tmp
mkdir RadioHead
cd RadioHead
cp /usr/local/projects/arduino/libraries/RadioHead/*.h .

  • cp /usr/local/projects/arduino/libraries/RadioHead/*.cpp .
  • cp /usr/local/projects/arduino/libraries/RadioHead/examples/cc110/cc110_client/cc110_client.pde application.cpp

Edit application.cpp and comment out any #include <SPI.h> so it looks like:
// #include <SPI.h>
Connect your Photon by USB. Put it in DFU mode as descibed in Photon documentation. Light should be flashing yellow
Compile the RadioHead sketch and install it as the user program (this does not update the rest of the Photon firmware, just the user part:
cd /tmp/firmware-develop/main
PATH=$PATH:/tmp/gcc-arm-none-eabi-5_2-2015q4/bin make APPDIR=/tmp/RadioHead all PLATFORM=photon program-dfu
You should see RadioHead compile without errors and download the finished sketch into the Photon.

@RWB good news I was able to get working the RFm96 with particle photon using ATOM(dev ide??) , is very simple the only need is to create a project folder with 2 folders one for the applications and second named “src” containing all radiohead library files *.h and *.cpp , the trick is to copy from radiohead library 2 files named atomic.h and atomic.cpp and put those files inside src folder, then yo need to edit the file radiohead.h go to line 859 and 861 and edit the location of the atomic.h

#include atomic.h

regards

@luisgcu Sweet :slight_smile:

That’s really great news!

Can you take a screen shot of how your folders and files are setup in Particle DEV so I can get a better idea of what it should look like?

Can you post the files to Github, or on here or somewhere so we can get the library together for others to use and advance for all of us?

I’m anxious to try this myself with an Electron & Photon.

Thanks for letting me know you got this working? Did it take you some time to figure this out?

not really a lot of time I just give another try using the guide to compile and upload the firmware using Linux, but won’t work for me… I got some error and I am not good on linux to dig what was going on , so i started to dig into the radio headlibrary, I notice the files that were added to interface with the ARM3 based CPU, RadioHead was updated to work with those CPU some time ago .
So i decided to reorganize the radiohead library folder to fit the recommended structure required by atom.
in there I spend some time since every time i tried to compile it gave an error for a file in very specific path “Rhutil/atomic.h” so the reference to that file is inside the file RadioHead.h line 859 and 861 , what I did was to remove the path to the folder Rhutil and place the atomic.h and cpp inside SRC folder where the rest of the files taht we previously mention to move to that folder.

@luisgcu

I’m trying to create a working library off your suggestions so I’m going to ask a few questions make sure I’m doing this correctly.

Is this what the modified RadioHead.h file is supposed to look like on Line #859 & 861?

I added the Atomic.h file from the RadioHead/RHutil folder. There was no Atomic.cpp.

I also added the HarwareSerial.cpp, HarwareSerial.h, and RadioHead.h to the folder named “src” the same as you did in your screenshot.

I then added the RF95_server example code file to the main folder. I need do you know what all needs to be called at the beginning of the rf95_server.ino to make sure the RF95 radio & library work properly?

I see in your mesh example code you start off calling these libraries:

Do you have any idea what would be needed to run the rf95_server.ino example code?

Can you post a picture or let me know what pins you are using to connect the RF95 module to the Photon?

Most importantly how is it working for you? Have you done much testing or did you just now get the code to compile?

@RWB
in your application rf95-server.ino you have to get rid of the spi.h --> //#include <SPI.h>
the wring diagram you can use this as reference


I see your RadioHead.h file is not exactly like mine, maybe your is not the latest version.
regards

@luisgcu OK, I got the library to compile after a few tweaks.

First, on the RadioHead.h file I had to change lines 916 and 918 instead lines 859 & 861. See below:

I had to delete these 2 files from the scr folder because they were referencing a RF24 folder that I did not import or need to import because I’m not using RF24 radios so this threw a compile error.

Now I can successfully compile the RF95_server example .ino

Now I just need to figure out how to properly connect the RF95 chip to the Photon :slight_smile:

Thanks for the wiring diagram! That’s perfect.

I’ll have to order up a few RF95 breakout boards and give it a go!

I noticed in the to of the RF95_server example that you need to add the digital line numbers that you use to connect to the NSS & DIO0 pins on the RF95 chip.

For instance on the Rocket Scream Breakout, they use digital pin 5 and 2 for pins NSS & DIO0.

The schematic for the RF95 chip on the RocketScream board is here:

For the Adafruit Board they using digital pins 8 and 3:

The RF95 board schematic for the Adafruit M0 board is here:

So you just need to change those pin numbers to whatever digital pins you use on the Photon or Electron when wiring up your RF95 chip to it.

Did you have the RF95 connected to a Photon with successful communication?

@RWB & @luisgcu, the easiest way to set up a new project in Dev is via the dedicated button.
This will provide you with the correct file structure and create a project.properties file which adds a new layer of convenience in regards of importing and using libraries.
To add dependencies (for libs) without the need to actually download the sources of already published libraries, just use the library manager

1 Like

Thanks,
we definitely need to play more with this platform.

1 Like

FYI for anyone using an RFM95 on the non default SPI config eg. SPI1

I have managed to get the radiohead library to compile by simply replacing all SPI with SPI1 in the RHHardwareSPI.cpp file. I have only just started testing this but so the it appears initialize the device correctly.

I also didn’t seem to need to include the atomic.h or half of the other source files? The compiler never asked for most of them so I assume that they are dependencies for other platforms

Hopefully someone with more experience will be able to do a better port job than this in the future though! :joy:

1 Like