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