NetMF MP3 Player Part 3

Building on the file listing code from the previous post in the next class we built a basic multi threaded music player using an enhanced version of the Vs1053B driver based on the softelectrotech.and bluecone code.

The code uses five interrupt ports (I used 5 buttons on the 4 analog ports on the Seeedstudio V2 Base shield and D5 to make it easier to fit the Mp3 shield)

As the Vs1053 driver is now running asynchronously it fires an event when the current track has finished playing

   public class Program
   {
      static string[] MusicFiles;
      static Vs1053B player = new Vs1053B(Pins.GPIO_PIN_D2, Pins.GPIO_PIN_D6, Pins.GPIO_PIN_D7, Pins.GPIO_PIN_D8);
      static byte volume = 200;
      static int trackNumber = 1;
      static public string TrackFilename { get { return MusicFiles[trackNumber - 1]; } }

      public static void Main()
      {
         MusicFiles = Directory.GetFiles(@"\SD");

         foreach( string file in MusicFiles)
         {
            Debug.Print(file);
         }

         InterruptPort playPause = new InterruptPort(Pins.GPIO_PIN_D5, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
         playPause.OnInterrupt += playPause_OnInterrupt;

         InterruptPort volumeUp = new InterruptPort(Pins.GPIO_PIN_A0, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
         volumeUp.OnInterrupt += volumeUp_OnInterrupt;

         InterruptPort volumeDown = new InterruptPort(Pins.GPIO_PIN_A1, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
         volumeDown.OnInterrupt += volumeDown_OnInterrupt;

         InterruptPort previousTrack = new InterruptPort(Pins.GPIO_PIN_A2, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
         previousTrack.OnInterrupt += previousTrack_OnInterrupt;

         InterruptPort nextTrack = new InterruptPort(Pins.GPIO_PIN_A3, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLow);
         nextTrack.OnInterrupt += nextTrack_OnInterrupt;

         player.SetVolume(volume);
         player.Filename = TrackFilename;
         player.onMusicFinished += player_onMusicFinished;

         player.Play();

         Thread.Sleep(Timeout.Infinite);
      }

      static void player_onMusicFinished()
      {
         trackNumber += 1;

         if (trackNumber > MusicFiles.Length)
         {
            trackNumber = 1;
         }
         Debug.Print("player_onMusicFinished " + trackNumber + " of " + MusicFiles.Length);

         player.Filename = TrackFilename;
      }

      static void previousTrack_OnInterrupt(uint data1, uint data2, DateTime time)
      {
         trackNumber -= 1;

         if (trackNumber < 1)          {             trackNumber = MusicFiles.Length;          }          Debug.Print("previousTrack_OnInterrupt " + trackNumber + " of " + MusicFiles.Length);          player.CancelPlayback();          player.Filename = TrackFilename;       }       static void nextTrack_OnInterrupt(uint data1, uint data2, DateTime time)       {          trackNumber += 1;          if (trackNumber > MusicFiles.Length)
         {
            trackNumber = 1;
         }
         Debug.Print("nextTrack_OnInterrupt " + trackNumber + " of " + MusicFiles.Length);

         player.CancelPlayback();
         player.Filename = TrackFilename;
      }

      static void playPause_OnInterrupt(uint data1, uint data2, DateTime time)
      {
         Debug.Print("playPause_OnInterrupt ");
         if (player.IsPaused())
         {
            player.Resume();
         }
         else
         {
            player.Pause();
         }
      }

      static void volumeDown_OnInterrupt(uint data1, uint data2, DateTime time)
      {
         Debug.Print("volumeDown_OnInterrupt " + volume);
         if (volume > 0)
         {
            volume -= 1;
         }
         player.SetVolume(volume);
      }

      static void volumeUp_OnInterrupt(uint data1, uint data2, DateTime time)
      {
         Debug.Print("volumeUp_OnInterrupt " + volume);
         if (volume < 255)
         {
            volume += 1;
         }
         player.SetVolume(volume);
      }
   }

Next steps are to ignore contact bounce on the buttons and play MP3 files in a more consistent order
musicplayerv2

Netduino with Mp3 Shield and 5 button UI

Netduino Mp3 player with 5 button UI

Netduino + SeeedStudio Grove LCD RGB Backlight

For the last year I have been teaching introductory programing classes using Netduino devices and Seeedstudio sensors which were sponsored by Microsoft New Zealand. The kits are based on a Grove Starter Kit for Arduino/Genuino 101 which contain a Grove LCD RGB Backlight display.

Seeedstudio have published an Arduino driver for the display and it looks like Sirsnork used this as the basis for his Netduino NetMF Port. In class a few people have commented that they have had difficulty getting the driver to work on a Netduino 2 or Netduino Plus 2 device.

I think the two main issues are the lack of termination resistors on the Grove RGB Backlight. Possibly the missing R9 & R10 in the picture below?

SeeedStudioRGBLCDBack

I have found the easiest way to work around this issue is to have another I2C device (In this case it’s a Grove 3 Axis Accelerometer ±16G)

SeeedStudioNetduinoAndRGBLCD

The other is the need to strobe the Serial Data Line (SDA) of the I2C port on later Netduino devices to get it to work

using System;
using System.Threading;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace SeeedStudio.Grove.RGBLCD
{
   public class Program
   {
      public static void Main()
      {
         using (OutputPort i2cPort = new OutputPort(Pins.GPIO_PIN_SDA, true))
         {
            i2cPort.Write(false);
         }

.....

With the I2C bus terminated and the SDA port strobe I have found the Netduino and display work as expected.

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.

St Margaret’s CodeClub information

If you want to follow along at home all the software is free (Visual Studio Express 2013) or open source (Netduino software & Hardware + NetMF) and can be downloaded from the following locations. The packages need to be sequentially installed in the order below.

If you want to purchase your own hardware, we use Netduino 2 Plus devices (we use them mainly for their integrated networking and MicroSD card) and Seeedstudio Grove sensors. (Prices as at 05/2015)

We are looking into Apple friendly options for later this term.

Netduino Water flow Sensor

A few months ago I purchased 1/2” and 3/4″ inch water flow sensors from SeeedStudio. My plan was to monitor our water and power consumption data to see what environmental impact my house has.

To see how the sensor works I built a simple proof of concept Netduino application which counted the pulses produced by the sensor and calculated the instantaneous water flow.

The next steps are to upload the water flow data to the cloud over a cabled then wireless connections.

public class Program
{
   private static int waterFlowCounter = 0;

   public static void Main()
   {
      InterruptPort flowCounterSensor = new InterruptPort(Pins.GPIO_PIN_D5, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeHigh);
      flowCounterSensor.OnInterrupt += new NativeEventHandler(flowCounter_OnInterrupt);

      Timer waterFlowUpdate = new Timer(waterFlowUpdateProc, null, 0, 1000);
      Thread.Sleep(Timeout.Infinite);
   }

   static void flowCounter_OnInterrupt(uint data1, uint data2, DateTime time)
   {
      Interlocked.Increment(ref waterFlowCounter);
   }

   static void waterFlowUpdateProc(object status)
   {
      int flowCount = Interlocked.Exchange(ref waterFlowCounter, 0);
      double flowLitresMinute = flowCount / 5.5 ; // The q value from documentation
      Debug.Print(flowLitresMinute.ToString("F1") + "L/m";);
    }
}
Netduino based water flow sensor

water flow sensor

Bill of Materials (Prices as at 12-2014)

Netduino Plus 2 USD60 NZD108

Grove Base Shield V2 USD8.90

G3/4″ Water Flow Sensor USD14.90

G1/2″ Water Flow Sensor USD9.50 NZD8.30

Netduino Galvanic Skin Response(GSR)

One of CodeClub’s sponsors is Orion Health so I have been evaluating sensors suitable for health focused projects. We already use the SeeedStudio Grove Heart rate sensor and Grove EMG Detector, so I purchased a Grove GSR sensor for testing. Galvanic Skin Response(GSR) is a method of measuring the electrical conductivity of the skin, which depends on the amount of sweat on the skin.

Netduino with Grove GSR sensor

Netduino with Grove GSR sensor

The GSR detector outputs a single analog signal which I connected to A0. For the evaluation I averaged the first 3000 samples to determine the initial offset, then sampled roughly every 100mSec.

I’m a bit worried about the robustness of the wires connecting the two probes to the black cable so it will be interesting to see how long they last at Code Club.

I also updated the Minimum and Maximum values with each sample as this appeared to make the display more reliable.

I found the display responded well to me holding my breath for as long as I could.

Pulse rate + EMG + GSR = Polygraph or DIY lie detector maybe a project for next term.

for (int sampleCounter = 0; sampleCounter < calibrationSampleCount; sampleCounter++)
{
   double value = gsr.Read();
   sampleSum += value;
}
offset = sampleSum / calibrationSampleCount ;

I then displayed the magnitude of the adjusted signal on a Seeedstudio LED bar using code written by Famoury Toure

while(true)
{
   double value = emg.Read() - offset;

   if (value < valueMinimum)
   {
      valueMinimum = value;
   }

   if (value > valueMaximum)
   {
      valueMaximum = value;
   }
   range = valueMaximum - valueMinimum;
   if (value < 0)
   {
      value = value / valueMaximum * 10.0;
   }
   else
   {
      value = value / valueMinimum * 10.0;
   }
   Debug.Print("Val " + value.ToString("F3") + " Max " + valueMaximum.ToString("F3") + " Min " +valueMinimum.ToString("F3"));

   int bar = 1;
   value = 10.0 - value;
   bar = bar << (int)value ;
   ledBar.setLED((uint)bar);
   Thread.Sleep(100);
}

Bill of Materials (Prices as at October 2014)