Arduino LoRa Payload Addressing Client

This is a demo Arduino client (based on one of the examples from Arduino-LoRa) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI field gateway proof of concept(PoC).

Bill of materials (Prices Sep 2018)

ArduinoUnoR3DraginoLoRa

The code is pretty basic, it shows how to pack the payload and set the necessary RFM9X/SX127X LoRa module configuration, has no power conservation, advanced wireless configuration etc.

/*
  Adapted from LoRa Duplex communication with Sync Word

  Sends temperature & humidity data from Seeedstudio 

  https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-High-Accuracy-Min-p-1921.html

  To my Windows 10 IoT Core RFM 9X library

  https://blog.devmobile.co.nz/2018/09/03/rfm9x-iotcore-payload-addressing/

*/
#include
#include
#include
const int csPin = 10;          // LoRa radio chip select
const int resetPin = 9;       // LoRa radio reset
const int irqPin = 2;         // change for your board; must be a hardware interrupt pin

// Field gateway configuration
const byte FieldGatewayAddress[] = "LoRaIoT1";
const float FieldGatewayFrequency =  915000000.0;
const byte FieldGatewaySyncWord = 0x12 ;

// Payload configuration
const int PayloadSizeMaximum = 64 ;
char payload[PayloadSizeMaximum] = "";
const byte SensorReadingSeperator = ',' ;

// Manual serial number configuration
char DeviceId[] = {"Arduino1"};

const int LoopSleepDelaySeconds = 60;

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

  Serial.println("LoRa Setup");

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

  if (!LoRa.begin(FieldGatewayFrequency))
  {
    Serial.println("LoRa init failed. Check your connections.");
    while (true);
  }

  // Need to do this so field gateways pays attention to messsages from this device
  LoRa.enableCrc();
  LoRa.setSyncWord(FieldGatewaySyncWord);  

  //LoRa.dumpRegisters(Serial);
  Serial.println("LoRa Setup done.");

  // Configure the Seeedstudio TH02 temperature & humidity sensor
  Serial.println("TH02 setup");
  TH02.begin();
  delay(100);
  Serial.println("TH02 Setup done");  

  Serial.println("Setup done");
}

void loop()
{
  int payloadLength = 0 ;
  float temperature ;
  float humidity ;

  Serial.println("Loop called");
  memset(payload, 0, sizeof(payload));

  // prepare the payload header with "To" Address length (top nibble) and "From" address length (bottom nibble)
  payload[0] = (strlen(FieldGatewayAddress) << 4) | strlen( DeviceId ) ;
  payloadLength += 1;

  // Copy the "To" address into payload
  memcpy(&payload[payloadLength], FieldGatewayAddress, strlen(FieldGatewayAddress));
  payloadLength += strlen(FieldGatewayAddress) ;

  // Copy the "From" into payload
  memcpy(&payload[payloadLength], DeviceId, strlen(DeviceId));
  payloadLength += strlen(DeviceId) ;

  // Read the temperature and humidity values then display nicely
  temperature = TH02.ReadTemperature();
  humidity = TH02.ReadHumidity();

  Serial.print("T:");
  Serial.print( temperature, 1 ) ;
  Serial.print( "C" ) ;

  Serial.print(" H:");
  Serial.print( humidity, 0 ) ;
  Serial.println( "%" ) ;

  // Copy the temperature into the payload
  payload[ payloadLength] = 't';
  payloadLength += 1 ;
  payload[ payloadLength] = ' ';
  payloadLength += 1 ;
  payloadLength += strlen( dtostrf(temperature, -1, 1, &payload[payloadLength]));
  payload[ payloadLength] = SensorReadingSeperator;
  payloadLength += sizeof(SensorReadingSeperator) ;

  // Copy the humidity into the payload
  payload[ payloadLength] = 'h';
  payloadLength += 1 ;
  payload[ payloadLength] = ' ';
  payloadLength += 1 ;
  payloadLength += strlen( dtostrf(humidity, -1, 0, &payload[payloadLength]));  

  // display info about payload then send it (No ACK) with LoRa unlike nRF24L01
  Serial.print( "RFM9X/SX127X Payload length:");
  Serial.print( payloadLength );
  Serial.println( " bytes" );

  LoRa.beginPacket();
  LoRa.write( payload, payloadLength );
  LoRa.endPacket();      

  Serial.println("Loop done");

  delay(LoopSleepDelaySeconds * 1000l);
}

In the debug output window the messages from the device looked like this

Register 0x40 – Value 0X00 – Bits 00000000
Register 0x41 – Value 0X00 – Bits 00000000
Register 0x42 – Value 0X12 – Bits 00010010

19:15:21-RX From Arduino1 PacketSnr 9.3 Packet RSSI -49dBm RSSI -105dBm = 11 byte message "t 18.8,h 91"

19:15:30-TX 25 byte message Hello from LoRaIoT1 ! 255
19:15:30-TX Done
19:15:31-RX From Arduino1 PacketSnr 9.3 Packet RSSI -49dBm RSSI -103dBm = 11 byte message "t 18.8,h 91"

19:15:41-RX From Arduino1 PacketSnr 9.3 Packet RSSI -48dBm RSSI -106dBm = 11 byte message "t 18.8,h 91"

There must be a nicer way of building the payload, a topic for a future post maybe.

RFM9X.NetMF on Github

After a month of posts the source code of V0.9 of my RFM9X/SX127X library is on GitHub. I included all of the source for my test harness and proof of concept(PoC) applications so other people can follow along with “my learning experience”.

I need to trial with some more hardware, frequency bands, variety of clients, initialisation configurations and backport the last round of fixes from my .Net library.

The simplest possible application .NetMF using the new library

/---------------------------------------------------------------------------------
// 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( Rfm9XDevice.RegOpModeMode.ReceiveContinuous, 915000000, paBoost: true, rxPayloadCrcOn: true);
         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);
            rfm9XDevice.SendMessage(messageBytes);

            Thread.Sleep(10000);
         }
      }

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

      static void rfm9XDevice_OnDataReceived(byte[] data)
      {
         try
         {
            string messageText = new string(UTF8Encoding.UTF8.GetChars(data));

            Debug.Print("Received " + data.Length.ToString() + " byte message " + messageText);
         }
         catch (Exception ex)
         {
            Debug.Print(ex.Message);
         }
      }
   }
}

// Dirty hack for Rosyln 
namespace System.Diagnostics
{
   public enum DebuggerBrowsableState
   {
      Never = 0,
      Collapsed = 2,
      RootHidden = 3
   }
}

I need to do more testing (especially of the initialisation options) and will add basic device addressing soon so my field gateway will only see messages which it is interested in.

RFM9X.IoTCore on Github

After a month of posts the source code of V0.9 of my RFM9X/SX127X library is on GitHub. I included all of the source for my test harness and proof of concept(PoC) applications so other people can follow along with “my learning experience”.

I started wanting a library to for a LoRa telemetry field gateway and ended up writing one (which is usually not a good idea). My use case was a device that was configured, then run for long periods of time, was not battery powered, and if settings were changed could be restarted. I need to trial with some more hardware, frequency bands, variety of clients, initialisation configurations and backport the last round of fixes to my .NetMF library.

I am also looking at writing an RFM69 library using a pair of shields (434MHz & 915MHz)  from seegel-systeme.

The simplest possible application using the new library (a fair bit of the code is to support the different supported shields)

//---------------------------------------------------------------------------------
// 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.Rfm9x.LoRaDeviceClient
{
	using System;
	using System.Diagnostics;
	using System.Text;
	using System.Threading.Tasks;

	using devMobile.IoT.Rfm9x;
	using Windows.ApplicationModel.Background;

	public sealed class StartupTask : IBackgroundTask
    {
		private byte NessageCount = Byte.MaxValue;
#if DRAGINO
		private const byte ChipSelectLine = 25;
		private const byte ResetLine = 17;
		private const byte InterruptLine = 4;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, ChipSelectLine, ResetLine, InterruptLine);
#endif
#if M2M
		private const byte ChipSelectLine = 25;
		private const byte ResetLine = 17;
		private const byte InterruptLine = 4;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, ChipSelectLine, ResetLine, InterruptLine);
#endif
#if ELECROW
		private const byte ResetLine = 22;
		private const byte InterruptLine = 25;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS1, ResetLine, InterruptLine);
#endif
#if ELECTRONIC_TRICKS
		private const byte ResetLine = 22;
		private const byte InterruptLine = 25;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, 22, 25);
#endif

		public void Run(IBackgroundTaskInstance taskInstance)
		{
			rfm9XDevice.Initialise(Rfm9XDevice.RegOpModeMode.ReceiveContinuous, 915000000.0, paBoost: true);

#if DEBUG
			rfm9XDevice.RegisterDump();
#endif
			rfm9XDevice.OnReceive += Rfm9XDevice_OnReceive;
			rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

			Task.Delay(10000).Wait();

			while (true)
			{
				string messageText = string.Format("Hello W10 IoT Core LoRa! {0}", NessageCount);
				NessageCount -= 1;

				byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
				Debug.WriteLine("{0:HH:mm:ss}-TX {1} byte message {2}", DateTime.Now, messageBytes.Length, messageText);
				this.rfm9XDevice.Send(messageBytes);

				Task.Delay(10000).Wait();
			}
		}

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

				Debug.WriteLine("{0:HH:mm:ss}-RX {1} byte message {2}", DateTime.Now, e.Data.Length, messageText);
			}
			catch (Exception ex)
			{
				Debug.WriteLine(ex.Message);
			}
		}

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

I have a shield from uputronics on order which should arrive from the UK in roughly a week. This shield has two RFM9X devices onboard (In my case 434MHz & 915MHz) so it will be interesting to see how my library copes with two instances of the stack running together.

I need to do more testing (especially of the initialisation options) and will add basic device addressing soon so my field gateway will only see messages which it is interested in.

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