/*********************************************************************
21 May 23 - TO DO
DONE | TESTED | Add Adafruit BLE
DONE | NOT-TESTED | Add DPS310 sensor (Atmospheric Pressure)
DONE | NOT-TESTED | Add SDP31 sensor (Diff Pressure)
DONE | NOT-TESTED | Add SHT40 sensor (Temp/Hum)
DONE | TESTED | Add 100mA charging
DONE | TESTED | Add Battery Voltage
DONE | TESTED | Add Charging sense
DONE | NOT-TESTED | Add sensor values to MSG function
NOT-TESTED | Flight Testing
May 2023 - BLE print functionality is now broken out to a separate Function.
Ie: Build Sentence part-1 -> BLE Print ->
back to Build Sentence part-2 -> BLE Print (again) -> Loop
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 <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;
// 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
// Infineon Atmospheric Pressure Variable
float absPres = 966.0; // Variable for the Atmospheric absolute pressure
// Ambient Environment Variables
float ambTemp = 0;
float ambHumi = 0;
// // Battery Voltage Variables
float batVolt = 4.00; // Battery Volts
float batPrct = 50.0; // Battery SOC %
//===================SETUP========================
void setup() {
//Serial.begin(115200);
// 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 Sensirion SHT
sht4x.begin(Wire);
// Initialise Infineon 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 ");
// Serial.println(central_name);
}
void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
(void)conn_handle;
(void)reason;
// Serial.print("Disconnected, reason = 0x");
// Serial.println(reason, HEX);
}
//================ 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;
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);
}
}
// 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;
}
}
}
// 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
bool IsChargingBattery = !digitalRead(23);
if (IsChargingBattery > 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
{
char strSDP[8];
float sdp = sdpPres; // Differential Pressure from SDP sensor
dtostrf(sdp, 6, 2, strSDP); // format float value to string
char strDPS[8];
float dps = absPres; // Atmospheric Pressure from DPS sensor
dtostrf(dps, 6, 2, strDPS); // format float value to string
char strSHT[8];
float sht = ambTemp; // Temparature from SHT sensor
dtostrf(sht, 6, 2, strSHT); // format float value to string
char strSHH[8];
float shh = ambHumi; // Humidity from SHT sensor
dtostrf(shh, 6, 2, strSHH); // format float value to string
char strBPC[8];
float bpc = batPrct; // Humidity from SHT sensor
dtostrf(bpc, 6, 2, strBPC); // format float value to string
String msg = "$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("PIT");
msg += F(",");
msg += (strSDP); // Pitot Differential Pressure
msg += F(",");
msg += F("PRS");
msg += F(",");
msg += (strDPS); // Atmospheric Pressure
msg += F(",");
msg += F("OAT");
msg += F(",");
msg += (strSHT); // Air Temperature
msg += F(",");
msg += F("OAH");
msg += F(",");
msg += (strSHH); // Air Relative Humidity
msg += F(",");
msg += F("PCT");
msg += F(",");
msg += (strBPC); // Battery State of Charge %
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 ----------------------------------------