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 3 wifi Azure Event Hub client

Over the last couple of weeks I have been beta testing a Netduino 3 Wifi board. One of the great features of the new board is baked in support for SSL 3.0 and TLS 1.2 which enables direct connection to services which require https.

EDIT: The device has been running under my desk powered by a wall wart for a week. It has been monitoring the temperature of my office and the air gap between the curtains and the glass. My ADSL has gone down a couple of times but the N3 has recovered all by itself and kept on going.

In a couple of previous blog posts I have shown how to upload data to an Azure Event Hub from other NetMFdevices. I built an application that runs on a FEZ Spider and a lightweight Service Gateway so I was keen to see how well a Netduino 3 Wifi based solution worked.

To get something working on my Netduino 3 device I started with the application I had written for the FEZ spider. The code is based on OBD Recorder for .Net Micro Framework with ServiceBus, AMQP (for IoT) samples. The test client used a couple of DS18B20 temperature sensors to monitor the temperature of my fridge and freezer.

Netduino 3 device with temperature sensors

Netduino 3 device with Seeedstudio Shield and two temperature sensors

I created an Event Hub and associated device access keys and fired up Service Bus Explorer so I could see what was happening.ServiceBus Explorer showing my fridge and freezer temperatures

In the application the first step was to add code to wait for the device to acquire an IP address. (Will replace this code with a more efficient approach)

// Wait for Network address if DHCP
NetworkInterface networkInterface = NetworkInterface.GetAllNetworkInterfaces()[0];

if (networkInterface.IsDhcpEnabled)
{
   Debug.Print(" Waiting for IP address " );

   while (NetworkInterface.GetAllNetworkInterfaces()[0].IPAddress == IPAddress.Any.ToString()) ;
}

// Display network config for debugging
Debug.Print("Network configuration");
Debug.Print(" Network interface type: " + networkInterface.NetworkInterfaceType.ToString());
Debug.Print(" MAC Address: " + BytesToHexString(networkInterface.PhysicalAddress));
Debug.Print(" DHCP enabled: " + networkInterface.IsDhcpEnabled.ToString());
Debug.Print(" Dynamic DNS enabled: " + networkInterface.IsDynamicDnsEnabled.ToString());
Debug.Print(" IP Address: " + networkInterface.IPAddress.ToString());
Debug.Print(" Subnet Mask: " + networkInterface.SubnetMask.ToString());
Debug.Print(" Gateway: " + networkInterface.GatewayAddress.ToString());

foreach (string dnsAddress in networkInterface.DnsAddresses)
{
   Debug.Print(" DNS Server: " + dnsAddress.ToString());
}

deviceId = BytesToHexString(networkInterface.PhysicalAddress);

Then to send the message to the event hub the request has to have a authorisation token attached

private void EventHubSendMessage(string eventHubAddressHttps, string messageBody)
{
   string token = CreateSasToken(eventHubAddressHttps + "/messages", sasKeyName, sasKeyText);

   try
   {
      using( HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(eventHubAddressHttps + "/messages" + "?timeout=60" + ApiVersion))
      {
         request.Timeout = 2500;
         request.Method = "POST";

         // Enable these options to suit your environment
         //request.Proxy = new WebProxy("myproxy.myorganisation.com", true);
         //request.Credentials = new NetworkCredential("myusername", "mytopsecretpassword"); 

         request.Headers.Add("Authorization", token);
         request.Headers.Add("ContentType", "application/json;charset=utf-8");

         byte[] buffer = Encoding.UTF8.GetBytes(messageBody);

         request.ContentLength = buffer.Length;

         // request body
         using (Stream stream = request.GetRequestStream())
         {
            stream.Write(buffer, 0, buffer.Length);
         }

         using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
         {
            Debug.Print("HTTP Status:" + response.StatusCode + " : " + response.StatusDescription);
         }
      }
   }
   catch (WebException we)
   {
      Debug.Print(we.Message);
   }
}

// Create a SAS token for a specified scope. SAS tokens are described in http://msdn.microsoft.com/en-us/library/windowsazure/dn170477.aspx.
private static string CreateSasToken(string uri, string keyName, string key)
{
   // Set token lifetime to 20 minutes. When supplying a device with a token, you might want to use a longer expiration time.
   uint tokenExpirationTime = GetExpiry(20 * 60);

   string stringToSign = HttpUtility.UrlEncode(uri) + "\n" + tokenExpirationTime;

   var hmac = SHA.computeHMAC_SHA256(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(stringToSign));
   string signature = Convert.ToBase64String(hmac);

   signature = Base64NetMf42ToRfc4648(signature);

   string token = "SharedAccessSignature sr=" + HttpUtility.UrlEncode(uri) + "&sig=" + HttpUtility.UrlEncode(signature) + "&se=" + tokenExpirationTime.ToString() + "&skn=" + keyName;

   return token;
}

private static string Base64NetMf42ToRfc4648(string base64netMf)
{
   var base64Rfc = string.Empty;

   for (var i = 0; i < base64netMf.Length; i++)
   {
      if (base64netMf[i] == '!')
      {
         base64Rfc += '+';
      }
      else if (base64netMf[i] == '*')
      {
         base64Rfc += '/';
      }
      else
      {
         base64Rfc += base64netMf[i];
      }
   }
   return base64Rfc;
}

static uint GetExpiry(uint tokenLifetimeInSeconds)
{
   const long ticksPerSecond = 1000000000 / 100; // 1 tick = 100 nano seconds

   DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
   TimeSpan diff = DateTime.Now.ToUniversalTime() - origin;

   return ((uint)(diff.Ticks / ticksPerSecond)) + tokenLifetimeInSeconds;
}

The initial version of the Netduino TI CC3100 driver has some limitations e.g. no server certificate validation but these should be attended to in future releases.

The software was based on Brad’s One-Wire and DS18B20 library with fixes from here.

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

EVolocity Innovation Challenge Parking Aids

At evelocity boot camp on the 22nd of March we talked about aids for use in the parking challenge. For example, a Netduino and one or more ultrasonic rangers could be used to measure the distance to obstacles placed around the car park.

In the picture below the LED bar is displaying the distance to the base shield box with each LED segment representing 2cm.
Netduino based Park Distance Control

Bill of Materials for this project (Prices as at March 2015)

This code is just to illustrate how this could done and should not be used in production

public class Program
{
   private static OutputPort triggerOutput;
   private static InterruptPort echoInterrupt;
   private static long pulseStartTicks;
   private static GroveLedBarGraph distanceBar;
   private const int MillimetersPerBar = 20;

   public static void Main()
   {
      OutputPort distanceCin = new OutputPort(Pins.GPIO_PIN_D8, false);
      OutputPort distanceDin = new OutputPort(Pins.GPIO_PIN_D9, false);

      distanceBar = new GroveLedBarGraph(distanceCin, distanceDin);

      triggerOutput = new OutputPort(Pins.GPIO_PIN_D5, false);
      echoInterrupt = new InterruptPort(Pins.GPIO_PIN_D4,
         true,
         Port.ResistorMode.Disabled,
         Port.InterruptMode.InterruptEdgeBoth);

      echoInterrupt.OnInterrupt += new NativeEventHandler(echoInterruptPort_OnInterrupt);

      Timer distanceUpdate = new Timer(distanceUpdateCallbackProc, null, 0, 500);

      Thread.Sleep(Timeout.Infinite);
   }

   public static void distanceUpdateCallbackProc(object state)
   {
      triggerOutput.Write(false);
      Thread.Sleep(2);
      triggerOutput.Write(true);
      Thread.Sleep(10);
      triggerOutput.Write(false);
      Thread.Sleep(2);
   }

   static void echoInterruptPort_OnInterrupt(uint data1, uint data2, DateTime time)
   {
      long pulseWidthTicks;

      if (data2 == 1) // leading edge, start of pulse
      {
         pulseStartTicks = time.Ticks;
      }
      else
      {
         pulseWidthTicks = time.Ticks - pulseStartTicks;

         long distance = pulseWidthTicks / 58;

         Debug.Print("distance = " + distance.ToString() + "mm");

         uint ledBar = 1;
         ledBar = ledBar <<(int)(distance / MillimetersPerBar );
         distanceBar.setLED(ledBar);
      }
   }
}

Electric Longboard devDuino V2 Controller

I really wanted to get the longboard working so I had a look at buying Wiiceiver from AustinDavid.com.

The source code is available on Github and I had a spare devicter devDuino Sensor Node V2 sitting on my desk. With some modification (changing pins numbers and removing all references to the second LED) I got the wiiceiver code running on my devDuino.

The Electronic Speed Controller(ESC) and the plastic lunch box (containing the batteries and devDuino) are attached to the deck with 3M Command adhesive strips. The first set of command adhesive strips I tried were for hanging pictures and had a Velcro quick release system. This approach was a failure and the ESC & electronics box fell off after 10-15 minutes use. The Velcro backing tape was getting pulled in the wrong direction so was unable to hold the weight of the electronics when vibration levels increased. I tried them because a “quick release” capability would be handy but I have gone back to using conventional 3M Command adhesive strips and these are working well.

devDuino V2 and ESC on longboard

devDuino based controller interfaced with ESC and wireless WiiChuk

Initial rides went well, though I need to recalibrate the acceleration and braking ramp up/down settings to suit my hardware and riding style.

Bill of Materials for this project (Prices as at Feb 2015)

  • 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)
  • devDuino Sensor Node V2 USD15.99
  • Grove Nunchuck adaptor USD2.90
  • Grove Branch Cable for Servo USD4.90
  • Wireless Nunchuck NZD25.00
  • Moose 9.5×42 Longboard Flush Mount Deck Green Stain NZD57

WARNING – Disconnect the power supply pin on the Grove Branch Cable for Servos as the ESC will supply sufficient current to make the batteries on the devDuino go pop. Wrap some tape around the other servo connector so it can’t cause a short circuit.

Thanks to Austin David for making the code for the Wiiciever open source, if anyone is interested in my code I can tidy it up and share.

/*
 * Pin IDs -- NOT LOCATIONS !!!
 * don't change these ever; see &quot;pinLocation&quot; below for
 * actual locations
 */
#define RED_LED_ID   0
//#define GREEN_LED_ID 1

#define ESC_PPM_ID   2
//#define ESC2_PPM_ID   6
#define ESC_GROUND 00     // hard-wired

//#define WII_POWER_ID 3
//#define WII_GROUND 00     // hard-wired

#define WII_SCL_ID   4
#define WII_SDA_ID   5

I made my devDuino look like a V3 wiiceiver

int pinLocation(int pinID) {
  int pinMap[7][3] = {
  // v1, v2, v3
    {8,   8,  9},  // RED_LED     any digital pin
    {7,   6,  8},  // GREEN_LED   any digital pin
    {10,  9,  3},  // ESC_PPM     PWM required
    {9,  11,  5},  // WII_POWER   any digital pin
    {19, 19, 19}, // WII_SCL     A5, don't change
    {18, 18, 18}, // WII_SDA     A4, don't change
    {0,  10, 0}, // ESC2_PPM    PWM required
  };

The rest of my changes were commenting out all references to the Green LED as the devDuino only has one onboard LED.

Netduino Crazyflie Wii Nunchuk Remote Control V1.0

After flying the Crazyflie for a couple of days with the Joystick shield based remote control I figured an alternate user interface based on a Wii Nunchuk could be interesting. (It might also make the Crazyflie easier to operate for novice pilots). After a couple of hours coding I have a proof of concept Netduino based Crazyflie Nanocopter Wii Nunchck remote control unit.

Initially the nanocopter was difficult to fly, bouncing up and down (thrust control issues) and swaying side to side (roll & pitch control issues). After some digging I found that every so often the Wii nunchuk (my cheap clone) would return a buffer full of 0xFF or 0x00 bytes.  The 0xFF case had been handled but not the 0x00 one. I added a second test into the GetData  method (around line 335) to catch the 0x00 scenario and this appeared to fix the problem.

cnt = 0;
for (int i = 0; i &lt; inputBuffer.Length; i++)
   if (inputBuffer[i] == 0x0) cnt++;
if (cnt == inputBuffer.Length)
{
   return false;
}
CrazyFlie Netduino Wiichuck based Remote

CrazyFlie Netduino Wiichuck based Remote

Bill on Materials (Prices as at Jan 2014)

This software was built using tooling created and shared by others.

Big thanks to

Jakub Bartkowiak – Gralin.NETMF.Nordic.NRF24L01Plus

Szymon Kobalczyk – Wiichuck I2C driver

Antao Almada – HydraMF.BitConverter

The nunchuck accelerometer provides roll and pitch, the joystick is for thrust and yaw. The first version of the CrazyFlieWiiChuckV1.0 is pretty basic and I have intentionally reduced the maximum roll, pitch, thrust and yaw values to make it easier to fly. (Need to set the Grove Base Shield to 3V3 for my code to work)

Currently I only calculate offset values for thrust & yaw. After a couple of test flights some visual indication of the pitch and roll values from the nunchuk would be helpfull.

Netduino Crazyflie Joystick Shield Remote Control V1.0

Sometimes you start with a goal in mind then a couple of days later you have built something interesting but totally unrelated to what you originally intended to do….

I have several devices (both Netduino & Arduino) which I want to use to collect and upload data to the cloud so I can monitor the resource usage of my house.

I had read Clemens Vaster post on Service Assisted Communication and I was planning to use a Windows Server Essentials 2012 box I have running 24/7 in the hallway to forward updates to the cloud.

I wanted to connect the remote data acquisition nodes directly to the server using their baked in nRF24L01+ support. On the server end the Crazyradio 2.4 Hhz nRF24LU1 USB dongle looked ideal. After some initial positive results I found that the CrazyRadio firmware had been implemented in a way that made it not suitable for my application. (I even considered downloading the BitCraze development VM and building my own custom firmware)

After spending a few hours trying to get the CrazyRadio dongle working I looked at my Crazyflie Nano QuadCopter sitting on the bookshelf.

Then I realised what I really needed is a more portable Crazyflie remote control unit so I didn’t have to unpack my laptop. So two nights later I have a proof of concept Netduino based Crazyflie Nanocopter remote control unit.

CrazyFlie nano copter and Netduino Based Remote

CrazyFlie Netduino based Remote

Bill on Materials (Prices as at Jan 2014)

This software was built using tooling created and shared by others.

Big thanks to

Jakub Bartkowiak – Gralin.NETMF.Nordic.NRF24L01Plus

Antao Almada – HydraMF.BitConverter

Mike McCauley – NRF24 library for Arduino.

I used the NRF24 CrazyFlie emulator to debug my project. No doubt stopping me crashing my Crazyflie many times while debugging.

The Joystick shield has to be modified to work with the common Netduino nRF24L01 libraries which use interrupts rather than polling.

The joystick on the shield is for roll and pitch, the external joystick is for thrust and yaw. The first version of the JoystickShieldnRF24l01V1.0 is pretty basic but I’ll try and enhance it over the next couple of posts.

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

Azure Event Hub Updates from a NetMF Device

I had read about how Azure Event Hubs supported both Advanced Message Queuing Protocol(AMQP) & Hypertext Transfer Protocol (HTTP) access and was keen to see how easy the REST API was to use from a .Net Microframework (NetMF) device.

My initial concept was an exercise monitoring system with a Global Positioning System (GPS) unit and a pulse oximeter connected to a FEZ Spider device. My posting GPS Tracker Azure Service Bus has more info about GPS Drivers  and Azure Service Bus connectivity.

FEZ Spider, GPS and PulseOximeter

Fez spider and sensors for exercise monitoring device

The software was inspired by the Service Bus Event Hubs Getting started, Scale Out Event Processing with Event Hubs,Service Bus Event Hubs Large Scale Secure Publishing and OBD Recorder for .Net Micro Framework with ServiceBus, AMQP (for IoT) samples. I created an Event Hub and associated device access keys and fired up Service Bus Explorer so I could monitor and tweak the configuration.

I started by porting the REST API SendMessage implementation of Service Bus Event Hubs Large Scale Secure Publishing sample to NetMF. My approach was to get the application into my local source control and then cut ‘n’ paste the code into a NetMF project and see what breaks. I then modified the code over several iterations so it ran on both the desktop and NetMF clients.

The next step was to download the HTTPS certificates and add them to the project as resources so the requests could be secured. See this post for more detail.

For the connection to be secured you need to set the local time (so the certificate valid to/from can be checked) and load the certificates so they can be attached to the HTTP requests

void ProgramStarted()
{
   ...
   Microsoft.SPOT.Hardware.Utility.SetLocalTime(NtpClient.GetNetworkTime());
   caCerts = new X509Certificate[] { new X509Certificate(Resources.GetBytes(Resources.BinaryResources.Baltimore)) };

I used the Network Time Protocol (NTP) library from the OBD Recorder for .Net Micro Framework sample to get the current time.

The Service Bus Event Hubs Large Scale Secure Publishing uses an asynchronous HTTP request which is not available on the NetMF platform. So I had to replace it with a synchronous version.

static void EventHubSendMessage(string eventHubAddressHttps, string token, string messageBody)
{
   try
   {
      HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(eventHubAddressHttps + "/messages" + "?timeout=60" + ApiVersion);
      {
         ...
         request.Headers.Add("Authorization", token);
         request.Headers.Add("ContentType", "application/atom+xml;type=entry;charset=utf-8");
         byte[] buffer = Encoding.UTF8.GetBytes(messageBody);
         request.ContentLength = buffer.Length;

         // request body
         using (Stream stream = request.GetRequestStream())
         {
            stream.Write(buffer, 0, buffer.Length);
         }
         using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
         {
            Debug.Print("HTTP Status:" + response.StatusCode + " : " + response.StatusDescription);
         }
      }
   }
   catch (WebException we)
   {
      Debug.Print(we.Message);
   }
}

The code to generate the SAS Token also required some modification as string.format, timespan, and SHA256 functionality are not natively available on the .NetMF platform. The GetExpiry, and SHA256 implementations were part of the OBD Recorder for .Net Micro Framework sample.

static string CreateSasToken(string uri, string keyName, string key)
{
   // Set token lifetime to 20 minutes. When supplying a device with a token, you might want to use a longer expiration time.
   uint tokenExpirationTime = GetExpiry(20 * 60);

   string stringToSign = HttpUtility.UrlEncode(uri) + "\n" + tokenExpirationTime;
   var hmac = SHA.computeHMAC_SHA256(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(stringToSign));

   string signature = Convert.ToBase64String(hmac);
   signature = Base64NetMf42ToRfc4648(signature);

   string token = "SharedAccessSignature sr=" + HttpUtility.UrlEncode(uri) + "&sig=" + HttpUtility.UrlEncode(signature) + "&se=" + tokenExpirationTime.ToString() + "&skn=" + keyName;
   return token;
}

static uint GetExpiry(uint tokenLifetimeInSeconds)
{
   const long ticksPerSecond = 1000000000 / 100; // 1 tick = 100 nano seconds</code>

   DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
   TimeSpan diff = DateTime.Now.ToUniversalTime() - origin;

   return ((uint)(diff.Ticks / ticksPerSecond)) + tokenLifetimeInSeconds;
}

private static string Base64NetMf42ToRfc4648(string base64netMf)
{
   var base64Rfc = string.Empty;
   for (var i = 0; i < base64netMf.Length; i++)
   {
      if (base64netMf[i] == '!')
      {
         base64Rfc += '+';
      }
      else if (base64netMf[i] == '*')
      {
         base64Rfc += '/';
      }
      else
      {
         base64Rfc += base64netMf[i];
      }
   }
return base64Rfc;
}

The HttpUtility class came from the OBD Recorder for .Net Micro Framework sample. The Base64NetMf42ToRfc4648 functionality is still necessary on NetMF 4.3.

After a couple of hours I had data upload working.(No GPS data as the device was running on my desk where GPS coverage is poor)

ServiceBusExplorerEventHub

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)