/*********************************************************************
FOR USE WITH XCSOAR
Tools > Board > Arduino MBed OS Boards > Seeed XIAO nRF52840 Sense
Current draw with BLE paired = 0.V / 93.3Ohms = 6.26mA
Current draw LED on = 0.V / 93.3Ohms = .mA
This sketch = 0.69V / 93.3Ohms = 7.4mA
With sensors = 7.7mA
25th Nov - Corrected flashLED function
23rd Nov - Converted LED battery charging delay timing to non-blocking timing.
15th Nov - Brought battery Voltage sense/threshold values down | Added BLE "Battery Charging" message (Working). Need to add a noDelay for LED flash duration.
6th Nov - This sketch modified to if test USB power is connected (+5V). Search "D10" & "charging".
2nd Nov - All working but SDP needs to be checked with a pitot. Chg LED lit when batt connected.
31st October 22 - Compiles. SDP & AHT not working.
WORKING WITH BLE AT IntervalIAS = 1250mS | 650mS | 250mS
*********************************************************************/
//========================================
// ----------LIBRARIES--------------
#include <HardwareBLESerial.h>
#include <Wire.h> // for I2C
#include <sdpsensor.h> // Sensirion library
#include <AHTxx.h>
#include <avr/dtostrf.h> // for dtostrf
// ---------CONSTANTS---------------
// Function Timing
const int IntervalIAS = 551; // number of mS before IAS function repeats and also sends all BLE data
const int IntervalPOL = 500; // number of mS before Poll HardwareBLESerial function repeats
const int IntervalTHM = 4800; // number of mS before Temperature Humidity function repeats
const int IntervalHum = 9700; // number of mS before Humidity function prints RH%
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
AHTxx aht20(AHTXX_ADDRESS_X38, AHT2x_SENSOR); //sensor address, sensor type
//------------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;
// Airspeed Variables
float diffPressure; // Storage for the differential pressure
float sdpTemp; // Storage for the temperature
float rho; // Density of Air kg/m3 (variable dependent on temperature)
float const abspressure = 101325; // Absolute pressure at Sealevel (Pa)
float const specificgas = 287.058; // Specific gas constant for dry air (J/kg.K)
float const correctionFactor = 1.000000; // Correction factor for Pitot hardware
float velmps = 0; // Calculated Velocity (m/s)
float velkts = 0; // Calculated Velocity (kts)
float velkmh = 0; // Calculated Velocity (km/hr)
// Battery Voltage Variables
int adcin = 0; // Variable for reading the Voltage-Divide network on Xiao
float volts = 3.40; // Battery Volts
// Battery Charging Variable
#define charging D10 // Variable for detecting USB plugged in - so battery is charging
// Ambient Environment Variables
float ahtValue; //to store T/Rh result temporarily
float ambTemp = 0;
// NEMA CheckSum Variables
const byte buff_len = 90;
char CRCbuffer[buff_len];
// NEMA pre-defined strings
String cmd = "$POV,"; // OpenVario prefix
char strX[8]; // The sensor value converted to a character
String Y = ""; // The OpenVario sensor type ("A" = Airspeed, "T" = Temperature, etc)
float x = 0; // The sensor value
String delim = ",";
String splat = "*";
String msg = ""; // The final constructed NEMA sentence
String crcNEW = "";
// Function Timing
unsigned long currentMillis = 0; // stores the value of millis() in each iteration of loop()
unsigned long previousIASMillis = 0;
unsigned long previousPOLMillis = 0;
long previousTHMMillis = 0;
long previousHumMillis = 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("IAS_probe");
// Initialise I2C
Wire.begin();
// Initialise Serial Monitor (only used for debugging)
Serial.begin(115200);
delay(200); // let serial console settle
// Initialise Sensirion SDP
int ret = sdp.init();
if (ret == 0) {
}
else {
while (true) {
delay(1000);
}
}
// Initialise Asair AHT2x Temp|Humidity Sensor
while (aht20.begin() != true) {
delay(200);
}
// 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
adcin = analogRead(P0_31);
volts = ((510e3 + 1000e3) / 510e3) * 2.4 * adcin / 4096;
int n;
// float volts = 4.2; // for testing logic
{
if (volts > 3.9) {
// fully charged ... four beeps
for (n = 1; n <= 4; n++)
{
tone(buzzer, 880, 200);
delay(400);
}
} else if (volts > 3.8) {
// well charged ... three beeps
for (n = 1; n <= 3; n++)
{
tone(buzzer, 880, 200);
delay(400);
}
} else if (volts > 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
readIAS(); // This function reads IASsends to buildMSG for BLEprinting
readTp(); // This function reads ambient Temperature and sends to buildMSG for BLEprinting
readHm(); // This function prints ambient Humidity and sends to buildMSG for BLEprinting
readBV(); // This function reads Battery Voltage and sends to buildMSG for BLEprinting
flashLED(); // This function flashes the LED
// Note: function 'buildMSG' takes the sensor value, calcs CRC & builds the NEMA sentence. Then BLEprints it.
}
//================ 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 madebleSerial.poll();
bleSerial.poll(); // this must be called regularly to perform BLE updates
}
}
//================ SENSOR FUNCTIONS =====================
// readIAS+++++++++++
void readIAS() // read IAS, append to a NEMA message, add CRC, then print / write to serial / BLE
{
if (currentMillis >= previousIASMillis + IntervalIAS) // run 'readIAS' function only once time is up
{
previousIASMillis = currentMillis; // save the time when change was made
int ret = sdp.readSample();
if (ret == 0) {
diffPressure = sdp.getDifferentialPressure();
sdpTemp = sdp.getTemperature();
}
diffPressure = diffPressure - 0.02;
if (diffPressure < 0) {// If there is a negative pressure set Differential pressure to zero
diffPressure = 0;
}
rho = abspressure / (specificgas * (ambTemp + 273.15)); // we use +273 to convert from Kelvin to Celcius
velmps = sqrt((2 * diffPressure * correctionFactor) / rho);
velkts = 1.9438444924574 * velmps;
velkmh = 3.6 * velmps;
// OpenVario variables
x = velkmh; // Airspeed in km/hr. I have set to display in Knots on my XCSoar screen
Y = "S";
buildMSG(); // Call function to build the NEMA sentence, then transmit to BLE
}
}
// readTH+++++++++++
void readTp() // read Temp & Hum and print Temp (only temperature)
{
if (currentMillis >= previousTHMMillis + IntervalTHM) // run 'readTH' function only once time is up
{
previousTHMMillis = currentMillis; // save the time when change was made
ahtValue = aht20.readTemperature(); //read 6-bytes via I2C, takes 80 milliseconds
ambTemp = ahtValue;
// OpenVario variables
x = ahtValue; // Temperature in degC
Y = "T";
buildMSG(); // Call function to build the NEMA sentence, then transmit to BLE
}
}
// readHm+++++++++++
void readHm() // print Humidity
{
if (currentMillis >= previousHumMillis + IntervalHum) // run 'readTH' function only once time is up
{
previousHumMillis = currentMillis; // save the time when change was made
ahtValue = aht20.readHumidity(); //read another 6-bytes via I2C, takes 80 milliseconds
// OpenVario variables
x = ahtValue; // Humidity RH%
Y = "H";
buildMSG(); // Call function to build the NEMA sentence, then transmit to BLE
}
}
// readBV+++++++++++
void readBV() // read battery Volts
{
if (currentMillis >= previousBTVMillis + IntervalBTV) // run 'readBV' function only once time is up
{
previousBTVMillis = currentMillis; // save the time when change was made
adcin = analogRead(P0_31);
volts = ((510e3 + 1000e3) / 510e3) * 2.4 * adcin / 4096;
// OpenVario variables
x = volts; // Airspeed in km/hr. I have set to display in Knots on my XCSoar screen
Y = "V";
buildMSG(); // Call function to build the NEMA sentence, then transmit to BLE
}
}
// 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 =====================
// $POV,<type>,<value>,<type>,<value>,...*<checksum>
// buildMSG+++++++++++
void buildMSG()
{
// OpenVario NEMA Sentence with checksum (XCSoar requires checksum)
dtostrf(x, 1, 1, strX); // format float value x to string XX
msg = cmd + Y + delim + strX + splat;
outputMSG(msg); // Call fuction outputMsg - print the entire message string, and append the CRC
}
// convertToCRC+++++++++++used by 'outputMsg' function to calculate XOR CRC suffix+++++++
byte convertToCRC(char *buff)
{
// NMEA CRC: XOR each byte with previous for all chars between '$' and '*'
char c;
byte i;
byte start_with = 0; // index of starting char in msg
byte end_with = 0; // index of starting char in msg
byte crc = 0;
for (i = 0; i < buff_len; i++) {
c = buff[i];
if (c == '$') {
start_with = i;
}
if (c == '*') {
end_with = i;
}
}
if (end_with > start_with) {
for (i = start_with + 1; i < end_with; i++) { // XOR every character between '$' and '*'
crc = crc ^ buff[i] ; // xor the next char
}
}
else { // else if error, print a msg
// Serial.println("CRC ERROR");
}
return crc;
// based on code by Elimeléc López - July-19th-2013
}
// outputMSG+++++++++++
void outputMSG(String msg)
{
msg.toCharArray(CRCbuffer, sizeof(CRCbuffer)); // put complete string into CRCbuffer
byte crc = convertToCRC(CRCbuffer); // call function to compute the crc value
if (crc < 16) { // if CRC is 0-F
String crcHEX = String(crc, HEX); // crc is now in HEX base
crcNEW = "0";
crcNEW.concat(crcHEX); // adds a character 0 to the front of crc (if crc is only one character)
}
else {
crcNEW = String(crc, HEX); // crc is now in HEX base
}
msg.concat(crcNEW); // adds crcNEW to end of msg
//Serial.println(msg); // Print msg
// 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);
}