RFM69 hat library Part10

Encryption: Rasmatic/RFM69-Arduino-Library

The RFM69CW/RFM69HCW modules (based on the Semtech SX1231/SX1231H) have built in support for AES128 encryption. In this test harness I’m exploring the RFM69 AES128 implementation.

In the Arduino code I found the order of initialisation was critical. Because of the way the Rasmatic library is written the call to vRF69SetAesKey has to be after the vInitialize.

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

  pinMode(SENDER_DETECT_PIN, INPUT_PULLUP);  
  
  radio.Modulation     = FSK;
  radio.COB            = RFM69;
  radio.Frequency      = 915000;
  radio.OutputPower    = 10+18;          //10dBm OutputPower
  radio.PreambleLength = 16;             //16Byte preamble
  radio.FixedPktLength = false;          //packet in message which need to be send
  radio.CrcDisable     = false;          //CRC On
  radio.AesOn          = false;
  radio.SymbolTime     = 416000;         //2.4Kbps
  radio.Devation       = 35;             //35KHz for devation
  radio.BandWidth      = 100;            //100KHz for bandwidth
  radio.SyncLength     = 3;              //
  radio.SyncWord[0]    = 0xAA;
  radio.SyncWord[1]    = 0x2D;
  radio.SyncWord[2]    = 0xD4;

  // Highly secure 16byte fixed length key
  radio.AesKey[0] = 0x0;
  radio.AesKey[1] = 0x1;
  radio.AesKey[2] = 0x2;
  radio.AesKey[3] = 0x3;
  radio.AesKey[4] = 0x4;
  radio.AesKey[5] = 0x5;
  radio.AesKey[6] = 0x6;
  radio.AesKey[7] = 0x7;
  radio.AesKey[8] = 0x8;
  radio.AesKey[9] = 0x9;
  radio.AesKey[10] = 0xA;
  radio.AesKey[11] = 0xB;
  radio.AesKey[12] = 0xC;
  radio.AesKey[13] = 0xD;
  radio.AesKey[14] = 0xE;
  radio.AesKey[15] = 0xF;
  radio.AesOn = true ;

  radio.vInitialize();

  radio.vRF69SetAesKey();

When I first fired up the Arduino client on the Windows 10 IoT Core device I hadn’t configured the AES key but had enabled encryption.

19:21:25 Received 13 byte message =!{��>�_��5
19:21:26.114 RegIrqFlags 01000110
19:21:26 Received 13 byte message ���gǺm,0|��
19:21:26.273 Send-Done
19:21:26.453 RegIrqFlags 00001000
19:21:26.467 Transmit-Done
19:21:27.244 RegIrqFlags 01000110
19:21:27 Received 13 byte message w6�H�Y���#"#
19:21:28.373 RegIrqFlags 01000110
19:21:28 Received 13 byte message c�u�$mԙ���M{
...
Restart Arduino client
...
19:21:34.836 RegIrqFlags 01000110
19:21:34 Received 13 byte message ���gǺm,0|��
19:21:35.965 RegIrqFlags 01000110
19:21:35 Received 13 byte message w6�H�Y���#"#
19:21:36.429 Send-Done
19:21:36.610 RegIrqFlags 00001000
19:21:36.624 Transmit-Done
19:21:37.095 RegIrqFlags 01000110
19:21:37 Received 13 byte message c�u�$mԙ���M{
The program '[1560] backgroundTaskHost.exe' has exited with code -1 (0xffffffff).

When I restarted the Arduino client I got the same sequences of characters in the messages so it looks like the RFM69 encryption is most probably using electronic code book (ECB) rather than a mode with a changing initialisation vector(IV) e.g. cypher block chaining(CBC). (which wasn’t a surprise)

After modifying the Windows 10 IoT Core application to receive and transmit encrypted payloads

/*
    Copyright ® 2019 July devMobile Software, All Rights Reserved

	 MIT License

	 Permission is hereby granted, free of charge, to any person obtaining a copy
	 of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
	 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	 copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
	 copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	 SOFTWARE

 */
namespace devMobile.IoT.Rfm69Hcw.Encryption
{
	using System;
	using System.Diagnostics;
	using System.Runtime.InteropServices.WindowsRuntime;
	using System.Text;
	using System.Threading.Tasks;
	using Windows.ApplicationModel.Background;
	using Windows.Devices.Gpio;
	using Windows.Devices.Spi;

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

		public Rfm69HcwDevice(int chipSelectPin, int resetPin, int interruptPin)
		{
			SpiController spiController = SpiController.GetDefaultAsync().AsTask().GetAwaiter().GetResult();
			var settings = new SpiConnectionSettings(chipSelectPin)
			{
				ClockFrequency = 500000,
				Mode = SpiMode.Mode0,
			};

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

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

			InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;

			Rfm69Hcw = spiController.GetDevice(settings);
		}

		private void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
		{
			if (args.Edge != GpioPinEdge.RisingEdge)
			{
				return;
			}

			byte irqFlags = this.RegisterReadByte(0x28); // RegIrqFlags2
			Debug.WriteLine("{0:HH:mm:ss.fff} RegIrqFlags {1}", DateTime.Now, Convert.ToString((byte)irqFlags, 2).PadLeft(8, '0'));
			if ((irqFlags & 0b00000100) == 0b00000100)  // PayLoadReady set
			{
				// Read the length of the buffer
				byte numberOfBytes = this.RegisterReadByte(0x0);

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

				for (int i = 0; i < numberOfBytes; i++)
				{
					messageBytes[i] = this.RegisterReadByte(0x00); // RegFifo
				}

				string messageText = UTF8Encoding.UTF8.GetString(messageBytes);
				Debug.WriteLine("{0:HH:mm:ss} Received {1} byte message {2}", DateTime.Now, messageBytes.Length, messageText);
			}

			if ((irqFlags &amp; 0b00001000) == 0b00001000)  // PacketSent set
			{
				this.RegisterWriteByte(0x01, 0b00010000); // RegOpMode set ReceiveMode
				Debug.WriteLine("{0:HH:mm:ss.fff} Transmit-Done", DateTime.Now);
			}
		}

		public Byte RegisterReadByte(byte address)
		{
			byte[] writeBuffer = new byte[] { address &amp;= RegisterAddressReadMask };
			byte[] readBuffer = new byte[1];
			Debug.Assert(Rfm69Hcw != null);

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

			return readBuffer[0];
		}

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

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

			return readBuffer;
		}

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

			Rfm69Hcw.Write(writeBuffer);
		}

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

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

			Rfm69Hcw.Write(writeBuffer);
		}

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

				Debug.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", registerIndex, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));
			}
		}
	}


	public sealed class StartupTask : IBackgroundTask
	{
		private const int ChipSelectLine = 1;
		private const int ResetPin = 25;
		private const int InterruptPin = 22;
		private Rfm69HcwDevice rfm69Device = new Rfm69HcwDevice(ChipSelectLine, ResetPin, InterruptPin);

		const double RH_RF6M9HCW_FXOSC = 32000000.0;
		const double RH_RFM69HCW_FSTEP = RH_RF6M9HCW_FXOSC / 524288.0;

		public void Run(IBackgroundTaskInstance taskInstance)
		{
			//rfm69Device.RegisterDump();

			// regOpMode standby
			rfm69Device.RegisterWriteByte(0x01, 0b00000100);

			// BitRate MSB/LSB
			rfm69Device.RegisterWriteByte(0x03, 0x34);
			rfm69Device.RegisterWriteByte(0x04, 0x00);

			// Frequency deviation
			rfm69Device.RegisterWriteByte(0x05, 0x02);
			rfm69Device.RegisterWriteByte(0x06, 0x3d);

			// Calculate the frequency accoring to the datasheett
			byte[] bytes = BitConverter.GetBytes((uint)(915000000.0 / RH_RFM69HCW_FSTEP));
			Debug.WriteLine("Byte Hex 0x{0:x2} 0x{1:x2} 0x{2:x2} 0x{3:x2}", bytes[0], bytes[1], bytes[2], bytes[3]);
			rfm69Device.RegisterWriteByte(0x07, bytes[2]);
			rfm69Device.RegisterWriteByte(0x08, bytes[1]);
			rfm69Device.RegisterWriteByte(0x09, bytes[0]);

			// RegRxBW
			rfm69Device.RegisterWriteByte(0x19, 0x2a);

			// RegDioMapping1
			rfm69Device.RegisterWriteByte(0x26, 0x01);

			// Setup preamble length to 16 (default is 3) RegPreambleMsb RegPreambleLsb
			rfm69Device.RegisterWriteByte(0x2C, 0x0);
			rfm69Device.RegisterWriteByte(0x2D, 0x10);

			// RegSyncConfig Set the Sync length and byte values SyncOn + 3 custom sync bytes
			rfm69Device.RegisterWriteByte(0x2e, 0x90);

			// RegSyncValues1 thru RegSyncValues3
			rfm69Device.RegisterWriteByte(0x2f, 0xAA);
			rfm69Device.RegisterWriteByte(0x30, 0x2D);
			rfm69Device.RegisterWriteByte(0x31, 0xD4);

			// RegPacketConfig1 Variable length with CRC on
			rfm69Device.RegisterWriteByte(0x37, 0x90);

			// Set the AES key and turn on AES RegPacketConfig2
			rfm69Device.RegisterWriteByte(0x3D, 0x03);
	
			rfm69Device.RegisterWriteByte(0x3E, 0x00);
			rfm69Device.RegisterWriteByte(0x3F, 0x01);
			rfm69Device.RegisterWriteByte(0x40, 0x02);
			rfm69Device.RegisterWriteByte(0x41, 0x03);
			rfm69Device.RegisterWriteByte(0x42, 0x04);
			rfm69Device.RegisterWriteByte(0x43, 0x05);
			rfm69Device.RegisterWriteByte(0x44, 0x06);
			rfm69Device.RegisterWriteByte(0x45, 0x07);
			rfm69Device.RegisterWriteByte(0x46, 0x08);
			rfm69Device.RegisterWriteByte(0x47, 0x09);
			rfm69Device.RegisterWriteByte(0x48, 0x0A);
			rfm69Device.RegisterWriteByte(0x49, 0x0B);
			rfm69Device.RegisterWriteByte(0x4A, 0x0C);
			rfm69Device.RegisterWriteByte(0x4B, 0x0D);
			rfm69Device.RegisterWriteByte(0x4C, 0x0E);
			rfm69Device.RegisterWriteByte(0x4D, 0x0F);
			
/*
			// Clear out the AES key
			rfm69Device.RegisterWriteByte(0x3E, 0x0);
			rfm69Device.RegisterWriteByte(0x3F, 0x0);
			rfm69Device.RegisterWriteByte(0x40, 0x0);
			rfm69Device.RegisterWriteByte(0x41, 0x0);
			rfm69Device.RegisterWriteByte(0x42, 0x0);
			rfm69Device.RegisterWriteByte(0x43, 0x0);
			rfm69Device.RegisterWriteByte(0x44, 0x0);
			rfm69Device.RegisterWriteByte(0x45, 0x0);
			rfm69Device.RegisterWriteByte(0x46, 0x0);
			rfm69Device.RegisterWriteByte(0x47, 0x0);
			rfm69Device.RegisterWriteByte(0x48, 0x0);
			rfm69Device.RegisterWriteByte(0x49, 0x0);
			rfm69Device.RegisterWriteByte(0x4A, 0x0);
			rfm69Device.RegisterWriteByte(0x4B, 0x0);
			rfm69Device.RegisterWriteByte(0x4C, 0x0);
			rfm69Device.RegisterWriteByte(0x4D, 0x0);
*/			

			rfm69Device.RegisterDump();

			while (true)
			{
				// Standby mode while loading message into FIFO
				rfm69Device.RegisterWriteByte(0x01, 0b00000100);

				byte[] messageBuffer = UTF8Encoding.UTF8.GetBytes("hello world " + DateTime.Now.ToLongTimeString());
				rfm69Device.RegisterWriteByte(0x0, (byte)messageBuffer.Length);
				rfm69Device.RegisterWrite(0x0, messageBuffer);

				// Transmit mode once FIFO loaded
				rfm69Device.RegisterWriteByte(0x01, 0b00001100);

				Debug.WriteLine("{0:HH:mm:ss.fff} Send-Done", DateTime.Now);

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

I could see inbound messages from the transmit Arduino and Windows 10 device interleaved on the receive Arduino.

21:06:12.735 -> RX start
21:06:12.735 -> 0x0: 0x0
21:06:12.769 -> 0x1: 0x10
21:06:12.769 -> 0x2: 0x0
…
21:06:13.453 -> 0x3B: 0x0
21:06:13.453 -> 0x3C: 0x1
21:06:13.487 -> 0x3D: 0x1
21:06:15.218 -> MessageIn:hello world 9:06:15 PM
21:06:20.317 -> MessageIn:hello world 9:06:20 PM
21:06:24.559 -> MessageIn:Hello world:0
21:06:25.384 -> MessageIn:hello world 9:06:25 PM
21:06:28.009 -> MessageIn:Hello world:1
21:06:30.454 -> MessageIn:hello world 9:06:30 PM
21:06:31.455 -> MessageIn:Hello world:2
21:06:34.939 -> MessageIn:Hello world:3
21:06:35.596 -> MessageIn:hello world 9:06:35 PM
21:06:38.389 -> MessageIn:Hello world:4
21:06:40.666 -> MessageIn:hello world 9:06:40 PM
21:06:41.838 -> MessageIn:Hello world:5
21:06:45.316 -> MessageIn:Hello world:6
21:06:45.731 -> MessageIn:hello world 9:06:45 PM
21:06:48.761 -> MessageIn:Hello world:7
21:06:50.799 -> MessageIn:hello world 9:06:50 PM
21:06:52.214 -> MessageIn:Hello world:8

The next step will be merging and refactoring the test harness to extract the code for accessing the RFM69 registers into a separate class, then defining enumerations and constants for all the RFM69 settings.

RFM69 hat library Part9

Addressing: Rasmatic/RFM69-Arduino-Library

The RFM69CW/RFM69HCW modules (based on the Semtech SX1231/SX1231H) have built in support for addressing individual devices (register RegNodeAdrs 0x39) or broadcasting to groups of devices (register RegBroadcastAdrs 0x3A). In this test harness I’m exploring the RFM69 device support for these two different addressing modes which is configured in RegPacketConfig1 0x37.

RFM69 Address filtering options

The fixed length packet format contains the following fields

  • Preamble (1010…)
  • Sync word (Network ID)
  • Optional Address byte (Node ID)
  • Message data
  • Optional 2-bytes CRC checksum
Fixed length packet format

The variable length packet format contains the following fields

  • Preamble (1010…)
  • Sync word (Network ID)
  • Length byte
  • Optional Address byte (Node ID)
  • Message data
  • Optional 2-bytes CRC checksum
Variable length packet format

My first attempt at addressing was by modifying the payload (the extra space at the start of the payload was replaced by the target device address)

void loop() 
{
  char messageIn[128] = {""};
  char messageOut[32]= {" Hello world:"};

  if (digitalRead(SENDER_DETECT_PIN) == LOW)
  {
    if(radio.bGetMessage(messageIn)!=0)
    { 
      Serial.print("MessageIn:");
      Serial.println(messageIn);
    }    
  }
  else
  {  
    Serial.print("MessageOut:") ;
    itoa(counter,&messageOut[strlen(messageOut)],10);
    Serial.print("(");
    Serial.print(messageOut);
    Serial.println(")");
    Serial.print("Length:") ;
    Serial.println(strlen(messageOut));

    messageOut[0]=0x99;
    if (!radio.bSendMessage(messageOut, strlen(messageOut)))
    {
      Serial.println("bSendMessage failed");
    }
    counter++;
    delay(1000);
  }

The rasmatic/RFM69-Arduino-Library doesn’t natively support sending addressed payloads so I had to add a method to test my Windows 10 IoT Core client.

Initially it truncated messages because I neglected to include the byte with the length of the message in the length of the message. I also had to extend the timeout for sending a message a bit more than I expected for one extra byte.

bool RMRFM69::bSendMessage(byte address, byte msg[], byte length)
{
byte tmp;
 uint32_t overtime;
 word bittime;

 switch(COB)
	{
	case RFM65:									//only for Rx
	case RFM65C:
		return(false);
	case RFM69H:
	case RFM69HC:
 		vSpiWrite(((word)RegTestPa1<<8)+0x5D);		//for HighPower
	 	vSpiWrite(((word)RegTestPa2<<8)+0x7C);
		break;
	default:
	case RFM69:
	case RFM69C:
	 	vSpiWrite(((word)RegTestPa1<<8)+0x55);		//for NormalMode or RxMode
 		vSpiWrite(((word)RegTestPa2<<8)+0x70);
		break;
	}
	
 vSpiWrite(((word)RegDioMapping1<<8)+0x04);	//DIO0 PacketSend  / DIO1 FiflLevel / DIO2 Data /DIO3 FifoFull
 
 if(!FixedPktLength)
 	vSpiWrite(((word)RegFifo<<8)+length+1);
 vSpiWrite(((word)RegFifo<<8)+address);
 
 vSpiBurstWrite(RegFifo, msg, length);
 
 tmp = bSpiRead(RegOpMode);
 tmp&amp;= MODE_MASK;
 tmp |= RADIO_TX;
 vSpiWrite(((word)RegOpMode<<8)+tmp);
  
 //�ȴ��������
 bittime  = SymbolTime/1000;		//unit: us
 overtime = SyncLength+PreambleLength+length+1;
 if(!FixedPktLength)				//SyncWord &amp; PktLength &amp; 2ByteCRC
    overtime += 1;
 if(!CrcDisable)
 	overtime += 2;
 overtime<<=3;					//8bit == 1byte
 overtime*= bittime;
 overtime/= 1000;				//unit: ms
 if(overtime==0) 
 	overtime = 1;
 overtime += (overtime&gt;&gt;3);		//add 12.5% for ensure
 delay(overtime);			//
 for(tmp=0;tmp<1000;tmp++)		//about 50ms for overtime
 	{
 	if(digitalRead(_dio0Pin))
 		break; 	
 	delayMicroseconds(500);
 	}
 vGoStandby();	
 if(tmp&gt;=200)
 	return(false);
 else
 	return(true);
}

The Windows 10 IoT Core library interrupt handler needed some modification to display message only when the address matched and I also displayed the targeted address so I could check that device and broadcast addressing was working

/*
    Copyright ® 2019 July devMobile Software, All Rights Reserved

	 MIT License

	 Permission is hereby granted, free of charge, to any person obtaining a copy
	 of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
	 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	 copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
	 copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	 SOFTWARE

 */
namespace devMobile.IoT.Rfm69Hcw.Addressing
{
	using System;
	using System.Diagnostics;
	using System.Runtime.InteropServices.WindowsRuntime;
	using System.Text;
	using System.Threading.Tasks;
	using Windows.ApplicationModel.Background;
	using Windows.Devices.Gpio;
	using Windows.Devices.Spi;

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

		public Rfm69HcwDevice(int chipSelectPin, int resetPin, int interruptPin)
		{
			SpiController spiController = SpiController.GetDefaultAsync().AsTask().GetAwaiter().GetResult();
			var settings = new SpiConnectionSettings(chipSelectPin)
			{
				ClockFrequency = 500000,
				Mode = SpiMode.Mode0,
			};

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

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

			InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;

			Rfm69Hcw = spiController.GetDevice(settings);
		}

		private void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
		{
			if (args.Edge != GpioPinEdge.RisingEdge)
			{
				return;
			}

			byte irqFlags2 = this.RegisterReadByte(0x28); // RegIrqFlags2
			Debug.WriteLine("{0:HH:mm:ss.fff} RegIrqFlags2 {1}", DateTime.Now, Convert.ToString((byte)irqFlags2, 2).PadLeft(8, '0'));
			if ((irqFlags2 &amp; 0b00000100) == 0b00000100)  // PayLoadReady set
			{
				byte irqFlags1 = this.RegisterReadByte(0x27); // RegIrqFlags1

				// Read the length of the buffer
				byte numberOfBytes = this.RegisterReadByte(0x0);

				Debug.WriteLine("{0:HH:mm:ss.fff} RegIrqFlags1 {1}", DateTime.Now, Convert.ToString((byte)irqFlags1, 2).PadLeft(8, '0'));
				if ((irqFlags1 &amp; 0b00000001) == 0b00000001)  // SyncAddressMatch
				{
					byte address = this.RegisterReadByte(0x0);
					Debug.WriteLine("{0:HH:mm:ss.fff} Address 0X{1:X2} b{2}", DateTime.Now, address, Convert.ToString((byte)address, 2).PadLeft(8, '0'));
					numberOfBytes--;
				}

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

				for (int i = 0; i < numberOfBytes; i++)
				{
					messageBytes[i] = this.RegisterReadByte(0x00); // RegFifo
				}

				string messageText = UTF8Encoding.UTF8.GetString(messageBytes);
				Debug.WriteLine("{0:HH:mm:ss} Received:{1} byte message({2})", DateTime.Now, messageBytes.Length, messageText);
			}

			if ((irqFlags2 &amp; 0b00001000) == 0b00001000)  // PacketSent set
			{
				this.RegisterWriteByte(0x01, 0b00010000); // RegOpMode set ReceiveMode
				Debug.WriteLine("{0:HH:mm:ss.fff} Transmit-Done", DateTime.Now);
			}
		}

		public Byte RegisterReadByte(byte address)
		{
			byte[] writeBuffer = new byte[] { address &amp;= RegisterAddressReadMask };
			byte[] readBuffer = new byte[1];
			Debug.Assert(Rfm69Hcw != null);

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

			return readBuffer[0];
		}

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

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

			return readBuffer;
		}

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

			Rfm69Hcw.Write(writeBuffer);
		}

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

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

			Rfm69Hcw.Write(writeBuffer);
		}

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

				Debug.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", registerIndex, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));
			}
		}
	}

	public sealed class StartupTask : IBackgroundTask
	{
		private const int ChipSelectLine = 1;
		private const int ResetPin = 25;
		private const int InterruptPin = 22;
		private Rfm69HcwDevice rfm69Device = new Rfm69HcwDevice(ChipSelectLine, ResetPin, InterruptPin);

		const double RH_RF6M9HCW_FXOSC = 32000000.0;
		const double RH_RFM69HCW_FSTEP = RH_RF6M9HCW_FXOSC / 524288.0;

		public void Run(IBackgroundTaskInstance taskInstance)
		{
			//rfm69Device.RegisterDump();

			// regOpMode standby
			rfm69Device.RegisterWriteByte(0x01, 0b00000100);

			// BitRate MSB/LSB
			rfm69Device.RegisterWriteByte(0x03, 0x34);
			rfm69Device.RegisterWriteByte(0x04, 0x00);

			// Frequency deviation
			rfm69Device.RegisterWriteByte(0x05, 0x02);
			rfm69Device.RegisterWriteByte(0x06, 0x3d);

			// Calculate the frequency accoring to the datasheett
			byte[] bytes = BitConverter.GetBytes((uint)(915000000.0 / RH_RFM69HCW_FSTEP));
			Debug.WriteLine("Byte Hex 0x{0:x2} 0x{1:x2} 0x{2:x2} 0x{3:x2}", bytes[0], bytes[1], bytes[2], bytes[3]);
			rfm69Device.RegisterWriteByte(0x07, bytes[2]);
			rfm69Device.RegisterWriteByte(0x08, bytes[1]);
			rfm69Device.RegisterWriteByte(0x09, bytes[0]);

			// RegRxBW
			rfm69Device.RegisterWriteByte(0x19, 0x2a);

			// RegDioMapping1
			rfm69Device.RegisterWriteByte(0x26, 0x01);

			// Setup preamble length to 16 (default is 3) RegPreambleMsb RegPreambleLsb
			rfm69Device.RegisterWriteByte(0x2C, 0x0);
			rfm69Device.RegisterWriteByte(0x2D, 0x10);

			// RegSyncConfig Set the Sync length and byte values SyncOn + 3 custom sync bytes
			rfm69Device.RegisterWriteByte(0x2e, 0x90);

			// RegSyncValues1 thru RegSyncValues3
			rfm69Device.RegisterWriteByte(0x2f, 0xAA);
			rfm69Device.RegisterWriteByte(0x30, 0x2D);
			rfm69Device.RegisterWriteByte(0x31, 0xD4);

			// RegPacketConfig1 Variable length with CRC on
			//rfm69Device.RegisterWriteByte(0x37, 0x90);

			// RegPacketConfig1 Variable length with CRC on + NodeAddress
			//rfm69Device.RegisterWriteByte(0x37, 0x92);

			// RegPacketConfig1 Variable length with CRC on + NodeAddress &amp; Broadcast Address
			rfm69Device.RegisterWriteByte(0x37, 0x94);

			// RegNodeAdrs 
			rfm69Device.RegisterWriteByte(0x39, 0x99);

			// RegBroadcastAdrs
			rfm69Device.RegisterWriteByte(0x3A, 0x66);

			rfm69Device.RegisterDump();

			rfm69Device.RegisterWriteByte(0x01, 0b00010000); // RegOpMode set ReceiveMode

			while (true)
			{
				Debug.Write(".");

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

The debug output window shows the flags and messages

Register dump
Register 0x00 - Value 0X00 - Bits 00000000
Register 0x01 - Value 0X04 - Bits 00000100
Register 0x02 - Value 0X00 - Bits 00000000
…
Register 0x3b - Value 0X00 - Bits 00000000
Register 0x3c - Value 0X0f - Bits 00001111
Register 0x3d - Value 0X02 - Bits 00000010
...........
15:58:16.931 RegIrqFlags2 01100110
15:58:17.096 RegIrqFlags1 11011001
15:58:17.118 Address 0X99 b10011001
15:58:17 Received:14 byte message( Hello world:0)
.15:58:18.009 RegIrqFlags2 01100110
15:58:18.024 RegIrqFlags1 11011001
15:58:18.039 Address 0X99 b10011001
15:58:18 Received:14 byte message( Hello world:1)
.15:58:19.146 RegIrqFlags2 01100110
15:58:19.161 RegIrqFlags1 11011001
15:58:19.176 Address 0X99 b10011001
15:58:19 Received:14 byte message( Hello world:2)
.15:58:20.284 RegIrqFlags2 01100110
15:58:20.299 RegIrqFlags1 11011001
15:58:20.313 Address 0X99 b10011001
15:58:20 Received:14 byte message( Hello world:3)
.15:58:21.421 RegIrqFlags2 01100110
.15:58:21.454 RegIrqFlags1 11011001
15:58:21.469 Address 0X99 b10011001
15:58:21 Received:14 byte message( Hello world:4)
....

The next steps will be getting the RFM69 message encryption going, then building a fully featured library based on the code in each of individual test harnesses.

RFM69 hat library Part8

ReceiveTransmit Interrupt: Rasmatic/RFM69-Arduino-Library

I started by merging the transmit and receive interrupt samples, taking into account the simplex RFM69HCW radio link. I modified the code in the interrupt handler to process receive and transmit interrupts based on bit flags set in RegIrqFlags2.

The receive interrupt handler loads the inbound message (Need to set CRC checking flag) into a buffer for display. The transmit interrupt handler sets RegOpMode to receive mode and enables PayloadReady interrupts as soon as the outbound message had been sent.

/*
    Copyright ® 2019 July devMobile Software, All Rights Reserved

	 MIT License

	 Permission is hereby granted, free of charge, to any person obtaining a copy
	 of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
	 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	 copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all
	 copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	 SOFTWARE

 */
namespace devMobile.IoT.Rfm69Hcw.ReceiveTransmitInterrupt
{
	using System;
	using System.Diagnostics;
	using System.Runtime.InteropServices.WindowsRuntime;
	using System.Text;
	using System.Threading.Tasks;
	using Windows.ApplicationModel.Background;
	using Windows.Devices.Gpio;
	using Windows.Devices.Spi;

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

		public Rfm69HcwDevice(int chipSelectPin, int resetPin, int interruptPin)
		{
			SpiController spiController = SpiController.GetDefaultAsync().AsTask().GetAwaiter().GetResult();
			var settings = new SpiConnectionSettings(chipSelectPin)
			{
				ClockFrequency = 500000,
				Mode = SpiMode.Mode0,
			};

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

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

			InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;

			Rfm69Hcw = spiController.GetDevice(settings);
		}

		private void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
		{
			if (args.Edge != GpioPinEdge.RisingEdge)
			{
				return;
			}

			byte irqFlags = this.RegisterReadByte(0x28); // RegIrqFlags2
			Debug.WriteLine("{0:HH:mm:ss.fff} RegIrqFlags {1}", DateTime.Now, Convert.ToString((byte)irqFlags, 2).PadLeft(8, '0'));
			if ((irqFlags &amp; 0b00000100) == 0b00000100)  // PayLoadReady set
			{
				// Read the length of the buffer
				byte numberOfBytes = this.RegisterReadByte(0x0);

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

				for (int i = 0; i < numberOfBytes; i++)
				{
					messageBytes[i] = this.RegisterReadByte(0x00); // RegFifo
				}

				string messageText = UTF8Encoding.UTF8.GetString(messageBytes);
				Debug.WriteLine("{0:HH:mm:ss} Received {1} byte message {2}", DateTime.Now, messageBytes.Length, messageText);
			}

			if ((irqFlags &amp; 0b00001000) == 0b00001000)  // PacketSent set
			{
				this.RegisterWriteByte(0x01, 0b00010000); // RegOpMode set ReceiveMode
				Debug.WriteLine("{0:HH:mm:ss.fff} Transmit-Done", DateTime.Now);
			}
		}

		public Byte RegisterReadByte(byte address)
		{
			byte[] writeBuffer = new byte[] { address &amp;= RegisterAddressReadMask };
			byte[] readBuffer = new byte[1];
			Debug.Assert(Rfm69Hcw != null);

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

			return readBuffer[0];
		}

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

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

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

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

			Rfm69Hcw.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(Rfm69Hcw != null);

			Rfm69Hcw.Write(writeBuffer);
		}

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

				Debug.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", registerIndex, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));
			}
		}
	}


	public sealed class StartupTask : IBackgroundTask
	{
		private const int ChipSelectLine = 1;
		private const int ResetPin = 25;
		private const int InterruptPin = 22;
		private Rfm69HcwDevice rfm69Device = new Rfm69HcwDevice(ChipSelectLine, ResetPin, InterruptPin);

		const double RH_RF6M9HCW_FXOSC = 32000000.0;
		const double RH_RFM69HCW_FSTEP = RH_RF6M9HCW_FXOSC / 524288.0;

		public void Run(IBackgroundTaskInstance taskInstance)
		{
			//rfm69Device.RegisterDump();

			// regOpMode standby
			rfm69Device.RegisterWriteByte(0x01, 0b00000100);

			// BitRate MSB/LSB
			rfm69Device.RegisterWriteByte(0x03, 0x34);
			rfm69Device.RegisterWriteByte(0x04, 0x00);

			// Frequency deviation
			rfm69Device.RegisterWriteByte(0x05, 0x02);
			rfm69Device.RegisterWriteByte(0x06, 0x3d);

			// Calculate the frequency accoring to the datasheett
			byte[] bytes = BitConverter.GetBytes((uint)(915000000.0 / RH_RFM69HCW_FSTEP));
			Debug.WriteLine("Byte Hex 0x{0:x2} 0x{1:x2} 0x{2:x2} 0x{3:x2}", bytes[0], bytes[1], bytes[2], bytes[3]);
			rfm69Device.RegisterWriteByte(0x07, bytes[2]);
			rfm69Device.RegisterWriteByte(0x08, bytes[1]);
			rfm69Device.RegisterWriteByte(0x09, bytes[0]);

			// RegRxBW
			rfm69Device.RegisterWriteByte(0x19, 0x2a);

			// RegDioMapping1
			rfm69Device.RegisterWriteByte(0x26, 0x01);

			// Setup preamble length to 16 (default is 3) RegPreambleMsb RegPreambleLsb
			rfm69Device.RegisterWriteByte(0x2C, 0x0);
			rfm69Device.RegisterWriteByte(0x2D, 0x10);

			// RegSyncConfig Set the Sync length and byte values SyncOn + 3 custom sync bytes
			rfm69Device.RegisterWriteByte(0x2e, 0x90);

			// RegSyncValues1 thru RegSyncValues3
			rfm69Device.RegisterWriteByte(0x2f, 0xAA);
			rfm69Device.RegisterWriteByte(0x30, 0x2D);
			rfm69Device.RegisterWriteByte(0x31, 0xD4);

			// RegPacketConfig1 Variable length with CRC on
			rfm69Device.RegisterWriteByte(0x37, 0x90);

			rfm69Device.RegisterDump();

			while (true)
			{
				// Standby mode while loading message into FIFO
				rfm69Device.RegisterWriteByte(0x01, 0b00000100);

				byte[] messageBuffer = UTF8Encoding.UTF8.GetBytes("hello world " + DateTime.Now.ToLongTimeString());
				rfm69Device.RegisterWriteByte(0x0, (byte)messageBuffer.Length);
				rfm69Device.RegisterWrite(0x0, messageBuffer);

				// Transmit mode once FIFO loaded
				rfm69Device.RegisterWriteByte(0x01, 0b00001100);

				Debug.WriteLine("{0:HH:mm:ss.fff} Send-Done", DateTime.Now);

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

Arduino based transmit and receive test rig

I used the same Arduino devices and code as my receive and transmit samples. After sorting out the timing for enabling receive mode on the Windows 10IoT Core the transmission and receiving of packets was reliable (as long as two devices weren’t transmitting at the same time).

Byte Hex 0x00 0xc0 0xe4 0x00
Register dump
Register 0x00 - Value 0X00 - Bits 00000000
Register 0x01 - Value 0X04 - Bits 00000100
Register 0x02 - Value 0X00 - Bits 00000000
…
Register 0x3b - Value 0X00 - Bits 00000000
Register 0x3c - Value 0X0f - Bits 00001111
Register 0x3d - Value 0X02 - Bits 00000010
20:20:14.952 Send-Done
20:20:15.146 RegIrqFlags 00001000
20:20:15.160 Transmit-Done
20:20:15.298 RegIrqFlags 01000110
20:20:15 Received 14 byte message Hello world:89
20:20:17.429 RegIrqFlags 01000110
20:20:17 Received 14 byte message Hello world:90
The thread 0x1658 has exited with code 0 (0x0).
The thread 0x15cc has exited with code 0 (0x0).
20:20:19.560 RegIrqFlags 01000110
20:20:19 Received 14 byte message Hello world:91
20:20:20.031 Send-Done
20:20:20.178 RegIrqFlags 00001000
20:20:20.194 Transmit-Done

The above trace is from the Windows 10 IoT Core device as it transmits and receives messages.

20:24:36.311 -> RX start
20:24:36.345 -> 0x0: 0x0
20:24:36.345 -> 0x1: 0x10
20:24:36.345 -> 0x2: 0x0
20:24:36.380 -> 0x3: 0x34
...
20:24:37.028 -> 0x3B: 0x0
20:24:37.028 -> 0x3C: 0x1
20:24:37.063 -> 0x3D: 0x0
20:24:38.024 -> MessageIn:Hello world:212
20:24:38.848 -> MessageIn:hello world 8:24:38 PM
20:24:40.156 -> MessageIn:Hello world:213
20:24:42.293 -> MessageIn:Hello world:214
20:24:43.914 -> MessageIn:hello world 8:24:43 PM
20:24:44.427 -> MessageIn:Hello world:215
20:24:46.567 -> MessageIn:Hello world:216
20:24:48.674 -> MessageIn:Hello world:217
20:24:48.984 -> MessageIn:hello world 8:24:48 PM
20:24:50.841 -> MessageIn:Hello world:218
20:24:52.943 -> MessageIn:Hello world:219
20:24:54.044 -> MessageIn:hello world 8:24:53 PM
20:24:55.100 -> MessageIn:Hello world:220
20:24:57.235 -> MessageIn:Hello world:221
20:24:59.133 -> MessageIn:hello world 8:24:58 PM
20:24:59.377 -> MessageIn:Hello world:222
20:25:01.496 -> MessageIn:Hello world:223
20:25:03.655 -> MessageIn:Hello world:224
20:25:04.176 -> MessageIn:hello world 8:25:04 PM
20:25:05.777 -> MessageIn:Hello world:225
20:25:07.927 -> MessageIn:Hello world:226
20:25:09.281 -> MessageIn:hello world 8:25:09 PM
20:25:10.048 -> MessageIn:Hello world:227
20:25:12.208 -> MessageIn:Hello world:228
20:25:14.352 -> MessageIn:hello world 8:25:14 PM

The above trace is from the Arduino device configured to receive messages and it is receiving messages from the Windows 10 IoT Core device and the other Arduino.

RFM69 hat library Part7

Transmit Interrupt: Rasmatic/RFM69-Arduino-Library

I started with the transmit basic code, the first step was to add a parameter to the constructor for the interrupt pin connected to the RFM69HCW (based on Receive Interrupt code). I then added a Debug.Writeline my new interrupt handler to show when the message had been sent.

The Adafruit Radio Bonnet has pin 22 connected to the DIO0 pin the RFM69HCW so I set the RegDioMapping1 register to trigger DIO0 on PayloadSent. (Ignoring other flags)

/*
 Copyright ® 2019 June devMobile Software, All Rights Reserved

 MIT License

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE

*/
namespace devMobile.IoT.Rfm69Hcw.TransmitInterrupt
{
	using System;
	using System.Diagnostics;
	using System.Runtime.InteropServices.WindowsRuntime;
	using System.Text;
	using System.Threading.Tasks;
	using Windows.ApplicationModel.Background;
	using Windows.Devices.Spi;
	using Windows.Devices.Gpio;


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

		public Rfm69HcwDevice(int chipSelectPin, int resetPin, int interruptPin)
		{
			SpiController spiController = SpiController.GetDefaultAsync().AsTask().GetAwaiter().GetResult();
			var settings = new SpiConnectionSettings(chipSelectPin)
			{
				ClockFrequency = 500000,
				Mode = SpiMode.Mode0,
			};


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

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

			InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;

			Rfm69Hcw = spiController.GetDevice(settings);
		}

		private void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
		{
			if (args.Edge != GpioPinEdge.RisingEdge)
			{
				return;
			}

			byte irqFlags = this.RegisterReadByte(0x28); // RegIrqFlags2
			//Debug.WriteLine("{0:HH:mm:ss.fff} RegIrqFlags {1}", DateTime.Now, Convert.ToString((byte)irqFlags, 2).PadLeft(8, '0'));
			if ((irqFlags &amp; 0b00001000) == 0b00001000)  // PacketSent set
			{
				Debug.WriteLine("{0:HH:mm:ss.fff} Transmit-Done", DateTime.Now);
			}
		}

		public Byte RegisterReadByte(byte address)
		{
			byte[] writeBuffer = new byte[] { address &amp;= RegisterAddressReadMask };
			byte[] readBuffer = new byte[1];
			Debug.Assert(Rfm69Hcw != null);

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

			return readBuffer[0];
		}

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

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

			return readBuffer;
		}

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

			Rfm69Hcw.Write(writeBuffer);
		}

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

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

			Rfm69Hcw.Write(writeBuffer);
		}

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

				Debug.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", registerIndex, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));
			}
		}
	}


	public sealed class StartupTask : IBackgroundTask
	{
		private const int ChipSelectPin = 1;
		private const int ResetPin = 25;
		private const int InterruptPin = 22;
		private Rfm69HcwDevice rfm69Device = new Rfm69HcwDevice(ChipSelectPin, ResetPin, InterruptPin);

		const double RH_RF6M9HCW_FXOSC = 32000000.0;
		const double RH_RFM69HCW_FSTEP = RH_RF6M9HCW_FXOSC / 524288.0;

		const byte NetworkID = 100;
		const byte NodeAddressFrom = 0x03;
		const byte NodeAddressTo = 0x02;


		public void Run(IBackgroundTaskInstance taskInstance)
		{
			//rfm69Device.RegisterDump();

			// regOpMode standby
			rfm69Device.RegisterWriteByte(0x01, 0b00000100);

			// BitRate MSB/LSB
			rfm69Device.RegisterWriteByte(0x03, 0x34);
			rfm69Device.RegisterWriteByte(0x04, 0x00);

			// Frequency deviation
			rfm69Device.RegisterWriteByte(0x05, 0x02);
			rfm69Device.RegisterWriteByte(0x06, 0x3d);

			// Calculate the frequency accoring to the datasheett
			byte[] bytes = BitConverter.GetBytes((uint)(915000000.0 / RH_RFM69HCW_FSTEP));
			Debug.WriteLine("Byte Hex 0x{0:x2} 0x{1:x2} 0x{2:x2} 0x{3:x2}", bytes[0], bytes[1], bytes[2], bytes[3]);
			rfm69Device.RegisterWriteByte(0x07, bytes[2]);
			rfm69Device.RegisterWriteByte(0x08, bytes[1]);
			rfm69Device.RegisterWriteByte(0x09, bytes[0]);

			// RegRxBW
			rfm69Device.RegisterWriteByte(0x19, 0x55);

			// RegDioMapping1 (Table 22 in the RFMHCW Datasheet)
			rfm69Device.RegisterWriteByte(0x26, 0x00);

			// Setup preamble length to 16 (default is 3)
			rfm69Device.RegisterWriteByte(0x2C, 0x0);
			rfm69Device.RegisterWriteByte(0x2D, 0x10);

			// Set the Sync length and byte values SyncOn + 3 custom sync bytes
			rfm69Device.RegisterWriteByte(0x2e, 0x90);

			rfm69Device.RegisterWriteByte(0x2f, 0xAA);
			rfm69Device.RegisterWriteByte(0x30, 0x2D);
			rfm69Device.RegisterWriteByte(0x31, 0xD4);

			// RegPacketConfig1 changed for Variable length after 9:00PM vs 10:00PM fail
			rfm69Device.RegisterWriteByte(0x37, 0x90);
			//rfm69Device.RegisterWriteByte(0x38, 0x14);

			rfm69Device.RegisterDump();

			while (true)
			{
				// Standby mode while loading message into FIFO
				rfm69Device.RegisterWriteByte(0x01, 0b00000100);

				byte[] messageBuffer = UTF8Encoding.UTF8.GetBytes(" hello world " + DateTime.Now.ToLongTimeString());
				messageBuffer[0] = (byte)messageBuffer.Length;
				rfm69Device.RegisterWrite(0x0, messageBuffer);

				// Transmit mode once FIFO loaded
				rfm69Device.RegisterWriteByte(0x01, 0b00001100);

				Debug.WriteLine("{0:HH:mm:ss.fff} Send-Done", DateTime.Now);

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

I used the same Arduino device and code as my polled transmit sample and it worked first time. Which is very unusually and worries me as my code never works first time.

Register dump
Register 0x00 - Value 0X00 - Bits 00000000
Register 0x01 - Value 0X04 - Bits 00000100
Register 0x02 - Value 0X00 - Bits 00000000
…
Register 0x3b - Value 0X00 - Bits 00000000
Register 0x3c - Value 0X0f - Bits 00001111
Register 0x3d - Value 0X02 - Bits 00000010
20:44:41.152 Send-Done
20:44:41.343 Transmit-Done
The thread 0xca4 has exited with code 0 (0x0).
The thread 0x9e0 has exited with code 0 (0x0).
20:44:46.205 Send-Done
20:44:46.356 Transmit-Done
20:44:51.242 Send-Done
20:44:51.393 Transmit-Done
The thread 0x1238 has exited with code 0 (0x0).
20:44:56.323 Send-Done
20:44:56.473 Transmit-Done
The thread 0x41c has exited with code 0 (0x0).
20:45:01.389 Send-Done
20:45:01.540 Transmit-Done
The thread 0xaec has exited with code 0 (0x0).
20:45:06.459 Send-Done
20:45:06.610 Transmit-Done
The thread 0x1440 has exited with code 0 (0x0).
20:45:11.535 Send-Done
20:45:11.686 Transmit-Done

I ran the Windows 10 IoT Core Transmit Interrupt client for half an hour (no memory allocation etc. so in theory less to go wrong) and it didn’t appear to drop any send completion interrupts.

I then added the decimal seconds value so I could work out how long it takes for a message to get sent, which is roughly 150mSec.

RFM69 hat library Part6

Receive Interrupt: Rasmatic/RFM69-Arduino-Library

I started with the receive basic code, the first step was to add a parameter to the constructor for the interrupt pin connected to the RFM69HCW and configuring the GPIO pin. I then moved the code for getting the message payload from the device FIFO to my new interrupt handler.

The Adafruit Radio Bonnet has pin 22 connected to the DIO0 pin the RFM69HCW so I set the RegDioMapping1 register to trigger DIO0 on PayloadReady. (Ignored other flags)

/*
 Copyright ® 2019 June devMobile Software, All Rights Reserved

 MIT License

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE

*/
namespace devMobile.IoT.Rfm69Hcw.ReceiveInterrupt
{
	using System;
	using System.Diagnostics;
	using System.Runtime.InteropServices.WindowsRuntime;
	using System.Text;
	using System.Threading.Tasks;
	using Windows.ApplicationModel.Background;
	using Windows.Devices.Gpio;
	using Windows.Devices.Spi;

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

		public Rfm69HcwDevice(int chipSelectPin, int resetPin, int interruptPin)
		{
			SpiController spiController = SpiController.GetDefaultAsync().AsTask().GetAwaiter().GetResult();
			var settings = new SpiConnectionSettings(chipSelectPin)
			{
				ClockFrequency = 500000,
				Mode = SpiMode.Mode0,
			};

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

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

			InterruptGpioPin.ValueChanged += InterruptGpioPin_ValueChanged;

			Rfm69Hcw = spiController.GetDevice(settings);
		}

		private void InterruptGpioPin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
		{
			if (args.Edge != GpioPinEdge.RisingEdge)
			{
				return;
			}

			byte irqFlags = this.RegisterReadByte(0x28); // RegIrqFlags2
			Debug.WriteLine("{0:HH:mm:ss} RegIrqFlags {1}", DateTime.Now, Convert.ToString((byte)irqFlags, 2).PadLeft(8, '0'));
			if ((irqFlags &amp; 0b00000100) == 0b00000100)  // PayLoadReady set
			{
				// Read the length of the buffer
				byte numberOfBytes = this.RegisterReadByte(0x0);

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

				for (int i = 0; i < numberOfBytes; i++)
				{
					messageBytes[i] = this.RegisterReadByte(0x00); // RegFifo
				}

				string messageText = UTF8Encoding.UTF8.GetString(messageBytes);
				Debug.WriteLine("{0:HH:mm:ss} Received {1} byte message {2}", DateTime.Now, messageBytes.Length, messageText);
			}
		}

		public Byte RegisterReadByte(byte address)
		{
			byte[] writeBuffer = new byte[] { address &amp;= RegisterAddressReadMask };
			byte[] readBuffer = new byte[1];
			Debug.Assert(Rfm69Hcw != null);

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

			return readBuffer[0];
		}

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

			Rfm69Hcw.TransferSequential(writeBuffer, readBuffer);

			return readBuffer;
		}

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

			Rfm69Hcw.Write(writeBuffer);
		}

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

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

			Rfm69Hcw.Write(writeBuffer);
		}

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

				Debug.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", registerIndex, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));
			}
		}
	}


	public sealed class StartupTask : IBackgroundTask
	{
		private const int ChipSelectLine = 1;
		private const int ResetPin = 25;
		private const int InterruptPin = 22;
		private Rfm69HcwDevice rfm69Device = new Rfm69HcwDevice(ChipSelectLine, ResetPin, InterruptPin);

		const double RH_RF6M9HCW_FXOSC = 32000000.0;
		const double RH_RFM69HCW_FSTEP = RH_RF6M9HCW_FXOSC / 524288.0;

		public void Run(IBackgroundTaskInstance taskInstance)
		{
			//rfm69Device.RegisterDump();

			// regOpMode standby
			rfm69Device.RegisterWriteByte(0x01, 0b00000100);

			// BitRate MSB/LSB
			rfm69Device.RegisterWriteByte(0x03, 0x34);
			rfm69Device.RegisterWriteByte(0x04, 0x00);

			// Calculate the frequency accoring to the datasheett
			byte[] bytes = BitConverter.GetBytes((uint)(915000000.0 / RH_RFM69HCW_FSTEP));
			Debug.WriteLine("Byte Hex 0x{0:x2} 0x{1:x2} 0x{2:x2} 0x{3:x2}", bytes[0], bytes[1], bytes[2], bytes[3]);
			rfm69Device.RegisterWriteByte(0x07, bytes[2]);
			rfm69Device.RegisterWriteByte(0x08, bytes[1]);
			rfm69Device.RegisterWriteByte(0x09, bytes[0]);

			// RegRxBW
			rfm69Device.RegisterWriteByte(0x19, 0x2a);

			// RegDioMapping1
			rfm69Device.RegisterWriteByte(0x26, 0x01);

			// Setup preamble length to 16 (default is 3) RegPreambleMsb RegPreambleLsb
			rfm69Device.RegisterWriteByte(0x2C, 0x0);
			rfm69Device.RegisterWriteByte(0x2D, 0x10);

			// RegSyncConfig Set the Sync length and byte values SyncOn + 3 custom sync bytes
			rfm69Device.RegisterWriteByte(0x2e, 0x90);

			// RegSyncValues1 thru RegSyncValues3
			rfm69Device.RegisterWriteByte(0x2f, 0xAA);
			rfm69Device.RegisterWriteByte(0x30, 0x2D);
			rfm69Device.RegisterWriteByte(0x31, 0xD4);

			// RegPacketConfig1 Variable length with CRC on
			rfm69Device.RegisterWriteByte(0x37, 0x90);

			rfm69Device.RegisterWriteByte(0x01, 0b00010000); // RegOpMode set ReceiveMode

			rfm69Device.RegisterDump();

			Debug.WriteLine("Receive-Wait");
			Task.Delay(-1).Wait();
		}
	}
}

I used the same Arduino device and code as my polled receive sample and after I fixing a couple of typos in my code…

Register dump
Register 0x00 - Value 0X00 - Bits 00000000
Register 0x01 - Value 0X10 - Bits 00010000
...
Register 0x3b - Value 0X00 - Bits 00000000
Register 0x3c - Value 0X0f - Bits 00001111
Register 0x3d - Value 0X02 - Bits 00000010
Receive-Wait
The thread 0xe7c has exited with code 0 (0x0).
The thread 0x9dc has exited with code 0 (0x0).
13:45:56 RegIrqFlags 01000110
13:45:56 Received 13 byte message Hello world:0
13:45:58 RegIrqFlags 01000110
13:45:58 Received 13 byte message Hello world:1
13:46:00 RegIrqFlags 01000110
13:46:00 Received 13 byte message Hello world:2
13:46:02 RegIrqFlags 01000110
13:46:02 Received 13 byte message Hello world:3
13:46:04 RegIrqFlags 01000110
13:46:04 Received 13 byte message Hello world:4

I ran the Windows 10 IoT Core client for several hours and it didn’t appear to drop any messages or have any other issues.

RFM69 hat library Part4C

Transmit Basic: Rasmatic/RFM69-Arduino-Library

While I was searching for a suitable library on GitHub I downloaded the RFM-Arduino-Library by Rasmatic which had a link to sample library on the HopeRF website which I also downloaded.

/*
@author Tadeusz Studnik https://rasmatic.pl

MIT License
...

This library is a port of HopeRF's library:

https://www.hoperf.com/data/upload/back/20181122/HoepRF_HSP_V1.3.rar
*/

I made the minimum possible modifications to the C/C++ code to get it to compile, then to run on my Arduino Nano Radio Shield RFM69/95 device. I had to change the RFM69 DIO pin mode, the SPI config, and I added a method to dump all the registers.

/**********************************************************
**Name:     vInitialize
**Function: initialize rfm69 or rfm69c
**Input:    none
**Output:   none
**********************************************************/
void RMRFM69::vInitialize(void)
{
	pinMode (_csPin, OUTPUT);
	pinMode (_rstPin, OUTPUT);
	pinMode (_dio0Pin, INPUT_PULLDOWN); // Changed from INPUT_PULLDOWN

	digitalWrite(_csPin, HIGH);
	digitalWrite(_rstPin, LOW);
	
	vSpiInit();
	
	//�˿ڳ�ʼ�� for 32MHz
	FrequencyValue.Freq = (Frequency << 11) / 125; //Calc. Freq
	BitRateValue = (SymbolTime << 5) / 1000;	   //Calc. BitRate
	DevationValue = (Devation << 11) / 125;		   //Calc. Fdev
	BandWidthValue = bSelectBandwidth(BandWidth);

	vConfig();
	vGoStandby();
}
/**********************************************************
**Name:     vSpiInit
**Function: init SPI
**Input:    none
**Output:   none
**********************************************************/
void RMRFM69::vSpiInit()
{
	digitalWrite(_csPin, HIGH);
//	_spiPort-&gt;setFrequency(1000000); 
	_spiPort-&gt;setBitOrder(MSBFIRST);
	_spiPort-&gt;setDataMode(SPI_MODE0);
	_spiPort-&gt;begin();

}

void RMRFM69::dumpRegisters(Stream&amp; out)
{
  for (int i = 0; i <= 0x3d; i++) {
    out.print("0x");
    out.print(i, HEX);
    out.print(": 0x");
    out.println(this-&gt;bSpiRead(i), HEX);
  }
}

I created an application based on the RFM69-ESP32-arduino-example which received messages.

#include <SPI.h&gt;
#include <RMRFM69.h&gt;

RMRFM69 radio(SPI, 10, 2, 9);

void setup() 
{
  Serial.begin(9600);
  
  radio.Modulation     = FSK;
  radio.COB            = RFM69;
  radio.Frequency      = 915000;
  radio.OutputPower    = 10+18;          //10dBm OutputPower
  radio.PreambleLength = 16;             //16Byte preamble
  radio.FixedPktLength = false;          //packet in message which need to be send
  radio.CrcDisable     = false;          //CRC On
  radio.AesOn          = false;
  radio.SymbolTime     = 416000;         //2.4Kbps
  radio.Devation       = 35;             //35KHz for devation
  radio.BandWidth      = 100;            //100KHz for bandwidth
  radio.SyncLength     = 3;              //
  radio.SyncWord[0]    = 0xAA;
  radio.SyncWord[1]    = 0x2D;
  radio.SyncWord[2]    = 0xD4;

  radio.vInitialize();

  radio.dumpRegisters(Serial);
  radio.vGoRx();

  Serial.println("Start RX...");
}

void loop() 
{
  char messageIn[128] = {""};
  byte messageOut[] = {"Hello world"};

  if(radio.bGetMessage(messageIn)!=0)
  { 
    Serial.print("MessageIn:");
    Serial.print(messageIn);
    Serial.println();
  }    
}

The application started up after I sorted out the RFM69 chip select, interrupt and reset pin numbers.

20:03:56.574 -> 0x0: 0x0
20:03:56.608 -> 0x1: 0x4
20:03:56.608 -> 0x2: 0x0
20:03:56.608 -> 0x3: 0x34
20:03:56.643 -> 0x4: 0x0
20:03:56.643 -> 0x5: 0x2
20:03:56.643 -> 0x6: 0x3D
20:03:56.643 -> 0x7: 0xE4
20:03:56.677 -> 0x8: 0xC0
20:03:56.677 -> 0x9: 0x0
20:03:56.677 -> 0xA: 0x41
20:03:56.710 -> 0xB: 0x40
20:03:56.710 -> 0xC: 0x2
20:03:56.710 -> 0xD: 0x92
20:03:56.745 -> 0xE: 0xF5
20:03:56.745 -> 0xF: 0x20
20:03:56.745 -> 0x10: 0x24
20:03:56.779 -> 0x11: 0x9C
20:03:56.779 -> 0x12: 0x5
20:03:56.813 -> 0x13: 0xF
20:03:56.813 -> 0x14: 0x40
20:03:56.813 -> 0x15: 0xB0
20:03:56.846 -> 0x16: 0x7B
20:03:56.846 -> 0x17: 0x9B
20:03:56.846 -> 0x18: 0x88
20:03:56.880 -> 0x19: 0x2A
20:03:56.880 -> 0x1A: 0x2A
20:03:56.880 -> 0x1B: 0x78
20:03:56.880 -> 0x1C: 0x80
20:03:56.914 -> 0x1D: 0x6
20:03:56.914 -> 0x1E: 0x10
20:03:56.947 -> 0x1F: 0x0
20:03:56.947 -> 0x20: 0x0
20:03:56.947 -> 0x21: 0x0
20:03:56.981 -> 0x22: 0x0
20:03:56.981 -> 0x23: 0x2
20:03:56.981 -> 0x24: 0xFF
20:03:57.015 -> 0x25: 0x0
20:03:57.015 -> 0x26: 0xF7
20:03:57.049 -> 0x27: 0x80
20:03:57.049 -> 0x28: 0x0
20:03:57.049 -> 0x29: 0xFF
20:03:57.083 -> 0x2A: 0x0
20:03:57.083 -> 0x2B: 0x0
20:03:57.083 -> 0x2C: 0x0
20:03:57.118 -> 0x2D: 0x10
20:03:57.118 -> 0x2E: 0x90
20:03:57.152 -> 0x2F: 0xAA
20:03:57.152 -> 0x30: 0x2D
20:03:57.152 -> 0x31: 0xD4
20:03:57.152 -> 0x32: 0x0
20:03:57.186 -> 0x33: 0x0
20:03:57.186 -> 0x34: 0x0
20:03:57.186 -> 0x35: 0x0
20:03:57.219 -> 0x36: 0x0
20:03:57.219 -> 0x37: 0x90
20:03:57.219 -> 0x38: 0x40
20:03:57.253 -> 0x39: 0x0
20:03:57.253 -> 0x3A: 0x0
20:03:57.253 -> 0x3B: 0x0
20:03:57.288 -> 0x3C: 0x1
20:03:57.288 -> 0x3D: 0x0
20:03:57.322 -> Start RX...

I then manually set the RFM69HCW Radio Bonnet registers to match the Arduino device.

public sealed class StartupTask : IBackgroundTask
{
	private const int ChipSelectLine = 1;
	private const int ResetLine = 25;
	private Rfm69HcwDevice rfm69Device = new Rfm69HcwDevice(ChipSelectLine, ResetLine);

	const double RH_RF6M9HCW_FXOSC = 32000000.0;
	const double RH_RFM69HCW_FSTEP = RH_RF6M9HCW_FXOSC / 524288.0;

	const byte NetworkID = 100;
	const byte NodeAddressFrom = 0x03;
	const byte NodeAddressTo = 0x02;

	public void Run(IBackgroundTaskInstance taskInstance)
	{
		//rfm69Device.RegisterDump();

		// regOpMode standby
		rfm69Device.RegisterWriteByte(0x01, 0b00000100);

		// BitRate MSB/LSB
		rfm69Device.RegisterWriteByte(0x03, 0x34);
		rfm69Device.RegisterWriteByte(0x04, 0x00);

		// Frequency deviation
		rfm69Device.RegisterWriteByte(0x05, 0x02);
		rfm69Device.RegisterWriteByte(0x06, 0x3d);
			
		// Calculate the frequency accoring to the datasheett
		byte[] bytes = BitConverter.GetBytes((uint)(915000000.0 / RH_RFM69HCW_FSTEP));
		Debug.WriteLine("Byte Hex 0x{0:x2} 0x{1:x2} 0x{2:x2} 0x{3:x2}", bytes[0], bytes[1], bytes[2], bytes[3]);
		rfm69Device.RegisterWriteByte(0x07, bytes[2]);
		rfm69Device.RegisterWriteByte(0x08, bytes[1]);
		rfm69Device.RegisterWriteByte(0x09, bytes[0]);

		// RegRxBW
		rfm69Device.RegisterWriteByte(0x19, 0x55);
		// RegAfcBw
		rfm69Device.RegisterWriteByte(0x1A, 0x8b);
		// RegOokPeak
		rfm69Device.RegisterWriteByte(0x1B, 0x40);

		// Setup preamble length to 16 (default is 3)
		rfm69Device.RegisterWriteByte(0x2C, 0x0);
		rfm69Device.RegisterWriteByte(0x2D, 0x10);
	
		// Set the Sync length and byte values SyncOn + 3 custom sync bytes
		rfm69Device.RegisterWriteByte(0x2e, 0x90);

		rfm69Device.RegisterWriteByte(0x2f, 0xAA);
		rfm69Device.RegisterWriteByte(0x30, 0x2D);
		rfm69Device.RegisterWriteByte(0x31, 0xD4);

		// RegPacketConfig1 changed for Variable length after 9:00PM vs 10:00PM fail
		rfm69Device.RegisterWriteByte(0x37, 0x90);
		//rfm69Device.RegisterWriteByte(0x38, 0x14);

		rfm69Device.RegisterDump();
	
		while (true)
		{
			// Standby mode while loading message into FIFO
			rfm69Device.RegisterWriteByte(0x01, 0b00000100);
			byte[] messageBuffer = UTF8Encoding.UTF8.GetBytes(" hello world " + DateTime.Now.ToLongTimeString());
			messageBuffer[0] = (byte)messageBuffer.Length;
			rfm69Device.RegisterWrite(0x0, messageBuffer);

			// Transmit mode once FIFO loaded
			rfm69Device.RegisterWriteByte(0x01, 0b00001100);

			// Wait until send done, no timeouts in PoC
			Debug.WriteLine("Send-wait");
			byte IrqFlags = rfm69Device.RegisterReadByte(0x28); // RegIrqFlags2
			while ((IrqFlags & 0b00001000) == 0)  // wait until TxDone cleared
			{
				Task.Delay(10).Wait();
				IrqFlags = rfm69Device.RegisterReadByte(0x28); // RegIrqFlags
				Debug.Write(".");
			}
			Debug.WriteLine("");

			// Standby mode while sleeping
			rfm69Device.RegisterWriteByte(0x01, 0b00000100);
			Debug.WriteLine($"{DateTime.Now.ToLongTimeString()}Send-Done");
			Task.Delay(5000).Wait();
		}
	}
}

My Arduino device then started receiving messages from my Raspberry PI 3 running Windows 10 IoT Core.

20:03:57.288 -> 0x3C: 0x1
20:03:57.288 -> 0x3D: 0x0
20:03:57.322 -> Start RX...
20:03:58.648 -> MessageIn:hello world 8:03:58 PM
20:04:03.920 -> MessageIn:hello world 8:04:03 PM
20:04:09.161 -> MessageIn:hello world 8:04:09 PM
20:04:14.421 -> MessageIn:hello world 8:04:14 PM
20:04:19.662 -> MessageIn:hello world 8:04:19 PM
20:04:24.895 -> MessageIn:hello world 8:04:24 PM
20:04:30.139 -> MessageIn:hello world 8:04:30 PM
20:04:35.392 -> MessageIn:hello world 8:04:35 PM
20:04:40.637 -> MessageIn:hello world 8:04:40 PM
20:04:45.890 -> MessageIn:hello world 8:04:45 PM
20:04:51.158 -> MessageIn:hello world 8:04:51 PM

Transmit is working! Though it’s starting to look like I might have to create my own lightweight Arduino RFM69HCW library “inspired” by the Arduino-LoRa library.

RFM69 hat library Part4B

Transmit Basic: iwanders/plainRFM69

My first Arduino client was based on the plainRFM69 library which looks fairly lightweight (it has in memory message queues). I started by adapting the plainRFM69 “Minimal” sample.

/*
 *  Copyright (c) 2014, Ivor Wanders
 *  MIT License, see the LICENSE.md file in the root folder.
*/
#include <SPI.h&gt;
#include <plainRFM69.h&gt;

// slave select pin.
#define SLAVE_SELECT_PIN 10     

// connected to the reset pin of the RFM69.
#define RESET_PIN 9

// tie this pin down on the receiver.
#define SENDER_DETECT_PIN 4

/*
    This is very minimal, it does not use the interrupt.

    Using the interrupt is recommended.
*/

plainRFM69 rfm = plainRFM69(SLAVE_SELECT_PIN);

void sender(){

    uint32_t start_time = millis();

    uint32_t counter = 0; // the counter which we are going to send.

    while(true){
        rfm.poll(); // run poll as often as possible.

        if (!rfm.canSend()){
            continue; // sending is not possible, already sending.
        }
        if ((millis() - start_time) &gt; 500){ // every 500 ms. 
            start_time = millis();

            // be a little bit verbose.
            Serial.print("Send:");Serial.println(counter);

//            rfm.dumpRegisters(Serial);

            // send the number of bytes equal to that set with setPacketLength.
            // read those bytes from memory where counter starts.
            rfm.send(&amp;counter);
            
            counter++; // increase the counter.
        }
    }
}

void receiver(){
    uint32_t counter = 0; // to count the messages.

    while(true){

        rfm.poll(); // poll as often as possible.

        while(rfm.available()){ // for all available messages:

            uint32_t received_count = 0; // temporary for the new counter.
            uint8_t len = rfm.read(&amp;received_count); // read the packet into the new_counter.

            // print verbose output.
            Serial.print("Packet ("); Serial.print(len); Serial.print("): "); Serial.println(received_count);

            if (counter+1 != received_count){
                // if the increment is larger than one, we lost one or more packets.
                Serial.println("Packetloss detected!");
            }

            // assign the received counter to our counter.
            counter = received_count;
        }
    }
}

void setup(){
    Serial.begin(9600);
    SPI.begin();

    bareRFM69::reset(RESET_PIN); // sent the RFM69 a hard-reset.

    rfm.setRecommended(); // set recommended paramters in RFM69.
    rfm.setPacketType(false, false); // set the used packet type.

    rfm.setBufferSize(2);   // set the internal buffer size.
    rfm.setPacketLength(4); // set the packet length.
    rfm.setFrequency((uint32_t) 915*1000*1000); // set the frequency.

    // baudrate is default, 4800 bps now.
    rfm.dumpRegisters(Serial);
    
    rfm.receive();
    // set it to receiving mode.

    pinMode(SENDER_DETECT_PIN, INPUT_PULLUP);
    delay(5);
}

void loop(){
    if (digitalRead(SENDER_DETECT_PIN) == LOW){
        Serial.println("Going Receiver!");
        receiver(); 
        // this function never returns and contains an infinite loop.
    } else {
        Serial.println("Going sender!");
        sender();
        // idem.
    }
}
Arduino RFM69HCW Client in receive mode

I added code to dump the all the Arduino Nano Radio Shield RFM69/95 registers so I could compare it with my Adafruit RFM69HCW Radio Bonnet configuration. I also modified the code to set the three frequency registers so they matched the sample values based on the calculation in the RFM69HCW datasheet. I spent a lot of time manually configuring individual registers on the Adafruit bonnet (ignoring registers like 0x24 RegRssiValue).

void bareRFM69::dumpRegisters(Stream&amp; out)
{
  for (int i = 0; i <= 0x3d; i++) {
    out.print("0x");
    out.print(i, HEX);
    out.print(": 0x");
    out.println(this-&gt;readRegister(i), HEX);
  }
}

void plainRFM69::setFrequency(uint32_t freq){
     uint64_t frf = ((uint64_t)freq << 19) / 32000000;
    this-&gt;setFrf(frf);
}

After much “trial and error” I found that my Arduino device would only receive messages from my Windows 10 IoT Core device when a third Arduino device was transmitting.

21:10:50.819 -> 0x0: 0x0
21:10:50.819 -> 0x1: 0x4
21:10:50.852 -> 0x2: 0x0
21:10:50.852 -> 0x3: 0x1A
21:10:50.852 -> 0x4: 0xB
21:10:50.886 -> 0x5: 0x0
21:10:50.886 -> 0x6: 0x52
21:10:50.886 -> 0x7: 0xE4
21:10:50.920 -> 0x8: 0xC0
21:10:50.920 -> 0x9: 0x0
21:10:50.920 -> 0xA: 0x41
21:10:50.954 -> 0xB: 0x40
21:10:50.954 -> 0xC: 0x2
21:10:50.954 -> 0xD: 0x92
21:10:50.988 -> 0xE: 0xF5
21:10:50.988 -> 0xF: 0x20
21:10:50.988 -> 0x10: 0x24
21:10:51.022 -> 0x11: 0x9F
21:10:51.022 -> 0x12: 0x9
21:10:51.056 -> 0x13: 0x1A
21:10:51.056 -> 0x14: 0x40
21:10:51.056 -> 0x15: 0xB0
21:10:51.089 -> 0x16: 0x7B
21:10:51.089 -> 0x17: 0x9B
21:10:51.089 -> 0x18: 0x88
21:10:51.124 -> 0x19: 0x55
21:10:51.124 -> 0x1A: 0x8B
21:10:51.124 -> 0x1B: 0x40
21:10:51.157 -> 0x1C: 0x80
21:10:51.157 -> 0x1D: 0x6
21:10:51.157 -> 0x1E: 0x10
21:10:51.191 -> 0x1F: 0x0
21:10:51.191 -> 0x20: 0x0
21:10:51.191 -> 0x21: 0x0
21:10:51.226 -> 0x22: 0x0
21:10:51.226 -> 0x23: 0x2
21:10:51.226 -> 0x24: 0xFF
21:10:51.260 -> 0x25: 0x0
21:10:51.260 -> 0x26: 0x5
21:10:51.293 -> 0x27: 0x80
21:10:51.293 -> 0x28: 0x0
21:10:51.293 -> 0x29: 0xFF
21:10:51.328 -> 0x2A: 0x0
21:10:51.328 -> 0x2B: 0x0
21:10:51.328 -> 0x2C: 0x0
21:10:51.363 -> 0x2D: 0x3
21:10:51.363 -> 0x2E: 0x98
21:10:51.363 -> 0x2F: 0x1
21:10:51.363 -> 0x30: 0x1
21:10:51.397 -> 0x31: 0x1
21:10:51.397 -> 0x32: 0x1
21:10:51.397 -> 0x33: 0x0
21:10:51.432 -> 0x34: 0x0
21:10:51.432 -> 0x35: 0x0
21:10:51.466 -> 0x36: 0x0
21:10:51.466 -> 0x37: 0x50
21:10:51.466 -> 0x38: 0x4
21:10:51.500 -> 0x39: 0x0
21:10:51.500 -> 0x3A: 0x0
21:10:51.500 -> 0x3B: 0x0
21:10:51.535 -> 0x3C: 0x1
21:10:51.535 -> 0x3D: 0x0
21:10:51.535 -> Going Receiver!
21:10:51.672 -> Packet (4): 27
21:10:51.672 -> Packetloss detected!
21:10:52.151 -> Packet (4): 28
21:10:52.665 -> Packet (4): 29
21:10:53.182 -> Packet (4): 30
21:10:53.664 -> Packet (4): 31
21:10:54.665 -> Packet (4): 33
21:10:54.699 -> Packetloss detected!
21:10:55.178 -> Packet (4): 34
21:10:56.177 -> Packet (4): 36
21:10:56.177 -> Packetloss detected!
21:10:56.660 -> Packet (4): 37
21:10:57.180 -> Packet (4): 38
21:10:57.666 -> Packet (4): 39
21:10:58.151 -> Packet (4): 40
21:10:58.669 -> Packet (4): 41
21:10:59.186 -> Packet (4): 42
21:10:59.668 -> Packet (4): 43
21:11:00.191 -> Packet (4): 44
21:11:00.666 -> Packet (4): 45
21:11:01.182 -> Packet (4): 46
21:11:01.664 -> Packet (4): 47
21:11:02.183 -> Packet (4): 48
21:11:02.664 -> Packet (4): 49
21:11:03.182 -> Packet (4): 50
21:11:03.664 -> Packet (4): 51

I think the interoperability problem was caused by timing differences caused by the plainRFM69 library using AutoMode (see datasheet section 4.4) to sequence the transmit process rather than manually changing the mode etc.

AutoMode option looks promising and warrants further investigation but interoperability will be an issue.

void plainRFM69::sendPacket(void* buffer, uint8_t len){
    /*
        Just like with Receive mode, the automode is used.

        First, Rx mode is disabled by going into standby.
        Then the automode is set to start transmitting when FIFO level is above
        the thresshold, it stops transmitting after PacketSent is asserted.

        This results in a minimal Tx time and packetSent can be detected when
        automode is left again.
        
    */
    this->setMode(RFM69_MODE_SEQUENCER_ON | RFM69_MODE_STANDBY);
    this->setAutoMode(RFM69_AUTOMODE_ENTER_RISING_FIFOLEVEL, RFM69_AUTOMODE_EXIT_RISING_PACKETSENT, RFM69_AUTOMODE_INTERMEDIATEMODE_TRANSMITTER);
    // perhaps RFM69_AUTOMODE_ENTER_RISING_FIFONOTEMPTY is faster?
    
    // set it into automode for transmitting

    // p22 - Turn on the high power boost registers in transmitting mode.
    if (this->tx_power_boosted)
    {
        this->setPa13dBm1(true);
        this->setPa13dBm2(true);
    }

    // write the fifo.
    this->state = RFM69_PLAIN_STATE_SENDING; // set the state to sending.
    this->writeFIFO(buffer, len);
}

Looks like I need to investigate some of the other Arduino library options.