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.