Adding A Display to Your Project
In the past, the memory available in an standard Arduino Uno (2K bytes) was too small to add high quality displays. With the arrival of the ESP32 and the Raspberry Pi Pico this has all changed. These microcontrollers have around 100 times that RAM - typically around 200K bytes. So we are integrating low-cost OLED displays into many of our CoderDojo projects!
There are four main types of display technology that use for small microcontrollers.
- LED - Light Emitting Diode - these are often low-resolution but have larger area. The start with single color displays but there are also multi-color LED strips and LED matrix displays.
- OLED - Organic Light Emitting Diode - small low-cost and high-contrast monochrome displays used in watches.
- LCD - Liquid Crystal Display - many of these are monochrome displays that must have precise power to get consistent contrast.
- TFT - Thin Film Transistor - a type of LCD that are used for larger color screens.
240X240 TFT Display
Full Color LCD TFT Display SPI HD 65K Module ST7735
Before you begin to use these displays, there are a few things to understand to use them effectively. Based on your project needs, you can use this knowledge to find the right solution for you.
A framebuffer is a copy of the display information that is resident within the RAM of the microcontroller. It must be as large as the display. For a 128X64 monochrome display this would be 128 * 64 = 8192 bits or 1,024 bytes (1K). A full color 240X240 TFT which uses 8 bits for red, green and blue would require 3 X 8 X 240 X 240 = 1,382,400 bits or 172K bytes.
Not all all displays need framebuffers. Some displays can take a series of vector drawing commands such as "draw line" and "draw text". These displays can be useful if you don't have a large amount of RAM.
Display Chip Types
There are two common versions:
- SSD1306 - This is the most popular and versatile chip. It can be used to drive many different types and sizes of OLEDs. The SSD1306 can be used with both the simple 4 wire I2C interface as well as the slightly faster 7 wire SPI interface. These devices have only four wires labeled VCC, GND, SDA and SCL. SDA is for data and SCL is for the clock.
- SH1106 - This is less popular version and supports the 4-wire I2C interface.
- ST7735 - This chip is used on larger color TFT displays.
- ILI9341 - This chip is used on larger TDF displays.
You can usually look on the back of the display device and see what type of check controls your OLED display.
In addition to the multiple types of displays and types of chips driving the displays, there are also two options on how you want to communicate between your microcontroller and the display.
- I2C - This is the most common type and only requires two wires beside power and ground. Us this as your default unless you display does not support it. The original specification of I2C had a communication speed of 100K bits per second. Many systems can be run at 400K per second.
- SPI - This is a more complex interface and requires up to seven wires. Some devices only support SPI interfaces. SPI typically runs around 1M bits/second although it can go up to 10M bits/second in some applications. SPI is ideal when you want to transfer a large amount of display data to a screen quickly.
Basic Draw Functions
For our beginning labs we will just do some basic drawing. We will start out with just four functions:
- Initialize the display framebuffer memory with the right object class initialization
- Fill the framebuffer will zeros which are black pixels with the oled.fill(0)
- Draw white text in the framebuffer memory with the oled.text("Hello World!", 40, 10)
- Send the entire framebuffer to the display over the bus with the oled.show() function.
Initializing the Framebuffer
Let's assume that we have a four wire OLED that uses the popular SSD1306 chip with 128X64 pixels. We call our oled "oled" using the following line:
|oled.fill(0)||Fill the display with white or black||0=black and 1=white|
|oled.text("Hello",||Draw text||String, x (horizontal from left edge) and y (vertical from the top)Example: Draw "Hello World" 40 over and 10 down. oled.text("Hello World!", 40, 10)|
|show||Show the display||Send the current frame buffer to the display. You must do this after you make and changes to the Framebuffer.|
The full program would look like this:
1 2 3 4 5
This would display the following:
Full list of Drawing Functions
Every drawing library might have slightly different functions. But we can quickly see the functions that we want by using the dir() function on the SSD1306_I2C class.
1 2 3 4 5
The following are relevant for the SSD1306_I2C display.
The display has (0,0) in the upper left corner. X is horizontal (width) and Y is vertical (height). The state is 0=off (black) and 1=on (white).
|fill(state)||Fill||Fill with black (0) or white(1)|
|fill_rect||Fill a rectangle|
|hline(x1, x2, y, state)||Draw a horizontal line||Draw a horizontal line at the top of the display: oled.hline(0, 0, 127, 1)|
|invert||invert the display|
|line(x1,y1,x2,y2)||draw a line at any angle||Horizontal oled.line(0,0, 127, 63, 1)|
|pixel||Draw a single point on the screen|
|rect||Draw an empty rectangle|
|scroll||Scroll the display|
|text||Write text at a point|
|vline||Draw a Vertical Line||oled.vline(width - 1, 0, height - 1, 1) # right edge|
|init_display||Initialize the display|
|write_cmd||Write a command to the display|
|show||Update the display from the frame buffer|
Pros: Simple four wire interface
Example: 128X64 pixel monochrome displays
Types of Displays