RFM9X.IoTCore Uputronics Raspberry PI LoRa(TM) Expansion Board

The Raspberry Pi+ LoRa(TM) Expansion Board has two RF modules. In my setup CE0 was 915MHz and CE1 was 433MHz so I modified the demo application so I could run both ports independently or simultaneously.

#if UPUTRONICS_RPIPLUS_CS0 && !UPUTRONICS_RPIPLUS_CS1
		private const byte InterruptLine = 25;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS0, InterruptLine);
#endif
#if !UPUTRONICS_RPIPLUS_CS0 && UPUTRONICS_RPIPLUS_CS1
		private const byte InterruptLine = 16;
		private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectPin.CS1, InterruptLine);
#endif
#if UPUTRONICS_RPIPLUS_CS0 && UPUTRONICS_RPIPLUS_CS1 // 433MHz and 915MHz in my setup
	private const byte InterruptLineCS0 = 25;
	private Rfm9XDevice rfm9XDeviceCS0 = new Rfm9XDevice(ChipSelectPin.CS0, InterruptLineCS0);
	private const byte InterruptLineCS1 = 16;
	private Rfm9XDevice rfm9XDeviceCS1 = new Rfm9XDevice(ChipSelectPin.CS1, InterruptLineCS1);
#endif

The in the run method

#if UPUTRONICS_RPIPLUS_CS0 && UPUTRONICS_RPIPLUS_CS1
		public void Run(IBackgroundTaskInstance taskInstance)
		{
			rfm9XDeviceCS0.Initialise(915000000.0, paBoost: true, rxPayloadCrcOn: true);
			rfm9XDeviceCS1.Initialise(433000000.0, paBoost: true, rxPayloadCrcOn: true);
#if DEBUG
			rfm9XDeviceCS0.RegisterDump();
			rfm9XDeviceCS1.RegisterDump();
#endif

			rfm9XDeviceCS0.OnReceive += Rfm9XDevice_OnReceive;
			rfm9XDeviceCS1.OnReceive += Rfm9XDevice_OnReceive;
#if ADDRESSED_MESSAGES_PAYLOAD
			rfm9XDeviceCS0.Receive(UTF8Encoding.UTF8.GetBytes(Environment.MachineName));
			rfm9XDeviceCS1.Receive(UTF8Encoding.UTF8.GetBytes(Environment.MachineName));
#else
			rfm9XDeviceCS0.Receive();
			rfm9XDeviceCS1.Receive();
#endif
			rfm9XDeviceCS0.OnTransmit += Rfm9XDevice_OnTransmit;
			rfm9XDeviceCS1.OnTransmit += Rfm9XDevice_OnTransmit;

			Task.Delay(10000).Wait();

			while (true)
			{
				string messageText = string.Format("Hello from {0} ! {1}", Environment.MachineName, MessageCount);
				MessageCount -= 1;

				byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
				Debug.WriteLine("{0:HH:mm:ss}-TX {1} byte message {2}", DateTime.Now, messageBytes.Length, messageText);
#if ADDRESSED_MESSAGES_PAYLOAD
				this.rfm9XDeviceCS0.Send(UTF8Encoding.UTF8.GetBytes("Netduino"), messageBytes);
				this.rfm9XDeviceCS1.Send(UTF8Encoding.UTF8.GetBytes("Arduino1"), messageBytes);
#else
				this.rfm9XDeviceCS0.Send(messageBytes);
				this.rfm9XDeviceCS1.Send(messageBytes);
#endif
				Task.Delay(10000).Wait();
			}
		}

#else

		public void Run(IBackgroundTaskInstance taskInstance)
		{
			rfm9XDevice.Initialise(433000000, paBoost: true, rxPayloadCrcOn : true);
			rfm9XDevice.Initialise(915000000, paBoost: true, rxPayloadCrcOn : true);
#if DEBUG
			rfm9XDevice.RegisterDump();
#endif

		rfm9XDevice.OnReceive += Rfm9XDevice_OnReceive;
#if ADDRESSED_MESSAGES_PAYLOAD
			rfm9XDevice.Receive(UTF8Encoding.UTF8.GetBytes(Environment.MachineName));
#else
			rfm9XDevice.Receive();
#endif
			rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

			Task.Delay(10000).Wait();

			while (true)
			{
				string messageText = string.Format("Hello from {0} ! {1}", Environment.MachineName, MessageCount);
				MessageCount -= 1;

				byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
				Debug.WriteLine("{0:HH:mm:ss}-TX {1} byte message {2}", DateTime.Now, messageBytes.Length, messageText);
#if ADDRESSED_MESSAGES_PAYLOAD
				this.rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes("AddressHere"), messageBytes);
#else
				this.rfm9XDevice.Send(messageBytes);
#endif
				Task.Delay(10000).Wait();
			}
		}
#endif

In the debugger output I could see addressed messages being sent to and arriving from a 433MHz Arduino device and a 915MHz Netduino device.

The thread 0x5f8 has exited with code 0 (0x0).
22:06:44-RX From Netduino PacketSnr 9.5 Packet RSSI -49dBm RSSI -110dBm = 20 byte message "Hello NetMF LoRa! 82"
22:06:47-RX From Arduino1 PacketSnr 9.8 Packet RSSI -56dBm RSSI -101dBm = 11 byte message "t 20.2,h 90"
22:06:48-TX 25 byte message Hello from LoRaIoT1 ! 163
22:06:49-TX Done
22:06:49-TX Done
The thread 0xe9c has exited with code 0 (0x0).
22:06:54-RX From Netduino PacketSnr 9.8 Packet RSSI -46dBm RSSI -111dBm = 20 byte message "Hello NetMF LoRa! 83"
22:06:57-RX From Arduino1 PacketSnr 9.8 Packet RSSI -61dBm RSSI -93dBm = 11 byte message "t 20.3,h 90"
22:06:58-TX 25 byte message Hello from LoRaIoT1 ! 162
22:06:59-TX Done
22:06:59-TX Done
The thread 0xf88 has exited with code 0 (0x0).
22:07:04-RX From Netduino PacketSnr 9.5 Packet RSSI -48dBm RSSI -110dBm = 20 byte message "Hello NetMF LoRa! 84"
22:07:07-RX From Arduino1 PacketSnr 9.8 Packet RSSI -61dBm RSSI -93dBm = 11 byte message "t 20.2,h 90"
22:07:09-TX 25 byte message Hello from LoRaIoT1 ! 161
22:07:09-TX Done
22:07:09-TX Done

This particular configuration has not been extensively tested yet and should be treated as early Beta (11 Sept 2018)

 

RFM9X.IoTCore Payload Addressing

The reason for RFM9XLoRaNet was so that I could build a field gateway to upload telemetry data from “cheap n cheerful” *duino devices to Azure IoT Hubs and AdaFruit.IO.

I have extended the Windows10IoTCore sample application and library to show how the conditional compilation directive ADDRESSED_MESSAGES_PAYLOAD controls the configuration.

When the application is started the RFM9X is in sleep mode, then when the Receive method is called the device is set to ReceiveContinuous.

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

#if DEBUG
   rfm9XDevice.RegisterDump();
#endif

#if ADDRESSED_MESSAGES_PAYLOAD
   rfm9XDevice.OnReceive += Rfm9XDevice_OnReceive;
   rfm9XDevice.Receive(UTF8Encoding.UTF8.GetBytes(Environment.MachineName));
#else
   rfm9XDevice.Receive();
#endif
   rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;

   Task.Delay(10000).Wait();

   while (true)
   {
      string messageText = string.Format("Hello from {0} ! {1}", Environment.MachineName, NessageCount);
      MessageCount -= 1;

      byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
      Debug.WriteLine("{0:HH:mm:ss}-TX {1} byte message {2}", DateTime.Now, messageBytes.Length, messageText);
#if ADDRESSED_MESSAGES_PAYLOAD
      this.rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes("AddressGoesHere"), messageBytes);
#else
      this.rfm9XDevice.Send(messageBytes);
#endif
      Task.Delay(10000).Wait();
   }
}

On receipt of a message, the message is parsed and the to/from addresses and payload extracted (ADDRESSED_MESSAGES defined) or passed to the client application for processing.

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

#if ADDRESSED_MESSAGES_PAYLOAD
      string addressText = UTF8Encoding.UTF8.GetString(e.Address);

      Debug.WriteLine(@"{0:HH:mm:ss}-RX From {1} PacketSnr {2:0.0} Packet RSSI {3}dBm RSSI {4}dBm = {5} byte message ""{6}""", DateTime.Now, addressText, e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText);
#else
      Debug.WriteLine(@"{0:HH:mm:ss}-RX PacketSnr {1:0.0} Packet RSSI {2}dBm RSSI {3}dBm = {4} byte message ""{5}""", DateTime.Now, e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText);
#endif
   }
   catch (Exception ex)
   {
      Debug.WriteLine(ex.Message);
   }
}

The addressing implementation needs further testing and I’m building sample .NetMF and *duino clients.

Windows 10 IoT Core LoRa library

I have a pair of Windows 10 IoT Core nRF24L01 field gateway projects, one for AdaFruit.IO and the other for Azure IoTHub (Including Azure IoT Central). I use these field gateways for small scale indoor and outdoor deployments.

For larger systems e.g a school campus I was looking for something with a bit more range (line of site + in building penetration) and clients with lower power consumption (suitable for long term battery or solar power).

Other makers had had success with RFM69(proprietary) and RFM9X (LoRA) based devices and shields/hats so I had a look at both technologies.

To kick things off I purchased

I then did some searching and downloaded two commonly used libraries

Initially I trialled the emmellsoft Windows 10 IoT Core Dragino.LoRa code on a couple of Raspberry PI devices.

RPIDraginoP2P

After updating the Windows 10 Min/Max versions, plus the NuGet packages and setting the processor type to ARM the code compiled, downloaded and ran which was a pretty good start.

I could see messages being transmitted and received by the two devices

Packet RSSI: -33, RSSI: -91, SNR: 8, Length: 5
Message Received: CRC OK, Rssi=-91, PacketRssi=-33, PacketSnr=8, Buffer:[55, ff, 00, aa, 01], 2018-07-30 09:27:48
Successfully sent in 110 milliseconds.
Packet RSSI: -15, RSSI: -100, SNR: 9.2, Length: 5
Message Received: CRC OK, Rssi=-100, PacketRssi=-15, PacketSnr=9.2, Buffer:[55, ff, 00, aa, 02], 2018-07-30 09:27:53
Successfully sent in 36 milliseconds.
Packet RSSI: -35, RSSI: -101, SNR: 9, Length: 5
Message Received: CRC OK, Rssi=-101, PacketRssi=-35, PacketSnr=9, Buffer:[55, ff, 00, aa, 03], 2018-07-30 09:27:58
Successfully sent in 36 milliseconds.

I added my first attempt at device configuration for New Zealand (based on EU settings) in Dragino.LoRa\Radio\TransceiverSettings.cs

public static readonly TransceiverSettings ANZ915 = new TransceiverSettings(
             RadioModemKind.Lora,
             915000000,
             BandWidth.BandWidth_125_00_kHz,
             SpreadingFactor.SF7,
             CodingRate.FourOfFive,
             8,
             true,
             false,
             LoraSyncWord.Public);

The LoraSyncWord.Public would turn out to be a problem later!

Then I modified the sender and receiver sample application MainPage.xaml.cs files to reference my settings

private static TransceiverSettings GetRadioSettings()
{
   // *********************************************************************************************
   // #1/2. YOUR EDITING IS REQUIRED HERE!
   //
   // Choose transeiver settings:
   // *********************************************************************************************

   return TransceiverSettings.Standard.ANZ915;
}

I modified one of the RadioHead sample Arduino applications (centre frequency) and deployed it to a LoRa MiniDev device. I could see messages getting sent but they were not getting received by the RPI(s).

So I dumped the registers for the SX127X device in the HopeRF RFM95 module on both devices,

DraginoLoraMinDev

From the device on RPI Hat

SX1276/77/78/79 detected, starting.
1-85
2-1A
3-B
4-0
5-52
6-E4
7-C0
8-0
9-85
A-9
B-2B
C-23
D-0
E-80
F-0
10-0
11-0
12-0
13-0
14-0
15-0
16-0
17-0
18-4
19-0
1A-0
1B-42
1C-0
1D-72
1E-74
1F-9F
20-0
21-8
22-1
23-FF
24-0
25-0
26-4
27-0
28-0
29-0
2A-0
2B-0
2C-9
2D-50
2E-14
2F-45
30-55
31-C3
32-5
33-27
34-1C
35-A
36-3
37-A
38-42
39-34
The LoRa transceiver is initiated successfully.

I printed out the Radiohead and emmellsoft registers then manually compared them using the SX1275 datasheet for reference.

I found the 3 registers which contain the MSB, ISB and LSB for the centre frequency weren’t being calculated correctly (checked this by changing the frequency to 434MHz and comparing the register values to the worked example in the datasheet).

I manually “brute forced” the centre frequency registers in LoRaTransceiver.cs Init() and the RPI could then detect a signal but couldn’t decode the messages.

I went back to the Register dumps and found the SyncWord (odd name as it is a byte) were different. After updating the RPI settings the devices could exchange packets..

const double RH_RF95_FXOSC = 32000000.0;
const double RH_RF95_FSTEP = RH_RF95_FXOSC / 524288.0;
long frf = (long)(Settings.Frequency / RH_RF95_FSTEP);

byte[] bytes = BitConverter.GetBytes(frf);

byte[] x6 = { bytes[2] };
RegisterManager.WriteRegister(6, x6);
byte[] x7 = { bytes[1] };
RegisterManager.WriteRegister(7, x7);
byte[] x8 = { bytes[0] };
RegisterManager.WriteRegister(8, x8);

RegisterManager.Write(new LoraRegisterSyncWord(Settings.LoraSyncWord.Value));

This was not a long term solution, lots of code, and register setting changes with limited explanation…

Azure Meetup-Budget tank of 91 IoT

The premise of my Azure Meetup presentation was could you build an interesting project on a rainy weekend afternoon with a constrained budget (tank of 91 octane petrol) and minimal soldering .

Budget

Our family car is a VW Passat V6 4Motion which has a 62 Litre tank. The driver usually doesn’t usually stop to fill up until the fuel light has been on for a bit which helped.

PetrolReceipt

Based on the most recent receipt the budget was NZD132.

Where possible I purchased parts locally (the tech equivalent of food miles) or on special.

My bill of materials (prices as at 2018-06) was on budget.

The devDuino V2.2 and nRF24L01 module were USD26.20 approx. NZD37.50 (including freight) from elecrow.

Tradeoffs

I powered my Raspberry PI with a spare cellphone charger (make sure it can supply enough current to reliably power the device).

The devDuino V2.has an ATSHA204A which provides a guaranteed unique 72-bit serial number (makes it harder to screw up provisioning devices in the field).

I use a 32G MicroSD rather than a 16G MicroSD card as I have had issued with 16G cards getting corrupted by more recent upgrades (possibly running out of space?)

The Raspberry PI shield requires a simple modification to enable interrupt driven operation.

My sample devDuino V2.2 client uses an external temperature and humidity sensor, modifying this code to use the onboard temperature sensor an MCP9700 will be covered in another post.

The devDuino V2 is a little bit cheaper USD15.99 NZD37.31, has the same onboard temperature sensor as the V2.2 but no unique serial number chip.

The devDuino V4.0 has an onboard HTU21D temperature + humidity sensor but no unique serial number and the batteries are expensive.

The code and deployment instructions for the nRF24L01 field gateway applications for AdaFruit.IO and Azure IoT Hub/Azure IoT Central are available on hackster.IO.

RPiWithnRF24Plate

AdaFruit.IO has free and USD10.00/month options which work well for many hobbyist projects.

AdaFruitIO

Azure Meetup Christchurch notes

For the people who came to my Azure meetup session this evening

Sources of sensors and development boards

http://www.adafruit.com
http://www.elecrow.com (watering kits)
http://www.ingenuitymicro.com (NZ based dev boards)
http://www.netduino.com (.NetMF development boards)
http://www.makerfabs.com
http://www.seeedstudio.com
http://www.tindie.com

nRF24Shields for RPI devices
http://www.tindie.com/products/ceech/new-raspberry-pi-to-nrf24l01-shield/

nRF24Shields for *duino devices in AU
embeddedcoolness.com

Raspberry PI Source in CHC
http://www.wavetech.co.nz

RFM69 & LoRa Modules
http://www.wisen.com.au

local sensor and device resellers quick turnaround
http://www.mindkits.co.nz
http://www.nicegear.co.nz

http://www.diyelectricskateboard.com

The watch development platform
http://www.hexiwear.com

http://www.gowifi.co.nz (Antennas & other wireless kit based in Rangiora)

my projects
http://www.hackster.io/KiwiBryn
io.adafruit.com/BrynHLewis/dashboards/home-environment

Wireless field gateway protocol V1

I’m going to build a number of nRF2L01P field gateways (Netduino Ethernet & Wifi running .NetMF, Raspberry PI running Windows 10 IoT Core, RedBearLab 3200  etc.), clients which run on a variety of hardware (Arduino, devDuino, Netduino, Seeeduino etc.) which, then upload data to a selection of IoT Cloud services (AdaFruit.IO, ThingSpeak, Microsoft IoT Central etc.)

The nRF24L01P is widely supported with messages up to 32 bytes long, low power consumption and 250kbps, 1Mbps and 2Mbps data rates.

The aim is to keep the protocol simple (telemetry only initially) to implement and debug as the client side code will be utilised by high school student projects.

The first byte of the message specifies the message type

0 = Echo

The message is displayed by the field gateway as text & hexadecimal.

1 = Device identifier + Comma separated values (CSV) payload

[0] – Set to 1

[1] – Device identifier length

[2]..[2+Device identifier length] – Unique device identifier bytes e.g. Mac address

[2+Device identifier length+1 ]..[31] – CSV payload e.g.  SensorID value, SensorID value

Overtime I will support more message types and wireless protocols.

 

nRF24 Windows 10 IoT Core reboot

My first live deployment of the nRF24L01 Windows 10 IoT Core field gateway is now scheduled for mid Q1 2018 so time for a reboot. After digging out my Raspbery PI 2/3 devices and the nRF24L01+ shield (with modifications detailed here) I have a basic plan with some milestones.

My aim is to be able to wirelessly acquire data from several dozen Arduino, devduino, seeeduino, and Netduino devices, Then, using a field gateway on a Raspberry PI running Windows 10 IoT Core upload it to Microsoft IoT Central

First bit of code – Bleepy a simple background application to test the piezo beeper on the RPI NRF24 Shield

namespace devmobile.IoTCore.Bleepy
{
   public sealed class StartupTask : IBackgroundTask
   {
      private BackgroundTaskDeferral deferral;
      private const int ledPinNumber = 4;
      private GpioPin ledGpioPin;
      private ThreadPoolTimer timer;

      public void Run(IBackgroundTaskInstance taskInstance)
      {
         var gpioController = GpioController.GetDefault();
         if (gpioController == null)
         {
            Debug.WriteLine("GpioController.GetDefault failed");
            return;
         }

         ledGpioPin = gpioController.OpenPin(ledPinNumber);
         if (ledGpioPin == null)
         {
            Debug.WriteLine("gpioController.OpenPin failed");
            return;
         }

         ledGpioPin.SetDriveMode(GpioPinDriveMode.Output);

         this.timer = ThreadPoolTimer.CreatePeriodicTimer(Timer_Tick, TimeSpan.FromMilliseconds(500));

         deferral = taskInstance.GetDeferral();

         Debug.WriteLine("Rum completed");
      }

      private void Timer_Tick(ThreadPoolTimer timer)
      {
         GpioPinValue currentPinValue = ledGpioPin.Read();

         if (currentPinValue == GpioPinValue.High)
         {
            ledGpioPin.Write(GpioPinValue.Low);
         }
         else
         {
            ledGpioPin.Write(GpioPinValue.High);
         }
      }
   }
}

Note the blob of blu tack over the piezo beeper to mute noise
nRF24ShieldMuted