Saturday, June 1, 2024

Displays for your DIY Projects

 


There are dozens, if not hundreds of different small displays that you can add to your DIY project, from basic text displays to screens that can display images or even video.  This article is going to cover just a few of these as shown in the related YouTube Video: Add a Display to Your DIY Project

Even within the display types that I will cover, there are often variations, such as display/text/backlight colors for monochrome displays, different dimensions or resolution, etc. Some displays even support touch.  I'll include a couple of these and will mention that touch is supported, but I won't be going into detail about how to wire/integrate touch in this article.  If you are interested in using the touch interface for your display, see the links at the end of this article where I will share a project where I implemented touch on one of these displays.

Parts Used or Shown


Here are the different displays I'll be covering.  Full specs for each are covered below.

Display

Resolution

Interface

LCD 1602

16 x 2 (characters)

I2C

SSD1306

124 x 64

I2C

MAX7219 7-Segment

8 chars of 7-segment

SPI

MAX7219 Matrix

32 x 8

SPI

ST7789

240 x 240

SPI

ILI9341

320 x 240

SPI



Unless otherwise noted, I'll be using an ESP32 Mini.  While many of the displays may work fine with an ESP8266, if you plan on implementing animation or images on your display, the ESP8266 may struggle. See the documentation for the particular display and library to verify that the ESP8266 is supported.

For most of my testing, I'll be using a custom DIY breakout board that supports both the Wemo D1 Mini (ESP8266) and the ESP32 Mini.  
If you want to know more about building your own custom DIY breakout board for bench testing or prototyping your projects, see the links at the end of this article.  Of course for these displays, you could certainly use a standard breadboard, other type of breakout board, or in many cases, wire directly to the ESP pins.

Other Misc Parts

Item or Part

Notes

Dupont Jumpers

 

Breadboard Jumpers

 

5V 10A Power supply

Sufficient amps to support ESP32 and display.  In most cases 2-3A should be fine unless using multiple displays.

DHT22 Temp/Humidity Sensor

 

 

 



Using Multiple Displays - I2C and SPI


Five Displays - One ESP32

Since most (but certainly not all) small displays use either I2C or SPI for communication and control, it is possible to drive multiple displays from a single ESP32.  In the above photo, I am controlling two I2C and three SPI displays from one ESP32.  I won't be going into detail about using multiple displays with a single controller, but here's a bit of info if you do want to add more than one display to your project.

Inter-Integrated Circuit (I2C or IIC): 

I2C devices can share a single bus on the ESP controller.  It generally only uses two data wires (besides power/ground).  These are the data (SDA) and clock (SCL) pins.  Each device on an I2C bus must have a unique 7-bit I2C address, but in theory, you could connect up to 128 devices using just two pins from the ESP.  And since the ESP32 has two I2C busses, that means you could have 256 devices connected using only four pins from the ESP32!  In practice, you will run into other limitations well before reaching these large numbers.  First there is the processing power required, but there are other restrictions as well, such as a maximum capacitance on an I2C bus of 400pF.  However, you could easily run a few displays using a single controller... as long as each devices has (or can be changed to) a unique I2C address.

Serial Peripheral Interface (SPI):

SPI devices generally use four wires (again, in addition to power/ground).  These are Master IN Slave OUT (MISO), Master Out Slave IN (MOSI), Serial Clock (SCL) and Chip Select (CS).  The first three (MISO, MOSI and SCL) can be shared by multiple devices, but each device needs its own unique CS pin.  Up to three devices can be used on a single SPI bus.  Again, the ESP32 has two hardware SPI busses, so you can theoretically add six SPI devices or displays... but other limitations may apply.  Also note that many SPI displays may have additional pins that may or may not be required, such as reset, LED, backlight pins, etc.  SPI devices can be a little more difficult to wire and configure due to the varying number and types of wires, but they are generally faster than I2C devices so most displays that are capable of advanced animations, graphics, etc. are usually SPI devices.

For a good comparison of the differences between I2C and SPI, you can take a look at this:  The Differences Between I2C and SPI

Again, I won't be covering the wiring/configuration of multiple displays in this article, but it is still good to have an understanding of the pros and cons of the different types of displays.

Display Options


For each display that I'm going to cover, I'll provide the basic connections and short examples of how they are configured/used in both ESPHome and Arduino/C++.  For these examples, I'll just be showing basic numeric/text output.  For those displays that support graphics, animations, shapes, graphs, etc. I will leave links to both the ESPHome and Arduino library info at the end of each display section.  

For ESPHome and Home Assistant, I am using the most current version at the time of publication (2024.5.x).  For Arduino, I am using IDE 1.8.16 and all libraries are the latest versions.

My code examples will also be using simple 'static' text or numbers.  See the section near the end for how to adapt these examples to display data from Home Assistant or other connected sensors.

While the ESP32 generally allows you to use any appropriate pin and define it in software, I'll generally be using the 'default' pins for the primary I2C and SPI busses unless otherwise noted:

I2C:
  • SDA (GPIO21)
  • SCL (GPIO22)
SPI:
  • MOSI (GPIO23)
  • MISO (GPIO19)
  • CLK (GPIO18)
  • CS (GPIO5)
Note that there are many variations of each type shown.  Your particular model may have different pinouts, labels and/or voltage requirements.  Always check the documentation/pinout for your particular board!

LCD1602 (I2C)



The LCD1602 is one of the easiest and most common displays to use if you only need to show basic text or numeric values.

General Specs
  • Display Type:  LCD
  • Resolution:  2 rows of 16 characters (16 x 2)
  • Colors:  2 - fixed (text + backlight)
  • Graphics Support: No 
  • Animations: Limited (like scrolling), based on library used
  • Touch Capable: No
  • Bus: I2C (with alternate 16-pin, 8-bit header - not covered here)
  • Operating Voltage:  5V
  • Current Draw: 160mA (blue backlight... other colors may vary)

While the colors on the display are "fixed" (cannot be changed), various colors of text on different color backgrounds can be found.  You can also find models that have different numbers of rows/columns for character display.  They all work in the same manner.

Pinout / Wiring

You will generally find two variations on these displays (although others are also possible).


The "older" model contained a daughterboard with the LCD1602 and a variable potentiometer for adjusting the backlight/contrast.  The "newer" model has the chip soldered directly on the board.  Note that the I2C address may vary based on the version (check your board's documentation).  I'm using the 'new' model and it has an I2C address of 0x27.  Regardless, the pinout/wiring is the same.

Click to enlarge

This is pretty standard wiring for an I2C device.  SDA and SCL are connected to the standard I2C pins on the ESP32 (GPIO21 and 22), although you can use other pins if you define those in ESPHome/Arduino.  This display requires 5V.  Due to the potential maximum current draw, you may wish to power the display directly off of the 5V power supply and not the 5V pin on the ESP32.

ESPHome

i2c:
  sda: 21   #optional if using default pins
  scl: 22
  
display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    lambda: |-
      it.print(0, 0, "Resinchem Tech");
      it.print(3, 1, "Subscribe!");

Since the LCD1602 uses I2C, i2c: must be included in the ESPHome node.  If you are using the standard pins, you can probably omit the pin definitions.  If you include scan: true under the i2c: section, you can probably also omit the address under the display definition.  But it never hurts to explicitly include these parameters. 

Then a display is defined, in this case using a platform of lcd_pcf8574 (for my board, yours may be different.  Check the documentation linked below).  Then the dimension and I2C address are specified.

For most types of display in ESPHome, data is output to the display by using "lambda".  Lambdas are short segments of Arduino/C++ code, so it can be helpful if you have some basic understanding of Arduino formatting and print statements, but the ESPHome documentation on the display component contains numerous examples of both text and graphics.  For my simple example, I'm simply outputting static text.  The first line starts at the top left position (x,y.... 0,0) and outputs 'Resinchem Tech'.  I then move the cursor to the 4th column, second line and output  'Subscribe!'.

This is a very simple example and I'll show some info on displaying data from Home Assistant towards the end of this article.  But please refer to the following for more information:


Arduino/C++

#include <Wire.h>
#include <LiquidCrystal_I2C.h> 

LiquidCrystal_I2C lcd1602(0x27, 16, 2); //I2C address, cols, rows

void setup() {
...
lcd1602.init();
lcd1602.backlight();
...
}

void lcdDisplayExample() {
  lcd1602.clear();  //clear the display
  lcd1602.setCursor(0, 0);
  lcd1602.print("Resinchem Tech");
  lcd1602.setCursor(1, 4);
  lcd1602.print("Subscribe!)";
}

This is just the basic snippets of Arduino code to display static text on the LCD display.  The wire library is needed for I2C and the display uses the LiquidCrystal_I2C library.  An instance of the display is created in the declarations, it is initialized and the backlight enabled in the Setup and then text or data is sent to the display by positioning the cursor and using a print statement.  Many more details and examples can be found in the documentation for the library:

 
Testing - results are the same with ESPHome or Arduino

SSD1306 (I2C)



The SSD1306 is another I2C device, which makes connecting it relatively simple.  But this device has a few additional capabilities over the LCD1306.  Do note that the SSD1306 is also available as an SPI device, but I'm using the I2C version here.

General Specs
  • Display Type:  OLED
  • Size/Resolution:  0.96" / 128 x 64 (other dimensions available)
  • Colors:  1 or /2 - fixed (segmented)
  • Graphics Support: Yes - basic 
  • Animations: Yes
  • Touch Capable: No
  • Bus: I2C 
  • Operating Voltage:  3.3v or 5V
  • Current Draw: 80mA (fully lit)
The most common of these types of displays will show blue on a black background, although you can find ones with white displays.  In the case of a dual-color display (yellow/blue) like above, the display is divided into two ranges.


Anything printed in the top two rows (8 pixels) will appear in yellow while everything below that will appear in blue.  This is fixed and cannot be changed.

The display can show images, graphs and other basic shapes.  It will struggle with displaying photo-realistic images due to the lack of full color and limited resolution.  So while it provides some graphics capabilities, it is probably more ideal for a mix of text and some simply images or icons:


Pinout / Wiring
You can find SSD1306 displays that utilize SPI wiring and these may have a few more capabilities at the expense of slightly more complicated wiring.  But here, I am focusing on the I2C version.  Its pinouts and wiring is nearly identical to the LCD1602 (or really any I2C device):


Again, this wiring example uses the standard default I2C pins on the ESP32... GPIO21 for data (SDA) and GPIO22 for clock (SCL).  You can use other pins, but need to indicate the pins used in ESPHome or your Arduino code.  The display can also be powered via the 3.3V pin instead of the 5V pin if desired.

ESPHome

i2c:
  sda: 21   #optional if using default pins
  scl: 22

font:
  - file: "gfonts://Rubik@500"
    id: font1
    size: 15
  - file: "gfonts://Rubik@500"
    id: font2
    size: 18

display:
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    address: 0x3C
    lambda: |-
      it.print(0, 0, id(font1), "Resinchem Tech");
      it.print(0, 30, id(font2), "SSD1306 I2C");    

The ESPHome code is very similar to the LCD1602 above, but with a few significant differences.  For the display, you specify a model in addition to the platform instead of dimensions.  All of the supported models are listed in the documentation.

In addition, you must include a font for anything that text you wish to display using the it.print statement.  Fonts are defined under a font: section and can be fonts installed locally or you can use any Google font and it will be pulled in automatically.  I'm defining two fonts, which are technically the same font but different sizes.  Each font is given an id.  The the font to use for a given print statement is included in the statement itself.  You can find more info on using fonts in the ESPHome documentation.

Output from the ESPHome code

ESPHome SSD1306 Documentation


Arduino/C++

#include <Wire.h>
#include <Adafruit_GFX.h>         
#include <Adafruit_SSD1306.h>     

//Width 128, Height 64, I2C, Reset Pin -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, RESET);

void setup() {
...
  display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDR);  //Addr: 0x3c
  display.clearDisplay();
...
}

void ssd1603DisplayExample () {
  display.clearDisplay();
  display.setTextColor(WHITE);
  display.setTextSize(1);
  display.setCursor(0,0);
  display.println("Resinchem Tech");
  display.setTextSize(3);
  display.setCursor(0,30);
  display.println("SSD1306  I2C");
  display.display();
}

Two libraries are imported for the SSD1306 in this case.  One is for graphics and the other is the actual SSD1306 library.  Both are needed in this case.  Other libraries are also available.

The code is similar to the LCD1602, but when creating the display instance, there is a parameter to define a RESET pin.  Since the SSD1306 is also available using SPI (some models contain both options), there may be a reset pin that can be connected to a GPIO pin to allow a reset of the OLED display.  Since my board does not have a reset pin, this value is just set to -1.

The board is initialized in the setup routine, providing the exact board type and I2C address.  I'm optionally clearing the display here as well.

The output is the same as the LCD1602 except that you must include a text color.  Even though the board is monochrome (or dual color by zone), you still have to include a text color in the output.  This could be moved up to the setup function as well.  Since the color really doesn't matter in the case of this board, I just use WHITE.  You also set a text size here (in lieu of a font).  Then just as before, you specify a cursor position and use .println to output the data. 

Finally, you must call the .display() function to actually refresh the screen and show the output.



MAX7219 Seven Segment (SPI)





The first of two MAX7219 displays is a 7-segment LED display.

General Specs
  • Display type: Common Cathode LED
  • Eight individual segments, although other numbers available
  • Single Color (various colors available)
  • Graphics: No
  • Animations: No (other than basic scrolling)
  • Touch Capable: No
  • SPI
  • Operating voltage: 3.3V or 5V
  • Current Draw: Peak 150 mA per digit (@5V all segments lit/red)
Obviously, the primary purpose of these displays is to display numeric values.  While you can also display letters and symbols, the limitation of only 7 segments means that not all letters or symbols will render well.

Pinout / Wiring
While the SPI communication protocol calls for four wires, this particular device only uses three.  I'm using the standard SPI pins for the ESP32.


The standard MISO/SDI pin is labeled DIN on this particular model.  The display may also be powered by the 3.3V pin if desired.  Also note that the opposite end of the board contains pinouts for daisy-chaining multiple displays together, where the data out (DOUT) pin would connect to the data in (DIN) on the next board.

ESPHome

spi:
  clk_pin: 18
  mosi_pin: 23

display:
  - platform: max7219
    cs_pin: 5
    num_chips: 1  # defaults to 1
    intensity: 10 # 0-15 /brightness
    lambda: |-
      it.print("0123.4567");

The SPI integration needs to be included for SPI to be used with any other entities.  Unlike I2C, you do need to provide the clock pin used on the ESP32.  Either one of MOSI or MISO (or both) need to be provided based on the pinout of the device.  A device may only use one of these pins if the communication is one-way.

A display is then defined.  For this board, a platform of max7219 is selected.  You must provide the chip select pin (CS) used.  The num_chips line is optional and will default to '1' if omitted.  This is used to indicate the total number of MAX7219 chips in use when you are daisy-chaining multiple displays together.  The intensity is also optional, but sets the brightness level of the display.  This will default to 15 (max brightness) if omitted.

Just like all the other displays, a lambda is used to output data to the display using the it.print function.  Unlike other displays, there are no other settings, like cursor position or font.  Any printable ASCII character can be sent and the string will be left justified on the display by default. You can include a decimal and it will turn on the 'dot' for the digit that precedes the decimal.


As mentioned, it is possible to output any printable ASCII character, but the results can be mixed.  Some characters will render better than others.  This is simply the limit of 7-segments.





Arduino/C++

#include <SPI.h>
#include "LedController.hpp"

LedController<1,1> max7; //x-segments,y-segments

void setup() {
..
  max7=LedController<1,1>(23,18,5); //MISO,CLK,CS
  max7.setIntensity(4);
  max7.clearMatrix();
..
}

void displaySample() {
  max7.clearMatrix();
  max7.setChar(0,7,'0',false);
  max7.setChar(0,6,'1',false);
  max7.setChar(0,5,'2',false);
  max7.setChar(0,4,'3',true);
  max7.setChar(0,3,'4',false);
  max7.setChar(0,2,'5',false);
  max7.setChar(0,1,'6',false);
  max7.setChar(0,0,'7',false);
}

Since this is an SPI display, the SPI library must be included.  The library used for this particular controller is the LEDController.hpp.  An instance is created with the number of rows and columns (<1,1>) of displays.  Note that this refers to the number of MAX7219 chips in use... not the number of digits or segments.  Since my display of eight digits only uses a single chip, then the declaration is 1,1.

The display is initialized in setup, specifying the MISO, CLK and CS pins used.  Optionally, you can set an intensity or brightness level of the display from 0-15.

To output values to the display, there are various functions available in the library.  I'm using a simple method of outputting a character value to a specific digit.  The positions are zero based and start with the rightmost digit.  So the setChar function includes the row (I only have one row/MAX7219, so this first value is always zero) and then the 'column' or digit position.  The boolean at the end indicates whether the decimal dot should be on or off for that digit.


MAX7219 Dot Matrix (SPI)



This display is controlled by the same MAX7219 chip as the previous 7-segment display except it uses a dot-matrix layout.  Even though it is the same chip, different libraries will be needed since the output to 7-segments vs. the dot matrix is significantly different.

General Specs
  • Display type: Common Cathode LED
  • Resolution: 32 x 8 (each segment consists of an 8 x 8 matrix)
  • Single Color (other colors available)
  • Graphics: Basic - just want can be drawn within the 32 x 8 matrix
  • Animations: Basic... scrolling, etc.
  • Touch Capable: No
  • SPI
  • Operating voltage: 5V
  • Current Draw: Peak 1A (all LEDs lit red max brightness)
Similar to the 7-segment display, multiple matrices may be daisy-chained together.  But unlike the 7-segment display, each 8 x8 matrix section has its own MAX7219 driver.  This means that you can have independent sections and these sections can be daisy-chained in both rows and columns.

Pinout/Wiring

The pinout and wiring for the matrix is identical to the 7-segment version.  The MISO pin is labeled DIN on the board.  But unlike the 7-segment display, this one can only be powered by 5V.  In fact, due to the potential current draw of the matrix, especially if you will have a large number of LEDs simultaneously lit or run in maximum brightness, it would probably be a good idea to run 5V in parallel to the matrix and ESP32... much like you would do with larger LED installations.

ESPHome

spi:
  clk_pin: 18
  mosi_pin: 23
  
font:
  - file: "fonts/pixelmix.ttf"
    id: font1
    size: 8

display:
  - platform: max7219digit
    cs_pin: 5
    num_chips: 4  # defaults to 4
    intensity: 2  # 0 - 16
    lambda |-
      it.print(0, 0, id(font1), "2:15 P");

The ESPHome code is very similar to the 7-segment version.  But in this case, you must include one or more fonts, as your text can be displayed using these different fonts.  Similar to the SSD1602, you can import Google fonts directly without downloading.  Although I had difficulty getting Google fonts to work correctly (they'd print starting in the middle of the display with the bottom of the text cut off).  Alternatively, you can download a font file and place it a /fonts folder within your ESPHome directory.


I found that the pixelmix font worked well with the display.  When using a local font file, you just specify the path to the file as shown in the code above.

Using the pixelmix.ttf font

A nice touch with the ESPHome library is that if your text is too long to fit on the display, it will automatically scroll the text.  So, for example, if I change my output statement to:

it.print(0, 0, id(font1), "Resinchem Tech");
 
The text is too long to fit, so the display automatically scrolls left.  You can also set options to control the speed of scrolling, whether it repeats, how long it pauses or disable it altogether.  See the documentation linked below for more details.


Arduino/C++

#include <SPI.h>
#include <MD_MAX72xx.h>
#include <MD_Parola.h>  

#define HDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
MD_Parola max7219Matrix = MD_Parola(HDWARE_TYPE, 23, 18, 5, MAX_DEVICES);

void setup() {
...
  max7219Matrix.begin();
  max7219Matrix.setIntensity(2);
...
}

void displayMatrix() {
  max7219Matrix.displayClear();
  max7219Matrix.setTextAlignment(PA_CENTER);
  max7219Matrix.print("2:15 P");
}

While there are numerous libraries available for the MAX7219 matrix, I'm opting to use the MD_Parola library as it offers a number of advantageous features, including using different fonts and providing numerous animations.  This does rely on the MD_MAX72xx library for the hardware interface so it needs to be included as well.

An instance of the display is created by specifying the hardware type (see the library documentation), along with the MISO, CLK and CS pins and the total number of MAX7219 chips.  For my devices, this is four separate 8 x 8 sections, each with its own chip, so I have 4 devices.

A simply begin() statement will initialize the display in the setup section and I also opt to set the intensity or brightness here as well.

To output text, you can specify the alignment (e.g. left, center, right) and then use the basic .print function.  

But I did mention the ability to scroll text.  The implementation is just a bit different here.

void setup() {
...
  max7219Matrix.begin();
  max7219Matrix.setIntensity(2);
  max7219Matrix.displayClear();
  max7219Matrix.displayText("Resinchem Tech", PA_CENTER, 35,       1000, PA_SCROLL_LEFT, PA_SCROLL_LEFT);
...
}

In this case, I'm defining the text within a displayText() function.  This function takes the actual text string, alignment, speed and dwell times, then two effects... one for the 'in' effect and another for the 'out' effect.  Mixing and matching the in and out can create some interesting effects!


The speed is actually a delay between frame updates, so the higher the number the slower the effect.  The dwell time is the time (in milliseconds)that the effect will pause before it is considered "complete".  Most commonly this is used when you are looping an effect or switching effects.

To actually animate the text, you must include the animation function in your main loop.

void loop() {
... 
  if (max7219Matrix.displayAnimate()) {
    max7219Matrix.displayReset();
  }
...
}

For the animation to run, the displayAnimate() function must be repeatedly called (hence including it in the main loop).  It will return 'true' when the animation completes.  If you wish the animation to restart once complete or change to a different animation, you can call the displayReset() function.  If you want to animation to only run once and then stop, simply omit the displayReset(). 

The library offers many other features as well.  See the library documentation for full details and many other examples.


ST7789 PCI



The final two displays offer more features, but as a result are a bit more difficult to implement and use.  The first one is the ST7790 IPS Touch Display.  

General Specs
  • Display Type:  IPS
  • Size/Resolution:  1.3" / 240 x 240 (model used)
  • Colors:  RGB Full Color
  • Orientation: Portrait or Landscape
  • Graphics Support: Yes 
  • Animations: Yes
  • Touch Capable: Yes (TFT)
  • Bus: SPI 
  • Operating Voltage:  3.3v ONLY
  • Current Draw: Backlight ~40 mA
I won't be going into the touch capabilities for this display (or the following ILI9341), but I do have a separate video and blog article that demonstrates using touch with ESPHome.  A link to that information will be included at the end of this article.

Pinout/Wiring


At first glance, the presence of the SCL and SDA pins makes it appear that this is an I2C device.  But it is not I2C!  The lack of a required CS (chip select) pin also makes it appear that this is not an SPI device.  But it does use SPI for communication. This is just an example of some SPI devices that use non-standard labeling or alternate pinouts for SPI.  And if it is not clearly explained in the documentation for the device (and good luck with that!), you may have to do a little research or trial-and-error to figure out the proper GPIO pins to use on the ESP32.


This is the initial wiring diagram that I started with for ESPHome.  I say initial because it wasn't the only combination I tried.

ESPHome

Despite spending hours trying, I simply could not get this display to work with ESPHome.  ESPHome has a display platform for the ST7789V, which is actually a different model where the display is part of the ESP32:


This platform also states that it supports various models, including one for "custom":


Despite trying numerous combinations of models, GPIO pins and other settings, I could not get the display to work.  In fact, numerous times when I tried to use the ST7789V platform, it sent the ESP32 into a boot loop, requiring that I reflash it via USB.  But there was a note in the documentation for this platform:


So I tried using the ILI9XXX platform instead.


This platform supports various models as well, but not the ST7789 (without the "V").  Again, I tried all sorts of models, combinations and GPIO pins without success.  I did get the backlit to illuminate and the board didn't boot loop with this platform, but I simply could not get anything to display.  Maybe support for this particular ST7789 board will be added in the future and if I do get it to work, I will update this section of the blog.  If you have managed to get this particular model to work with ESPHome, let me know down in the comment!

And I do know that the display itself is good, because it worked just fine with Arduino code.


Arduino/C++

#include <SPI.h>
#include <TFT_eSPI.h> 

TFT_eSPI st7789 = TFT_eSPI(); //Pins in userSetup.h

void setup() {
...
  st7789.init();
  st7789.setRotation(1);
  st7789.fillScreen(TFT_BLUE);
...
}

void displayTest() {
  st7789.fillScreen(TFT_BLACK);
  st7789.setTextSize(2);
  st7789.setCursor(0, 20, 4);
  st7789.setTextColor(TFT_YELLOW);
  st7789.println("Basement");
  st7789.setTextColor(TFT_CYAN);
  st7789.println("71.4 F");
  st7789.setTextColor(TFT_RED);
  st7789.println("56% RH");
}

While other libraries are available, I'm using the TFT_eSPI library.  This library contains many features and options, supports many types of SPI displays and offers an easy-to-use function for outputting text.

Do note that the library includes a User_Setup.h file where you specify the model of your board, GPIO pins and numerous other options.  Full documentation is included in the header file and in the library documentation.

An instance is created (again, pins aren't specified here but in the header file) and the display is initialized in the setup function.  Optionally, the display can be rotated 90°, 180° or 270° by setting a rotation value.  I'm also setting the backlight color to blue just to let me know the board is initialized.  This is optional.

To output simple text, the screen can be filled with a color (basically setting the backlight or background).  As a full RGB display, the background can be any color and the 'forecolor' or text a different color.  I'm simply setting the background to black.  A text size is set and the size shown will be somewhat dependent on the font used.

Setting the cursor position includes setting the x,y coordinates, but can optionally include a font.  The library comes with a number of enumerated fonts, but you can also import and use additional fonts.  For my example, I'm using built-in font "4".

I can then specify a color for the text and use a normal println statement to output text.  The nice thing about using println is that it contains a newline character, meaning I don't need to specify cursor coordinates for the next line (although I certainly just use print and set the cursor to control where the next line appears).  I then change the color for each subsequent line and output the text.

This is a very, very basic example and the library contains many more options for text, graphics and animations.



ILI9341 SPI


The largest and most capable display of all that I'm including here is the ILI9341.  Not only does this have full graphics and animation capabilities, it also has an SD card slot for holding images or other files.  In addition, it supports multi-point touch.  I won't be covering touch or use of the SD card here, but you can find more information in the library and documentation links below.  In addition, I do have a separate video and blog article of using the display for touch in ESPHome and links to those will be at the end of this article.

General Specs
  • Display Type:  IPS
  • Size/Resolution:  2.8" / 240 x 320 (model used)
  • Colors:  RGB Full Color
  • Orientation: Portrait or Landscape
  • Graphics Support: Yes 
  • Animations: Yes
  • Touch Capable: Yes (TFT)
  • SD Card Reader
  • Bus: SPI 
  • Operating Voltage:  3.3v or 5V
  • Current Draw: Backlight ~80 mA

Pinout/Wiring


The ILI9341 has more pins than any of the other displays as well.  But the top five pins (labeled as touch) are for enabling touch capabilities and I won't be using those here.  Luckily this board is labeled with the "standard" four SPI pins... MOSI, MISO, CLK and CS.  But there are additional pins as well, including that DC (data/command), reset and LCD for controlling the backlight brightness.

Note that there are four additional SPI pins on the opposite end of the board.  This is for interfacing and controlling the SD card reader.  Again, I won't be using those but documentation can be found in the linked libraries below.

Click to enlarge

These are the GPIO pins I am using, which include the standard hardware SPI pins for the ESP32.  Of course other pins can be used if defined in the code.  I'm using 3.3V to power the display.

ESPHome

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23
  miso_pin: GPIO19
  
font:
  - file: "gfonts://Rubik@500"
    id: font30
    size: 30
  - file: "gfonts://Rubik@500"
    id: font40
    size: 40

color:
  - id: color_green
    red: 0%
    green: 100%
    blue: 0%
  - id: color_orange
    red: 100%
    green: 65%
    blue: 0%
  - id: color_cyan
    red: 0%
    green: 100%
    blue: 100%

display:
  - platform: ili9xxx
    model: ili9341
    cs_pin: GPIO5
    dc_pin: GPIO32
    reset_pin: GPIO22
    rotation: 270
    lambda: |-
      it.print(10, 10, id(font30), id(color_orange),
               TextAlign::TOP_LEFT, "Resinchem Tech");
      it.print(160, 60, id(font40), id(color_cyan), 
               TextAlign::TOP_CENTER, "Basement");
      it.print(160, 100, id(font40), id(color_green), 
               TextAlign::TOP_CENTER, "71.4° F");

Once again, the SPI integration is included.  Since this display actually has a MISO pin, it is included in the SPI definition.  Then much like other displays that support fonts and colors, a handful of fonts and colors are defined.  In this case, I'm using Google fonts so no font files need to be installed within ESPHome.

The display is defined much like the others, including the specific pins used.  I'm rotating the display 270°, but it can also be rotated 90° or 180°.  The actual text output is very similar to the others, specifying a starting x,y position, but I'm also including a font and color and then finally the actual text.


And while I said I would only be covering basic text output in this article, I did want to show both the capabilities of the ILI9341 and how easy it is to display an image.


In this case, I'm displaying an image and overlaying that with text.  I only had to make the following changes to the ESPHome code above to make this work:

image:
  - file: "images/chicken.png"
    id: chicken
    resize: 104x200
    type: RGB24
    use_transparency: True


display:
  - platform: ili9xxx
    #...
    lambda: |-
      it.image(100, 0, id(chicken));
      it.print(160, 10, id(font30), id(color_orange),
               TextAlign::TOP_CENTER, "Resinchem Tech");

I simply placed a .png file in an /images folder within ESPHome.  The image can either be resized to fit before copying to ESPHome, or you can use a resize parameter when defining the image to fit the display.  Then a simple it.image function with the x,y coordinates and the image ID is used to output the image to the display.  Text can then be overlaid on top of this image.  More info and additional features for using the ILI9341 in ESPHome can be found in the following documentation.


Arduino

#include <SPI.h>
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

//Pins: CS, DC, MOSI, CLK, RST, MISO
Adafruit_ILI9341 ili9341 = Adafruit_ILI9341(33, 5, 23, 18, 13, 19);

void setup() {
...
  pinMode(21, OUTPUT);  //backlight
  digitalWrite(21, HIGH);
  ili9341.begin();
  ili9341.setRotation(3);
...
}
  
void simpleText() {
  ili9341.fillScreen(ILI9341_BLACK);
  ili9341.setCursor(40, 10);
  ili9341.setTextSize(3);
  ili9341.setTextColor(ILI9341_ORANGE);
  ili9341.print("Resinchem Tech");
  ili9341.setCursor(70, 70);
  ili9341.setTextSize(4);
  ili9341.setTextColor(ILI9341_CYAN);
  ili9341.print("Basement");
  ili9341.setCursor(80, 110);
  ili9341.setTextSize(5);
  ili9341.setTextColor(ILI9341_GREEN);
  ili9341.print("71.4 F");
}

The Arduino code is very similar to the ST7789.  In fact, I could have used the same TFT_eSPI library here as it also supports the ILI9341.  But I opted to just use the Adafruit ILI9341 library for this example.

In the instantiation of the display, the SPI GPIO pins used are defined, along with the pins for DC and Reset.  In the setup, I'm simply defining a digital output for the LED (backlight) pin and setting it to high.  Optionally, you could setup a PWM pin and have the ability to change the display brightness.  I'm also setting the rotation to 270°.

The actual output is once again very similar to other displays.  For each line, I set the cursor position, text size and color and then output the text.

This is about as basic as it can be.  The library contains many more advanced functions for not only outputting text, but shapes, images and more.



Displaying Data from Home Assistant (or other local sensors)


All the above examples simply show outputting static or fixed text to the various displays.  It is likely that you will want to output variable data, like sensor readings, to the display.  Luckily, this is pretty easy to do, especially with ESPHome. For my examples here, I'll be using a simple DHT22 temperature and humidity sensor:


I'll only show temperature, but humidity and most other types of sensors will work in a similar manner.


ESPHome

Note that nearly every display uses a 'print' statement to output information to the display.  

display.print(10, 20, "71.4° F");

The example I'll show here should pretty much work for all the different displays.  First, your sensor should be defined in the ESPHome node for the display and have an ID:

#This example is for a sensor connected to the ESP32
sensor: 
  - platform: dht
    model: DHT22
    pin: GPIO17
    temperature: 
      id: bsmttemp
    update_interval: 1s 

Or if you are importing a sensor/entity from Home Assistant:

#This example is importing sensor from Home Assistant
sensor:
  - platform: homeassistant
    id: bsmttemp
    entity_id: sensor.basement_temp

To show this temperature on the display, simply substitute the print with printf and the fixed value with a format and the state of the sensor, using its ID.

printf(10, 20, "%.1f °F", id(bsmttemp).state); 

In this example, the previous fixed value of "71.4" is replaced with a two-part format/value parameter:

'format statement', state/value of sensor

The format statement can consist of any static text (like °F), but then determines how the value from the sensor should be displayed.  In this case, the '%' says the following is a format string and the value should be formatted as a floating point number with one decimal.  Recall that this is part of a lambda and lambdas are really just C++ statements.  You can do a search for the printf statement to see how to format values.  I'll leave a link to one resource to get you started below.  The ESPHome documentation also has some basic information.

For the second half of this statement, you just pass in the state of the sensor or entity using its ID.  Any time the sensor value or state changes, the display will update automatically.


Arduino

If the sensor or other entity is connected directly to the same ESP32 as the display, the process of updating the display with the sensor data is pretty easy.  You would just take a sensor reading at some specified interval and pass that value to a routine that would update the display.

If previously using a print statement, you can likely just substitute a printf statement similar to the ESPHome example above.  However, check the documentation for the particular display library in use.  It may or may not support a 'printf'.  But most display libraries will have functions specifically designed for showing numeric values and these may offer easier or more flexible alternatives for displaying numbers or other types of variables

But what about remote sensors or entities, like those from Home Assistant?  Well, the first step is to get the value from Home Assistant into your Arduino code.  Probably the easiest and most common method of doing this is via MQTT.  If you are not familiar with MQTT, I have a series of videos that cover MQTT basics, how to use it in Home Assistant and Arduino and how to have them 'talk' to each other and pass values back and forth.


The gist is that you add MQTT to your Arduino device and subscribe to a topic.  Home Assistant will then publish a message to that topic when the value of a sensor or entity changes (either via an MQTT entity, templated sensor or automation).  When your Arduino code receives a new message, it stores that in a variable of an appropriate type and this value can be used to update the display in the same manner as above.

Wrap up and Additional Thoughts


This article (and related video) barely scratches the surface on both the types of displays and the various features and functions of many of those displays.  Many of the more 'advanced' full color displays can provide the ability to show moving graphics, charts, graphs and more.  Adding a display to your DIY project can really take that project to the next level.  Please let me know your thoughts, how you might have used a display in one of your own project, or of course if you have any questions.  Thanks for reading!

Links and Additional Information



Supporting this blog and related YouTube channel


It takes significant time and effort (and occasionally expense) to create these articles and videos.  If you'd like to support future content on this blog and the related YouTube channel, or just say thanks for something that helped you out, you can consider buying me a one-off cup of coffee or two at:



No comments:

Post a Comment

To help eliminate spam and to keep all conversations civil, submitted comments are moderated. Therefore your post may not show up immediately. Please be patient as most reviews are completed within a few hours.