.Net Meadow RFM95/96/97/98 LoRa library Part7

Transmit Interrupt

Starting with the TransmitBasic sample application I modified the code so that a hardware interrupt (specified in RegDioMapping1) was generated on TxDone (FIFO Payload Transmission completed).

The application inserts a message into the RFM95 transmit FIFO every 10 seconds with confirmation of transmission displayed shortly afterwards

      public Rfm9XDevice(IIODevice device, ISpiBus spiBus, IPin chipSelectPin, IPin resetPin, IPin interruptPin)
      {
         // Chip select pin configuration
         ChipSelectGpioPin = device.CreateDigitalOutputPort(chipSelectPin, initialState: true);
         if (ChipSelectGpioPin == null)
         {
            Console.WriteLine("ChipSelectGpioPin == null");
         }

         // Factory reset pin configuration
         IDigitalOutputPort resetGpioPin = device.CreateDigitalOutputPort(resetPin);
         if (resetGpioPin == null)
         {
            Console.WriteLine("resetGpioPin == null");
         }
         resetGpioPin.State = false;
         Task.Delay(10);
         resetGpioPin.State = true;
         Task.Delay(10);

         // Interrupt pin for RX message & TX done notification 
         InterruptGpioPin = device.CreateDigitalInputPort(interruptPin, InterruptMode.EdgeRising);
         InterruptGpioPin.Changed += InterruptGpioPin_ValueChanged;

         Rfm9XLoraModem = new SpiPeripheral(spiBus, ChipSelectGpioPin);
         if (Rfm9XLoraModem == null)
         {
            Console.WriteLine("Rfm9XLoraModem == null");
         }
      }

      private void InterruptGpioPin_ValueChanged(object sender, DigitalInputPortEventArgs args)
      {
        byte irqFlags = this.RegisterReadByte(0x12); // RegIrqFlags
        this.RegisterWriteByte(0x12, 0xff);// Clear RegIrqFlags

        //Console.WriteLine(string.Format("RegIrqFlags:{0}", Convert.ToString(irqFlags, 2).PadLeft(8, '0')));
         if ((irqFlags & 0b00001000) == 0b00001000)  // TxDone
         {
            Console.WriteLine("Transmit-Done");
         }
      }
…
   public class MeadowApp : App<F7Micro, MeadowApp>
   {
      private Rfm9XDevice rfm9XDevice;

      public MeadowApp()
      {
         ISpiBus spiBus = Device.CreateSpiBus(500);
         if (spiBus == null)
         {
            Console.WriteLine("spiBus == null");
         }

         rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D11, Device.Pins.D10);

         // Put device into LoRa + Sleep mode
         rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // RegOpMode 

         // Set the frequency to 915MHz
         byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 }; // RegFrMsb, RegFrMid, RegFrLsb
         rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes);

         // More power PA Boost
         rfm9XDevice.RegisterWriteByte(0x09, 0b10000000); // RegPaConfig

         // Interrupt on TxDone
         rfm9XDevice.RegisterWriteByte(0x40, 0b01000000); // RegDioMapping1 0b00000000 DI0 TxDone

         while (true)
         {
            // Set the Register Fifo address pointer
            rfm9XDevice.RegisterWriteByte(0x0E, 0x00); // RegFifoTxBaseAddress 

            // Set the Register Fifo address pointer
            rfm9XDevice.RegisterWriteByte(0x0D, 0x0); // RegFifoAddrPtr 

            string messageText = "Hello LoRa!";

            // load the message into the fifo
            byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
            rfm9XDevice.RegisterWrite(0x0, messageBytes); // RegFifo 

            // Set the length of the message in the fifo
            rfm9XDevice.RegisterWriteByte(0x22, (byte)messageBytes.Length); // RegPayloadLength
            Console.WriteLine("Sending {0} bytes message {1}", messageBytes.Length, messageText);
            rfm9XDevice.RegisterWriteByte(0x01, 0b10000011); // RegOpMode 

            Task.Delay(10000).Wait();
         }
      }
   }

The output in the debug window

'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'. 
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\TransmitInterrupt\bin\Debug\net472\App.exe'. Symbols loaded.
'App.exe' (CLR v4.0.30319: App.exe): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\TransmitInterrupt\bin\Debug\net472\Meadow.dll'. 
The program '[11164] App.exe: Program Trace' has exited with code 0 (0x0).
The program '[11164] App.exe' has exited with code 0 (0x0).
.
.
DirectRegisterAccess = True
.
.
Sending 11 bytes message Hello LoRa!
Transmit-Done
Sending 11 bytes message Hello LoRa!
Transmit-Done
Sending 11 bytes message Hello LoRa!
Transmit-Done

On the Arduino test client the serial monitor displayed

13:02:09.098 -> Sending HeLoRa World! 4
13:02:19.130 -> Message: ⸮LoRaIoT1Maduino2at 79.7,ah 39,wsa 6,wsg 13,wd 28.13,r 0.00,
13:02:19.177 -> RSSI: -72
13:02:19.177 -> Snr: 9.25
13:02:19.177 -> 
13:02:19.431 -> Sending HeLoRa World! 6
13:02:29.994 -> Sending HeLoRa World! 8
13:02:32.000 -> Message: Hello LoRa!
13:02:32.000 -> RSSI: -46
13:02:32.047 -> Snr: 9.50
13:02:32.047 -> 
13:02:40.750 -> Sending HeLoRa World! 10
13:02:42.260 -> Message: Hello LoRa!
13:02:42.260 -> RSSI: -45
13:02:42.314 -> Snr: 9.50
13:02:42.314 -> 
13:02:51.286 -> Sending HeLoRa World! 12
13:02:52.541 -> Message: Hello LoRa!
13:02:52.541 -> RSSI: -45
13:02:52.541 -> Snr: 9.75
13:02:52.541 -> 
13:03:02.112 -> Sending HeLoRa World! 14
13:03:02.745 -> Message: Hello LoRa!
13:03:02.745 -> RSSI: -45
13:03:02.792 -> Snr: 9.50
13:03:02.792 -> 

Now that I’m confident my hardware is all working the next step will be building a full featured client based on my Windows 10 IoT Core library.

.Net Meadow RFM95/96/97/98 LoRa library Part6

Receive Interrupt

This proof of concept (PoC) was to confirm I could configure the Semtech 127X then handle the received messages using a Meadow event handler. The event handler code is based on the implementation in my Windows IoT 10 Core library.

I had added a few Console.WriteLine statements (Debug.Print currently doesn’t work Dec 2019) so I could see what was going on. But, using Console.WriteLine in the event handler caused me some problems which I had to debug. The irqFlags bit mask indicated there was a message in the FIFO but it wasn’t displayed and the interrupt mask wasn’t getting reset. As a temporary fix I refactored the code so the Console.WriteLine was the last statement in the EventHandler(which may cause other issues).

public Rfm9XDevice(IIODevice device, ISpiBus spiBus, IPin chipSelectPin, IPin resetPin, IPin interruptPin)
      {
         // Chip select pin configuration
         ChipSelectGpioPin = device.CreateDigitalOutputPort(chipSelectPin, initialState: true);
         if (ChipSelectGpioPin == null)
         {
            Console.WriteLine("ChipSelectGpioPin == null");
         }

         // Factory reset pin configuration
         IDigitalOutputPort resetGpioPin = device.CreateDigitalOutputPort(resetPin);
         if (resetGpioPin == null)
         {
            Console.WriteLine("resetGpioPin == null");
         }
         resetGpioPin.State = false;
         Task.Delay(10);
         resetGpioPin.State = true;
         Task.Delay(10);

         // Interrupt pin for RX message & TX done notification 
         InterruptGpioPin = device.CreateDigitalInputPort(interruptPin, InterruptMode.EdgeRising);
         InterruptGpioPin.Changed += InterruptGpioPin_ValueChanged;

         Rfm9XLoraModem = new SpiPeripheral(spiBus, ChipSelectGpioPin);
         if (Rfm9XLoraModem == null)
         {
            Console.WriteLine("Rfm9XLoraModem == null");
         }
      }
...
      private void InterruptGpioPin_ValueChanged(object sender, DigitalInputPortEventArgs args)
      {
         byte irqFlags = this.RegisterReadByte(0x12); // RegIrqFlags
         byte numberOfBytes = 0;
         string messageText = "";

         //Console.WriteLine(string.Format("RegIrqFlags:{0}", Convert.ToString(irqFlags, 2).PadLeft(8, '0')));
         if ((irqFlags & 0b01000000) == 0b01000000)
         {
            //Console.WriteLine("Receive-Message");
            byte currentFifoAddress = this.RegisterReadByte(0x10); // RegFifiRxCurrent
            this.RegisterWriteByte(0x0d, currentFifoAddress); // RegFifoAddrPtr

            numberOfBytes = this.RegisterReadByte(0x13); // RegRxNbBytes
            byte[] messageBytes = this.RegisterRead(0x00, numberOfBytes); // RegFifo
            messageText = UTF8Encoding.UTF8.GetString(messageBytes);
         }

         this.RegisterWriteByte(0x12, 0xff);// RegIrqFlags
         if (numberOfBytes > 0)
         {
            Console.WriteLine("Received {0} byte message {1}", numberOfBytes, messageText);
         }
      }
...
public class MeadowApp : App
{
private Rfm9XDevice rfm9XDevice;
  public MeadowApp()
  {
     ISpiBus spiBus = Device.CreateSpiBus(500);
     if (spiBus == null)
     {
        Console.WriteLine("spiBus == null");
     }

     rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D11, Device.Pins.D10);

     // Put device into LoRa + Sleep mode
     rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // RegOpMode 

     // Set the frequency to 915MHz
     byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 }; // RegFrMsb, RegFrMid, RegFrLsb
     rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes);

     rfm9XDevice.RegisterWriteByte(0x0F, 0x0); // RegFifoRxBaseAddress 

     rfm9XDevice.RegisterWriteByte(0x40, 0b00000000); // RegDioMapping1 0b00000000 DI0 RxReady & TxReady

     rfm9XDevice.RegisterWriteByte(0x01, 0b10000101); // RegOpMode set LoRa & RxContinuous

     rfm9XDevice.RegisterDump();

     Console.WriteLine("Receive-Wait");
     Task.Delay(-1).Wait();
  }
}

The output in the output debug window looked like this

'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'. 
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\ReceiveInterrupt\bin\Debug\net472\App.exe'. Symbols loaded.
'App.exe' (CLR v4.0.30319: App.exe): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\ReceiveInterrupt\bin\Debug\net472\Meadow.dll'. 
The program '[40348] App.exe: Program Trace' has exited with code 0 (0x0).
The program '[40348] App.exe' has exited with code 0 (0x0).
.
.
DirectRegisterAccess = True
.
.
Register dump
Register 0x00 - Value 0X6c - Bits 01101100
…
Register 0x42 - Value 0X12 - Bits 00010010
Receive-Wait
Received 15 byte message HeLoRa World! 0
Received 59 byte message ???LoRaIoT1Maduino2at 57.2,ah 67,wsa 1,wsg 3,wd 56.63,r 0.00,
Received 15 byte message HeLoRa World! 2
Received 15 byte message HeLoRa World! 4
Received 15 byte message HeLoRa World! 6
Received 15 byte message HeLoRa World! 8
Received 16 byte message HeLoRa World! 10

Next steps will be wiring up the transmit done interrupt, then building a full featured client based on my Windows 10 IoT Core library.

.Net Meadow RFM95/96/97/98 LoRa library Part5

Receive Basic

For testing this code I used the same version of the LoRaSetSyncWord example as Transmit Basic

20:57:40.239 -> LoRa init succeeded.
20:57:40.759 -> Sending HeLoRa World! 0
20:57:40.968 -> Message: ⸮LoRaIoT1Maduino2at 57.7,ah 64,wsa 1,wsg 2,wd 88.13,r 0.00,
20:57:41.036 -> RSSI: -72
20:57:41.036 -> Snr: 9.50
20:57:41.036 -> 
20:57:51.766 -> Sending HeLoRa World! 2
20:58:02.532 -> Sending HeLoRa World! 4
20:58:12.845 -> Sending HeLoRa World! 6
20:58:23.434 -> Sending HeLoRa World! 8
20:58:34.190 -> Sending HeLoRa World! 10
20:58:42.005 -> Message: ⸮LoRaIoT1Maduino2at 57.6,ah 64,wsa 2,wsg 5,wd 74.25,r 0.00,
20:58:42.074 -> RSSI: -72
20:58:42.074 -> Snr: 9.75

The Meadow code builds on my Windows 10 IoT Core Receive Basic and the Meadow Transmit Basic samples.

public class MeadowApp : App<F7Micro, MeadowApp>
{
	private Rfm9XDevice rfm9XDevice;

	public MeadowApp()
	{
		ISpiBus spiBus = Device.CreateSpiBus(500);
		if (spiBus == null)
		{
			Console.WriteLine("spiBus == null");
		}

		rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D11);

		// Put device into LoRa + Sleep mode
		rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // RegOpMode 

		// Set the frequency to 915MHz
		byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 }; // RegFrMsb, RegFrMid, RegFrLsb
		rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes);

		rfm9XDevice.RegisterWriteByte(0x0F, 0x0); // RegFifoRxBaseAddress 

		rfm9XDevice.RegisterWriteByte(0x01, 0b10000101); // RegOpMode set LoRa & RxContinuous

		while (true)
		{
			// Wait until a packet is received, no timeouts in PoC
			Console.WriteLine("Receive-Wait");
			byte IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
			while ((IrqFlags & 0b01000000) == 0)  // wait until RxDone cleared
			{
				Task.Delay(100).Wait();
				IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
				Console.Write(".");
			}
			Console.WriteLine("");
			Console.WriteLine(string.Format("RegIrqFlags {0}", Convert.ToString((byte)IrqFlags, 2).PadLeft(8, '0')));
			Console.WriteLine("Receive-Message");
			byte currentFifoAddress = rfm9XDevice.RegisterReadByte(0x10); // RegFifiRxCurrent
			rfm9XDevice.RegisterWriteByte(0x0d, currentFifoAddress); // RegFifoAddrPtr

			byte numberOfBytes = rfm9XDevice.RegisterReadByte(0x13); // RegRxNbBytes

			byte[] messageBytes = rfm9XDevice.RegisterRead(0x00, numberOfBytes); // RegFifo

			rfm9XDevice.RegisterWriteByte(0x0d, 0);
			rfm9XDevice.RegisterWriteByte(0x12, 0b11111111); // RegIrqFlags clear all the bits

			string messageText = UTF8Encoding.UTF8.GetString(messageBytes);
			Console.WriteLine("Received {0} byte message {1}", messageBytes.Length, messageText);

			Console.WriteLine("Receive-Done");
		}
	}
}

The receive code works reliably but has no error detection or correction capability, so every so often a message gets corrupted. Which is can be seen in the Debug output below.

'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'. 
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\ReceiveBasic\bin\Debug\net472\App.exe'. Symbols loaded.
'App.exe' (CLR v4.0.30319: App.exe): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\ReceiveBasic\bin\Debug\net472\Meadow.dll'. 
The program '[18208] App.exe: Program Trace' has exited with code 0 (0x0).
The program '[18208] App.exe' has exited with code 0 (0x0).
.
.
DirectRegisterAccess = True
.
.
Receive-Wait
RegIrqFlags 01010000
Receive-Message
Received 61 byte message ???LoRaIoT1Maduino2at 57.7,ah 64,wsa 2,wsg 4,wd 254.25,r 0.00,
Receive-Done
Receive-Wait
RegIrqFlags 01110000
Receive-Message
Received 60 byte message ???LoReE??????!???ngvyno2at,57/7-ah 6???,w???a 2,w?????????6,7$.13,r 0.00-
Receive-Done
Receive-Wait
RegIrqFlags 01010000
Receive-Message
Received 16 byte message HeLoRa World! 0
Receive-Done
Receive-Wait
RegIrqFlags 01000000
Receive-Message
Received 60 byte message ???LoRaIoT1Maduino2at 57.7,ah 64,wsa 1,wsg 2,wd 88.13,r 0.00,
Receive-Done
Receive-Wait
RegIrqFlags 01010000
Receive-Message
Received 16 byte message HeLoRa World! 2
Receive-Done
Receive-Wait

I will look at implementing some sort of carrier-sense multiple access with collision avoidance solution to reduce the number of corrupted messages when two (or possibly more devices) transmit at the same time.

Transmitting/receiving with interrupts or design goals next.

.Net Meadow RFM95/96/97/98 LoRa library Part4

Transmit Basic

I had a couple of Armtronix IA005 SX1276 loRa nodes sitting on my desk from a recent post so I used one of them running a modified version of the Arduino LoRa library LoRaSetSyncWord example to receive messages from my Meadow device.

/*
  LoRa Duplex communication with Sync Word
 
  Sends a message every half second, and polls continually
  for new incoming messages. Sets the LoRa radio's Sync Word.
 
  Spreading factor is basically the radio's network ID. Radios with different
  Sync Words will not receive each other's transmissions. This is one way you
  can filter out radios you want to ignore, without making an addressing scheme.
 
  See the Semtech datasheet, http://www.semtech.com/images/datasheet/sx1276.pdf
  for more on Sync Word.
 
  created 28 April 2017
  by Tom Igoe
*/
#include <stdlib.h>
#include <LoRa.h>
const int csPin = PA4;          // LoRa radio chip select
const int resetPin = PC13;       // LoRa radio reset
const int irqPin = PA11;         // change for your board; must be a hardware interrupt pin
 
byte msgCount = 0;            // count of outgoing messages
int interval = 2000;          // interval between sends
long lastSendTime = 0;        // time of last packet send
 
void setup() {
  Serial.begin(9600);                   // initialize serial
  while (!Serial);
 
  Serial.println("LoRa Duplex - Set sync word");
 
  // override the default CS, reset, and IRQ pins (optional)
  LoRa.setPins(csPin, resetPin, irqPin);// set CS, reset, IRQ pin
 
  if (!LoRa.begin(915E6)) {             // initialize ratio at 915 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }
 
  LoRa.setSyncWord(0x12);           // ranges from 0-0xFF, default 0x34, see API docs
 
  LoRa.dumpRegisters(Serial);
  Serial.println("LoRa init succeeded.");
}
 
void loop() {
  if (millis() - lastSendTime > interval) {
    String message = "HeLoRa World! ";   // send a message
    message += msgCount;
    sendMessage(message);
    Serial.println("Sending " + message);
    lastSendTime = millis();            // timestamp the message
    interval = random(1000) + 10000;    // 10-11 seconds
    msgCount++;
  }
 
  // parse for a packet, and call onReceive with the result:
  onReceive(LoRa.parsePacket());
}
 
void sendMessage(String outgoing) {
  LoRa.beginPacket();                   // start packet
  LoRa.print(outgoing);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  msgCount++;                           // increment message ID
}
 
void onReceive(int packetSize) {
  if (packetSize == 0) return;          // if there's no packet, return
 
  // read packet header bytes:
  String incoming = "";
 
  while (LoRa.available()) {
    incoming += (char)LoRa.read();
  }
 
  Serial.println("Message: " + incoming);
  Serial.println("RSSI: " + String(LoRa.packetRssi()));
  Serial.println("Snr: " + String(LoRa.packetSnr()));
  Serial.println();
}

The Meadow application

	public class MeadowApp : App<F7Micro, MeadowApp>
	{
		private Rfm9XDevice rfm9XDevice;

		public MeadowApp()
		{
			ISpiBus spiBus = Device.CreateSpiBus(500);
			if (spiBus == null)
			{
				Console.WriteLine("spiBus == null");
			}

			rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D11);

			// Put device into LoRa + Sleep mode
			rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // RegOpMode 

			// Set the frequency to 915MHz
			byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 }; // RegFrMsb, RegFrMid, RegFrLsb
			rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes);

			// More power PA Boost
			rfm9XDevice.RegisterWriteByte(0x09, 0b10000000); // RegPaConfig

			while (true)
			{
				rfm9XDevice.RegisterWriteByte(0x0E, 0x0); // RegFifoTxBaseAddress 

				// Set the Register Fifo address pointer
				rfm9XDevice.RegisterWriteByte(0x0D, 0x0); // RegFifoAddrPtr 

				string messageText = "Hello LoRa!";

				// load the message into the fifo
				byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
				foreach (byte b in messageBytes)
				{
					rfm9XDevice.RegisterWriteByte(0x0, b); // RegFifo
				}

				// Set the length of the message in the fifo
				rfm9XDevice.RegisterWriteByte(0x22, (byte)messageBytes.Length); // RegPayloadLength

				Console.WriteLine("Sending {0} bytes message {1}", messageBytes.Length, messageText);
				/// Set the mode to LoRa + Transmit
				rfm9XDevice.RegisterWriteByte(0x01, 0b10000011); // RegOpMode 

				// Wait until send done, no timeouts in PoC
				Console.WriteLine("Send-wait");
				byte IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
				while ((IrqFlags & 0b00001000) == 0)  // wait until TxDone cleared
				{
					Task.Delay(10).Wait();
					IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
					Console.Write(".");
				}
				Console.WriteLine("");
				rfm9XDevice.RegisterWriteByte(0x12, 0b00001000); // clear TxDone bit
				Console.WriteLine("Send-Done");

				Task.Delay(30000).Wait();
			}
		}

When I ran the meadow application after some messing around with the jumper wires.

'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'. 
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\TransmitBasic\bin\Debug\net472\App.exe'. Symbols loaded.
'App.exe' (CLR v4.0.30319: App.exe): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\TransmitBasic\bin\Debug\net472\Meadow.dll'. 
The program '[37572] App.exe: Program Trace' has exited with code 0 (0x0).
The program '[37572] App.exe' has exited with code 0 (0x0).
.
.
DirectRegisterAccess = True
.
.
Sending 11 bytes message Hello LoRa!
Send-wait
Send-Done
Sending 11 bytes message Hello LoRa!
Send-wait
.
Send-Done

I could the see the messages arriving at the Armtronix device in the Arduino monitor (the other messages in the monitor are my solar powered weather station and soil moisture monitoring node).

12:47:55.198 -> Sending HeLoRa World! 10
12:48:05.745 -> Sending HeLoRa World! 12
12:48:06.663 -> Message: ⸮LoRaIoT1Maduino2at 85.8,ah 19,wsa 5,wsg 8,wd 123.38,r 0.00,
12:48:06.730 -> RSSI: -71
12:48:06.730 -> Snr: 10.00
12:48:06.730 ->
12:48:08.770 -> Message: Hello LoRa!
12:48:08.770 -> RSSI: -47
12:48:08.804 -> Snr: 9.00
12:48:08.804 ->
12:48:16.555 -> Sending HeLoRa World! 14
12:48:26.847 -> Sending HeLoRa World! 16
12:48:37.154 -> Sending HeLoRa World! 18
12:48:39.469 -> Message: Hello LoRa!
12:48:39.469 -> RSSI: -46
12:48:39.536 -> Snr: 9.00
12:48:39.536 ->
12:48:47.311 -> Sending HeLoRa World! 20
12:48:58.094 -> Sending HeLoRa World! 22
12:49:07.748 -> Message: ⸮LoRaIoT1Maduino2at 86.0,ah 19,wsa 5,wsg 15,wd 155.63,r 0.00,
12:49:07.817 -> RSSI: -71
12:49:07.817 -> Snr: 9.50
12:49:07.817 ->
12:49:08.464 -> Sending HeLoRa World! 24
12:49:10.097 -> Message: Hello LoRa!
12:49:10.097 -> RSSI: -46
12:49:10.130 -> Snr: 9.75
12:49:10.130 ->
12:49:19.373 -> Sending HeLoRa World! 26
12:49:30.125 -> Sending HeLoRa World! 28
12:49:40.262 -> Sending HeLoRa World! 30
12:49:40.671 -> Message: Hello LoRa!
12:49:40.671 -> RSSI: -46
12:49:40.705 -> Snr: 9.25
12:49:40.705 ->
12:49:50.725 -> Sending HeLoRa World! 32
12:50:01.081 -> Sending HeLoRa World! 34
12:50:08.800 -> Message: ⸮LoRaIoT1Maduino2at 85.6,ah 19,wsa 5,wsg 11,wd 159.00,r 0.00,
12:50:08.868 -> RSSI: -72
12:50:08.868 -> Snr: 10.00
12:50:08.868 ->
12:50:11.219 -> Message: Hello LoRa!
12:50:11.219 -> RSSI: -46
12:50:11.252 -> Snr: 9.25
12:50:11.252 ->
12:50:11.526 -> Sending HeLoRa World! 36
12:50:21.731 -> Sending HeLoRa World! 38
12:50:32.696 -> Sending HeLoRa World! 40
12:50:41.741 -> Message: Hello LoRa!
12:50:41.741 -> RSSI: -46
12:50:41.775 -> Snr: 9.25
12:50:41.775 ->
12:50:43.685 -> Sending HeLoRa World! 42
12:50:54.566 -> Sending HeLoRa World! 44
12:51:05.604 -> Sending HeLoRa World! 46
12:51:09.852 -> Message: ⸮LoRaIoT1Maduino2at 85.3,ah 19,wsa 2,wsg 8,wd 150.75,r 0.00,
12:51:09.954 -> RSSI: -71
12:51:09.954 -> Snr: 9.50
12:51:09.954 ->
12:51:12.400 -> Message: Hello LoRa!
12:51:12.400 -> RSSI: -46
12:51:12.433 -> Snr: 9.00
12:51:12.433 ->
12:51:16.511 -> Sending HeLoRa World! 48
12:51:27.530 -> Sending HeLoRa World! 50
12:51:37.796 -> Sending HeLoRa World! 52
12:51:42.968 -> Message: Hello LoRa!
12:51:42.968 -> RSSI: -45
12:51:43.003 -> Snr: 9.25
12:51:43.003 ->
12:51:48.389 -> Sending HeLoRa World! 54
12:51:59.052 -> Sending HeLoRa World! 56
12:52:09.251 -> Sending HeLoRa World! 58
12:52:10.912 -> Message: ⸮LoRaIoT1Maduino2at 85.1,ah 19,wsa 2,wsg 6,wd 84.00,r 0.00,
12:52:11.013 -> RSSI: -70
12:52:11.013 -> Snr: 9.75
12:52:11.013 ->
12:52:13.546 -> Message: Hello LoRa!
12:52:13.546 -> RSSI: -46
12:52:13.581 -> Snr: 9.75
12:52:13.581 ->

This PoC code is getting a bit nasty with magic numbers and no error checking. The next step is getting a basic packet receive working…

.Net Meadow RFM95/96/97/98 LoRa library Part3

Register Read and Write

Now that I could reliably dump all the Arduino shield registers I wanted to be able to configure the Semtech 1276/7/8/9 device and reset it back to factory settings.

A factory reset is done by strobing the reset pin on the device. To support this my Rfm9XDevice class constructor gained an additional parameter, the reset GPIO pin.

Arduino shield connected to Meadow with jumper wires

To configure the RFM9X I wrote some wrapper functions for the Meadow SPI API to read/write byte values, word values and arrays of bytes. The Meadow APIs (Dec 2019) return an additional byte at the start of each reply (unlike the .NetMF and Windows 10 IoT Core APIs) which has to be removed.

Each method was tested by read/writing suitable register(s) in the device configuration (Needed to set it into LoRa mode first).

//---------------------------------------------------------------------------------
// Copyright (c) December 2019, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.ReadAndWrite
{
   using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices.WindowsRuntime;
    using System.Threading.Tasks;

   using Meadow;
   using Meadow.Devices;
   using Meadow.Hardware;

   public sealed class Rfm9XDevice
   {
      private SpiPeripheral Rfm9XLoraModem;
      private IDigitalOutputPort ChipSelectGpioPin;
		private const byte RegisterAddressReadMask = 0X7f;
		private const byte RegisterAddressWriteMask = 0x80;

		public Rfm9XDevice(IIODevice device, ISpiBus spiBus, IPin chipSelectPin, IPin resetPin)
      {
         // Chip select pin configuration
         ChipSelectGpioPin = device.CreateDigitalOutputPort(chipSelectPin, initialState: true);
         if (ChipSelectGpioPin == null)
         {
            Console.WriteLine("ChipSelectGpioPin == null");
         }

         // Factory reset pin configuration
         IDigitalOutputPort resetGpioPin = device.CreateDigitalOutputPort(resetPin);
         if (resetGpioPin == null)
         {
            Console.WriteLine("resetGpioPin == null");
         }
         resetGpioPin.State = false;
         Task.Delay(10);
         resetGpioPin.State = true;
         Task.Delay(10);

			Rfm9XLoraModem = new SpiPeripheral(spiBus, ChipSelectGpioPin);
         if (Rfm9XLoraModem == null)
         {
            Console.WriteLine("Rfm9XLoraModem == null");
         }
      }

      public Byte RegisterReadByte(byte address)
      {
         byte[] writeBuffer = new byte[] { address &= RegisterAddressReadMask };
			Debug.Assert(Rfm9XLoraModem != null);

			byte[] readBuffer = Rfm9XLoraModem.WriteRead(writeBuffer, 2);

         return readBuffer[1];
      }

		public ushort RegisterReadWord(byte address)
		{
			byte[] writeBuffer = new byte[] { address &= RegisterAddressReadMask };
			Debug.Assert(Rfm9XLoraModem != null);

			byte[] readBuffer = Rfm9XLoraModem.WriteRead(writeBuffer, 3);

			return (ushort)(readBuffer[2] + (readBuffer[1] << 8));
		}

		public byte[] RegisterRead(byte address, int length)
		{
			byte[] writeBuffer = new byte[] { address &= RegisterAddressReadMask };
			byte[] repyBuffer = new byte[length + 1];
			Debug.Assert(Rfm9XLoraModem != null);

			byte[] readBuffer = Rfm9XLoraModem.WriteRead(writeBuffer, (ushort)repyBuffer.Length);

			Array.Copy(readBuffer, 1, repyBuffer, 0, length);

			return repyBuffer;
		}

		public void RegisterWriteByte(byte address, byte value)
		{
			byte[] writeBuffer = new byte[] { address |= RegisterAddressWriteMask, value };
			Debug.Assert(Rfm9XLoraModem != null);

			Rfm9XLoraModem.WriteBytes(writeBuffer);
		}

		public void RegisterWriteWord(byte address, ushort value)
		{
			byte[] valueBytes = BitConverter.GetBytes(value);
			byte[] writeBuffer = new byte[] { address |= RegisterAddressWriteMask, valueBytes[0], valueBytes[1] };
			Debug.Assert(Rfm9XLoraModem != null);

			Rfm9XLoraModem.WriteBytes(writeBuffer);
		}
		public void RegisterWrite(byte address, [ReadOnlyArray()] byte[] bytes)
		{
			byte[] writeBuffer = new byte[1 + bytes.Length];
			Debug.Assert(Rfm9XLoraModem != null);

			Array.Copy(bytes, 0, writeBuffer, 1, bytes.Length);
			writeBuffer[0] = address |= RegisterAddressWriteMask;

			Rfm9XLoraModem.WriteBytes(writeBuffer);
		}

		public void RegisterDump()
		{
			Console.WriteLine("Register dump");
			for (byte registerIndex = 0; registerIndex <= 0x42; registerIndex++)
			{
				byte registerValue = this.RegisterReadByte(registerIndex);

				Console.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", registerIndex, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));
			}
		}
	}

	public class MeadowApp : App<F7Micro, MeadowApp>
   {
      private Rfm9XDevice rfm9XDevice;

      public MeadowApp()
      {
			ISpiBus spiBus = Device.CreateSpiBus(500);
         if (spiBus == null)
         {
            Console.WriteLine("spiBus == null");
         }

			rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D11);

			while (true)
         {
				rfm9XDevice.RegisterDump();

				Byte regOpMode = rfm9XDevice.RegisterReadByte(0x1);

				Console.WriteLine("Set LoRa mode and sleep mode (write byte)");
				rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // 

				Console.WriteLine("Read the preamble (read word)");
				ushort preamble = rfm9XDevice.RegisterReadWord(0x20);
				Console.WriteLine("Preamble 0x{0:x2} - Bits {1}", preamble, Convert.ToString(preamble, 2).PadLeft(16, '0'));

				Console.WriteLine("Set the preamble to 0x80 (write word)");
				rfm9XDevice.RegisterWriteWord(0x20, 0x80);

				Console.WriteLine("Read the centre frequency (read byte array)");
				byte[] frequencyReadBytes = rfm9XDevice.RegisterRead(0x06, 3);
				Console.WriteLine("Frequency Msb 0x{0:x2} Mid 0x{1:x2} Lsb 0x{2:x2}", frequencyReadBytes[0], frequencyReadBytes[1], frequencyReadBytes[2]);

				Console.WriteLine("Set the centre frequency to 916MHz ( write byte array)");
				byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 };
				rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes);

				rfm9XDevice.RegisterDump();

				Task.Delay(30000).Wait();
			}
		}
   }
}

The output of the application looked like this

'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'. 
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\RegisterReadAndWrite\bin\Debug\net472\App.exe'. Symbols loaded.
'App.exe' (CLR v4.0.30319: App.exe): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\RegisterReadAndWrite\bin\Debug\net472\Meadow.dll'. 
The program '[31296] App.exe: Program Trace' has exited with code 0 (0x0).
The program '[31296] App.exe' has exited with code 0 (0x0).
.
.
DirectRegisterAccess = True
==========================================================
Ignore the exceptions generated by the DateTime call here.
==========================================================
.
Register dump
.
Register 0x00 - Value 0X00 - Bits 00000000
Register 0x01 - Value 0X09 - Bits 00001001
Register 0x02 - Value 0X1a - Bits 00011010
Register 0x03 - Value 0X0b - Bits 00001011
Register 0x04 - Value 0X00 - Bits 00000000
Register 0x05 - Value 0X52 - Bits 01010010
Register 0x06 - Value 0X6c - Bits 01101100
Register 0x07 - Value 0X80 - Bits 10000000
Register 0x08 - Value 0X00 - Bits 00000000
Register 0x09 - Value 0X4f - Bits 01001111
Register 0x0a - Value 0X09 - Bits 00001001
Register 0x0b - Value 0X2b - Bits 00101011
Register 0x0c - Value 0X20 - Bits 00100000
Register 0x0d - Value 0X08 - Bits 00001000
Register 0x0e - Value 0X02 - Bits 00000010
Register 0x0f - Value 0X0a - Bits 00001010
Register 0x10 - Value 0Xff - Bits 11111111
Register 0x11 - Value 0X70 - Bits 01110000
Register 0x12 - Value 0X15 - Bits 00010101
Register 0x13 - Value 0X0b - Bits 00001011
Register 0x14 - Value 0X28 - Bits 00101000
Register 0x15 - Value 0X0c - Bits 00001100
Register 0x16 - Value 0X12 - Bits 00010010
Register 0x17 - Value 0X47 - Bits 01000111
Register 0x18 - Value 0X32 - Bits 00110010
Register 0x19 - Value 0X3e - Bits 00111110
Register 0x1a - Value 0X00 - Bits 00000000
Register 0x1b - Value 0X00 - Bits 00000000
Register 0x1c - Value 0X00 - Bits 00000000
Register 0x1d - Value 0X00 - Bits 00000000
Register 0x1e - Value 0X00 - Bits 00000000
Register 0x1f - Value 0X40 - Bits 01000000
Register 0x20 - Value 0X00 - Bits 00000000
Register 0x21 - Value 0X00 - Bits 00000000
Register 0x22 - Value 0X00 - Bits 00000000
Register 0x23 - Value 0X00 - Bits 00000000
Register 0x24 - Value 0X05 - Bits 00000101
Register 0x25 - Value 0X00 - Bits 00000000
Register 0x26 - Value 0X03 - Bits 00000011
Register 0x27 - Value 0X93 - Bits 10010011
Register 0x28 - Value 0X55 - Bits 01010101
Register 0x29 - Value 0X55 - Bits 01010101
Register 0x2a - Value 0X55 - Bits 01010101
Register 0x2b - Value 0X55 - Bits 01010101
Register 0x2c - Value 0X55 - Bits 01010101
Register 0x2d - Value 0X55 - Bits 01010101
Register 0x2e - Value 0X55 - Bits 01010101
Register 0x2f - Value 0X55 - Bits 01010101
Register 0x30 - Value 0X90 - Bits 10010000
Register 0x31 - Value 0X40 - Bits 01000000
Register 0x32 - Value 0X40 - Bits 01000000
Register 0x33 - Value 0X00 - Bits 00000000
Register 0x34 - Value 0X00 - Bits 00000000
Register 0x35 - Value 0X0f - Bits 00001111
Register 0x36 - Value 0X00 - Bits 00000000
Register 0x37 - Value 0X00 - Bits 00000000
Register 0x38 - Value 0X00 - Bits 00000000
Register 0x39 - Value 0Xf5 - Bits 11110101
Register 0x3a - Value 0X20 - Bits 00100000
Register 0x3b - Value 0X82 - Bits 10000010
Register 0x3c - Value 0X00 - Bits 00000000
Register 0x3d - Value 0X02 - Bits 00000010
Register 0x3e - Value 0X80 - Bits 10000000
Register 0x3f - Value 0X40 - Bits 01000000
Register 0x40 - Value 0X00 - Bits 00000000
Register 0x41 - Value 0X00 - Bits 00000000
Register 0x42 - Value 0X12 - Bits 00010010
Set LoRa mode and sleep mode (write byte)
Read the preamble (read word)
Preamble 0x08 - Bits 0000000000001000
Set the preamble to 0x80 (write word)
Read the centre frequency (read byte array)
Frequency Msb 0x6c Mid 0x80 Lsb 0x00
Set the centre frequency to 916MHz ( write byte array)
Register dump
Register 0x00 - Value 0Xf4 - Bits 11110100
Register 0x01 - Value 0X80 - Bits 10000000
Register 0x02 - Value 0X1a - Bits 00011010
Register 0x03 - Value 0X0b - Bits 00001011
Register 0x04 - Value 0X00 - Bits 00000000
Register 0x05 - Value 0X52 - Bits 01010010
Register 0x06 - Value 0Xe4 - Bits 11100100
Register 0x07 - Value 0Xc0 - Bits 11000000
Register 0x08 - Value 0X00 - Bits 00000000
Register 0x09 - Value 0X4f - Bits 01001111
Register 0x0a - Value 0X09 - Bits 00001001
Register 0x0b - Value 0X2b - Bits 00101011
Register 0x0c - Value 0X20 - Bits 00100000
Register 0x0d - Value 0X02 - Bits 00000010
Register 0x0e - Value 0X80 - Bits 10000000
Register 0x0f - Value 0X00 - Bits 00000000
Register 0x10 - Value 0X00 - Bits 00000000
Register 0x11 - Value 0X00 - Bits 00000000
Register 0x12 - Value 0X00 - Bits 00000000
Register 0x13 - Value 0X00 - Bits 00000000
Register 0x14 - Value 0X00 - Bits 00000000
Register 0x15 - Value 0X00 - Bits 00000000
Register 0x16 - Value 0X00 - Bits 00000000
Register 0x17 - Value 0X00 - Bits 00000000
Register 0x18 - Value 0X10 - Bits 00010000
Register 0x19 - Value 0X00 - Bits 00000000
Register 0x1a - Value 0X00 - Bits 00000000
Register 0x1b - Value 0X00 - Bits 00000000
Register 0x1c - Value 0X00 - Bits 00000000
Register 0x1d - Value 0X72 - Bits 01110010
Register 0x1e - Value 0X70 - Bits 01110000
Register 0x1f - Value 0X64 - Bits 01100100
Register 0x20 - Value 0X80 - Bits 10000000
Register 0x21 - Value 0X00 - Bits 00000000
Register 0x22 - Value 0X01 - Bits 00000001
Register 0x23 - Value 0Xff - Bits 11111111
Register 0x24 - Value 0X00 - Bits 00000000
Register 0x25 - Value 0X00 - Bits 00000000
Register 0x26 - Value 0X04 - Bits 00000100
Register 0x27 - Value 0X00 - Bits 00000000
Register 0x28 - Value 0X00 - Bits 00000000
Register 0x29 - Value 0X00 - Bits 00000000
Register 0x2a - Value 0X00 - Bits 00000000
Register 0x2b - Value 0X00 - Bits 00000000
Register 0x2c - Value 0X00 - Bits 00000000
Register 0x2d - Value 0X50 - Bits 01010000
Register 0x2e - Value 0X14 - Bits 00010100
Register 0x2f - Value 0X45 - Bits 01000101
Register 0x30 - Value 0X55 - Bits 01010101
Register 0x31 - Value 0Xc3 - Bits 11000011
Register 0x32 - Value 0X05 - Bits 00000101
Register 0x33 - Value 0X27 - Bits 00100111
Register 0x34 - Value 0X1c - Bits 00011100
Register 0x35 - Value 0X0a - Bits 00001010
Register 0x36 - Value 0X03 - Bits 00000011
Register 0x37 - Value 0X0a - Bits 00001010
Register 0x38 - Value 0X42 - Bits 01000010
Register 0x39 - Value 0X12 - Bits 00010010
Register 0x3a - Value 0X49 - Bits 01001001
Register 0x3b - Value 0X1d - Bits 00011101
Register 0x3c - Value 0X00 - Bits 00000000
Register 0x3d - Value 0Xaf - Bits 10101111
Register 0x3e - Value 0X00 - Bits 00000000
Register 0x3f - Value 0X00 - Bits 00000000
Register 0x40 - Value 0X00 - Bits 00000000
Register 0x41 - Value 0X00 - Bits 00000000
Register 0x42 - Value 0X12 - Bits 00010010

The next step is to extract the SPI register access functionality into a module and configure the bare minimum of settings required to get the RFM9X to transmit.

.Net Meadow RFM95/96/97/98 LoRa library Part2

Register Dump

Next step was to dump all registers (0x00 thru 0x42) of the SX1276/7/8/9 device

//---------------------------------------------------------------------------------
// Copyright (c) December 2019, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.RegisterScan
{
   using System;
   using System.Threading.Tasks;

   using Meadow;
   using Meadow.Devices;
   using Meadow.Hardware;

   public sealed class Rfm9XDevice
   {
      private SpiPeripheral sx127xDevice;
      private IDigitalOutputPort spiPeriphChipSelect;

      public Rfm9XDevice(IIODevice device, ISpiBus spiBus, IPin chipSelectPin)
      {
         spiPeriphChipSelect = device.CreateDigitalOutputPort(chipSelectPin, initialState: true);
         if (spiPeriphChipSelect == null)
         {
            Console.WriteLine("spiPeriphChipSelect == null");
         }

         sx127xDevice = new SpiPeripheral(spiBus, spiPeriphChipSelect);
         if (sx127xDevice == null)
         {
            Console.WriteLine("sx127xDevice == null");
         }
      }

      public Byte RegisterReadByte(byte registerAddress)
      {
         byte[] txBuffer = new byte[] { registerAddress };

         byte[] rxBuffer = sx127xDevice.WriteRead(txBuffer, 2);

         return rxBuffer[1];
      }
   }

   public class MeadowApp : App<F7Micro, MeadowApp>
   {
      private Rfm9XDevice rfm9XDevice;

      public MeadowApp()
      {
         ISpiBus spiBus = Device.CreateSpiBus(500);
         if (spiBus == null)
         {
            Console.WriteLine("spiBus == null");
         }

         rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09);

         while (true)
         {
            for (byte registerIndex = 0; registerIndex <= 0x42; registerIndex++)
            {
               byte registerValue = rfm9XDevice.RegisterReadByte(registerIndex);

               Console.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", registerIndex, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));
            }

            Task.Delay(10000).Wait();
         }
      }
   }
}

The output of the application looked like this

'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'. 
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\RegisterScan\bin\Debug\net472\App.exe'. Symbols loaded.
'App.exe' (CLR v4.0.30319: App.exe): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\RegisterScan\bin\Debug\net472\Meadow.dll'. 
The program '[3148] App.exe: Program Trace' has exited with code 0 (0x0).
The program '[3148] App.exe' has exited with code 0 (0x0).
.
.
DirectRegisterAccess = True
==========================================================
Ignore the exceptions generated by the DateTime call here.
==========================================================
.
Register 0x00 - Value 0X00 - Bits 00000000
Register 0x01 - Value 0X09 - Bits 00001001
Register 0x02 - Value 0X1a - Bits 00011010
Register 0x03 - Value 0X0b - Bits 00001011
Register 0x04 - Value 0X00 - Bits 00000000
Register 0x05 - Value 0X52 - Bits 01010010
Register 0x06 - Value 0X6c - Bits 01101100
Register 0x07 - Value 0X80 - Bits 10000000
Register 0x08 - Value 0X00 - Bits 00000000
Register 0x09 - Value 0X4f - Bits 01001111
Register 0x0a - Value 0X09 - Bits 00001001
Register 0x0b - Value 0X2b - Bits 00101011
Register 0x0c - Value 0X20 - Bits 00100000
Register 0x0d - Value 0X08 - Bits 00001000
Register 0x0e - Value 0X02 - Bits 00000010
Register 0x0f - Value 0X0a - Bits 00001010
Register 0x10 - Value 0Xff - Bits 11111111
Register 0x11 - Value 0X71 - Bits 01110001
Register 0x12 - Value 0X15 - Bits 00010101
Register 0x13 - Value 0X0b - Bits 00001011
Register 0x14 - Value 0X28 - Bits 00101000
Register 0x15 - Value 0X0c - Bits 00001100
Register 0x16 - Value 0X12 - Bits 00010010
Register 0x17 - Value 0X47 - Bits 01000111
Register 0x18 - Value 0X32 - Bits 00110010
Register 0x19 - Value 0X3e - Bits 00111110
Register 0x1a - Value 0X00 - Bits 00000000
Register 0x1b - Value 0X00 - Bits 00000000
Register 0x1c - Value 0X00 - Bits 00000000
Register 0x1d - Value 0X00 - Bits 00000000
Register 0x1e - Value 0X00 - Bits 00000000
Register 0x1f - Value 0X40 - Bits 01000000
Register 0x20 - Value 0X00 - Bits 00000000
Register 0x21 - Value 0X00 - Bits 00000000
Register 0x22 - Value 0X00 - Bits 00000000
Register 0x23 - Value 0X00 - Bits 00000000
Register 0x24 - Value 0X05 - Bits 00000101
Register 0x25 - Value 0X00 - Bits 00000000
Register 0x26 - Value 0X03 - Bits 00000011
Register 0x27 - Value 0X93 - Bits 10010011
Register 0x28 - Value 0X55 - Bits 01010101
Register 0x29 - Value 0X55 - Bits 01010101
Register 0x2a - Value 0X55 - Bits 01010101
Register 0x2b - Value 0X55 - Bits 01010101
Register 0x2c - Value 0X55 - Bits 01010101
Register 0x2d - Value 0X55 - Bits 01010101
Register 0x2e - Value 0X55 - Bits 01010101
Register 0x2f - Value 0X55 - Bits 01010101
Register 0x30 - Value 0X90 - Bits 10010000
Register 0x31 - Value 0X40 - Bits 01000000
Register 0x32 - Value 0X40 - Bits 01000000
Register 0x33 - Value 0X00 - Bits 00000000
Register 0x34 - Value 0X00 - Bits 00000000
Register 0x35 - Value 0X0f - Bits 00001111
Register 0x36 - Value 0X00 - Bits 00000000
Register 0x37 - Value 0X00 - Bits 00000000
Register 0x38 - Value 0X00 - Bits 00000000
Register 0x39 - Value 0Xf5 - Bits 11110101
Register 0x3a - Value 0X20 - Bits 00100000
Register 0x3b - Value 0X82 - Bits 10000010
Register 0x3c - Value 0Xfd - Bits 11111101
Register 0x3d - Value 0X02 - Bits 00000010
Register 0x3e - Value 0X80 - Bits 10000000
Register 0x3f - Value 0X40 - Bits 01000000
Register 0x40 - Value 0X00 - Bits 00000000
Register 0x41 - Value 0X00 - Bits 00000000
Register 0x42 - Value 0X12 - Bits 00010010

I also started to refactor the code, passing the Meadow IIODevice & ISpiBus into the constructor, and extracting the Read functionality.

The device was not in LoRa mode (Bit 7 of RegOpMode 0x01) so the next step was to read and write registers so I could change its configuration.

ubidots MQTT LoRa Field Gateway

Back in April I started working on an MQTT LoRa Field gateway which was going to support a selection of different Software as a service(SaaS) Internet of Things(IoT) platforms.

After a long pause in development I have a working ubidots client and have 3 proof of concept (PoC) integrations for Adafruit.IO, AskSensors, and Losant. I am also working on Azure IoT Hub, Azure IoT Central. The first iteration is focused on Device to Cloud (D2C) messaging in the next iteration I will add Cloud to Device where viable(C2D).

My applications use a lightweight, easy to implemented protocol which is intended for hobbyist and educational use rather than commercial applications (I have been working on a more secure version as yet another side project)

I have a number of sample Arduino with Dragino LoRa Shield for Arduino, MakerFabs Maduino, Dragino LoRa Mini Dev, M2M Low power Node and Netduino with Elecrow LoRa RFM95 Shield etc. clients. These work with both my platform specific (Adafruit.IO, Azure IoT Central) gateways and protocol specific field gateways.

Ubidots dashboard

When the application is first started it creates a minimal configuration file which should be downloaded, the missing information filled out, then uploaded using the File explorer in the Windows device portal.

{
  "MQTTUserName": "Ubidots generated usname here",
  "MQTTPassword": "NotVerySecure",
  "MQTTClientID": "MQTTLoRaGateway",
  "MQTTServer": "industrial.api.ubidots.com",
  "Address": "LoRaIoT1",
  "Frequency": 915000000.0,
  "MessageHandlerAssembly": "Mqtt.IoTCore.FieldGateway.LoRa.Ubidots",
  "PlatformSpecificConfiguration": ""
}

The application logs debugging information to the Windows 10 IoT Core ETW logging Microsoft-Windows-Diagnostics-LoggingChannel

MQTT LoRa Field Gateway with ubidots plugin generated telemetry
ubidots device management
ubidot managment

The message handler uploads all values in an inbound messages in one MQTT message using the ubidots MQTT message format

async void IMessageHandler.Rfm9XOnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
{
	LoggingFields processReceiveLoggingFields = new LoggingFields();
	JObject telemetryDataPoint = new JObject();
	char[] sensorReadingSeparators = { ',' };
	char[] sensorIdAndValueSeparators = { ' ' };

	processReceiveLoggingFields.AddString("PacketSNR", e.PacketSnr.ToString("F1"));
	processReceiveLoggingFields.AddInt32("PacketRSSI", e.PacketRssi);
	processReceiveLoggingFields.AddInt32("RSSI", e.Rssi);

	string addressBcdText = BitConverter.ToString(e.Address);
	processReceiveLoggingFields.AddInt32("DeviceAddressLength", e.Address.Length);
	processReceiveLoggingFields.AddString("DeviceAddressBCD", addressBcdText);

	string messageText;
	try
	{
		messageText = UTF8Encoding.UTF8.GetString(e.Data);
		processReceiveLoggingFields.AddString("MessageText", messageText);
	}
	catch (Exception ex)
	{
		processReceiveLoggingFields.AddString("Exception", ex.ToString());
		this.Logging.LogEvent("PayloadProcess failure converting payload to text", processReceiveLoggingFields, LoggingLevel.Warning);
		return;
	}

	// Chop up the CSV text
	string[] sensorReadings = messageText.Split(sensorReadingSeparators, StringSplitOptions.RemoveEmptyEntries);
	if (sensorReadings.Length < 1)
	{
		this.Logging.LogEvent("PayloadProcess payload contains no sensor readings", processReceiveLoggingFields, LoggingLevel.Warning);
		return;
	}

	// Chop up each sensor read into an ID & value
	foreach (string sensorReading in sensorReadings)
	{
		string[] sensorIdAndValue = sensorReading.Split(sensorIdAndValueSeparators, StringSplitOptions.RemoveEmptyEntries);
		// Check that there is an id & value
		if (sensorIdAndValue.Length != 2)
		{
			this.Logging.LogEvent("PayloadProcess payload invalid format", processReceiveLoggingFields, LoggingLevel.Warning);
			return;
		}

		string sensorId = sensorIdAndValue[0];
		string value = sensorIdAndValue[1];

		telemetryDataPoint.Add(addressBcdText + sensorId, Convert.ToDouble(value));
	}
	processReceiveLoggingFields.AddString("MQTTClientId", MqttClient.Options.ClientId);

	string stateTopic = string.Format(stateTopicFormat, MqttClient.Options.ClientId);

	try
	{
		var message = new MqttApplicationMessageBuilder()
			.WithTopic(stateTopic)
			.WithPayload(JsonConvert.SerializeObject(telemetryDataPoint))
			.WithAtLeastOnceQoS()
			.Build();
		Debug.WriteLine(" {0:HH:mm:ss} MQTT Client PublishAsync start", DateTime.UtcNow);
		await MqttClient.PublishAsync(message);
		Debug.WriteLine(" {0:HH:mm:ss} MQTT Client PublishAsync finish", DateTime.UtcNow);

		this.Logging.LogEvent("PublishAsync Ubidots payload", processReceiveLoggingFields, LoggingLevel.Information);
	}
	catch (Exception ex)
	{
		processReceiveLoggingFields.AddString("Exception", ex.ToString());
		this.Logging.LogEvent("PublishAsync Ubidots payload", processReceiveLoggingFields, LoggingLevel.Error);
	}
}

The “automagic” provisioning of feeds does make setting up small scale systems easier, though I’m not certain how well it would scale.

Some of the fields weren’t obviously editable e.g.”ÄPI Label” in device configuration which I only discovered by clicking on them..

The limitations of the free account meant I couldn’t evaluate ubidots in much depth but what was available appeared to be robust and reliable (Nov 2019).

Maduino LoRa Air Temperature and Soil Moisture

This is a demo MakerFabs Maduino LoRa Radio 868MHz client (based on Maduino LoRa 868MHz example) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI AdaFruit.IO and Azure IoT Hub field gateways.

The code is available on github

Sample hardware
Azure IoT Central data visualisation

The Maduino device in the picture is a custom version with an onboard Microchip ATSHA204 crypto and authentication chip (currently only use for the unique 72 bit serial number) and a voltage divider connected to the analog pin A6 to monitor the battery voltage.

There are compile time options ATSHA204 & BATTERY_VOLTAGE_MONITOR which can be used to selectively enable this functionality.

I use the Arduino lowpower library to aggressively sleep the device between measurements

// Adjust the delay so period is close to desired sec as possible, first do 8sec chunks. 
  int delayCounter = SensorUploadDelay / 8 ;
  for( int i = 0 ; i < delayCounter ; i++ )
  {
     LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);  
  }
  
  // Then to 4 sec chunk
  delayCounter =  ( SensorUploadDelay % 8 ) / 4;
  for( int i = 0 ; i < delayCounter ; i++ )
  {
     LowPower.powerDown(SLEEP_4S, ADC_OFF, BOD_OFF);  
  }

  // Then to 2 sec chunk
  delayCounter =  ( SensorUploadDelay % 4 ) / 2 ;
  for( int i = 0 ; i < delayCounter ; i++ )
  {
     LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);  
  }

  // Then to 1 sec chunk
  delayCounter =  ( SensorUploadDelay % 2 ) ;
  for( int i = 0 ; i < delayCounter ; i++ )
  {
     LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);  
  }
}

I use a spare digital PIN for powering the soil moisture probe so it can be powered down when not in use. I have included a short delay after powering up the device to allow the reading to settle.

  // Turn on soil mosture sensor, take reading then turn off to save power
  digitalWrite(SoilMoistureSensorEnablePin, HIGH);
  delay(SoilMoistureSensorEnableDelay);
  int soilMoistureADCValue = analogRead(SoilMoistureSensorPin);
  digitalWrite(SoilMoistureSensorEnablePin, LOW);
  int soilMoisture = map(soilMoistureADCValue,SoilMoistureSensorMinimum,SoilMoistureSensorMaximum, SoilMoistureValueMinimum, SoilMoistureValueMaximum); 
  PayloadAdd( "s", soilMoisture, false);

Bill of materials (Prices Nov 2019)

  • Maduino LoRa Radion (868MHz) 18.90
  • SHT20 I2C Temperature & Humidity Sensor (Waterproof Probe) USD22.50
  • Pinotech SoilWatch 10 – Soil moisture sensor USD23
  • Elecrow 1 Watt solar panel with wires USD3.80
  • 500 mAh LI-Ion battery

The software could easily be modified to support additional sensors.

iwanders/plainRFM69 revisited

After problems with interleaved interrupt handling in my Windows 10 IoT Core client I figured the AutoMode used by the plainRFM69 library might be worth investigation. My first Arduino client was based on the plainRFM69 library but had Interoperability issues.

For this attempt I also started with the minimal sample and modified the code to send and receive text messages.

/*
    Copyright (c) 2014, Ivor Wanders, Bryn Lewis 2019
    MIT License, see the LICENSE.md file in the root folder.
*/

#include <SPI.h>
#include <plainRFM69.h>

// slave select pin.
#define SLAVE_SELECT_PIN 10

// connected to the reset pin of the RFM69.
#define RESET_PIN 9

// tie this pin down on the receiver.
#define SENDER_DETECT_PIN A0

const uint8_t tx_buffer[] = "ABCDEFGHIJKLMNOPQRSTURWXYZ1234567890";
//const uint8_t tx_buffer[] = "abcdefghijklmnopqrstuvwxyz1234567890";
uint8_t rx_buffer[sizeof(tx_buffer)] = "";

plainRFM69 rfm = plainRFM69(SLAVE_SELECT_PIN);


void sender() {

  uint32_t start_time = millis();

  uint32_t counter = 1; // the counter which we are going to send.

  while (true) {
    rfm.poll(); // run poll as often as possible.

    if (!rfm.canSend()) {
      continue; // sending is not possible, already sending.
    }

    if ((millis() - start_time) > 1000) { // every 500 ms.
      start_time = millis();

      // be a little bit verbose.
      Serial.print("Send:"); Serial.println(counter);

      // send the number of bytes equal to that set with setPacketLength.
      // read those bytes from memory where counter starts.
      rfm.sendVariable(tx_buffer, counter);

      counter++; // increase the counter.

      if ( counter > strlen(tx_buffer))
      {
        counter = 1;
      }
    }
  }
}

void receiver() {
  uint32_t counter = 0; // to count the messages.

  while (true) {

    rfm.poll(); // poll as often as possible.

    while (rfm.available())
    {
      uint8_t len = rfm.read(rx_buffer); // read the packet into the new_counter.

      // print verbose output.
      Serial.print("Packet Len:");
      Serial.print( len );
      Serial.print(" : ");
      Serial.println((char*)rx_buffer);
    }
  }
}

void setup() {
  Serial.begin(9600);
  SPI.begin();

  bareRFM69::reset(RESET_PIN); // sent the RFM69 a hard-reset.

  //rfm.setRecommended(); // set recommended paramters in RFM69.
  rfm.setPacketType(true, false); // set the used packet type.

  rfm.setBufferSize(2);   // set the internal buffer size.
  rfm.setPacketLength(sizeof(rx_buffer)); // set the packet length.

  rfm.setFrequency((uint32_t)909560000); // set the frequency.

  rfm.setLNA(RFM69_LNA_IMP_200OHM, RFM69_LNA_GAIN_AGC_LOOP);

  // p71, 3 preamble bytes.
  rfm.setPreambleSize(16);

  // p71, 4 bytes sync of 0x01, only start listening when sync is matched.
  //uint8_t syncthing[] = {0xaa, 0x2d, 0xd4};
  uint8_t syncthing[] = {0xd4, 0x2d, 0xaa};
  rfm.setSyncConfig(true, false, sizeof(syncthing), 0);
  rfm.setSyncValue(&syncthing, sizeof(syncthing));

  rfm.dumpRegisters(Serial);

  // baudrate is default, 4800 bps now.

  rfm.receive();
  // set it to receiving mode.

  pinMode(SENDER_DETECT_PIN, INPUT_PULLUP);
  delay(5);
}

void loop() {
  if (digitalRead(SENDER_DETECT_PIN) == LOW) {
    Serial.println("Going Receiver!");
    receiver();
    // this function never returns and contains an infinite loop.
  } else {
    Serial.println("Going sender!");
    sender();
    // idem.
  }
}

I took the list register values and loaded them into a Excel spreadsheet alongside the values from my Windows 10 IoT Core application

17:35:03.044 -> 0x0: 0x0
17:35:03.078 -> 0x1: 0x4
17:35:03.078 -> 0x2: 0x0
17:35:03.078 -> 0x3: 0x1A
17:35:03.112 -> 0x4: 0xB
17:35:03.112 -> 0x5: 0x0
17:35:03.112 -> 0x6: 0x52
17:35:03.146 -> 0x7: 0xE3
17:35:03.146 -> 0x8: 0x63
17:35:03.146 -> 0x9: 0xD7
17:35:03.180 -> 0xA: 0x41
17:35:03.180 -> 0xB: 0x40
17:35:03.180 -> 0xC: 0x2
17:35:03.215 -> 0xD: 0x92
17:35:03.215 -> 0xE: 0xF5
17:35:03.249 -> 0xF: 0x20
17:35:03.249 -> 0x10: 0x24
17:35:03.249 -> 0x11: 0x9F
17:35:03.282 -> 0x12: 0x9
17:35:03.282 -> 0x13: 0x1A
17:35:03.282 -> 0x14: 0x40
17:35:03.317 -> 0x15: 0xB0
17:35:03.317 -> 0x16: 0x7B
17:35:03.317 -> 0x17: 0x9B
17:35:03.317 -> 0x18: 0x88
17:35:03.351 -> 0x19: 0x86
17:35:03.351 -> 0x1A: 0x8A
17:35:03.384 -> 0x1B: 0x40
17:35:03.384 -> 0x1C: 0x80
17:35:03.384 -> 0x1D: 0x6
17:35:03.418 -> 0x1E: 0x10
17:35:03.418 -> 0x1F: 0x0
17:35:03.452 -> 0x20: 0x0
17:35:03.452 -> 0x21: 0x0
17:35:03.452 -> 0x22: 0x0
17:35:03.487 -> 0x23: 0x2
17:35:03.487 -> 0x24: 0xFF
17:35:03.487 -> 0x25: 0x0
17:35:03.521 -> 0x26: 0x5
17:35:03.521 -> 0x27: 0x80
17:35:03.521 -> 0x28: 0x0
17:35:03.556 -> 0x29: 0xFF
17:35:03.556 -> 0x2A: 0x0
17:35:03.556 -> 0x2B: 0x0
17:35:03.556 -> 0x2C: 0x0
17:35:03.590 -> 0x2D: 0x10
17:35:03.590 -> 0x2E: 0x90
17:35:03.624 -> 0x2F: 0xAA
17:35:03.624 -> 0x30: 0x2D
17:35:03.624 -> 0x31: 0xD4
17:35:03.659 -> 0x32: 0x0
17:35:03.659 -> 0x33: 0x0
17:35:03.659 -> 0x34: 0x0
17:35:03.693 -> 0x35: 0x0
17:35:03.693 -> 0x36: 0x0
17:35:03.728 -> 0x37: 0xD0
17:35:03.728 -> 0x38: 0x25
17:35:03.728 -> 0x39: 0x0
17:35:03.761 -> 0x3A: 0x0
17:35:03.761 -> 0x3B: 0x0
17:35:03.761 -> 0x3C: 0x1
17:35:03.795 -> 0x3D: 0x0
17:35:03.795 -> Going sender!
17:35:04.725 -> Send:1

Arduino RFM69HCW Client in receive mode

First thing I noticed was the order of the three sync byes (Registers 0x2F, 0x30, 0x31) was reversed. I then modified the run method in the Windows 10 code so the registers settings on both devices matched. (I removed the PlainRFM69 SetRecommended call so as many of the default options as possible were used).

public void Run(IBackgroundTaskInstance taskInstance)
{
	byte[] syncValues = { 0xAA, 0x2D, 0xD4 };
	byte[] aesKeyValues = { 0x0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0X0E, 0X0F };

	try
	{
		rfm69Device.Initialise(Rfm69HcwDevice.RegOpModeMode.StandBy
										,frequency: 909560000.0 
										,dio0Mapping: Rfm69HcwDevice.Dio0Mapping.ReceiveCrcOk
										,preambleSize: 16												
										,syncValues: syncValues
										,packetFormat: Rfm69HcwDevice.RegPacketConfig1PacketFormat.VariableLength
										,packetDcFree: Rfm69HcwDevice.RegPacketConfig1DcFree.Whitening
										,autoRestartRx: false
										//,addressNode: 0x22
										//,addressbroadcast: 0x99
										//,aesKey: aesKeyValues
										);

		rfm69Device.OnReceive += Rfm69Device_OnReceive;
		rfm69Device.OnTransmit += Rfm69Device_OnTransmit;

		rfm69Device.RegisterDump();
		rfm69Device.SetMode(Rfm69HcwDevice.RegOpModeMode.Receive);


		while (true)
		{
			if (true)
			{
				string message = $"hello world {Environment.MachineName} {DateTime.Now:hh-mm-ss}";

				byte[] messageBuffer = UTF8Encoding.UTF8.GetBytes(message);

				Debug.WriteLine("{0:HH:mm:ss.fff} Send-{1}", DateTime.Now, message);
				//rfm69Device.SendMessage( 0x11, messageBuffer);
				rfm69Device.SendMessage(messageBuffer);

				Debug.WriteLine("{0:HH:mm:ss.fff} Send-Done", DateTime.Now);

				Task.Delay(5000).Wait();
			}
			else
			{
				Debug.Write(".");
				Task.Delay(1000).Wait();
			}
		}
	}
	catch (Exception ex)
	{
		Debug.WriteLine(ex.Message);
	}
}

I also found an error with the declaration of the RegPacketConfig1DcFree enumeration (Whitening = 0b0100000 vs. Whitening = 0b01000000) which wouldn’t have helped.

public enum RegPacketConfig1DcFree : byte
{
	None = 0b00000000,
	Manchester = 0b00100000,
	Whitening = 0b01000000,
	Reserved = 0b01100000,
}
const RegPacketConfig1DcFree RegPacketConfig1DcFreeDefault = RegPacketConfig1DcFree.None;

I could then reliably sent messages to and receive messages from my Arduino Nano Radio Shield RFM69/95 device

Register 0x4c - Value 0X00 - Bits 00000000
Register 0x4d - Value 0X00 - Bits 00000000
...
17:55:53.559 Received 1 byte message A CRC Ok True
.17:55:54.441 Received 2 byte message AB CRC Ok True
.17:55:55.444 Received 3 byte message ABC CRC Ok True
.17:55:56.447 Received 4 byte message ABCD CRC Ok True
.17:55:57.449 Received 5 byte message ABCDE CRC Ok True
.17:55:58.453 Received 6 byte message ABCDEF CRC Ok True
The thread 0x578 has exited with code 0 (0x0).
.17:55:59.622 Received 7 byte message ABCDEFG CRC Ok True
.17:56:00.457 Received 8 byte message ABCDEFGH CRC Ok True
.17:56:01.460 Received 9 byte message ABCDEFGHI CRC Ok True
.17:56:02.463 Received 10 byte message ABCDEFGHIJ CRC Ok True
..17:56:03.955 Received 11 byte message ABCDEFGHIJK CRC Ok True
17:56:04.583 Received 12 byte message ABCDEFGHIJKL CRC Ok True

I did some investigation into that the plainRMF69 code and found the ReadMultiple and WriteMuliple methods reverse the byte order

void bareRFM69::writeMultiple(uint8_t reg, void* data, uint8_t len){
    SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));  // gain control of SPI bus
    this->chipSelect(true); // assert chip select
    SPI.transfer(RFM69_WRITE_REG_MASK | (reg & RFM69_READ_REG_MASK)); 
    uint8_t* r = reinterpret_cast<uint8_t*>(data);
    for (uint8_t i=0; i < len ; i++){
        SPI.transfer(r[len - i - 1]);
    }
    this->chipSelect(false);// deassert chip select
    SPI.endTransaction();    // release the SPI bus
}

void bareRFM69::readMultiple(uint8_t reg, void* data, uint8_t len){
    SPI.beginTransaction(SPISettings(10000000, MSBFIRST, SPI_MODE0));  // gain control of SPI bus
    this->chipSelect(true); // assert chip select
    
    SPI.transfer((reg % RFM69_READ_REG_MASK));
    uint8_t* r = reinterpret_cast<uint8_t*>(data);
    for (uint8_t i=0; i < len ; i++){
        r[len - i - 1] = SPI.transfer(0);
    }
    this->chipSelect(false);// deassert chip select
    SPI.endTransaction();    // release the SPI bus
}

I won’t be able to use interrupt AutoMode clients with the EasySensors shields as the DIO2 pin is not connected but on the AdaFruit RFM69HCW Radio Bonnet 433MHz or 915MHz it is connected to GPIO24.

RFM69 hat library lockups and corruptions

While doing yet more stress testing I noticed a couple of odd message go past and a long pause every so often when sending a message in the Visual Studio output window.

I have two Arduino devices sending addressed messages every (both individual and broadcast) to the Adafruit RFM69 HCW Radio Bonnet, on my two Windows 10 IoT Core devices every 100mSec. At the same time the windows 10 devices are sending each other a message every 5 seconds.

To help spot the pauses I added some code to mark any events where there was a significant gap. In this case ” is ASCII character for 0x22 the device address

21:10:30.746 Received To 34 a 23 byte message Hello World ---0x22:236 CRC Ok True
21:10:30.918 Received To 153 a 23 byte message Hello World ---0x99:236 CRC Ok True
21:10:31.399 Received To 34 a 23 byte message Hello World ---0x22:237 CRC Ok True
21:10:31.568 Send-hello world RFM69-915-01 09-10-31
21:10:31.580 Send-Done
21:10:31.592 Received To 34 a 33 byte message """"""""""""""""""""""""""""""""" CRC Ok True
RC-------------------------------------------
21:10:32.052 Received To 34 a 23 byte message Hello World ---0x22:238 CRC Ok True
21:10:32.225 Received To 153 a 23 byte message Hello World ---0x99:238 CRC Ok True
21:10:32.705 Received To 34 a 23 byte message Hello World ---0x22:239 CRC Ok True

There were also still some corrupted messages

21:10:30.746 Received To 34 a 23 byte message Hello World ---0x22:236 CRC Ok True
21:10:30.918 Received To 153 a 23 byte message Hello World ---0x99:236 CRC Ok True
21:10:31.399 Received To 34 a 23 byte message Hello World ---0x22:237 CRC Ok True
21:10:31.568 Send-hello world RFM69-915-01 09-10-31
21:10:31.580 Send-Done
21:10:31.592 Received To 34 a 33 byte message """"""""""""""""""""""""""""""""" CRC Ok True
RC-------------------------------------------
21:10:32.052 Received To 34 a 23 byte message Hello World ---0x22:238 CRC Ok True
21:10:32.225 Received To 153 a 23 byte message Hello World ---0x99:238 CRC Ok True
21:10:32.705 Received To 34 a 23 byte message Hello World ---0x22:239 CRC Ok True

It looks like if the base station receives a message as it is about to send a message the Rfm69Device_OnTransmit never gets called.

It also looks like every so often the transmitter gets stuck on one of Windows 10 devices effectively jamming the frequency.

Transmit stuck on
16:12:10.193 Received To 34 a 22 byte message Hello World ---0x22:65 CRC Ok True
16:12:10.360 Received To 153 a 22 byte message Hello World ---0x99:65 CRC Ok True
16:12:10.831 Received To 34 a 22 byte message Hello World ---0x22:66 CRC Ok True
16:12:10.998 Received To 153 a 22 byte message Hello World ---0x99:66 CRC Ok True
The thread 0x570 has exited with code 0 (0x0).
16:12:11.484 Send-hello world RFM69-915-01 04-12-11
16:12:11.494 Received To 34 a 22 byte message Hello World ---0x22:67 CRC Ok True
16:12:11.504 Send-Done
The thread 0x3a8 has exited with code 0 (0x0).
16:12:16.554 Send-hello world RFM69-915-01 04-12-16
16:12:16.566 Send-Done
16:12:16.660 Transmit-Done
T--------------------------------------------
16:12:16.736 Received To 153 a 22 byte message Hello World ---0x99:75 CRC Ok True
16:12:17.206 Received To 34 a 22 byte message Hello World ---0x22:76 CRC Ok True
16:12:17.374 Received To 153 a 22 byte message Hello World ---0x99:76 CRC Ok True
16:12:18.011 Received To 153 a 22 byte message Hello World ---0x99:77 CRC Ok True


Transmit stuck 
16:12:07.591 Transmit-Done
16:12:07.880 Received To 153 a 23 byte message Hello World ---0x99:137 CRC Ok True
16:12:08.533 Received To 153 a 23 byte message Hello World ---0x99:138 CRC Ok True
16:12:08.839 Received To 17 a 24 byte message Hello World ----0x11:139 CRC Ok True
16:12:09.186 Received To 153 a 23 byte message Hello World ---0x99:139 CRC Ok True
16:12:09.493 Received To 17 a 24 byte message Hello World ----0x11:140 CRC Ok True
16:12:10.799 Received To 17 a 24 byte message Hello World ----0x11:142 CRC Ok True
The thread 0xc8 has exited with code 0 (0x0).
16:12:12.567 Send-hello world RFM69-915-02 04-12-12
16:12:12.589 Send-Done
16:12:12.681 Transmit-Done
16:12:16.510 Received To 17 a 33 byte message hello world RFM69-915-01 04-12-16 CRC Ok True
16:12:16.576 Received To 153 a 22 byte message Hello World ---0x99:75 CRC Ok True
16:12:17.025 Received To 153 a 23 byte message Hello World ---0x99:151 CRC Ok True
16:12:17.214 Received To 153 a 22 byte message Hello World ---0x99:76 CRC Ok True
16:12:17.331 Received To 17 a 24 byte message Hello World ----0x11:152 CRC Ok True
The thread 0xfa0 has exited with code 0 (0x0).
16:12:17.661 Send-hello world RFM69-915-02 04-12-17
16:12:17.680 Send-Done
16:12:17.772 Transmit-Done
16:12:17.851 Received To 153 a 22 byte message Hello World ---0x99:77 CRC Ok True
16:12:18.331 Received To 153 a 23 byte message Hello World ---0x99:153 CRC Ok True
16:12:18.489 Received To 153 a 22 byte message Hello World ---0x99:78 CRC Ok True
16:12:18.638 Received To 17 a 24 byte message Hello World ----0x11:154 CRC Ok True
16:12:18.985 Received To 153 a 23 byte message Hello World ---0x99:154 CRC Ok True
16:12:19.291 Received To 17 a 24 byte message Hello World ----0x11:155 CRC Ok True
16:12:19.638 Received To 153 a 23 byte message Hello World ---0x99:155 CRC Ok True
16:12:19.944 Received To 17 a 24 byte message Hello World ----0x11:156 CRC Ok True
16:12:20.291 Received To 153 a 23 byte message Hello World ---0x99:156 CRC Ok True
16:12:20.597 Received To 17 a 24 byte message Hello World ----0x11:157 CRC Ok True

Then as rfm69Device.SetMode(Rfm69HcwDevice.RegOpModeMode.Receive) hasn’t been called no messages are received until another message is sent.

It looks like a timing issue around access to the message fifo (I have that in a critical section) so I need todo some more debugging. Maybe purging the receive buffer

byte regPacketConfig2 = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegPacketConfig2);
			regPacketConfig2 |= 0b00000100;
			RegisterManager.WriteByte((byte)Rfm69HcwDevice.Registers.RegPacketConfig2, regPacketConfig2);

The adfruit.io RFM69 shield has DIO2 which can be used for automode operation which might remove some of the synchronisation issues I am encountering.