Elecrow Lora RFM95 IOT Board for RPI

For the example code so far I had been using the Dragino LoRa GPS HAT for Raspberry PI which after looking at the schematic (to figure out how the chip select line was connected) worked pretty well.

I had also purchased a Lora RFM95 IOT Board for RPI from Elecrow and was keen to get that working for applications which don’t require Global Positioning System (GPS) support.

The RFM95 chip select line is connected to pin 26 (GPIO7), the reset line to pin 15(GPIO22) and the interrupt line (RFM96 DIO0) to pin 22(GPIO25).

Lora RFM95 IOT Board for RPI V1.0

The shield had a Light Emitting Diode (LED) connected to General Purpose Input Output (GPIO) 23 so the first step was to get that to flash.

//---------------------------------------------------------------------------------
// Copyright (c) July 2018, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.ElecrowLed
{
	using System;
	using System.Threading;
	using Windows.ApplicationModel.Background;
	using Windows.Devices.Gpio;

	public sealed class StartupTask : IBackgroundTask
	{
		public void Run(IBackgroundTaskInstance taskInstance)
		{
			GpioController gpioController = GpioController.GetDefault();
			GpioPin yellowLedPin = gpioController.OpenPin(23);
			yellowLedPin.SetDriveMode(GpioPinDriveMode.Output);

			while (true)
			{
				if (yellowLedPin.Read() == GpioPinValue.High)
				{
					yellowLedPin.Write(GpioPinValue.Low);
				}
				else
				{
					yellowLedPin.Write(GpioPinValue.High);
				}
				Thread.Sleep(500);
			}
		}
	}
}

To confirm I could access the RMF95 registers over the Serial Peripheral Interface (SPI) I read the RegVersion register.

//---------------------------------------------------------------------------------
// Copyright (c) July 2018, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.ElecrowSPI
{
	using System;
	using System.Diagnostics;
	using System.Threading;
	using Windows.ApplicationModel.Background;
	using Windows.Devices.Spi;

	public sealed class StartupTask : IBackgroundTask
    {
		public void Run(IBackgroundTaskInstance taskInstance)
		{
			SpiController spiController = SpiController.GetDefaultAsync().AsTask().GetAwaiter().GetResult();
			var settings = new SpiConnectionSettings(1)	// GPIO7 Elecrow shield
			{
				ClockFrequency = 500000,
				Mode = SpiMode.Mode0,	// From SemTech docs pg 80 CPOL=0, CPHA=0
			};

			SpiDevice Device = spiController.GetDevice(settings);

			while (true)
			{
				byte[] writeBuffer = new byte[] { 0x42 }; // RegVersion
				byte[] readBuffer = new byte[1];

				Device.TransferSequential(writeBuffer, readBuffer);

				byte registerValue = readBuffer[0];
				Debug.WriteLine("Register 0x{0:x2} - Value 0X{1:x2} - Bits {2}", 0x42, registerValue, Convert.ToString(registerValue, 2).PadLeft(8, '0'));

				Thread.Sleep(10000);
			}
		}
	}
}

The debug output confirmed I was reading the right value from the RegVer register

Register 0x42 - Value 0X12 - Bits 00010010
Register 0x42 - Value 0X12 - Bits 00010010
Register 0x42 - Value 0X12 - Bits 00010010
Register 0x42 - Value 0X12 - Bits 00010010

I’m going to unsolder the coil antenna and replace it with a short SMA Extension drop cable so I can connect an external antenna.

ElecrowLoRaShield

 

RFM95/96/97/98 shield library Part2

Register Dump

Next step was to dump all registers (0x00 thru 0x40) of the SX1276/7/8/9 device

//---------------------------------------------------------------------------------
// Copyright (c) July 2018, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.RegisterScan
{
   using System;
   using System.Diagnostics;
   using System.Threading.Tasks;
   using Windows.ApplicationModel.Background;
   using Windows.Devices.Gpio;
   using Windows.Devices.Spi;

   public sealed class Rfm9XDevice
   {
      private SpiDevice rfm9XLoraModem;
      private GpioPin chipSelectGpioPin;

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

         GpioController gpioController = GpioController.GetDefault();
         chipSelectGpioPin = gpioController.OpenPin(chipSelectPin);
         chipSelectGpioPin.SetDriveMode(GpioPinDriveMode.Output);
         chipSelectGpioPin.Write(GpioPinValue.High);

         rfm9XLoraModem = spiController.GetDevice(settings);
      }

      public Byte RegisterReadByte(byte registerAddress)
      {
         byte[] writeBuffer = new byte[] { registerAddress };
         byte[] readBuffer = new byte[1];
         Debug.Assert(rfm9XLoraModem != null);

         chipSelectGpioPin.Write(GpioPinValue.Low);
         rfm9XLoraModem.Write(writeBuffer);
         rfm9XLoraModem.Read(readBuffer);
         chipSelectGpioPin.Write(GpioPinValue.High);

         return readBuffer[0];
      }
   }

   public sealed class StartupTask : IBackgroundTask
   {
      private const int ChipSelectLine = 25;
      private Rfm9XDevice rfm9XDevice = new Rfm9XDevice(ChipSelectLine);

      public void Run(IBackgroundTaskInstance taskInstance)
      {
         while (true)
         {
            for (byte registerIndex = 0; registerIndex <= 0x42; registerIndex++)
            {
               byte registerValue = rfm9XDevice.RegisterReadByte(registerIndex);

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

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

The output of the application looked like this

Register 0x00 – Value 0X00 – Bits 00000000
Register 0x01 – Value 0X09 – Bits 00001001
Register 0x02 – Value 0X1a – Bits 00011010
Register 0x03 – Value 0X0b – Bits 00001011
Register 0x04 – Value 0X00 – Bits 00000000
Register 0x05 – Value 0X52 – Bits 01010010
Register 0x06 – Value 0X6c – Bits 01101100
Register 0x07 – Value 0X80 – Bits 10000000
Register 0x08 – Value 0X00 – Bits 00000000
Register 0x09 – Value 0X4f – Bits 01001111
Register 0x0a – Value 0X09 – Bits 00001001
Register 0x0b – Value 0X2b – Bits 00101011
Register 0x0c – Value 0X20 – Bits 00100000
Register 0x0d – Value 0X08 – Bits 00001000
Register 0x0e – Value 0X02 – Bits 00000010
Register 0x0f – Value 0X0a – Bits 00001010
Register 0x10 – Value 0Xff – Bits 11111111
Register 0x11 – Value 0X71 – Bits 01110001
Register 0x12 – Value 0X15 – Bits 00010101
Register 0x13 – Value 0X0b – Bits 00001011
Register 0x14 – Value 0X28 – Bits 00101000
Register 0x15 – Value 0X0c – Bits 00001100
Register 0x16 – Value 0X12 – Bits 00010010
Register 0x17 – Value 0X47 – Bits 01000111
Register 0x18 – Value 0X32 – Bits 00110010
Register 0x19 – Value 0X3e – Bits 00111110
Register 0x1a – Value 0X00 – Bits 00000000
Register 0x1b – Value 0X00 – Bits 00000000
Register 0x1c – Value 0X00 – Bits 00000000
Register 0x1d – Value 0X00 – Bits 00000000
Register 0x1e – Value 0X00 – Bits 00000000
Register 0x1f – Value 0X40 – Bits 01000000
Register 0x20 – Value 0X00 – Bits 00000000
Register 0x21 – Value 0X00 – Bits 00000000
Register 0x22 – Value 0X00 – Bits 00000000
Register 0x23 – Value 0X00 – Bits 00000000
Register 0x24 – Value 0X05 – Bits 00000101
Register 0x25 – Value 0X00 – Bits 00000000
Register 0x26 – Value 0X03 – Bits 00000011
Register 0x27 – Value 0X93 – Bits 10010011
Register 0x28 – Value 0X55 – Bits 01010101
Register 0x29 – Value 0X55 – Bits 01010101
Register 0x2a – Value 0X55 – Bits 01010101
Register 0x2b – Value 0X55 – Bits 01010101
Register 0x2c – Value 0X55 – Bits 01010101
Register 0x2d – Value 0X55 – Bits 01010101
Register 0x2e – Value 0X55 – Bits 01010101
Register 0x2f – Value 0X55 – Bits 01010101
Register 0x30 – Value 0X90 – Bits 10010000
Register 0x31 – Value 0X40 – Bits 01000000
Register 0x32 – Value 0X40 – Bits 01000000
Register 0x33 – Value 0X00 – Bits 00000000
Register 0x34 – Value 0X00 – Bits 00000000
Register 0x35 – Value 0X0f – Bits 00001111
Register 0x36 – Value 0X00 – Bits 00000000
Register 0x37 – Value 0X00 – Bits 00000000
Register 0x38 – Value 0X00 – Bits 00000000
Register 0x39 – Value 0Xf5 – Bits 11110101
Register 0x3a – Value 0X20 – Bits 00100000
Register 0x3b – Value 0X82 – Bits 10000010
Register 0x3c – Value 0Xff – Bits 11111111
Register 0x3d – Value 0X02 – Bits 00000010
Register 0x3e – Value 0X80 – Bits 10000000
Register 0x3f – Value 0X40 – Bits 01000000
Register 0x40 – Value 0X00 – Bits 00000000
Register 0x41 – Value 0X00 – Bits 00000000
Register 0x42 – Value 0X12 – Bits 00010010
Register 0x00 – Value 0X00 – Bits 00000000
Register 0x01 – Value 0X09 – Bits 00001001
Register 0x02 – Value 0X1a – Bits 00011010
Register 0x03 – Value 0X0b – Bits 00001011
Register 0x04 – Value 0X00 – Bits 00000000
Register 0x05 – Value 0X52 – Bits 01010010
Register 0x06 – Value 0X6c – Bits 01101100
Register 0x07 – Value 0X80 – Bits 10000000
Register 0x08 – Value 0X00 – Bits 00000000
Register 0x09 – Value 0X4f – Bits 01001111
Register 0x0a – Value 0X09 – Bits 00001001
Register 0x0b – Value 0X2b – Bits 00101011
Register 0x0c – Value 0X20 – Bits 00100000
Register 0x0d – Value 0X08 – Bits 00001000
Register 0x0e – Value 0X02 – Bits 00000010
Register 0x0f – Value 0X0a – Bits 00001010
Register 0x10 – Value 0Xff – Bits 11111111
Register 0x11 – Value 0X71 – Bits 01110001
Register 0x12 – Value 0X15 – Bits 00010101
Register 0x13 – Value 0X0b – Bits 00001011
Register 0x14 – Value 0X28 – Bits 00101000
Register 0x15 – Value 0X0c – Bits 00001100
Register 0x16 – Value 0X12 – Bits 00010010
Register 0x17 – Value 0X47 – Bits 01000111
Register 0x18 – Value 0X32 – Bits 00110010
Register 0x19 – Value 0X3e – Bits 00111110
Register 0x1a – Value 0X00 – Bits 00000000
Register 0x1b – Value 0X00 – Bits 00000000
Register 0x1c – Value 0X00 – Bits 00000000
Register 0x1d – Value 0X00 – Bits 00000000
Register 0x1e – Value 0X00 – Bits 00000000
Register 0x1f – Value 0X40 – Bits 01000000
Register 0x20 – Value 0X00 – Bits 00000000
Register 0x21 – Value 0X00 – Bits 00000000
Register 0x22 – Value 0X00 – Bits 00000000
Register 0x23 – Value 0X00 – Bits 00000000
Register 0x24 – Value 0X05 – Bits 00000101
Register 0x25 – Value 0X00 – Bits 00000000
Register 0x26 – Value 0X03 – Bits 00000011
Register 0x27 – Value 0X93 – Bits 10010011
Register 0x28 – Value 0X55 – Bits 01010101
Register 0x29 – Value 0X55 – Bits 01010101
Register 0x2a – Value 0X55 – Bits 01010101
Register 0x2b – Value 0X55 – Bits 01010101
Register 0x2c – Value 0X55 – Bits 01010101
Register 0x2d – Value 0X55 – Bits 01010101
Register 0x2e – Value 0X55 – Bits 01010101
Register 0x2f – Value 0X55 – Bits 01010101
Register 0x30 – Value 0X90 – Bits 10010000
Register 0x31 – Value 0X40 – Bits 01000000
Register 0x32 – Value 0X40 – Bits 01000000
Register 0x33 – Value 0X00 – Bits 00000000
Register 0x34 – Value 0X00 – Bits 00000000
Register 0x35 – Value 0X0f – Bits 00001111
Register 0x36 – Value 0X00 – Bits 00000000
Register 0x37 – Value 0X00 – Bits 00000000
Register 0x38 – Value 0X00 – Bits 00000000
Register 0x39 – Value 0Xf5 – Bits 11110101
Register 0x3a – Value 0X20 – Bits 00100000
Register 0x3b – Value 0X82 – Bits 10000010
Register 0x3c – Value 0Xff – Bits 11111111
Register 0x3d – Value 0X02 – Bits 00000010
Register 0x3e – Value 0X80 – Bits 10000000
Register 0x3f – Value 0X40 – Bits 01000000
Register 0x40 – Value 0X00 – Bits 00000000
Register 0x41 – Value 0X00 – Bits 00000000
Register 0x42 – Value 0X12 – Bits 00010010

I also started to refactor the code (extracting the Read functionality and format the output in both hexadecimal and binary (convert.ToString in base2 + padleft) to make comparison of register values with the datasheet easier.

The device was not in LoRa mode (Bit 7 of RegOpMode 0x01) so the next step was to read and write registers so I could change its configuration.

RFM95/96/97/98 shield library Part1

Register Read

Over the last couple of weeks I have been working on a Windows 10 IoT Core C# library for my Dragino LoRa GPS hat for Raspberry PI. I initially started with the Dragino.LoRa library but after some experimentation and hacking I decided to write my own library (which is usually not a good idea).

DraginoLoraGPSHat

I wanted a lightweight LoRa only library (hopefully possible to backport to .NetMF) which didn’t try to hide how the Semtech 1276 chip functioned, and in the future could be configured to work with other vendors’ shields (dragino, electronictricks, elecrow, m2m).

I’m also working on an RFM69 Raspberry PI shield based on an electronictricks PCB populated with a RFM69HCW so I figured the experience of building a library would be useful.

The first step was to build a basic universal windows platform (UWP) background task to confirm that I could reliably communicate with the shield over SPI bus by reading a single register value (RegVersion the silicon version specified in the vendor datasheet).

//---------------------------------------------------------------------------------
// Copyright (c) July 2018, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.RegisterRead
{
   using System;
   using System.Diagnostics;
   using System.Threading.Tasks;
   using Windows.ApplicationModel.Background;
   using Windows.Devices.Gpio;
   using Windows.Devices.Spi;

   public sealed class StartupTask : IBackgroundTask
   {
      private const int ChipSelectLine = 25;
      private SpiDevice rfm9XLoraModem;

      public void Run(IBackgroundTaskInstance taskInstance)
      {
         // Have to setup the SPI bus with custom Chip Select line rather than std CE0/CE1
         SpiController spiController = SpiController.GetDefaultAsync().AsTask().GetAwaiter().GetResult();
         var settings = new SpiConnectionSettings(0)
         {
            ClockFrequency = 500000,
            Mode = SpiMode.Mode0,
         };

         GpioController gpioController = GpioController.GetDefault();
         GpioPin chipSelectGpioPin = gpioController.OpenPin(ChipSelectLine);
         chipSelectGpioPin.SetDriveMode(GpioPinDriveMode.Output);
         chipSelectGpioPin.Write(GpioPinValue.High);

         rfm9XLoraModem = spiController.GetDevice(settings);

         while (true)
         {
            byte[] writeBuffer = new byte[]{ 0x42 }; // RegVersion
            byte[] readBuffer = new byte[1];

            chipSelectGpioPin.Write(GpioPinValue.Low);
            rfm9XLoraModem.Write(writeBuffer);
            rfm9XLoraModem.Read(readBuffer);
            chipSelectGpioPin.Write(GpioPinValue.High);

            Debug.WriteLine("RegVersion {0:x2}", readBuffer[0]);

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

The dragino shield has the chip select (also know as slave select) line connected to pin 25 rather than the usual CS0 (pin 24) & CS1 (pin 26) so it has to be manually strobed.

'backgroundTaskHost.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Data\Users\DefaultAccount\AppData\Local\DevelopmentFiles\RegisterRead-uwpVS.Debug_ARM.Bryn.Lewis\System.Diagnostics.Debug.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.
RegVersion 12
RegVersion 12

Next step was to dump all the registers of the HopeRF module.

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

nRF24 Windows 10 IoT Core Background Task

First step is to build a basic Windows 10 IoT Core background task which can receive and display messages sent from a variety of devices across an nRF24L01 wireless link.

If you create a new “Windows IoT Core” “Background Application” project then copy this code into StartupTasks.cs the namespace has to be changed in the C# file, project properties\library\Default namespace and “Package.appxmanifest”\declarations\Entry Point.

/*

Copyright ® 2017 December devMobile Software, All Rights Reserved

THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
PURPOSE.

http://www.devmobile.co.nz

*/
using System;
using System.Diagnostics;
using System.Text;
using Radios.RF24;
using Windows.ApplicationModel.Background;

namespace devmobile.IoTCore.nRF24BackgroundTask
{
    public sealed class StartupTask : IBackgroundTask
    {
      private const byte ChipEnablePin = 25;
      private const byte ChipSelectPin = 0;
      private const byte nRF24InterruptPin = 17;
      private const string BaseStationAddress = "Base1";
      private const byte nRF24Channel = 10;
      private RF24 Radio = new RF24();
      private BackgroundTaskDeferral deferral;

      public void Run(IBackgroundTaskInstance taskInstance)
        {
         Radio.OnDataReceived += Radio_OnDataReceived;
         Radio.OnTransmitFailed += Radio_OnTransmitFailed;
         Radio.OnTransmitSuccess += Radio_OnTransmitSuccess;

         Radio.Initialize(ChipEnablePin, ChipSelectPin, nRF24InterruptPin);
         Radio.Address = Encoding.UTF8.GetBytes(BaseStationAddress);
         Radio.Channel = nRF24Channel;
         Radio.PowerLevel = PowerLevel.High;
         Radio.DataRate = DataRate.DR250Kbps;
         Radio.IsEnabled = true;

         Debug.WriteLine("Address: " + Encoding.UTF8.GetString(Radio.Address));
         Debug.WriteLine("PA: " + Radio.PowerLevel);
         Debug.WriteLine("IsAutoAcknowledge: " + Radio.IsAutoAcknowledge);
         Debug.WriteLine("Channel: " + Radio.Channel);
         Debug.WriteLine("DataRate: " + Radio.DataRate);
         Debug.WriteLine("IsDynamicAcknowledge: " + Radio.IsDyanmicAcknowledge);
         Debug.WriteLine("IsDynamicPayload: " + Radio.IsDynamicPayload);
         Debug.WriteLine("IsEnabled: " + Radio.IsEnabled);
         Debug.WriteLine("Frequency: " + Radio.Frequency);
         Debug.WriteLine("IsInitialized: " + Radio.IsInitialized);
         Debug.WriteLine("IsPowered: " + Radio.IsPowered);

         deferral = taskInstance.GetDeferral();

         Debug.WriteLine("Run completed");
      }

      private void Radio_OnDataReceived(byte[] data)
      {
         // Display as Unicode
         string unicodeText = Encoding.UTF8.GetString(data);
         Debug.WriteLine("Unicode - Payload Length {0} Unicode Length {1} Unicode text {2}", data.Length, unicodeText.Length, unicodeText);

         // display as hex
         Debug.WriteLine("Hex - Length {0} Payload {1}", data.Length, BitConverter.ToString(data));
      }

      private void Radio_OnTransmitSuccess()
      {
         Debug.WriteLine("Transmit Succeeded!");
      }

      private void Radio_OnTransmitFailed()
      {
         Debug.WriteLine("Transmit Failed!");
      }
   }
}

This was displayed in the output window of Visual Studio

Address: Base1
PA: 15
IsAutoAcknowledge: True
Channel: 10
DataRate: DR250Kbps
IsDynamicAcknowledge: False
IsDynamicPayload: True
IsEnabled: True
Frequency: 2410
IsInitialized: True
IsPowered: True
Run completed

Interrupt Triggered: FallingEdge
Unicode – Payload Length 19 Unicode Length 19 Unicode text T  23.8,H  73,V 3.26
Hex – Length 19 Payload 54-20-32-33-2E-38-2C-48-20-20-37-33-2C-56-20-33-2E-32-36
Interrupt Triggered: RisingEdge

Note the odd formatting of the Temperature and humidity values which is due to the way dtostrf function in the Atmel AVR library works.

Also noticed the techfooninja nRF24 library has configurable output power level which I will try to retrofit onto the Gralin NetMF library.

Next, several simple Arduino, devDuino V2.2, Seeeduino V4.2 and Netduino 2/3 clients (plus possibly some others)

Electric Longboard devDuino V2 Controller

I really wanted to get the longboard working so I had a look at buying Wiiceiver from AustinDavid.com.

The source code is available on Github and I had a spare devicter devDuino Sensor Node V2 sitting on my desk. With some modification (changing pins numbers and removing all references to the second LED) I got the wiiceiver code running on my devDuino.

The Electronic Speed Controller(ESC) and the plastic lunch box (containing the batteries and devDuino) are attached to the deck with 3M Command adhesive strips. The first set of command adhesive strips I tried were for hanging pictures and had a Velcro quick release system. This approach was a failure and the ESC & electronics box fell off after 10-15 minutes use. The Velcro backing tape was getting pulled in the wrong direction so was unable to hold the weight of the electronics when vibration levels increased. I tried them because a “quick release” capability would be handy but I have gone back to using conventional 3M Command adhesive strips and these are working well.

devDuino V2 and ESC on longboard

devDuino based controller interfaced with ESC and wireless WiiChuk

Initial rides went well, though I need to recalibrate the acceleration and braking ramp up/down settings to suit my hardware and riding style.

Bill of Materials for this project (Prices as at Feb 2015)

  • Single Motor Mechanical Electric Longboard Kit USD223
  • Turnigy Aerodrive SK3-6364-245kv Brushless Outrunner Motor USD70.68
  • HobbyKing 150A High Performance Brushless Car ESC USD68.99
  • ZIPPY Flightmax 5000mAh battery X 2 USD31.99 each
  • HXT4mm Battery Harness 14AWG for 2 Packs in Series USD2.43
  • HXT 4mm Gold Connector with Protector (10pcs/set)
  • devDuino Sensor Node V2 USD15.99
  • Grove Nunchuck adaptor USD2.90
  • Grove Branch Cable for Servo USD4.90
  • Wireless Nunchuck NZD25.00
  • Moose 9.5×42 Longboard Flush Mount Deck Green Stain NZD57

WARNING – Disconnect the power supply pin on the Grove Branch Cable for Servos as the ESC will supply sufficient current to make the batteries on the devDuino go pop. Wrap some tape around the other servo connector so it can’t cause a short circuit.

Thanks to Austin David for making the code for the Wiiciever open source, if anyone is interested in my code I can tidy it up and share.

/*
 * Pin IDs -- NOT LOCATIONS !!!
 * don't change these ever; see &quot;pinLocation&quot; below for
 * actual locations
 */
#define RED_LED_ID   0
//#define GREEN_LED_ID 1

#define ESC_PPM_ID   2
//#define ESC2_PPM_ID   6
#define ESC_GROUND 00     // hard-wired

//#define WII_POWER_ID 3
//#define WII_GROUND 00     // hard-wired

#define WII_SCL_ID   4
#define WII_SDA_ID   5

I made my devDuino look like a V3 wiiceiver

int pinLocation(int pinID) {
  int pinMap[7][3] = {
  // v1, v2, v3
    {8,   8,  9},  // RED_LED     any digital pin
    {7,   6,  8},  // GREEN_LED   any digital pin
    {10,  9,  3},  // ESC_PPM     PWM required
    {9,  11,  5},  // WII_POWER   any digital pin
    {19, 19, 19}, // WII_SCL     A5, don't change
    {18, 18, 18}, // WII_SDA     A4, don't change
    {0,  10, 0}, // ESC2_PPM    PWM required
  };

The rest of my changes were commenting out all references to the Green LED as the devDuino only has one onboard LED.

Electric Vehicle Camp 2014-06

The Hardware

The software

Flash an LED

OutputPort led = new OutputPort(Pins.ONBOARD_LED, false);
while ( true)
{
   Led.Write(!Led.Read())
   Thread.Sleep(500)
}

Digital Input – Polled

InputPort button = new InputPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled);
OutputPort led = new OutputPort(Pins.ONBOARD_LED, false);
while (true)
{
   led.Write(button.Read());
   Thread.Sleep(1000);
}

Digital Input – Interrupt

static OutputPort interuptled = new OutputPort(Pins.ONBOARD_LED, false);
InterruptPort button = new InterruptPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);
button.OnInterrupt += new NativeEventHandler(button_OnInterrupt);&amp;amp;lt;/span&amp;amp;gt;&amp;amp;lt;/code&amp;amp;gt;

Thread.Sleep(Timeout.Infinite);
static void button_OnInterrupt(uint data1, uint data2, DateTime time)
{
   interuptled.Write(!interuptled.Read());
}

Analog Input

AnalogInput Sensor = new AnalogInput(Cpu.AnalogChannel.ANALOG_0);
while ( true)
{
   Debug.Print( &quot;Value &quot; + Sensor.Read(&quot;F2&quot;));
   Thread.Sleep(500)
}

Pulse Width Modulation Output

AnalogInput brightness = new AnalogInput(AnalogChannels.ANALOG_PIN_A0);
PWM led = new PWM(PWMChannels.PWM_PIN_D5, 1000, 0.0, false);

led.Start();

while (true)
{
   Debug.Print(&amp;amp;quot;Brightness &amp;amp;quot; + led.DutyCycle.ToString("F2"));
   led.DutyCycle = brightness.Read();
   Thread.Sleep(500);
}
led.Stop();

Telemetry – Mobile station

Configure the NRF24L01 library for the  elecfreaks Joystick ShieldV2.4, for more detail see this post 

_module.OnDataReceived += OnReceive;
_module.OnTransmitFailed += OnSendFailure;
_module.OnTransmitSuccess += OnSendSuccess;
_module.Initialize(SPI.SPI_module.SPI1, Pins.GPIO_PIN_D10, Pins.GPIO_PIN_D9, Pins.GPIO_PIN_D1);
_module.Configure(myAddress, channel);
_module.Enable();

Timer joystickPositionUpdates = new Timer(JoyStickTimerProc, null, 500, 500);
Thread.Sleep( Timeout.Infinite ) ;

Send the data to the base station (converting it from Unicode to ASCII)

private void JoyStickTimerProc(object state)
{
   double xVal = x.Read();
   double yVal = y.Read();
   Debug.Print("X " + xVal.ToString("F1") + " Y &" + yVal.ToString("F1"));

   _module.SendTo(baseStationAddress, Encoding.UTF8.GetBytes( xVal.ToString("F1") + " " + yVal.ToString("F1")));
}

Telemetry – Base Station

Configure the NRF24L01 library for the Embedded Coolness board, for more detail see this post

private readonly NRF24L01Plus _module;

_module.OnDataReceived += OnReceive;
_module.OnTransmitFailed += OnSendFailure;
_module.OnTransmitSuccess += OnSendSuccess;

_module.Initialize(SPI.SPI_module.SPI1, Pins.GPIO_PIN_D7, Pins.GPIO_PIN_D3, Pins.GPIO_PIN_D2);
_module.Configure(_myAddress, channel);
_module.Enable();

Display the inbound message (converting it from ASCII to Unicode)

private void OnReceive(byte[] data)
{
string message = new String(Encoding.UTF8.GetChars(data));
Debug.Print("Receive " + message); ;
}