.NET nanoFramework SX127X LoRa library Refactoring

I had been planning this for a while, then the code broke when I tried to build a version for my SparkFun LoRa Gateway-1-Channel (ESP32). There was a namespace (static configuration class in configuration.cs) collision and the length of SX127XDevice.cs file was getting silly.

This refactor took a couple of days and really changed the structure of the library.

VS2022 Solution structure after refactoring

I went through the SX127XDevice.cs extracting the enumerations, masks and defaults associated with the registers the library supports.


Fork Refactoring Check-ins

The RegOpMode.cs file is a good example…

namespace devMobile.IoT.SX127xLoRaDevice
{
	using System;

	// RegOpMode bit flags from Semtech SX127X Datasheet
	[Flags]
	internal enum RegOpModeModeFlags : byte
	{
		LongRangeModeLoRa = 0b10000000,
		LongRangeModeFskOok = 0b00000000,
		LongRangeModeDefault = LongRangeModeFskOok,
		AcessSharedRegLoRa = 0b00000000,
		AcessSharedRegFsk = 0b01000000,
		AcessSharedRegDefault = AcessSharedRegLoRa,
		LowFrequencyModeOnHighFrequency = 0b00000000,
		LowFrequencyModeOnLowFrequency = 0b00001000,
		LowFrequencyModeOnDefault = LowFrequencyModeOnLowFrequency
	}

	internal enum RegOpModeMode : byte
	{
		Sleep = 0b00000000,
		StandBy = 0b00000001,
		FrequencySynthesisTX = 0b00000010,
		Transmit = 0b00000011,
		FrequencySynthesisRX = 0b00000100,
		ReceiveContinuous = 0b00000101,
		ReceiveSingle = 0b00000110,
		ChannelActivityDetection = 0b00000111,
	};
}

The library is designed to be a approximate .NET nanoFramework equivalent of Arduino-LoRa so it doesn’t support/implement all of the functionality of the Semtech SX127X. Still got a bit of refactoring to go but the structure is slowly improving.

I use Fork to manage my Github repositories, it’s an excellent product especially as it does a pretty good job of keeping me from screwing up.

.NET nanoFramework SX127X LoRa library RegLna LnaGain

Every so often I print my code out (landscape for notes in margin, double sided to save paper, and colour so it looks like Visual Studio 2022) and within 100 lines noticed the first of no doubt many issues. The SX127X RegLNA enumeration was wrong.

// RegLna
[Flags]
public enum RegLnaLnaGain : byte
{
	G1 = 0b00000001,
	G2 = 0b00000010,
	G3 = 0b00000011,
	G4 = 0b00000100,
	G5 = 0b00000101,
	G6 = 0b00000110
}
SX127X RegLna options

The LnaGain value is bits 5-7 rather than rather than bits 0-2 which could be a problem if the specified lnaGain and lnaBoost values are not the default values.

// Set RegLna if any of the settings not defaults
if ((lnaGain != Configuration.LnaGainDefault) || (lnaBoost != Configuration.LnaBoostDefault))
{
	byte regLnaValue = (byte)lnaGain;

	regLnaValue |= Configuration.RegLnaLnaBoostLfDefault;
	regLnaValue |= Configuration.RegLnaLnaBoostHfDefault;

	if (lnaBoost)
	{
		if (_frequency > Configuration.SX127XMidBandThreshold)
		{
			regLnaValue |= Configuration.RegLnaLnaBoostHfOn;
		}
		else
		{
			regLnaValue |= Configuration.RegLnaLnaBoostLfOn;
		}
	}
	_registerManager.WriteByte((byte)Configuration.Registers.RegLna, regLnaValue);
}

The default lnaGain is G1 and the default lnaBoost is false so if the gain was set to G3(011) then LnaBoostHf current would be 150% and LnaGain would be 000 which is a reserved value.

// RegLna
[Flags]
public enum RegLnaLnaGain : byte
{
	G1 = 0b00100000,
	G2 = 0b01000000,
	G3 = 0b01100000,
	G4 = 0b10000000,
	G5 = 0b10100000,
	G6 = 0b11000000
}

I need to check my usage of Configuration.SX127XMidBandThreshold for LnaBoostLf vs. LnaBoostHf is correct.(arduino-LoRa)

Rfm9xLoRaDevice NetMF LNA

While fixing up the Signal to Noise Ratio(SNR) and Received Signal Strength Indication(RSSI) in a previous posts. I noted the Low Noise Amplifier(LNA) had High Frequency(LF) and Low Frequency settings.

First step was to update the initialise method parameter list (the parameter list is huge but for most scenarios the defaults are fine)

 public void Initialise(RegOpModeMode regOpModeAfterInitialise, // RegOpMode
       double frequency = FrequencyDefault, // RegFrMsb, RegFrMid, RegFrLsb
         bool paBoost = false, byte maxPower = RegPAConfigMaxPowerDefault, byte outputPower = RegPAConfigOutputPowerDefault, // RegPaConfig
         bool ocpOn = true, byte ocpTrim = RegOcpOcpTrimDefault, // RegOcp
         RegLnaLnaGain lnaGain = LnaGainDefault, bool lnaBoost = false, // RegLna
         RegModemConfigBandwidth bandwidth = RegModemConfigBandwidthDefault, RegModemConfigCodingRate codingRate = RegModemConfigCodingRateDefault, RegModemConfigImplicitHeaderModeOn implicitHeaderModeOn = RegModemConfigImplicitHeaderModeOnDefault, //RegModemConfig1
         RegModemConfig2SpreadingFactor spreadingFactor = RegModemConfig2SpreadingFactorDefault, bool txContinuousMode = false, bool rxPayloadCrcOn = false,
         ushort symbolTimeout = SymbolTimeoutDefault,
         ushort preambleLength = PreambleLengthDefault,
         byte payloadLength = PayloadLengthDefault,
         byte payloadMaxLength = PayloadMaxLengthDefault,
         byte freqHoppingPeriod = FreqHoppingPeriodDefault,
         bool lowDataRateOptimize = false, bool agcAutoOn = false,
         byte ppmCorrection = ppmCorrectionDefault,
         RegDetectOptimizeDectionOptimize detectionOptimize = RegDetectOptimizeDectionOptimizeDefault,
         bool invertIQ = false,
         RegisterDetectionThreshold detectionThreshold = RegisterDetectionThresholdDefault,
         byte syncWord = RegSyncWordDefault)
      {

The SX127X RegLNA configuration code and the SetMode method required modification

if ((lnaGain != LnaGainDefault) || (lnaBoost != false))
         {
            byte regLnaValue = (byte)lnaGain;
            if (lnaBoost)
            {
               if (Frequency > RFMidBandThreshold)
               {
                  regLnaValue |= RegLnaLnaBoostHfOn;
               }
               else
               {
                  regLnaValue |= RegLnaLnaBoostLfOn;
               }
            }
            Rfm9XLoraModem.WriteByte((byte)Registers.RegLna, regLnaValue);
         }
public void SetMode(RegOpModeMode mode)
      {
         byte regOpModeValue;

         regOpModeValue = RegOpModeLongRangeModeLoRa;
         regOpModeValue |= RegOpModeAcessSharedRegLoRa;
         if (Frequency > RFMidBandThreshold)
         {
            regOpModeValue |= RegOpModeLowFrequencyModeOnHighFrequency;
         }
         else
         {
            regOpModeValue |= RegOpModeLowFrequencyModeOnLowFrequency;
         }
         regOpModeValue |= (byte)mode;
         Rfm9XLoraModem.WriteByte((byte)Registers.RegOpMode, regOpModeValue);
      }

Having to convert all the Flags & masks from binary to hexadecimal values was a bit painful

// RegDioMapping1
[Flags]
public enum RegDioMapping1
{
Dio0RxDone = 0x00,
Dio0TxDone = 0x40,
Dio0CadDone = 0x80,
}

The HF & LF differences where not obviously handled in Arduino-LoRa library and the Semtech LoRaMac node GitHub repository wasn’t so helpful.

When I stress tested this code the UTF8Encoding.UTF8.GetChars kept on throwing exceptions as the messages were corrupt. Need to add CRC presence and validity checking to next version.

 static void rfm9XDevice_OnDataReceived(float packetSnr, int packetRssi, int rssi,  byte[] data)
{
   try
   {
      string messageText = new string(UTF8Encoding.UTF8.GetChars(data));

      Debug.Print(DateTime.UtcNow.ToString("HH:MM:ss") + "-Rfm9X PacketSnr " + packetSnr.ToString("F1") + " Packet RSSI " + packetRssi + "dBm RSSI " + rssi + "dBm = " + data.Length + " byte message " + @"""" + messageText + @"""") ;
   }
   catch (Exception ex)
   {
      Debug.Print(ex.Message);
   }
}