Using the Featherwing M0 LoRa w/ Boron

I am working on a project that is using the Boron board for cellular connection to the cloud. If the device I am using is not in cellular range, I want to use the featherwing M0 LoRa board as a LoRa peripheral to send data to another LoRa transceiver nearby.

I am having a hard time getting the right libraries and code in order. I downloaded and installed the RadioHead particle fork and code from here. It fails to initialize the manager, I put the code at the bottom

A few questions to start:

  1. is there any reason I can't use this board strictly as an addition to the Boron to use LoRa?
  2. I attached the featherwing on-top of the boron exactly, do these SPI pins hook up the same way?
  3. I defined the CS, interrupt, and IRQ pins but I don't know where to hook them into the RadioHead Library.

Any help would be greatly appreciated!!

#include <RH_RF95.h>
#include <RHReliableDatagram.h>

#define RFM95_CS D3
#define RFM95_RST D5
#define RFM95_INT D2

#define MY_ADDRESS      3
#define SERVER_ADDRESS  2
#define TXPWR           5

RH_RF95 rf95;
RHDatagram manager(rf95, MY_ADDRESS);
const float FREQ = 915.0;

void setup() {
  Serial.begin(115200);
  setupRadio();

}

void loop() {
  Serial.println("Main Loop");
  delay(5000);
}

void setupRadio(void)
{
  if (manager.init())
  {
    if (!rf95.setFrequency(FREQ))
      Serial.println("Unable to set RF95 frequency");
    if (!rf95.setModemConfig(RH_RF95::Bw500Cr45Sf128))
      Serial.println("Invalid setModemConfig() option");
    rf95.setTxPower(TXPWR);
    Serial.println("RF95 radio initialized.");
  }
  else
    Serial.println("RF95 radio initialization failed.");
  Serial.print("RF95 max message length = ");
  Serial.println(rf95.maxMessageLength());
}

@krixen1 ,

You might find this thread interesting:

A few of us have been working on this solution for a while and you might be able to benefit from some of what we have learned. In particular, we both started with the LoRA Feathering in the beginning of the project and we did find that it can work.

Thanks,

Chip

1 Like

Hello @chipmc Thank you so much for pointing me to this post, sorry for the late reply. I’ve been using it and had success very quickly sending data through the boron/featherwing lora combo.

I have a couple of questions:

  1. You say that you used the LoRa featherwing at the beginning of the project, did you switch to something else? if so, why?

  2. I am pretty new to using LoRa, I notice that all communication seems to require an address to send data to, is this required by the protocol? The reason I ask is because I want to setup new nodes over time that can connect into a mesh of sorts. I was hoping I could broadcast out indiscriminately and see if I get any responses back. Is this possible with LoRa?

Thank you again

@krixen1 ,

Glad you are seeing some success.

  1. The feathering is a great way to get started and it can support early proofs of concept. But, as you move to deploying these devices for extended periods, I believe you need to develop a more integrated approach. @jgskarda developed a customized board that integrated an microcontroller, the LoRA radio and sensors as this fit his needs. I modified my existing Boron carrier board to include an optional LoRA radio so I could share hardware and software with my existing devices. I shared pictures of the carrier board and the EAGLE files in that thread.

  2. LoRA devices get assigned a node number when they “join” a network. These node numbers are how the devices address their communications. In our approach, the gateway is node zero and all the nodes sent their data to the gateway. If a node cannot reach the gateway, it will attempt to find a node that can relay the signal - this is an adhoc mesh network as these routes can be torn down and reestablished as needed. This is part of the Radio Head library and all you need to do is leave a node in “listening mode” and it will take care of this work without any help from you.

I hope this helps, please share as you make progress.

Chip

1 Like

@chipmc

Thank you for the info, on point #2. Let’s say I have a mesh network, mostly in sleep mode, and I want to add a new device to the mesh. I would put the new device into listening mode and I would need to have the other devices, that are already meshed, send a signal out looking for new nodes?

Also, what function puts the device into listening mode? is it just manager.available()?

@krixen1,

Here is how I do it:

  1. General principle, one gateway per network all other devices are nodes. Every “conversation” is initiated by a node. Gateways only acknowledge a message initiated by a node.

  2. Since we are solar powered, there is a “window” when all the devices wake up. The gateway goes into listening mode while the nodes take turns sending their data. When not sending data, nodes are also listening.

  3. If a new node joins the network, it has an “unconfigured” node number - in my case 11 - and it sends a “join” message that includes a unique identifier, in my case the Particle deviceID. The gateway, accepts the join request, stores the identifier and assigns an open node number in a “join acknowledgement”. From then on, the new node uses its assigned node number and starts reporting.

  4. After the reporting window is over, the gateway and nodes go back to sleep till next time.

Does this make sense?

Chip

Yes this makes sense to me. The part i am confused about is how the function sendToWait takes in an address you are trying to talk to.

Let’s say I have all the nodes in listening mode and a new node wakes up to join the mesh. It should send a message to the ‘gateway’, correct? manager.sendtoWait(buf, strlen((char *)buf), GATE_WAY_ADDRESS)

Will other nodes in the area respond to this? If so, what if multiple nodes hear this message? since the client is the same address every time, does that not matter?

Not sure if you can share code on your procedure but that would be helpful.

The gateway’s node address is always “0”.

Here is my code:

Take a look and let me know what questions you have.

Chip

1 Like

thanks! going to get right into this!

So every node is a LoRA gateway listener also? but the nodes just pass the messages along?

Does each node store the ID of neighbor nodes (if not in range of gateway) or is only the gateway storing this info?

Sorry about the barrage of questions, one more I had about something you said earlier. In a very simple example, would this work:

  1. I setup a gateway node to listen all the time. this is address 0
  2. I setup a node that sends data periodically and also listens at all other times. this is address 1
  3. I setup a new node that is too far from the gateway, but can reach the other node. If I tell it to send a message to address 0 (gateway). If the node (address 1) is listening, it will automatically know to relay that data to the gateway because I told it to talk to address 0 which corresponds to the gateway? that is all done by the RH library?

I love your code, I just want to build it piece by piece so I understand every part thoroughly if that makes sense.

@krixen1 ,

Yes, this scenario will work. Your new node 2 will fail to connect to the gateway. Node 1, which is listening will notice this and then relay the message to the gateway and the acknowledgement from the gateway to the node automagically. You can tell this is happening because the message header will say that hops = 1.

One thing, node 2 will remember that the route to the gateway is through node 1 and will continue to use that route until it fails. Then, it will go back into route discovery mode. In this way, the routes are dynamic. You can, however, also specify the route hardcoding it into the mesh but this is more for testing as the dynamic approach is better.

Good luck and glad to see more folks getting into LoRA.

Chip

I didn’t have success with this unfortunately, the new further node never made it to the gateway. I can’t monitor what is happening on the node in range of the gateway because i had to place it on the ground. I can post my code here though, its a simplified version.

{
	Serial.begin(9600);

	// Wait for a USB serial connection for up to 15 seconds
	waitFor(Serial.isConnected, 15000);

	if (!manager.init())
		Serial.println("init failed");

	// Setup ISM frequency
	driver.setFrequency(frequency);

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

// Dont put this on the stack:
uint8_t recvBuf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t sendBuf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t buf[RH_RF95_MAX_MESSAGE_LEN];
uint8_t receivedClientAddress;

int counter = 0;

void loop()
{
	Serial.println("Sending to rf95_reliable_datagram_server");

	// Send a message to manager_server
	snprintf((char *)buf, sizeof(buf), "to server counter=%d", ++counter);

	if (manager.sendtoWait(buf, strlen((char *)buf), SERVER_ADDRESS))
	{
		// Now wait for a reply from the server
		uint8_t len = sizeof(buf);
		uint8_t from;
		if (manager.recvfromAckTimeout(buf, &len, 2000, &from))
		{
			buf[len] = 0;
			Serial.printlnf("got reply from 0x%02x rssi=%d %s", from, driver.lastRssi(), (char *) buf);
		}
		else
		{
			Serial.println("No reply, is rf95_reliable_datagram_server running?");
		}
	}
	else
		Serial.println("sendtoWait failed");
	
	uint8_t len = sizeof(buf);
	uint8_t from;
	if (manager.recvfromAck(buf, &len, &from))
	{
		buf[len] = 0;
		Serial.printlnf("got packet from 0x%02x rssi=%d %s", from, driver.lastRssi(), (char *)buf);

		int request = 0;
		char *cp = strchr((char *)buf, '=');
		if (cp) {
			request = atoi(cp + 1);
		}

		snprintf((char *)buf, sizeof(buf), "request=%d rssi=%d", request, driver.lastRssi());

		// Send a reply back to the originator client
		if (!manager.sendtoWait(buf, strlen((char *)buf), from))
			Serial.println("sendtoWait failed");
	}

	delay(500);
}```

anything stupid i am missing?

I should note that I do change their address. so both are being received by the gateway and then after i split, one eventually dies out but is in clear line of sight of the other node.

@krixen1 I am not implementing these calls quite the same way but I am not sure this will work. The conditional for manager.recvfromAckTimeout() will be executed before the gateway can compose a response. I believe the best approach is to call the receive function often - the library actually suggests you put it in the main loop.

https://www.airspayce.com/mikem/arduino/RadioHead/classRHReliableDatagram.html#af172410589842760babf938a0538334c

I have had good luck calling manager.recvfromAck() in a tight loop to ensure I don't miss a message.

I also use the blue led on d7 to help "see" what is going on with the lora radio. Could be helpful in troubleshooting when away from your desk.

Hope this helps,

Chip

1 Like

@chipmc

Didn’t have success this morning, I now send a message from the nodes every second but i am constantly checking the recvFromAck. I check to see if the destination sent matches the current address, if it does ill read it / print info. If its not for me, i just move on.

I think I came across a big difference in our code. I am using a library that does not include the RH Mesh code, whereas yours does. It does seem that you NEED to use the RH mesh manager and not just the reliable datagram manager. The RHMesh.h file talks about how it will automatically find a path to the destination and the RH datagram h file does not.

I am going to work on implementing the mesh manager instead

edit: that was the ticket, was just using the wrong manager. I pulled that forked repo which did not include the RH mesh

@krixen1 ,

OK, perhaps a bit of a reset then. I understand you want mesh functionality. For that, you will need to use the branch that @jgskarda made of the Particle RH LoRA library. We have not been successful in getting Particle to agree to merge the branch so this may be a new library someday - we will see.

Take a look at this simplified code - it should work and let you test mesh functionality before adding all the complexity that comes with an actual functional solution.

Edit your pin assignments based on your implementation.

A minimal mesh node sketch

/*
 * Project Simple-LoRA-Mesh-Node
 * Description: Simple code to test mesh functionality
 * Author: Chip McClelland based on example code in the RF9X-RK library
 * Date:2/15/23
 */

// Mesh has much greater memory requirements, and you may need to limit the
// max message length to prevent wierd crashes
#define RH_MESH_MAX_MESSAGE_LEN 50

#include "Particle.h"
#include <RHMesh.h>
#include <RH_RF95.h>

SerialLogHandler logHandler(LOG_LEVEL_INFO);     // Easier to see the program flow

// In this small artifical network of 4 nodes,
#define CLIENT_ADDRESS 1
#define SERVER1_ADDRESS 2
#define SERVER2_ADDRESS 3
#define SERVER3_ADDRESS 4
// Edit the RHRouter.cpp file to test different mesh topologies

// Prototypes and System Mode calls
SYSTEM_THREAD(ENABLED);                             // Means my code will not be held up by Particle processes.
SYSTEM_MODE(MANUAL);

// #define RF95_FREQ 915.0
#define RF95_FREQ 926.84

//Define pins for the RFM9x on the Feather Exapander board - this is not the same as on the carrier board
const pin_t RFM95_CS =      D5;                     // Carrier Board setting
const pin_t RFM95_RST =     D6;                     // Carrier Board Setting
// const pin_t RFM95_CS =      D6;                  // Featherwing Setting
// const pin_t RFM95_RST =     A5;                  // Featherwing Setting
const pin_t RFM95_INT =     D2;                     // Interrupt from radio
const pin_t BLUE_LED  =     D7;

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

// Class to manage message delivery and receipt, using the driver declared above
RHMesh manager(driver, CLIENT_ADDRESS);

void setup() 
{
	pinMode(BLUE_LED,OUTPUT);
	digitalWrite(BLUE_LED,HIGH);

	waitFor(Serial.isConnected, 10000);				// Wait for serial connection

	if (!manager.init()) Log.info("init failed");	// Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36
		
	driver.setFrequency(RF95_FREQ);					// Frequency is typically 868.0 or 915.0 in the Americas, or 433.0 in the EU

	driver.setTxPower(23, false);					// you can set transmitter powers from 5 to 23 dBm:

	digitalWrite(BLUE_LED,LOW);
}

uint8_t data[] = "Hello World!";
uint8_t buf[RH_MESH_MAX_MESSAGE_LEN];				// Dont put this on the stack:

void loop()
{
	Log.info("Sending to manager_mesh_server1");

	// Send a message to manager_server
	// A route to the destination will be automatically discovered.
  	digitalWrite(BLUE_LED,HIGH);
	if (manager.sendtoWait(data, sizeof(data), SERVER1_ADDRESS) == RH_ROUTER_ERROR_NONE) {
    	// It has been reliably delivered to the next node.
    	// Now wait for a reply from the ultimate server
    	uint8_t len = sizeof(buf);
    	uint8_t from;     
		uint8_t hops;
   		 if (manager.recvfromAckTimeout(buf, &len, 3000, &from)){
			buf[len] = 0;
			Serial.printlnf("got reply from 0x%02x rssi=%d %s and %d hops", from, driver.lastRssi(), (char *) buf, hops);
		}
		else {
			Log.info("No reply, is rf95_mesh_server1, rf95_mesh_server2 and rf95_mesh_server3 running?");
		}
	}
	else Log.info("sendtoWait failed. Are the intermediate mesh servers running?");
  	digitalWrite(BLUE_LED,LOW);
	delay(3000);
}


A minimal Mesh Gateway sketch

/*
 * Project Simple-LoRA-Mesh-Server
 * Description: Simple implementation based on RH9X-RK example code
 * Author: Chip McClelland
 * Date: 2/15/23
 */

// Mesh has much greater memory requirements, and you may need to limit the
// max message length to prevent wierd crashes
#define RH_MESH_MAX_MESSAGE_LEN 50

#include "Particle.h"
#include <RHMesh.h>
#include <RH_RF95.h>

SerialLogHandler logHandler(LOG_LEVEL_INFO);     // Easier to see the program flow

// In this small artifical network of 4 nodes,
#define CLIENT_ADDRESS 1
#define SERVER1_ADDRESS 2
#define SERVER2_ADDRESS 3
#define SERVER3_ADDRESS 4

// Prototypes and System Mode calls
SYSTEM_THREAD(ENABLED);                             // Means my code will not be held up by Particle processes.
SYSTEM_MODE(MANUAL);

// #define RF95_FREQ 915.0
#define RF95_FREQ 926.84

//Define pins for the RFM9x on the Feather Exapander board - this is not the same as on the carrier board
const pin_t RFM95_CS =      D5;                     // SPI Chip select pin - Standard SPI pins otherwise was A5
const pin_t RFM95_RST =     D6;                     // Radio module reset was D3
// const pin_t RFM95_CS =      D6;                     // SPI Chip select pin - Standard SPI pins otherwise was A5
// const pin_t RFM95_RST =     A5;                     // Radio module reset was D3
const pin_t RFM95_INT =     D2;                     // Interrupt from radio
const pin_t BLUE_LED  =     D7;

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

// Class to manage message delivery and receipt, using the driver declared above
RHMesh manager(driver, SERVER1_ADDRESS);

void setup() 
{
	pinMode(BLUE_LED,OUTPUT);
	digitalWrite(BLUE_LED,HIGH);

	waitFor(Serial.isConnected, 10000);				// Wait for serial connection

	Log.info("Starting the Simple LoRA Server");

	if (!manager.init()) Log.info("init failed");	// Defaults after init are 434.0MHz, 0.05MHz AFC pull-in, modulation FSK_Rb2_4Fd36

	driver.setFrequency(RF95_FREQ);					// Frequency is typically 868.0 or 915.0 in the Americas, or 433.0 in the EU

	driver.setTxPower(23, false);					// you can set transmitter powers from 5 to 23 dBm:

	digitalWrite(BLUE_LED,LOW);
}

uint8_t data[] = "And hello back to you from server1";
uint8_t buf[RH_MESH_MAX_MESSAGE_LEN];				// Dont put this on the stack:

void loop()
{
	uint8_t len = sizeof(buf);
	uint8_t from;
	uint8_t hops;
	if (manager.recvfromAck(buf, &len, &from, &hops))
	{
    digitalWrite(BLUE_LED,HIGH);
		buf[len] = 0;
			Serial.printlnf("got request from : 0x%02x rssi=%d %s and %d hops", from, driver.lastRssi(), (char *) buf, hops);
    
    // Send a reply back to the originator client
    if (manager.sendtoWait(data, sizeof(data), from) != RH_ROUTER_ERROR_NONE)
      Log.info("sendtoWait failed");
    digitalWrite(BLUE_LED,LOW);
	}

}

You can test the mesh network by editing the RHRouter.cpp file so you can keep all your gear on your desktop and monitor the serial messages.

Hope this helps,

Chip

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.