NetMF MP3 Player Part 1

For one of my class projects the students build an NetMF MP3 player using a Netduino, MP3 shield, MicroSD card and some code. The first step is to learn about files, and directories by enumerating the contents of the MicroSD card.

public static void Main()
 {
    string[] musicFiles = Directory.GetFiles(@"\SD");

    foreach (string musicFile in musicFiles)
    {
       Debug.Print(musicFile);
    }
}

The NetMF implementation of GetFiles doesn’t support wildcards (unlike the full .Net Framework) so the list of files has to be manually filtered.

public static void Main()
{
   string[] musicFiles = Directory.GetFiles(@"\SD");

   foreach (string musicFile in musicFiles)
   {
      if (filePath.IndexOf(".mp3") != -1)
      {
         Debug.Print(musicFile);
      }
   }
}

The code above displayed
\SD\01 Pride (In the Name of Love).mp3
\SD\02 New Year’s Day.mp3
\SD\03 With or Without You.mp3
\SD\04 I Still Haven’t Found What I’m Looking For.mp3
\SD\05 Sunday Bloody Sunday.mp3
\SD\06 Bad.mp3
\SD\07 Where the Streets Have No Name.mp3
\SD\08 I Will Follow.mp3
\SD\09 The Unforgettable Fire.mp3
\SD\10 Sweetest Thing [The Single Mix].mp3
\SD\11 Desire.mp3
\SD\12 When Love Comes to Town.mp3
\SD\13 Angel of Harlem.mp3
\SD\14 All I Want Is You.mp3

For this project Directory.GetFiles was used (rather than Directory.EnumerateFiles) because the list files on the MicroSD will be used in other parts of the application.

The parsing and processing of file paths is important and can if done wrong can introduce hard to find issues(e.g. directory traversal attacks)

Debug.Print(musicFile .Substring( 0, musicFile.LastIndexOf(".mp3")));

Updating the code to use the built in System.IO.Path functionality

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

   foreach (string musicFile in MusicFiles)
   {
      if (Path.GetExtension(musicFile) == ".mp3" )
      {
         Debug.Print(Path.GetFileNameWithoutExtension(musicFile));
      }
   }
}

The next step is to load and play the MP3 files using a provided library.

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.

Netduino 3 Wifi pollution Sensor Part 2

In a previous post I had started building a driver for the Seeedstudio Grove Dust Sensor. It was a proof of concept and it didn’t handle some edge cases well.

While building the pollution monitor with a student we started by simulating the negative occupancy of the Shinyei PPD42NJ Particle sensor with the Netduino’s on-board button. This worked and reduced initial complexity. But it also made it harder to simulate the button being pressed as the program launches (the on-board button is also the reset button), or simulate if the button was pressed at the start or end of the period.

Dust sensor simulation with button

Netduino 3 Wifi Test Harness

The first sample code processes button press interrupts and displays the values of the data1 & data2 parameters

public class Program
{
   public static void Main()
   {
      InterruptPort button = new InterruptPort(Pins.GPIO_PIN_D5, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
      button.OnInterrupt += button_OnInterrupt;

      Thread.Sleep(Timeout.Infinite);
   }

   static void button_OnInterrupt(uint data1, uint data2, DateTime time)
   {
      Debug.Print(time.ToString("hh:mm:ss.fff") + " data1 =" + data1.ToString() + " data2 = " + data2.ToString());
   }
}

Using the debugging output from this application we worked out that data1 was the Microcontroller Pin number and data2 was the button state.

12:00:14.389 data1 =24 data2 = 0
12:00:14.389 data1 =24 data2 = 1
12:00:14.389 data1 =24 data2 = 0
12:00:15.851 data1 =24 data2 = 1
12:00:16.078 data1 =24 data2 = 0

We then extended the code to record the duration of each button press.

public class Program
{
   static DateTime buttonLastPressedAtUtc = DateTime.UtcNow;

   public static void Main()
   {
      InterruptPort button = new InterruptPort(Pins.ONBOARD_BTN, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
      button.OnInterrupt += button_OnInterrupt;

      Thread.Sleep(Timeout.Infinite);
   }

   static void button_OnInterrupt(uint data1, uint data2, DateTime time)
   {
      if (data2 == 0)
      {
         TimeSpan duration = time - buttonLastPressedAtUtc;

         Debug.Print(duration.ToString());
      }
      else
      {
         buttonLastPressedAtUtc = time;
      }
   }
}

The thread ” (0x4) has exited with code 0 (0x0).
00:00:00.2031790
00:00:00.1954150
00:00:00.1962350

The next step was to keep track of the total duration of the button presses since the program started executing.

public class Program
{
   static DateTime buttonLastPressedAtUtc = DateTime.UtcNow;
   static TimeSpan buttonPressedDurationTotal;

   public static void Main()
   {
      InterruptPort button = new InterruptPort(Pins.ONBOARD_BTN, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
      button.OnInterrupt += button_OnInterrupt;

      Thread.Sleep(Timeout.Infinite);
   }

   static void button_OnInterrupt(uint data1, uint data2, DateTime time)
   {
      if (data2 == 0)
      {
         TimeSpan duration = time - buttonLastPressedAtUtc;

         buttonPressedDurationTotal += duration;
          Debug.Print(duration.ToString() + " " + buttonPressedDurationTotal.ToString());
      }
      else
      {
         buttonLastPressedAtUtc = time;
      }
   }
}

The thread ” (0x4) has exited with code 0 (0x0).
00:00:00.2476460 00:00:00.2476460
00:00:00.2193600 00:00:00.4670060
00:00:00.2631400 00:00:00.7301460
00:00:00.0001870 00:00:00.7303330

We then added a timer to display the amount of time the button was pressed in the configured period.

public class Program
{
   static TimeSpan measurementDueTime = new TimeSpan(0, 0, 30);
   static TimeSpan measurementperiodTime = new TimeSpan(0, 0, 30);
   static DateTime buttonLastPressedAtUtc = DateTime.UtcNow;
   static TimeSpan buttonPressedDurationTotal;


   public static void Main()
   {
      InterruptPort button = new InterruptPort(Pins.GPIO_PIN_D5, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
      button.OnInterrupt += button_OnInterrupt;

      Timer periodTimer = new Timer(periodTimerProc, button, measurementDueTime, measurementperiodTime);

      Thread.Sleep(Timeout.Infinite);
   }

   static void periodTimerProc(object status)
   {
      InterruptPort button = (InterruptPort)status;

      if (button.Read())
      {
         TimeSpan duration = DateTime.UtcNow - buttonLastPressedAtUtc;

         buttonPressedDurationTotal += duration; 
      }

      Debug.Print(buttonPressedDurationTotal.ToString());

      buttonPressedDurationTotal = new TimeSpan(0, 0, 0);
      buttonLastPressedAtUtc = DateTime.UtcNow;
   }

   static void button_OnInterrupt(uint data1, uint data2, DateTime time)
   {
      if (data2 == 0)
      {
         TimeSpan duration = time - buttonLastPressedAtUtc;

         buttonPressedDurationTotal += duration;

         Debug.Print(duration.ToString() + " " + buttonPressedDurationTotal.ToString());
      }
      else
      {
         buttonLastPressedAtUtc = time;
      }
   }
}

The thread ” (0x4) has exited with code 0 (0x0).
00:00:00
00:00:00
00:00:00.2299050 00:00:00.2299050
00:00:00.1956980 00:00:00.4256030
00:00:00.1693190 00:00:00.5949220
00:00:00.5949220

After some testing we identified that the handling of button presses at the period boundaries was problematic and revised the code some more. We added a timer for the startup period to simplify the interrupt handling code.

public class Program
{
   static TimeSpan measurementDueTime = new TimeSpan(0, 0, 60);
   static TimeSpan measurementperiodTime = new TimeSpan(0, 0, 30);
   static DateTime buttonLastPressedAtUtc = DateTime.UtcNow;
   static TimeSpan buttonPressedDurationTotal;

   public static void Main()
   {
      InterruptPort button = new InterruptPort(Pins.GPIO_PIN_D5, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
      button.OnInterrupt += button_OnInterrupt;

      Timer periodTimer = new Timer(periodTimerProc, button, Timeout.Infinite, Timeout.Infinite);

      Timer startUpTImer = new Timer(startUpTimerProc, periodTimer, measurementDueTime.Milliseconds, Timeout.Infinite);

      Thread.Sleep(Timeout.Infinite);
   }

   static void startUpTimerProc(object status)
   {
      Timer periodTimer = (Timer)status;

      Debug.Print( DateTime.UtcNow.ToString("hh:mm:ss") + " -Startup complete");

      buttonLastPressedAtUtc = DateTime.UtcNow;
      periodTimer.Change(measurementDueTime, measurementperiodTime);
   }

   static void periodTimerProc(object status)
   {
      InterruptPort button = (InterruptPort)status;
      Debug.Print(DateTime.UtcNow.ToString("hh:mm:ss") + " -Period timer");

      if (button.Read())
      {
         TimeSpan duration = DateTime.UtcNow - buttonLastPressedAtUtc;

         buttonPressedDurationTotal += duration;
      }

      Debug.Print(buttonPressedDurationTotal.ToString());

      buttonPressedDurationTotal = new TimeSpan(0, 0, 0);
      buttonLastPressedAtUtc = DateTime.UtcNow;
   }

   static void button_OnInterrupt(uint data1, uint data2, DateTime time)
   {
      Debug.Print(DateTime.UtcNow.ToString("hh:mm:ss") + " -OnInterrupt");

      if (data2 == 0)
      {
         TimeSpan duration = time - buttonLastPressedAtUtc;

         buttonPressedDurationTotal += duration;

         Debug.Print(duration.ToString() + " " + buttonPressedDurationTotal.ToString());
      }
      else
      {
         buttonLastPressedAtUtc = time;
      }
   }
}

The debugging output looked positive, but more testing is required.

The thread ” (0x2) has exited with code 0 (0x0).
12:00:13 -Startup complete
12:01:13 -Period timer
00:00:00
12:01:43 -Period timer
00:00:00
12:01:46 -OnInterrupt
12:01:48 -OnInterrupt
00:00:01.2132510 00:00:01.2132510
12:01:49 -OnInterrupt
12:01:50 -OnInterrupt
00:00:01.3001240 00:00:02.5133750
12:01:53 -OnInterrupt
12:01:54 -OnInterrupt
00:00:01.1216510 00:00:03.6350260
12:02:13 -Period timer
00:00:03.6350260

Next steps – multi threading, extract code into a device driver and extend to support sensors like the SeeedStudio Smart dust Sensor which has two digital outputs, one for small particles (e.g. smoke) the other for larger particles (e.g. dust).

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.

CodeClub Internet of Things Boxes sponsored by Microsoft NZ

A few months ago Microsoft NZ donated NZ6K to CodeClub NZ for the purchase of kits for our basic electronics and programming classes.

Over the last couple of months I have been assembling these so we now have 15 kits ready to go. Each one has enough gear for 2-6 students, fits into a 7L Sistema plastic box and contains the following items

2 x Netduino 2 Plus devices
2 x Seeedstudio Grove Starter kits for Arduino which contain

  • 1xBase Shield
  • 1xGrove – LCD RGB Backlight
  • 1xGrove – Smart Relay
  • 1xGrove – Buzzer
  • 1xGrove – Sound Sensor
  • 1xGrove – Touch Sensor
  • 1xGrove – Rotary Angle Sensor
  • 1xGrove – Temperature Sensor
  • 1xGrove – Light Sensor
  • 1xGrove – Button
  • 1xGrove LED Blue-Blue
  • 1xGrove LED Green-Green
  • 1xGrove  LED Red-Red
  • 1xMini Servo
  • 10xGrove Cables
  • 1x9V to Barrel Jack Adapter
  • 1xGrove starter kit Manual
  • 1xGreen Plastic Box
  • 1 x ultrasonic ranger

In addition to the Netduino devices and the Grove starter kits, we also include

Thanks to Embedded coolness, Secret Labs, and Seeedstudio which discounted their products so our funding went further.

CodeClub Programming and electronics kits

CodeClub Programming and electronics kits

CodeClub Mashup 2014 @ Epic Innovation

On the 8th of November  I was an industry mentor at CodeClub Mashup 2014 which was held at Epic Innovation in Christchurch.

The event was sponsored by Environment Canterbury (ECAN), Land Information New Zealand (LINZ) and Code Club Aotearoa.

Mashup 2014 was a “concept to cash” in one day competition for local high schools. We had roughly 40 students turn up and we started the day with introductions to hardware/software based product development and basic business skills.

Mashup2014 Judging begins

Mashup2014 Judging begins

A team from a Selwyn House an Independent School for Girls from year 1 – 8 explained in a blog post what is mashup.

Some students from Burnside High School produced the official video about the day

Thinking about “Internet of Things” and/or “Farm of Things” mashups in Q1 2015

 

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)