RFM9X.NetMF Payload Addressing

I have extended the NetMF sample application and library to show how the conditional compilation directive ADDRESSED_MESSAGES_PAYLOAD controls the configuration.

When the application is started the RFM9X is in sleep mode, then when the Receive method is called the device is set to ReceiveContinuous.

//---------------------------------------------------------------------------------
// Copyright (c) August 2018, 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.NetMF.Rfm9X.Client
{
   using System;
   using System.Text;
   using System.Threading;
   using devMobile.IoT.NetMF.ISM;
   using Microsoft.SPOT;
   using SecretLabs.NETMF.Hardware.Netduino;

   public class Program
   {
      public static void Main()
      {
         Rfm9XDevice rfm9XDevice = new Rfm9XDevice(Pins.GPIO_PIN_D10, Pins.GPIO_PIN_D9, Pins.GPIO_PIN_D2);
         byte MessageCount = Byte.MinValue;

         rfm9XDevice.Initialise( frequency:915000000, paBoost: true, rxPayloadCrcOn: true);
#if ADDRESSED_MESSAGES
         rfm9XDevice.Receive(Encoding.UTF8.GetBytes("Netduino"));
#else
         rfm9XDevice.Receive();
#endif
         rfm9XDevice.OnDataReceived += rfm9XDevice_OnDataReceived;
         rfm9XDevice.OnTransmit += rfm9XDevice_OnTransmit;

         while (true)
         {
            string messageText = "Hello NetMF LoRa! " + MessageCount.ToString();
            MessageCount += 1;
            byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
            Debug.Print("Sending " + messageBytes.Length + " bytes message " + messageText);

#if ADDRESSED_MESSAGES
            rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes("LoRaIoT1"), messageBytes);
#else
            rfm9XDevice.Send(messageBytes);
#endif
            Thread.Sleep(10000);
         }
      }

      static void rfm9XDevice_OnTransmit()
      {
         Debug.Print("Transmit-Done");
      }

#if ADDRESSED_MESSAGES
      static void rfm9XDevice_OnDataReceived(byte[] address, float packetSnr, int packetRssi, int rssi, byte[] data)
#else
      static void rfm9XDevice_OnDataReceived(float packetSnr, int packetRssi, int rssi,  byte[] data)
#endif
      {
         try
         {
            string messageText = new string(UTF8Encoding.UTF8.GetChars(data));
#if ADDRESSED_MESSAGES
            string addressText = new string(UTF8Encoding.UTF8.GetChars(address));

            Debug.Print(DateTime.UtcNow.ToString("HH:MM:ss") + "-From " + addressText + " PacketSnr " + packetSnr.ToString("F1") + " Packet RSSI " + packetRssi + "dBm RSSI " + rssi + "dBm = " + data.Length + " byte message " + @"""" + messageText + @"""") ;
#else
            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 + @"""") ;
#endif
         }
         catch (Exception ex)
         {
            Debug.Print(ex.Message);
         }
      }
   }
}

namespace System.Diagnostics
{
   public enum DebuggerBrowsableState
   {
      Never = 0,
      Collapsed = 2,
      RootHidden = 3
   }
}

The Netduino client “plays nicely” with my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

The Semech SX127X datasheet describes how addressing can be implemented using interrupts which I will have a look at soon.

Library needs further testing and I’m working on a sample Arduino application.

Re-reading the SX1276 datasheet

I sat down and read the Semtech SX1276 datasheet paying close attention to any references to CRCs and headers. Then to test some ideas I modified my Receive Basic test harness to see if I could reliably reproduce the problem with my stress test harness.LoRaStress2

public sealed class StartupTask : IBackgroundTask
	{
		private const int ChipSelectLine = 25;
		private const int ResetLine = 17;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectLine, ResetLine);

		public void Run(IBackgroundTaskInstance taskInstance)
		{
			// 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
				{
					Task.Delay(20).Wait();
					IrqFlags = rfm9XDevice.RegisterReadByte(0x12); // RegIrqFlags
					Debug.Write(".");
				}
				Debug.WriteLine("");

				if ((IrqFlags & 0b00100000) == 0b00100000)
				{
					Debug.WriteLine("Payload CRC error");
				}

				byte regHopChannel = rfm9XDevice.RegisterReadByte(0x1C);
				Debug.WriteLine(string.Format("regHopChannel {0}", Convert.ToString((byte)regHopChannel, 2).PadLeft(8, '0')));

				byte currentFifoAddress = rfm9XDevice.RegisterReadByte(0x10); // RegFifiRxCurrent
				rfm9XDevice.RegisterWriteByte(0x0d, currentFifoAddress); // RegFifoAddrPtr*
				byte numberOfBytes = rfm9XDevice.RegisterReadByte(0x13); // RegRxNbBytes

				// Allocate buffer for message
				byte[] messageBytes = new byte[numberOfBytes];

				for (int i = 0; i < numberOfBytes; i++)
				{
					messageBytes[i] = rfm9XDevice.RegisterReadByte(0x00); // RegFifo
				}
				rfm9XDevice.RegisterWriteByte(0x12, 0xff); // RegIrqFlags clear all the bits

				string messageText = UTF8Encoding.UTF8.GetString(messageBytes);
				Debug.WriteLine("Received {0} byte message {1}", messageBytes.Length, messageText);
				Debug.WriteLine(string.Format("RegIrqFlags {0}", Convert.ToString((byte)IrqFlags, 2).PadLeft(8, '0')));
				Debug.WriteLine("Receive-Done");
			}
		}
	}

The RegHopChannel register has a flag indicating whether there was a CRC extracted from the packet header.

regHopChannel 00000000
Received 23 byte message 1 Hello Arduino LoRa! 1
RegIrqFlags 01010000
Receive-Done
Receive-Wait
…………………………..
regHopChannel 00000000
Received 23 byte message 1 Hello Arduino LoRa! 2
RegIrqFlags 01010000
Receive-Done
Receive-Wait
……………………………
regHopChannel 00000000
Received 23 byte message 1 Hello Arduino LoRa! 3
RegIrqFlags 01010000
Receive-Done
Receive-Wait

I then modified my Arduino-LoRa library based client to include a CRC

void setup() {
  Serial.begin(9600);                   // initialize serial
  while (!Serial);

  Serial.println("LoRa Duplex - Set sync word");

  // override the default CS, reset, and IRQ pins (optional)
  LoRa.setPins(csPin, resetPin, irqPin);// set CS, reset, IRQ pin

  if (!LoRa.begin(915E6)) {             // initialize ratio at 915 MHz
    Serial.println("LoRa init failed. Check your connections.");
    while (true);                       // if failed, do nothing
  }

  LoRa.enableCrc();  // BHL This was my change

  LoRa.setSyncWord(0x12);           // ranges from 0-0xFF, default 0x34, see API docs

  LoRa.dumpRegisters(Serial);
  Serial.println("LoRa init succeeded.");
}

void loop() {
  if (millis() - lastSendTime > interval) {
    String message = "5 Hello Arduino LoRa! ";   // send a message
    message += msgCount;
    sendMessage(message);
    Serial.println("Sending " + message);
    lastSendTime = millis();            // timestamp the message
    //interval = random(2000) + 1000;    // 2-3 seconds
    interval = 1000;
  }

  // parse for a packet, and call onReceive with the result:
  onReceive(LoRa.parsePacket());
}

void sendMessage(String outgoing) {
  LoRa.beginPacket();                   // start packet
  LoRa.print(outgoing);                 // add payload
  LoRa.endPacket();                     // finish packet and send it
  msgCount++;                           // increment message ID
}

void onReceive(int packetSize) {
  if (packetSize == 0) return;          // if there's no packet, return

  // read packet header bytes:
  String incoming = "";

  while (LoRa.available()) {
    incoming += (char)LoRa.read();
  }

  Serial.println("Message: " + incoming);
  Serial.println("RSSI: " + String(LoRa.packetRssi()));
  Serial.println("Snr: " + String(LoRa.packetSnr()));
  Serial.println();
}

When I powered up a single client and the payload had a CRC

...............................
regHopChannel 01000000
Received 23 byte message 6 Hello Arduino LoRa! 6
RegIrqFlags 01010000
Receive-Done
Receive-Wait
.................................
regHopChannel 01000000
Received 23 byte message 6 Hello Arduino LoRa! 7
RegIrqFlags 01010000
Receive-Done
Receive-Wait
.................................
regHopChannel 01000000
Received 23 byte message 6 Hello Arduino LoRa! 8
RegIrqFlags 01010000
Receive-Done
Receive-Wait
...............................

Then when I increased the number of clients I started getting corrupted messages with CRC errors.

Received 24 byte message 6 Hello Arduino LoRa! 32
RegIrqFlags 01010000
Receive-Done
Receive-Wait
...............
regHopChannel 01000001
Received 25 byte message 8 Hello Arduino LoRa! 114
RegIrqFlags 01010000
Receive-Done
Receive-Wait
Payload CRC error
regHopChannel 01000000
Received 24 byte message s��=��(��p�^j�\ʏ�����
RegIrqFlags 01100000
Receive-Done
Receive-Wait
.............
regHopChannel 01000000
Received 24 byte message 6 Hello Arduino LoRa! 33
RegIrqFlags 01010000
Receive-Done
Receive-Wait
...............
regHopChannel 01000001
Received 25 byte message 8 Hello Arduino LoRa! 115
RegIrqFlags 01010000
Receive-Done
Receive-Wait

I need to do some more testing but now I think the problem was the RegIrqFlags PayloadCRCError flag was never going to get set because there was no CRC on the payload.

RFM95/96/97/98 shield library Part 10

Enums & Masks

The code was filled with “magic numbers” so it was time to get rid of them. In C# there are bit constants which I missed for my backport to .NetMF.

I sat down with the Semtech SX1276 datasheet and started typing in register names and adding constants and enums for all the bit masks, flags and defaults.

The initialisation of the RFM9X is now done in one of two constructors and an initialise method. Much like the approach used in the nRF24L01P libraries I use on Windows 10 IoT Core and .NetMF.

A few weeks ago I had a failed attempt at building a library which tried to hide as much of the hardware and wireless implementation details from the user as possible. Realistically if you’re building systems using LoRa, a basic understanding of the technology plus any regional regulatory requirements (frequency use, duty cycles etc.) is necessary.

	sealed class Rfm9XDevice
	{
		// Registers from SemTech SX127X Datasheet
		enum Registers : byte
		{
			MinValue = RegOpMode,
			RegFifo = 0x0,
			RegOpMode = 0x01,
			//Reserved 0x02-0x06
			RegFrMsb = 0x06,
			RegFrMid = 0x7,
			RegFrLsb = 0x08,
			RegPAConfig = 0x09,
			//RegPARamp = 0x0A, // not inlcuded as FSK/OOK functionality
			RegOcp = 0x0B,
			RegLna = 0x0C,
			RegFifoAddrPtr = 0x0D,
			//RegFifoTxBaseAddr = 0x0E
			RegFifoRxCurrent =0x10,
			RegIrqFlagsMask = 0x11,
			RegIrqFlags = 0x12,
			// RegRxNdBytes = 0x13
			// RegRxHeaderCnValueMsb=0x14
			// RegRxHeaderCnValueLsb=0x15
			// RegRxPacketCntValueMsb=0x16
			// RegRxPacketCntValueMsb=0x17
			// RegModemStat=0x18
			// RegPktSnrVale=0x19
			// RegPktRssiValue=0x1A
			// RegRssiValue=0x1B
			// RegHopChannel=0x1C
			RegModemConfig1 = 0x1D,
			RegModemConfig2 = 0x1E,
			RegSymbTimeout = 0x1F,
			RegPreambleMsb = 0x20,
			RegPreambleLsb = 0x21,
			RegPayloadLength = 0x22,
			RegMaxPayloadLength = 0x23,
			RegHopPeriod = 0x24,
			// RegFifiRxByteAddr = 0x25
			RegModemConfig3 = 0x26,
			RegPpmCorrection = 0x27,
			// RegFeiMsb = 0x28
			// RegFeiMid = 0x29
			// RegFeiLsb = 0x2A
			// Reserved 0x2B
			// RegRssiWideband = 0x2C
			// Reserved 0x2D-0x30
			RegDetectOptimize = 0x31,
			// Reserved 0x32
			RegInvertIQ = 0x33,
			// Reserved 0x34-0x36
			RegDetectionThreshold = 0x37,
			// Reserved 0x38
			RegSyncWord = 0x39,
			RegDioMapping1 = 0x40,
			RegVersion = 0x42,

			MaxValue = RegVersion,
		}

		// RegOpMode mode flags
		private const byte RegOpModeLongRangeModeLoRa = 0b10000000;
		private const byte RegOpModeLongRangeModeFskOok = 0b00000000;
		private const byte RegOpModeLongRangeModeDefault = RegOpModeLongRangeModeFskOok;

		private const byte RegOpModeAcessSharedRegLoRa = 0b00000000;
		private const byte RegOpModeAcessSharedRegFsk = 0b01000000;
		private const byte RegOpModeAcessSharedRegDefault = RegOpModeAcessSharedRegLoRa;

		private const byte RegOpModeLowFrequencyModeOnHighFrequency = 0b00000000;
		private const byte RegOpModeLowFrequencyModeOnLowFrequency = 0b00001000;
		private const byte RegOpModeLowFrequencyModeOnDefault = RegOpModeLowFrequencyModeOnLowFrequency;

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

		// Frequency configuration magic numbers from Semtech SX127X specs
		private const double RH_RF95_FXOSC = 32000000.0;
		private const double RH_RF95_FSTEP = RH_RF95_FXOSC / 524288.0;

		// RegFrMsb, RegFrMid, RegFrLsb
		private const double FrequencyDefault = 434000000.0;

One constructor is for shields where the chip select pin is connected to one of the two standard lines CS0/CS1.

// Constructor for shields with chip select connected to CS0/CS1 e.g. Elecrow/Electronic tricks
		public Rfm9XDevice(ChipSelectPin chipSelectPin, int resetPinNumber, int interruptPinNumber)
		{
			RegisterManager = new RegisterManager(chipSelectPin);

			// Check that SX127X chip is present
			Byte regVersionValue = RegisterManager.ReadByte((byte)Registers.RegVersion);
			if (regVersionValue != RegVersionValueExpected)
			{
				throw new ApplicationException("Semtech SX127X not found");
			}

			GpioController gpioController = GpioController.GetDefault();

The other is for shields with the chip select connected to another pin (the chip select has to be set to one of the default pins even though I am implementing the drive logic in code

	// Constructor for shields with chip select not connected to CS0/CS1 (but needs to be configured anyway) e.g. Dragino
		public Rfm9XDevice(ChipSelectPin chipSelectPin, int chipSelectPinNumber, int resetPinNumber, int interruptPinNumber)
		{
			RegisterManager = new RegisterManager(chipSelectPin, chipSelectPinNumber);

			// Check that SX127X chip is present
			Byte regVersionValue = RegisterManager.ReadByte((byte)Registers.RegVersion);
			if (regVersionValue != RegVersionValueExpected)
			{
				throw new ApplicationException("Semtech SX127X not found");	
			}

			GpioController gpioController = GpioController.GetDefault();

The Initialise method has a large number of parameters (most of them can be ignored and defaults used). I only set registers if the configuration has been changed from the default value. This is fine for most settings, but some (like RegSymbTimeoutMsb & RegSymbTimeoutLsb span two registers and are combined with other settings.

public void Initialise(RegOpModeMode modeAfterInitialise, // 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 lnaBoostLF = false, bool lnaBoostHf = 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 )
		{
			Frequency = frequency; // Store this away for RSSI adjustments
			RegOpModeModeCurrent = modeAfterInitialise;

			// Strobe Reset pin briefly to factory reset SX127X chip
			ResetGpioPin.Write(GpioPinValue.Low);
			Task.Delay(10);
			ResetGpioPin.Write(GpioPinValue.High);
			Task.Delay(10);

			// Put the device into sleep mode so registers can be changed
			SetMode(RegOpModeMode.Sleep);

			// Configure RF Carrier frequency
			if (frequency != FrequencyDefault)
			{
				byte[] bytes = BitConverter.GetBytes((long)(frequency / RH_RF95_FSTEP));
				RegisterManager.WriteByte((byte)Registers.RegFrMsb, bytes[2]);
				RegisterManager.WriteByte((byte)Registers.RegFrMid, bytes[1]);
				RegisterManager.WriteByte((byte)Registers.RegFrLsb, bytes[0]);
			}

Next step is add event handlers for inbound and outbound messages, then the finally split the device specific code into a stand alone library.

 

.Net MicroFramework LoRa library Part9

Receive and Transmit Interrupts

For the second to last development iteration of my RFM9X LoRa NetMF library client I have got the interrupt handler working for transmitting and receiving messages. My code sends a message every 10 seconds then goes back to waiting in receive mode .

//---------------------------------------------------------------------------------
// Copyright (c) August 2018, 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.NetMF.Rfm9X.ReceiveTransmitInterrupt
{
   using System;
   using System.Text;
   using System.Threading;
   using Microsoft.SPOT;
   using Microsoft.SPOT.Hardware;
   using SecretLabs.NETMF.Hardware.Netduino;

   public sealed class Rfm9XDevice
   {
      private const byte RegisterAddressReadMask = 0X7f;
      private const byte RegisterAddressWriteMask = 0x80;

      private SPI Rfm9XLoraModem = null;
      private OutputPort ResetGpioPin = null;
      private InterruptPort InterruptPin = null;

      public Rfm9XDevice(Cpu.Pin chipSelect, Cpu.Pin resetPin, Cpu.Pin interruptPin)
      {
         // Factory reset pin configuration
         ResetGpioPin = new OutputPort(Pins.GPIO_PIN_D9, true);
         ResetGpioPin.Write(false);
         Thread.Sleep(10);
         ResetGpioPin.Write(true);
         Thread.Sleep(10);

         this.Rfm9XLoraModem = new SPI(new SPI.Configuration(chipSelect, false, 0, 0, false, false, 2000, SPI.SPI_module.SPI1));

         InterruptPin = new InterruptPort(interruptPin, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);

         InterruptPin.OnInterrupt += InterruptPin_OnInterrupt;

         Thread.Sleep(100);
      }

      public Rfm9XDevice(Cpu.Pin chipSelect, Cpu.Pin reset)
      {
         // Factory reset pin configuration
         ResetGpioPin = new OutputPort(Pins.GPIO_PIN_D9, true);
         ResetGpioPin.Write(false);
         Thread.Sleep(10);
         ResetGpioPin.Write(true);
         Thread.Sleep(10);

         this.Rfm9XLoraModem = new SPI(new SPI.Configuration(chipSelect, false, 0, 0, false, false, 2000, SPI.SPI_module.SPI1));

         Thread.Sleep(100);
      }

      public Byte RegisterReadByte(byte registerAddress)
      {
         byte[] writeBuffer = new byte[] { registerAddress };
         byte[] readBuffer = new byte[1];
         Debug.Assert(Rfm9XLoraModem != null);

         Rfm9XLoraModem.WriteRead(writeBuffer, readBuffer, 1);

         return readBuffer[0];
      }

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

         readBuffer[0] = RegisterReadByte(address);
         readBuffer[1] = RegisterReadByte(address += 1);

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

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

         for (byte index = 0; index < length; index++)
         {
            readBuffer[index] = RegisterReadByte(address += 1);
         }

         return readBuffer;
      }

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

         Rfm9XLoraModem.Write(writeBuffer);
      }

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

         Rfm9XLoraModem.Write(writeBuffer);
      }

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

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

         Rfm9XLoraModem.Write(writeBuffer);
      }

      public void RegisterDump()
      {
         Debug.Print("---Registers 0x00 thru 0x42---");
         for (byte registerIndex = 0; registerIndex  4];

         // Mask off the upper 4 bits to get the rest of it.
         hexString += hexChars[singlebyte & 0x0F];

         return hexString;
      }

      private static string WordToHexString(ushort singleword)
      {
         string hexString = string.Empty;

         byte[] bytes = BitConverter.GetBytes(singleword);

         hexString += ByteToHexString(bytes[1]);

         hexString += ByteToHexString(bytes[0]);

         return hexString;
      }

      public class Program
      {
         public static void Main()
         {
            Rfm9XDevice rfm9XDevice = new Rfm9XDevice(Pins.GPIO_PIN_D10, Pins.GPIO_PIN_D9, Pins.GPIO_PIN_D2);
            byte MessageCount = Byte.MinValue;

            // Put device into LoRa + Sleep mode
            rfm9XDevice.RegisterWriteByte(0x01, 0x80); // 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, 0x80); // RegPaConfig

            //rfm9XDevice.RegisterWriteByte(0x40, 0x0);

            rfm9XDevice.RegisterWriteByte(0x01, 0x85); // 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 NetMF LoRa! " + MessageCount.ToString() ;
               MessageCount += 1;

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

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

               rfm9XDevice.RegisterWriteByte(0x40, 0x40); // RegDioMapping1 

               /// Set the mode to LoRa + Transmit
               rfm9XDevice.RegisterWriteByte(0x01, 0x83); // RegOpMode
               Debug.Print("Sending " + messageBytes.Length + " bytes message " + messageText);

               Thread.Sleep(10000);
            }
         }
      }
   }
}

On the Netduino3 device messages were being sent and received

The thread '' (0x2) has exited with code 0 (0x0).
Sending 19 bytes message Hello NetMF LoRa! 0
RegIrqFlags 08
Transmit-Done
Sending 19 bytes message Hello NetMF LoRa! 1
RegIrqFlags 08
Transmit-Done
Sending 19 bytes message Hello NetMF LoRa! 2
RegIrqFlags 08
Transmit-Done
Sending 19 bytes message Hello NetMF LoRa! 3
RegIrqFlags 08
Transmit-Done
RegIrqFlags 50
Receive-Message
Received 15 byte message HeLoRa World! 0
RegIrqFlags 50
Receive-Message
Received 15 byte message HeLoRa World! 2
Sending 19 bytes message Hello NetMF LoRa! 4
RegIrqFlags 08
Transmit-Done
RegIrqFlags 50
Receive-Message

On my Windows 10 Core device I could see messages arriving

RegIrqFlags 01010000
RX-Done
Received 19 byte message Hello NetMF LoRa! 1
The thread 0x168 has exited with code 0 (0x0).
RegIrqFlags 01010000
RX-Done
Received 19 byte message Hello NetMF LoRa! 2
The thread 0x8 has exited with code 0 (0x0).
RegIrqFlags 01010000
RX-Done

This library is going to be quite a bit smaller/lighter than my Windows 10 IoT Core one so the next so next step event handlers and refactoring.

.Net MicroFramework LoRa library Part7

Transmit Interrupt

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

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

//---------------------------------------------------------------------------------
// Copyright (c) August 2018, 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.NetMF.Rfm9X.TransmitInterrupt
{
	using System;
	using System.Text;
	using System.Threading;
	using Microsoft.SPOT;
	using Microsoft.SPOT.Hardware;
	using SecretLabs.NETMF.Hardware.Netduino;

	public sealed class Rfm9XDevice
	{
		private const byte RegisterAddressReadMask = 0X7f;
		private const byte RegisterAddressWriteMask = 0x80;

		private SPI Rfm9XLoraModem = null;
		private OutputPort ResetGpioPin = null;
		private InterruptPort InterruptPin = null;

		public Rfm9XDevice(Cpu.Pin chipSelect, Cpu.Pin resetPin, Cpu.Pin interruptPin)
		{
			// Factory reset pin configuration
			ResetGpioPin = new OutputPort(Pins.GPIO_PIN_D9, true);
			ResetGpioPin.Write(false);
			Thread.Sleep(10);
			ResetGpioPin.Write(true);
			Thread.Sleep(10);

			this.Rfm9XLoraModem = new SPI(new SPI.Configuration(chipSelect, false, 0, 0, false, false, 2000, SPI.SPI_module.SPI1));

			InterruptPin = new InterruptPort(interruptPin, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);

			InterruptPin.OnInterrupt += InterruptPin_OnInterrupt;

			Thread.Sleep(100);
		}

		public Byte RegisterReadByte(byte registerAddress)
		{
			byte[] writeBuffer = new byte[] { registerAddress };
			byte[] readBuffer = new byte[1];
			Debug.Assert(Rfm9XLoraModem != null);

			Rfm9XLoraModem.WriteRead(writeBuffer, readBuffer, 1);

			return readBuffer[0];
		}

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

			readBuffer[0] = RegisterReadByte(address);
			readBuffer[1] = RegisterReadByte(address += 1);

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

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

			for (byte index = 0; index  4];

			// Mask off the upper 4 bits to get the rest of it.
			hexString += hexChars[singlebyte & 0x0F];

			return hexString;
		}

		private static string WordToHexString(ushort singleword)
		{
			string hexString = string.Empty;

			byte[] bytes = BitConverter.GetBytes(singleword);

			hexString += ByteToHexString(bytes[1]);

			hexString += ByteToHexString(bytes[0]);

			return hexString;
		}

		void InterruptPin_OnInterrupt(uint data1, uint data2, DateTime time)
		{
			byte IrqFlags = this.RegisterReadByte(0x12); // RegIrqFlags
			Debug.Print("RegIrqFlags " + ByteToHexString(IrqFlags));

			if ((IrqFlags & 0x08) == 0x08)  // TxDone
			{
				Debug.Print("Transmit-Done");
			}

			this.RegisterWriteByte(0x12, 0xff);// RegIrqFlags
		}

		public class Program
		{
			public static void Main()
			{
				Rfm9XDevice rfm9XDevice = new Rfm9XDevice(Pins.GPIO_PIN_D10, Pins.GPIO_PIN_D9, Pins.GPIO_PIN_D2);
				byte MessageCount = byte.MinValue;

				while (true)
				{
					// Put device into LoRa + Sleep mode
					rfm9XDevice.RegisterWriteByte(0x01, 0x80); // 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, 0x80); // RegPaConfig

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

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

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

						string messageText = "Hello NetMF LoRa! ";
						if (MessageCount != 0)
						{
							messageText += "-" + MessageCount.ToString();
						}
						else
						{
							messageText += MessageCount.ToString();
						}
						MessageCount += 1;

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

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

						Debug.Print("Sending " + messageBytes.Length + " bytes message " + messageText);
						/// Set the mode to LoRa + Transmit
						rfm9XDevice.RegisterWriteByte(0x01, 0x83); // RegOpMode 

						Thread.Sleep(10000);
					}
				}
			}
		}

	}
}

Unlike the Windows 10 IoT core version I can configure the interrupt to only trigger on the leading edge.

InterruptPin = new InterruptPort(interruptPin, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);

In the Debug output window of VS2K12 I could see

The thread '' (0x2) has exited with code 0 (0x0).
Sending 19 bytes message Hello NetMF LoRa! 0
RegIrqFlags 08
Transmit-Done
Sending 20 bytes message Hello NetMF LoRa! -1
RegIrqFlags 08
Transmit-Done
Sending 20 bytes message Hello NetMF LoRa! -2
RegIrqFlags 08
Transmit-Done

On my Arduino device the message arrived

LoRa Receiver Callback
Received packet 'Hello NetMF LoRa! 0' with RSSI -29
Received packet 'Hello NetMF LoRa! -1' with RSSI -29
Received packet 'Hello NetMF LoRa! -2' with RSSI -29
Received packet 'Hello NetMF LoRa! -3' with RSSI -29
Received packet 'Hello NetMF LoRa! -4' with RSSI -29

Next step interrupts for processing inbound messages

.Net MicroFramework LoRa library Part6

Receive Basic

I can configure the device to send messages now I need to configure the SX1276 device to receive them. For my first attempt just simple polling for a message to arrive nothing flash.

//---------------------------------------------------------------------------------
// Copyright (c) August 2018, 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.NetMF.Rfm9X.ReceiveBasic
{
   using System;
   using System.Text;
   using System.Threading;
   using Microsoft.SPOT;
   using Microsoft.SPOT.Hardware;
   using SecretLabs.NETMF.Hardware.Netduino;

   public sealed class Rfm9XDevice
   {
      private const byte RegisterAddressReadMask = 0X7f;
      private const byte RegisterAddressWriteMask = 0x80;

      private SPI Rfm9XLoraModem = null;
      private OutputPort ResetGpioPin = null;

      public Rfm9XDevice(Cpu.Pin chipSelect, Cpu.Pin reset)
      {
         // Factory reset pin configuration
         ResetGpioPin = new OutputPort(Pins.GPIO_PIN_D9, true);
         ResetGpioPin.Write(false);
         Thread.Sleep(10);
         ResetGpioPin.Write(true);
         Thread.Sleep(10);

         this.Rfm9XLoraModem = new SPI(new SPI.Configuration(chipSelect, false, 0, 0, false, false, 2000, SPI.SPI_module.SPI1));

         Thread.Sleep(100);
      }

      public Byte RegisterReadByte(byte registerAddress)
      {
         byte[] writeBuffer = new byte[] { registerAddress };
         byte[] readBuffer = new byte[1];
         Debug.Assert(Rfm9XLoraModem != null);

         Rfm9XLoraModem.WriteRead(writeBuffer, readBuffer, 1);

         return readBuffer[0];
      }

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

         readBuffer[0] = RegisterReadByte(address);
         readBuffer[1] = RegisterReadByte(address += 1);

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

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

         for (byte index = 0; index < length; index++)
         {
            readBuffer[index] = RegisterReadByte(address += 1);
         }

         return readBuffer;
      }

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

         Rfm9XLoraModem.Write(writeBuffer);
      }

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

         Rfm9XLoraModem.Write(writeBuffer);
      }

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

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

         Rfm9XLoraModem.Write(writeBuffer);
      }

      public void RegisterDump()
      {
         Debug.Print("---Registers 0x00 thru 0x42---");
         for (byte registerIndex = 0; registerIndex  4];

         // Mask off the upper 4 bits to get the rest of it.
         hexString += hexChars[singlebyte & 0x0F];

         return hexString;
      }

      private static string WordToHexString(ushort singleword)
      {
         string hexString = string.Empty;

         byte[] bytes = BitConverter.GetBytes(singleword);

         hexString += ByteToHexString(bytes[1]);

         hexString += ByteToHexString(bytes[0]);

         return hexString;
      }
   }
}

In the debug window I could see messages from one of my arduino devices

The thread '' (0x2) has exited with code 0 (0x0).
Receive-Wait
.
.
.
.
.
.
.

.
.
.
.

Receive-Message
Received 15 byte message HeLoRa World! 0
Receive-Done
Receive-Wait
.
.
.
.
.
.
.
.
.
.
.

Receive-Message
Received 15 byte message HeLoRa World! 2
Receive-Done
Receive-Wait
.
.

Next steps handling inbound & outbound messages with interrupts, then integrating all the samples into a useful functional V1 library.

.Net MicroFramework LoRa library Part5

Transmit Basic

Finally at the point where I can send a message to one of my Windows 10 IoT Core devices. Not going to use interrupts, just putting the bytes to send into the SX1276 Fifo and looping until they are sent.

//---------------------------------------------------------------------------------
// Copyright (c) August 2018, 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.NetMF.Rfm9X.TransmitBasic
{
   using System;
   using System.Text;
   using System.Threading;
   using Microsoft.SPOT;
   using Microsoft.SPOT.Hardware;
   using SecretLabs.NETMF.Hardware.Netduino;

   public sealed class Rfm9XDevice
   {
      private const byte RegisterAddressReadMask = 0X7f;
      private const byte RegisterAddressWriteMask = 0x80;

      private SPI Rfm9XLoraModem = null;
      private OutputPort ResetGpioPin = null;

      public Rfm9XDevice(Cpu.Pin chipSelect, Cpu.Pin reset)
      {
         // Factory reset pin configuration
         ResetGpioPin = new OutputPort(Pins.GPIO_PIN_D9, true);
         ResetGpioPin.Write(false);
         Thread.Sleep(10);
         ResetGpioPin.Write(true);
         Thread.Sleep(10);

         //this.Rfm9XLoraModem = new SPI(new SPI.Configuration(chipSelect, false, 0, 0, false, true, 2000, SPI.SPI_module.SPI1));
         this.Rfm9XLoraModem = new SPI(new SPI.Configuration(chipSelect, false, 0, 0, false, false, 2000, SPI.SPI_module.SPI1));

         Thread.Sleep(100);
      }

      public Byte RegisterReadByte(byte registerAddress)
      {
         byte[] writeBuffer = new byte[] { registerAddress };
         byte[] readBuffer = new byte[1];
         Debug.Assert(Rfm9XLoraModem != null);

         //Rfm9XLoraModem.WriteRead(writeBuffer, readBuffer, 1);
         Rfm9XLoraModem.WriteRead(writeBuffer, readBuffer, 1);

         return readBuffer[0];
      }

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

         //Rfm9XLoraModem.WriteRead(readBuffer, writeBuffer, 1 ); // Check this

         readBuffer[0] = RegisterReadByte(address);
         readBuffer[1] = RegisterReadByte(address += 1);

         return (ushort)(readBuffer[1] + (readBuffer[0] &lt;<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>&lt; 8));
      }

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

         //Rfm9XLoraModem.WriteRead(readBuffer, writeBuffer, 1); // Check this

         for (byte index = 0; index  4];

         // Mask off the upper 4 bits to get the rest of it.
         hexString += hexChars[singlebyte &amp; 0x0F];

         return hexString;
      }

      private static string WordToHexString(ushort singleword)
      {
         string hexString = string.Empty;

         byte[] bytes = BitConverter.GetBytes(singleword);

         hexString += ByteToHexString(bytes[1]);

         hexString += ByteToHexString(bytes[0]);

         return hexString;
      }

   }
}

The debugging output of the device shows the message being transmitted

The thread '' (0x2) has exited with code 0 (0x0).
Sending 17 bytes message Hello NetMF LoRa!
Send-wait
.
.
.
.
.
Send-Done
Sending 17 bytes message Hello NetMF LoRa!
Send-wait
.
.
.
.
.
Send-Done

On my Windows 10 IoT core device I could see the messages arriving

RegIrqFlags 01010000
Receive-Message
Received 17 byte message Hello NetMF LoRa!
The thread 0x6b4 has exited with code 0 (0x0).
Sending 22 bytes message W10 IoT Core LoRa! 252
RegIrqFlags 00001000
Transmit-Done
RegIrqFlags 01010000
Receive-Message
Received 17 byte message Hello NetMF LoRa!
The program '[2932] backgroundTaskHost.exe' has exited with code -1 (0xffffffff).

All default settings for not a lot of range etc. but it works

.Net MicroFramework LoRa library Part4

Register Read and Write

For configuration and operation I extended the RegisterManager class with methods for reading/writing bytes, words and byte arrays.

//---------------------------------------------------------------------------------
// Copyright (c) August 2018, 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.NetMF.Rfm9X.RegisterReadandWrite
{
   using System;
   using System.Threading;
   using Microsoft.SPOT;
   using Microsoft.SPOT.Hardware;
   using SecretLabs.NETMF.Hardware.Netduino;

   public sealed class Rfm9XDevice
   {
      private const byte RegisterAddressReadMask = 0X7f;
      private const byte RegisterAddressWriteMask = 0x80;

      private SPI Rfm9XLoraModem = null;
      private OutputPort ResetGpioPin = null;

      public Rfm9XDevice(Cpu.Pin chipSelect, Cpu.Pin reset)
      {
         // Factory reset pin configuration
         ResetGpioPin = new OutputPort(Pins.GPIO_PIN_D9, true);
         ResetGpioPin.Write(false);
         Thread.Sleep(10);
         ResetGpioPin.Write(true);
         Thread.Sleep(10);

         //this.Rfm9XLoraModem = new SPI(new SPI.Configuration(chipSelect, false, 0, 0, false, true, 2000, SPI.SPI_module.SPI1));
         this.Rfm9XLoraModem = new SPI(new SPI.Configuration(chipSelect, false, 0, 0, false, false, 2000, SPI.SPI_module.SPI1));

         Thread.Sleep(100);
      }

      public Byte RegisterReadByte(byte registerAddress)
      {
         byte[] writeBuffer = new byte[] { registerAddress };
         byte[] readBuffer = new byte[1];
         Debug.Assert(Rfm9XLoraModem != null);

         //Rfm9XLoraModem.WriteRead(writeBuffer, readBuffer, 1);
         Rfm9XLoraModem.WriteRead(writeBuffer, readBuffer, 1 );

         return readBuffer[0];
      }

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

         //Rfm9XLoraModem.WriteRead(readBuffer, writeBuffer, 4 ); // Check this

         readBuffer[0] = RegisterReadByte( address ) ;
         readBuffer[1] = RegisterReadByte(address+=1);

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

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

         //Rfm9XLoraModem.WriteRead(readBuffer, writeBuffer, 1); // Check this

         for (byte index = 0; index  4];

         // Mask off the upper 4 bits to get the rest of it.
         hexString += hexChars[singlebyte & 0x0F];

         return hexString;
      }

      private static string WordToHexString(ushort singleword)
      {
         string hexString = string.Empty;

         byte[] bytes = BitConverter.GetBytes(singleword);

         hexString += ByteToHexString(bytes[1]);

         hexString += ByteToHexString(bytes[0]);

         return hexString;
      }

   }
}

I had to add an extra "ToHexString" method so I could display the returned byte and word values

Read RegOpMode (read byte)
RegOpMode 0x09
Set LoRa mode and sleep mode (write byte)
Read the preamble (read word)
Preamble 0x0008
Set the preamble to 0x80 (write word)
Read the centre frequency (read byte array)
Frequency Msb 0x80 Mid 0x00 Lsb 0x4F
Set the centre frequency to 916MHz (write byte array)
—Registers 0x00 thru 0x42—
Register 0x00 – Value 0XF4
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

The register dump shows the SX1276 is in LoRa+Sleep mode and the preamble is set to 0x80 etc.

The way I read words and arrays of bytes isn’t very efficient will need to revisit when I have more time and/or access to a digital storage scope.

.Net MicroFramework LoRa library Part3

Register Scan

Next step was to scan the Semtech SX127X registers and check the values were as expected

//---------------------------------------------------------------------------------
// Copyright (c) August 2018, 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.NetMF.Rfm9X.RegisterScan
{
   using System;
   using System.Threading;
   using Microsoft.SPOT;
   using Microsoft.SPOT.Hardware;
   using SecretLabs.NETMF.Hardware.Netduino;

   public sealed class Rfm9XDevice
   {
      private SPI rfm9XLoraModem = null;

      public Rfm9XDevice(Cpu.Pin chipSelect)
      {
         this.rfm9XLoraModem = new SPI(new SPI.Configuration(chipSelect, false, 0, 0, false, true, 500, SPI.SPI_module.SPI1));

         Thread.Sleep(100);
      }

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

         rfm9XLoraModem.WriteRead(writeBuffer, readBuffer, 1);

         return readBuffer[0];
      }
   }

   public class Program
   {
      public static void Main()
      {
         Rfm9XDevice rfm9XDevice = new Rfm9XDevice(Pins.GPIO_PIN_D10);

         while (true)
         {
            Debug.Print("---Registers 0x00 thru 0x42---");
            for (byte registerIndex = 0; registerIndex > 4];

         // Mask off the upper 4 bits to get the rest of it.
         hexString += hexChars[singlebyte & 0x0F];

         return hexString;
      }
   }
}

On start-up the device is not in LoRa mode so some weren’t set up properly.

---Registers 0x00 thru 0x42---
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 0X70
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 0X00
Register 0x3D - Value 0X02
Register 0x3E - Value 0X80
Register 0x3F - Value 0X40
Register 0x40 - Value 0X00
Register 0x41 - Value 0X00
Register 0x42 - Value 0X12

Next step reading & writing registers

 

.Net MicroFramework LoRa library Part2

Register Read

Getting this bit too work took a bit longer than expected. The code below works but isn’t super efficient. I must be reading the SX1276 SPI timing diagram wrong or I need to read the .NetMF SPI implementation code some more.

//---------------------------------------------------------------------------------
// Copyright (c) August 2018, 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.NetMF.Rfm9X.RegisterRead
{
   using System;
   using System.Threading;
   using Microsoft.SPOT;
   using Microsoft.SPOT.Hardware;
   using SecretLabs.NETMF.Hardware;
   using SecretLabs.NETMF.Hardware.Netduino;

   public class Program
   {

      public static void Main()
      {
         //OutputPort reset = new OutputPort(Pins.GPIO_PIN_D9, true);
         OutputPort chipSelect = null;
         //chipSelect = new OutputPort(Pins.GPIO_PIN_D10, true);
         //SPI spiPort = new SPI(new SPI.Configuration(Pins.GPIO_NONE, false, 0, 0, true, true, 500, SPI.SPI_module.SPI1));
         SPI spiPort = new SPI(new SPI.Configuration(Pins.GPIO_PIN_D10, false, 0, 0, false, true, 500, SPI.SPI_module.SPI1));

         Thread.Sleep(100);

         while (true)
         {
            //byte[] writeBuffer = new byte[] { 0x42 }; // RegVersion exptecing 0x12
            byte[] writeBuffer = new byte[] { 0x06 }; // RegFreqMsb expecting 0x6C
            //byte[] writeBuffer = new byte[] { 0x07 }; // RegFreqMid expecting 0x80
            //byte[] writeBuffer = new byte[] { 0x08 }; // RegFreqLsb expecting 0x00
            byte[] readBuffer = new byte[1];

            if (chipSelect != null)
            {
               chipSelect.Write(false);
            }
            spiPort.WriteRead(writeBuffer, readBuffer, 1);
            if (chipSelect != null)
            {
               chipSelect.Write(true);
            }

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

            Thread.Sleep(1000);
         }
      }

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

         // Create a character array for hexidecimal conversion.
         const string hexChars = "0123456789ABCDEF";

         // Loop through the bytes.
         for (byte b = 0; b  0)
               hexString += "-";

            // Grab the top 4 bits and append the hex equivalent to the return string.
            hexString += hexChars[bytes[b] >> 4];

            // Mask off the upper 4 bits to get the rest of it.
            hexString += hexChars[bytes[b] & 0x0F];
         }

         return hexString;
      }
   }
}

The output indicated that I could success fully read the middle byte (of three) of the transmit frequency and it matched the default. There seems to be something odd about the chip select line/ sequencing

The thread '' (0x2) has exited with code 0 (0x0).
Value = 0xE4
Value = 0xE4
Value = 0xE4
Value = 0xE4
Value = 0xE4
Value = 0xE4

It’s late, so the next step will be scanning the SX1276 registers and starting to build out the register manager code to read+write byte values, word values and arrays.

Also noticed that setup seems to work a bit better on a Netduino3 device, have noticed this before with higher power consumption shields.