In this project, we’ll build an altimeter datalogger with the ESP32 and the BMP388 sensor. The BMP388 is a precise pressure sensor that allows us to estimate altitude with great accuracy. In this project, the pressure and altitude are logged to a file on a microSD. We’ve also added an OLED display to this project so that you can check the current altitude by pressing a pushbutton.
For a getting started guide for the BMP388, check the following tutorial: ESP32 with BMP388 Barometric/Altimeter Sensor (Arduino IDE)
Table of Contents:
- Project Overview
- Parts Required
- Prerequisites
- Schematic Diagram (circuit)
- Code and How it Works
- Demonstration
- Wrapping Up
- Why we created this project
Project Overview
Before going straight to the project, let’s look at the main features of this project.
- The ESP32 sets an access point (1) that you can connect to using your smarpthone. Once connected, you can access a web page with an input field, where you can enter the current sea level pressure at your location. You must enter this value when the project starts running. The altitude is calculated comparaing the sensor’s pressure with the current sea level pressure, that’s why this step is important for accurate results. You can check the current sea level pressure.
- The ESP32 is connected to a BMP388 pressure sensor, a microSD card module, an OLED display and a pushbutton.
- Every minute (or other period of time you define in the code), the ESP32 records new sensor readings to a file on the microSD card (2). It records current seal level pressure, current pressure at the sensor’s location, temperature and altitude estimation.
- When you press the pushbutton, the OLED display turns on and shows the current altitude and temperature (3). After 5 seconds, it turns off (to save power).
To better understand how this project works, it might be helpful to take a look at the following tutorials:
- ESP32 with BMP388 Barometric/Altimeter Sensor (Arduino IDE)
- ESP32: Guide for MicroSD Card Module using Arduino IDE
- ESP32 OLED Display with Arduino IDE
- Input Data on HTML Form ESP32/ESP8266 Web Server using Arduino IDE
- How to Set an ESP32 Access Point (AP) for Web Server
Parts Required
To build this project, you need the following parts:
- DOIT ESP32 DEVKIT V1 Board – read Best ESP32 Development Boards
- BMP388 sensor module (Guide for BMP388)
- MicroSD card module + microSD card
- Pushbutton
- 10k Ohm resistor
- Breadboard
- Jumper wires
Prerequisites
Before continuing, make sure you check the following prerequisites.
1. Install ESP32 Board in Arduino IDE
We’ll program the ESP32 using Arduino IDE. So, you must have the ESP32 add-on installed. Follow the next tutorial if you haven’t already:
If you want to use VS Code with the PlatformIO extension, follow the next tutorial instead to learn how to program the ESP32:
2. Installing Libraries
To build this project, you need to install the following libraries:
- Adafruit BMP3XX library (Arduino Library Manager)
- Adafruit_Sensor library (Arduino Library Manager)
- Adafruit_SSD1306 library (Arduino Library Manager)
- Adafruit_GFX library (Arduino Library Manager)
- ESPAsyncWebServer (.zip folder);
- AsyncTCP (.zip folder).
You can install the first four libraries using the Arduino Library Manager. Go to Sketch > Include Library > Manage Libraries and search for the library name.
The ESPAsyncWebServer and AsynTCP libraries aren’t available to install through the Arduino Library Manager, so you need to copy the library files to the Arduino Installation Libraries folder. Alternatively, in your Arduino IDE, you can go to Sketch > Include Library > Add .zip Library and select the libraries you’ve just downloaded.
Installing Libraries (VS Code + PlatformIO)
If you’re programming the ESP32 using PlatformIO, you should add the following lines to the platformio.ini file to include the libraries (also change the Serial Monitor speed to 115200):
monitor_speed = 115200
lib_deps = ESP Async WebServer
adafruit/Adafruit SSD1306@^2.4.6
adafruit/Adafruit GFX Library@^1.10.10
adafruit/Adafruit BMP3XX Library@^2.1.0
3. Formatting the MicroSD Card
Before proceeding with the tutorial, make sure you format your microSD card as FAT32. Follow the next instructions to format your microSD card or use a software tool like SD Card Formater (compatible with Windows and Mac OS).
1. Insert the microSD card into your computer. Go to My Computer and right-click on the SD card. Select Format as shown in the figure below.
2. A new window pops up. Select FAT32, press Start to initialize the formatting process, and follow the onscreen instructions.
Schematic Diagram
Connect all the components as shown in the following schematic diagram.
You can also check the wiring in the following table:
Component | ESP32 Pin Assignment |
BMP388 | GPIO 21 (SDA), GPIO 22 (SCL) |
OLED Display | GPIO 21 (SDA), GPIO 22 (SCL) |
MicroSD card Module | GPIO 5 (CS), GPIO 23 (MOSI), GPIO 18 (CLK), GPIO 19 (MISO) |
Pushbutton | GPIO 4 |
Code
Copy the following code to your ESP32 board, and the project will work straight away.
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/altimeter-datalogger-esp32-bmp388/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BMP3XX.h"
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
//Libraries for microSD card
#include "FS.h"
#include "SD.h"
#include "SPI.h"
AsyncWebServer server(80);
// Replace with your network credentials
const char* ssid = "ESP32";
const char* password = NULL;
//OLED Display
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Variables for BMP388
float seaLevelPressure = 1013.25;
Adafruit_BMP3XX bmp;
float alt;
float temp;
float pres;
String dataMessage;
//Pushbutton
const int buttonPin = 4;
int buttonState;
int lastButtonState = LOW;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
//Timers for datalogging
unsigned long lastTimer = 0;
unsigned long timerDelay = 18000;
const char* PARAM_INPUT_1 = "seaLevelPressure";
// HTML web page to handle 1 input field
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head>
<title>Sea Level Pressure</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head><body>
<form action="/get">
Sea Level Pressure: <input type="float" name="seaLevelPressure">
<input type="submit" value="Submit">
</form>
</body></html>)rawliteral";
void initBMP(){
if (!bmp.begin_I2C()) { // hardware I2C mode, can pass in address & alt Wire
//if (! bmp.begin_SPI(BMP_CS)) { // hardware SPI mode
//if (! bmp.begin_SPI(BMP_CS, BMP_SCK, BMP_MISO, BMP_MOSI)) { // software SPI mode
Serial.println("Could not find a valid BMP3 sensor, check wiring!");
while (1);
}
// Set up oversampling and filter initialization
bmp.setTemperatureOversampling(BMP3_OVERSAMPLING_8X);
bmp.setPressureOversampling(BMP3_OVERSAMPLING_4X);
bmp.setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_3);
bmp.setOutputDataRate(BMP3_ODR_50_HZ);
}
void getReadings(){
if (! bmp.performReading()) {
Serial.println("Failed to perform reading :(");
return;
}
temp = bmp.temperature;
pres = bmp.pressure / 100.0;
alt = bmp.readAltitude(seaLevelPressure);
}
void initDisplay(){
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
delay(500);
display.clearDisplay();
display.setTextColor(WHITE);
}
void displayReadings(){
display.clearDisplay();
// display temperature
display.setTextSize(1);
display.setCursor(0,0);
display.print("Temperature: ");
display.setTextSize(2);
display.setCursor(0,10);
display.print(String(temp));
display.print(" ");
display.setTextSize(1);
display.cp437(true);
display.write(167);
display.setTextSize(2);
display.print("C");
// display altitude
display.setTextSize(1);
display.setCursor(0, 35);
display.print("Altitude: ");
display.setTextSize(2);
display.setCursor(0, 45);
display.print(String(alt));
display.print(" m");
display.display();
}
// Initialize SD card
void initSDCard(){
if (!SD.begin()) {
Serial.println("Card Mount Failed");
return;
}
}
// Write to the SD card
void writeFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file) {
Serial.println("Failed to open file for writing");
return;
}
if(file.print(message)) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
// Append data to the SD card
void appendFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if(!file) {
Serial.println("Failed to open file for appending");
return;
}
if(file.print(message)) {
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
file.close();
}
// Initialize WiFi
void initWiFi() {
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
}
void setup() {
Serial.begin(115200);
initBMP();
initDisplay();
initSDCard();
initWiFi();
pinMode(buttonPin, INPUT);
File file = SD.open("/data.txt");
if(!file) {
Serial.println("File doesn't exist");
Serial.println("Creating file...");
writeFile(SD, "/data.txt", "Pressure, Altitude, Temperature \r\n");
}
else {
Serial.println("File already exists");
}
file.close();
// Send web page with input fields to client
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html);
});
// Send a GET request to <ESP_IP>/get?input1=<inputMessage>
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage;
// GET input1 value on <ESP_IP>/get?input1=<inputMessage>
if (request->hasParam(PARAM_INPUT_1)) {
inputMessage = request->getParam(PARAM_INPUT_1)->value();
seaLevelPressure = inputMessage.toFloat();
}
else {
inputMessage = "No message sent";
}
Serial.println(inputMessage);
request->send(200, "text/html", "HTTP GET request sent to your ESP on input field with value: " + inputMessage +
"<br><a href=\"/\">Return to Home Page</a>");
});
server.begin();
}
void loop() {
int reading = digitalRead(buttonPin);
display.clearDisplay();
// Light up when the pushbutton is pressed
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == HIGH) {
getReadings();
displayReadings();
delay(5000);
display.clearDisplay();
display.display();
lastDebounceTime = millis();
}
}
}
lastButtonState = reading;
// Log data every timerDelay seconds
if ((millis() - lastTimer) > timerDelay) {
//Concatenate all info separated by commas
getReadings();
dataMessage = String(pres) + "," + String(alt) + "," + String(temp)+ "," + String(seaLevelPressure) + "\r\n";
Serial.print(dataMessage);
//Append the data to file
appendFile(SD, "/data.txt", dataMessage.c_str());
lastTimer = millis();
}
}
How the Code Works
Continue reading to learn how the code works, or skip to the demonstration section.
Start by including all the necessary libraries:
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BMP3XX.h"
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
//Libraries for microSD card
#include "FS.h"
#include "SD.h"
#include "SPI.h"
The following lines set the name and password for the access point. In this case, we set the password to NULL—this creates an open access point. You can add a password for the access point if you want.
// Replace with your network credentials
const char* ssid = "ESP32";
const char* password = NULL;
Set the OLED display size and instantiate an instance on the default I2C pins.
// OLED Display
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
We set the default sea level pressure as 1013.25 hPa. However, you should connect to the access point to change this value for more accurate results.
float seaLevelPressure = 1013.25;
Create an instance for the BMP388 sensor called bmp—this automatically uses the default I2C pins.
Adafruit_BMP3XX bmp;
The following variables will be used to save the sensor data.
float alt;
float temp;
float pres;
String dataMessage;
The pushbutton is connected to GPIO 4.
const int buttonPin = 4;
The buttonState and lastButtonState variables save the current button state and the last button state.
int buttonState;
int lastButtonState = LOW;
The next variables are used to debounce the pushbutton.
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers
The BMP388 readings are saved every minute. You can change that in the timerDelay variable (in milliseconds).
//Timers for datalogging
unsigned long lastTimer = 0;
unsigned long timerDelay = 60000;
The PARAM_INPUT_1 variable will be used to search for the input value on the HTML form. To learn more about HTML forms with the ESP32, we recommend this tutorial.
const char* PARAM_INPUT_1 = "seaLevelPressure";
The index_html variable saves a simple HTML page that displays an input field to enter the current sea level pressure.
// HTML web page to handle 1 input field
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head>
<title>Sea Level Pressure</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head><body>
<form action="/get">
Sea Level Pressure: <input type="float" name="seaLevelPressure">
<input type="submit" value="Submit">
</form>
</body></html>)rawliteral";
When you submit a new pressure value, the ESP32 receives a request on the following URL (for example, pressure = 1022):
/get?seaLevelPressure=1022
Initialize BMP388
The initBMP() function initializes the BMP388 sensor. Read the ESP32 with the BMP388 tutorial to learn more.
void initBMP(){
if (!bmp.begin_I2C()) { // hardware I2C mode, can pass in address & alt Wire
//if (! bmp.begin_SPI(BMP_CS)) { // hardware SPI mode
//if (! bmp.begin_SPI(BMP_CS, BMP_SCK, BMP_MISO, BMP_MOSI)) { // software SPI mode
Serial.println("Could not find a valid BMP3 sensor, check wiring!");
while (1);
}
// Set up oversampling and filter initialization
bmp.setTemperatureOversampling(BMP3_OVERSAMPLING_8X);
bmp.setPressureOversampling(BMP3_OVERSAMPLING_4X);
bmp.setIIRFilterCoeff(BMP3_IIR_FILTER_COEFF_3);
bmp.setOutputDataRate(BMP3_ODR_50_HZ);
}
Get BMP388 Readings
The getReadings() function gets new readings: temperature, pressure, and altitude and saves them on the temp, pres, and alt variables.
void getReadings(){
if (! bmp.performReading()) {
Serial.println("Failed to perform reading :(");
return;
}
temp = bmp.temperature;
pres = bmp.pressure / 100.0;
alt = bmp.readAltitude(seaLevelPressure);
}
Initialize OLED Display
The initDisplay() function initializes the OLED display.
void initDisplay(){
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
delay(500);
display.clearDisplay();
display.setTextColor(WHITE);
}
To learn more about the OLED display with the ESP32, read ESP32 OLED Display with Arduino IDE.
Display BMP388 Readings
The displayReadings() function displays the temperature and altitude on the display.
void displayReadings(){
display.clearDisplay();
// display temperature
display.setTextSize(1);
display.setCursor(0,0);
display.print("Temperature: ");
display.setTextSize(2);
display.setCursor(0,10);
display.print(String(temp));
display.print(" ");
display.setTextSize(1);
display.cp437(true);
display.write(167);
display.setTextSize(2);
display.print("C");
// display altitude
display.setTextSize(1);
display.setCursor(0, 35);
display.print("Altitude: ");
display.setTextSize(2);
display.setCursor(0, 45);
display.print(String(alt));
display.print(" m");
display.display();
}
Initialize microSD card
The initSDCard() function initializes the microSD card on the default SPI pins.
// Initialize SD card
void initSDCard(){
if (!SD.begin()) {
Serial.println("Card Mount Failed");
return;
}
}
If you want to use other pins, read this article to learn how to set custom SPI pins.
Write to the microSD card
The writeFile() and appendFile() functions write and append a message to a file on the microSD card.
// Write to the SD card
void writeFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if(!file) {
Serial.println("Failed to open file for writing");
return;
}
if(file.print(message)) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
// Append data to the SD card
void appendFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Appending to file: %s\n", path);
File file = fs.open(path, FILE_APPEND);
if(!file) {
Serial.println("Failed to open file for appending");
return;
}
if(file.print(message)) {
Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
file.close();
}
To learn more about microSD card functions, read ESP32: Guide for MicroSD Card Module using Arduino IDE.
Set Access Point
Initialize Wi-Fi by setting the ESP32 as an access point.
// Initialize WiFi
void initWiFi() {
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
}
setup()
In the setup(), initialize the Serial Monitor, the BMP388 sensor, the OLED display, the microSD card, start the access point and define the pushbutton as an INPUT.
Serial.begin(115200);
initBMP();
initDisplay();
initSDCard();
initWiFi();
pinMode(buttonPin, INPUT);
Create a new file on the microSD card called data.txt if it doesn’t exist already.
File file = SD.open("/data.txt");
if(!file) {
Serial.println("File doesn't exist");
Serial.println("Creating file...");
writeFile(SD, "/data.txt", "Pressure, Altitude, Temperature \r\n");
}
else {
Serial.println("File already exists");
}
file.close();
When you access the Access Point on the root (/) URL, the server (ESP32) sends the HTML page (index_html variable) with the form.
// Send web page with input fields to client
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html);
});
The following part gets the input field you’ve inserted in the form and saves it in the seaLevelPressure variable.
// Send web page with input fields to client
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html);
});
// Send a GET request to <ESP_IP>/get?input1=<inputMessage>
server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage;
// GET input1 value on <ESP_IP>/get?input1=<inputMessage>
if (request->hasParam(PARAM_INPUT_1)) {
inputMessage = request->getParam(PARAM_INPUT_1)->value();
seaLevelPressure = inputMessage.toFloat();
}
else {
inputMessage = "No message sent";
}
Serial.println(inputMessage);
request->send(200, "text/html", "HTTP GET request sent to your ESP on input field with value: " + inputMessage +
"<br><a href=\"/\">Return to Home Page</a>");
});
To learn more about how this works, read: Input Data on HTML Form ESP32/ESP8266 Web Server using Arduino IDE.
loop()
In the loop() is where we check the state of the pushbutton. If it was pressed, we light up the OLED display with the current temperature and altitude readings.
int reading = digitalRead(buttonPin);
display.clearDisplay();
// Light up when the pushbutton is pressed
if (reading != lastButtonState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
if (reading != buttonState) {
buttonState = reading;
if (buttonState == HIGH) {
getReadings();
displayReadings();
delay(5000);
display.clearDisplay();
display.display();
lastDebounceTime = millis();
}
}
}
lastButtonState = reading;
We also save new readings every 60 seconds (timerDelay) variable.
if ((millis() - lastTimer) > timerDelay) {
//Concatenate all info separated by commas
getReadings();
dataMessage = String(pres) + "," + String(alt) + "," + String(temp)+ "," + String(seaLevelPressure) + "\r\n";
Serial.print(dataMessage);
//Append the data to file
appendFile(SD, "/data.txt", dataMessage.c_str());
lastTimer = millis();
}
Demonstration
Upload the code to your board. Don’t forget to select the right board (ESP32) and COM port.
After uploading, you should get the following messages on the Serial Monitor*.
*In my case, it shows “File already exists”. But, when you’re running it for the first time, it should show “File doesn’t exist”, “Creating file …”.
After a few seconds, it will display the first readings.
After that, if you want to get more accurate altitude readings, on your computer or smartphone, connect to the ESP32 access point. Open a browser and go to the 192.168.4.1 IP address and insert the current sea level pressure at your location.
After you’ve clicked the submit button, you’ll see the inserted value on the Serial Monitor.
If you click on the pushbutton, you can check the current temperature and altitude on the OLED display.
If you want to check all the readings, you just need to connect the microSD card to your computer, and you can access the data.txt file with all the records. To analyze your data, you can use Google Sheets, Excel, or other software.
Wrapping Up
This project showed you how to log data to a microSD card using the ESP32 and how to get altitude using the BMP388 pressure sensor. You also learned how to create an access point where you can enter data that the ESP32 will use—like the sea level pressure.
We have a similar project using a temperature sensor (it connects to an NTP server to timestamp the readings, so the ESP32 needs to have access to the internet):
Learn more about the ESP32 with our resources:
- Learn ESP32 with Arduino IDE (eBook + video course)
- Build Web Servers with ESP32 and ESP8266 eBook (2nd Edition)
- More ESP32 Projects and Tutorials …
Why we created this project?
Some curiosities about this project for those of you that like to know a little more about us. A few weeks ago, we visited Pico Island, a Portuguese island in the Azores archipelago. The landscape features a volcano, Ponta do Pico, the highest mountain in Portugal, and the Mid-Atlantic Ridge’s highest elevation with 2351 meters.
One of the highlights of visiting Pico Island is climbing/hiking the mountain to the highest point. We decided that this trip would be a good opportunity to test the BMP388 sensor at varying altitudes—so, we created this datalogging project.
At the top, the sensor was marking approximately 2260 meters, which is approximately 90 meters from the real value (2351 meters). I don’t think that a difference of 90 meters is relevant at such an altitude. What do you think? The temperature was marking 13ºC (55ºF), which was the same temperature predicted in the forecast.
We tried to take pictures of the OLED display showing the results using our smartphone. Unfortunately, due to the frame rate of the OLED display, the numbers are not visible.
Have you visited Pico Island or the Azores? Let us know in the comments below.
Thanks for reading.
Thanks for posting. I hope you guys had a great time . What did you use as a power source?
Hi.
We had a great time!
We used a portable charger (power bank).
Regards,
Sara
Sara,
I used a Ravpower 20000 mAh cell phone power bank with a USB outlet.
Thanks for all you do,
Jim
I just finished an ESP32_BMP388_IL9341 project with adjustment for local barometric pressure. I took it up in an airplane to 8500 feet and it was spot on with the plane’s altimeter. Having an SD card logger will make it really useful.
Hi Jim.
That’s great! Thanks for sharing your experience with the sensor.
This seems one of the best sensors to estimate altitude.
Regards,
Sara
A nice project, as usually. I have suggestion for improvement to be more usable, 90 hight-meters difference is not a good precission. Instead of entering a seal level pressure, ask user to enter current elevation and calculate corresponding seal level pressure by iteration. And more ideas are comming: What about to ask the mobile phone browser for its current time by javascript (instead of NTP call), set the system time and use this real time as for calculation of timestamp of each evelation point in the log in SD card…
That’s great!
Thanks for those suggestions.
Regards,
Sara
Nice projects..love you two.. love what you doing.. more power and more blessings to both of you guys!..