Random wanderings through Microsoft Azure esp. PaaS plumbing, the IoT bits, AI on Micro controllers, AI on Edge Devices, .NET nanoFramework, .NET Core on *nix and ML.NET+ONNX
The voltage my test setup was calculating looked wrong, then I realised that the sample calculation in the RAK Wireless forums wasn’t applicable to my setup.
I updated the formula used to calculate the battery voltage and deployed the application
public static void Main()
{
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} devMobile.IoT.RAK.Wisblock.AzureIoTHub.RAK11200.PowerSleep starting");
Thread.Sleep(5000);
try
{
double batteryVoltage;
Configuration.SetPinFunction(Gpio.IO04, DeviceFunction.I2C1_DATA);
Configuration.SetPinFunction(Gpio.IO05, DeviceFunction.I2C1_CLOCK);
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Battery voltage measurement");
// Configure Analog input (AIN0) port then read the "battery charge"
AdcController adcController = new AdcController();
using (AdcChannel batteryVoltageAdcChannel = adcController.OpenChannel(AdcControllerChannel))
{
batteryVoltage = batteryVoltageAdcChannel.ReadValue() / 723.7685;
Debug.WriteLine($" BatteryVoltage {batteryVoltage:F2}");
if (batteryVoltage < Config.BatteryVoltageBrownOutThreshold)
{
Sleep.EnableWakeupByTimer(Config.FailureRetryInterval);
Sleep.StartDeepSleep();
}
}
catch (Exception ex)
{
...
}
To test the accuracy of the voltage calculation I am going to run my setup on the office windowsill for a week regularly measuring the voltage. Then, turn the solar panel over (so the battery is not getting charged) and monitor the battery discharging until the RAK11200 WisBlock WiFi Module won’t connect to the network.
After some “tinkering” I found the voltage calculation was surprisingly accurate (usually within 0.01V) for my RAK19001 and RAK19007 base boards.
When the battery voltage was close to its minimum working voltage of the ESP32 device it would reboot when the WifiNetworkHelper.ConnectDhcp method was called. This would quickly drain the battery flat even when the solar panel was trying to charge the battery.
Now, before trying to connect to the wireless network the battery voltage is checked and if too low (more experimentation required) the device goes into a deep sleep for a configurable period (more experimentation required). This is so the solar panel can charge the battery to a level where wireless connectivity will work.
public static void Main()
{
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} devMobile.IoT.RAK.Wisblock.AzureIoTHub.RAK11200.PowerSleep starting");
Thread.Sleep(5000);
try
{
Configuration.SetPinFunction(Gpio.IO04, DeviceFunction.I2C1_DATA);
Configuration.SetPinFunction(Gpio.IO05, DeviceFunction.I2C1_CLOCK);
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Wifi connecting");
if (!WifiNetworkHelper.ConnectDhcp(Config.Ssid, Config.Password, requiresDateTime: true))
{
if (NetworkHelper.HelperException != null)
{
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} WifiNetworkHelper.ConnectDhcp failed {NetworkHelper.HelperException}");
}
Sleep.EnableWakeupByTimer(Config.FailureRetryInterval);
Sleep.StartDeepSleep();
}
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Wifi connected");
// Configure the SHTC3
I2cConnectionSettings settings = new(I2cDeviceBusID, Shtc3.DefaultI2cAddress);
I2cDevice device = I2cDevice.Create(settings);
Shtc3 shtc3 = new(device);
// Assuming that if TryGetTemperatureAndHumidity fails accessing temperature or relativeHumidity will cause an exception
shtc3.TryGetTemperatureAndHumidity(out var temperature, out var relativeHumidity);
#if SLEEP_SHT3C
shtc3.Sleep();
#endif
// Configure Analog input (AIN0) port then read the "battery charge"
AdcController adcController = new AdcController();
AdcChannel batteryChargeAdcChannel = adcController.OpenChannel(AdcControllerChannel);
double batteryCharge = batteryChargeAdcChannel.ReadRatio() * 100.0;
Debug.WriteLine($" Temperature {temperature.DegreesCelsius:F1}°C Humidity {relativeHumidity.Value:F0}% BatteryCharge {batteryCharge:F1}");
// Assemble the JSON payload, should use nanoFramework.Json
string payload = $"{{\"RelativeHumidity\":{relativeHumidity.Value:F0},\"Temperature\":{temperature.DegreesCelsius.ToString("F1")}, \"BatteryCharge\":{batteryCharge:F1}}}";
// Configure the HttpClient uri, certificate, and authorization
string uri = $"{Config.AzureIoTHubHostName}.azure-devices.net/devices/{Config.DeviceID}";
HttpClient httpClient = new HttpClient()
{
SslProtocols = System.Net.Security.SslProtocols.Tls12,
HttpsAuthentCert = new X509Certificate(Config.DigiCertBaltimoreCyberTrustRoot),
BaseAddress = new Uri($"https://{uri}/messages/events?api-version=2020-03-13"),
};
httpClient.DefaultRequestHeaders.Add("Authorization", SasTokenGenerate(uri, Config.Key, DateTime.UtcNow.Add(Config.SasTokenRenewFor)));
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Azure IoT Hub device {Config.DeviceID} telemetry update start");
HttpResponseMessage response = httpClient.Post("", new StringContent(payload));
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Response code:{response.StatusCode}");
response.EnsureSuccessStatusCode();
}
catch (Exception ex)
{
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Azure IoT Hub telemetry update failed:{ex.Message} {ex?.InnerException?.Message}");
Sleep.EnableWakeupByTimer(Config.FailureRetryInterval);
Sleep.StartDeepSleep();
}
Sleep.EnableWakeupByTimer(Config.TelemetryUploadInterval);
#if SLEEP_LIGHT
Sleep.StartLightSleep();
#endif
#if SLEEP_DEEP
Sleep.StartDeepSleep();
#endif
}
The LightSleep or DeepSleep based code is significantly less complex because the allocation and deallocation of resources does not have to be managed because the application is restarted when the WakeUp Timer triggers.
RAK1702 Schematic with voltage divider to ADC_VBAT connection highlighted
RAK1701 Schematic with ADC_VBAT to CPU slot connection highlighted
The RAK19007 WisBlock Base Board has a voltage divider (R3&R4 with output ADC_VBAT) which is connected (via R7) to pin 21(AIN0) on the CPU slot connector.
RAK11200 schematic with CPU Slot to ESP32-WROVER-B connection highlighted
public class Program
{
private const int I2cDeviceBusID = 1;
private const int AdcControllerChannel = 0;
public static void Main()
{
DateTime sasTokenValidUntilUtc = DateTime.UtcNow;
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} devMobile.IoT.RAK.Wisblock.AzureIoTHub.RAK11200.PowerBaseline starting");
Configuration.SetPinFunction(Gpio.IO04, DeviceFunction.I2C1_DATA);
Configuration.SetPinFunction(Gpio.IO05, DeviceFunction.I2C1_CLOCK);
if (!WifiNetworkHelper.ConnectDhcp(Config.Ssid, Config.Password, requiresDateTime: true))
{
if (NetworkHelper.HelperException != null)
{
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} WifiNetworkHelper.ConnectDhcp failed {NetworkHelper.HelperException}");
}
Thread.Sleep(Timeout.Infinite);
}
string uri = $"{Config.AzureIoTHubHostName}.azure-devices.net/devices/{Config.DeviceID}";
// not setting Authorization here as it will change as SAS Token refreshed
HttpClient httpClient = new HttpClient
{
SslProtocols = System.Net.Security.SslProtocols.Tls12,
HttpsAuthentCert = new X509Certificate(Config.DigiCertBaltimoreCyberTrustRoot),
BaseAddress = new Uri($"https://{uri}/messages/events?api-version=2020-03-13"),
};
I2cConnectionSettings settings = new(I2cDeviceBusID, Shtc3.DefaultI2cAddress);
I2cDevice device = I2cDevice.Create(settings);
Shtc3 shtc3 = new(device);
AdcController adcController = new AdcController();
AdcChannel batteryChargeAdcChannel = adcController.OpenChannel(AdcControllerChannel);
string sasToken = "";
while (true)
{
DateTime standardisedUtcNow = DateTime.UtcNow;
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Azure IoT Hub device {Config.DeviceID} telemetry update start");
if (sasTokenValidUntilUtc <= standardisedUtcNow)
{
sasTokenValidUntilUtc = standardisedUtcNow.Add(Config.SasTokenRenewEvery);
sasToken = SasTokenGenerate(uri, Config.Key, sasTokenValidUntilUtc);
Debug.WriteLine($" Renewing SAS token for {Config.SasTokenRenewFor} valid until {sasTokenValidUntilUtc:HH:mm:ss dd-MM-yy}");
}
if (!shtc3.TryGetTemperatureAndHumidity(out var temperature, out var relativeHumidity))
{
Debug.WriteLine($" Temperature and Humidity read failed");
continue;
}
double batteryCharge = batteryChargeAdcChannel.ReadRatio() * 100.0;
Debug.WriteLine($" Temperature {temperature.DegreesCelsius:F1}°C Humidity {relativeHumidity.Value:F0}% BatteryCharge {batteryCharge:F1}%");
string payload = $"{{\"RelativeHumidity\":{relativeHumidity.Value:F0},\"Temperature\":{temperature.DegreesCelsius.ToString("F1")}, \"BatteryCharge\":{batteryCharge:F1}}}";
try
{
using (HttpContent content = new StringContent(payload))
{
content.Headers.Add("Authorization", sasToken);
using (HttpResponseMessage response = httpClient.Post("", content))
{
Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Response code:{response.StatusCode}");
response.EnsureSuccessStatusCode();
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Azure IoT Hub POST failed:{ex.Message} {ex?.InnerException?.Message}");
}
Debug.WriteLine($"{DateTime.UtcNow:HH:mm:ss} Azure IoT Hub telemetry update done");
Thread.Sleep(Config.TelemetryUploadInterval);
}
}
...
}
I used Azure IoT Explorer to monitor the Azure IoT Hub device telemetry to see how BatteryCharge value decreased to a level where the device wouldn’t transmit.
Azure IoT Explorer telemetry – device connected to a USB charger (11:01:19) then un-plugged (11:02:02)
Azure IoT Explorer telemetry – Last two messages sent by the device
With no use of the “power conservation” functionality of the ESP32-WROVER-B powered by a 1200mAH battery the device ran for approximately 11hrs (11:00am – 10:00pm).
RAK2305 Wisblock AIN0 pin highlighted
I think the RAK2305 will not be able to measure “battery charge” as the SENSOR_VP pin on the Espressif ESP32-WROVER-B is not connected to AIN0.
I then used Visual Studio 2022 Debugger to “single step” further into the BME680 code and the first thing that looked a bit odd was the TryReadTemperatureCore, TryReadPressureCore, TryReadHumidityCore and TryReadGasResistanceCore return values were ignored.
I then single stepped into the TryReadTemperatureCore which was returning a boolean indicating whether the read was success.
private bool TryReadTemperatureCore(out Temperature temperature)
{
if (TemperatureSampling == Sampling.Skipped)
{
temperature = default;
return false;
}
var temp = (int)Read24BitsFromRegister((byte)Bme680Register.TEMPDATA, Endianness.BigEndian);
temperature = CompensateTemperature(temp >> 4);
return true;
}
This library was based on the dotnet/iotBmxx80 code, it looked similar, but I missed an important detail lots more ?’s…
Console.WriteLine("Hello BME680!");
// The I2C bus ID on the Raspberry Pi 3.
const int busId = 1;
// set this to the current sea level pressure in the area for correct altitude readings
Pressure defaultSeaLevelPressure = WeatherHelper.MeanSeaLevel;
I2cConnectionSettings i2cSettings = new(busId, Bme680.DefaultI2cAddress);
I2cDevice i2cDevice = I2cDevice.Create(i2cSettings);
using Bme680 bme680 = new Bme680(i2cDevice, Temperature.FromDegreesCelsius(20.0));
while (true)
{
// reset will change settings back to default
bme680.Reset();
// 10 consecutive measurement with default settings
for (var i = 0; i < 10; i++)
{
// Perform a synchronous measurement
var readResult = bme680.Read();
// Print out the measured data
Console.WriteLine($"Gas resistance: {readResult.GasResistance?.Ohms:0.##}Ohm");
Console.WriteLine($"Temperature: {readResult.Temperature?.DegreesCelsius:0.#}\u00B0C");
Console.WriteLine($"Pressure: {readResult.Pressure?.Hectopascals:0.##}hPa");
Console.WriteLine($"Relative humidity: {readResult.Humidity?.Percent:0.#}%");
if (readResult.Temperature.HasValue && readResult.Pressure.HasValue)
{
var altValue = WeatherHelper.CalculateAltitude(readResult.Pressure.Value, defaultSeaLevelPressure, readResult.Temperature.Value);
Console.WriteLine($"Altitude: {altValue.Meters:0.##}m");
}
if (readResult.Temperature.HasValue && readResult.Humidity.HasValue)
{
// WeatherHelper supports more calculations, such as saturated vapor pressure, actual vapor pressure and absolute humidity.
Console.WriteLine($"Heat index: {WeatherHelper.CalculateHeatIndex(readResult.Temperature.Value, readResult.Humidity.Value).DegreesCelsius:0.#}\u00B0C");
Console.WriteLine($"Dew point: {WeatherHelper.CalculateDewPoint(readResult.Temperature.Value, readResult.Humidity.Value).DegreesCelsius:0.#}\u00B0C");
}
// when measuring the gas resistance on each cycle it is important to wait a certain interval
// because a heating plate is activated which will heat up the sensor without sleep, this can
// falsify all readings coming from the sensor
Thread.Sleep(1000);
}
...
}
The Bme680 Read() method checked the TryReadTemperatureCore, TryReadPressureCore, TryReadHumidityCore & TryReadGasResistanceCore return values.
/// <summary>
/// Performs a synchronous reading.
/// </summary>
/// <returns><see cref="Bme680ReadResult"/></returns>
public Bme680ReadResult Read()
{
SetPowerMode(Bme680PowerMode.Forced);
Thread.Sleep((int)GetMeasurementDuration(HeaterProfile).Milliseconds);
var tempSuccess = TryReadTemperatureCore(out var temperature);
var pressSuccess = TryReadPressureCore(out var pressure, skipTempFineRead: true);
var humiditySuccess = TryReadHumidityCore(out var humidity, skipTempFineRead: true);
var gasSuccess = TryReadGasResistanceCore(out var gasResistance);
return new Bme680ReadResult(tempSuccess ? temperature : null, pressSuccess ? pressure : null, humiditySuccess ? humidity : null, gasSuccess ? gasResistance : null);
}
Visual Studio 2022 Output Window Output window when application failed
When I connected to the device with Tera Term it confirmed that the device was in a “kernel panic” loop.
nanoFramework Kernel Panic loop captured with Tera Term
Before I could debug the BME680 sample I had to get the Bmxx80 & Bmxx80.sample projects to compile (update NuGet packages and remove NerdBank.GitVersioning references).
/// <summary>
/// Sets the power mode to the given mode
/// </summary>
/// <param name="powerMode">The <see cref="Bme680PowerMode"/> to set.</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the power mode does not match a defined mode in <see cref="Bme680PowerMode"/>.</exception>
[Property("PowerMode")]
public void SetPowerMode(Bme680PowerMode powerMode)
{
//if (!powerMode.Equals(Bme680PowerMode.Forced) &&
// !powerMode.Equals(Bme680PowerMode.Sleep))
//{
// throw new ArgumentOutOfRangeException();
//}
var status = Read8BitsFromRegister((byte)Bme680Register.CTRL_MEAS);
status = (byte)((status & (byte)~Bme680Mask.PWR_MODE) | (byte)powerMode);
SpanByte command = new[]
{
(byte)Bme680Register.CTRL_MEAS, status
};
_i2cDevice.Write(command);
}
The first problem was the two powerMode.Equals statements used to validate the powerMode parameter around line 287 in Bme680.cs so I commented them out.
Exception when getting the “GasResistance” value
On start-up references to readResult.GasResistance.Ohms would regularly fail, so I commented out everywhere it was used.
Exception when getting the “Barometric Pressure” value
Then references to readResult.Pressure.Hectopascals would randomly fail, so I commented out everywhere it was used.
public static void RunSample()
{
Debug.WriteLine("Hello BME680!");
//////////////////////////////////////////////////////////////////////
Configuration.SetPinFunction(Gpio.IO04, DeviceFunction.I2C1_DATA);
Configuration.SetPinFunction(Gpio.IO05, DeviceFunction.I2C1_CLOCK);
// The I2C bus ID on the MCU.
const int busId = 1;
// set this to the current sea level pressure in the area for correct altitude readings
Pressure defaultSeaLevelPressure = WeatherHelper.MeanSeaLevel;
I2cConnectionSettings i2cSettings = new(busId, Bme680.DefaultI2cAddress);
I2cDevice i2cDevice = I2cDevice.Create(i2cSettings);
using Bme680 bme680 = new Bme680(i2cDevice, Temperature.FromDegreesCelsius(20.0));
while (true)
{
// reset will change settings back to default
bme680.Reset();
// 10 consecutive measurement with default settings
for (var i = 0; i < 10; i++)
{
// Perform a synchronous measurement
var readResult = bme680.Read();
// Print out the measured data
//Debug.WriteLine($"Gas resistance: {readResult.GasResistance.Ohms}Ohm");
Debug.WriteLine($"Temperature: {readResult.Temperature.DegreesCelsius}\u00B0C");
//Debug.WriteLine($"Pressure: {readResult.Pressure.Hectopascals}hPa");
Debug.WriteLine($"Relative humidity: {readResult.Humidity.Percent}%");
/*
if (!readResult.Temperature.Equals(null) && !readResult.Pressure.Equals(null))
{
var altValue = WeatherHelper.CalculateAltitude(readResult.Pressure, defaultSeaLevelPressure, readResult.Temperature);
Debug.WriteLine($"Altitude: {altValue.Meters}m");
}
if (!readResult.Temperature.Equals(null) && !readResult.Humidity.Equals(null))
{
// WeatherHelper supports more calculations, such as saturated vapor pressure, actual vapor pressure and absolute humidity.
Debug.WriteLine($"Heat index: {WeatherHelper.CalculateHeatIndex(readResult.Temperature, readResult. Humidity).DegreesCelsius}\u00B0C");
Debug.WriteLine($"Dew point: {WeatherHelper.CalculateDewPoint(readResult.Temperature, readResult.Humidity).DegreesCelsius}\u00B0C");
}
*/
// when measuring the gas resistance on each cycle it is important to wait a certain interval
// because a heating plate is activated which will heat up the sensor without sleep, this can
// falsify all readings coming from the sensor
Thread.Sleep(1000);
}
...
}
Visual Studio Debugger output displaying temperature and humidity values
I was using Azure IoT Explorer to monitor the telemetry and found that the initial versions of the application would fail after 6 or 7 hours. After reviewing the code I added a couple of “using” statements which appear to have fixed the problem as the soak test has been running for 12hrs, 24hrs, 36hrs, 48hrs, 96hrs…
//---------------------------------------------------------------------------------
// Copyright (c) September 2022, 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.
//
// https://docs.rakwireless.com/Product-Categories/WisBlock/RAK2305
//
// https://docs.rakwireless.com/Product-Categories/WisBlock/RAK11200
//
// https://store.rakwireless.com/products/rak1901-shtc3-temperature-humidity-sensor
//
// https://github.com/nanoframework/nanoFramework.IoT.Device/tree/develop/devices/Shtc3
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.RAK.Wisblock.RAK1901
{
using System;
using System.Diagnostics;
using System.Device.I2c;
using System.Threading;
using nanoFramework.Hardware.Esp32;
using Iot.Device.Shtc3;
public class Program
{
public static void Main()
{
Debug.WriteLine("devMobile.IoT.RAK.Wisblock.RAK11200RAK1901 starting");
try
{
// RAK11200 & RAK2305
Configuration.SetPinFunction(Gpio.IO04, DeviceFunction.I2C1_DATA);
Configuration.SetPinFunction(Gpio.IO05, DeviceFunction.I2C1_CLOCK);
I2cConnectionSettings settings = new(1, Shtc3.DefaultI2cAddress);
using (I2cDevice device = I2cDevice.Create(settings))
using (Shtc3 shtc3 = new(device))
{
while (true)
{
if (shtc3.TryGetTemperatureAndHumidity(out var temperature, out var relativeHumidity))
{
Debug.WriteLine($"Temperature {temperature.DegreesCelsius:F1}°C Humidity {relativeHumidity.Value:F0}%");
}
Thread.Sleep(10000);
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"SHTC3 initialisation or read failed {ex.Message}");
Thread.Sleep(Timeout.Infinite);
}
}
}
}
Visual Studio Output window displaying SHT31 temperature & humidity values
The RAK2305 Low Level Developer documentation described how to upload software developed with the Arduino tools by putting the ESP32 into “bootloader mode”. This is done by connecting (with the white jumper) the GPIO0 and GND pins on J14, and pressing the reset button.
The RAK2305 has has one onboard LED(TEST_LED) attached to IO18 which I added to the .NET nanoFrameworkBlinky sample.
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
//
using System;
using System.Device.Gpio;
using System.Threading;
using nanoFramework.Hardware.Esp32;
namespace Blinky
{
public class Program
{
private static GpioController s_GpioController;
public static void Main()
{
s_GpioController = new GpioController();
// pick a board, uncomment one line for GpioPin; default is STM32F769I_DISCO
// DISCOVERY4: PD15 is LED6
//GpioPin led = s_GpioController.OpenPin(PinNumber('D', 15), PinMode.Output);
// ESP32 DevKit: 4 is a valid GPIO pin in, some boards like Xiuxin ESP32 may require GPIO Pin 2 instead.
//GpioPin led = s_GpioController.OpenPin(4, PinMode.Output);
// FEATHER S2:
//GpioPin led = s_GpioController.OpenPin(13, PinMode.Output);
// F429I_DISCO: PG14 is LEDLD4
//GpioPin led = s_GpioController.OpenPin(PinNumber('G', 14), PinMode.Output);
// NETDUINO 3 Wifi: A10 is LED onboard blue
//GpioPin led = s_GpioController.OpenPin(PinNumber('A', 10), PinMode.Output);
// QUAIL: PE15 is LED1
//GpioPin led = s_GpioController.OpenPin(PinNumber('E', 15), PinMode.Output);
// STM32F091RC: PA5 is LED_GREEN
//GpioPin led = s_GpioController.OpenPin(PinNumber('A', 5), PinMode.Output);
// STM32F746_NUCLEO: PB75 is LED2
//GpioPin led = s_GpioController.OpenPin(PinNumber('B', 7), PinMode.Output);
//STM32F769I_DISCO: PJ5 is LD2
//GpioPin led = s_GpioController.OpenPin(PinNumber('J', 5), PinMode.Output);
// ST_B_L475E_IOT01A: PB14 is LD2
//GpioPin led = s_GpioController.OpenPin(PinNumber('B', 14), PinMode.Output);
// STM32L072Z_LRWAN1: PA5 is LD2
//GpioPin led = s_GpioController.OpenPin(PinNumber('A', 5), PinMode.Output);
// TI CC13x2 Launchpad: DIO_07 it's the green LED
//GpioPin led = s_GpioController.OpenPin(7, PinMode.Output);
// TI CC13x2 Launchpad: DIO_06 it's the red LED
//GpioPin led = s_GpioController.OpenPin(6, PinMode.Output);
// ULX3S FPGA board: for the red D22 LED from the ESP32-WROOM32, GPIO5
//GpioPin led = s_GpioController.OpenPin(5, PinMode.Output);
// Silabs SLSTK3701A: LED1 PH14 is LLED1
//GpioPin led = s_GpioController.OpenPin(PinNumber('H', 14), PinMode.Output);
// RAK11200 on RAK5005
//GpioPin led = s_GpioController.OpenPin(Gpio.IO12, PinMode.Output); // LED1 Green
//GpioPin led = s_GpioController.OpenPin(Gpio.IO02, PinMode.Output); // LED2 Blue
// RAK11200 on RAK19001 needs battery connected or power switch in rechargeable position.
//GpioPin led = s_GpioController.OpenPin(Gpio.IO12, PinMode.Output); // LED1 Green
//GpioPin led = s_GpioController.OpenPin(Gpio.IO02, PinMode.Output); // LED2 Blue
// RAK2305
//GpioPin led = s_GpioController.OpenPin(Gpio.IO18, PinMode.Output); // LED Green (Test LED) on device
// RAK2305 On 5005 throws exceptions
//GpioPin led = s_GpioController.OpenPin(Gpio.IO34, PinMode.Output); // LED1 Green
//GpioPin led = s_GpioController.OpenPin(Gpio.IO35, PinMode.Output); // LED2 Blue
// RAK2305 On 17001 throws exceptions
//GpioPin led = s_GpioController.OpenPin(Gpio.IO34, PinMode.Output); // LED1 Green
//GpioPin led = s_GpioController.OpenPin(Gpio.IO35, PinMode.Output); // LED2 Blue
led.Write(PinValue.Low);
while (true)
{
led.Toggle();
Thread.Sleep(125);
led.Toggle();
Thread.Sleep(125);
led.Toggle();
Thread.Sleep(125);
led.Toggle();
Thread.Sleep(525);
}
}
static int PinNumber(char port, byte pin)
{
if (port < 'A' || port > 'J')
throw new ArgumentException();
return ((port - 'A') * 16) + pin;
}
}
}
I added the RAK2305 configuration to my version of the nanoFramework Blinky sample and could reliably flash the onboard LED.