TinyCLR OS V2 nRF24L01 library Part2

After sorting out Serial Peripheral Interface(SPI) connectivity the next step porting the techfooninja nRF24L01P library to GHI Electronics TinyCLR was rewriting the initialisation code. Overall changes were minimal as the TinyCLR V2 SPI library has similar methods to the Windows 10 IoT Core ones.

SC20100 and MikroE nRF24 C Click

I need to refactor the initialise method so that failure exceptions are not caught and add the interrupt trigger edge so I can remove test from the handler.

      public void Initialize(string spiPortName, byte chipEnablePin, byte chipSelectPin, byte interruptPin, int clockFrequency = 2000000)
      {
         var gpio = GpioController.GetDefault();

         if (gpio == null)
         {
            Debug.WriteLine("GPIO Initialization failed.");
         }
         else
         {
            _cePin = gpio.OpenPin(chipEnablePin);
            _cePin.SetDriveMode(GpioPinDriveMode.Output);
            _cePin.Write(GpioPinValue.Low);

            _irqPin = gpio.OpenPin((byte)interruptPin);
            _irqPin.SetDriveMode(GpioPinDriveMode.InputPullUp);
            _irqPin.Write(GpioPinValue.High);
            _irqPin.ValueChanged += _irqPin_ValueChanged;
         }

         try
         {
            var settings = new SpiConnectionSettings()
            {
               ChipSelectType = SpiChipSelectType.Gpio,
               ChipSelectLine = gpio.OpenPin(chipSelectPin),
               Mode = SpiMode.Mode0,
               ClockFrequency = clockFrequency,
               ChipSelectActiveState = false,
            };

            SpiController controller = SpiController.FromName(spiPortName);
            _spiPort = controller.GetDevice(settings);
         }
         catch (Exception ex)
         {
            Debug.WriteLine("SPI Initialization failed. Exception: " + ex.Message);
            return;
         }

         // Module reset time
         Thread.Sleep(100);

         IsInitialized = true;

         // Set reasonable default values
         Address = Encoding.UTF8.GetBytes("NRF1");
         DataRate = DataRate.DR2Mbps;
         IsDynamicPayload = true;
         IsAutoAcknowledge = true;

         FlushReceiveBuffer();
         FlushTransferBuffer();
         ClearIrqMasks();
         SetRetries(5, 60);

         // Setup, CRC enabled, Power Up, PRX
         SetReceiveMode();
      }

The Initialise method gained parameters for the SPI port name and SPI clock frequency.

      static void Main()
      {
         RF24 Radio = new RF24();

         try
         {
            Radio.OnDataReceived += Radio_OnDataReceived;
            Radio.OnTransmitFailed += Radio_OnTransmitFailed;
            Radio.OnTransmitSuccess += Radio_OnTransmitSuccess;

            // SC20100.GpioPin.PD3
            Radio.Initialize(SC20100.SpiBus.Spi3, SC20100.GpioPin.PD4, SC20100.GpioPin.PD3, SC20100.GpioPin.PC5);
            Radio.Address = Encoding.UTF8.GetBytes(DeviceAddress);

            Radio.Channel = 15;
            //Radio.PowerLevel = PowerLevel.Max;
            //Radio.PowerLevel = PowerLevel.High;
            //Radio.PowerLevel = PowerLevel.Low;
            //Radio.PowerLevel = PowerLevel.Minimum
            Radio.DataRate = DataRate.DR250Kbps;
            //Radio.DataRate = DataRate.DR1Mbps;
            Radio.IsEnabled = true;

            Radio.IsAutoAcknowledge = true;
            Radio.IsDyanmicAcknowledge = false;
            Radio.IsDynamicPayload = true;

            Debug.WriteLine($"Address: {Encoding.UTF8.GetString(Radio.Address)}");
            Debug.WriteLine($"PowerLevel: {Radio.PowerLevel}");
            Debug.WriteLine($"IsAutoAcknowledge: {Radio.IsAutoAcknowledge}");
            Debug.WriteLine($"Channel: {Radio.Channel}");
            Debug.WriteLine($"DataRate: {Radio.DataRate}");
            Debug.WriteLine($"IsDynamicAcknowledge: {Radio.IsDyanmicAcknowledge}");
            Debug.WriteLine($"IsDynamicPayload: {Radio.IsDynamicPayload}");
            Debug.WriteLine($"IsEnabled: {Radio.IsEnabled}");
            Debug.WriteLine($"Frequency: {Radio.Frequency}");
            Debug.WriteLine($"IsInitialized: {Radio.IsInitialized}");
            Debug.WriteLine($"IsPowered: {Radio.IsPowered}");

            while (true)
            {
               string payload = "hello " + DateTime.Now.Second;
               Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss}-TX {payload.Length} byte message {payload}");
               Radio.SendTo(Encoding.UTF8.GetBytes(BaseStationAddress), Encoding.UTF8.GetBytes(payload));

               Thread.Sleep(30000);
            }
         }
         catch (Exception ex)
         {
            Debug.WriteLine(ex.Message);

            return;
         }
      }

I can send and receive messages but the PowerLevel doesn’t look right so I need to apply fix from the Meadow version.

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Address: Dev01
PowerLevel: 15
IsAutoAcknowledge: True
Channel: 15
DataRate: 2
IsDynamicAcknowledge: False
IsDynamicPayload: True
IsEnabled: False
Frequency: 2415
IsInitialized: True
IsPowered: True
00:00:01-TX 7 byte message hello 1
Data Sent!
00:00:01-TX Succeeded!
00:00:31-TX 8 byte message hello 31
Data Sent!
00:00:31-TX Succeeded!

TinyCLR OS V2 nRF24L01 library Part1

After debugging Windows 10 IoT Core, .NetMF and Wilderness Labs Meadow nRF24L01P libraries I figured yet another port, this time to a GHI Electronics Tiny CLR V2 powered device shouldn’t be “rocket science”.

This test rig uses SC20100S Dev and MikroE nRF C Click boards.

//---------------------------------------------------------------------------------
// Copyright (c) May 2020, 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.nRf24L01.ModuleSPI
{
   using System;
   using System.Diagnostics;
   using System.Reflection;
   using System.Text;
   using System.Threading;

   using GHIElectronics.TinyCLR.Devices.Gpio;
   using GHIElectronics.TinyCLR.Devices.Spi;
   using GHIElectronics.TinyCLR.Pins;

   class Program
   {
      const byte SETUP_AW = 0x03;
      const byte RF_CH = 0x05;
      const byte RX_ADDR_P0 = 0x0A;
      const byte R_REGISTER = 0b00000000;
      const byte W_REGISTER = 0b00100000;
      const string P0_Address = "ZYXWV";
      static SpiDevice nrf24L01Device;

      static void Main()
      {
         try
         {
            GpioController gpioController = GpioController.GetDefault();

            var settings = new SpiConnectionSettings()
            {
               ChipSelectType = SpiChipSelectType.Gpio,
               //ChipSelectLine = FEZ.GpioPin.D10,
               ChipSelectLine = gpioController.OpenPin(SC20100.GpioPin.PD3),
               Mode = SpiMode.Mode0,
               //Mode = SpiMode.Mode1,
               //Mode = SpiMode.Mode2,
               //Mode = SpiMode.Mode3,
               ClockFrequency = 500000,
               //ChipSelectActiveState = true
               ChipSelectActiveState = false,
               //ChipSelectHoldTime = new TimeSpan(0, 0, 0, 0, 500),
               //ChipSelectSetupTime = new TimeSpan(0, 0, 0, 0, 500),
            };

            var spiController = SpiController.FromName(SC20100.SpiBus.Spi3);

            Debug.WriteLine("nrf24L01Device Device...");
            nrf24L01Device = spiController.GetDevice(settings);
            if (nrf24L01Device == null)
            {
               Debug.WriteLine("nrf24L01Device == null");
            }

            Thread.Sleep(100);

            Debug.WriteLine("ConfigureSpiPort Done...");
            Debug.WriteLine("");

            Thread.Sleep(500);
         }
         catch (Exception ex)
         {
            Debug.WriteLine("Configure SpiPort " + ex.Message);
         }

         try
         {
            // Read the Address width
            Debug.WriteLine("Read address width");
            byte[] txBuffer1 = new byte[] { SETUP_AW | R_REGISTER, 0x0 };
            byte[] rxBuffer1 = new byte[txBuffer1.Length];

            Debug.WriteLine(" nrf24L01Device.TransferFullDuplex...SETUP_AW");
            Debug.WriteLine(" txBuffer:" + BitConverter.ToString(txBuffer1));
            nrf24L01Device.TransferFullDuplex(txBuffer1, rxBuffer1);
            Debug.WriteLine(" rxBuffer:" + BitConverter.ToString(rxBuffer1));

            // Extract then adjust the address width
            byte addressWidthValue = rxBuffer1[1];
            addressWidthValue &= 0b00000011;
            addressWidthValue += 2;
            Debug.WriteLine($"Address width 0x{SETUP_AW:x2} - Value 0X{rxBuffer1[1]:x2} Value adjusted {addressWidthValue}");
            Debug.WriteLine("");

            // Write Pipe0 Receive address
            Debug.WriteLine($"Write Pipe0 Receive Address {P0_Address}");
            byte[] txBuffer2 = new byte[addressWidthValue + 1];
            txBuffer2[0] = RX_ADDR_P0 | W_REGISTER;
            Array.Copy(Encoding.UTF8.GetBytes(P0_Address), 0, txBuffer2, 1, addressWidthValue);

            Debug.WriteLine(" nrf24L01Device.Write...RX_ADDR_P0");
            Debug.WriteLine(" txBuffer:" + BitConverter.ToString(txBuffer2));
            nrf24L01Device.Write(txBuffer2);
            Debug.WriteLine("");

            // Read Pipe0 Receive address
            Debug.WriteLine("Read Pipe0 Receive address");
            byte[] txBuffer3 = new byte[addressWidthValue + 1];
            txBuffer3[0] = RX_ADDR_P0 | R_REGISTER;
            byte[] rxBuffer3 = new byte[txBuffer3.Length];

            Debug.WriteLine(" nrf24L01Device.TransferFullDuplex...RX_ADDR_P0");
            Debug.WriteLine(" txBuffer:" + BitConverter.ToString(txBuffer3));
            nrf24L01Device.TransferFullDuplex(txBuffer3, rxBuffer3);
            Debug.WriteLine(" rxBuffer:" + BitConverter.ToString(rxBuffer3));
            Debug.WriteLine($"Address 0x{RX_ADDR_P0:x2} Address {UTF8Encoding.UTF8.GetString(rxBuffer3, 1, addressWidthValue)}");
            Debug.WriteLine("");

            // Read the RF Channel
            Debug.WriteLine("RF Channel read 1");
            byte[] txBuffer4 = new byte[] { RF_CH | R_REGISTER, 0x0 };
            byte[] rxBuffer4 = new byte[txBuffer4.Length];

            Debug.WriteLine(" nrf24L01Device.TransferFullDuplex...RF_CH");
            Debug.WriteLine(" txBuffer:" + BitConverter.ToString(txBuffer4));
            nrf24L01Device.TransferFullDuplex(txBuffer4, rxBuffer4);
            Debug.WriteLine(" rxBuffer:" + BitConverter.ToString(rxBuffer4));

            ushort rfChannel1 = rxBuffer4[1];
            rfChannel1 += 2400;
            Debug.WriteLine($"RF Channel 1 0x{RF_CH:x2} - Value 0X{rxBuffer4[1]:x2} - Value adjusted {rfChannel1}");
            Debug.WriteLine("");

            // Write the RF Channel
            Debug.WriteLine("RF Channel write");
            byte[] txBuffer5 = new byte[] { RF_CH | W_REGISTER, rxBuffer4[1]+=1};

            Debug.WriteLine(" nrf24L01Device.Write...RF_CH");
            Debug.WriteLine(" txBuffer:" + BitConverter.ToString(txBuffer5));
            nrf24L01Device.Write(txBuffer5);
            Debug.WriteLine("");

            // Read the RF Channel
            Debug.WriteLine("RF Channel read 2");
            byte[] txBuffer6 = new byte[] { RF_CH | R_REGISTER, 0x0 };
            byte[] rxBuffer6 = new byte[txBuffer6.Length];

            Debug.WriteLine(" nrf24L01Device.TransferFullDuplex...RF_CH");
            Debug.WriteLine(" txBuffer:" + BitConverter.ToString(txBuffer6));
            nrf24L01Device.TransferFullDuplex(txBuffer6, rxBuffer6);
            Debug.WriteLine(" rxBuffer:" + BitConverter.ToString(rxBuffer6));

            ushort rfChannel2 = rxBuffer6[1];
            rfChannel2 += 2400;
            Debug.WriteLine($"RF Channel 2 0x{RF_CH:x2} - Value 0X{rxBuffer6[1]:x2} - Value adjusted {rfChannel2}");
            Debug.WriteLine("");
         }
         catch (Exception ex)
         {
            Debug.WriteLine("Configure Port0 " + ex.Message);
         }
      }
   }
}

After lots of tinkering with SPI configuration options I can read and write my nRF24L01 device receive port address

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
nrf24L01Device Device...
ConfigureSpiPort Done...

Read address width
 nrf24L01Device.TransferFullDuplex...SETUP_AW
 txBuffer:03-00
 rxBuffer:0E-03
Address width 0x03 - Value 0X03 Value adjusted 5

Write Pipe0 Receive Address ZYXWV
 nrf24L01Device.Write...RX_ADDR_P0
 txBuffer:2A-5A-59-58-57-56

Read Pipe0 Receive address
 nrf24L01Device.TransferFullDuplex...RX_ADDR_P0
 txBuffer:0A-00-00-00-00-00
 rxBuffer:0E-5A-59-58-57-56
Address 0x0a Address ZYXWV

RF Channel read 1
 nrf24L01Device.TransferFullDuplex...RF_CH
 txBuffer:05-00
 rxBuffer:0E-15
RF Channel 1 0x05 - Value 0X15 - Value adjusted 2421

RF Channel write
 nrf24L01Device.Write...RF_CH
 txBuffer:25-16

RF Channel read 2
 nrf24L01Device.TransferFullDuplex...RF_CH
 txBuffer:05-00
 rxBuffer:0E-16
RF Channel 2 0x05 - Value 0X16 - Value adjusted 2422

RFM9X.NetNF on Github

The source code of my nanoFramework RFM9X/SX127X library is now available on GitHub. One test harness uses an STM32F429 Discovery and a dragino technology LoRa shield for Arduino with some jumper wires.

STM32F429 Discovery kit with Dragino Shield

The other uses Sparkfun LoRa Gateway 1 Channel ESP32 for a LoRaWAN.

A sample application which shows how to send/receive address/un-addressed payloads.

//---------------------------------------------------------------------------------
// Copyright (c) April/May 2020, 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.
//
//---------------------------------------------------------------------------------
//#define ADDRESSED_MESSAGES_PAYLOAD
#define ST_STM32F429I_DISCOVERY       //nanoff --target ST_STM32F429I_DISCOVERY --update
//#define ESP32_WROOM_32_LORA_1_CHANNEL   //nanoff --target ESP32_WROOM_32 --serialport COM4 --update
namespace devMobile.IoT.Rfm9x.LoRaDeviceClient
{
	using System;
	using System.Text;
	using System.Threading;

#if ESP32_WROOM_32_LORA_1_CHANNEL
	using nanoFramework.Hardware.Esp32;
#endif

	using devMobile.IoT.Rfm9x;

	class Program
	{
		const double Frequency = 915000000.0;
#if ST_STM32F429I_DISCOVERY
		private const string SpiBusId = "SPI5";
#endif
#if ESP32_WROOM_32_LORA_1_CHANNEL
		private const string SpiBusId = "SPI1";
#endif

		static void Main()
		{
			byte MessageCount = System.Byte.MaxValue;
#if ADDRESSED_MESSAGES_PAYLOAD
			const string HostName = "ESP32LoRa";
			const string DeviceName = "LoRaIoT1";
#else
			const string DeviceName = "ESP32LoRa";
#endif
#if ST_STM32F429I_DISCOVERY
			int chipSelectPinNumber = PinNumber('C', 2);
			int resetPinNumber = PinNumber('C', 3);
			int interruptPinNumber = PinNumber('A', 4);
#endif
#if ESP32_WROOM_32_LORA_1_CHANNEL
         int chipSelectPinNumber = Gpio.IO16;
         int interruptPinNumber = Gpio.IO26;

			Configuration.SetPinFunction(Gpio.IO12, DeviceFunction.SPI1_MISO);
			Configuration.SetPinFunction(Gpio.IO13, DeviceFunction.SPI1_MOSI);
			Configuration.SetPinFunction(Gpio.IO14, DeviceFunction.SPI1_CLOCK);

			Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SpiBusId, chipSelectPinNumber, interruptPinNumber);
#endif
#if ST_STM32F429I_DISCOVERY
			Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SpiBusId, chipSelectPinNumber, resetPinNumber, interruptPinNumber);
#endif
			rfm9XDevice.Initialise(Frequency, paBoost: true, rxPayloadCrcOn: true);

#if DEBUG
			rfm9XDevice.RegisterDump();
#endif

			rfm9XDevice.OnReceive += Rfm9XDevice_OnReceive;
#if ADDRESSED_MESSAGES_PAYLOAD
			rfm9XDevice.Receive(UTF8Encoding.UTF8.GetBytes(DeviceName));
#else
			rfm9XDevice.Receive();
#endif
			rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

			Thread.Sleep(10000);

			while (true)
			{
				string messageText = string.Format("Hello from {0} ! {1}", DeviceName, MessageCount);
				MessageCount -= 1;

				byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
				Console.WriteLine(string.Format("{0}-TX {1} byte message {2}", DateTime.UtcNow.ToString("HH:mm:ss"), messageBytes.Length, messageText));
#if ADDRESSED_MESSAGES_PAYLOAD
				rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes(HostName), messageBytes);
#else
				rfm9XDevice.Send(messageBytes);
#endif
				Thread.Sleep(10000);
			}
		}

		private static void Rfm9XDevice_OnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
		{
			try
			{
				// Remove unprintable characters from messages
				for (int index = 0; index < e.Data.Length; index++)
				{
					if ((e.Data[index] < 0x20) || (e.Data[index] > 0x7E))
					{
						e.Data[index] = 0x20;
					}
				}

				string messageText = UTF8Encoding.UTF8.GetString(e.Data, 0, e.Data.Length);

#if ADDRESSED_MESSAGES_PAYLOAD
				string addressText = UTF8Encoding.UTF8.GetString(e.Address, 0, e.Address.Length);

				Console.WriteLine(string.Format(@"{0}-RX From {1} PacketSnr {2} Packet RSSI {3}dBm RSSI {4}dBm ={5} ""{6}""", DateTime.UtcNow.ToString("HH:mm:ss"), addressText, e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText));
#else
				Console.WriteLine(string.Format(@"{0}-RX PacketSnr {1} Packet RSSI {2}dBm RSSI {3}dBm ={4} ""{5}""", DateTime.UtcNow.ToString("HH:mm:ss"), e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText));
#endif
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.Message);
			}
		}

		private static void Rfm9XDevice_OnTransmit(object sender, Rfm9XDevice.OnDataTransmitedEventArgs e)
		{
			Console.WriteLine( string.Format("{0}-TX Done", DateTime.UtcNow.ToString("HH:mm:ss")));
		}

#if ST_STM32F429I_DISCOVERY
      static int PinNumber(char port, byte pin)
      {
         if (port < 'A' || port > 'J')
            throw new ArgumentException();

         return ((port - 'A') * 16) + pin;
      }
#endif
	}
}


The addressing support is pretty basic as my goal was a library that I could extend with optional functionality like tamper detection via signing and privacy via payload encryption, mesh network support etc.

The library works but should be treated as early beta.

nanoFramework LoRa library Part6

Transmit and Receive with Interrupts

For the final revision my nanoFramework SX127X Library test harness I checked interrupts were working for the interleaved transmission and reception of messages.

      private void InterruptGpioPin_ValueChanged(object sender, GpioPinValueChangedEventArgs e)
      {
         if (e.Edge != GpioPinEdge.RisingEdge)
         {
            return;
         }

         byte irqFlags = this.RegisterReadByte(0x12); // RegIrqFlags
         Console.WriteLine($"RegIrqFlags 0X{irqFlags:x2}");

         if ((irqFlags & 0b01000000) == 0b01000000)  // RxDone 
         {
            Console.WriteLine("Receive-Message");
            byte currentFifoAddress = this.RegisterReadByte(0x10); // RegFifiRxCurrent
            this.RegisterWriteByte(0x0d, currentFifoAddress); // RegFifoAddrPtr

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

            // Allocate buffer for message
            byte[] messageBytes = this.RegisterRead(0X0, numberOfBytes);

            // Remove unprintable characters from messages
            for (int index = 0; index < messageBytes.Length; index++)
            {
               if ((messageBytes[index] < 0x20) || (messageBytes[index] > 0x7E))
               {
                  messageBytes[index] = 0x20;
               }
            }

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

         if ((irqFlags & 0b00001000) == 0b00001000)  // TxDone
         {
            this.RegisterWriteByte(0x01, 0b10000101); // RegOpMode set LoRa & RxContinuous
            Console.WriteLine("Transmit-Done");
         }

         this.RegisterWriteByte(0x40, 0b00000000); // RegDioMapping1 0b00000000 DI0 RxReady & TxReady
         this.RegisterWriteByte(0x12, 0xff);// RegIrqFlags
      }

…
class Program
{
      static void Main()
      {
         int SendCount = 0;
#if ST_STM32F429I_DISCOVERY
         int chipSelectPinNumber = PinNumber('C', 2);
         int resetPinNumber = PinNumber('C', 3);
         int interruptPinNumber = PinNumber('A', 4);
#endif
#if ESP32_WROOM_32_LORA_1_CHANNEL
         int chipSelectPinNumber = Gpio.IO16;
         int interruptPinNumber = Gpio.IO26;
#endif

         try
         {
#if ESP32_WROOM_32_LORA_1_CHANNEL
            Configuration.SetPinFunction(Gpio.IO12, DeviceFunction.SPI1_MISO);
            Configuration.SetPinFunction(Gpio.IO13, DeviceFunction.SPI1_MOSI);
            Configuration.SetPinFunction(Gpio.IO14, DeviceFunction.SPI1_CLOCK);
            Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SpiBusId, chipSelectPinNumber, interruptPinNumber);
#endif
#if ST_STM32F429I_DISCOVERY
            Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SpiBusId, chipSelectPinNumber, resetPinNumber, interruptPinNumber);
#endif
            Thread.Sleep(500);

            // Put device into LoRa + Standby 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 {SendCount += 1}!";

               // 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 {messageBytes.Length} bytes message {messageText}");
               rfm9XDevice.RegisterWriteByte(0x01, 0b10000011); // RegOpMode 

               Thread.Sleep(10000);
            }
         }
         catch (Exception ex)
         {
            Console.WriteLine(ex.Message);
         }
      }

The diagnostic output shows inbound and outbound messages

'nanoFramework.Tools.VS2019.Extension.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.NetNF\packages\nanoFramework.Hardware.Esp32.1.2.1-preview.10\lib\nanoFramework.Hardware.Esp32.dll', Symbols loaded.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Sending 13 bytes message Hello LoRa 1!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X50
Receive-Message
Received 17 byte message HeLoRa World! 136
Sending 13 bytes message Hello LoRa 2!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X50
Receive-Message
Received 17 byte message HeLoRa World! 138
Sending 13 bytes message Hello LoRa 3!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X50
Receive-Message
Received 17 byte message HeLoRa World! 140
20:32:58.079 -> Sending HeLoRa World! 134
20:33:04.553 -> Message: Hello LoRa 1!
20:33:04.553 -> Length: 13
20:33:04.587 -> FirstChar: 72
20:33:04.587 -> RSSI: -52
20:33:04.587 -> Snr: 9.50
20:33:04.622 -> 
20:33:08.137 -> Sending HeLoRa World! 136
20:33:14.568 -> Message: Hello LoRa 2!
20:33:14.568 -> Length: 13
20:33:14.602 -> FirstChar: 72
20:33:14.602 -> RSSI: -53
20:33:14.602 -> Snr: 9.75
20:33:14.635 -> 
20:33:19.135 -> Sending HeLoRa World! 138
20:33:24.560 -> Message: Hello LoRa 3!
20:33:24.560 -> Length: 13
20:33:24.594 -> FirstChar: 72
20:33:24.594 -> RSSI: -52
20:33:24.594 -> Snr: 9.25
20:33:24.628 -> 

There did appear to be some oddness (leading to corrupted first message) with the RegOpMode setting(0b10000000 vs. 0b10000001) for my STM32F429 Discovery and Sparkfun LoRa Gateway 1 Channel ESP32.

I think it maybe due to the Discovery having the rest line connected but unlike the Sparkfun LoRa Gateway.

RFM9X.TinyCLR V2 on Github

The source code of my GHI Electronics TinyCLR-OS RFM9X/SX127X library is live on GitHub. The test harness uses a dragino technology LoRa shield for Arduino with some jumper wires. For a more robust solution I have some Casco Logix RFM95 LoRa MikroBUS Modules on order (May 2020)

SC20100 with Dragino shield

A sample application which shows how to send/receive address/un-addresses payloads

//---------------------------------------------------------------------------------
// Copyright (c) March/April 2020, 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.LoRaDeviceClient
{
	using System;
	using System.Diagnostics;
	using System.Text;
	using System.Threading;

	using GHIElectronics.TinyCLR.Pins;

	using devMobile.IoT.Rfm9x;

	class Program
   {
      static void Main()
      {
			//const string DeviceName = "SC20100LoRa";
			//const string HostName = "LoRaIoT1";
			const string DeviceName = "LoRaIoT1";
#if ADDRESSED_MESSAGES_PAYLOAD
			const string HostName = "LoRaIoT2";
#endif
			const double Frequency = 915000000.0;
			byte MessageCount = System.Byte.MaxValue;
			Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14, SC20100.GpioPin.PE4);

			rfm9XDevice.Initialise(Frequency, paBoost: true, rxPayloadCrcOn: true);
#if DEBUG
			rfm9XDevice.RegisterDump();
#endif

			rfm9XDevice.OnReceive += Rfm9XDevice_OnReceive;
#if ADDRESSED_MESSAGES_PAYLOAD
			rfm9XDevice.Receive(UTF8Encoding.UTF8.GetBytes(DeviceName));
#else
			rfm9XDevice.Receive();
#endif
			rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

			Thread.Sleep(10000);

			while (true)
			{
				string messageText = string.Format("Hello from {0} ! {1}", DeviceName, MessageCount);
				MessageCount -= 1;

				byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
				Debug.WriteLine($"{DateTime.Now:HH:mm:ss}-TX {messageBytes.Length} byte message {messageText}");
#if ADDRESSED_MESSAGES_PAYLOAD
				rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes(HostName), messageBytes);
#else
				rfm9XDevice.Send(messageBytes);
#endif
				Thread.Sleep(10000);
			}
		}

		private static void Rfm9XDevice_OnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
		{
			try
			{
				string messageText = UTF8Encoding.UTF8.GetString(e.Data);

#if ADDRESSED_MESSAGES_PAYLOAD
				string addressText = UTF8Encoding.UTF8.GetString(e.Address);

				Debug.WriteLine($@"{DateTime.Now:HH:mm:ss}-RX From {addressText} PacketSnr {e.PacketSnr} Packet RSSI {e.PacketRssi}dBm RSSI {e.Rssi}dBm = {e.Data.Length} byte message ""{messageText}""");
#else
				Debug.WriteLine($@"{DateTime.Now:HH:mm:ss}-RX PacketSnr {e.PacketSnr} Packet RSSI {e.PacketRssi}dBm RSSI {e.Rssi}dBm = {e.Data.Length} byte message ""{messageText}""");
#endif
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex.Message);
			}
		}

		private static void Rfm9XDevice_OnTransmit(object sender, Rfm9XDevice.OnDataTransmitedEventArgs e)
		{
			Debug.WriteLine($"{DateTime.Now:HH:mm:ss}-TX Done");
		}
	}
}

The addressing support is pretty basic as my goal was a library that I could extend with optional functionality like tamper detection via signing and privacy via payload encryption, mesh network support etc.

The library works but should be treated as late beta.

TinyCLR OS V2 LoRa library Part4

Transmit and Receive with Interrupts

For the final revision of the “nasty” test harness I ensured interrupts were working for the simultaneous transmission and reception of messages. It’s not quite simultaneous, the code sends a message every 10 seconds then goes back to receive continuous mode after each message has been sent.

private void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs e)
{
   if (e.Edge != GpioPinEdge.RisingEdge)
   {
      return;
   }

   byte irqFlags = this.RegisterReadByte(0x12); // RegIrqFlags
   Debug.WriteLine($"RegIrqFlags 0X{irqFlags:x2}");

   if ((irqFlags & 0b01000000) == 0b01000000)  // RxDone 
   {
      Debug.WriteLine("Receive-Message");
      byte currentFifoAddress = this.RegisterReadByte(0x10); // RegFifiRxCurrent
      this.RegisterWriteByte(0x0d, currentFifoAddress); // RegFifoAddrPtr

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

      // Allocate buffer for message
      byte[] messageBytes = this.RegisterRead(0X0, numberOfBytes);

      string messageText = UTF8Encoding.UTF8.GetString(messageBytes);
      Debug.WriteLine($"Received {messageBytes.Length} byte message {messageText}");
   }

  if ((irqFlags & 0b00001000) == 0b00001000)  // TxDone
  {
      this.RegisterWriteByte(0x01, 0b10000101); // RegOpMode set LoRa & RxContinuous
      Debug.WriteLine("Transmit-Done");
  }

   this.RegisterWriteByte(0x40, 0b00000000); // RegDioMapping1 0b00000000 DI0 RxReady & TxReady
   this.RegisterWriteByte(0x12, 0xff);// RegIrqFlags
}
…
class Program
{
   static void Main()
   {
      Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14, SC20100.GpioPin.PE4);
      int sendCount = 0;

      // 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 

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

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

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

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

         string messageText = $"Hello LoRa {sendCount += 1}!";

         // 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
         rfm9XDevice.RegisterWriteByte(0x40, 0b01000000); // RegDioMapping1 0b00000000 DI0 RxReady & TxReady
         rfm9XDevice.RegisterWriteByte(0x01, 0b10000011); // RegOpMode 

         Debug.WriteLine($"Sending {messageBytes.Length} bytes message {messageText}");

         Thread.Sleep(10000);
      }
   }
}

The diagnostic output shows inbound and outbound messages

Found debugger!
Create TS.
Loading Deployment Assemblies.
Attaching deployed file.
Assembly: mscorlib (2.0.0.0)  Attaching deployed file.
Assembly: GHIElectronics.TinyCLR.Devices.Spi (2.0.0.0)  Attaching deployed file.
Assembly: GHIElectronics.TinyCLR.Native (2.0.0.0)  Attaching deployed file.
Assembly: GHIElectronics.TinyCLR.Devices.Gpio (2.0.0.0)  Attaching deployed file.
Assembly: ReceiveTransmitInterrupt (1.0.0.0)  Resolving.
The debugging target runtime is loading the application assemblies and starting execution.
Ready.
'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ReceiveTransmitInterrupt\bin\Debug\pe\..\GHIElectronics.TinyCLR.Native.dll'
'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ReceiveTransmitInterrupt\bin\Debug\pe\..\GHIElectronics.TinyCLR.Devices.Gpio.dll'
'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ReceiveTransmitInterrupt\bin\Debug\pe\..\GHIElectronics.TinyCLR.Devices.Spi.dll'
'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ReceiveTransmitInterrupt\bin\Debug\pe\..\ReceiveTransmitInterrupt.exe', Symbols loaded.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Sending 13 bytes message Hello LoRa 1!
RegIrqFlags 0X08
Transmit-Done
Sending 13 bytes message Hello LoRa 2!
RegIrqFlags 0X08
Transmit-Done
Sending 13 bytes message Hello LoRa 3!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X50
Receive-Message
Received 59 byte message  �LoRaIoT1Maduino2at 77.9,ah 24,wsa 0,wsg 1,wd 12.38,r 0.00,
Sending 13 bytes message Hello LoRa 4!
RegIrqFlags 0X08
Transmit-Done
15:11:42.775 -> ync word
15:11:42.775 -> 0x0: 0x8A
15:11:42.775 -> 0x1: 0x81
15:11:42.798 -> 0x2: 0x1A
15:11:42.798 -> 0x3: 0xB
15:11:42.798 -> 0x4: 0x0
15:11:42.798 -> 0x5: 0x52
…
15:11:44.223 -> 0x7B: 0x0
15:11:44.223 -> 0x7C: 0x0
15:11:44.256 -> 0x7D: 0x0
15:11:44.256 -> 0x7E: 0x0
15:11:44.256 -> 0x7F: 0x0
15:11:44.291 -> LoRa init succeeded.
15:11:44.839 -> Sending HeLoRa World! 0
15:11:48.788 -> Message: ⸮LoRaIoT1Maduino2at 77.5,ah 25,wsa 1,wsg 5,wd 21.00,r 0.00,
15:11:48.856 -> Length: 59
15:11:48.856 -> FirstChar: 136
15:11:48.891 -> RSSI: -83
15:11:48.891 -> Snr: 9.50
15:11:48.891 -> 
15:11:49.234 -> Message: Hello LoRa 22!
15:11:49.234 -> Length: 14
15:11:49.268 -> FirstChar: 72
15:11:49.268 -> RSSI: -47
15:11:49.268 -> Snr: 9.75
15:11:49.303 -> 
15:11:55.815 -> Sending HeLoRa World! 2
15:11:59.219 -> Message: Hello LoRa 23!
15:11:59.219 -> Length: 14
15:11:59.254 -> FirstChar: 72
15:11:59.254 -> RSSI: -48
15:11:59.254 -> Snr: 9.75
15:11:59.288 -> 
15:12:06.597 -> Sending HeLoRa World! 4
15:12:09.218 -> Message: Hello LoRa 24!
15:12:09.218 -> Length: 14
15:12:09.253 -> FirstChar: 72
15:12:09.253 -> RSSI: -46
15:12:09.253 -> Snr: 9.25
15:12:09.287 -> 
15:12:16.919 -> Sending HeLoRa World! 6
15:12:19.240 -> Message: Hello LoRa 25!
15:12:19.240 -> Length: 14
15:12:19.275 -> FirstChar: 72
15:12:19.275 -> RSSI: -47
15:12:19.275 -> Snr: 9.75
15:12:19.309 -> 

The final step is back porting all the necessary changes to my Rfm9XDevice class then functionality and stress testing.

TinyCLR OS V2 LoRa library Part3

Transmit and Receive Basic

I had an Armtronix IA005 SX1276 loRa node sitting on my desk so used it running a modified version of the Arduino LoRa library LoRaSetSyncWord example to send messages to and receive messages from my SC20100 device.

Armtronix and SC20100 TinyCLR V2 testrig

The SC20100 transmit application configures the SX127X, sends a message, waits until transmission is completed, then repeats every 30 seconds.

   class Program
   {
      static void Main()
      {
         Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14);

         int SendCount = 0;

         // 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

         //rfm9XDevice.RegisterDump();

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

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

            string messageText = $"Hello LoRa {SendCount += 1}!";
               
            // 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

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

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

            Thread.Sleep(30000);
         }
      }
   }

When I ran the SC20100 application in Visual Studio

'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\TransmitBasic\bin\Debug\pe\..\TransmitBasic.exe', Symbols loaded.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Sending 13 bytes message Hello LoRa 1!
Send-wait
.
.
.
.
Send-Done
Sending 13 bytes message Hello LoRa 2!
Send-wait
.
.
.
.
Send-Done

I could the see the messages arriving at the Armtronix device in the Arduino monitor.

14:13:34.722 -> Message: Hello LoRa 1!
14:13:34.722 -> Length: 13
14:13:34.756 -> FirstChar: 72
14:13:34.756 -> RSSI: -48
14:13:34.756 -> Snr: 9.75
14:13:34.790 -> 
14:13:36.658 -> Sending HeLoRa World! 24
14:13:47.105 -> Sending HeLoRa World! 26
14:13:57.740 -> Sending HeLoRa World! 28
14:14:04.745 -> Message: Hello LoRa 2!
14:14:04.745 -> Length: 13
14:14:04.779 -> FirstChar: 72
14:14:04.779 -> RSSI: -49
14:14:04.779 -> Snr: 9.50
14:14:04.847 -> 

The SC20100 receive application configures the SX127X, polls a status register to looking to see if a message has arrived, displays it as text and then goes back to waiting.

   class Program
   {
      static void Main()
      {
         Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14);

         // 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
            Debug.WriteLine("Receive-Wait");
            byte irqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
            while ((irqFlags & 0b01000000) == 0)  // wait until RxDone cleared
            {
               Thread.Sleep(100);
               irqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
               //Debug.Write(".");
            }
            Debug.WriteLine("");
            Debug.WriteLine($"RegIrqFlags 0X{irqFlags:X2}");
            Debug.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);
            Debug.WriteLine($"Received {messageBytes.Length} byte message {messageText}");

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

When I ran the SC20100 application in Visual Studio

'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ReceiveBasic\bin\Debug\pe\..\ReceiveBasic.exe', Symbols loaded.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Receive-Wait

RegIrqFlags 0X50
Receive-Message
Received 16 byte message HeLoRa World! 74
Receive-Done
Receive-Wait

RegIrqFlags 0X50
Receive-Message
Received 59 byte message  �LoRaIoT1Maduino2at 64.6,ah 66,wsa 2,wsg 3,wd 37.13,r 0.00,
Receive-Done
Receive-Wait

RegIrqFlags 0X50
Receive-Message
Received 16 byte message HeLoRa World! 76
Receive-Done
Receive-Wait

RegIrqFlags 0X50
Receive-Message
Received 16 byte message HeLoRa World! 78
Receive-Done
Receive-Wait

I could the see the messages arriving at the Armtronix device in the Arduino monitor.

14:18:02.785 -> Sending HeLoRa World! 74
14:18:09.270 -> Message: ⸮LoRaIoT1Maduino2at 64.6,ah 66,wsa 2,wsg 3,wd 37.13,r 0.00,
14:18:09.339 -> Length: 59
14:18:09.339 -> FirstChar: 136
14:18:09.407 -> RSSI: -83
14:18:09.407 -> Snr: 9.75
14:18:09.407 -> 
14:18:13.249 -> Sending HeLoRa World! 76
14:18:23.416 -> Sending HeLoRa World! 78
14:18:33.582 -> Sending HeLoRa World! 80
14:18:43.883 -> Sending HeLoRa World! 82
14:18:54.136 -> Sending HeLoRa World! 84

I’ll merge the transmit and receive on interrupt samples in the next post as a final step before porting the core library modules.