netNF Electric Longboard Part 4

The Rideable Prototype

After some experimentation I gave up on the radio control(RC) servo library for controlling my Longboard’s Electronic Speed Control(ESC).

My new longboard controller uses the following parts

  • Netduino 3 Wifi
  • Generic wireless wii nuchuk
  • generic Arduino protoshield

I reused the initial protoshield and only had to shift the PWM output to the ESC from pin 8 to pin 7.

FEZ Panda III Protoshield for longboard with RC Servo for testing
Protoshield for longboard RC Servo test
public class Program
{
   private const double PulseFrequency = 50.0;
   private const double PulseDurationMinimum = 0.05; // 1000uSec
   private const double PulseDurationMaximum = 0.1; // 2000uSec
   private const double WiiNunchukYMinimum = 0.0;
   private const double WiiNunchukYMaximum = 255.0;
   private const int ThrottleUpdatePeriod = 100;

   public static void Main()
   {
      Debug.WriteLine("devMobile.Longboard starting");
      Debug.WriteLine($"I2C:{I2cDevice.GetDeviceSelector()}");
      Debug.WriteLine($"PWM:{PwmController.GetDeviceSelector()}");

      try
      {
         Debug.WriteLine("LED Starting");
         GpioPin led = GpioController.GetDefault().OpenPin(PinNumber('A', 10));
         led.SetDriveMode(GpioPinDriveMode.Output);
         led.Write(GpioPinValue.Low);

         Debug.WriteLine("LED Starting");
         WiiNunchuk nunchuk = new WiiNunchuk("I2C1");

         Debug.WriteLine("ESC Starting");
         PwmController pwm = PwmController.FromId("TIM5");
         PwmPin pwmPin = pwm.OpenPin(PinNumber('A', 1));
         pwmPin.Controller.SetDesiredFrequency(PulseFrequency);
         pwmPin.Start();

         Debug.WriteLine("Thread.Sleep Starting");
         Thread.Sleep(2000);

         Debug.WriteLine("Mainloop Starting");
         while (true)
         {
            nunchuk.Read();

            double duration = Map(nunchuk.AnalogStickY, WiiNunchukYMinimum, WiiNunchukYMaximum, PulseDurationMinimum, PulseDurationMaximum);
            Debug.WriteLine($"Value:{nunchuk.AnalogStickY} Duration:{duration:F3}");

            pwmPin.SetActiveDutyCyclePercentage(duration);
            led.Toggle();
            Thread.Sleep(ThrottleUpdatePeriod);
         }
      }
      catch (Exception ex)
      {
         Debug.WriteLine(ex.Message);
      }
   }

   private static int PinNumber(char port, byte pin)
   {
      if (port < 'A' || port > 'J')
         throw new ArgumentException();

      return ((port - 'A') * 16) + pin;
   }

   private static double Map(double x, double inputMinimum, double inputMaximum, double outputMinimum, double outputMaximum)
   {
      return (x - inputMinimum) * (outputMaximum - outputMinimum) / (inputMaximum - inputMinimum) + outputMinimum;
   }
}

The nanoFramework code polls the wii nunchuk for the joystick position every 100mSec and then updates the PWM duty cycle.

By convention the ESSC PWM frequency is 50Hz (a pulse ever 20mSec) and the duration of the pulse is 1000uSec(minimum throttle) to 2000uSec(maximum throttle), note the change of units.

After converting to the same units there is a pulse every 20mSec and its duration is 1mSec too 2mSec. Then converting the durations to the active duty cycle percentage (for the PWM SetActiveDutyCyclePercentage) the duration of the pulse is 5% to 10%.

I need to re-calibrate the ESC for these durations and ensure that reverse is disabled. Then tinker with the brake (braking percent & percent drag brake) and acceleration(initial acceleration low, medium, high, very high) configurations of my ESC to make the longboard easier to ride.

Next I will look at configurable throttle maps (to make it easier for new and different weight users), then using one of the wii-nunchuk buttons for cruise control (keeping the throttle steady when riding is difficult) and how the software reacts when the connection with nunchuk fails

netNF Electric Longboard Part 1

Wiichuck connectivity

Roughly four years ago I build myself an electric longboard as summer transport. It initially had a controller built with a devDuino V2.2 which after a while I “upgraded” to a GHI Electronics .NET Microframework device.

Configuring the original netMF based longboard

Now that GHI Electronics no longer supports the FEZ Panda III I figured upgrading to a device that runs the nanoFramework would be a good compromise.

I control the speed of the longboard with a generic wireless wii nunchuk. So my first project is porting the .NET Micro Framework Toolbox code to the nanoFramework.

wireless controller test rig

My test rig uses (prices as at Aug 2020) the following parts

  • Netduino 3 Wifi
  • Grove-Base Shield V2.0 for Arduino USD4.45
  • Grove-Universal 4 Pin Bucked 5cm cable(5 PCs Pack) USD1.90
  • Grove-Nunchuck USD2.90
  • Generic wireless WII nunchuk

My changes were mainly related to the Inter Integrated Circuit(I2C) configuration and the reading+writing of registers.

/// <summary>
/// Initialises a new Wii Nunchuk
/// </summary>
/// <param name="busId">The unique identifier of the I²C to use.</param>
/// <param name="slaveAddress">The I²C address</param>
/// <param name="busSpeed">The bus speed, an enumeration that defaults to StandardMode</param>
/// <param name="sharingMode">The sharing mode, an enumeration that defaults to Shared.</param>
public WiiNunchuk(string busId, ushort slaveAddress = 0x52, I2cBusSpeed busSpeed = I2cBusSpeed.StandardMode, I2cSharingMode sharingMode = I2cSharingMode.Shared)
   {
      I2cTransferResult result;

      // This initialisation routine seems to work. I got it at http://wiibrew.org/wiki/Wiimote/Extension_Controllers#The_New_Way
      Device = I2cDevice.FromId(busId, new I2cConnectionSettings(slaveAddress)
      {
         BusSpeed = busSpeed,
         SharingMode = sharingMode,
      });

      result = Device.WritePartial(new byte[] { 0xf0, 0x55 });
      if (result.Status != I2cTransferStatus.FullTransfer)
      {
         throw new ApplicationException("Something went wrong reading the Nunchuk. Did you use proper pull-up resistors?");
      }

      result = Device.WritePartial(new byte[] { 0xfb, 0x00 });
      if (result.Status != I2cTransferStatus.FullTransfer)
      {
         throw new ApplicationException("Something went wrong reading the Nunchuk. Did you use proper pull-up resistors?");
      }

      this.Device.Write(new byte[] { 0xf0, 0x55 });
      this.Device.Write(new byte[] { 0xfb, 0x00 });
   }

   /// <summary>
   /// Reads all data from the nunchuk
   /// </summary>
   public void Read()
   {
      byte[] WaitWriteBuffer = { 0 };
      I2cTransferResult result;

      result = Device.WritePartial(WaitWriteBuffer);
      if (result.Status != I2cTransferStatus.FullTransfer)
      {
         throw new ApplicationException("Something went wrong reading the Nunchuk. Did you use proper pull-up resistors?");
      }

      byte[] ReadBuffer = new byte[6];
      result = Device.ReadPartial(ReadBuffer);
      if (result.Status != I2cTransferStatus.FullTransfer)
      {
         throw new ApplicationException("Something went wrong reading the Nunchuk. Did you use proper pull-up resistors?");
      }

      // Parses data according to http://wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck#Data_Format

      // Analog stick
      this.AnalogStickX = ReadBuffer[0];
      this.AnalogStickY = ReadBuffer[1];

      // Accelerometer
      ushort AX = (ushort)(ReadBuffer[2] << 2);
      ushort AY = (ushort)(ReadBuffer[3] << 2);
      ushort AZ = (ushort)(ReadBuffer[4] << 2);
      AZ += (ushort)((ReadBuffer[5] & 0xc0) >> 6); // 0xc0 = 11000000
      AY += (ushort)((ReadBuffer[5] & 0x30) >> 4); // 0x30 = 00110000
      AX += (ushort)((ReadBuffer[5] & 0x0c) >> 2); // 0x0c = 00001100
      this.AcceleroMeterX = AX;
      this.AcceleroMeterY = AY;
      this.AcceleroMeterZ = AZ;

      // Buttons
      ButtonC = (ReadBuffer[5] & 0x02) != 0x02;    // 0x02 = 00000010
      ButtonZ = (ReadBuffer[5] & 0x01) != 0x01;    // 0x01 = 00000001
}

The nanoFramework code polls for the joystick position and accelerometer values every 100mSec

public class Program
{
   public static void Main()
   {
      Debug.WriteLine("devMobile.Longboard.WiiNunchuckTest starting");
      Debug.WriteLine(I2cDevice.GetDeviceSelector());

      try
      {
         WiiNunchuk nunchuk = new WiiNunchuk("I2C1");

         while (true)
         {
            nunchuk.Read();

            Debug.WriteLine($"JoyX: {nunchuk.AnalogStickX} JoyY:{nunchuk.AnalogStickY} AX:{nunchuk.AcceleroMeterX} AY:{nunchuk.AcceleroMeterY} AZ:{nunchuk.AcceleroMeterZ} BtnC:{nunchuk.ButtonC} BtnZ:{nunchuk.ButtonZ}");

            Thread.Sleep(100);
         }
      }
      catch (Exception ex)
      {
         Debug.WriteLine(ex.Message);
      }
   }
}

The setup to use for the I2C port was determined by looking at the board.h and target_windows_devices_I2C_config.cpp file

//
// Copyright (c) 2018 The nanoFramework project contributors
// See LICENSE file in the project root for full license information.
//

#include <win_dev_i2c_native_target.h>

//////////
// I2C1 //
//////////

// pin configuration for I2C1
// port for SCL pin is: GPIOB
// port for SDA pin is: GPIOB
// SCL pin: is GPIOB_6
// SDA pin: is GPIOB_7
// GPIO alternate pin function is 4 (see alternate function mapping table in device datasheet)
I2C_CONFIG_PINS(1, GPIOB, GPIOB, 6, 7, 4)

Then checking this against the Netduino 3 Wifi schematic.

This image has an empty alt attribute; its file name is netduinoschematic-1.jpg

After some experimentation with how to detect if an I2C read or write had failed the debugging console output began displaying reasonable value

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.Longboard.WiiNunchuckTest starting
I2C1
JoyX: 128 JoyY:128 AX:520 AY:508 AZ:708 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:520 AY:504 AZ:716 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:524 AY:508 AZ:716 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:524 AY:536 AZ:708 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:516 AY:528 AZ:724 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:492 AY:524 AZ:720 BtnC:True BtnZ:False
JoyX: 128 JoyY:128 AX:508 AY:528 AZ:700 BtnC:True BtnZ:False
JoyX: 128 JoyY:128 AX:504 AY:532 AZ:716 BtnC:True BtnZ:False
JoyX: 128 JoyY:128 AX:512 AY:532 AZ:724 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:516 AY:532 AZ:712 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:520 AY:532 AZ:708 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:524 AY:532 AZ:708 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:480 AY:504 AZ:688 BtnC:True BtnZ:True
JoyX: 128 JoyY:128 AX:480 AY:520 AZ:728 BtnC:False BtnZ:True
JoyX: 128 JoyY:128 AX:512 AY:520 AZ:704 BtnC:False BtnZ:True
JoyX: 128 JoyY:128 AX:512 AY:548 AZ:708 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:504 AY:516 AZ:728 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:548 AY:536 AZ:704 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:500 AY:528 AZ:728 BtnC:True BtnZ:False
JoyX: 128 JoyY:128 AX:496 AY:524 AZ:716 BtnC:True BtnZ:False
JoyX: 128 JoyY:128 AX:528 AY:536 AZ:696 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:540 AY:540 AZ:720 BtnC:False BtnZ:False
JoyX: 128 JoyY:128 AX:500 AY:520 AZ:684 BtnC:False BtnZ:False
JoyX: 128 JoyY:0 AX:520 AY:508 AZ:696 BtnC:False BtnZ:False
JoyX: 29 JoyY:0 AX:488 AY:576 AZ:716 BtnC:False BtnZ:False
JoyX: 0 JoyY:128 AX:532 AY:540 AZ:700 BtnC:False BtnZ:False
JoyX: 0 JoyY:128 AX:492 AY:512 AZ:708 BtnC:False BtnZ:False
JoyX: 0 JoyY:128 AX:492 AY:516 AZ:708 BtnC:False BtnZ:False
JoyX: 0 JoyY:128 AX:504 AY:512 AZ:708 BtnC:False BtnZ:False
JoyX: 27 JoyY:128 AX:508 AY:520 AZ:700 BtnC:False BtnZ:False
JoyX: 106 JoyY:128 AX:504 AY:516 AZ:700 BtnC:False BtnZ:False
JoyX: 0 JoyY:128 AX:496 AY:520 AZ:700 BtnC:False BtnZ:False
JoyX: 0 JoyY:128 AX:512 AY:532 AZ:716 BtnC:False BtnZ:False
JoyX: 0 JoyY:128 AX:500 AY:516 AZ:708 BtnC:False BtnZ:False
JoyX: 85 JoyY:113 AX:500 AY:536 AZ:720 BtnC:False BtnZ:False
JoyX: 128 JoyY:110 AX:512 AY:532 AZ:712 BtnC:False BtnZ:False
JoyX: 128 JoyY:90 AX:516 AY:528 AZ:716 BtnC:False BtnZ:False
JoyX: 128 JoyY:43 AX:508 AY:468 AZ:660 BtnC:False BtnZ:False
JoyX: 128 JoyY:0 AX:508 AY:532 AZ:712 BtnC:False BtnZ:False
JoyX: 128 JoyY:0 AX:496 AY:524 AZ:716 BtnC:False BtnZ:False

The next test rig will be getting Pulse Width Modulation(PWM) working.

NetMF Electric Longboard Part 2

In part 1 I got the wireless WiiChuck working with a plug n play setup using a SeeedStudio Grove base shield, Wii Chuck adaptor, modified 5CM cable and FEZ Lemur. The FEZ Lemur is limited to a 5VDC supply which wasn’t going to work so well with the 6VDC output of my HobbyKing HK150 ESC without some extra electronics.

GHI Electronics also sell the FEZ Panda III which has a 6VDC-9VDC voltage range and more processing power which would be useful for my next project a dual motor longboard with traction control and anti lock braking .

I also figured vibration could be a problem for the plug n play setup so I built a custom shield for my wireless Wii Chuck dual motor longboard control using an Arduino protoshield, and a Wii-Nunchuck breakout board. The shield provides a solid mount for the wireless Wii chuck dongle and power for the NetMF board from the output of the ESC.

 

FEZ Panda III Protoshield for longboard with RC Servo for testing

FEZ Panda II Protoshield for longboard RC Servo test

I tested the shield wiring using a Radio Control (RC) Servo so the scope of disaster was greatly reduced. (The red jumper wire is supplying 3v3 to the servo for testing)

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

public class Program
{
   private const int NunchukYMinimum = 0;
   private const int NunchukYMaximum = 255;
   private const int PWMPeriodMicroseconds = 20000;
   private const int DurationMinimum = 1250;
   private const int DurationMaximum = 1750;

   public static void Main()
   {
      WiiNunchuk Nunchuk = new WiiNunchuk();
      PWM pwm = new PWM(GHI.Pins.FEZPandaIII.PwmOutput.D6, PWMPeriodMicroseconds, DurationMinimum, PWM.ScaleFactor.Microseconds, false);

      pwm.Start();

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

         Debug.Print(Nunchuk.AnalogStickX.ToString() + " " + Nunchuk.AnalogStickY.ToString());

         uint duration = (uint)map(Nunchuk.AnalogStickY, NunchukYMinimum, NunchukYMaximum, DurationMinimum, DurationMaximum);

         Debug.Print("Duration " + duration.ToString());
         pwm.Duration = duration;

         Thread.Sleep(100);
      }
   }

   private static long map(long x, long inputMinimum, long inputMaximum, long outputMinimum, long outputMaximum)
   {
      return (x - inputMinimum) * (outputMaximum - outputMinimum) / (inputMaximum - inputMinimum) + outputMinimum;
   }
}

I then connected the setup to my longboard ESC and it worked. (need a right angle connection for FEZ Panda III power)

Longboard with FEZ Panda III board based controller

Longboard with FEZ Panda III board based controller

The proof of concept software worked, the next step is to add throttle mapping and failure mode handling e.g. loss of communications with the Wiichuck.

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.

Electric Longboard First Assembly

My next project an electric motor powered longboard.

DIY longboard

Electric Longboard Parts

Bill of Materials for this project (Prices as at Jan 2014)

  • 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) USD4.44
  • Netduino 2 Plus (may replace this with a Netduino) USD59.95
  • Grove Base Shield V2 USD 8.90
  • Grove Nunchuck Adaptor USD2.90
  • Wireless Nunchuck NZD25.00
  • Moose 9.5×42 Longboard Flush Mount Deck Green Stain NZ57

Initial trials of the Wireless Nunchuck have not been very positive. As the Netduino device starts the connection to the handset is dropped. Need to do some more investigation to see if I can get this to work otherwise I will have to use a wired Nunchuck.