NetMF Electric Longboard Part 1

When I first built my electric longboard I started with a devicter devduino V2 running a modified version of the Wiiceiver code from AustinDavid.com. This plug n play setup has worked really well and proved quite robust considering the hostile environment it is working in.

devduino V2 longboard controller, wiresless Wiichuck ESC and batteries

devduino V2 controller

I had been thinking about purchasing a dual motor kit and experimenting with traction control and anti lock braking (after a couple of close calls indoor on a tiled floor) which would require a bit more processing power.

The first version of my NetMF controller will be powered by a GHI Electronics FEZ Lemur which is an ARM Cortex M4 based System on a Chip (SoC) running at 84MHz.

The FEZ Lemur is an Arduino pinout-compatible mainboard but with the Inter-Integrated Circuit (I2C) pins on D2-Serial Data Line(SDA) and D3-Serial Clock Line (SCL).

My first proof of concept (PoC) uses a standard wii Nunchuk and some jumper wires.

FEZ Lemur and Nunchuck connected with jumper wires and seeedstudio adaptor

FEZ Lemur Wii NunChuck interface

Bill of materials (Prices in USD as at Mar 2016)

I then tested my hardware setup with an application based on the driver software written by Szymon Kobalczyk and it worked. I have used this software on a couple of projects but have never been able to get to work with my wireless Wii Nunchuk.

I compared the Arduino wiiceiver code and the C# version and found the initialisation process was different. I then did some research and found that the WiiNunChuk driver of the .Net Micro Framework Toolbox by Stefan Thoolen used a similar approach as the wiiceiver code.

using System;
using System.Threading;
using Microsoft.SPOT;
using Toolbox.NETMF.Hardware;

public class Program
{
   public static void Main()
   {
      WiiNunchuk nunchuk = new WiiNunchuk();

      while (true)
      {
         // Reads all values
         nunchuk.Read();

         Debug.Print(nunchuk.AnalogStickX + " " + nunchuk.AnalogStickY);

         Thread.Sleep(100);
      }
   }
}

I then tried the wireless Wii NunChuk device and it worked (The tape is to stop the wireless dongle falling off due to vibration when mounted on my skateboard)

FEZ Lemur Wireless Wii NunChuck interface

FEZ Lemur Wireless Wii NunChuck interface

The PoC was working so now I needed to make it more robust and plug n play. For many of my projects I use the Seeedstudio Grove system which provides plug n play digital inputs, digital outputs, analog inputs and I2C connectivity for *duino (and other) format devices.

The Seeedstudio base shield V2 can be configured for *duino devices which implement I2C connectivity on the Analog Input pins 4 & 5 or dedicated pins SDA & SCL pins.

FEZ Lemur Wireless Wii NunChuck PnP interface

FEZ Lemur Wireless Wii NunChuck interface

To get the SeeedStudion Base Shield to work with my FEZ Lemur I had to put a twist in the jumper cable to get the SDA & SCL the right way round and plug it into the D2 socket.

NOTE : put some tape on the top of the MicroSD card socket to stop a accidental short circuit.

Seeedstudio 5CM cable with SDA & SCL Pins reversed.

5CM cable with SDA & SCL Pins reversed

Connecting to G30_G30...Connected
128 128
128 128
128 128
128 128
128 128
128 128
128 128
128 170
80 209
63 255
128 255
128 255
128 255
128 255
244 255
255 255
255 250
255 210
255 128
255 128
255 128
255 107
255 5
255 0
255 0
128 0
128 0
41 0
24 0
0 103
0 128
0 128
8 235
77 255
128 255
128 255
128 128
128 128

Next step is to get inerface to the 150A Electronic Speed Control(ESC) working.

Fez Cobra III Analog Input read rates

In other blog posts I have measured the AnalogInput read rate of my Netduino, FEZ Lemur and FEZ Panda III devices and was surprised by some of the numbers. Now, I have another project which uses a GHI Electronics FEZ Covbra III so have done another quick test.

This is just a simple test, not terribly representative of real world I just wanted to get comparable numbers.

public static void Main()
{
   int value;
   AnalogInput x1 = new AnalogInput(FEZLemur.AnalogInput.D19);
   Stopwatch stopwatch = Stopwatch.StartNew();

   Debug.Print("Starting");

   stopwatch.Start();
   for (int i = 0; i <; SampleCount; i++)
   {
      value = x1.ReadRaw();
   }
   stopwatch.Stop();

   Debug.Print("Duration = " + stopwatch.ElapsedMilliseconds.ToString() + " mSec " + (SampleCount * 1000 / stopwatch.ElapsedMilliseconds).ToString() + "/sec";);
}

Fez CobraIII 120 MHz CPU
Duration = 9297 mSec 10756/sec
Duration = 9297 mSec 10756/sec
Duration = 9298 mSec 10755/sec
Duration = 9296 mSec 10757/sec
Duration = 9298 mSec 10755/sec

Something is not quite right here need to look at my code and the numbers some more.

Mikrobus.Net Quail Robot

In a previous post I had replaced a Netduino and Elecfreaks Joystick shield based remote control with a MikrobusNet Quail, thumbstick click and an nRF-C click. The next step was to replace the Netduino on the robot chassis with a MikrobusNet Quail, a pair of DC Motor Clicks and an nRF-C click.

Bill of materials (prices in USD as at Feb 2016)

The first version of the robot uses a pair of battery packs one for the motors the other for the Quail board.

MikrobusNetQual4WDRobot

The drivers developed by MikroBUSNet team greatly reduced the amount of code I had to write to get the robot to work.

public class Program
{
   private static double Scale = 100.0;
   private static byte RobotControlChannel = 10;
   private static byte[] ControllerAddress = Encoding.UTF8.GetBytes("RC1");
   private static byte[] RobotAddress = Encoding.UTF8.GetBytes("RB1");
   private static TimeSpan MessageMaximumInterval = new TimeSpan(0, 0, 1);
   private static DateTime _MessageLastReceivedAt = DateTime.UtcNow;
   private static DCMotorClick motor1 = new DCMotorClick(Hardware.SocketOne);
   private static DCMotorClick motor2 = new DCMotorClick(Hardware.SocketTwo);

public static void Main()
{
   NRFC nrf = new NRFC(Hardware.SocketFour);
   nrf.Configure(RobotAddress, RobotControlChannel);
   nrf.OnTransmitFailed += nrf_OnTransmitFailed;
   nrf.OnTransmitSuccess += nrf_OnTransmitSuccess;
   nrf.OnDataReceived += nrf_OnDataReceived;
   nrf.Enable();

   Timer CommunicationsMonitorTimer = new Timer(CommunicationsMonitorTimerProc, null, 500, 500);

   Thread.Sleep(Timeout.Infinite);
}

static void nrf_OnDataReceived(byte[] data)
{
   Hardware.Led1.Write(true);
   _MessageLastReceivedAt = DateTime.UtcNow;

   if (data.Length != 5)
   {
   return;
   }

   Debug.Print("M1D=" + data[0].ToString() + " M2D=" + data[1].ToString() + " M1S=" + data[2].ToString() + " M2S=" + data[3].ToString());
   if (data[0] == 1)
   {
      motor1.Move(DCMotorClick.Directions.Forward, (data[2] / Scale ));
   }
   else
   {
     motor1.Move(DCMotorClick.Directions.Backward, (data[2] / Scale ));
   }

   if (data[1] == 1)
   {
      motor2.Move(DCMotorClick.Directions.Forward, (data[3] / Scale ));
   }
   else
   {
      motor2.Move(DCMotorClick.Directions.Backward, (data[3] / Scale ));
   }
}

private static void CommunicationsMonitorTimerProc(object status)
{
   if ((DateTime.UtcNow - _MessageLastReceivedAt) > MessageMaximumInterval)
   {
      Debug.Print("Communications timeout");

      motor1.Move(MBN.Modules.DCMotorClick.Directions.Forward, 0.0);
      motor2.Move(MBN.Modules.DCMotorClick.Directions.Forward, 0.0);
   }
}

I have kept the communications monitoring functionality which stops the motors when the robot gets out of range of the remote control software fails.

 

 

 

Mikrobus.Net Quail Robot Remote Control

In a previous pair of posts  (part1 & part2) in February 2014 I built a 4WD Robot and remote control using a pair of Netduinos, an elecfreaks Smart Car Chassis 4WD, an elecfreaks joystick 2.4, an Embedded coolness nRF24Lo1 shield and a Pololu MC33926 motor shield.

My Quail device looked like a good platform for building a handheld control with a different form factor.

Bill of materials (prices in USD as at Jan 2016)

Quail4WDRobotController

The Quail device and battery pack aren’t quite small enough to work with one hand. A Mikrobus.Net Dalmatian or Tuatara based remote might be easier to use.

I tried using the thumbstick button pushed message for the horn functionality but it made the throttle and heading jump.

The first version of the code is just to test the wireless link, the motor speed code needs a little work.(Currently the device won’t rotate with motors going in opposite directions)

public class Program
{
   private const double Deadband = 0.1;
   private static double Scale = 100.0;
   private static byte RobotControlChannel = 10;
   private static byte[] ControllerAddress = Encoding.UTF8.GetBytes(&amp;quot;RC1&amp;quot;);
   private static byte[] RobotAddress = Encoding.UTF8.GetBytes(&amp;quot;RB1&amp;quot;);

   public static void Main()
   {
      ThumbstickClick thumbStick = new ThumbstickClick(Hardware.SocketThree);
      thumbStick.ThumbstickOrientation = ThumbstickClick.Orientation.RotateZeroDegrees;
      thumbStick.Calibrate();

      NRFC nrf = new NRFC(Hardware.SocketFour);
      nrf.Configure(ControllerAddress, RobotControlChannel );
      nrf.OnTransmitFailed += nrf_OnTransmitFailed;
      nrf.OnTransmitSuccess += nrf_OnTransmitSuccess;
      nrf.Enable();

      while (true)
      {
         byte motor1Direction, motor2Direction;
         byte motor1Speed, motor2Speed;
         double x = thumbStick.GetPosition().X;
         double y = thumbStick.GetPosition().Y;

         Debug.Print("X=: + x.ToString("F1") + " Y=" + y.ToString("F1") + " IsPressed=" + thumbStick.IsPressed);

         // See if joystick x or y is in centre deadband
         if (System.Math.Abs(x) < Deadband)
         {
            x = 0.0;
         }

         // See if joystick y is in centre deadband
         if (System.Math.Abs(y) < Deadband)
         {
            y = 0.0;
         }

         // Set direction of both motors, no swivel on spot yet
         if (y >= 0.0)
         {
            motor1Direction = (byte)1;
            motor2Direction = (byte)1;
         }
         else
         {
            motor1Direction = (byte)0;
            motor2Direction = (byte)0;
         }

         // Straight ahead/backward
         if (x == 0.0)
         {
            motor1Speed = (byte)(System.Math.Abs(y) * Scale);
            motor2Speed = (byte)(System.Math.Abs(y) * Scale);
         }
         // Turning right
         else if (x > 0.0)
         {
            motor1Speed = (byte)(System.Math.Abs(y) * Scale);
            motor2Speed = (byte)(System.Math.Abs(y) * (1.0 - System.Math.Abs(x)) * Scale);
         }
         // Turning left
         else
         {
            motor1Speed = (byte)(System.Math.Abs(y) * (1.0 - System.Math.Abs(x)) * Scale);
            motor2Speed = (byte)(System.Math.Abs(y) * Scale);
         }

         Debug.Print("X=" + x.ToString("F1") + " Y=" + y.ToString("F1") + " IsPressed=" + thumbStick.IsPressed + " M1D=" + motor1Direction.ToString() + " M2D=" + motor2Direction.ToString() + " M1S=" + motor1Speed.ToString() + " M2S=" + motor2Speed.ToString());

         byte[] command =
         {
            motor1Direction,
            motor2Direction,
            motor1Speed,
            motor2Speed,
            (byte)0)
         };
         nrf.SendTo(RobotAddress, command );

         MBN.Hardware.Led1.Write(true);

         Thread.Sleep(250);
      }
   }

   static void nrf_OnTransmitSuccess()
   {
     MBN.Hardware.Led1.Write(false);
     Debug.Print("nrf_OnTransmitSuccess");
   }

   static void nrf_OnTransmitFailed()
   {
      Debug.Print("nrf_OnTransmitFailed");
   }
}

The Mikrobus.Net team have done a great job with the number and quality of the drivers for the Mikroe click boards. The Mikroe click boards are individually packaged with professionally written click specific and handling instructions.

Mikrobus.Net Quail, Weather & nRF-C clicks and xively

My next proof of concept uses a Weather click and nRF C click to upload temperature and humidity data to a Xively gateway running on a spare Netduino 2 Plus. I have a couple of Azure Event hub gateways (direct & queued) which require a Netduino 3 Wifi (for TLS/AMQPS support) and I’ll build a client for them in a coming post.

I initially purchased an nRF T click but something wasn’t quite right with its interrupt output. The interrupt line wasn’t getting pulled low at all so there were no send success/failure events. If I disabled the pull up resistor and strobed the interrupt pin on start-up the device would work for a while.


using (OutputPort Int = new OutputPort(socket.Int, true))
{
 Int.Write(true);
};

...

_irqPin = new InterruptPort(socket.Int, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);

The code sends a reading every 10 seconds and has been running for a couple of days. It strobes Led1 for each successful send and turns on Led2 when a send fails.

private static readonly byte[] deviceAddress = Encoding.UTF8.GetBytes(&quot;Quail&quot;);
private static readonly byte[] gatewayAddress = Encoding.UTF8.GetBytes(&quot;12345&quot;);
private const byte gatewayChannel = 10;
private const NRFC.DataRate gatewayDataRate = NRFC.DataRate.DR1Mbps;
private const int XivelyUpdateDelay = 10000;
private const char XivelyGatewayChannelIdTemperature = 'J';
private const char XivelyGatewayChannelIdHumidity = 'K';

public static void Main()
{
   NRFC nRF24Click = new NRFC(Hardware.SocketFour);
   nRF24Click.Configure(deviceAddress, gatewayChannel, gatewayDataRate);
   nRF24Click.OnTransmitFailed += nRF24Click_OnTransmitFailed;
   nRF24Click.OnTransmitSuccess += nRF24Click_OnTransmitSuccess;
   nRF24Click.Enable();

   // Configure the weather click
   WeatherClick weatherClick = new WeatherClick(Hardware.SocketOne, WeatherClick.I2CAddresses.Address0);
   weatherClick.SetRecommendedMode(WeatherClick.RecommendedModes.WeatherMonitoring);

   Thread.Sleep(XivelyUpdateDelay);

   while (true)
   {
      string temperatureMessage = XivelyGatewayChannelIdTemperature + weatherClick.ReadTemperature().ToString("F1");
      Debug.Print(temperatureMessage);
      MBN.Hardware.Led1.Write(true);
      nRF24Click.SendTo(gatewayAddress, Encoding.UTF8.GetBytes(temperatureMessage));

      Thread.Sleep(XivelyUpdateDelay);

      string humidityMessage = XivelyGatewayChannelIdHumidity + weatherClick.ReadHumidity().ToString("F1");
      Debug.Print(humidityMessage);
      MBN.Hardware.Led1.Write(true);
      nRF24Click.SendTo(gatewayAddress, Encoding.UTF8.GetBytes(humidityMessage));

      Thread.Sleep(XivelyUpdateDelay);
   }
}

static void nRF24Click_OnTransmitSuccess()
{
   MBN.Hardware.Led1.Write(false);
   if (MBN.Hardware.Led2.Read())
   {
      MBN.Hardware.Led2.Write(false);
   }

   Debug.Print("nRF24Click_OnTransmitSuccess");
}

static void nRF24Click_OnTransmitFailed()
{
   MBN.Hardware.Led2.Write(true);

   Debug.Print("nRF24Click_OnTransmitFailed");
}

I need to have a look at interfacing some more sensors and soak testing the solution.

The MikroBus.Net team have done a great job with the number & quality of the drivers they have available.

Fez Lemur & Panda III AnalogInput read rates

I had previously have measured the AnalogInput read rate of my Netduino devices and was surprised by some of the numbers. Now, I have another project in the planning phase which will be using a GHI Electronics Fez Lemur or Fez Panda III device and had time for a quick test.

This is just a simple test, not terribly representative of real world just to get comparable numbers.

public static void Main()
{
   int value;
   AnalogInput x1 = new AnalogInput(FEZLemur.AnalogInput.A0);
   Stopwatch stopwatch = Stopwatch.StartNew();

   Debug.Print("Starting");

   stopwatch.Start();
   for (int i = 0; i < SampleCount; i++)
   {
      value = x1.ReadRaw();
   }
   stopwatch.Stop();

   Debug.Print("Duration = " + stopwatch.ElapsedMilliseconds.ToString() + " mSec " + (SampleCount * 1000 / stopwatch.ElapsedMilliseconds).ToString() + "/sec");
}

Fez Lemur 84 MHz CPU
Duration = 2855 mSec 35026/sec
Duration = 2854 mSec 35038/sec
Duration = 2854 mSec 35038/sec
Duration = 2854 mSec 35038/sec
Duration = 2861 mSec 34952/sec

Duration = 2856 mSec 35014/sec
Duration = 2854 mSec 35038/sec
Duration = 2855 mSec 35026/sec
Duration = 2854 mSec 35038/sec
Duration = 2854 mSec 35038/sec

Fez Panda III 180MHz CPU
Duration = 1799 mSec 55586/sec
Duration = 1799 mSec 55586/sec
Duration = 1799 mSec 55586/sec
Duration = 1799 mSec 55586/sec
Duration = 1799 mSec 55586/sec

Duration = 1799 mSec 55586/sec
Duration = 1799 mSec 55586/sec
Duration = 1799 mSec 55586/sec
Duration = 1799 mSec 55586/sec
Duration = 1799 mSec 55586/sec

It looks like the GHI Team have a performant implementation of AnalogInput.ReadRaw()

Mikrobus.Net Quail, EthClick and xively

My second proof of concept application for the Mikrobus.Net Quail and EthClick uploads temperature and humidity data to Xively every 30 seconds for display and analysis.

Temperature and humidity Xively data stream

Temperature and humidity Xively data stream

The Xively REST API uses an HTTP PUT which initially didn’t work because the payload was not getting attached.

I patched the AssembleRequest method in the EtherClick driver to fix this issue.

private byte[] AssembleRequest()
{
   var a = RequestType;
   a += " " + Path + " " + Protocol + "\r\nHost: ";
   a += Host + "\r\n";

   foreach (object aHeader in Headers.Keys)
      a += (string)aHeader + ": " + (string)Headers[aHeader] + "\r\n";

   a += "\r\n"; // Cache-Control: no-cache\r\n  //Accept-Charset: utf-8;\r\n

   if (Content != null && Content != string.Empty && (RequestType == "POST" || RequestType == "PUT")) a += Content;

   return Encoding.UTF8.GetBytes(a);
}

The code reads the WeatherClick temperature and humidity values then assembles a CSV payload which it uploads with an HTTP PUT

</pre>
public class Program
{
   private const string xivelyHost = @"api.xively.com";
   private const string xivelyApiKey = @"YourAPIKey";
   private const string xivelyFeedId = @"YourFeedID";

   public static void Main()
   {
      WeatherClick weatherClick = new WeatherClick(Hardware.SocketOne, WeatherClick.I2CAddresses.Address0);
      weatherClick.SetRecommendedMode(WeatherClick.RecommendedModes.WeatherMonitoring);

      EthClick ethClick = new EthClick(Hardware.SocketTwo);
      ethClick.Start(ethClick.GenerateUniqueMacAddress("devMobileSoftware"), "QuailDevice");

      // Wait for an internet connection
      while (true)
      {
         if (ethClick.ConnectedToInternet)
         {
            Debug.Print("Connected to Internet");
            break;
         }
         Debug.Print("Waiting on Internet connection");
      }

      while (true)
      {
         Debug.Print("T " + weatherClick.ReadTemperature().ToString("F1") + " H " + weatherClick.ReadHumidity().ToString("F1") + " P " + weatherClick.ReadPressure(PressureCompensationModes.Uncompensated).ToString("F1"));

         HttpRequest request = new HttpRequest(@"http://" + xivelyHost + @"/v2/feeds/" + xivelyFeedId + @".csv");
         request.Host = xivelyHost;
         request.RequestType = "PUT";
         request.Headers.Add("Content-Type", "text/csv");
         request.Headers.Add("X-ApiKey", xivelyApiKey );

         request.Content = "OfficeT," + weatherClick.ReadTemperature().ToString("F1") + "\r\n" + "OfficeH," + weatherClick.ReadHumidity().ToString("F1") ;
         request.Headers.Add("Content-Length", request.Content.Length.ToString());

         var response = request.Send();
         if (response != null)
         {
            Debug.Print("Response: " + response.Message);
         }
         else
         {
            Debug.Print("No response");
         }
      Thread.Sleep(30000);
      }
   }
}
MikrobustNet Quail with Eth and Weather Clicks

MikrobustNet Quail with Eth and Weather Clicks

This proof of concept code appears to be reliable and has run for days at a time. The IP stack looks like it needs a bit more work.