# MATH131 Numerical methods was useful

Back in 1986 in my second first year at the University of Canterbury I did “MATH131 Numerical Methods” which was a year of looking at why mathematics in FORTRAN, C, and Pascal sometimes didn’t return the result you were expecting…

While testing my GHI Electronics TinyCLR2 RAK Wireless RAK811 LoRaWAN client I noticed the temperature numbers didn’t quite match…

I have implemented my own Cayenne Low Power Payload encoder in C# based on the sample Mbed C code

```uint8_t CayenneLPP::addTemperature(uint8_t channel, float celsius) {
if ((cursor + LPP_TEMPERATURE_SIZE) > maxsize) {
return 0;
}
int16_t val = celsius * 10;
buffer[cursor++] = channel;
buffer[cursor++] = LPP_TEMPERATURE;
buffer[cursor++] = val >> 8;
buffer[cursor++] = val;

return cursor;
}
```

My translation of that code to C#

```public void TemperatureAdd(byte channel, float celsius)
{
if ((index + TemperatureSize) > buffer.Length)
{
throw new ApplicationException("TemperatureAdd insufficent buffer capacity");
}

short val = (short)(celsius * 10);

buffer[index++] = channel;
buffer[index++] = (byte)DataType.Temperature;
buffer[index++] = (byte)(val >> 8);
buffer[index++] = (byte)val;
}
```

After looking at the code I think the issues was most probably due to the representation of the constant 10(int32), 10.0(double), and 10.0f(single) . To confirm my theory I modified the client to send the temperature with the calculation done with three different constants.

After some trial and error I settled on this C# code for my decoder

```public void TemperatureAdd(byte channel, float celsius)
{
if ((index + TemperatureSize) > buffer.Length)
{
throw new ApplicationException("TemperatureAdd insufficent buffer capacity");
}

short val = (short)(celsius * 10.0f);

buffer[index++] = channel;
buffer[index++] = (byte)DataType.Temperature;
buffer[index++] = (byte)(val >> 8);
buffer[index++] = (byte)val;
}
```

I don’t think this is specifically an issue with the TinyCLR V2 just with number type used for the constant.

# nRF24L01-TinyCLR V2 RC2 on Github

The source code of RC2 of my port GHI Electronics TinyCLR-0SV2RC1 nRF24L01 library is live on GitHub. The sample application now supports Fezduino (with embeddedcoolness.com or other Arduino shield), Fezportal and the SC2010 Dev board (with mikroe nrf24C Click, mikroe nRF24S Click or mikroenRF24T Click) .

The application has gained four compile time configuration options

• TINYCLR_V2_SC20100DEV_MIKROBUS_1
• TINYCLR_V2_SC20100DEV_MIKROBUS_2
• TINYCLR_V2_FEZDUINO
• TINYCLR_V2_FEZPORTAL

These options configure the chip enable, chip selected and interrupt pins.

```//---------------------------------------------------------------------------------
// 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
//
//
// 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.
//
// Need one of TINYCLR_V2_SC20100DEV_MIKROBUS_1/TINYCLR_V2_SC20100DEV_MIKROBUS_2/TINYCLR_V2_FEZDUINO/TINYCLR_V2_FEZPORTAL defined
//---------------------------------------------------------------------------------
namespace devMobile.IoT.FieldGateway.TinyCLRV2nRF24Client
{
using System;
using System.Diagnostics;
using System.Text;

using GHIElectronics.TinyCLR.Pins;

class Program
{
private const string BaseStationAddress = "Base1";
private const string DeviceAddress = "Dev01";

static void Main()
{
RF24 radio = new RF24();
byte messageCount = System.Byte.MaxValue;

try
{

#if TINYCLR_V2_SC20100DEV_MIKROBUS_1
radio.Initialize(SC20100.SpiBus.Spi3, SC20100.GpioPin.PD4, SC20100.GpioPin.PD3, SC20100.GpioPin.PC5);
#endif
#if TINYCLR_V2_SC20100DEV_MIKROBUS_2
radio.Initialize(SC20100.SpiBus.Spi3, SC20100.GpioPin.PD15, SC20100.GpioPin.PD14, SC20100.GpioPin.PA8);
#endif
#if TINYCLR_V2_FEZDUINO
radio.Initialize(SC20100.SpiBus.Spi6, SC20100.GpioPin.PE11, SC20100.GpioPin.PC4, SC20100.GpioPin.PA1);
#endif
#if TINYCLR_V2_FEZPORTAL
radio.Initialize(SC20100.SpiBus.Spi3, SC20100.GpioPin.PD4, SC20100.GpioPin.PC13, SC20100.GpioPin.PC2);
#endif

while (true)
{
string payload = \$"hello {messageCount}";
messageCount -= 1;

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

{
// display as hex
Debug.WriteLine(\$"{DateTime.UtcNow:HH:mm:ss}-RX Hex Length {data.Length} Payload {BitConverter.ToString(data)}");

// Display as Unicode
string unicodeText = Encoding.UTF8.GetString(data);
Debug.WriteLine(\$"{DateTime.UtcNow:HH:mm:ss}-RX Unicode Length {unicodeText.Length} Unicode text {unicodeText}");
}

private static void Radio_OnTransmitSuccess()
{
Debug.WriteLine(\$"{DateTime.UtcNow:HH:mm:ss}-TX Succeeded!");
}

private static void Radio_OnTransmitFailed()
{
Debug.WriteLine(\$"{DateTime.UtcNow:HH:mm:ss}-TX failed!");
}
}
}
```

# RFM9X.TinyCLR V2 RC2 on Github

The source code of RC2 of my GHI Electronics TinyCLR-0SV2RC1 RFM9X/SX127X library is live on GitHub. The sample application now supports Fezduino(dragino_LoRa shield for Arduino + others), Fezportal and the SC2010 Dev board (with CascoLogix LoRa Click) . I will add FezFeather and Fezstick support soon.

```//---------------------------------------------------------------------------------
// 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
//
//
// 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.
//
// Need one of TINYCLR_V2_SC20100DEV_MIKROBUS_1/TINYCLR_V2_SC20100DEV_MIKROBUS_2/TINYCLR_V2_FEZDUINO/TINYCLR_V2_FEZPORTAL defined
//---------------------------------------------------------------------------------
{
using System;
using System.Diagnostics;
using System.Text;

using GHIElectronics.TinyCLR.Pins;

using devMobile.IoT.Rfm9x;

class Program
{
static void Main()
{
#if TINYCLR_V2_SC20100DEV_MIKROBUS_1 || TINYCLR_V2_SC20100DEV_MIKROBUS_2
const string DeviceName = "SC20100DEVLoRa";
#endif
#if TINYCLR_V2_FEZDUINO
const string DeviceName = "FezduinoLoRa";
#endif
#if TINYCLR_V2_FEZPORTAL
const string DeviceName = "FezportalLoRa";
#endif
const string HostName = "LoRaIoT1";
#endif
const double Frequency = 915000000.0;
byte MessageCount = System.Byte.MaxValue;
#if TINYCLR_V2_SC20100DEV_MIKROBUS_1
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PD3, SC20100.GpioPin.PD4, SC20100.GpioPin.PC5);
#endif
#if TINYCLR_V2_SC20100DEV_MIKROBUS_2
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PD14, SC20100.GpioPin.PD15, SC20100.GpioPin.PA8);
#endif
#if TINYCLR_V2_FEZDUINO
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi6, SC20100.GpioPin.PB1, SC20100.GpioPin.PA15, SC20100.GpioPin.PA1);
#endif
#if TINYCLR_V2_FEZPORTAL
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PC13, SC20100.GpioPin.PD4,SC20100.GpioPin.PC2);
#endif

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

#else
#endif
rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

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}");
rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes(HostName), messageBytes);
#else
rfm9XDevice.Send(messageBytes);
#endif
}
}

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

#else
#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 library works but should be treated as late beta.

# TinyCLR OS V2 RC1 LoRa library Part4

## Interrupts Revisited

In my last post I had two approaches for fixing the issue with transmit interrupts. As I was sorting out the configuration for my Fezportal device and SS20100 Dev board(both sockets) with a Cascologix MikroBus LoRa click I had similar issues with both receive and transmit.

I noticed the extra RegIrqFlags 0X58, Receive-Message in the debugging output.

```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
RegIrqFlags 0X58
Received 23 byte message  �LoRaIoT1N3WT 20.5,H 86
Transmit-Done
Sending 13 bytes message Hello LoRa 3!
RegIrqFlags 0X08
Transmit-Done
```

This was with the modified device write methods so I tried changing the Serial Peripheral Interface(SPI) configuration.

```public RegisterManager(string spiPortName,  int chipSelectPin, int clockFrequency = 500000)
{
GpioPin chipSelectGpio = GpioController.GetDefault().OpenPin(chipSelectPin);

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

SpiController spiController = SpiController.FromName(spiPortName);

rfm9XLoraModem = spiController.GetDevice(settings);
}
```

When I added the ChipSelectHoldTime (even the smallest possible one) the code worked as expected.

```The thread '' (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
RegIrqFlags 0X50
Received 23 byte message �LoRaIoT1N3WT 20.5,H 87
Sending 13 bytes message Hello LoRa 3!
RegIrqFlags 0X08
Transmit-Done
Sending 13 bytes message Hello LoRa 4!
RegIrqFlags 0X08
Transmit-Done```

At this point I have run out of ideas so I will release the code this fix.

# RAK811LoRaWAN.TinyCLR on Github

The source code of my TinyCLR V2 RAK811 Module library is now available on GitHub. My demonstration application uses a FezDuino and a modified RAK811 LPWAN Evaluation Board(EVB).

A sample application which shows how to connect using Over the Air Activation(OTAA) or Activation By Personalisation(ABP) then send and receive byte array/Binary Coded Decimal(BCD) messages .

```//---------------------------------------------------------------------------------
// Copyright (c) July 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
//
//
// 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.
//
// must have one of
//    TINYCLR_V2_FEZDUINO or
//    OTAA or ABP
//
// For confirmed messages define CONFIRMED
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rak811LoRaWanDeviceClient
{
using System;
using System.Diagnostics;

using GHIElectronics.TinyCLR.Pins;
using GHIElectronics.TinyCLR.Devices.Uart;

using devMobile.IoT.LoRaWan;

public class Program
{
#if TINYCLR_V2_FEZDUINO
private const string SerialPortId = SC20100.UartPort.Uart5;
#endif
#if OTAA
private const string DevEui = "...";
private const string AppEui = "...";
private const string AppKey = "...";
#endif
#if ABP
private const string DevAddress = "...";
private const string NwksKey = "...";
private const string AppsKey = "...";
#endif
private const string Region = "AS923";
private static readonly TimeSpan JoinTimeOut = new TimeSpan(0, 0, 10);
private static readonly TimeSpan SendTimeout = new TimeSpan(0, 0, 10);
private const byte MessagePort = 1;
private const string PayloadBcd = "48656c6c6f204c6f526157414e"; // Hello LoRaWAN in BCD
#endif
private static readonly byte[] PayloadBytes = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x4c, 0x6f, 0x52, 0x61, 0x57, 0x41, 0x4e}; // Hello LoRaWAN in bytes
#endif

public static void Main()
{
Result result;

Debug.WriteLine("devMobile.IoT.Rak811LoRaWanDeviceClient starting");

try
{
using (Rak811LoRaWanDevice device = new Rak811LoRaWanDevice())
{
result = device.Initialise(SerialPortId, 9600, UartParity.None, 8, UartStopBitCount.One);
if (result != Result.Success)
{
Debug.WriteLine(\$"Initialise failed {result}");
return;
}

#if CONFIRMED
device.OnMessageConfirmation += OnMessageConfirmationHandler;
#endif

Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} Region {Region}");
result = device.Region(Region);
if (result != Result.Success)
{
Debug.WriteLine(\$"Region failed {result}");
return;
}

if (result != Result.Success)
{
Debug.WriteLine(\$"ADR on failed {result}");
return;
}

#if CONFIRMED
Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} Confirmed");
result = device.Confirm(LoRaConfirmType.Confirmed);
if (result != Result.Success)
{
Debug.WriteLine(\$"Confirm on failed {result}");
return;
}
#else
Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} Unconfirmed");
result = device.Confirm(LoRaConfirmType.Unconfirmed);
if (result != Result.Success)
{
Debug.WriteLine(\$"Confirm off failed {result}");
return;
}
#endif

#if OTAA
Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} OTAA");
result = device.OtaaInitialise(DevEui, AppEui, AppKey);
if (result != Result.Success)
{
Debug.WriteLine(\$"OTAA Initialise failed {result}");
return;
}
#endif

#if ABP
Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} ABP");
result = device.AbpInitialise(DevAddress, NwksKey, AppsKey);
if (result != Result.Success)
{
Debug.WriteLine(\$"ABP Initialise failed {result}");
return;
}
#endif

//Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} Join start Timeout:{JoinTimeOut:hh:mm:ss}");
result = device.Join(JoinTimeOut);
if (result != Result.Success)
{
Debug.WriteLine(\$"Join failed {result}");
return;
}
Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} Join finish");

while (true)
{
result = device.Send(MessagePort, PayloadBcd, SendTimeout);
#endif
result = device.Send(MessagePort, PayloadBytes, SendTimeout);
#endif
if (result != Result.Success)
{
Debug.WriteLine(\$"Send failed {result}");
}

// if we sleep module too soon response is missed

Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} Sleep");
result = device.Sleep();
if (result != Result.Success)
{
Debug.WriteLine(\$"Sleep failed {result}");
return;
}

Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} Wakeup");
result = device.Wakeup();
if (result != Result.Success)
{
Debug.WriteLine(\$"Wakeup failed {result}");
return;
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}

static void OnMessageConfirmationHandler(int rssi, int snr)
{
}

static void OnReceiveMessageHandler(int port, int rssi, int snr, string payloadBcd)
{

}
}
}
```

I noticed (mid July 2020) that when changing from from ABP to OTAA and vice versa I needed to reset the the device “frame counters” in the Things Network console.

# TinyCLR OS V2 RC1 RAK811 LoRaWAN library Part2

## Nasty OTAA connect

After getting basic connectivity for my RAK811 LPWAN Evaluation Board(EVB) and Fezduino test rig working. I wanted to see if I could get the device connected to The Things Network(TTN) via the RAK7246G LPWAN Developer Gateway on my desk. I had got the EVB configuration sorted with a nanoFramework device so I was confident it should work.

My Over the Air Activation (OTAA) implementation is very “nasty” I assumed that there would be no timeouts or failures and I only send one BCD message “48656c6c6f204c6f526157414e” which is “hello LoRaWAN”

I configured the RAK811 module for LoRaWAN

```// Set the Working mode to LoRaWAN
txByteCount = serialDevice.Write(UTF8Encoding.UTF8.GetBytes("at+set_config=lora:work_mode:0\r\n"));
Debug.WriteLine(\$"TX: work mode {txByteCount} bytes");

// Read the response
if (rxByteCount > 0)
{
byte[] rxBuffer = new byte[rxByteCount];
Debug.WriteLine(\$"RX :{UTF8Encoding.UTF8.GetString(rxBuffer)}");
}
...
```

Then just sequentially step through the necessary configuration to join the TTN network with a suitable delay after each command is sent.

```// Set the Region to AS923
txByteCount = serialDevice.Write(UTF8Encoding.UTF8.GetBytes("at+set_config=lora:region:AS923\r\n"));
Debug.WriteLine(\$"TX: region {txByteCount} bytes");

// Read the response
if (rxByteCount > 0)
{
byte[] rxBuffer = new byte[rxByteCount];
Debug.WriteLine(\$"RX :{UTF8Encoding.UTF8.GetString(rxBuffer)}");
}

// Set the JoinMode
txByteCount = serialDevice.Write(UTF8Encoding.UTF8.GetBytes("at+set_config=lora:join_mode:0\r\n"));
Debug.WriteLine(\$"TX: join_mode {txByteCount} bytes");

// Read the response
if (rxByteCount > 0)
{
byte[] rxBuffer = new byte[rxByteCount];
Debug.WriteLine(\$"RX :{UTF8Encoding.UTF8.GetString(rxBuffer)}");
}

// OTAA set the devEUI
txByteCount = serialDevice.Write(UTF8Encoding.UTF8.GetBytes(\$"at+set_config=lora:dev_eui:{DevEui}\r\n"));
Debug.WriteLine(\$"TX: dev_eui: {txByteCount} bytes");

// Read the response
if (rxByteCount > 0)
{
byte[] rxBuffer = new byte[rxByteCount];
Debug.WriteLine(\$"RX :{UTF8Encoding.UTF8.GetString(rxBuffer)}");
}
...
```

The code is not suitable for production but it confirmed my software and hardware configuration worked.

```The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.NetworkJoinOTAA starting
TX: work mode 32 bytes
RX :UART1 work mode: RUI_UART_NORAMAL
Current work_mode:LoRaWAN, join_mode:OTAA, Class: A
Initialization OK

TX: region 33 bytes
RX :OK

TX: join_mode 32 bytes
RX :OK

TX: dev_eui: 45 bytes
RX :OK

TX: app_eui 45 bytes
RX :OK

TX: app_key 61 bytes
RX :OK

TX: confirm 30 bytes
RX :OK

TX: join 9 bytes
RX :OK Join Success

TX: send 43 bytes
RX :OK

TX: send 43 bytes
RX :OK
```

In the Visual Studio 2019 debug output I could see messages getting sent and then after a short delay they were visible in the TTN console.

I had some issues with TimeSpan.ToString(…) throwing a CLR_E_UNSUPPORTED_INSTRUCTION exception which has been mentioned on the GHI Forums.

I had to modify my code to fix this issue

```Debug.WriteLine(\$"{DateTime.UtcNow:hh:mm:ss} Join start Timeout:{JoinTimeOut:hh:mm:ss}");

// Became

Debug.WriteLine(\$" {DateTime.UtcNow:hh:mm:ss} Join Start Timeout {timeout.TotalSeconds} seconds");
```

I won’t bother with confirming any other functionality as I’m reasonably confident the nanoFramework library (which this code is based on) is working as expected.

# TinyCLR OS V2 RC1 RAK811 LoRaWAN library Part1

## Basic connectivity

Over the weekend I have been working on a GHI Electronics TinyCLR V2  C# library for my modified RAK811 LPWAN Evaluation Board(EVB) from RAK Wireless. My initial test rig is based on an Fezduino board which has Arduino Uno R3 format socket for the EVB.

The code has compile time options for synchronous and asynchronous operation.

```   public class Program
{
private static UartController serialDevice;
private const string ATCommand = "at+version\r\n";
#if TINYCLR_V2_FEZDUINO
private static string SerialPortId = SC20100.UartPort.Uart5;
#endif

public static void Main()
{
Debug.WriteLine("devMobile.IoT.Rak811.ShieldSerial starting");

try
{
serialDevice = UartController.FromName(SerialPortId);

serialDevice.SetActiveSettings(new UartSetting()
{
BaudRate = 9600,
Parity = UartParity.None,
StopBits = UartStopBitCount.One,
Handshaking = UartHandshake.None,
DataBits = 8
});

serialDevice.Enable();

#endif

while (true)
{
byte[] txBuffer = UTF8Encoding.UTF8.GetBytes(ATCommand);

int txByteCount = serialDevice.Write(txBuffer);
Debug.WriteLine(\$"TX: {txByteCount} bytes");

while( serialDevice.BytesToWrite>0)
{
Debug.WriteLine(\$" BytesToWrite {serialDevice.BytesToWrite}");
}

int rxByteCount = serialDevice.BytesToRead;
if (rxByteCount>0)
{
byte[] rxBuffer = new byte[rxByteCount];

Debug.WriteLine(\$"RX sync:{rxByteCount} bytes read");
String response = UTF8Encoding.UTF8.GetString(rxBuffer);
Debug.WriteLine(\$"RX sync:{response}");
}
#endif

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

private static void SerialDevice_DataReceived(UartController sender, DataReceivedEventArgs e)
{
byte[] rxBuffer = new byte[e.Count];

Debug.WriteLine(\$"RX Async:{e.Count} bytes read");
String response = UTF8Encoding.UTF8.GetString(rxBuffer);
Debug.WriteLine(\$"RX Async:{response}");
}
#endif
}

```

When I first ran the code I noticed the serialDevice.Read timed out before any characters were received.

```The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.ShieldSerial starting
TX: 12 bytes
TX: 12 bytes
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

TX: 12 bytes
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

TX: 12 bytes
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3
```

I then added code to check the message had been sent and the code worked as expected. I now think, that rather than checking that the characters had been sent the short 100mSec delay was more important.

```The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.ShieldSerial starting
TX: 12 bytes
BytesToWrite 10
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

TX: 12 bytes
BytesToWrite 10
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3

TX: 12 bytes
BytesToWrite 10
RX sync:19 bytes read
RX sync:OK V3.0.0.13.H.T3
```

I then added code to receive data asynchronously and the response to the version request was received as expected.

```The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.IoT.Rak811.ShieldSerial starting
TX: 12 bytes
RX Async:1 bytes read
RX Async:O
RX Async:8 bytes read
RX Async:K V3.0.0
RX Async:10 bytes read
RX Async:.13.H.T3

TX: 12 bytes
RX Async:1 bytes read
RX Async:O
RX Async:5 bytes read
RX Async:K V3.
RX Async:9 bytes read
RX Async:0.0.13.H.
RX Async:4 bytes read
RX Async:T3
```

# RFM9X.TinyCLR V2 RC1 on Github

The source code of my GHI Electronics TinyCLR-0SV2RC1 RFM9X/SX127X library is live on GitHub. The test harness uses a Fezduino and a dragino technology LoRa shield for Arduino. I will add FezPortal, FezFeather and Fezstick support soon.

```//---------------------------------------------------------------------------------
// 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
//
//
// 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.
//
// Need one of TINYCLR_V2_SC20100DEV/TINYCLR_V2_FEZDUINO defined
//---------------------------------------------------------------------------------
{
using System;
using System.Diagnostics;
using System.Text;

using GHIElectronics.TinyCLR.Pins;

using devMobile.IoT.Rfm9x;

class Program
{
static void Main()
{
#if TINYCLR_V2_SC20100DEV
const string DeviceName = "SC20100DEVLoRa";
#endif
#if TINYCLR_V2_FEZDUINO
const string DeviceName = "FezduinoLoRa";
#endif
const string HostName = "LoRaIoT1";
#endif
const double Frequency = 915000000.0;
byte MessageCount = System.Byte.MaxValue;
#if TINYCLR_V2_SC20100DEV
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14, SC20100.GpioPin.PE4);
#endif
#if TINYCLR_V2_FEZDUINO
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi6, SC20100.GpioPin.PB1, SC20100.GpioPin.PA15, SC20100.GpioPin.PA1);
#endif

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

#else
#endif
rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

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}");
rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes(HostName), messageBytes);
#else
rfm9XDevice.Send(messageBytes);
#endif
}
}

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

#else
#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 RC1 LoRa library Part3

## Why are Transmit Interrupts broken?

I had read about restrictions for interrupts pins e.g. PA1 & PB1 can’t be used at the same time, but PA1 and PB2 can, which got me thinking…

In my dragino shield based setup Arduino D2(PA1) is the interrupt(IRQ) line and Arduino D10(PB1) is chip select (CS) so I changed the pins with jumper wires just incase.

I tried several different configurations of CS and IRQ pins and none worked.

On other embedded .net platforms I have written Serial Peripheral Interface(SPI) based drivers for (e.g. Wilderness Labs Meadow and the nanoFramework) there had been issues with the use of device.Write vs. device. TransferFullDuplex (or its equivalent).

The RegisterWriteByte method in the event handler appeared to be the problem so I tried modifying it.

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

rfm9XLoraModem.Write(writeBuffer);
}
```

Became

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

}

```

In the diagnostic output I could see confirmations arriving

```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
Sending 13 bytes message Hello LoRa 6!
RegIrqFlags 0X08
Transmit-Done
```

This was clue it might be a timing problem, so I took a closer look at how the SPI port was configured. After some experimentation I found that by adding a small ChipSelectHoldTime the .Write statements worked.

When I went back and checked there had also been some timing “tweaks” required to get my .Net Microframework LoRa library to work reliably.

```public Rfm9XDevice(string spiPortName, int chipSelectPin, int resetPin, int interruptPin)
{
GpioController gpioController = GpioController.GetDefault();

GpioPin chipSelectGpio = gpioController.OpenPin(chipSelectPin);

var settings = new SpiConnectionSettings()
{
ChipSelectType = SpiChipSelectType.Gpio,
ChipSelectLine = chipSelectGpio,
Mode = SpiMode.Mode0,
ClockFrequency = 500000,
ChipSelectActiveState = false,
//ChipSelectHoldTime = new TimeSpan(50),
//ChipSelectHoldTime = new TimeSpan(25),
//ChipSelectHoldTime = new TimeSpan(10),
ChipSelectHoldTime = new TimeSpan(5),
//ChipSelectHoldTime = new TimeSpan(1),
};

SpiController spiController = SpiController.FromName(spiPortName);

rfm9XLoraModem = spiController.GetDevice(settings);

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

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

InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;
}
```

In the diagnostic output I could see confirmations arriving

```...
Sending 16 bytes message Hello LoRa 1115!
RegIrqFlags 0X08
Transmit-Done
Sending 16 bytes message Hello LoRa 1116!
RegIrqFlags 0X08
Transmit-Done
Sending 16 bytes message Hello LoRa 1117!
RegIrqFlags 0X08
Transmit-Done
Sending 16 bytes message Hello LoRa 1118!
RegIrqFlags 0X08
Transmit-Done
Sending 16 bytes message Hello LoRa 1119!
RegIrqFlags 0X08
Transmit-Done
Sending 16 bytes message Hello LoRa 1120!
RegIrqFlags 0X08
Transmit-Done
Sending 16 bytes message Hello LoRa 1121!
RegIrqFlags 0X08
Transmit-Done
Sending 16 bytes message Hello LoRa 1122!
RegIrqFlags 0X08
The program '[13] TinyCLR application: Managed' has exited with code 0 (0x0).
```

After some soak testing it looks like the ChipSelectHoldTime modification works pretty reliably but I will need watch for issues.

EDIT: After a long walk I have updated the code to use TransferFullDuplex rather than add a ChipSelectHoldTime.

# TinyCLR OS V2 RC1 LoRa library Part2

## Receive and Transmit

The first step was to confirm the transmission of messages with polled completion confirmation was working as expected.

```   class Program
{
static void Main()
{
#if TINYCLR_V2_SC20100DEV
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14);
#endif
#if TINYCLR_V2_FEZDUINO
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi6, SC20100.GpioPin.PB1, SC20100.GpioPin.PA15);
#endif
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
{
IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
Debug.WriteLine(".");
}
rfm9XDevice.RegisterWriteByte(0x12, 0b00001000); // clear TxDone bit
Debug.WriteLine("Send-Done");

}
}
}
```

The diagnostic output shows messages being sent and on another device I could see the messages arriving. I do wonder why the first message often takes so long to send?

```Register dump
Register 0x00 - Value 0Xc3
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 0X80
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 0X00
Register 0x21 - Value 0X08
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
Sending 13 bytes message Hello LoRa 1!
Send-wait
.
.
.
.
.
Send-Done
Sending 13 bytes message Hello LoRa 2!
Send-wait
Send-Done
Sending 13 bytes message Hello LoRa 3!
Send-wait
Send-Done
Sending 13 bytes message Hello LoRa 4!
Send-wait
Send-Done
Sending 13 bytes message Hello LoRa 5!
Send-wait
Send-Done
Sending 13 bytes message Hello LoRa 6!
Send-wait
Send-Done
```

The second step was to confirm the polled reception of messages was working as expected.

```   class Program
{
static void Main()
{
#if TINYCLR_V2_SC20100DEV
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14);
#endif
#if TINYCLR_V2_FEZDUINO
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi6, SC20100.GpioPin.PB1, SC20100.GpioPin.PA15);
#endif

// 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
byte irqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
while ((irqFlags & 0b01000000) == 0)  // wait until RxDone cleared
{
irqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
//Debug.Write(".");
}
Debug.WriteLine("");
Debug.WriteLine(\$"RegIrqFlags 0X{irqFlags:X2}");

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}");

}
}
}

```

The diagnostic output shows messages being received from one of my other devices.

The thread ” (0x2) has exited with code 0 (0x0).

RegIrqFlags 0X50
Received 23 byte message �LoRaIoT1N3WT 18.8,H 78

RegIrqFlags 0X50
Received 23 byte message �LoRaIoT1N3WT 18.8,H 78

The next step was to confirm the interrupt driven reception of messages was working as expected.

```   class Program
{
static void Main()
{
#if TINYCLR_V2_SC20100DEV
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14, SC20100.GpioPin.PE4);
#endif
#if TINYCLR_V2_FEZDUINO
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi6, SC20100.GpioPin.PB1, SC20100.GpioPin.PA15, SC20100.GpioPin.PA1);
#endif

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

}
}

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
{

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
}

```

The diagnostic output shows messages being received from one of my other devices.

```The thread '<No Name>' (0x2) has exited with code 0 (0x0).
RegIrqFlags 0X50
Received 23 byte message  �LoRaIoT1N3WT 18.8,H 78
RegIrqFlags 0X50
Received 23 byte message  �LoRaIoT1N3WT 18.7,H 79

```

The final step was to confirm the interrupt driven transmission of messages was working as expected.

```   class Program
{
static void Main()
{
#if TINYCLR_V2_SC20100DEV
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi3, SC20100.GpioPin.PA13, SC20100.GpioPin.PA14, SC20100.GpioPin.PE4);
#endif
#if TINYCLR_V2_FEZDUINO
Rfm9XDevice rfm9XDevice = new Rfm9XDevice(SC20100.SpiBus.Spi6, SC20100.GpioPin.PB1, SC20100.GpioPin.PA15, SC20100.GpioPin.PA1); // Doesn't work
#endif
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

}
}
}

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
}

```

The diagnostic output shows messages being sent but after the first message (sometimes the second or third) there are no confirmations.

The thread ” (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!
Sending 13 bytes message Hello LoRa 3!
Sending 13 bytes message Hello LoRa 4!
Sending 13 bytes message Hello LoRa 5!
Sending 13 bytes message Hello LoRa 6!
Sending 13 bytes message Hello LoRa 7!
Sending 13 bytes message Hello LoRa 8!
Sending 13 bytes message Hello LoRa 9!
Sending 14 bytes message Hello LoRa 10!
Sending 14 bytes message Hello LoRa 11!
Sending 14 bytes message Hello LoRa 12!
Sending 14 bytes message Hello LoRa 13!
Sending 14 bytes message Hello LoRa 14!

It looks like something has been broken (possibly by RC1) in my implementation of interrupt driven transmission of messages.