ESP32 – Light, Temperature, OLED!

In my AI Society project, external variables will be used to affect the nodes emotions and be converted into a form of currency. To do this they need to be able to read their local temperature and light level. So, in this post I will be covering how to set this up on the ESP32.Now, there are many Arduino tutorials already available, some of which I referenced when putting this together, so why should you read this? Well, there are a few differences when using an ESP 32 instead of an Arduino, especially when using the form factor development board that I purchased. It sounds straight forward but I ran into a few issues that caused my implementation to take a bit longer to finish then I had hoped.

In addition to covering these issues I will also be covering ESP32 programming using the Deviot package for Sublime Text 3.

The code can be found on GitHub here, including a PDF of the circuit diagram.

Parts

No. Name Link Price
1 Photo resistor eBay £0.99 (10 pieces)
1 Thermistor eBay £1.85 (5 pieces)
2 10k resistor eBay £0.99 (20 pieces)
1 OLED 0.96″ I2C eBay £4.19
1 ESP32 eBay £5.99

As you can see from the image below, this ESP32 module has double stacked the GPIO pins to decrease its form factor, this means it’s not possible to directly plug this into a breadboard as you would bridge the pins. If you want one you can stick on a breadboard I would recommend getting this one for £11.59 or, if you don’t mind waiting, this one from China for £4.71.

For this I’m using my module in “dead bug” configuration and using breadboard wires to hook up the pins I need.

ESP32
Dead bug and not dead bug
ESP32 connected to thermistor, photoresistor and OLED
All hooked up

One of the lovely features of the ESP32 is the ability to use any GPIO pins as SDA and SCL for I2C. For this I used the two default I2C pins which are 21 for SDA and 22 for SCL. Another feature of the ESP32 is its 12bit ADC, this tripped me up a bit when doing the temperature and light conversions whilst following the Arduino tutorials, this is because most, if not all Atmel ATMega and ATTiny chips use a 10bit ADC. For a 10bit ADC the values it can read range from 0 to 1023, with the ESP32 the 12bit ADC has a range of 0 to 4095!

The thermistor is connected to GPIO 27 and the photo resistor is connected to GPIO 25. Below is the circuit diagram, it uses the ESP32S WROOM module layout, which is the ESP32S not mounted to any board. 

(Click for a bigger image)

Circuit diagram
Nice and simple

Sublime text, Deviot and Libraries

To program the ESP32 and upload the code I am writing in Sublime Text 3 with the Deviot plugin. Initially I wasn’t sure what board to select to program this ESP32 module but found that the Node32 board works perfectly.

Although compiling worked fine, when I tried to upload the code I got the following error:

*** Do not know how to make File target 'program' (C:UsersUserAppDataLocalTempDeviotmainprogram).  Stop.

The end result was a lot of googling until I read that you should set the programmer to ‘None’ in the Deviot settings. After that, no more issues!

Reading the Thermistor

This is quite straightforward, all we need to do is read the analog input from the GPIO pin the thermistor is connected to.

//Pins
#define THERMISTORPIN 25

void setup() {}

void loop() {
  double thermVal;
  
  //Read the thermistor value
  thermVal = analogRead(THERMISTORPIN);
  // Print it to serial
  Serial.print("Thermistor reading: ");
  Serial.println(thermVal);
  
  // Wait 2 seconds and do it again
  delay(2000);
}

Sampling Thermistor values and converting to Celsius

Now, as we aren’t using the most accurate or expensive components if you were to output in a shorter times span in an environment that isn’t changing much you would notice that the value changes quite rapidly. Not to mention that this thermistors accuracy is really only 1 degree Celsius. How can this output be improved? Well for starters it would be nice if it was in degrees and fluctuated a bit less.

This can be achieved by taking a number of samples and averaging them, converting the value to resistance, then plumbing them through the simplified B Parameter equation of the Steinhart equation. This isn’t completely accurate but does the job well enough for the AI Society project.

One final noteworthy item, during usage the thermistor will heat up slightly, so if the reading seems a bit off it is because of this.

The Steinhart equation mentioned above, specifically the B parameter simplification is as follows:

T = 1/( 1/To + 1/B * ln(R/Ro) )

T  = Temperature in Kelvin
R  = Resistance measured
Ro = Resistance at nominal temperature
B  = Coefficent of the thermistor
To = Nominal temperature in kelvin

I have rearranged it slightly for easier implementation, see the tutorials I’ve referenced at the end of this post for a more in depth look at this.

Instead of adding all the code to handle sampling and conversion in the main loop function, I created a getTemp() function. This will make it easier for me to migrate it to the AI Society code later on.

// Pins
#define THERMISTORPIN 25

// Series resistor value
#define SERIESRESISTOR 10000

// Number of samples to average
#define SAMPLERATE 5
// Nominal resistance at 25C
#define THERMISTORNOMINAL 10000
// Nominal temperature in degrees
#define TEMPERATURENOMINAL 25
// Beta coefficient
#define BCOEFFICIENT 3380

void setup() {}

int getTemp() {
    double thermalSamples[SAMPLERATE];
    double average, kelvin, resistance, celsius;
    int i;

    // Collect SAMPLERATE (default 5) samples
    for (i=0; i<SAMPLERATE; i++) {
        thermalSamples[i] = analogRead(THERMISTORPIN);
        delay(10);
    }
    
    // Calculate the average value of the samples
    average = 0;
    for (i=0; i<SAMPLERATE; i++) {
        average += thermalSamples[i];
    }
    average /= SAMPLERATE;

    // Convert to resistance
    resistance = 4095 / average - 1;
    resistance = SERIESRESISTOR / resistance;

    /*
     * Use Steinhart equation (simplified B parameter equation) to convert resistance to kelvin
     * B param eq: T = 1/( 1/To + 1/B * ln(R/Ro) )
     * T  = Temperature in Kelvin
     * R  = Resistance measured
     * Ro = Resistance at nominal temperature
     * B  = Coefficent of the thermistor
     * To = Nominal temperature in kelvin
     */
    kelvin = resistance/THERMISTORNOMINAL;                              // R/Ro
    kelvin = log(kelvin);                                                                   // ln(R/Ro)
    kelvin = (1.0/BCOEFFICIENT) * kelvin;                                   // 1/B * ln(R/Ro)
    kelvin = (1.0/(TEMPERATURENOMINAL+273.15)) + kelvin;  // 1/To + 1/B * ln(R/Ro)
    kelvin = 1.0/kelvin;                                                                    // 1/( 1/To + 1/B * ln(R/Ro) )

    // Convert Kelvin to Celsius
    celsius = kelvin - 273.15;

    // Send the value back to be displayed
    return celsius;
}

void loop() {
  int temp;
  
  // Call the function to get the temperature in degrees celsius
  temp = getTemp();
  
  // Output the temp to serial
  Serial.print("Temp: ");
  Serial.print(temp);
  Serial.println(" C");
}

Reading the Photo Resistor

Like the thermistor this is also very easy.

//Pins
#define PHOTORESISTORPIN 27

void setup() {}

void loop() {
  double photoVal;
  
  // Read the photo resistor value
  photoVal = analogRead(PHOTORESISTORPIN);
  // Print it to serial 
  Serial.print("Photo resistor reading: ");
  Serial.println(thermVal);
  
  // Wait 2 seconds and do it again
  delay(2000);
}

Sampling the Photo Resistor and converting to a useful value

Similarly to the thermistor, I also put this code into its own function. This time there was nothing special about the implementation.

One thing that you should be aware of is that this value does not represent lux or any other light value reading. To actually read lux you need to build a lookup table that is specific to your circuit and component due to the exponential graph a photo resistor produces. This requires a pre calibrated lux meter and quite a bit of time compiling data. I don’t happen to have a lux meter, and really for the AI society I don’t care about having values like that right now. Mostly the nodes should know, that it’s dark, quite dark, light or very light.

//Pins
#define PHOTORESISTORPIN 27

// Series resistor value
#define SERIESRESISTOR 10000

void setup() {}

int getLight() {
    int ldrSamples[SAMPLERATE];
    int i, light, average;

    // Get a new sample every 10 milliseconds, get SAMPLERATE samples (default 5)
    for (i=0; i<SAMPLERATE; i++) {
        ldrSamples[i] = analogRead(PHOTORESISTORPIN);
        delay(10);
    }

    // Calculate the average
    average = 0;
    for (i=0; i<SAMPLERATE; i++) {
        average += ldrSamples[i];
    }
    average /= SAMPLERATE;

    // Invert
    average = 4095 - average;

    // Map light to a value between 0-100
    light = map(average, 0, 4095, 0, 100);

    return light;
}

void loop() {
  int photoVal;
  
  // Call the function to get the light level
  photoVal = getLight();
  
  // Output the light to serial
  Serial.print("Light: ");
  Serial.println(photoVal);
}

Displaying the values on the OLED

Now, a bit of fun. I didn’t need to add an OLED, but added value is always good!

First things first, you need to install the right library. Following the OLED tutorial linked in the references below, I installed the Adafruit SSD1306 library and their GFX one. I uploaded the 128×64 example to make sure everything was working. If you do this, you will most likely need to change the header file to tell it to use the size 64 OLED. Installing libraries with Deviot is really nice and it tends to find the majority of them quite easily. Select the option “Find/Install Library” in the Deviot menu in Sublime and enter the search term for the library you want to use, select the right one and it installs it. Done!

As mentioned in the introduction I have the SDA connected to GPIO 21 and the SCL connected to GPIO 22.You may need to use the Arduino I2C scanner sketch to find out what the address of your display is, in my case it is 0x3C and it’s likely that yours will be the same if you are using the OLED I linked above. If that doesn’t work then, or if you want to check before hand anyway see here.

As you can see from the image below I’m displaying the temperature, light and a refresh count. Below that is a snippet of the code I’m using to show that.

OLED displaying Temperature, Light and refresh count
Looking sharp
#include <Wire.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET -1
Adafruit_SSD1306 display(OLED_RESET);

int refreshCount;

void setup() {
  refreshCount = 0;
  
  // Initialise the OLED
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.display();

  // Setup the text display values
  display.setTextSize(1);
  display.setTextColor(WHITE);
}

void loop() {
  int temp, photoVal;

  photoVal = getLight();  
  temp = getTemp();
  
  // Display thermal reading
  display.setCursor(20, 20);
  display.print("Temp: ");
  display.print(temp);
  display.println(" C");

  // Display light reading
  display.setCursor(20, 30);
  display.print("Light: ");
  display.println(photoVal);

  // Display update count
  display.setCursor(20, 40);
  display.print("Refreshes: ");
  display.println(refreshCount);

  // Display photo values
  display.display();

  // Clear the display for new values after 2 seconds
  delay(2000);
  display.clearDisplay();

  // Increment refresh counter
  refreshCount++;
}

All together now!

And that is that. My main goal with this little project was to prepare the code I need to have the nodes in the AI society read temperature and light. I hope that others will find this information useful when creating projects with the ESP32 as there are some trip ups when converting arduino based tutorials for it.

I have made the complete code available on GitHub here, so feel free to improve it!

References

Putting this together I used the following for information:

Want to know when I post? Why not subscribe!

Recent Posts

chris.holdt Written by:

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *