#include #include #include #include #include #include #include #include #include #include #include // Define I2C pins for ESP32 #define SDA_PIN 21 #define SCL_PIN 22 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C #define HDC1000_ADDRESS 0x40 // I2C address for HDC1000 sensor #define SD_CS_PIN 5 // SD card CS pin on GPIO 5 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); Adafruit_LPS22 lps22; Adafruit_LSM6DS3TRC lsm6ds3trc; const int button1Pin = 32; // SW1 const int button2Pin = 33; // SW2 const int irPin = 36; const int potPin = 34; const int ldrPin = 35; const int redLEDPin = 15; // Swapped with blue LED const int blueLEDPin = 2; // Swapped with red LED const int rgbLEDPin = 4; const int buzzerPin = 27; Adafruit_NeoPixel rgbLED(1, rgbLEDPin, NEO_GRB + NEO_KHZ800); int currentScreen = 0; unsigned long pulseLow, pulseHigh; unsigned long irHexValue; int potValue, ldrValue; bool buzzerActive = false; float temp = 0; float humidity = 0; float pressure = 0; float altitude = 0; // Adjustable logging interval (1 second default) unsigned long loggingInterval = 1000; unsigned long previousMillis = 0; // SD Card status variable bool sdCardAvailable = false; String sdCardErrorMessage = ""; // Known sea level pressure for Centurion, South Africa in hPa const float knownSeaLevelPressure = 1026.0; // IMU sensor data sensors_event_t accelEvent; sensors_event_t gyroEvent; sensors_event_t tempEvent; // Added to hold temperature data from IMU // Mutexes for shared resources SemaphoreHandle_t xSensorDataMutex; SemaphoreHandle_t xDataMutex; // Button debouncing variables int button1State = HIGH; // the current stable state of button1 int lastButton1Reading = HIGH; // the previous reading from the button1 pin unsigned long button1LastDebounceTime = 0; // the last time the button1 input pin was toggled int button2State = HIGH; // the current stable state of button2 int lastButton2Reading = HIGH; // the previous reading from the button2 pin unsigned long button2LastDebounceTime = 0; // the last time the button2 input pin was toggled const unsigned long debounceDelay = 50; // the debounce time in milliseconds void setup() { Serial.begin(115200); while (!Serial) delay(10); // Wait for serial port to be available pinMode(button1Pin, INPUT_PULLUP); pinMode(button2Pin, INPUT_PULLUP); pinMode(irPin, INPUT); pinMode(potPin, INPUT); pinMode(ldrPin, INPUT); pinMode(redLEDPin, OUTPUT); pinMode(blueLEDPin, OUTPUT); pinMode(buzzerPin, OUTPUT); // Initialize I2C communication Wire.begin(SDA_PIN, SCL_PIN); // Initialize display Serial.println("Initializing display..."); if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1306 allocation failed")); for (;;); } display.clearDisplay(); display.display(); // Show splash screen displaySplashScreen(); // Wait for user to press SW1 for IMU calibration waitForIMUCalibration(); // Initialize the LSM6DS3TR-C IMU Serial.println("Initializing LSM6DS3TR-C IMU..."); if (!lsm6ds3trc.begin_I2C()) { Serial.println("Failed to find LSM6DS3TR-C chip"); while (1) { vTaskDelay(pdMS_TO_TICKS(10)); } } Serial.println("LSM6DS3TR-C Found!"); // Initialize the LPS22HB pressure sensor Serial.println("Initializing LPS22HB sensor..."); if (!lps22.begin_I2C(0x5C)) { // Explicitly use address 0x5C Serial.println("Failed to find LPS22HB sensor at 0x5C"); while (1) { vTaskDelay(pdMS_TO_TICKS(10)); } } Serial.println("LPS22HB sensor initialized."); // Initialize RGB LED rgbLED.begin(); rgbLED.show(); // Print free heap memory Serial.print("Free heap memory before mutex creation: "); Serial.println(ESP.getFreeHeap()); // Create mutex for sensor data xSensorDataMutex = xSemaphoreCreateMutex(); if (xSensorDataMutex == NULL) { Serial.println("Failed to create sensor data mutex"); while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); } } // Create mutex for shared data xDataMutex = xSemaphoreCreateMutex(); if (xDataMutex == NULL) { Serial.println("Failed to create data mutex"); while (1) { vTaskDelay(pdMS_TO_TICKS(1000)); } } Serial.print("Free heap memory after mutex creation: "); Serial.println(ESP.getFreeHeap()); // Initialize SD card (Optional: Comment out if not using SD card) /* Serial.println("Initializing SD card..."); if (!SD.begin(SD_CS_PIN)) { Serial.println("SD card initialization failed!"); sdCardAvailable = false; sdCardErrorMessage = "SD Init Failed"; } else { Serial.println("SD card initialized."); sdCardAvailable = true; sdCardErrorMessage = ""; } */ // Create FreeRTOS tasks with sufficient stack sizes xTaskCreate(sensorTask, "Sensor Task", 4096, NULL, 1, NULL); xTaskCreate(displayTask, "Display Task", 4096, NULL, 1, NULL); // Only create logTask if SD card is available (Optional) /* if (sdCardAvailable) { xTaskCreate(logTask, "Log Task", 4096, NULL, 1, NULL); } */ } void loop() { // Main loop is not used in this FreeRTOS implementation } void displaySplashScreen() { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(10, 0); display.setTextWrap(false); display.println("Welcome!"); display.println(""); display.println("Place the board on a"); display.println("stable, stationary,"); display.println("and level surface."); display.println(""); display.println("Press SW1 to calibrate"); display.println("the IMU."); display.display(); } void waitForIMUCalibration() { // Wait for SW1 to be pressed while (digitalRead(button1Pin) == HIGH) { vTaskDelay(pdMS_TO_TICKS(100)); // Small debounce delay } vTaskDelay(pdMS_TO_TICKS(200)); // Additional delay to ensure button release // Start IMU calibration once the button is pressed display.clearDisplay(); display.setCursor(0, 0); display.setTextWrap(false); display.println("Calibrating IMU..."); display.display(); calibrateIMU(); } void calibrateIMU() { // Calibration code can be added here if necessary vTaskDelay(pdMS_TO_TICKS(2000)); // Simulate calibration delay display.clearDisplay(); display.setCursor(0, 0); display.setTextWrap(false); display.println("Calibration complete!"); display.display(); vTaskDelay(pdMS_TO_TICKS(1000)); } void sensorTask(void *pvParameters) { (void)pvParameters; for (;;) { unsigned long currentMillis = millis(); // Read the current state of button1 int reading1 = digitalRead(button1Pin); // If the switch changed, due to noise or pressing: if (reading1 != lastButton1Reading) { // reset the debouncing timer button1LastDebounceTime = currentMillis; } if ((currentMillis - button1LastDebounceTime) > debounceDelay) { // whatever the reading is at, it's been there for longer than the debounce delay // so take it as the actual current state // if the button state has changed: if (reading1 != button1State) { button1State = reading1; // only increment screen if the new button state is LOW (button pressed) if (button1State == LOW) { currentScreen++; if (currentScreen > 11) currentScreen = 0; } } } lastButton1Reading = reading1; // Similar code for button2 int reading2 = digitalRead(button2Pin); if (reading2 != lastButton2Reading) { button2LastDebounceTime = currentMillis; } if ((currentMillis - button2LastDebounceTime) > debounceDelay) { if (reading2 != button2State) { button2State = reading2; if (button2State == LOW) { currentScreen--; if (currentScreen < 0) currentScreen = 11; } } } lastButton2Reading = reading2; // Read IMU data if (xSemaphoreTake(xSensorDataMutex, pdMS_TO_TICKS(100)) == pdTRUE) { if (!lsm6ds3trc.getEvent(&accelEvent, &gyroEvent, &tempEvent)) { Serial.println("Failed to read IMU data"); } xSemaphoreGive(xSensorDataMutex); } else { Serial.println("Failed to take sensor data mutex in sensorTask"); } // Read other sensors based on the current screen switch (currentScreen) { case 0: readIRSensor(); break; case 3: readTempHumidity(); break; case 4: readPressureSensor(); break; case 5: potValue = averageAnalogRead(potPin, 20); break; case 6: ldrValue = analogRead(ldrPin); break; case 9: // Read potentiometer value for RGB LED control potValue = averageAnalogRead(potPin, 5); break; // Cases 7-11 are for LED, buzzer, and SD card status } vTaskDelay(pdMS_TO_TICKS(10)); // Adjust the delay as needed } } void readIRSensor() { if (digitalRead(irPin) == LOW) { pulseLow = pulseIn(irPin, LOW, 100000); // Timeout of 100 ms pulseHigh = pulseIn(irPin, HIGH, 100000); // Timeout of 100 ms irHexValue = (pulseHigh << 16) | pulseLow; } } void readTempHumidity() { if (xSemaphoreTake(xDataMutex, pdMS_TO_TICKS(100)) == pdTRUE) { temp = readTemperature(); humidity = readHumidity(); xSemaphoreGive(xDataMutex); } else { Serial.println("Failed to take data mutex in readTempHumidity"); } } void readPressureSensor() { if (xSemaphoreTake(xDataMutex, pdMS_TO_TICKS(100)) == pdTRUE) { sensors_event_t pressure_event; lps22.getPressureSensor()->getEvent(&pressure_event); pressure = pressure_event.pressure; altitude = calculateAltitude(pressure, knownSeaLevelPressure); xSemaphoreGive(xDataMutex); } else { Serial.println("Failed to take data mutex in readPressureSensor"); } } void displayTask(void *pvParameters) { (void)pvParameters; for (;;) { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); display.setTextWrap(false); // Turn off RGB LED before switching screens unless on screen 9 if (currentScreen != 9) { rgbLED.setPixelColor(0, rgbLED.Color(0, 0, 0)); rgbLED.show(); } // Turn off LEDs before switching screens digitalWrite(redLEDPin, LOW); digitalWrite(blueLEDPin, LOW); // Turn off buzzer before switching screens controlBuzzer(false); switch (currentScreen) { case 0: displayIRData(); break; case 1: displayAccelData(); break; case 2: displayGyroData(); break; case 3: displayTempHumidity(); break; case 4: displayPressureData(); break; case 5: displayPotData(); break; case 6: displayLDRData(); break; case 7: display.println("Red LED ON"); digitalWrite(redLEDPin, HIGH); break; case 8: display.println("Blue LED ON"); digitalWrite(blueLEDPin, HIGH); break; case 9: displayRGBControl(); break; case 10: display.println("Buzzer ON"); controlBuzzer(true); break; case 11: displaySDCardStatus(); break; } display.display(); vTaskDelay(pdMS_TO_TICKS(100)); // Delay for task } } void displayIRData() { display.setCursor(0, 0); display.println("IR Receiver Data"); display.println(""); display.print("Pulse Low: "); display.println(pulseLow); display.print("Pulse High: "); display.println(pulseHigh); display.print("Hex Value: 0x"); display.println(irHexValue, HEX); } void displayAccelData() { if (xSemaphoreTake(xSensorDataMutex, pdMS_TO_TICKS(100)) == pdTRUE) { display.setCursor(0, 0); display.println("Accelerometer Data"); display.println(""); display.print("X: "); display.print(accelEvent.acceleration.x / 9.81, 2); // Convert m/s² to g display.print(" g"); display.println(""); display.print("Y: "); display.print(accelEvent.acceleration.y / 9.81, 2); // Convert m/s² to g display.print(" g"); display.println(""); display.print("Z: "); display.print(accelEvent.acceleration.z / 9.81, 2); // Convert m/s² to g display.print(" g"); xSemaphoreGive(xSensorDataMutex); } else { display.println("Accelerometer Data"); display.println("Unavailable"); } } void displayGyroData() { if (xSemaphoreTake(xSensorDataMutex, pdMS_TO_TICKS(100)) == pdTRUE) { display.setCursor(0, 0); display.println("Gyroscope Data"); display.println(""); display.print("X: "); display.print(gyroEvent.gyro.x, 2); display.print(" rad/s"); display.println(""); display.print("Y: "); display.print(gyroEvent.gyro.y, 2); display.print(" rad/s"); display.println(""); display.print("Z: "); display.print(gyroEvent.gyro.z, 2); display.print(" rad/s"); xSemaphoreGive(xSensorDataMutex); } else { display.println("Gyroscope Data"); display.println("Unavailable"); } } void displayTempHumidity() { if (xSemaphoreTake(xDataMutex, pdMS_TO_TICKS(100)) == pdTRUE) { display.setCursor(0, 0); display.println("Temperature & Humidity"); display.println(""); display.print("Temp: "); display.print(temp, 1); display.println(" C"); display.print("Humidity: "); display.print(humidity, 1); display.println(" %"); xSemaphoreGive(xDataMutex); } else { display.println("Temperature & Humidity"); display.println("Unavailable"); } } void displayPressureData() { if (xSemaphoreTake(xDataMutex, pdMS_TO_TICKS(100)) == pdTRUE) { display.setCursor(0, 0); display.println("Pressure & Altitude"); display.println(""); display.print("Pressure: "); display.print(pressure, 1); display.println(" hPa"); display.print("Altitude: "); display.print(altitude, 1); display.println(" m"); xSemaphoreGive(xDataMutex); } else { display.println("Pressure & Altitude"); display.println("Unavailable"); } } void displayPotData() { display.setCursor(0, 0); display.println("Potentiometer"); display.println(""); display.print("Value: "); display.println(potValue); } void displayLDRData() { display.setCursor(0, 0); display.println("LDR Data"); display.println(""); display.print("Value: "); display.println(ldrValue); } void displayRGBControl() { display.setCursor(0, 0); display.println("RGB LED Control"); display.println(""); display.println("Turn pot to change"); display.println("RGB LED color"); // Map potentiometer value to RGB color uint16_t potValue = averageAnalogRead(potPin, 5); uint16_t hue = map(potValue, 0, 4095, 0, 65535); uint32_t color = rgbLED.gamma32(rgbLED.ColorHSV(hue)); // Update RGB LED color rgbLED.setPixelColor(0, color); rgbLED.show(); } void displaySDCardStatus() { display.setCursor(0, 0); display.println("SD Card Status:"); display.println(""); if (sdCardAvailable) { display.println("SD Card OK"); } else { display.println("SD Card Error:"); display.println(sdCardErrorMessage); } } void controlBuzzer(bool state) { if (state && !buzzerActive) { tone(buzzerPin, 1000); // Play a 1000 Hz tone buzzerActive = true; } else if (!state && buzzerActive) { noTone(buzzerPin); // Stop the tone buzzerActive = false; } } float readTemperature() { Wire.beginTransmission(HDC1000_ADDRESS); Wire.write(0x00); // Temperature register Wire.endTransmission(); vTaskDelay(pdMS_TO_TICKS(15)); // Wait for conversion Wire.requestFrom(HDC1000_ADDRESS, 2); if (Wire.available() == 2) { uint16_t rawTemp = (Wire.read() << 8) | Wire.read(); return ((rawTemp / 65536.0) * 165.0) - 40.0; } return NAN; // Return NaN if no data is available } float readHumidity() { Wire.beginTransmission(HDC1000_ADDRESS); Wire.write(0x01); // Humidity register Wire.endTransmission(); vTaskDelay(pdMS_TO_TICKS(15)); // Wait for conversion Wire.requestFrom(HDC1000_ADDRESS, 2); if (Wire.available() == 2) { uint16_t rawHum = (Wire.read() << 8) | Wire.read(); return (rawHum / 65536.0) * 100.0; } return NAN; // Return NaN if no data is available } float calculateAltitude(float pressure, float seaLevelPressure) { return 44330.0 * (1.0 - pow(pressure / seaLevelPressure, 0.1903)); } int averageAnalogRead(int pin, int samples) { int sum = 0; for (int i = 0; i < samples; i++) { sum += analogRead(pin); vTaskDelay(pdMS_TO_TICKS(5)); // Small delay between readings } return sum / samples; } // Optional: Implement logTask if using SD card /* void logTask(void *pvParameters) { (void)pvParameters; for (;;) { if (sdCardAvailable) { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= loggingInterval) { previousMillis = currentMillis; // Open the file for appending File dataFile = SD.open("/data.txt", FILE_APPEND); if (dataFile) { // Prepare data to log String dataString = ""; dataString += "Time: "; dataString += currentMillis / 1000; dataString += " s, "; // Add sensor data dataString += "Temp: "; dataString += temp; dataString += " C, Humidity: "; dataString += humidity; dataString += " %, Pressure: "; dataString += pressure; dataString += " hPa, Altitude: "; dataString += altitude; dataString += " m"; // Write data to file if (dataFile.println(dataString)) { dataFile.close(); } else { // Write failed, possibly SD card full Serial.println("Failed to write to SD card"); sdCardAvailable = false; sdCardErrorMessage = "SD Write Failed"; dataFile.close(); } } else { // Failed to open file Serial.println("Failed to open file on SD card"); sdCardAvailable = false; sdCardErrorMessage = "SD Open Failed"; } } } vTaskDelay(pdMS_TO_TICKS(100)); // Small delay } } */