Friday, March 31, 2017

ESP8266, BME280 and OLED Displays

Now that we've successfully read temperature, humidity, and air pressure from a BME280, we turn to the problem of making this data available without having to use a serial monitor. Our first solution is to use a tiny OLED display.

In this tutorial we will:

  1. Install the Correct Library
  2. Add the Display to the Breadboard
  3. Test the Display
  4. Show Sensor Data in the Display


Install the Correct Library
A wide variety of tiny OLED displays are available, and for this tutorial we'll be using a 0.96 inch, monochrome, 128 x 64 pixel screen that is driven by the SSD1306, and that has an I2C interface. As it goes, this device has I2C address 0x3c. There are several libraries for the SSD1306, and we will use the one entitled "ESP8266 and ESP32 Oled Driver for SSD1306 display by Daniel Eichhorn, Fabrice Weinberg". To install this library:

  1. In the Arduino IDE, choose the menu Sketch | Include Library | Manage Libraries...
  2. Enter SSD1306, click the right one, and install the latest version of that library (currently version 3.2.7).


Add the Display to the Breadboard
Along the top of the display there will be four pins that read something like

  • GND, VCC, SCL, and SDA
  • or GND, VDD, SCK, and SDA

First disconnect the BME280, then wire the display as follows:

  • ESP8266 <--> OLED
  • 3V3 <--> VCC
  • GND <--> GND
  • SCL <--> D5
  • SDA <--> D6

If there isn't room on the breadboard to add the display, either use a full-size breadboard or use a second half-sized breadboard!


Test the Display
Ignoring the BME280 for a minute, the following sketch tests the features we will be using when we display data from the BME280.

The library works as follows:

  1. We initialize it in the setup() function, and we flip the screen vertically. The end result is to create a memory buffer
  2. Drawing commands will be written to this buffer - they will NOT be immediately visible
  3. When we are ready, write the buffer to the display using the command:
    display.display();

We will be using the following commands in the final sketch:

  • display.setFont(fontName);
  • display.drawString(x, y, message);
  • display.display();
  • display.clear();
There are many other commands in this library - check out the sample code!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
 * Simple-OLED-Test
 * 
 * By: Mike Klepper
 * Date: 29 March 2017
 * 
 * This program demonstrates a VERY few simple commands from the
 * "ESP8266 Oled driver library for SSD1306 display" library 
 * by Daniel Eichhorn, Fabrice Weinberg.
 * 
 * Connections:
 * Display VCC --> NodeMCU 3V3
 * Display GND --> NodeMCU GND
 * Display SCL --> NodeMCU D5
 * Display SDA --> NodeMCU D6
 */

#include "SSD1306.h"

SSD1306 display(0x3c, D6, D5);

void setup() 
{
  display.init();
  display.flipScreenVertically();
}

void loop() 
{
  display.clear();
  
  display.drawRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
  
  display.setFont(ArialMT_Plain_16);
  display.drawString(20, 7, "Hello, world!");
  
  display.setFont(ArialMT_Plain_10);
  display.drawString(8, 30, "DISPLAY_WIDTH = " + String(DISPLAY_WIDTH));
  display.drawString(8, 45, "DISPLAY_HEIGHT = " + String(DISPLAY_HEIGHT));
  
  display.display();

  yield();
  delay(2000);
}

18Include the library we downloaded earlier
20Create a display with I2C address 0x3c, the SDA connected to D6, and the SCL connected to D5
24Initialize the display (this creates the buffer)
25Change the orientation of the display
30Clear the buffer
32Draw a border around the screen - notice that the library makes two constants available to us: DISPLAY_WIDTH and DISPLAY_HEIGHT
34Set the font we'll be using.
35Print "Hello, world!" at x = 20 and y = 7
37Change font to ArialMT_Plain_10
38 - 39Print the screen width - note that this value is available in the constants DISPLAY_WIDTH and DISPLAY_HEIGHT
41Copy the buffer to the physical display
43-44Wait for a bit before doing it all again

The library includes three fonts: ArialMT_Plain_10, ArialMT_Plain_16, and ArialMT_Plain_24. Additional fonts can be created using the tools found at:


Show Sensor Data on the Display
Now we will show the temperature, humidity and barometric pressure on the display. To make this a little more interesting, we will alternate showing British units and metric units.

You would think that pulling this off would be simply a matter combining the above code with the sketch from the last tutorial - after all, there is no pin overlap, right? As it goes, restoring the jumper wires as follows WILL NOT WORK!

The ESP8266 has exactly one I2C bus, so the two devices (the BME280 and the OLED display) must be on that one I2C bus! Here's what the connections will look like on a full-sized breadboard.

Once the connections are correct, the code is very easy!

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/**
 * BME280-OLED
 * 
 * By: Mike Klepper
 * Date: 31 March 2017
 * 
 * This program reads data from the BMP280 and shows it on a 
 * SSD1306 OLED display. It will alternte between British and 
 * metric units.
 * 
 * See blog post on patriot-geek.blogspot.com 
 * for connections.
 */

#include "Wire.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_BME280.h"
#include "SSD1306.h"

const float SEA_LEVEL_PRESSURE_HPA = 1013.25;
const int DELAY = 3000;
const int STARTUP_DELAY = 500;


Adafruit_BME280 bme;

SSD1306 display(0x3c, D6, D5);

void setup() 
{
  Serial.begin(115200);
  
  if(!bme.begin())
  {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1)
    {
        yield();
        delay(DELAY);
    }
  }
  delay(STARTUP_DELAY);

  display.init();
  display.flipScreenVertically();

}

void loop() 
{
  float tempC = bme.readTemperature();
  float humidity = bme.readHumidity();
  float pressurePascals = bme.readPressure();

  // Print to serial monitor
  printToSerial(tempC, humidity, pressurePascals);

  // Display data on screen in British units
  drawWithBritishUnits(tempC, humidity, pressurePascals);
  yield();
  delay(DELAY);

  // Display data on screen in metric units
  drawWithMetricUnits(tempC, humidity, pressurePascals);
  yield();
  delay(DELAY);
}


void drawWithBritishUnits(float tempC, float humidity, float pressurePascals)
{
  float tempF = 9.0/5.0 * tempC + 32.0;
  float pressureInchesOfMercury = 0.000295299830714 * pressurePascals;
  
  display.clear();
  
  display.drawRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
  
  display.setFont(ArialMT_Plain_16);
  display.drawString(35, 3, "BME280");
  
  display.setFont(ArialMT_Plain_10);
  display.drawString(5, 22, "Temperature = " + String(tempF) + " *F");
  display.drawString(5, 35, "Humidity = " + String(humidity) + "%");
  display.drawString(5, 48, "Pressure = " + String(pressureInchesOfMercury) + " inHg");
  
  display.display();
}

void drawWithMetricUnits(float tempC, float humidity, float pressurePascals)
{
  float pressureHectoPascals = pressurePascals / 100.0;
  
  display.clear();
  
  display.drawRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
  
  display.setFont(ArialMT_Plain_16);
  display.drawString(35, 3, "BME280");
  
  display.setFont(ArialMT_Plain_10);
  display.drawString(5, 22, "Temperature = " + String(tempC) + " *C");
  display.drawString(5, 35, "Humidity = " + String(humidity) + "%");
  display.drawString(5, 48, "Pressure = " + String(pressureHectoPascals) + " h,Pa");
  
  display.display();
}

void printToSerial(float tempC, float humidity, float pressurePascals)
{
    // Temperature
    float tempF = 9.0/5.0 * tempC + 32.0;

    Serial.println("Temperature:");
    printValueAndUnits(tempC, "*C");
    printValueAndUnits(tempF, "*F");
    //printValueAndUnits(tempC, "°C");
    //printValueAndUnits(tempF, "°F");
    Serial.println("");

    // Barometric pressure
    float pressureHectoPascals = pressurePascals / 100.0;
    float pressureInchesOfMercury = 0.000295299830714 * pressurePascals;

    Serial.println("Pressure:");
    printValueAndUnits(pressurePascals, "Pa");
    printValueAndUnits(pressureHectoPascals, "hPa");
    printValueAndUnits(pressureInchesOfMercury, "inHg");
    Serial.println("");

    // Humidity
    Serial.println("Humidity:");
    printValueAndUnits(humidity, "%");
    Serial.println("");

    // Approximate altitude
    float altitudeMeters = bme.readAltitude(SEA_LEVEL_PRESSURE_HPA);
    float altitudeFeet = 3.28 * altitudeMeters;
    
    Serial.println("Approx. Altitude:");
    printValueAndUnits(altitudeMeters, "m");
    printValueAndUnits(altitudeFeet, "ft");
    Serial.println();
}

void printValueAndUnits(float value, String units)
{
    Serial.print("     ");
    Serial.print(value);
    Serial.print(" ");
    Serial.println(units);
}

This completes our attempt to display sensor data using hardware. Although we were successful, we did not use the WiFi capabilities of the ESP8266! That's what the next tutorial will be about!

12 comments:

  1. Hi,
    Thank you for the great guide !
    Could you please tel me if all the wires need to be joined together or the 3.3v and GND can be connected separately for the BME280 and the oled display.

    ReplyDelete
    Replies
    1. Hi MSG,
      Try using the "power rail" approach like in the last Fritzig shows. It works great and is quite easy!
      - MMK

      Delete
  2. Hmmm... why do I have -190°C and -1500 m and 100% humidity?
    Thanks.

    ReplyDelete
    Replies
    1. Hello,

      Check the following:
      1. Wiring
      2. Use latest version of Adafruit libraries
      3. Check the BME280's I2C address

      Hope this helps!
      - Mike K

      Delete
  3. Hi Mike,
    your tutorials are excellent, easy to understand and very instructive.
    I miss the sequel to the tutorial ESP8266 with BME280 and OLED display with using the WiFi capabilities.

    ReplyDelete
    Replies
    1. Hi sunnyboy66,

      Sorry for the late response! Here's the one for exposing the BME280 data through wifi:
      https://patriot-geek.blogspot.com/2017/09/exposing-data-in-captive-portal.html

      Hope this helps!

      Mike

      Delete
  4. I have a similar problem to 'Unknown' - weird values from the BME280, but only when the OLED display is enabled (it can remain physically connected without any issues)

    To try and work out where the problem is, I used a "#define oled" and wrapped everything related to the OLED in "if(oled){ ... }".

    With "#define oled 0", I get sensible values (t= 20.29 *C, h= 33.70 % and pressure= 1018.08 hPa
    With "#define oled 1", I get t= -143.81 *C, h= 100% and pressure= 1135.39 hPa

    I2C addresses are 0x76 for the BME280 and 0x3C for the OLED.

    Does anybody know what the problem is?

    ReplyDelete
  5. hi I get this error

    C:\Users\worth\Documents\Arduino\temperature sensors\test_useless\test_useless.ino: In function 'void drawWithBritishUnits(float, float, float)':
    test_useless:77:26: error: 'DISPLAY_WIDTH' was not declared in this scope
    display.drawRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
    ^
    test_useless:77:41: error: 'DISPLAY_HEIGHT' was not declared in this scope
    display.drawRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
    ^
    C:\Users\worth\Documents\Arduino\temperature sensors\test_useless\test_useless.ino: In function 'void drawWithMetricUnits(float, float, float)':
    test_useless:96:26: error: 'DISPLAY_WIDTH' was not declared in this scope
    display.drawRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
    ^
    test_useless:96:41: error: 'DISPLAY_HEIGHT' was not declared in this scope
    display.drawRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT);
    ^
    exit status 1
    'DISPLAY_WIDTH' was not declared in this scope

    ReplyDelete
    Replies
    1. Hello!

      There has been a change in one of the libraries - DISPLAY_WIDTH and DISPLAY_HEIGHT used to be defined, but isn't anymore!

      Try adding the following lines near the top:
      const int DISPLAY_WIDTH = 128;
      const int DISPLAY_HEIGHT = 64;

      It is also possible to revert to an older version of the library.

      Hope this helps!

      Delete
  6. Hi
    thanks for the nice tutorial.
    I'm getting the error: 'DISPLAY_WIDTH' was not declared in this scope
    Is something wrong with the library or do you know how to solve this problem?
    Thanks

    ReplyDelete
    Replies
    1. Glad you got it to work! There was a change in the library and that's what's causing the error!

      Delete