First project: CO2 and Temperature with Arduino Nano

I recently ordered the components

  • Arduino Nano
  • DHT20 (temperature and humidity sensor)
  • MH-Z19C (CO2 sensor)
  • SH1106 (128x64 OLED Display)

And after arriving I want to connect the parts like in the picture attached.
However, I am very uncertain about my first code to get the project running.
I basically just copied text from similar projects with different combinations of sensors and can neighter tell if the pins in the code fit to the wiring in the picture, nor do I know if the code is complete to achieve the goal: The display showing the temperature in the first line, the humidity in the second and the co2 content in the third.
For instance, I was wondering that not every pin was defined or has the code "int" in front of it in the other projects I copied code from.

I would be very glad if someone could look over the code. I tried to structure and comment it well.


// CO2
#include <Arduino.h>
#include <MHZ19.h>
#include <SoftwareSerial.h>
// Temp
#include <DFRobot_DHT20.h>
// Display
#include <Adafruit_SH1106.h>
#include <Wire.h>

// CO2
#define RX_PIN 5                                          // Rx pin which the MHZ19 Tx pin is attached to D2
#define TX_PIN 6                                          // Tx pin which the MHZ19 Rx pin is attached to D3
#define BAUDRATE 9600                                     // Device to MH-Z19 Serial baudrate (should not be changed)
MHZ19 myMHZ19;                                             // Constructor for library
SoftwareSerial mySerial(RX_PIN, TX_PIN);                   // (Uno example) create device to MH-Z19 serial
// Temperature
DFRobot_DHT20 dht20;
// Display
#define OLED_RESET 4  // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SH1106 display(OLED_RESET);

void setup()
{
  Serial.begin(9600);
  // Start Co2
  mySerial.begin(BAUDRATE);                               // (Uno example) device to MH-Z19 serial start
  myMHZ19.begin(mySerial);                                // *Serial(Stream) refence must be passed to library begin().
  myMHZ19.autoCalibration();                              // Turn auto calibration ON (OFF autoCalibration(false))

  // Start Temp
  while (dht20.begin()) {
    Serial.println("Initialize sensor failed");
    delay(1000);
  }

  // Start Display
  display.begin(SH1106_SWITCHCAPVCC, 0x3C);
  display.display();
  delay(2000);
  display.clearDisplay();
}


void loop()
{
  //Get CO2
  int CO2;
  CO2 = myMHZ19.getCO2();                             // Request CO2 (as ppm)

  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0, 0);
  display.print(dht20.getTemperature());
  display.print('\n');
  display.print(dht20.getHumidity());
  display.print("%");
  display.print('\n');
  display.print(CO2);
  display.print(" ppm");
  display.display();
  delay(2000);
}

Conventional wisdom suggests that you start with a set of smaller programs, each of which exercises one piece of hardware, a sensor or display in your case. Once you have established that the hardware works and that you can write code for it, combine them one by one, testing as you go.

3 Likes

Like @wildbill says, get each part working on its own. The examples from the different libraries will help with that.

How is the project powered?

1 Like

Probably it's not even possible to run my project on a nano :frowning:
I got the message: Global variables use 2097 bytes (102%) of dynamic memory, leaving -49 bytes for local variables. Maximum is 2048 bytes.

but even after removing the temperature sensor and all it's code, it is still at 101%.

And I think to start with arduino, it is the least I can start with, one sensor and one display?
So either there is something wrong with my code, or I'm not sure what I'm doing wrong...

I plan to power it with micro usb

It's not your code, it's your libraries - they're consuming most of your RAM before you get started. You're going to need a device with more memory.

1 Like

okay thanks, I will order an arduino every then and read more about programming until it arrives

The display buffer is using half the ram. Look at the u8g2 library using a page buffer, that takes much less memory.

You can get on with writing code for your sensors at least - you can always emit the results to serial.

1 Like

I downloaded the libary and set up page buffer, now it says Global variables use 2807 bytes (137%) of dynamic memory

yes, I will do that.
I'm thinking about ordering a 16x2 LCD, which might help aswell since it seems to use a much smaller library. Yet I will have to research how to solve the issue it using the same pins as the CO2 sensor (D2 and D3 are used by both devices)... the OLED only used 4 pins but the LCD would use many more. I'm almost certain it is possible but will take me some time to figure out how

The LCD library will allow you to use any pins that you want for the LCD. On a Nano, you can also use the analog inputs as they are digital pins first and have analog input as a special function. If you want to minimize the number of pins that the LCD uses, use an I2C enabled 1602 display or an I2C enabled 20x4 display.

If you go the I2C 1602 or 2004 LCD route, the best library is the hd44780 library by Bill Perry. The library is available through the library manager. For an I2C LCD display to work, the I2C address and the I2C backpack to LCD pin mapping must be correct. If the library default settings for either or both are not correct the LCD will not work. You can try to figure out the right pin mapping and use an I2C scanner to find the address, but if you install and use the hd44780 library that is done automatically by the library. The library is actively maintained and faster than any of the older LiquidCrystal_I2C libraries.

The hd44780 library also supports the 4 pin parallel interface.

Is the code you posted the entire code you are compiling? That seems very unlikely to need that much memory.

Where did you get the library for the CO2 sensor?

I dont necessarily want to limit the number of the pins used, I was just a little confused at first that the ones that were used for the CO2 sensor were also by default used by the display. However I have 3 arduino projects in mind, I might ordner and use the normal 16x2 one for this project, order the l2c ones for the next and use the already bought OLED for the third project. It might help me to figure out the functionality and programming for arduino better.

So I did some research and it maybe led me to a wrong path, I thought I have two possibilitys to assign pins: eighter I find out which pin has which number on my arduino board by using a link like this (in my case pins 1-30) or I use the name of the pin (like D1,D2...). Actually, neighter seems to be the case: seems like I cannot use D1,D2... to assign something, nor do the numbers 1-30.

However I found this default template for the display and it says

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

which is very confusing because in the description it says

  • LCD D4 pin to digital pin 5
  • LCD D5 pin to digital pin 4
  • LCD D6 pin to digital pin 3
  • LCD D7 pin to digital pin 2

so actually I guess if I define in a arduino code I just use the number of the digital pin written on the hardware minus the "D", so for pin D4 I would just use "4"..., for pin D5 I would use "5"...

Which would mean, if the display should not use pin D2-D4, I would change the code to:

const int rs = 12, en = 11, d4 = 10, d5 = 9, d6 = 8, d7 = 7;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

That would mean, I would connect the digital pins d4-d7 of the LCD to my arduinos digital pins D10-D7...

Wow, I am totally confused now, I might try to find a book that explains those kind of basics

Yes it is the entire code. Think I found and installed it in the normal library in the arduino code program, searching for MHZ19

This should let you do some testing on the Nano, I've taken out the Adafruit display library and replaced it with u8g2. There are several constructors for the SH1106 display, not sure which one will work with your display, and the font is not the same as Adafruit's. This shows 1346 bytes of ram used, if that is still too much you can try the u8x8 library that does not use a display buffer.

// CO2
#include <Arduino.h>
#include <MHZ19.h>
#include <SoftwareSerial.h>
// Temp
#include <DFRobot_DHT20.h>
// Display
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

#include <Wire.h>

// CO2
#define RX_PIN 5                                          // Rx pin which the MHZ19 Tx pin is attached to D2
#define TX_PIN 6                                          // Tx pin which the MHZ19 Rx pin is attached to D3
#define BAUDRATE 9600                                     // Device to MH-Z19 Serial baudrate (should not be changed)
MHZ19 myMHZ19;                                             // Constructor for library
SoftwareSerial mySerial(RX_PIN, TX_PIN);                   // (Uno example) create device to MH-Z19 serial
// Temperature
DFRobot_DHT20 dht20;
// Display
//  pick the constructor that works with your display

//U8G2_SH1106_128X64_NONAME_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SH1106_128X64_VCOMH0_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);    // same as the NONAME variant, but maximizes setContrast() range
//U8G2_SH1106_128X64_WINSTAR_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);   // same as the NONAME variant, but uses updated SH1106 init sequence
//U8G2_SH1106_128X32_VISIONOX_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SH1106_128X32_VISIONOX_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SH1106_72X40_WISE_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);

void setup()
{
  Serial.begin(9600);
  // Start Co2
  mySerial.begin(BAUDRATE);                               // (Uno example) device to MH-Z19 serial start
  myMHZ19.begin(mySerial);                                // *Serial(Stream) refence must be passed to library begin().
  myMHZ19.autoCalibration();                              // Turn auto calibration ON (OFF autoCalibration(false))

  // Start Temp
  while (dht20.begin()) {
    Serial.println(F("Initialize sensor failed"));
    delay(1000);
  }

  u8g2.begin();
}


void loop()
{
  //Get CO2
  int CO2;
  CO2 = myMHZ19.getCO2();                             // Request CO2 (as ppm)
  
  //note - get all the data before entering the display loop
  //       otherwise the data may change between display pages
  float dht20Temp = dht20.getTemperature();
  float dht20Humidity = dht20.getHumidity();

  u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
  byte lineSpacing = u8g2.getAscent() - u8g2.getDescent() + 1; //u8g2 does not support newline
  
  u8g2.firstPage();
  do {
    u8g2.setCursor(0, lineSpacing);
    u8g2.print(dht20Temp);
    u8g2.setCursor(0, 3 * lineSpacing);
    u8g2.print(dht20Humidity);
    u8g2.print('%');
    u8g2.setCursor(0, 5 * lineSpacing);
    u8g2.print(CO2);
    u8g2.print(F(" ppm"));
  } while ( u8g2.nextPage() );

  delay(2000);
}
1 Like

unfortunately, it does not work so far.
I uploaded the code to the Nano but the display kept dark
I thought it might be a good idea to test if the display works by deleting everything out of your code except the a print command (just for testing the display), which brought me to this code:


// Display
#include <U8g2lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

#include <Wire.h>

// Display
//  pick the constructor that works with your display

//U8G2_SH1106_128X64_NONAME_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
//U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SH1106_128X64_VCOMH0_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);    // same as the NONAME variant, but maximizes setContrast() range
//U8G2_SH1106_128X64_WINSTAR_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);   // same as the NONAME variant, but uses updated SH1106 init sequence
//U8G2_SH1106_128X32_VISIONOX_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
//U8G2_SH1106_128X32_VISIONOX_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
U8G2_SH1106_72X40_WISE_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);

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

  u8g2.begin();
}


void loop()
{
  
  u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
  byte lineSpacing = u8g2.getAscent() - u8g2.getDescent() + 1; //u8g2 does not support newline
  
  u8g2.firstPage();
  do {
    u8g2.setCursor(0, lineSpacing);
    u8g2.print("HI");
  } while ( u8g2.nextPage() );

  delay(2000);
}

On top of the code, where you wrote "pick the constructor that works with your display", I tried all the different ones but the display just kept dark...
It definitely notices my board on port COM3, the Nano is blinking when I upload the code, but after that nothing happens. If I open the serial monitor and extend the code by

Serial.println("Hello world!");

the arduino itself seems to work fine... so I am not sure if the display is broken or there is something missing in the code

Does your display work properly with the examples from the Adafruit SH1106 library?

1 Like

no, unfortunately it does not.
It is this display I ordered:

however the device that I received has another pinout it seems.
I took some pictures of the current wiring:

on the product page it has GND on the left, while mine seems to have the GND on the second place

SCL is A5, SDA is A4, you have those reversed.

1 Like

wow, that actually fixed the display problem! it is working now with the example from adafruit!
I already tried going to the next step, getting the CO2 Sensor running and yet again facing issues...
After googling for a long time someone mentioned that possibly the Arduino does not supply enough power, so I hooked the sensor up to a buck converter, which did not help.
Actually there seem to be only two cables that need to be connected to the Arduino: the TX and RX. Since you gave the advice of running the example first, I tried that with the CO2 sensor too.
I connected the parts like this, so buck converter to CO2 for energy, CO2 to Arduino for data (TX to D5, RX to D6

The example code is actually very short:

#include <Arduino.h>
#include "MHZ19.h"                                        
#include <SoftwareSerial.h>                                // Remove if using HardwareSerial

#define RX_PIN 6                                      // Rx pin which the MHZ19 Tx pin is attached to
#define TX_PIN 5                                         // Tx pin which the MHZ19 Rx pin is attached to
#define BAUDRATE 9600                                      // Device to MH-Z19 Serial baudrate (should not be changed)

MHZ19 myMHZ19;                                             // Constructor for library
SoftwareSerial mySerial(RX_PIN, TX_PIN);                   // (Uno example) create device to MH-Z19 serial

unsigned long getDataTimer = 0;

void setup()
{
    Serial.begin(9600);                                     // Device to serial monitor feedback

    mySerial.begin(BAUDRATE);                               // (Uno example) device to MH-Z19 serial start   
    myMHZ19.begin(mySerial);                                // *Serial(Stream) refence must be passed to library begin(). 

    myMHZ19.autoCalibration();                              // Turn auto calibration ON (OFF autoCalibration(false))
}

void loop()
{
    if (millis() - getDataTimer >= 2000)
    {
        int CO2; 

        /* note: getCO2() default is command "CO2 Unlimited". This returns the correct CO2 reading even 
        if below background CO2 levels or above range (useful to validate sensor). You can use the 
        usual documented command with getCO2(false) */

        CO2 = myMHZ19.getCO2();                             // Request CO2 (as ppm)
        
        Serial.print("CO2 (ppm): ");                      
        Serial.println(CO2);                                

        int8_t Temp;
        Temp = myMHZ19.getTemperature();                     // Request Temperature (as Celsius)
        Serial.print("Temperature (C): ");                  
        Serial.println(Temp);                               

        getDataTimer = millis();
    }
}

but the output is just errors:
!ERROR: Failed to verify connection(1) to sensor.
!ERROR: Initial communication errorCode recieved
!Error: Timed out waiting for response
!Warning: Clearing Byte: 255
!Error: Timed out waiting for response

You need a ground connection between the sensor and the arduino, in addition to the Rx and Tx.