Translate

Monday, February 10, 2025

Weather station with ESP32 CYD

 


The board used in this project, identified as ESP32-2432S028R and also called CYD (Cheap Yellow Display), is very interesting because, in addition to a powerful 32-bit MCU like the ESP32, it has a relatively large 2.8” TFT touch display with a resolution of 240 x 320 pixels. It was designed to develop in IOT using languages ​​such as Arduino IDE, ESP-IDF, Micro/CircuitPython and Scratc, given the large availability of libraries, I used the Arduino IDE.

There are numerous IOT and gaming entertainment applications on the net, in this project I did not use the WiFi that the ESP32 is equipped with but I created a small weather station capable of recording the ambient temperature, atmospheric pressure and relative humidity on SD. 

To make this small weather station you don't need any soldering or manual skills, you just need to load the Aduino IDE development system with the ESP32 package and the various libraries used for the display, the touch controller, the sensor and the SD on your PC.

This article is mainly used to learn how to use the touch display and the SD together with the I2C interface that allows you to connect countless devices. For the I2C interface, you just need to take into account that the external devices must be powered at 3.3V and not at 5V. This is because, if the device is powered at 5V, even the two SDA and SCL signals will have similar levels that could damage the ESP32 which has levels of 3.3V.

The ESP32-2432S028R board (CYD)

This board is well built but has significant hardware design flaws, in particular it does not have an acceptable serial UART: UART0 is already used by the USB adapter, and it is better not to use it as an RX even if there is a connector (P1) on the board. It has a useless tricolor LED on the back that could be replaced by a more useful UART2. It has only three I/O pins available on the connectors and also duplicated. Another absurd thing is that the two SPI buses have been used for the display and for the touch controller while the SD slot has other pins. In my project with ESP32 and separate displays, I had used the same bus for the display and the SD. If I want to use the SD with hardware SPI I have to give up the touch, unless you use a software SPI, as I did in this project. I used the mySD.h library that seems to also be contained in the ESP32 package because the compiler gives repetition warnings, but it works anyway.

 I2C Interface

The CYD board has two I2C bus interfaces, but no dedicated I2C pins. Instead, it allows flexible pin assignment, meaning any GPIO pin can be configured as I2C SDA (data line) and SCL (clock line).
However, GPIO21 (SDA) and GPIO22 (SCL) are commonly used as the default I2C pins to simplify things for people using existing Arduino code, libraries and sketches.
For I2C peripherals, use the CN1 connector and set GPIO27 as SDA. The connections with the BME280 sensor are as shown in the figure 1. For the connection, use the connector included in the package with the ends headed with single-pin female connectors that are inserted into the male pin strips according to the order shown.
Figure 1

Here's how to set them up in the program:

#include <Wire.h>

#define SDA  27

#define SCL  22

And in setup():

Wire.begin (SDA, SCL);// assign the I2C pins

The BME280 sensor

The BME280 is a Bosch temperature, barometric pressure and relative humidity sensor in a tiny metal case measuring just 2.5x2.5x0.93 mm. It also consumes very little power, making it particularly suitable for cell phones, GPS receivers or watches. It has a pressure accuracy of 0.12 hPa, which is equivalent to about 1 m of altitude difference with a thermal drift of only 12.6 cm/°C. The temperature sensor has a resolution of 0.01 °C and an accuracy of 0.5 °C at 25 °C. The relative humidity sensor has an accuracy of 3 %RH at 25 °C.

The sensor has the same pinout as the BMP280, which however lacks the humidity sensor.

It can be powered from 1.7V to 3.6V, in this case it is powered at 3.3V by the LDO (low drop-out) regulator inside the module.

Modules with this sensor are commercially available with SPI and/or I2C interfaces, the latter should be chosen for our project.

As libraries, in addition to Wire.h which manages the I2C bus, you need the two libraries Adafruit_Sensor.h and Adafruit_BME280.h, which can be downloaded from the Arduino IDE.

 

SPI Interfaces

The ESP32 has four SPI peripherals, called SPI0, SPI1, SPI2 (HSPI), and SPI3 (VSPI). SPI0 is entirely dedicated to the flash cache that the ESP32 uses to map the SPI flash device it is connected to into memory. SPI1 is connected to the same hardware lines as SPI0 and is used to write to the flash chip. HSPI and VSPI are general purpose and available to the user.

Similar to I2C, the ESP32 allows flexible pin assignments for SPI. This means that any GPIO pin can be configured as an SPI pin. The CYD board uses VSPI for the touch controller, and SPI for the TFT display.

 

The TFT display

The display is a 2.8-inch 320x240 pixel TFT touch with 18-bit for 262k colors with 6-bit encoding per color. The control chip used for this screen is an ILI9341 while the Touch Screen Controller is an XPT2046. The TFT_eSPI.h library was used, specifically dedicated to 32-bit MCUs, when you insert it with the Arduino IDE, you need to go to the folder …\Documents\Arduino\libraries\TFT_eSPI\ and replace the configuration file “User_Setup.h” according to the I/O of the CYD board, I loaded the one by Rui and Sara Santos from their site https://randomnerdtutorials.com/cheap-yellow-display-esp32-2432s028r/.

 

The Touch Controller

The TFT_eSPI.h library also has XPT2046 support, but the examples provided do not work. Maybe because it only works with SPI bus shared by ILI9341 and XPT2046 controllers, in our case they are connected to different SPI buses, so we need to use the XPT2046_Touchscreen.h library by Paul Stoffregen. And also modify the examples, if we need them.

 

SD Interface

It uses a 1-bit SPI bus, so you only need 4 pins to use it:

SD CLK                    GPIO18

SD MISO                 GPIO19

SD MOSI                 GPIO23

SD CS                      GPIO5

As already written, the two available SPI buses are used by the display and the touch controller, so you need to use a slower software interface. I have successfully used the mySD.h library.

This project does not require a high writing speed as one line of text is written every few tens of seconds.

 

The program

To develop the program I used the Arduino IDE, version 2.3.3 with the ESP32 package. I set “ESP32-WROOM-DA Mod” as the board. The sensor and display libraries must be loaded using the same IDE with “Manage libraries...”. The mySD.h library from SparkFun Electronics can be downloaded from the site https://github.com/nhatuan84/esp32-micro-sdcard/tree/master.

Every “period” [s], the program measures the three variables and prints them on the display, then transforms them into integers, multiplying the temperature and pressure by 10, and puts them in the “Gbuff” graphics buffer (3x320).

The graph starts from left to right and, once the buffer is filled, it scrolls the measurements from right to left and inserts the last measurements on the right. To have a graph and measurements of the last sampling, a graph of only one variable is executed, but by touching the display, the variable changes according to the sequence T, P, H.

To save energy, the display turns on for only 20 seconds, but by touching it turns on. The touch is used only for this function.

Initially it takes a measurement to evaluate the minimum and maximum values ​​to have graphs with good precision.

With period = 60 seconds, on the display, you have a graph of 60 * 320 = 19200 seconds equal to 5h 20'.

If you insert a micro SD into the slot, the program initially writes a line (header) with the names of the variables and the sampling period and saves the measurements in the file "TPH.TXT". If the file is already present, it appends a new header and the measurements.

If it acquires measurements, the number of samples saved on the SD card will appear on the display next to the temperature.

The Arduino code

/* program CYD_PTH

   uses ESP32-2432S028 board with BME280 sensor on CN1

   save data on SD (with software SPI)

   Giovanni Carrera, rev.171124 */

#include <WiFi.h>

#include <Adafruit_Sensor.h>

#include <Adafruit_BME280.h>

#include <Wire.h>

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme;// I2C

#define SDA 27

#define SCL 22

#include <TFT_eSPI.h> // Graphics and font library for TFT driver chip

#include <SPI.h>

TFT_eSPI tft = TFT_eSPI();  // Invoke library, pins defined in User_Setup.h

#include <XPT2046_Touchscreen.h>

// pins used for XPT2046 on CYD board

#define XPT2046_MOSI 32

#define XPT2046_MISO 39

#define XPT2046_CLK 25

#define XPT2046_CS 33

#define XPT2046_IRQ 36

SPIClass tsSPI = SPIClass(VSPI);

XPT2046_Touchscreen ts(XPT2046_CS, XPT2046_IRQ);// interrupt enabled polling

#include <mySD.h>

ext::File myFile;

//define pins of SD card slot

#define SD_CS    5

#define SD_MOSI  23

#define SD_MISO  19

#define SD_SCK   18

 

float temperature, humidity, pressure, altitude;

int16_t GBuff [3][320];// data buffer for graphics

int16_t BuffInd= 0;// data buffer index

int16_t GVal [3];

int16_t ValMin [3];

int16_t ValMax [3];

 

const char ValName[4] = {'T', 'P', 'H'};

uint32_t time_now = 0;

bool first_buffer = true;// first buffer fill flag

int period = 60;// sample period in seconds, for 5h20' plot (if = 90, 8 hours plot)

uint32_t secperiod = 1000; // 1 second

uint8_t sec, mp = 0;

int8_t BLsec = 20;

bool SDok = false;

bool ftime = true;

uint32_t vep = 0;

 

void setup() {

  WiFi.disconnect(true);// turn OFF WiFi to save power

  WiFi.mode(WIFI_OFF);

  tft.init();

  tft.setRotation(1);

  tft.fillScreen(TFT_BLACK);

  tft.drawString("TPH monitor by GCAR", 0, 0, 4);

  tft.setTextColor(TFT_YELLOW, TFT_BLACK);

  Wire.begin(SDA, SCL);

  if (!bme.begin(0x76, &Wire)){

    tft.drawString("BME280 NOT OK", 0, 26, 4);

    while (1); // sketch halts in an endless loop

  }

  GetMeasures();// initial values to calculate ValMin and ValMax

  ValMin [0] = GVal[0]-50;// T-5 C°

  ValMin [1] = GVal[1]-50;// P-5 hPa

  ValMin [2] = 20;// 20 %

  ValMax [0] = GVal[0]+50;// T+5 C°

  ValMax [1] = GVal[1]+50;// P+5 hPa

  ValMax [2] = 100;// 100 %

  tsSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);// define SPI pins for touchscreen

  ts.begin(tsSPI);

  ts.setRotation(1); // display in landscape aspect

  tft.drawString("Initializing SD card...", 0, 26, 4);

  if (!SD.begin(SD_CS, SD_MOSI, SD_MISO, SD_SCK)) {//initialise SD card

    tft.setTextColor(TFT_RED, TFT_BLACK);// red color

    tft.drawString("SD card failed !", 0, 52, 4);

    tft.setTextColor(TFT_WHITE, TFT_BLACK);// white

  } else {

    SDok = true;

    tft.drawString("SD card installed", 0, 52, 4);

  }

  delay(5000);

 }

 

void loop() {

 if(millis() - time_now > secperiod) {

    time_now = millis();

    sec ++;

    if (ts.tirqTouched() && ts.touched()) { // display is touched

      digitalWrite(TFT_BL, HIGH); // TFT backlight ON

      mp++;

      if (mp == 3) mp = 0;

      PrintMeasures();

      PlotMeasures(319, mp);// plot measure

      BLsec = 20;

    }

    BLsec --;

    if (BLsec <= 0){

      digitalWrite(TFT_BL, LOW); // TFT backlight OFF

    }

    if (sec == period){

      sec = 0;

      GetMeasures();

      if (SDok) SaveMeasures();

      if (first_buffer){// store them in the buffer

        for (int j=0; j <= 2; j++){

          GBuff[j][BuffInd]= GVal[j];

        }  

        BuffInd++;

        if (BuffInd == 320) first_buffer = false;      

      } else {

         for (int j=0; j <= 2; j++){

           for (int i=0; i <= 238; i++){

             GBuff[j][i]= GBuff [j][i+1];// shift left previous values

           }

         }

         for (int j=0; j <= 2; j++){

           GBuff[j][319]= GVal[j];// update buffer with last values

         }  

      }

    }

  }

}

/************************** MY FUNCTIONS **************************/

void PlotMeasures(int16_t k,uint8_t m) {

 //plot k graph points of all measures

  const int yp [3] = {2,106,210};// upper left corners of the plots

  //digitalWrite(TFTLed, LOW);// turn ON display leds

  tft.fillRect(0,110,320,130,TFT_BLACK);// clear only the plot window

  tft.drawRect(0,110,320,130,TFT_WHITE);

  tft.setTextColor(TFT_CYAN);

  tft.drawString(String(ValName[m]), 5, 112, 2);

  for (int j=0; j <= k; j++){

      int val = map(GBuff[m][j], ValMin[m], ValMax[m], 130, 0);

      tft.drawPixel(j, val+110, TFT_CYAN);// draw the graph

   }

}

 

void GetMeasures(){

  temperature = bme.readTemperature();

  pressure = bme.readPressure()/100.0F;// pressure in hPascal

  humidity = bme.readHumidity()+13;// corrected in comparison with a precision hygrometer

  GVal[0] = int(temperature*10);//stores temperature in tenths of a degree

  GVal[1] = int(pressure*10);//stores pressure in tenths of a millibar

  GVal[2] = int(humidity);//stores relative humidity in %

}

 

void PrintMeasures(){

  tft.fillRect(0,26,300,78,TFT_BLACK);// clear only the print window

  tft.setTextColor(TFT_YELLOW, TFT_BLACK);

  tft.drawString("T = "+String(temperature,1)+" [C]", 0, 26, 4);

  if (SDok) tft.drawString("Ns= "+String(vep), 180, 26, 4);

  tft.drawString("P = "+String(pressure,1)+" [hPa]", 0, 52, 4);

  tft.drawString("H = "+ String(humidity,1)+" [%]", 0, 78, 4);

 }

 

 void SaveMeasures(){

  myFile = SD.open("TPH.TXT" , FILE_WRITE);            

  if (myFile) {  // if the file is available, write to it:

    if (ftime){ // at the beginning write the header line

       myFile.println("T[C], P[hPa], H [%], Period = "+String(period)+" [s]");

       ftime = false;

    }  

    myFile.print(temperature,1);

    myFile.print(',');

    myFile.print(pressure,1);

    myFile.print(',');

    myFile.println(humidity);

    vep++;

    myFile.close();

  }

 }


No comments:

Post a Comment