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

TinyCLR OS V2 LoRa library Part2

Register Dump, Read and Write

Next step was to dump all registers (0x00 thru 0x42) of the SX1276/7/8/9 device and confirm I could read/write registers as bytes, words and arrays of bytes. The original TinyCLR V1 code didn’t require many changes to work with TinyCLR V2(Preview 5), and these were mainly limited to SPI port setup.

I do like the TinyCLR support for string interpolation with the $ special character. Often neat language features like this have been first to go to reduce the size of the runtime.

namespace devMobile.IoT.Rfm9x.RegisterScan
{
   using System;
   using System.Diagnostics;
   using System.Threading;
   using GHIElectronics.TinyCLR.Devices.Gpio;
   using GHIElectronics.TinyCLR.Devices.Spi;
   using GHIElectronics.TinyCLR.Pins;

   public sealed class Rfm9XDevice
   {
      private SpiDevice rfm9XLoraModem = null;

      public Rfm9XDevice(string spiPortName, int chipSelectPin)
      {
         GpioPin chipSelectGpio = GpioController.GetDefault().OpenPin(chipSelectPin);

         var settings = new SpiConnectionSettings()
         {
            ChipSelectType = SpiChipSelectType.Gpio,
            ChipSelectLine = chipSelectGpio,
            Mode = SpiMode.Mode0,
            ClockFrequency = 500000,
            ChipSelectActiveState = false,
         };

         SpiController spiController = SpiController.FromName(spiPortName);

         rfm9XLoraModem = spiController.GetDevice(settings);
      }

      public Byte RegisterReadByte(byte registerAddress)
      {
         byte[] writeBuffer = new byte[] { registerAddress, 0x0 };
         byte[] readBuffer = new byte[writeBuffer.Length];
         Debug.Assert(rfm9XLoraModem != null);

         rfm9XLoraModem.TransferFullDuplex(writeBuffer, readBuffer);

         return readBuffer[1];
      }
   }

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

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

               Debug.WriteLine($"Register 0x{registerIndex:x2} - Value 0X{registerValue:x2}");
            }
            Debug.WriteLine("");

            Thread.Sleep(10000);
         }
      }
   }
}

The output of the application looked like this

'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\RegisterScan\bin\Debug\pe\..\RegisterScan.exe', Symbols loaded.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Register 0x00 - Value 0X00
Register 0x01 - Value 0X0f
Register 0x02 - Value 0X1a
Register 0x03 - Value 0X0b
Register 0x04 - Value 0X00
Register 0x05 - Value 0X52
Register 0x06 - Value 0X6c
Register 0x07 - Value 0X80
Register 0x08 - Value 0X00
Register 0x09 - Value 0X4f
Register 0x0a - Value 0X09
Register 0x0b - Value 0X2b
Register 0x0c - Value 0X20
Register 0x0d - Value 0X08
Register 0x0e - Value 0X02
Register 0x0f - Value 0X0a
Register 0x10 - Value 0Xff
Register 0x11 - Value 0X00
Register 0x12 - Value 0X15
Register 0x13 - Value 0X0b
Register 0x14 - Value 0X28
Register 0x15 - Value 0X0c
Register 0x16 - Value 0X12
Register 0x17 - Value 0X47
Register 0x18 - Value 0X32
Register 0x19 - Value 0X3e
Register 0x1a - Value 0X00
Register 0x1b - Value 0X00
Register 0x1c - Value 0X00
Register 0x1d - Value 0X00
Register 0x1e - Value 0X00
Register 0x1f - Value 0X40
Register 0x20 - Value 0X00
Register 0x21 - Value 0X00
Register 0x22 - Value 0X00
Register 0x23 - Value 0X00
Register 0x24 - Value 0X0d
Register 0x25 - Value 0X00
Register 0x26 - Value 0X03
Register 0x27 - Value 0X93
Register 0x28 - Value 0X55
Register 0x29 - Value 0X55
Register 0x2a - Value 0X55
Register 0x2b - Value 0X55
Register 0x2c - Value 0X55
Register 0x2d - Value 0X55
Register 0x2e - Value 0X55
Register 0x2f - Value 0X55
Register 0x30 - Value 0X90
Register 0x31 - Value 0X40
Register 0x32 - Value 0X40
Register 0x33 - Value 0X00
Register 0x34 - Value 0X00
Register 0x35 - Value 0X0f
Register 0x36 - Value 0X00
Register 0x37 - Value 0X00
Register 0x38 - Value 0X00
Register 0x39 - Value 0Xf5
Register 0x3a - Value 0X20
Register 0x3b - Value 0X82
Register 0x3c - Value 0X00
Register 0x3d - Value 0X02
Register 0x3e - Value 0X00
Register 0x3f - Value 0X40
Register 0x40 - Value 0X00
Register 0x41 - Value 0X00
Register 0x42 - Value 0X12

The device was not in LoRa mode (Bit 7 of RegOpMode 0x01) so the next step was to update the TinyCLR V1 code and confirm I could change the device configuration.

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

         rfm9XDevice.RegisterDump();

         while (true)
         {
            Debug.WriteLine("Read RegOpMode (read byte)");
            Byte regOpMode1 = rfm9XDevice.RegisterReadByte(0x1);
            Debug.WriteLine($"RegOpMode 0x{regOpMode1:x2}");

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

            Debug.WriteLine("Read RegOpMode (read byte)");
            Byte regOpMode2 = rfm9XDevice.RegisterReadByte(0x1);
            Debug.WriteLine($"RegOpMode 0x{regOpMode2:x2}");

            Debug.WriteLine("Read the preamble (read word)");
            ushort preamble = rfm9XDevice.RegisterReadWord(0x20);
            Debug.WriteLine($"Preamble 0x{preamble:x2}");

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

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

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

            rfm9XDevice.RegisterDump();

            Thread.Sleep(30000);
         }
      }

The output of the application looked like this

Register dump
Register 0x00 - Value 0X00
Register 0x01 - Value 0X09
Register 0x02 - Value 0X1a
Register 0x03 - Value 0X0b
Register 0x04 - Value 0X00
Register 0x05 - Value 0X52
Register 0x06 - Value 0X6c
Register 0x07 - Value 0X80
Register 0x08 - Value 0X00
Register 0x09 - Value 0X4f
Register 0x0a - Value 0X09
Register 0x0b - Value 0X2b
Register 0x0c - Value 0X20
Register 0x0d - Value 0X08
Register 0x0e - Value 0X02
Register 0x0f - Value 0X0a
Register 0x10 - Value 0Xff
Register 0x11 - Value 0X71
Register 0x12 - Value 0X15
Register 0x13 - Value 0X0b
Register 0x14 - Value 0X28
Register 0x15 - Value 0X0c
Register 0x16 - Value 0X12
Register 0x17 - Value 0X47
Register 0x18 - Value 0X32
Register 0x19 - Value 0X3e
Register 0x1a - Value 0X00
Register 0x1b - Value 0X00
Register 0x1c - Value 0X00
Register 0x1d - Value 0X00
Register 0x1e - Value 0X00
Register 0x1f - Value 0X40
Register 0x20 - Value 0X00
Register 0x21 - Value 0X00
Register 0x22 - Value 0X00
Register 0x23 - Value 0X00
Register 0x24 - Value 0X05
Register 0x25 - Value 0X00
Register 0x26 - Value 0X03
Register 0x27 - Value 0X93
Register 0x28 - Value 0X55
Register 0x29 - Value 0X55
Register 0x2a - Value 0X55
Register 0x2b - Value 0X55
Register 0x2c - Value 0X55
Register 0x2d - Value 0X55
Register 0x2e - Value 0X55
Register 0x2f - Value 0X55
Register 0x30 - Value 0X90
Register 0x31 - Value 0X40
Register 0x32 - Value 0X40
Register 0x33 - Value 0X00
Register 0x34 - Value 0X00
Register 0x35 - Value 0X0f
Register 0x36 - Value 0X00
Register 0x37 - Value 0X00
Register 0x38 - Value 0X00
Register 0x39 - Value 0Xf5
Register 0x3a - Value 0X20
Register 0x3b - Value 0X82
Register 0x3c - Value 0Xff
Register 0x3d - Value 0X02
Register 0x3e - Value 0X80
Register 0x3f - Value 0X40
Register 0x40 - Value 0X00
Register 0x41 - Value 0X00
Register 0x42 - Value 0X12
Read RegOpMode (read byte)
RegOpMode 0x09
Set LoRa mode and sleep mode (write byte)
Read RegOpMode (read byte)
RegOpMode 0x80
Read the preamble (read word)
Preamble 0x08
Set the preamble to 0x80 (write word)
Read the center frequency (read byte array)
Frequency Msb 0x6c Mid 0x80 Lsb 0x00
Set the center frequency to 915MHz (write byte array)
Register dump
Register 0x00 - Value 0X88
Register 0x01 - Value 0X80
Register 0x02 - Value 0X1a
Register 0x03 - Value 0X0b
Register 0x04 - Value 0X00
Register 0x05 - Value 0X52
Register 0x06 - Value 0Xe4
Register 0x07 - Value 0Xc0
Register 0x08 - Value 0X00
Register 0x09 - Value 0X4f
Register 0x0a - Value 0X09
Register 0x0b - Value 0X2b
Register 0x0c - Value 0X20
Register 0x0d - Value 0X01
Register 0x0e - Value 0X80
Register 0x0f - Value 0X00
Register 0x10 - Value 0X00
Register 0x11 - Value 0X00
Register 0x12 - Value 0X00
Register 0x13 - Value 0X00
Register 0x14 - Value 0X00
Register 0x15 - Value 0X00
Register 0x16 - Value 0X00
Register 0x17 - Value 0X00
Register 0x18 - Value 0X10
Register 0x19 - Value 0X00
Register 0x1a - Value 0X00
Register 0x1b - Value 0X00
Register 0x1c - Value 0X00
Register 0x1d - Value 0X72
Register 0x1e - Value 0X70
Register 0x1f - Value 0X64
Register 0x20 - Value 0X80
Register 0x21 - Value 0X00
Register 0x22 - Value 0X01
Register 0x23 - Value 0Xff
Register 0x24 - Value 0X00
Register 0x25 - Value 0X00
Register 0x26 - Value 0X04
Register 0x27 - Value 0X00
Register 0x28 - Value 0X00
Register 0x29 - Value 0X00
Register 0x2a - Value 0X00
Register 0x2b - Value 0X00
Register 0x2c - Value 0X00
Register 0x2d - Value 0X50
Register 0x2e - Value 0X14
Register 0x2f - Value 0X45
Register 0x30 - Value 0X55
Register 0x31 - Value 0Xc3
Register 0x32 - Value 0X05
Register 0x33 - Value 0X27
Register 0x34 - Value 0X1c
Register 0x35 - Value 0X0a
Register 0x36 - Value 0X03
Register 0x37 - Value 0X0a
Register 0x38 - Value 0X42
Register 0x39 - Value 0X12
Register 0x3a - Value 0X49
Register 0x3b - Value 0X1d
Register 0x3c - Value 0X00
Register 0x3d - Value 0Xaf
Register 0x3e - Value 0X00
Register 0x3f - Value 0X00
Register 0x40 - Value 0X00
Register 0x41 - Value 0X00
Register 0x42 - Value 0X12

In addition to confirming register read/write worked, I also temporarily exposed the SX127X/RFM9X reset functionality to ensure the device registers were being reset to factory as well.

TinyCLR OS V2 LoRa library Part1

Shortly after finishing my TinyCLR OS V1 LoRa Library port GHI Electronics started talking publicly about their new SITCore hardware and updated TinyCLR.

To get started I used a Dragino LoRa shield for Arduino and some jumper leads to connect it to the SC20100 device I had been sent

Dragino Arduino LoRa Shield Schematic

The shield uses D10 for chip select, D2 for RFM9X DI0 interrupt and D9 for Reset. The shield ships with the SPI lines configured for ICSP so the three jumpers diagonally across the shield from the antenna connector need to be swapped to the side closest to the edge of the shield.

SC20100 Device with Dragino Shield

First step was to confirm I could (using the TinyCLR SPI NuGet library) read a couple of the Semtech SX1276 registers.

namespace devMobile.IoT.Rfm9x.ShieldSpi
{
   using System;
   using System.Diagnostics;

   using System.Threading;
   using GHIElectronics.TinyCLR.Devices.Spi;
   using GHIElectronics.TinyCLR.Pins;

   class Program
   {
      static void Main()
      {
         var settings = new SpiConnectionSettings()
         {
            ChipSelectType = SpiChipSelectType.Gpio,
            //ChipSelectLine = FEZ.GpioPin.D10,
            ChipSelectLine = GHIElectronics.TinyCLR.Devices.Gpio.GpioController.GetDefault().OpenPin(SC20100.GpioPin.PA13), 
            Mode = SpiMode.Mode0,
            //Mode = SpiMode.Mode1,
            //Mode = SpiMode.Mode2,
            //Mode = SpiMode.Mode3,
            ClockFrequency = 500000,
            //DataBitLength = 8, Removed as part of TiyCLR V2 Upgrade
            //ChipSelectActiveState = true
            ChipSelectActiveState = false,
            //ChipSelectHoldTime = new TimeSpan(0, 0, 0, 0, 500),
            //ChipSelectSetupTime = new TimeSpan(0, 0, 0, 0, 500),
         };

         //var controller = SpiController.FromName(FEZ.SpiBus.Spi1);
         var controller = SpiController.FromName(SC20100.SpiBus.Spi3);
         var device = controller.GetDevice(settings);

         Thread.Sleep(500);

         while (true)
         {
            byte register;
            byte[] writeBuffer;
            byte[] readBuffer;

            // Silicon Version info
            register = 0x42; // RegVersion expecting 0x12

            // Frequency
            //register = 0x06; // RegFrfMsb expecting 0x6C
            //register = 0x07; // RegFrfMid expecting 0x80
            //register = 0x08; // RegFrfLsb expecting 0x00

            //register = 0x17; //RegPayoadLength expecting 0x47

            // Preamble length 
            //register = 0x18; // RegPreambleMsb expecting 0x32
            //register = 0x19; // RegPreambleLsb expecting 0x3E

            //register <<= 1;
            //register |= 0x80;

            //writeBuffer = new byte[] { register };
            writeBuffer = new byte[] { register, 0x0 };
            //writeBuffer = new byte[] {register, 0x0, 0x0};
            //writeBuffer = new byte[] {register, 0x0, 0x0, 0x0};

            readBuffer = new byte[writeBuffer.Length];

            //device.TransferSequential(writeBuffer, readBuffer);
            device.TransferFullDuplex(writeBuffer, readBuffer);

            Debug.WriteLine("Value = 0x" + BytesToHexString(readBuffer));

            Thread.Sleep(1000);
         }
      }

      private static string BytesToHexString(byte[] bytes)
      {
         string hexString = string.Empty;

         // Loop through the bytes.
         for (byte b = 0; b < bytes.Length; b++)
         {
            if (b > 0)
               hexString += "-";

            hexString += bytes[b].ToString("x2");
         }

         return hexString;
      }
   }
}

After updating the way the chip select line was configured I could successfully read the RegVersion and default frequency values

'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ShieldSPI\bin\Debug\pe\..\GHIElectronics.TinyCLR.Native.dll'
'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ShieldSPI\bin\Debug\pe\..\GHIElectronics.TinyCLR.Devices.Gpio.dll'
'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ShieldSPI\bin\Debug\pe\..\GHIElectronics.TinyCLR.Devices.Spi.dll'
'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ShieldSPI\bin\Debug\pe\..\ShieldSPI.exe', Symbols loaded.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Value = 0x00-12
Value = 0x00-12
Value = 0x00-12
Value = 0x00-12
Value = 0x00-12

I also updated the BytesToHexString method to use the byte.ToString( string format) overload which is implemented by the TinyCLR runtime.

RFM9X.TinyCLR on Github

The source code of V1.0 of my GHI Electronics TinyCLR-OS RFM9X/SX127X library is on GitHub.

I initially started with a Dragino LoRa Shield for Arduino but have tested with an Elecrow RFM95 shield as well.

Dragino LoRa Shield for Arduino based test harness

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

//---------------------------------------------------------------------------------
// Copyright (c) March 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 = "FEZLoRa";
#if ADDRESSED_MESSAGES_PAYLOAD
			const string HostName = "LoRaIoT1";
#endif
			const double Frequency = 915000000.0;
			byte MessageCount = System.Byte.MaxValue;
			Rfm9XDevice rfm9XDevice = new Rfm9XDevice(FEZ.GpioPin.D10, FEZ.GpioPin.D9, FEZ.GpioPin.D2);

			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 LoRa library Part8

Transmit and Receive with Interrupts

For the final iteration of the “nasty” test harness I got the interrupts working for the transmitting and receiving 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.

      public Rfm9XDevice(int chipSelectPin, int resetPin, int interruptPin)
      {
         var settings = new SpiConnectionSettings()
         {
            ChipSelectType = SpiChipSelectType.Gpio,
            ChipSelectLine = chipSelectPin,
            Mode = SpiMode.Mode0,
            ClockFrequency = 500000,
            DataBitLength = 8,
            ChipSelectActiveState = false,
         };

         SpiController spiCntroller = SpiController.FromName(FEZ.SpiBus.Spi1);

         rfm9XLoraModem = spiCntroller.GetDevice(settings);

         // Factory reset pin configuration
         GpioController gpioController = GpioController.GetDefault();
         GpioPin resetGpioPin = gpioController.OpenPin(resetPin);
         resetGpioPin.SetDriveMode(GpioPinDriveMode.Output);
         resetGpioPin.Write(GpioPinValue.Low);
         Thread.Sleep(10);
         resetGpioPin.Write(GpioPinValue.High);
         Thread.Sleep(10);

         // Interrupt pin for RX message & TX done notification 
         InterruptGpioPin = gpioController.OpenPin(interruptPin);
         resetGpioPin.SetDriveMode(GpioPinDriveMode.Input);

         InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;
      }

      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
      }

…

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

            Debug.WriteLine($"Register 0x{registerIndex:x2} - Value 0X{registerValue:x2}");
         }
      }
   }

   class Program
   {
      static void Main()
      {
         Rfm9XDevice rfm9XDevice = new Rfm9XDevice(FEZ.GpioPin.D10, FEZ.GpioPin.D9, FEZ.GpioPin.D2);
         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 (Arduino then Meadow diagnostics)

'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
RegIrqFlags 0X50
Receive-Message
Received 17 byte message HeLoRa World! 156
Sending 13 bytes message Hello LoRa 2!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X50
Receive-Message
Received 17 byte message HeLoRa World! 158
Sending 13 bytes message Hello LoRa 3!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X50
Receive-Message
Received 17 byte message HeLoRa World! 160
Sending 13 bytes message Hello LoRa 4!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X50
Receive-Message
Received 17 byte message HeLoRa World! 162
Sending 13 bytes message Hello LoRa 5!
RegIrqFlags 0X08
Transmit-Done
RegIrqFlags 0X50
Receive-Message
Received 17 byte message HeLoRa World! 164
Sending 13 bytes message Hello LoRa 6!
RegIrqFlags 0X08
Transmit-Done

20:55:09.280 -> Sending HeLoRa World! 152
20:55:25.179 -> Message: Hello LoRa 1!
20:55:25.179 -> RSSI: -44
20:55:25.212 -> Snr: 9.50
20:55:25.212 -> 
20:55:30.707 -> Sending HeLoRa World! 156
20:55:35.182 -> Message: Hello LoRa 2!
20:55:35.182 -> RSSI: -43
20:55:35.215 -> Snr: 9.50
20:55:35.215 -> 
20:55:41.358 -> Sending HeLoRa World! 158
20:55:45.181 -> Message: Hello LoRa 3!
20:55:45.181 -> RSSI: -44
20:55:45.214 -> Snr: 9.50
20:55:45.214 -> 
20:55:52.032 -> Sending HeLoRa World! 160
20:55:55.190 -> Message: Hello LoRa 4!
20:55:55.190 -> RSSI: -44
20:55:55.258 -> Snr: 9.50
20:55:55.258 -> 
20:56:02.451 -> Sending HeLoRa World! 162
20:56:05.206 -> Message: Hello LoRa 5!
20:56:05.206 -> RSSI: -41
20:56:05.241 -> Snr: 9.00
20:56:05.241 -> 
20:56:12.685 -> Sending HeLoRa World! 164
20:56:15.226 -> Message: Hello LoRa 6!
20:56:15.226 -> RSSI: -41
20:56:15.261 -> Snr: 9.25
20:56:15.261 -> 
20:56:22.817 -> Sending HeLoRa World! 166
20:56:25.248 -> Message: Hello LoRa 7!
20:56:25.248 -> RSSI: -36
20:56:25.283 -> Snr: 10.00
20:56:25.283 -> 

Next step is some refactoring to extract the register access code and merging with my Windows 10 IoT Core RMF9X library code base.

TinyCLR OS 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(int chipSelectPin, int resetPin, int interruptPin)
      {
         var settings = new SpiConnectionSettings()
         {
            ChipSelectType = SpiChipSelectType.Gpio,
            ChipSelectLine = chipSelectPin,
            Mode = SpiMode.Mode0,
            ClockFrequency = 500000,
            DataBitLength = 8,
            ChipSelectActiveState = false,
         };

         SpiController spiCntroller = SpiController.FromName(FEZ.SpiBus.Spi1);

         rfm9XLoraModem = spiCntroller.GetDevice(settings);

         // Factory reset pin configuration
         GpioController gpioController = GpioController.GetDefault();
         GpioPin resetGpioPin = gpioController.OpenPin(resetPin);
         resetGpioPin.SetDriveMode(GpioPinDriveMode.Output);
         resetGpioPin.Write(GpioPinValue.Low);
         Thread.Sleep(10);
         resetGpioPin.Write(GpioPinValue.High);
         Thread.Sleep(10);

         // Interrupt pin for RX message & TX done notification 
         InterruptGpioPin = gpioController.OpenPin(interruptPin);
         resetGpioPin.SetDriveMode(GpioPinDriveMode.Input);

         InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;
      }

      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 & 0b00001000) == 0b00001000)  // TxDone
         {
            Debug.WriteLine("Transmit-Done");
         }

         this.RegisterWriteByte(0x12, 0xff);// RegIrqFlags
      }
…
   class Program
   {
      static void Main()
      {
         Rfm9XDevice rfm9XDevice = new Rfm9XDevice(FEZ.GpioPin.D10, FEZ.GpioPin.D9, FEZ.GpioPin.D2);
         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

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

            Thread.Sleep(10000);
         }
      }
   }

The output in the debug window

'GHIElectronics.TinyCLR.VisualStudio.ProjectSystem.dll' (Managed): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\TransmitInterrupt\bin\Debug\pe\..\TransmitInterrupt.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
Sending 13 bytes message Hello LoRa 4!
RegIrqFlags 0X08
Transmit-Done
Sending 13 bytes message Hello LoRa 5!
RegIrqFlags 0X08
Transmit-Done

On the Arduino test client the serial monitor displayed

19:39:55.595 -> LoRa init succeeded.
19:39:56.157 -> Sending HeLoRa World! 0
19:39:58.742 -> Message: Hello LoRa 5!
19:39:58.742 -> RSSI: -36
19:39:58.789 -> Snr: 9.50
19:39:58.789 -> 
19:40:07.151 -> Sending HeLoRa World! 2
19:40:11.920 -> Message: Hello LoRa 1!
19:40:11.920 -> RSSI: -41
19:40:11.967 -> Snr: 9.75
19:40:11.967 -> 
19:40:17.924 -> Sending HeLoRa World! 4
19:40:21.905 -> Message: Hello LoRa 2!
19:40:21.905 -> RSSI: -41
19:40:21.952 -> Snr: 9.50
19:40:21.952 -> 
19:40:28.248 -> Sending HeLoRa World! 6
19:40:31.928 -> Message: Hello LoRa 3!
19:40:31.928 -> RSSI: -36
19:40:31.975 -> Snr: 9.50
19:40:31.975 -> 
19:40:38.803 -> Sending HeLoRa World! 8
19:40:41.928 -> Message: Hello LoRa 4!
19:40:41.928 -> RSSI: -34
19:40:41.975 -> Snr: 9.25
19:40:41.975 -> 

Now that I’m confident my hardware is all working as expected the next step will be building a client which has a receive and transmit interrupt handler.

TinyCLR OS 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 TinyCLR GpioPinValueChangedEventHandler.

   public sealed class Rfm9XDevice
   {
      private SpiDevice rfm9XLoraModem;
      private GpioPin InterruptGpioPin = null;
      private const byte RegisterAddressReadMask = 0X7f;
      private const byte RegisterAddressWriteMask = 0x80;

      public Rfm9XDevice(int chipSelectPin, int resetPin, int interruptPin)
      {
         var settings = new SpiConnectionSettings()
         {
            ChipSelectType = SpiChipSelectType.Gpio,
            ChipSelectLine = chipSelectPin,
            Mode = SpiMode.Mode0,
            ClockFrequency = 500000,
            DataBitLength = 8,
            ChipSelectActiveState = false,
         };

         SpiController spiCntroller = SpiController.FromName(FEZ.SpiBus.Spi1);

         rfm9XLoraModem = spiCntroller.GetDevice(settings);

         // Factory reset pin configuration
         GpioController gpioController = GpioController.GetDefault();
         GpioPin resetGpioPin = gpioController.OpenPin(resetPin);
         resetGpioPin.SetDriveMode(GpioPinDriveMode.Output);
         resetGpioPin.Write(GpioPinValue.Low);
         Thread.Sleep(10);
         resetGpioPin.Write(GpioPinValue.High);
         Thread.Sleep(10);

         // Interrupt pin for RX message & TX done notification 
         InterruptGpioPin = gpioController.OpenPin(interruptPin);
         resetGpioPin.SetDriveMode(GpioPinDriveMode.Input);

         InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;
      }

      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

            // Get number of bytes in the message
            byte[] messageBytes = this.RegisterRead(0x00, numberOfBytes);

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

         this.RegisterWriteByte(0x12, 0xff);// RegIrqFlags
      }
...
   class Program
   {
      static void Main()
      {
         Rfm9XDevice rfm9XDevice = new Rfm9XDevice(FEZ.GpioPin.D10, FEZ.GpioPin.D9, FEZ.GpioPin.D2);

         // 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();

         Debug.WriteLine("Receive-Wait");
         Thread.Sleep(Timeout.Infinite);
      }
   }

The output in the output debug window looked like this

'C:\Users\BrynLewis\source\repos\RFM9X.TinyCLR\ReceiveInterrupt\bin\Debug\pe\..\ReceiveInterrupt.exe', Symbols loaded.
The thread '<No Name>' (0x2) has exited with code 0 (0x0).
Register dump
Register 0x00 - Value 0X88
Register 0x01 - Value 0X85
Register 0x02 - Value 0X1a
..
Register 0x41 - Value 0X00
Register 0x42 - Value 0X12
Receive-Wait
RegIrqFlags 0X50
Receive-Message
Received 15 byte message HeLoRa World! 0
RegIrqFlags 0X50
Receive-Message
Received 15 byte message HeLoRa World! 2
RegIrqFlags 0X50
Receive-Message
Received 15 byte message HeLoRa World! 4
RegIrqFlags 0X50
Receive-Message
Received 15 byte message HeLoRa World! 6
RegIrqFlags 0X50
Receive-Message
Received 15 byte message HeLoRa World! 8
RegIrqFlags 0X50
Receive-Message
Received 16 byte message HeLoRa World! 10
RegIrqFlags 0X50
Receive-Message
Received 16 byte message HeLoRa World! 12

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