Memory optimization tips

Hello, I am new to Arduino and was writing a Arduino script and got the following when compiling:
Sketch uses 31006 bytes (96%) of program storage space. Maximum is 32256 bytes.
Global variables use 1827 bytes (89%) of dynamic memory, leaving 221 bytes for local variables. Maximum is 2048 bytes.

I saw from a different post that for memory you should keep it below 70%-80% for dynamic memory. I am hoping someone can give me tips on how to optimize my code so it doesn't go over that limit.

Full code:

#include <Wire.h>
#include <QMC5883LCompass.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_TSL2591.h>
#include <math.h>  // For atan2() and degrees()
#include "Arduino.h"
#include "MHZCO2.h"
#include "Adafruit_BME680.h"
#include <SPI.h>
#include <SoftwareSerial.h>
#include <SD.h>
//O2

// Define RX and TX pins for SoftwareSerial
const uint8_t pinRX = 5; // Arduino RX <- Sensor TXD (P3-2)
const uint8_t pinTX = 6; // Arduino TX -> Sensor RXD (P3-3)

SoftwareSerial sensorSerial(pinRX, pinTX); // RX, TX

// Packet structure as per US1010 datasheet
typedef struct {
  uint8_t header;
  uint8_t len;
  uint8_t cmd;
  uint8_t grData[20];
  uint8_t checksum;
} O2Sensor_t;

struct O2data {
  float o2percentage;
  float flowrate;
};

O2Sensor_t snsPacket;

// State machine variables
uint8_t state = 0;
uint8_t idx = 0;
uint8_t numBytes = 0;

//END O2

// SD
const uint8_t chipSelect = 10;

// Light Sensor
Adafruit_TSL2591 tsl = Adafruit_TSL2591(0x29);  // Corrected I2C address for TSL2591

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

#define SEALEVELPRESSURE_HPA (1027.25)

Adafruit_BME680 bme680(&Wire); // I2C

// Magnetometer
QMC5883LCompass compass;

// Use #define for memory efficiency
#define MQ4Pin A0   // Methane 
#define MQ8Pin A1   // Hydrogen
#define MQ7Pin A2   // Carbon Monoxide
#define MQ137Pin A3 // Ammonia
#define UVON 7
#define AON 8
#define Ozone 4

//co2 sensor vars
//  adjust to calibrate.
#define MAX_CONCENTRATION 5000  // Max CO2 concentration in PPM (adjust as needed)
//
//  interrupt variables
volatile uint32_t period;
volatile uint32_t width;

//co2 funcs
void IRQ()
{
  static uint32_t lastPeriod = 0;
  static uint32_t start = 0;

  uint32_t now = micros();
  if (digitalRead(3) == HIGH)
  {
    period = now - lastPeriod;
    lastPeriod = now;  // <-- FIX: update lastPeriod
    start = now;
  }
  else
  {
    width = micros() - start;
  }
}

float CO2_PPM()
{
  noInterrupts();
  uint32_t TimeHigh = width;     //  microseconds
  uint32_t TimePeriod = period;  //  microseconds
  interrupts();
  float concentration = (MAX_CONCENTRATION * (TimeHigh - 2000)) / (TimePeriod - 4000);
  return concentration;
}
// end of co2 funcs

//bme func:
void printValues() {
    if (! bme680.performReading()) {
      Serial.println(F("Failed to perform reading :("));
      return;
    }
    Serial.print(F("Temperature = "));
    Serial.print(bme680.temperature);
    Serial.println(F(" *C"));

    Serial.print(F("Pressure = "));
    Serial.print(bme680.pressure / 100.0);
    Serial.println(" hPa");

    Serial.print(F("Humidity = "));
    Serial.print(bme680.humidity);
    Serial.println(" %");

    Serial.print(F("Gas = "));
    Serial.print(bme680.gas_resistance / 1000.0);
    Serial.println(F(" KOhms"));

    Serial.print(F("IAQ = "));
    Serial.print(calculateIAQ((bme680.gas_resistance/1000), 90));
    Serial.println(F(" %(relative to 50% being good)"));

    Serial.print(F("Approx. Altitude = "));
    Serial.print(bme680.readAltitude(SEALEVELPRESSURE_HPA));
    Serial.println(F(" m"));

    Serial.println();
}
//end of bme functions

void setup() {
  Serial.begin(9600);

  //delay(1000);
  //Serial.begin(9600);
  //while (!Serial) {} // Wait for serial monitor (useful for some boards)
  pinMode(UVON, OUTPUT);
  pinMode(AON, OUTPUT);
  pinMode(Ozone, OUTPUT);
  Serial.println(F("Initializing sensors..."));


  // Initialize magnetometer
  Wire.begin();
  compass.init(); // Initialize the QMC5883L magnetometer
  Serial.println(F("QMC5883L magnetometer initialized."));

  //bme 280

  if (!bme680.begin()) {
    Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
    while (1);
  }
  // Set up oversampling and filter initialization
  bme680.setTemperatureOversampling(BME680_OS_8X);
  bme680.setHumidityOversampling(BME680_OS_2X);
  bme680.setPressureOversampling(BME680_OS_4X);
  bme680.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme680.setGasHeater(320, 150); // 320*C for 150 ms
  Serial.println(F("BME680 initialized..."));
// Initialize light sensor
  if (!tsl.begin()) {
    Serial.println(F("TSL2591 not found! Check wiring or address."));
    while (1);
  }
  configureLightSensor();

// co2

  Serial.println(__FILE__);
  Serial.print(F("MHZCO2_LIB_VERSION: "));
  Serial.println(MHZCO2_LIB_VERSION);
  Serial.println();

  attachInterrupt(digitalPinToInterrupt(3), IRQ, CHANGE);
//end co2
//o2
  sensorSerial.begin(9600);

  // Populate our transmit packet template: [0x11, 0x01, 0x01, checksum]
  snsPacket.header   = 0x11;  // Read command
  snsPacket.len      = 0x01;  // Data length = 1 (only CMD)
  snsPacket.cmd      = 0x01;  // Sub-command
  // Checksum = 0xFF - (sum of header + len + cmd) + 1
  uint8_t sum = snsPacket.header + snsPacket.len + snsPacket.cmd;
  snsPacket.checksum = 0xFF - sum + 1;

  delay(200);  // Allow bootloader to finish
  Serial.println(F("US1010 o2 sensor Setup complete."));
     
//end o2
//SD
  Serial.print(F("Initializing SD card..."));

  if (!SD.begin(chipSelect)) {
    Serial.println(F("initialization failed. Things to check:"));
    Serial.println(F("1. is a card inserted?"));
    Serial.println(F("2. is your wiring correct?"));
    Serial.println(F("3. did you change the chipSelect pin to match your shield or module?"));
    Serial.println(F("Note: press reset button on the board and reopen this Serial Monitor after fixing your issue!"));
    while (true);
  }

  Serial.println(F("SD card initialized."));
//end of SD

  // Preheat gas sensors
  digitalWrite(AON, HIGH);
  digitalWrite(Ozone, HIGH);
  Serial.println(F("Preheating MQ sensors..."));
  //delay(20000);
  Serial.println(F("preheating..."));
  for (int i = 300;i>=0;i--){
    delay(1000);
    if (! bme680.performReading()) {
      Serial.println(F("Failed to perform reading - bme680 :("));
    }
    else 
      Serial.print(F("BME succesfully made reading #"));
    Serial.println((i));
  }
  //delay(20000);

  Serial.println(F("Sensor readings in ppm:"));
}

void loop() {
  // Multiplex Ammonia sensor and UV sensor
  digitalWrite(AON, HIGH);
  delay(30);
  float ammoniaVoltage = analogRead(MQ137Pin) * (5.0 / 1023.0);
  digitalWrite(AON, LOW);
  delay(30);
  digitalWrite(UVON, HIGH);
  delay(30);
  uint16_t UVvoltage = analogRead(MQ137Pin);
  delay(10);
  uint16_t UVvoltage2 = analogRead(MQ137Pin);
  delay(10);
  uint16_t UVvoltage3 = analogRead(MQ137Pin);
  float ave = (UVvoltage + UVvoltage2 + UVvoltage3) / 3.0;

  digitalWrite(UVON, LOW);
  delay(30);
  digitalWrite(Ozone, HIGH);
  float ozoneVoltage = analogRead(MQ137Pin) * (5.0 / 1023.0);
  digitalWrite(Ozone, LOW);

  // Gas Sensors
  float methaneVoltage = analogRead(MQ4Pin) * (5.0 / 1023.0);
  float hydrogenVoltage = analogRead(MQ8Pin) * (5.0 / 1023.0);
  float carbonMonoxideVoltage = analogRead(MQ7Pin) * (5.0 / 1023.0);

  // Convert sensor readings to ppm
  float methanePPM = methaneVoltage * 0.5;
  float hydrogenPPM = hydrogenVoltage * 0.5;
  float carbonMonoxidePPM = carbonMonoxideVoltage * 1.0;
  float ammoniaPPM = ammoniaVoltage * 0.5;
  float ozonePPM = ozoneVoltage * 0.05;

  // Light Sensor
  uint16_t lux = tsl.getLuminosity(TSL2591_VISIBLE);
  uint16_t broadband = tsl.getLuminosity(TSL2591_FULLSPECTRUM);
  uint16_t infrared = tsl.getLuminosity(TSL2591_INFRARED);

  // Magnetometer
  compass.read(); // Read the QMC5883L sensor data

  // Retrieve the X, Y, and Z values
  int16_t x = compass.getX();
  int16_t y = compass.getY();
  int16_t z = compass.getZ();

  // Calculate the heading (angle in degrees)
  float heading = atan2(y, x);
  
  // Convert from radians to degrees
  heading = degrees(heading);
  
  // Normalize the heading to be between 0 and 360
  if (heading < 0) {
    heading += 360;
  }

//o2 sensor
  // Transmit the read-command packet for o2 sensor
  sensorSerial.write(snsPacket.header);
  sensorSerial.write(snsPacket.len);
  sensorSerial.write(snsPacket.cmd);
  sensorSerial.write(snsPacket.checksum);

  // Wait for o2 sensor to reply
  delay(100);
  O2data O2Sensordata;

  // Read & process all available bytes through state machine
  while (sensorSerial.available()) {
    uint8_t ch = sensorSerial.read();

    switch (state) {
      case 0:
        if (ch == 0x16) {        // Response header
          snsPacket.header = ch;
          state = 1;
        }
        break;

      case 1:
        snsPacket.len = ch;      // Length byte
        state = 2;
        break;

      case 2:
        snsPacket.cmd = ch;      // Command echo
        if (snsPacket.len == 1) {
          state = 4;             // Skip data bytes
        } else {
          numBytes = snsPacket.len - 1;
          idx = 0;
          state = 3;
        }
        break;

      case 3:
        snsPacket.grData[idx++] = ch;  // Data bytes
        if (--numBytes == 0) {
          state = 4;
        }
        break;

      case 4:
        snsPacket.checksum = ch;       // Final checksum byte
        O2Sensordata = CheckPacket();     
        if (O2Sensordata.flowrate != 1234567.75){            
          // Process and display data
          Serial.print(F("O₂ Concentration: "));
          Serial.print(O2Sensordata.o2percentage);
          Serial.println(" %");

          Serial.print(F("Flow Rate: "));
          Serial.print(O2Sensordata.flowrate);
          Serial.println(F(" L/min"));
        }
        state = 0;                     // Reset for next packet
        break;
    }
  }
//end o2

  // Print results
  Serial.println(F("-----------------------------------------------------------------"));
  Serial.println(F(" Gas Sensor Readings "));
  Serial.println(F("-----------------------------------------------------------------\n"));

  Serial.print(F("Methane (MQ4)      : "));
  Serial.print(methanePPM);
  Serial.println(F(" ppm"));

  Serial.print(F("Hydrogen (MQ8)      : "));
  Serial.print(hydrogenPPM);
  Serial.println(F(" ppm"));

  Serial.print(F("Carbon Monoxide (MQ7): "));
  Serial.print(carbonMonoxidePPM);
  Serial.println(F(" ppm"));

  Serial.print(F("Ammonia (MQ137)     : "));
  Serial.print(ammoniaPPM);
  Serial.println(F(" ppm"));

  Serial.print(F("Ozone (MQ131)     : "));
  Serial.print(ozonePPM);
  Serial.println(F(" ppm"));

  Serial.print(F("CO2     : "));
  Serial.print(CO2_PPM(), 1);
  Serial.println(F(" ppm\n"));
/*
  Serial.print(F("O₂ Concentration: "));
  Serial.print(O2Sensordata.o2percentage);
  Serial.println(" %");

  Serial.print(F("Flow Rate: "));
  Serial.print(O2Sensordata.flowrate);
  Serial.println(F(" L/min"));
*/
  Serial.println(F("-----------------------------------------------------------------"));
  Serial.println(F(" Light Sensor Readings "));
  Serial.println(F("-----------------------------------------------------------------\n"));

  Serial.print(F("Full Spectrum       : "));
  Serial.print(broadband);
  Serial.println(F(" (broadband)"));

  Serial.print(F("Infrared raw        : "));
  Serial.println(infrared);

  Serial.print(F("Infrared μW/cm²     : "));
  Serial.println(infrared/154.1);

  Serial.print(F("Lux                 : "));
  Serial.print(lux);
  Serial.println(F(" lx"));

  Serial.print(F("UV index            : "));
  Serial.println(getUVIndex(ave-120));

  Serial.print(F("UV                  : "));
  Serial.print(ave-120);
  Serial.println(F(" Analog\n"));

  Serial.println(F("-----------------------------------------------------------------"));
  Serial.println(F(" Magnetometer Readings "));
  Serial.println(F("-----------------------------------------------------------------\n"));

  // Output the magnetometer data (X, Y, Z axis)
  Serial.print(F("Magnetic Field X   : "));
  Serial.print(x);
  Serial.println(F(" µT"));

  Serial.print(F("Magnetic Field Y   : "));
  Serial.print(y);
  Serial.println(F(" µT"));

  Serial.print(F("Magnetic Field Z   : "));
  Serial.print(z);
  Serial.println(F(" µT"));

  Serial.print(F("Heading (°): "));
  Serial.println(heading);

  Serial.println(F("-----------------------------------------------------------------"));
  Serial.println(F("  BME 680 Readings "));
  Serial.println(F("-----------------------------------------------------------------\n"));  

  printValues();

  Serial.println(F(">>>>>>>New_Readings>>>>>>>\n"));

  delay(5000);
}

void configureLightSensor() {
  tsl.setGain(TSL2591_GAIN_MED);
  tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS);

  Serial.println(F("TSL2591 configured:"));
  Serial.print(F("Gain               : Medium\n"));
  Serial.print(F("Integration Time   : 100ms\n"));
}

O2data CheckPacket() {
  static O2data O2returndata;
  // Print raw data bytes
  
  Serial.print(F("Raw Data: "));
  for (uint8_t i = 0; i < (snsPacket.len - 1); i++) {
    if (snsPacket.grData[i] < 0x10) Serial.print('0');
    Serial.print(snsPacket.grData[i], HEX);
    Serial.print(' ');
  }
  
  // Recompute checksum to verify integrity
  uint8_t sum = snsPacket.header + snsPacket.len + snsPacket.cmd;
  for (uint8_t i = 0; i < (snsPacket.len - 1); i++) {
    sum += snsPacket.grData[i];
  }
  sum = 0xFF - sum + 1;

  if (sum == snsPacket.checksum) {
    Serial.println(F("[Checksum OK]"));
  } else {
    Serial.print(F("[Checksum Error: Expected "));
    if (sum < 0x10) Serial.print('0');
    Serial.print(sum, HEX);
    Serial.print(F(", Received "));
    if (snsPacket.checksum < 0x10) Serial.print('0');
    Serial.print(snsPacket.checksum, HEX);
    Serial.println(']');
    O2returndata.flowrate = 1234567.8;
    return O2returndata;
  }

  // Parse and display sensor data
  if (snsPacket.len >= 9) {
    // Oxygen Concentration (D1 and D2)
    uint16_t o2_raw = (uint16_t(snsPacket.grData[0]) << 8) | uint16_t(snsPacket.grData[1]);
    float o2_concentration = o2_raw / 10.0;

    // Flow Rate (D3 and D4)
    uint16_t flow_raw = (uint16_t(snsPacket.grData[2]) << 8) | uint16_t(snsPacket.grData[3]);
    float flow_rate = flow_raw / 10.0;

    // Temperature (D5 and D6)
    uint16_t temp_raw = (uint16_t(snsPacket.grData[4]) << 8) | uint16_t(snsPacket.grData[5]);
    float temperature = temp_raw / 10.0;
    /*
    // Display the parsed values
    Serial.print(F("O₂ Concentration: "));
    Serial.print(o2_concentration);
    Serial.println(" %");

    Serial.print(F("Flow Rate: "));
    Serial.print(flow_rate);
    Serial.println(F(" L/min"));

    Serial.print(F("Gas Temperature: "));
    Serial.print(temperature);
    Serial.println(F(" °C"));
    */
    O2returndata.o2percentage = o2_concentration;
    O2returndata.flowrate = flow_rate;
    return O2returndata;
  }
  /*
  Serial.println();  // Blank line between packets
  */
}

int getUVIndex(int analogValue) {
  // For values less than 10, return UV index 0
  if (analogValue < 10) {
    return 0;
  }
  // For values between defined thresholds, return corresponding UV index
  else if (analogValue < 46) {
    return 0; // Still considered UV index 0 for values between 10-45
  }
  else if (analogValue < 65) {
    return 1;
  }
  else if (analogValue < 83) {
    return 2;
  }
  else if (analogValue < 103) {
    return 3;
  }
  else if (analogValue < 124) {
    return 4;
  }
  else if (analogValue < 142) {
    return 5;
  }
  else if (analogValue < 162) {
    return 6;
  }
  else if (analogValue < 180) {
    return 7;
  }
  else if (analogValue < 200) {
    return 8;
  }
  else if (analogValue < 221) {
    return 9;
  }
  else if (analogValue < 240) {
    return 10;
  }
  else {
    return 11; // For values 240 and above, return UV index 11+
  }
}

// Simple approach to get a relative air quality reading
float calculateIAQ(float gasResistance, float baselineResistance) {
  // Higher values indicate better air quality (0-100 scale, with 100 being excellent)
  float ratio = gasResistance / baselineResistance;
  float iaq = constrain(ratio * 50.0, 0.0, 100.0);
  return iaq;
}

I also have an SD card hooked up to it and was wondering, if I could tell the Arduino that I have supplied it some extra storage and it can utilize the SD card for its own uses because this would fix the problem easily in my opinion. Although I don't know if that would work. Thanks to anyone who can help me.

No, and in fact, your program will not run with 221 bytes for local variables, because the SD library dynamically allocates 512 bytes for the file buffer (not included in that summary).

The posted program is far larger than what is reasonable for an Arduino Uno and the like, so to get something working, cut out all the parts you don't need right away.

Or switch to a different Arduino-compatible processor with significantly more memory.


Looks good to me, I like the definitive messages. You have over 500 bytes used just for the '-' lines, and since each line is about 65 characters repeated 8 times, you could create a function to print them instead. Even a single print statement in a function, called as needed, would save space.

You can also move many of your static messages into EEPROM (you have 1K available), which frees up memory for your code. On many of my modules, I use an RTC that includes EEPROM, but I typically replace it with a FRAM chip, giving me 32K x 8 of non-volatile memory. While you can’t execute code from it, it’s excellent for storing large amounts of data reliably.

Ok, I will make a function to print them, thanks. Will be back soon.

That should give you 1/2 K more memory. You can make a table of messages and place in EEPROM and gain about 1K in total.

Current project status:

Sketch uses 30492 bytes (94%) of program storage space. Maximum is 32256 bytes.
Global variables use 1827 bytes (89%) of dynamic memory, leaving 221 bytes for local variables. Maximum is 2048 bytes.

Now about this part:

You can also move many of your static messages into EEPROM (you have 1K available), which frees up memory for your code. On many of my modules, I use an RTC that includes EEPROM, but I typically replace it with a FRAM chip, giving me 32K x 8 of non-volatile memory. While you can’t execute code from it, it’s excellent for storing large amounts of data reliably.

How do I do that? Sorry I am really new to all of this and have never had to deal with memory issues, thanks for all the help.

The static memory is not the problem.

Please describe what happens when you upload and run the program.

Oh, so the main issue is:

Global variables use 1827 bytes (89%) of dynamic memory, leaving 221 bytes for local variables. Maximum is 2048 bytes.

? And just wondering, what things in my code use that type of memory? Thanks.

All dynamic variables, the call stack and the heap consume dynamic memory.

Please post the run time errors.

I don't really get errors, its just that the output stops abruptly and nothing happens. It usually stops around the message:

Initializing sensors...
QMC5883L magnetometer initialized.
or 
itializing sensors...
QMC5883L magnetometer ini

I am guessing it just runs out of memory when calling things under the hood like:

// Initialize magnetometer
  Wire.begin();
  compass.init(); // Initialize the QMC5883L magnetometer

?

I see, you are running out of dynamic memory very early in the initialization process, which has unpredictable consequences. If you manage to get past that, SD.begin() will fail when it tries to allocate the file buffer.

Experienced people start with running code that they understand and works properly, like a device library example. Then they add one new program option at a time, debugging as they go. Only when that bit is working perfectly is it time to add another option.

I had all of it working before I added the SD part, as soon as I added the libraries and code for it, it went over the memory limits.

The obvious step is to remove all the SD-related code and if the resulting program runs as expected, examine and post the memory report for that.

Sorry, I don't understand. Do I remove something with the SD code cause right now my code doesn't do anything with the SD card, it just includes the libraries needed and starts the card but still goes over the limits. And eventually I will need to put some data in the SD card which needs the SD library. Thanks for the help on the matter.

Remove all trace of SD. Get the rest working. Then work to optimise that footprint, including testing and using memory monitor to tell you the dynamic situation. When done, then re-add the SD support.

The posted program cannot be made to run on an Arduino Uno, so to continue with the Uno, you will have to sacrifice parts that exceed the dynamic memory limit.

If storing data on the SD card is required, start with an SD data logging example and add whatever sensor code fits on top of that, bit by bit.

Hint: the Adafruit Unified Sensor library is bloatware, and you don't need it to access the sensors. There are simpler and smaller libraries for many, if not all of those sensors.

2 Likes

OK, I have taken out the sd code and am currently getting results:

Initializing sensors...
QMC5883L magnetometer initialized.
BME680 initialized...
TSL2591 configured:
Gain               : Medium
Integration Time   : 100ms
C:\Users\erick\OneDrive\Documents\Arduino\Can_Sat_final\Can_Sat_final.ino
MHZCO2_LIB_VERSION: 0.2.1

US1010 o2 sensor Setup complete.
Preheating MQ sensors...
preheating...
BME succesfully made reading #300
BME succesfully made reading #299
BME succesfully made reading #298
BME succesfully made reading #297
BME succesfully made reading #296
BME succesfully made reading #295
BME succesfully made reading #294
BME succesfully made reading #293
BME succesfully made reading #292
BME succesfully made reading #291
BME succesfully made reading #290
BME succesfully made reading #289
...
O₂ Concentration: 20.50 %
Flow Rate: 0.00 L/min
-----------------------------------------------------------------
 Gas Sensor Readings 
-----------------------------------------------------------------

Methane (MQ4)      : 0.30 ppm
Hydrogen (MQ8)      : 0.65 ppm
Carbon Monoxide (MQ7): 0.86 ppm
Ammonia (MQ137)     : 0.40 ppm
Ozone (MQ131)     : 0.03 ppm
CO2     : 668.0 ppm

-----------------------------------------------------------------

 Light Sensor Readings 
-----------------------------------------------------------------

Full Spectrum       : 206 (broadband)
Infrared raw        : 93
Infrared μW/cm²     : 0.60
Lux                 : 112 lx
UV index            : 0
UV                  : 27.00 Analog

-----------------------------------------------------------------
 Magnetometer Readings 
-----------------------------------------------------------------

Magnetic Field X   : 1143 µT
Magnetic Field Y   : 1091 µT
Magnetic Field Z   : 510 µT
Heading (°): 43.67
-----------------------------------------------------------------
  BME 680 Readings 
-----------------------------------------------------------------

Temperature = 23.59 *C
Pressure = 1005.00 hPa
Humidity = 38.53 %
Gas = 125.75 KOhms
IAQ = 69.44 %(relative to 50% being good)
Approx. Altitude = 184.18 m

Now to optimize the code...

Hint: the Adafruit Unified Sensor library is bloatware, and you don't need it to access the sensors. There are simpler and smaller libraries for many, if not all of those sensors.

Lets get started with this one because I think this could free up quite some memory, maybe enough for the sd library. The sensors I am using are: US1010 o2 sensor, MHZ19E CO2 sensor, TSL2591, BME680, MQ sensors(131,137,8,7,4), and GUVAS12SD UV sensor. The libraries I have included for the sensors are:

#include <Wire.h> //BME680 & QMC5883L
#include <QMC5883LCompass.h> // QMC5883L compass/magnetometer
#include <Adafruit_Sensor.h> // My guess is for other Adafruit libraries
#include <Adafruit_TSL2591.h> //TSL2591 light sensor
#include "MHZCO2.h" //MHZ19E CO2 sensor
#include "Adafruit_BME680.h" // BME 680
#include <math.h>  // For atan2() and degrees() 
#include <SoftwareSerial.h> // US1010 O2 sensor

Correct me if I am wrong, I believe that the libraries would be affected by Adafruit Adafruit_Sensor.hlibrary are: Adafruit_TSL2591.h and Adafruit_BME680.h, now If we find libraries smaller for these sensors, can we remove Adafruit's libraries entirely? Does anyone know of any smaller libraries for this thanks.

A web search will find libraries and tutorials for sensors. Example search phrase "arduino tsl2591". Minimal TSL2591 example

Sparkfun is a good source of simple device libraries.

You're writing a novel with your Serial Printing. Here's a rework of some of the wordiest lines you had, saved over 1000 bytes.

void setup() {
  Serial.begin(115200); // was there a reason you used 9600 baud? Try 115200 if hardware allows
  // Sketch uses 3072 bytes (9%) of program storage space. Maximum is 32256 bytes.
  /*
  Serial.println(F("-----------------------------------------------------------------"));
  // "sensor readings" is unnecessary. Obviously they are sensor readings
  Serial.println(F(" Gas Sensor Readings "));  // the space at the beginning and end is two wasted bytes
  Serial.println(F("-----------------------------------------------------------------\n"));
  Serial.println(F("-----------------------------------------------------------------"));
  Serial.println(F(" Light Sensor Readings "));
  Serial.println(F("-----------------------------------------------------------------\n"));
  Serial.println(F("-----------------------------------------------------------------"));
  Serial.println(F(" Magnetometer Readings "));
  Serial.println(F("-----------------------------------------------------------------\n"));
  Serial.println(F("-----------------------------------------------------------------"));
  Serial.println(F("  BME 680 Readings "));
  Serial.println(F("-----------------------------------------------------------------\n"));
  Serial.print(F("Hydrogen (MQ8):      ")); // are six spaces really needed? Spaces cost memory
  Serial.println(F(" ppm"));
  Serial.print(F("Carbon Monoxide (MQ7): "));
  Serial.println(F(" ppm"));
  Serial.print(F("Ammonia (MQ137)     : "));
  Serial.println(F(" ppm"));
  Serial.print(F("Ozone (MQ131)     : "));
  Serial.println(F(" ppm"));
  Serial.print(F("CO2     : "));
  Serial.println(F(" ppm"));

  Serial.println(F("Initializing sensors..."));
  Serial.println(F("QMC5883L magnetometer initialized."));
  Serial.println(F("Could not find a valid BME680 sensor, check wiring!"));
  Serial.println(F("TSL2591 not found! Check wiring or address."));
  Serial.println(F("BME680 initialized..."));
  Serial.println(F("US1010 o2 sensor Setup complete."));
  Serial.print(F("Initializing SD card..."));

  Serial.println(F("initialization failed. Things to check:"));
  Serial.println(F("1. is a card inserted?"));
  Serial.println(F("2. is your wiring correct?"));
  Serial.println(F("3. did you change the chipSelect pin to match your shield or module?"));
  // the following line is pointless because you wouldn't hot swap the hardware anyway.
  Serial.println(F("Note: press reset button on the board and reopen this Serial Monitor after fixing your issue!"));
  // this line is also pointless since you append "ppm" to every gas reading anyway later
   Serial.println(F("Sensor readings in ppm:"));
*/

  // let's save 1008 bytes!
  // Sketch uses 2064 bytes (6%) of program storage space. Maximum is 32256 bytes.
  printDashLine();
  Serial.println(F("Gas Data"));  // you could lose "data" too as it's self evident...
  printDashLine();

  printDashLine();
  Serial.println(F("Lux Data"));
  printDashLine();

  printDashLine();
  Serial.println(F("Mag Data"));
  printDashLine();

  printDashLine();
  Serial.println(F("VOC Data"));
  printDashLine();

  Serial.print(F("H2   : "));
  printPpmText();
  Serial.print(F("CO   : "));
  printPpmText();
  Serial.print(F("NH3  : "));
  printPpmText();
  Serial.print(F("O3   : "));
  printPpmText();
  Serial.print(F("CO2  : "));
  printPpmText();

  Serial.println(F("Initializing..."));
  Serial.println(F("Mag OK."));
  Serial.println(F("VOC fail! Check wiring!"));
  Serial.println(F("Lux fail! Check wiring or address."));
  Serial.println(F("VOC init."));
  Serial.println(F("O2 OK."));
  Serial.println(F("SD Card ready"));

  Serial.println(F("Hardware failure. Check:"));
  Serial.println(F("Card inserted?"));
  Serial.println(F("Wiring correct?"));
  Serial.println(F("Correct chipSelect pin?"));
  // the following line is pointless because you wouldn't hot swap the hardware anyway.
  // Serial.println(F("Note: press reset button on the board and reopen this Serial Monitor after fixing your issue!"));
}

void loop() {
}


void printPpmText() {
  Serial.println(F(" ppm"));
}

void printDashLine() {
  for (int i = 0; i < 65; i++) {
    Serial.print(F("-"));
  }
  Serial.print('\n');
}

of program memory. OP is short on dynamic memory.