Nexus Analog, GPIO and PWM testing

Over the weekend I have been testing a beta Ingenuity Micro Nexus device building a series of simple applications to exercise all of the input and output ports.

The device is equipped with 11 x Seeedstudio Grove compatible sockets (2 x UART, 5 x I2C, 3 x ADC, 1 x PWM sockets) which support a wide variety of sensors.

Test cables and devices
Grove Cable Modification with a cross stitch needle

So I could test all the analog port pins I modified a Grove Branch Cable by carefully unplugging the yellow and white branch cables and replacing them with yellow and white (plugged into the yellow connector on both sensor connectors) cables split from a spare Grove Universal Buckled 20cm cable. I used a pair of Grove Rotary Angle Sensors as analog inputs.

public static void Main()
{
	AnalogInput analogSensor1 = new AnalogInput
	(
		Pins.Analog.Socket1Pin1
		//Pins.Analog.Socket2Pin1
		//Pins.Analog.Socket3Pin1
		//Pins.Analog.Socket4Pin1
	);
	AnalogInput analogSensor2 = new AnalogInput
	(
		Pins.Analog.Socket1Pin2
		//Pins.Analog.Socket2Pin2
		//Pins.Analog.Socket3Pin2
		//Pins.Analog.Socket4Pin2
	);

	Debug.Print("Program running");

	while (true)
	{
		double sensorValue1 = analogSensor1.Read();
		double sensorValue2 = analogSensor2.Read();

		Debug.Print("Value 1:" + sensorValue1.ToString("F2") + " Value 2:" + sensorValue2.ToString("F2"));

		Thread.Sleep(500);
	}
}

To speed up testing of the GPIO and PWM ports I modified a Grove Universal Buckled 20cm cable by twisting the white and yellow wires.

I used a pair of Grove illuminated buttons (Red, Yellow or Blue). The button was the digital input, the LED was the digital output. By uncommenting pairs of socket pins I could quickly step through all the ports checking that pressing the button toggled the state of the LED.

public class Program
{
	const Cpu.Pin ButtonLedPin =
		Pins.Gpio.Socket1Pin1;
		//Pins.Gpio.Socket1Pin2;
		//Pins.Gpio.Socket2Pin1;
		//Pins.Gpio.Socket2Pin2;
		//Pins.Gpio.Socket3Pin1;
		//Pins.Gpio.Socket3Pin2;
		//Pins.Gpio.Socket4Pin1;
		//Pins.Gpio.Socket4Pin2;
		//Pins.Gpio.Socket5Pin1;
		//Pins.Gpio.Socket5Pin2;
		//Pins.Gpio.Socket6Pin1;
		//Pins.Gpio.Socket6Pin2;
		//Pins.Gpio.Socket7Pin1;
		//Pins.Gpio.Socket7Pin2;
		//Pins.Gpio.Socket8Pin1;
		//Pins.Gpio.Socket8Pin2;
		//Pins.Gpio.Socket9Pin1;
		//Pins.Gpio.Socket9Pin2;
		//Pins.Gpio.Socket10Pin1;
		//Pins.Gpio.Socket10Pin2;
		//Pins.Gpio.Socket11Pin1;
		//Pins.Gpio.Socket11Pin2;
	const Cpu.Pin ButtonPin =
		//Pins.Gpio.Socket1Pin1;
		Pins.Gpio.Socket1Pin2;
		//Pins.Gpio.Socket2Pin1;
		//Pins.Gpio.Socket2Pin2;
		//Pins.Gpio.Socket3Pin1;
		//Pins.Gpio.Socket3Pin2;
		//Pins.Gpio.Socket4Pin1;
		//Pins.Gpio.Socket4Pin2;
		//Pins.Gpio.Socket5Pin1;
		//Pins.Gpio.Socket5Pin2;
		//Pins.Gpio.Socket6Pin1;
		//Pins.Gpio.Socket6Pin2;
		//Pins.Gpio.Socket7Pin1;
		//Pins.Gpio.Socket7Pin2;
		//Pins.Gpio.Socket8Pin1;
		//Pins.Gpio.Socket8Pin2;
		//Pins.Gpio.Socket9Pin1;
		//Pins.Gpio.Socket9Pin2;
		//Pins.Gpio.Socket10Pin1;
		//Pins.Gpio.Socket10Pin2;
		//Pins.Gpio.Socket11Pin1;
		//Pins.Gpio.Socket11Pin2;
	static OutputPort buttonLed = new OutputPort(ButtonLedPin, false);

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

		Debug.Print("Program running");

		Thread.Sleep(Timeout.Infinite);
	}

	private static void Button_OnInterrupt(uint data1, uint data2, DateTime time)
	{
		Debug.Print(time.ToString("hh:mm:ss") + " Data1:" + data1 + " Data 2:" + data2);

		buttonLed.Write(!buttonLed.Read());
	}

So I could test the PWM port I used a Grove Rotary Angle Sensor plugged into Socket 4 and a Grove LED (Red, Green or Blue) plugged into Socket 6 with a standard cable for pin 1 or my twisted cable for pin 2.

public class Program
{
	public static void Main()
	{
		AnalogInput analogSensor = new AnalogInput(Pins.Analog.Socket4Pin1);

		//const Cpu.PWMChannel LedPin = Pins.Pwm.Socket6Pin1;
		const Cpu.PWMChannel LedPin = Pins.Pwm.Socket6Pin2;
			
		PWM ledDim = new PWM(LedPin, 1000.0, 0.0, false);

		ledDim.Start();
		Debug.Print("Program running");

		while (true)
		{
			double sensorValue = analogSensor.Read();

			Debug.Print(DateTime.Now.ToString("hh:mm:ss") +" Value:" + sensorValue.ToString("F1"));

			ledDim.DutyCycle = sensorValue;

			Thread.Sleep(500);
		}
	}
}

All of the Analog, GPIO & PWM sockets/pins worked as expected, there maybe a couple of extra PWM outputs available on I2C sockets.

Nexus LoRa Radio 915 MHz Payload Addressing client

This is a demo Ingenuity Micro Nexus client (based on the Netduino example for my RFM9XLoRaNetMF library) that uploads temperature and humidity data to my Azure IoT Hubs/Central or AdaFruit.IO on Raspberry PI field gateways

Bill of materials (Prices June 2019).

// <copyright file="client.cs" company="devMobile Software">
// Copyright ® 2019 Feb devMobile Software, All Rights Reserved
//
//  MIT License
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE"
//
// </copyright>
namespace devMobile.IoT.Nexus.FieldGateway
{
	using System;
	using System.Text;
	using System.Threading;
	using Microsoft.SPOT;
	using Microsoft.SPOT.Hardware;

	using devMobile.IoT.NetMF.ISM;
	using devMobile.NetMF.Sensor;
	using IngenuityMicro.Nexus;

	class NexusClient
	{
		private Rfm9XDevice rfm9XDevice;
		private readonly TimeSpan dueTime = new TimeSpan(0, 0, 15);
		private readonly TimeSpan periodTime = new TimeSpan(0, 0, 60);
		private readonly SiliconLabsSI7005 sensor = new SiliconLabsSI7005();
		private readonly Led _led = new Led();
		private readonly byte[] fieldGatewayAddress = Encoding.UTF8.GetBytes("LoRaIoT1");
		private readonly byte[] deviceAddress = Encoding.UTF8.GetBytes("Nexus915");

		public NexusClient()
		{
			rfm9XDevice = new Rfm9XDevice(SPI.SPI_module.SPI3, (Cpu.Pin)28, (Cpu.Pin)15, (Cpu.Pin)26);
			_led.Set(0, 0, 0);
		}

		public void Run()
		{

			rfm9XDevice.Initialise(frequency: 915000000, paBoost: true, rxPayloadCrcOn: true);
			rfm9XDevice.Receive(deviceAddress);

			rfm9XDevice.OnDataReceived += rfm9XDevice_OnDataReceived;
			rfm9XDevice.OnTransmit += rfm9XDevice_OnTransmit;

			Timer humidityAndtemperatureUpdates = new Timer(HumidityAndTemperatureTimerProc, null, dueTime, periodTime);

			Thread.Sleep(Timeout.Infinite);
		}


		private void HumidityAndTemperatureTimerProc(object state)
		{
			_led.Set(0, 128, 0);

			double humidity = sensor.Humidity();
			double temperature = sensor.Temperature();

			Debug.Print(DateTime.UtcNow.ToString("hh:mm:ss") + " H:" + humidity.ToString("F1") + " T:" + temperature.ToString("F1"));

			rfm9XDevice.Send(fieldGatewayAddress, Encoding.UTF8.GetBytes("t " + temperature.ToString("F1") + ",H " + humidity.ToString("F0")));
		}

		void rfm9XDevice_OnTransmit()
		{
			_led.Set(0, 0, 0);

			Debug.Print("Transmit-Done");
		}

		void rfm9XDevice_OnDataReceived(byte[] address, float packetSnr, int packetRssi, int rssi, byte[] data)
		{
			try
			{
				string messageText = new string(UTF8Encoding.UTF8.GetChars(data));
				string addressText = new string(UTF8Encoding.UTF8.GetChars(address));

				Debug.Print(DateTime.UtcNow.ToString("HH:MM:ss") + "-Rfm9X PacketSnr " + packetSnr.ToString("F1") + " Packet RSSI " + packetRssi + "dBm RSSI " + rssi + "dBm = " + data.Length + " byte message " + @"""" + messageText + @"""");
			}
			catch (Exception ex)
			{
				Debug.Print(ex.Message);
			}
		}
	}
}

Overall the development process was good with no modifications to my RFM9X.NetMF library or SI7005 library (bar removing a Netduino I2C work around) required

Nexus device with Seeedstudio Temperature & Humidity Sensors
Nexus Sensor data in Azure IoT Hub Field Gateway ETW Logging
Nexus temperature & humidity data displayed in Azure IoT Central

IoT.Net LoRa Radio 915 MHz Payload Addressing client

This is a demo ingenuity micro IoT.Net client (based on one of the examples in my RFM9XLoRaNetMF library) that uploads telemetry data to my Windows 10 IoT Core on Raspberry PI field gateway. 

Thought the silk screen says RFM69 this is a prototype running an RFM95 module.

iotnetlora.jpg

Bill of materials (Prices Sep 2018)

  • IoT.Net device (Beta tester will add price when available)

The device has an onboard MCP9808 temperature sensor which kept the BoM really short. I have had to make some modifications to my RFM9XLoRaNetMF library as the IoT.Net device uses a different SPI port. The code for this devices and the changes will be uploaded to GitHub in the next couple of days.

//---------------------------------------------------------------------------------
// Copyright (c) Sept 2018, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// git remote add origin https://github.com/KiwiBryn/FieldGateway.LoRa.IoTNetClient.git
// git push -u origin master
//---------------------------------------------------------------------------------
namespace devMobile.IoT.IoTNet.FieldGateway
{
	using System;
	using System.Text;
	using System.Threading;
	using Microsoft.SPOT;
	using Microsoft.SPOT.Hardware;
	using devMobile.IoT.NetMF.ISM;
	using IngenuityMicro.Sensors;

	class IoTNetClient
	{
		private readonly Rfm9XDevice rfm9XDevice;
		private readonly TimeSpan dueTime = new TimeSpan(0, 0, 10);
		private readonly TimeSpan periodTime = new TimeSpan(0, 0, 30);
		private readonly MCP9808 mcp9808 = new MCP9808();
		private readonly OutputPort _led = new OutputPort((Cpu.Pin)16 + 8, false);
		private readonly byte[] fieldGatewayAddress = Encoding.UTF8.GetBytes("LoRaIoT1");
		private readonly byte[] deviceAddress = Encoding.UTF8.GetBytes("IoTNet1");

		public IoTNetClient()
		{
			rfm9XDevice = new Rfm9XDevice( SPI.SPI_module.SPI3, (Cpu.Pin)16 + 9, (Cpu.Pin)5, (Cpu.Pin)4);
		}

		public void Run()
		{
			rfm9XDevice.Initialise(frequency: 915000000, paBoost: true, rxPayloadCrcOn: true);
			rfm9XDevice.Receive(deviceAddress);

			rfm9XDevice.OnDataReceived += rfm9XDevice_OnDataReceived;
			rfm9XDevice.OnTransmit += rfm9XDevice_OnTransmit;

			Timer temperatureUpdates = new Timer(TemperatureTimerProc, null, dueTime, periodTime);

			Thread.Sleep(Timeout.Infinite);
		}

		private void TemperatureTimerProc(object state)
		{
			_led.Write(true);

			double temperature = mcp9808.ReadTempInC();

			Debug.Print(DateTime.UtcNow.ToString("hh:mm:ss") + "  T:" + temperature.ToString("F1"));

			rfm9XDevice.Send(fieldGatewayAddress, Encoding.UTF8.GetBytes("t " + temperature.ToString("F1")));

			_led.Write(true);
		}

		void rfm9XDevice_OnTransmit()
		{
			Debug.Print("Transmit-Done");
			_led.Write(false);
		}

		void rfm9XDevice_OnDataReceived(byte[] address, float packetSnr, int packetRssi, int rssi, byte[] data)
		{
			try
			{
				string messageText = new string(UTF8Encoding.UTF8.GetChars(data));
				string addressText = new string(UTF8Encoding.UTF8.GetChars(address));

				Debug.Print(DateTime.UtcNow.ToString("HH:MM:ss") + "-Rfm9X PacketSnr " + packetSnr.ToString("F1") + " Packet RSSI " + packetRssi + "dBm RSSI " + rssi + "dBm = " + data.Length + " byte message " + @"""" + messageText + @"""");
			}
			catch (Exception ex)
			{
				Debug.Print(ex.Message);
			}
		}
	}
}
}

.Net Framework debug output Field Gateway

22:55:39-RX From IoTNet1 PacketSnr 9.5 Packet RSSI -50dBm RSSI -110dBm = 6 byte message "t 23.6"
 Sensor IoTNet1t Value 23.6
 AzureIoTHubClient SendEventAsync start
 AzureIoTHubClient SendEventAsync finish
The thread 0xbec has exited with code 0 (0x0).
The thread 0xbb4 has exited with code 0 (0x0).
The thread 0xa0c has exited with code 0 (0x0).
The thread 0x13c has exited with code 0 (0x0).
22:56:09-RX From IoTNet1 PacketSnr 9.3 Packet RSSI -44dBm RSSI -102dBm = 6 byte message "t 23.8"
 Sensor IoTNet1t Value 23.8
 AzureIoTHubClient SendEventAsync start
 AzureIoTHubClient SendEventAsync finish

A small footprint, battery powered .NetMF 4.4 LoRa device designed and made in New Zealand with Visual Studio 2017 support is great.