netNF Electric Longboard Part 3

Servo Control

The next step was to figure out how to operate a radio control(RC) servo as a proxy for an Electronic Speed Control(ESC).

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 20cm cable(5 PCs Pack) USD2.90
  • Grove-Servo USD5.90
  • Grove-Rotary Angle Sensor USD2.90

My servo test harness

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

      try
      {
         AdcController adc = AdcController.GetDefault();
         AdcChannel adcChannel = adc.OpenChannel(0);

         ServoMotor servo = new ServoMotor("TIM5", ServoMotor.ServoType.Positional, PinNumber('A', 0));
         servo.ConfigurePulseParameters(0.6, 2.3);

         while (true)
         {
            double value = adcChannel.ReadRatio();
            double position = Map(value, 0.0, 1.0, 0.0, 180);

            Debug.WriteLine($"Value: {value:F2} Position: {position:F1}");

            servo.Set(position);

            Thread.Sleep(100);
         }
      }
      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 for the rotary angle sensor for its position every 100mSec and then updates the servo.

The servo code was based on sample code provided by GHI Electronics for their TinyCLR which I had to adapt to work with the nanoFramework.

The next test rig will be getting the Netduino 3 software working my Longboard ESC and Lithium Polymer(LiPo) batteries.

netNF Electric Longboard Part 2

Analog Inputs & Pulse Width Modulation

The next step was to figure out how to configure a Pulse Width Modulation (PWM) output and an Analog Input so I could adjust the duty cycle and control the brightness of a Light Emitting Diode(LED).

Netduino 3 ADC & PWN 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-Universal 4 Pin Bucked 20cm cable(5 PCs Pack) USD2.90
  • Grove-LED Pack USD2.90
  • Grove-Rotary Angle Sensor USD2.90

My analog input test harness

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

         try
         {
            AdcController adc = AdcController.GetDefault();
            AdcChannel adcChannel = adc.OpenChannel(0);

            while (true)
            {
               double value = adcChannel.ReadRatio();

               Debug.WriteLine($"Value: {value:F2}");

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

The nanoFramework code polls for the rotary angle sensor for its position value every 100mSec.

The setup to use for the Analog to Digital Convertor(ADC) port was determined by looking at the board.h and target_windows_devices_adc_config.cpp file.

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

#include <win_dev_adc_native_target.h>

const NF_PAL_ADC_PORT_PIN_CHANNEL AdcPortPinConfig[] = {
    
    // ADC1
    {1, GPIOC, 0, ADC_CHANNEL_IN10},
    {1, GPIOC, 1, ADC_CHANNEL_IN11},

    // ADC2
    {2, GPIOC, 2, ADC_CHANNEL_IN14},
    {2, GPIOC, 3, ADC_CHANNEL_IN15},

    // ADC3
    {3, GPIOC, 4, ADC_CHANNEL_IN12},
    {3, GPIOC, 5, ADC_CHANNEL_IN13},

    // these are the internal sources, available only at ADC1
    {1, NULL, 0, ADC_CHANNEL_SENSOR},
    {1, NULL, 0, ADC_CHANNEL_VREFINT},
    {1, NULL, 0, ADC_CHANNEL_VBAT},
};

const int AdcChannelCount = ARRAYSIZE(AdcPortPinConfig);

The call to AdcController.GetDeviceSelector() only returned one controller

The thread '<No Name>' (0x2) has exited with code 0 (0x0).
devMobile.Longboard.AdcTest starting
ADC1

After some experimentation it appears that only A0 & A1 work on a Netduino. (Aug 2020).

My PWM test harness

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

      try
      {
         PwmController pwm = PwmController.FromId("TIM5");
         AdcController adc = AdcController.GetDefault();
         AdcChannel adcChannel = adc.OpenChannel(0);

         PwmPin pwmPin = pwm.OpenPin(PinNumber('A', 0));
         pwmPin.Controller.SetDesiredFrequency(1000);
         pwmPin.Start();

         while (true)
         {
            double value = adcChannel.ReadRatio();

            Debug.WriteLine(value.ToString("F2"));

            pwmPin.SetActiveDutyCyclePercentage(value);

            Thread.Sleep(100);
         }
      }
      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;
   }
}

I had to refer to the Netduino schematic to figure out pin mapping

With my test rig (with easy access to D0 thru D8) I found that only D2,D3,D7 and D8 work as PWM outputs.

The next test rig will be getting Servo working.

Grove Base Hat for Raspberry PI Zero Windows 10 IoT Core

During the week a package arrived from Seeedstudio with a Grove Base Hat for RPI Zero. So I have modified my Grove Base Hat for RPI Windows 10 IoT Core library to add support for the new shield.

Grove Base Hat for Raspberry PI Zero on Raspberry PI 3

The Raspberry PI Zero hat has a two less analog ports and a different device id so some conditional compile options were necessary

namespace devMobile.Windows10IoTCore.GroveBaseHatRPI
{
#if (!GROVE_BASE_HAT_RPI && !GROVE_BASE_HAT_RPI_ZERO)
#error Library must have at least one of GROVE_BASE_HAT_RPI or GROVE_BASE_HAT_RPI_ZERO defined
#endif

#if (GROVE_BASE_HAT_RPI && GROVE_BASE_HAT_RPI_ZERO)
#error Library must have at most one of GROVE_BASE_HAT_RPI or GROVE_BASE_HAT_RPI_ZERO defined
#endif

	public class AnalogPorts : IDisposable
	{
		private const int I2CAddress = 0x04;
		private const byte RegisterDeviceId = 0x0;
		private const byte RegisterVersion = 0x02;
		private const byte RegisterPowerSupplyVoltage = 0x29;
		private const byte RegisterRawBase = 0x10;
		private const byte RegisterVoltageBase = 0x20;
		private const byte RegisterValueBase = 0x30;
#if GROVE_BASE_HAT_RPI
		private const byte DeviceId = 0x0004;
#endif
#if GROVE_BASE_HAT_RPI_ZERO
		private const byte DeviceId = 0x0005;
#endif
		private I2cDevice Device= null;
		private bool Disposed = false;

		public enum AnalogPort
		{
			A0 = 0,
			A1 = 1,
			A2 = 2,
			A3 = 3,
			A4 = 4,
			A5 = 5,
#if GROVE_BASE_HAT_RPI
			A6 = 6,
			A7 = 7,
#endif
		};

The code updates have been “smoke” tested and I have updated the GitHub repository.

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()