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.

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

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

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&= MODE_MASK; tmp |= RADIO_TX; vSpiWrite(((word)RegOpMode<<8)+tmp); //�ȴ�������� bittime = SymbolTime/1000; //unit: us overtime = SyncLength+PreambleLength+length+1; if(!FixedPktLength) //SyncWord & PktLength & 2ByteCRC overtime += 1; if(!CrcDisable) overtime += 2; overtime<<=3; //8bit == 1byte overtime*= bittime; overtime/= 1000; //unit: ms if(overtime==0) overtime = 1; overtime += (overtime>>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>=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 & 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 & 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 & 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 & 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 &= 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 &= 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 & 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.