Update2: The bluefruit BLE library is fast! Getting 10 full $PTVSOAR sentences per second received.
Update1: TotalVario beta#29 - $PTVSOAR sentence does not (yet) support 'CHG', so use this sprintf ...
sprintf(buf,
",PIT,%06.2f"
//",%08.3f"
",OAT,%+03.0f"
",OAH,%03.0f"
",PCT,%03.0f",
//",CHG,%d",
sdpPres // Pitot Differential Pressure 999.99
//, absPres // Atmospheric Pressure 9999.999
,
ambTemp // Air Temperature -99 .. +99
,
ambHumi // Air Relative Humidity 000 - 100
,
batPrct); // Battery State of Charge % 000 - 100
//,
//battChrg > 0 ? 1 : 0); // Battery Charging Flag
CHG is commented out. This sketch now works (well).
/*********************************************************************
Code tidied: 05 June 2023
05 June 2023 - added BLE connect|Disconnect tones
05 June 2023 - added CHG to sentence to indicate battery charging
04 June 2023 - used Bernd's sprintf sketch
22 April 2023 - Working with XIAO nRF (ie: not XIAO mBed device).
INFO:
MCU Hardware - Seeed XIAO nRF52840 (not "Sense" model)
Using Arduino IDE 1.8.19
Using Arduino 'Board' -> "Seeed nRF52 Boards" -> "Seeed XIAO nRF52840"
*********************************************************************/
// ----------LIBRARIES--------------
#include <bluefruit.h> // for BLE - Adafruit BLE Library
#include <Adafruit_LittleFS.h>
#include <InternalFileSystem.h>
#include <Wire.h> // for I2C comms
#include <avr/dtostrf.h> // for dtostrf
#include <sdpsensor.h> // Sensirion SDP3x library
#include <AHTxx.h> // Asair AHTx 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 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 = 3313; // number of mS before the Battery Charging function repeats (turns on Red LED)
const int LightLED = 500; // millisecs that the (red) LED, indicating charging, is on
// -----------CLASSES---------------
// BLE Services
BLEDfu bledfu; // OTA DFU service
BLEDis bledis; // device information
BLEUart bleuart; // uart over ble
// Dual Pressure Sensor
SDP3XSensor sdp;
// Temperature & Humidity Sensor
//SensirionI2CSht4x sht4x;
AHTxx aht20(AHTXX_ADDRESS_X38, AHT2x_SENSOR); //sensor address, sensor type
// Absolute Pressure Sensor
//Adafruit_DPS310 dps;
//------------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;
// Function Timing
unsigned long currentMillis = 0; // Stores the value of millis() in each iteration of loop()
unsigned long previousSDPMillis = 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
// 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
// Ambient Environment Variables
float ambTemp = 0;
float ambHumi = 0;
float ahtValue; // To store T/Rh result temporarily
// Battery Variables
float batVolt = 4.00; // Battery Volts
float batPrct = 50.0; // Battery SOC %
bool battChrg = 0; // Battery Charging
//===================SETUP========================
void setup() {
//Serial.begin(115200); // For debugging
// Initialise I2C
Wire.begin();
// Setup Xiao Battery Monitoring
pinMode(VBAT_ENABLE, OUTPUT); // Set the battery Voltage divider ground leg as an Output
pinMode(23, INPUT); // Set monitoring leg as an Input
// Setup Xiao Battery Charging Rate
pinMode(22, OUTPUT); // Set the charger mA selection pin as an Output
digitalWrite(22, LOW); // Charge Current = 100mA (22 High = 50mA)
// Setup Buzzer Output
pinMode(D1, OUTPUT); // D1 output to Buzzer
// Initialise Sensirion SDP
int ret = sdp.init();
if (ret == 0) {
} else {
while (true) {
delay(500);
}
}
// Initialise Asair AHT2x Temp|Humidity Sensor
while (aht20.begin() != true) {
delay(200);
}
// // 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
batVolt = analogRead(PIN_VBAT) * (0.003515625F) * (1510.0 / 510.0);
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);
//BLE
#if CFG_DEBUG
// Blocking wait for connection when debug mode is enabled via IDE
while (!Serial) yield();
#endif
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.begin();
Bluefruit.setTxPower(-12); // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
//Bluefruit.setName(getMcuUniqueID()); // useful testing with multiple central connections or (eg): Bluefruit.setName("Name");
Bluefruit.setName("ProbeS4");
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
// Configure and Start Device Information Service
bledis.setManufacturer("Seeed");
bledis.setModel("XIAOnRF");
bledis.begin();
// Configure and Start BLE Uart Service
bleuart.begin();
// Set up and start advertising
startAdv();
}
void startAdv(void) {
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
// Include bleuart 128-bit uuid
Bluefruit.Advertising.addService(bleuart);
// Secondary Scan Response packet (optional)
// Since there is no room for 'Name' in Advertising packet
Bluefruit.ScanResponse.addName();
Bluefruit.Advertising.restartOnDisconnect(true);
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
}
//===================LOOP========================
void loop() {
currentMillis = millis(); // Capture the latest value of millis()
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)
}
//================BLE FUNCTIONS=====================
// Callback invoked when central connects
void connect_callback(uint16_t conn_handle) {
// Get the reference to current connection
BLEConnection* connection = Bluefruit.Connection(conn_handle);
char central_name[32] = { 0 };
connection->getPeerName(central_name, sizeof(central_name));
// Serial.print("Connected to "); // For debugging
// Serial.println(central_name); // For debugging
tone(buzzer, 440, 400);
delay(400);
tone(buzzer, 880, 200);
}
void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
(void)conn_handle;
(void)reason;
// Serial.print("Disconnected, reason = 0x"); // For debugging
// Serial.println(reason, HEX); // For debugging
tone(buzzer, 440, 600);
delay(400);
tone(buzzer, 330, 600);
}
//================ SENSOR FUNCTIONS =====================
// readSPD+++++++++++
void readSDP() // read Differential Pressure from SDP snsor, 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; // Offset if needed
//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);
// absPres = pressure_event.pressure;
// }
//}
// 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); // For debugging
// Serial.println(ambHumi); // For debugging
ahtValue = aht20.readTemperature(); //read 6-bytes via I2C, takes 80 milliseconds
ambTemp = ahtValue;
//Serial.println(ambTemp); // For debugging
ahtValue = aht20.readHumidity(); //read another 6-bytes via I2C, takes 80 milliseconds
ambHumi = ahtValue;
//Serial.println(ambHumi); // 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
batVolt = batVolt = analogRead(PIN_VBAT) * (0.003515625F) * (1510.0 / 510.0);
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(batPrct); // For debugging
}
}
// flashLED+++++++++++
void flashLED() // flash the LED if battery is charging
{
// If battery is charging go to next step flash LED
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 battery is charging
battChrg = !digitalRead(23);
// battChrg = digitalRead(23); // Inverse of battery charging - For debugging
if (battChrg > 0) {
digitalWrite(LED_RED, 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(LED_RED, 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() // Function to build sentence
{
String msg = F("$PTVSOAR,MNA,FlyingSilicon,MMO,ProbeS4,MSN,2023Q2001"); // FIRST Half of $PTVSOAR Sentence
//printMSG(msg); // Call the printMSG function to transmit to BLE then come back here ...
// // SECOND Half of $PTVSOAR Sentence
//msg = F("$PTV"); // Short PTV sentence structure (if using)
char buf[100]; // enough characters
sprintf(buf,
",PIT,%06.2f"
//",%08.3f"
",OAT,%+03.0f"
",OAH,%03.0f"
",PCT,%03.0f"
",CHG,%d",
sdpPres // Pitot Differential Pressure 999.99
//, absPres // Atmospheric Pressure 9999.999
,
ambTemp // Air Temperature -99 .. +99
,
ambHumi // Air Relative Humidity 000 - 100
,
batPrct // Battery State of Charge % 000 - 100
,
battChrg > 0 ? 1 : 0); // Battery Charging Flag
msg += buf;
msg.concat(" \n"); // End of sentence notifier for TV
printMSG(msg); // Call the printMSG function to transmit to BLE then come back here ...
}
// END OF BUILD MSG ---------------------------------------------------------
// printMSG+++++++++++
void printMSG(String msg) // Function to transmit to BLE
{
uint8_t buf[64]; // create a variable 'buf1' of size 64
msg.getBytes(buf, msg.length() + 1); // move msg1 characters into 'buf', for length of msg
bleuart.write(buf, msg.length() + 0); // write 'buf2' characters to BLE, for length of msg
msg = ""; // Clear the msg buffer for next read and print
}
// END OF BLE UART WRITE MSG FUNCTION ----------------------------------------