RFM69 hat library h WWWWWWWWoo

Again, while doing some stress testing I noticed an odd message go past in the Visual Studio output window. I had multiple devices sending addressed messages (both individual and broadcast) to the Adafruit RFM69 HCW Radio Bonnet, on my Windows 10 IoT Core device while it was sending a message every 5 seconds.

Received From 102 a 15 byte message Hello World:161
23:42:33.343 RegIrqFlags2 01100110
23:42:33.356 RegIrqFlags1 11011001
23:42:33.374 Address 0X99 10011001
Received From 153 a 15 byte message Hello World:106
23:42:33.761 RegIrqFlags2 01100110
23:42:33.774 RegIrqFlags1 11011001
23:42:33.791 Address 0X66 01100110
Received From 102 a 15 byte message Hello World:162
The thread 0xd20 has exited with code 0 (0x0).
23:42:34.500 RegIrqFlags2 01100110
23:42:34.501 Send-hello world 11:42:34 PM
23:42:34.520 RegIrqFlags1 11011001
23:42:34.545 Send-Done
23:42:34.551 Address 0X10 00010000
Received From 16 a 15 byte message h    WWWWWWWWoo
23:42:34.686 RegIrqFlags2 00001000
23:42:34.701 RegIrqFlags1 10110000
23:42:34.715 Transmit-Done
Transmit-Done
23:42:34.902 RegIrqFlags2 01100110
23:42:34.915 RegIrqFlags1 11011001
23:42:34.931 Address 0X66 01100110
Received From 102 a 15 byte message Hello World:163
23:42:35.626 RegIrqFlags2 01100110
23:42:35.640 RegIrqFlags1 11011001
23:42:35.659 Address 0X99 10011001
Received From 153 a 15 byte message Hello World:108
23:42:36.042 RegIrqFlags2 01100110
23:42:36.055 RegIrqFlags1 11011001
23:42:36.073 Address 0X66 01100110

The RegIrqFlags2 CrcOk (bit 1) was set and the message was corrupt.

RegIrqFlags2 bit flags from SX1231 datasheet

I have added code to check the CRC on inbound messages if this functionality is enabled. So the library can be used with CRCs disabled I have added a flag to the OnDataReceivedEventArgs class to indicate whether the CRC on the inbound message was OK.

private readonly Object Rfm9XRegFifoLock = new object();
...
private void ProcessPayloadReady(RegIrqFlags1 irqFlags1, RegIrqFlags2 irqFlags2)
{
	byte? address = null;
	byte numberOfBytes;
	byte[] messageBytes;

	lock (Rfm9XRegFifoLock)
	{
		// Read the length of the buffer if variable length packets
		if (PacketFormat == RegPacketConfig1PacketFormat.VariableLength)
		{
			numberOfBytes = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegFifo);
		}
		else
		{
			numberOfBytes = PayloadLength;
		}

		// Remove the address from start of the payload
		if (AddressingEnabled)
		{
			address = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegFifo);

			Debug.WriteLine("{0:HH:mm:ss.fff} Address 0X{1:X2} {2}", DateTime.Now, address, Convert.ToString((byte)address, 2).PadLeft(8, '0'));
			numberOfBytes--;
		}

		// Allocate a buffer for the payload and read characters from the Fifo
		messageBytes = new byte[numberOfBytes];

		for (int i = 0; i < numberOfBytes; i++)
		{
			messageBytes[i] = RegisterManager.ReadByte((byte)Rfm69HcwDevice.Registers.RegFifo);
		}
	}
...
public void SendMessage(byte[] messageBytes)
{
#region Guard conditions
#endregion

	lock (Rfm9XRegFifoLock)
	{
		SetMode(RegOpModeMode.StandBy);

		if (PacketFormat == RegPacketConfig1PacketFormat.VariableLength)
		{
			RegisterManager.WriteByte((byte)Registers.RegFifo, (byte)messageBytes.Length);
		}

		foreach (byte b in messageBytes)
		{
			this.RegisterManager.WriteByte((byte)Registers.RegFifo, b);
		}

		SetMode(RegOpModeMode.Transmit);
	}
}

I can most probably reduce the duration which I hold the lock for but that will require some more stress testing.

RFM69 hat library Part12D

Enums and Masks – Packet lengths, addressing & CRCs

The RFM69CW/RFM69HCW module (based on the Semtech SX1231/SX1231H) has configurable (RegSyncConfig) synchronisation sequences (the length, tolerance for errors and the individual byte values).

By default synchronisation is enabled and a default sequence of bytes is used, in my library synchronisation is NOT enabled until a SyncValue is provided.

I added some additional constants and enumerations for the other settings configured in RegSyncConfig.

// RegSyncConfig 
// This is private because default ignored and flag set based on SyncValues parameter being specified rather than default
private enum RegSyncConfigSyncOn
{
	Off = 0b00000000,
	On = 0b10000000
}

public enum RegSyncConfigFifoFileCondition
{
	SyncAddressInterrupt = 0b00000000,
	FifoFillCondition =    0b01000000
}

private const RegSyncConfigFifoFileCondition SyncFifoFileConditionDefault = RegSyncConfigFifoFileCondition.SyncAddressInterrupt;
readonly byte[] SyncValuesDefault = {0x01, 0x01, 0x01, 0x01};
public const byte SyncValuesSizeDefault = 4;
public const byte SyncValuesSizeMinimum = 1;
public const byte SyncValuesSizeMaximum = 8;

private const byte SyncToleranceDefault = 0;
public const byte SyncToleranceMinimum = 0;
public const byte SyncToleranceMaximum = 7;

I also added some guard conditions to the initialise method which validate the syncFifoFileCondition, syncTolerance and syncValues length.

public void Initialise(RegOpModeMode modeAfterInitialise,
	BitRate bitRate = BitRateDefault,
	ushort frequencyDeviation = frequencyDeviationDefault,
	double frequency = FrequencyDefault,
	ListenModeIdleResolution listenModeIdleResolution = ListenModeIdleResolutionDefault, ListenModeRXTime listenModeRXTime = ListenModeRXTimeDefault, ListenModeCrieria listenModeCrieria = ListenModeCrieriaDefault, ListenModeEnd listenModeEnd = ListenModeEndDefault,
	byte listenCoefficientIdle = ListenCoefficientIdleDefault,
	byte listenCoefficientReceive = ListenCoefficientReceiveDefault,
	bool pa0On = pa0OnDefault, bool pa1On = pa1OnDefaut, bool pa2On = pa2OnDefault, byte outputpower = OutputpowerDefault,
	PaRamp paRamp = PaRampDefault,
	bool ocpOn = OcpOnDefault, byte ocpTrim = OcpTrimDefault,
	LnaZin lnaZin = LnaZinDefault, LnaCurrentGain lnaCurrentGain = LnaCurrentGainDefault, LnaGainSelect lnaGainSelect = LnaGainSelectDefault,
	byte dccFrequency = DccFrequencyDefault, RxBwMant rxBwMant = RxBwMantDefault, byte RxBwExp = RxBwExpDefault,
	byte dccFreqAfc = DccFreqAfcDefault, byte rxBwMantAfc = RxBwMantAfcDefault, byte bxBwExpAfc = RxBwExpAfcDefault,
	ushort preambleSize = PreambleSizeDefault,
	RegSyncConfigFifoFileCondition? syncFifoFileCondition = null, byte? syncTolerance = null, byte[] syncValues = null,
	RegPacketConfig1PacketFormat packetFormat = RegPacketConfig1PacketFormat.FixedLength,
	RegPacketConfig1DcFree packetDcFree = RegPacketConfig1DcFreeDefault,
	bool packetCrc = PacketCrcOnDefault,
	bool packetCrcAutoClearOff = PacketCrcAutoClearOffDefault,
	RegPacketConfig1CrcAddressFiltering packetAddressFiltering = PacketAddressFilteringDefault,
	byte payloadLength = PayloadLengthDefault,
	byte addressNode = NodeAddressDefault, byte addressbroadcast = BroadcastAddressDefault,
	TxStartCondition txStartCondition = TxStartConditionDefault, byte fifoThreshold = FifoThresholdDefault,
	byte interPacketRxDelay = InterPacketRxDelayDefault, bool restartRx = RestartRxDefault, bool autoRestartRx = AutoRestartRxDefault,
	byte[] aesKey = null
	)
{
	RegOpModeModeCurrent = modeAfterInitialise;
	PacketFormat = packetFormat;

	#region RegSyncConfig + RegSyncValue1 to RegSyncValue8 guard conditions
	if (syncValues != null)
	{
		// If sync enabled (i.e. SyncValues array provided) check that SyncValues not to short/long and SyncTolerance not to small/big
		if ((syncValues.Length < SyncValuesSizeMinimum) || (syncValues.Length &gt; SyncValuesSizeMaximum))
		{
			throw new ArgumentException($"The syncValues array length must be between {SyncValuesSizeMinimum} and {SyncValuesSizeMaximum} bytes", "syncValues");
		}
		if (syncTolerance.HasValue)
		{
			if ((syncTolerance < SyncToleranceMinimum) || (syncTolerance &gt; SyncToleranceMaximum))
			{
				throw new ArgumentException($"The syncTolerance size must be between {SyncToleranceMinimum} and {SyncToleranceMaximum}", "syncTolerance");
			}
		}
	}
	else
	{
		// If sync not enabled (i.e. SyncValues array null) check that no syncFifoFileCondition or syncTolerance configuration specified
		if (syncFifoFileCondition.HasValue)
		{
			throw new ArgumentException($"If Sync not enabled syncFifoFileCondition is not supported", "syncFifoFileCondition");
		}

		if (syncTolerance.HasValue)
		{
			throw new ArgumentException($"If Sync not enabled SyncTolerance is not supported", "syncTolerance");
		}
	}
	#endregion

I also ensure that the syncFifoFileCondition and syncTolerance are not specified if synchronisation is not enabled.

The library also supports the built in RFRM69 node and broadcast addressing which is enabled when the AddressNode and/or AddressBroadcast parameters of the Initialise method are set.

RegPacketConfig1 address filtering options

My first attempt at getting encryption and addressing working together failed badly, the Windows 10 IoT Core device didn’t receive any addressed messages when encryption was enabled. So, I went back and re-read the datasheet again and noticed

“If the address filtering is expected then AddressFiltering must be enabled on the transmitter side as well to prevent address byte to be encrypted”(Sic).

The Arduino client code had to be modified so I could set the node + broadcast address registers and AddressFiltering bit flag in RegPacketConfig1

My RMRFM69.h modifications

enum moduleType {RFM65, RFM65C, RFM69, RFM69C, RFM69H, RFM69HC};
	
 #define ADDRESS_NODE_DEFAULT 0x0
 #define ADDRESS_BROADCAST_DEFAULT 0x0
 #define ADDRESSING_ENABLED_NODE 0x2
 #define ADDRESSING_ENABLED_NODE_AND_BROADCAST 0x4

	class RMRFM69
	{
	 public:
		 RMRFM69(SPIClass &spiPort, byte csPin, byte dio0Pin, byte rstPin);
		 modulationType Modulation; //OOK/FSK/GFSK
		 moduleType COB;				//Chip on board
		 uint32_t Frequency;			//unit: KHz
		 uint32_t SymbolTime;			//unit: ns
		 uint32_t Devation;				//unit: KHz
		 word BandWidth;				//unit: KHz
		 byte OutputPower;				//unit: dBm   range: 0-31 [-18dBm~+13dBm] for RFM69/RFM69C
										//            range: 0-31 [-11dBm~+20dBm] for RFM69H/RFM69HC
		 word PreambleLength;			//unit: byte

		 bool CrcDisable; //fasle: CRC enable�� & use CCITT 16bit
						  //true : CRC disable
		 bool CrcMode;	//false: CCITT

		 bool FixedPktLength; //false: for contain packet length in Tx message, the same mean with variable lenth
							  //true : for doesn't include packet length in Tx message, the same mean with fixed length
		 bool AesOn;		  //false:
							  //true:
		 bool AfcOn;		  //false:
							  //true:
		 byte SyncLength;	 //unit: none, range: 1-8[Byte], value '0' is not allowed!
		 byte SyncWord[8];
		 byte PayloadLength; //PayloadLength is need to be set a value, when FixedPktLength is true.
		 byte AesKey[16];	//AES Key block, note [0]->[15] == MSB->LSB
		 byte AddressNode = ADDRESS_NODE_DEFAULT;
		 byte AddressBroadcast = ADDRESS_BROADCAST_DEFAULT;

		 void vInitialize(void);
		 void vConfig(void);
		 void vGoRx(void);
		 void vGoStandby(void);
		 void vGoSleep(void);
		 bool bSendMessage(byte msg[], byte length);
		 bool bSendMessage(byte Address, byte msg[], byte length);
		 byte bGetMessage(byte msg[]);
		 void vRF69SetAesKey(void);
		 void vTrigAfc(void);

		 void vDirectRx(void);			  //go continuous rx mode, with init. inside
		 void vChangeFreq(uint32_t freq); //change frequency
		 byte bReadRssi(void);			  //read rssi value
		 void dumpRegisters(Stream& out);
	

My RMRFM69.cpp modifications in vConfig

 if(!CrcDisable)
 	{
 	i += CrcOn;
	if(CrcMode)
		i += CrcCalc_IBM;
	else
		i += CrcCalc_CCITT;
	}

if((AddressNode!=ADDRESS_NODE_DEFAULT) || (AddressBroadcast==ADDRESS_BROADCAST_DEFAULT))
   {
      i += ADDRESSING_ENABLED_NODE;
   }

if((AddressNode!=ADDRESS_NODE_DEFAULT) || (AddressBroadcast!=ADDRESS_BROADCAST_DEFAULT))
  {
     i += ADDRESSING_ENABLED_NODE_AND_BROADCAST;
  }

 vSpiWrite(((word)RegPacketConfig1<<8)+i);		

I also validate the lengths of the messages to be sent taking into account whether encryption is enabled\disabled.

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.

Rfm9xLoRaDevice NetMF Payload CRCs

To ensure I was only handling messages with valid contents I added code to appended a cyclic redundancy check(CRC) onto outbound messages and validate the CRC on inbound messages.

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

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

The rxPayloadCrcOn needs to be set to True for outbound messages to have a CRC.

Then in the RxDone interrupt handler the CRC is checked (regHopChannel & regIrqFlagsMask) if this feature is enabled. Any messages with missing\invalid CRCs will currently be silently discarded and I’m not certain this is a good idea.

 // Check to see if payload has CRC
         if (RxDoneIgnoreIfCrcMissing)
         {
            byte regHopChannel = this.Rfm9XLoraModem.ReadByte((byte)Registers.RegHopChannel);
            if ((regHopChannel & (byte)RegHopChannelFlags.CrcOnPayload) != (byte)RegHopChannelFlags.CrcOnPayload)
            {
               return;
            }
         }

         // Check to see if payload CRC is valid
         if (RxDoneIgnoreIfCrcInvalid)
         {
            if (((byte)IrqFlags & (byte)RegIrqFlagsMask.PayLoadCrcErrorMask) == (byte)RegIrqFlagsMask.PayLoadCrcErrorMask)
            {
               return;
            }
         }

The conversion of the payload from an array of bytes to a string for display stopped failing with an exception. When I had a number of clients running up to 10% of the messages were getting corrupted.