/*********************************************************************
FOR USE WITH TOTALVARIO
Tools > Board > Arduino MBed OS Boards > Seeed XIAO nRF52840
XIAO BLE Arduino device driver bug in v2.7.2 (and v2.9.0)
Note: See - https://forum.seeedstudio.com/t/xiao-ble-sense-mbed-2-7-2-battery-charge-and-voltage-monitor-analogread-p0-31-does-not-work/266438
Note: (SEE 30th JANUARY NOTE BELOW)
HardwareBLESerial library needs to be modified (HardwareBLESerial.h file) so that the BLE characterstics used are HM-10 compatible
(not Nordic nRF)
Current draw with BLE paired and sensors enabled = 7.7mA (measured at: 2Hz)
20 April 23: Reduced Abs Pressure to Oversampling x32 and 16 Measurements per second.
This to comply with DSP310 datasheet "possible combinations ... Pressure Measurement Rate and Oversampling".
16 April 23: Use Abs Pressure to correct Diff Pressure (formula in Sensirion tech sheet)
Changed code to accomodate the addition of a Infineon DPS310 absolute pressure sensor.
23 March 23:
Changed code to accomodate replacement of AHT temp/hum sensor to: SHT45.
31 January 23:
Changed SDP31 read interval to 99mS (10.1Hz).Preliminary testing seems to show stability of device at this setting.
30 January 23:
BWing kindly changed TotalVario's BLE configuration, Nordic BLE UUID characteristics are now accepted.
These are the default characteristics used by Uberi's Arduino library for Nordic Semiconductors' proprietary
UART/Serial Port Emulation over BLE protocol, using ArduinoBLE.
https://github.com/Uberi/Arduino-HardwareBLESerial
27 January 23:
Changed Battery Voltage output from Battery Volts, to Battery SOC %. Battery SOC now showing on TotalVario main display.
All functions working (with HM-10 BLE characteristics in HardwareBLESerial.h file, of HardwareBLESerial library)
23 January 23:
Second version sketch - Works. Had to make changes to 2.9.0 mbed analogread library as per above.
TV not showing battery Voltage on main screen.
Very good BLE Android Logger app: Serial Bluetooth Terminal 1.4.
Setup for BLE Devices:
Home button (three horizontal lines) > "Devices" > Tab: "Bluetooth LE" > "SCAN" > Device should be found -
Long press on device in list of devices (long press!)
"Edit" > "Custom" selection, then press "Service UUID" ... App will interogate device for UUID's, repeat for Read & Write chars too >
Go back and short press device.
You should now see the data streaming in now.
*********************************************************************/
//========================================
// ----------LIBRARIES--------------
#include <HardwareBLESerial.h>
#include <Wire.h> // For I2C comms
#include <sdpsensor.h> // Sensirion SDP3x library
#include <SensirionI2CSht4x.h> // Sensirion SHT4x library
#include <Adafruit_DPS310.h> // Adafruit DPS310 library
// ---------CONSTANTS---------------
// Function Timing
const int IntervalSDP = 99; // number of mS before IAS function repeats and also sends all BLE data
const int IntervalDPS = 113; // number of mS before DPS function repeats and collects Atmospheric Pressure
const int IntervalPOL = 500; // number of mS before Poll HardwareBLESerial function repeats
const int IntervalSHT = 4800; // number of mS before Temperature Humidity function repeats
const int IntervalBTV = 9963; // number of mS before Battery Voltage function repeats
const int IntervalLED = 3000; // number of mS before the LED indicating USB plugged in is on
const int LightLED = 500; // millisecs that the LED indicating USB plugged in is on
// -----------CLASSES---------------
// BLE Services
HardwareBLESerial &bleSerial = HardwareBLESerial::getInstance();
// Dual Pressure Sensor
SDP3XSensor sdp;
// Temperature & Humidity Sensor
SensirionI2CSht4x sht4x;
// Absolute Pressure Sensor
Adafruit_DPS310 dps;
//Adafruit_Sensor *dps_pressure = dps.getPressureSensor();
//------------VARIABLES-------------
// Startup Melody Notes & Variables
#define NOTE_C6 1047
#define NOTE_E6 1319
#define NOTE_G6 1568
#define REST 0
int tempo = 90;
int buzzer = 1;
int melody[] = {
NOTE_C6,
16,
NOTE_G6,
16,
NOTE_E6,
16,
NOTE_C6,
32,
NOTE_G6,
-16,
NOTE_E6,
8,
};
int notes = sizeof(melody) / sizeof(melody[0]) / 2;
int wholenote = (60000 * 4) / tempo;
int divider = 0, noteDuration = 0;
// Sensirion Differential Pressure Variable
float sdpPres; // Variable for the differential pressure
// Infinion Atmospheric Pressure Variable
float absPres = 966.0; // Variable for the Atmospheric absolute pressure
// Battery Voltage Variables
int adcRead = 0; // Variable for reading the Voltage-Divide network on Xiao
float batVolt = 4.00; // Battery Volts
float batPrct = 50.0; // Battery SOC %
// Battery Charging Variable
#define charging D10 // Variable for detecting USB plugged in - so battery is charging
// Ambient Environment Variables
float ambTemp = 0;
float ambHumi = 0;
// Sentence Buffer (String)
String msg = ""; // The final constructed sentence is: msg
// Function Timing
unsigned long currentMillis = 0; // Stores the value of millis() in each iteration of loop()
unsigned long previousSDPMillis = 0;
unsigned long previousPOLMillis = 0;
long previousSHTMillis = 0;
long previousDPSMillis = 0;
long previousBTVMillis = 0;
long previousLEDMillis = 0;
bool flashLEDstate = 0; // Used to record whether the LED is on or off
//===================SETUP========================
void setup() {
// Initialize the LED's as outputs and ensure they are off.
pinMode(LEDR, OUTPUT);
digitalWrite(LEDR, HIGH); // Ensure the LED off
pinMode(LEDG, OUTPUT);
digitalWrite(LEDG, HIGH); // Ensure the LED off
pinMode(LEDB, OUTPUT);
digitalWrite(LEDB, HIGH); // Ensure the LED off
// Setup Xiao Battery Monitoring
pinMode(P0_31, INPUT); // Battery Voltage monitoring pin
pinMode(P0_14, OUTPUT); // Enable Battery Voltage monitoring pin
digitalWrite(P0_14, LOW); // Enable
// Setup Xiao Battery Monitoring ADC
analogReference(AR_INTERNAL2V4); //Vref=2.4V
analogReadResolution(12); //12bits
// Setup Charge Monitoring
pinMode(D10, INPUT); //USB Voltage divided and fed to D10. Low =< 0.99V, High => 2.31V
// Setup Xiao Battery Charging Rate
pinMode(P0_13, OUTPUT); // Charge Current setting pin
digitalWrite(P0_13, LOW); // Charge Current = 100mA (13 High = 50mA)
// Setup Buzzer Output
pinMode(D1, OUTPUT); // D1 output to Buzzer
// Set up HardwareBLESerial and set BLE Name
bleSerial.beginAndSetupBLE("Airspeed_probe");
// Initialise I2C
Wire.begin();
// Initialise Serial Monitor (only used for debugging)
// Serial.begin(115200);
// while (!Serial) {
// delay(1000);
// }
// Initialise Sensirion SDP
int ret = sdp.init();
if (ret == 0) {
} else {
while (true) {
delay(500);
}
}
// Initialise Sensirion SHT
sht4x.begin(Wire);
// Initialise Infinion DPS sensor
if (! dps.begin_I2C()) { // Can pass in I2C address here
while (1) yield();
}
dps.configurePressure(DPS310_16HZ, DPS310_32SAMPLES); // See DPS310 datasheet for combinations
dps.configureTemperature(DPS310_2HZ, DPS310_2SAMPLES);
// Startup Melody
{
for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) {
divider = melody[thisNote + 1];
if (divider > 0) {
// regular note, just proceed
noteDuration = (wholenote) / divider;
} else if (divider < 0) {
// dotted notes are represented with negative durations!!
noteDuration = (wholenote) / abs(divider);
noteDuration *= 1.5; // increases the duration in half for dotted notes
}
tone(buzzer, melody[thisNote], noteDuration * 0.9);
delay(noteDuration);
noTone(buzzer);
}
delay(200);
}
// Battery SoC Beeps
adcRead = analogRead(P0_31);
batVolt = ((510e3 + 1000e3) / 510e3) * 2.4 * adcRead / 4096;
int n;
{
if (batVolt > 3.9) {
// fully charged ... four beeps
for (n = 1; n <= 4; n++) {
tone(buzzer, 880, 200);
delay(400);
}
} else if (batVolt > 3.8) {
// well charged ... three beeps
for (n = 1; n <= 3; n++) {
tone(buzzer, 880, 200);
delay(400);
}
} else if (batVolt > 3.7) {
// some charge ... two beeps
for (n = 1; n <= 2; n++) {
tone(buzzer, 880, 200);
delay(400);
}
} else {
// discharged ... one beep
tone(buzzer, 440, 300);
delay(400);
}
}
noTone(buzzer);
delay(100);
}
//=================== LOOP ========================
void loop() {
currentMillis = millis(); // Capture the latest value of millis()
pollBLE(); // This function must be called regularly to perform BLE updates
readSDP(); // This function reads SDP, sends to variable then calls buildMSG for BLEprinting
readDPS(); // This function reads DPS function reads Atmospheric Pressure and sends to variable
readSHT(); // This function reads ambient Temperature/Humidity and sends to variable
readBTV(); // This function reads Battery Voltage and sends to variable
flashLED(); // This function flashes the LED if USB is plugged (Battery charging)
}
//================ POLL BLESERIAL FUNCTION =====================
// pollBLE+++++++++++
void pollBLE() // Call function to poll BLE
{
if (currentMillis >= previousPOLMillis + IntervalPOL) // run 'pollBLE' function only once time is up
{
previousPOLMillis = currentMillis; // save the time when change was made to bleSerial.poll();
bleSerial.poll(); // this must be called regularly to perform BLE updates
}
}
//================ SENSOR FUNCTIONS =====================
// readSPD+++++++++++
void readSDP() // read Differential Pressure from SDP sensor, append to a NEMA message, then print / write to serial / BLE
{
if (currentMillis >= previousSDPMillis + IntervalSDP) // run 'readIAS' function only once time is up
{
previousSDPMillis = currentMillis; // save the time when change was made
int ret = sdp.readSample();
if (ret == 0) {
sdpPres = sdp.getDifferentialPressure();
// Serial.println(sdpPres); // Print Differential Pressure for debugging
}
//sdpPres = sdpPres - 0.02;
sdpPres = sdpPres * (966 / absPres); // Correction of presssure differential wrt altitude/QNH
if (sdpPres < 0) { // If there is a negative pressure set Differential pressure to zero
sdpPres = 0;
}
buildMSG(); // Call function to build the TotalVario sentence, then transmit to BLE
}
}
// readDPS+++++++++++
void readDPS() // read Atmospheric Pressure from DPS sensor
{
if (currentMillis >= previousDPSMillis + IntervalDPS) // run 'readDPS' function only once time is up
{
previousDPSMillis = currentMillis; // save the time when change was made
sensors_event_t temp_event, pressure_event;
while (!dps.temperatureAvailable() || !dps.pressureAvailable()) {
return; // wait until there's something to read
}
dps.getEvents(&temp_event, &pressure_event);
// Serial.print(F("Pressure = "));
// Serial.println(pressure_event.pressure);
absPres = pressure_event.pressure;
// Serial.println(absPres); // Print Static Pressure for debugging
}
}
// readSHT+++++++++++
void readSHT() // read Temperature & Humidity
{
if (currentMillis >= previousSHTMillis + IntervalSHT) // run 'readTH' function only once time is up
{
previousSHTMillis = currentMillis; // save the time when change was made
sht4x.measureHighPrecision(ambTemp, ambHumi);
// Serial.println(ambTemp); // Print OAT for debugging
// Serial.println(ambHumi); // Print Humidity RH for debugging
}
}
// readBTV+++++++++++
void readBTV() // read battery Volts
{
if (currentMillis >= previousBTVMillis + IntervalBTV) // run 'readBV' function only once time is up
{
previousBTVMillis = currentMillis; // save the time when change was made
adcRead = analogRead(P0_31);
batVolt = ((510e3 + 1000e3) / 510e3) * 2.4 * adcRead / 4096;
batPrct = -4370 + (2105 * batVolt) - (247.5144 * sq(batVolt)); // approximates battery SOC from Volts. Uses quadratic to approx.
if (batPrct < 0) // If battery Voltage is less than 0% set batPrct to 0%
{
batPrct = 0;
}
if (batPrct > 100) // If battery Voltage is more than 100% set batPrct to 100%
{
batPrct = 100;
}
// Serial.println(batVolt); // Print Battery Voltage for debugging
// Serial.println(batPrct); // Print Battery Percentage for debugging
}
}
// flashLED+++++++++++
void flashLED() // flash the LED if USB is plugged in
{
// If the USB power is connected go to next step to send BLE "Charging" message and flash LED's
if (flashLEDstate == 0) // if the LED is off, wait for the interval to expire before turning it on
{
if (currentMillis - previousLEDMillis >= IntervalLED) // time is up, so turn LED on
{
// Test to see if USB power is connected (so if battery is charging)
int value = digitalRead(charging);
if (value > 0) {
//bleSerial.println("Battery is charging"); // BLE Print
//Serial.println("Battery is charging"); // Serial Print
digitalWrite(LEDB, LOW); // LED on
flashLEDstate = 1; // Set the LED state to 1 or "on"
previousLEDMillis += IntervalLED; // save the time when change was made
}
}
} else // ie if LED is on. If on, we must wait for the duration to expire before turning it off
{
if (currentMillis - previousLEDMillis >= LightLED) // time is up, so turn LED off
{
digitalWrite(LEDB, HIGH); // LED off
flashLEDstate = 0; // Set the LED state to 0 or "off"
previousLEDMillis += LightLED; // save the time when change was made
}
}
}
//================ MESSAGE BUILD FUNCTIONS =====================
// $PTVSOAR,<type>,<value>,<type>,<value>,...*<checksum>
// Note: Checksum is optional
// buildMSG+++++++++++
void buildMSG() {
// TotalVario Sentence without checksum (checksum is optional)
msg += F("$PTVSOAR"); // Protocol Identifier
msg += F(",");
msg += F("MNA");
msg += F(",");
msg += F("FlyingSilicon"); // Device Manufacturer (free text modify as required)
msg += F(",");
msg += F("MMO");
msg += F(",");
msg += F("ProbeS4"); // Device Model (free text modify as required)
msg += F(",");
msg += F("MSN");
msg += F(",");
msg += F("2023Q2001"); // Device Serial Identifier (free text modify as required)
msg += F(",");
msg += F("PIT");
msg += F(",");
msg += String(sdpPres, 2); // Pitot Differential Pressure
msg += F(",");
msg += F("PRS");
msg += F(",");
msg += String(absPres, 2); // Atmospheric Pressure
msg += F(",");
msg += F("OAT");
msg += F(",");
msg += String(ambTemp, 2); // Air Temerature
msg += F(",");
msg += F("OAH");
msg += F(",");
msg += String(ambHumi, 2); // Air Relative Humidity
msg += F(",");
msg += F("PCT");
msg += F(",");
msg += String(batPrct, 1); // Battery State of Charge %
// Serial.println(msg); // Print msg for debugging
// Convert string to a character array and BLE Print
int buff_len = msg.length() + 1;
char buff_array[buff_len];
msg.toCharArray(buff_array, buff_len);
bleSerial.println(buff_array); // Print the entire message string
// Clear the msg buffer for next read
msg = "";
}