Table of Contents
We have used Liquid Crystal Displays in the DroneBot Workshop many times before, but the one we are working with today has a bit of a twist – it’s a circle! Perfect for creating electronic gauges and special effects.
Introduction
LCD, or Liquid Crystal Displays, are great choices for many applications. They aren’t that power-hungry, they are available in monochrome or full-color models, and they are available in all shapes and sizes.
And speaking about shapes and sizes, the LCD module that we have on the workbench today is circular.
Today we will see how to use this display with both an Arduino and an ESP32. We will also use a pair of them to make some rather spooky animated eyeballs!
GC9A01 IPS Display
The display we are using today is manufactured by Waveshare, there are also similar displays made by other manufacturers.
Waveshare actually has several round LCD modules, I chose the 1.28-inch model as it was readily available on Amazon. You could probably perform the same experiments using a different module, although you may require a different driver.
The module I used has the following specifications:
- Operating voltage: 3.3V/5V
- Interface: SPI
- LCD type: IPS
- Controller: GC9A01
- Resolution: 240(H)RGB x 240(V)
- Display size: Φ32.4mm
- Pixel size: 0.135(H)x0.135(V)mm
- Dimension: 40.4×37.5(mm) Φ37.5(mm)
It’s a pretty inexpensive module, so I picked up a couple to “play” with!
SPI Interface
We have used the SPI interface several times before, it’s a common interface for both displays and memory cards.
This device uses a 4-wire SPI bus configuration, a common configuration that consists of the following four wires:
- CS – This line selects the SPI device. There can be multiple CS lines for multiple devices.
- SCK – The Clock line, which provides timing for the SPI device.
- MOSI – The output from the controlling device.
- MISO – The input to the controlling device.
The display actually only uses three of these wires, MISO is not used as the display is purely an output device.
There are also some additional connections to the display. One of them, DC, sets the display into either Data or Command mode. Another, BL, is a control for the display’s backlight.
The above illustration shows the connections to the display. The Waveshare display can be used with either 3.3 or 5-volt logic, the power supply voltage should match the logic level (although you CAN use a 5-volt supply with 3.3-volt logic).
Similar GC9A01 Modules
There is another common round display that is also built around the GC9A01, you’ll find it on eBay and other common sources.
One difference with this model of GC9A01 is that it has no onboard voltage regulator, so it is limited to working with 3.3-volt logic.
Another difference is simply with the labeling on the display. There are two pins, one labeled SDA and the other labeled SCL. At a glance, you would assume that this is an I2C device, but it isn’t, it’s SPI just like the Waveshare device.
SDA is just the data input (DIN on the Waveshare display) and SCL is the clock input (CLK on the Waveshare device).
This display can be used for the experiments we will be doing with the ESP32, as that is a 3.3-volt logic microcontroller. You would need to use a voltage level converter if you wanted to use one of these with an Arduino Uno.
GC9A01 with Arduino Uno
The Arduino Uno is arguably the most common microcontroller on the planet, certainly for experiments it is. However, it is also quite old and compared to more modern devices its 16-MHz clock is pretty slow.
Nonetheless, it is possible to use the display with an Arduino Uno, especially if your application doesn’t require a lot of display refreshing.
Arduino Hookup
Hooking up the display to the Arduino Uno is pretty simple, and is illustrated here:
Note that as we are using 5-volt logic, we are powering the display with the Arduino’s 5-volt power output.
The Waveshare device comes with a cable for use with the display. Unfortunately, it only has female ends, which would be excellent for a Raspberry Pi (which is also supported) but not too handy for an Arduino Uno. I used short breadboard jumper wires to convert the ends into male ones suitable for the Arduino.
Once you have everything hooked up, you can start coding for the display. There are a few ways to do this, one of them is to grab the sample code that Waveshare provides on their Wiki.
The Waveshare Wiki does provide some information about the display and a bit of sample code for a few common controllers. It’s a reasonable support page, unfortunately, it is the only support that Waveshare provides(I would have liked to see more examples and a tutorial, but I guess I’m spoiled by Adafruit and Sparkfun LOL).
You’ll find a link to download the code at the bottom of the Wiki page. It’s a ZIP file, with samples for the Raspberry Pi, Arduino, and STM32,
After downloading the file, you’ll need to unzip it. Once you do, you’ll find three folders, one for each supported processor.
Open the Arduino folder. Inside you’ll find quite a few folders, one for each display size that Waveshare supports. As I’m using the 1.28-inch model, I selected the LCD_1inch28 folder.
You’ll need to copy the selected folder in its entirety to your Arduino directory or folder.
Once you do that, you can open your Arduino IDE and then navigate to that folder. Inside the folder, there is a sketch file named LCD_1inch28.ino which you will want to open.
When you open the sketch, you’ll be greeted by an error message in your Arduino IDE. The error is that two of the files included in the sketch contain unrecognized characters. The IDE offers the suggestion of fixing these with the “Fix Encoder & Reload” function (in the Tools menu), but that won’t work.
The error just seems to be with a couple of the Chinese characters used in the comments of the sketch. You can just ignore the error, the sketch will compile correctly in spite of it.
The code is pretty basic, I’m not repeating all of it here, as it consists of several files. But we can gather quite a bit of knowledge from the main file, as shown here.
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 |
#include <SPI.h> #include "LCD_Driver.h" #include "GUI_Paint.h" #include "image.h" void setup() { Config_Init(); LCD_Init(); LCD_SetBacklight(1000); Paint_NewImage(LCD_WIDTH, LCD_HEIGHT, 0, BLACK); Paint_Clear(BLACK); Paint_DrawCircle(120,120, 120, BLUE ,DOT_PIXEL_2X2,DRAW_FILL_EMPTY); Paint_DrawLine (120, 0, 120, 12,GREEN ,DOT_PIXEL_4X4,LINE_STYLE_SOLID); Paint_DrawLine (120, 228, 120, 240,GREEN ,DOT_PIXEL_4X4,LINE_STYLE_SOLID); Paint_DrawLine (0, 120, 12, 120,GREEN ,DOT_PIXEL_4X4,LINE_STYLE_SOLID); Paint_DrawLine (228, 120, 240, 120,GREEN ,DOT_PIXEL_4X4,LINE_STYLE_SOLID); Paint_DrawImage(gImage_70X70, 85, 25, 70, 70); Paint_DrawString_CN(56,140, "微雪电子", &Font24CN,BLACK, WHITE); Paint_DrawString_EN(123, 123, "WAVESHARE",&Font16, BLACK, GREEN); Paint_DrawLine (120, 120, 70, 70,YELLOW ,DOT_PIXEL_3X3,LINE_STYLE_SOLID); Paint_DrawLine (120, 120, 176, 64,BLUE ,DOT_PIXEL_3X3,LINE_STYLE_SOLID); Paint_DrawLine (120, 120, 120, 210,RED ,DOT_PIXEL_2X2,LINE_STYLE_SOLID); } void loop() { } /********************************************************************************************************* END FILE *********************************************************************************************************/ |
You can see from the code that after loading some libraries we initialize the display, set its backlight level (you can use PWM on the BL pin to set the level), and paint a new image. We then proceed to draw lines and strings onto the display.
Unfortunately, Waveshare doesn’t offer documentation for this, but you can gather quite a bit of information by reading the LCD_Driver.cpp file, where the functions are somewhat documented.
After uploading the code, you will see the display show a fake “clock”. It’s a static display, but it does illustrate how you can use this with the Waveshare code.
Using the Adafruit_GC9A01A Library
While the Waveshare sample code is OK, there are other libraries that are better documented and may be more suitable for this display.
One of these libraries is the Adafruit_GC9A01A Library, which you can obtain from GitHub.
This library is an extension of the Adafruit GFX library, which itself is one of the most popular display libraries around. Because of this, there is extensive documentation for this library available from Adafruit. This makes the library an excellent choice for those who want to write their own applications.
Grab the library from GitHub in ZIP format and use your Arduino IDE to install it as shown in the accompanying video.
The library has one example sketch called “graphicstest”, which is shown here:
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 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 |
#include "SPI.h" #include "Adafruit_GFX.h" #include "Adafruit_GC9A01A.h" #define TFT_DC 9 #define TFT_CS 10 // Hardware SPI on Feather or other boards Adafruit_GC9A01A tft(TFT_CS, TFT_DC); void setup() { Serial.begin(9600); Serial.println("GC9A01A Test!"); tft.begin(); Serial.println(F("Benchmark Time (microseconds)")); delay(10); Serial.print(F("Screen fill ")); Serial.println(testFillScreen()); delay(500); Serial.print(F("Text ")); Serial.println(testText()); delay(3000); Serial.print(F("Lines ")); Serial.println(testLines(GC9A01A_CYAN)); delay(500); Serial.print(F("Horiz/Vert Lines ")); Serial.println(testFastLines(GC9A01A_RED, GC9A01A_BLUE)); delay(500); Serial.print(F("Rectangles (outline) ")); Serial.println(testRects(GC9A01A_GREEN)); delay(500); Serial.print(F("Rectangles (filled) ")); Serial.println(testFilledRects(GC9A01A_YELLOW, GC9A01A_MAGENTA)); delay(500); Serial.print(F("Circles (filled) ")); Serial.println(testFilledCircles(10, GC9A01A_MAGENTA)); Serial.print(F("Circles (outline) ")); Serial.println(testCircles(10, GC9A01A_WHITE)); delay(500); Serial.print(F("Triangles (outline) ")); Serial.println(testTriangles()); delay(500); Serial.print(F("Triangles (filled) ")); Serial.println(testFilledTriangles()); delay(500); Serial.print(F("Rounded rects (outline) ")); Serial.println(testRoundRects()); delay(500); Serial.print(F("Rounded rects (filled) ")); Serial.println(testFilledRoundRects()); delay(500); Serial.println(F("Done!")); } void loop(void) { for(uint8_t rotation=0; rotation<4; rotation++) { tft.setRotation(rotation); testText(); delay(1000); } } unsigned long testFillScreen() { unsigned long start = micros(); tft.fillScreen(GC9A01A_BLACK); yield(); tft.fillScreen(GC9A01A_RED); yield(); tft.fillScreen(GC9A01A_GREEN); yield(); tft.fillScreen(GC9A01A_BLUE); yield(); tft.fillScreen(GC9A01A_BLACK); yield(); return micros() - start; } unsigned long testText() { tft.fillScreen(GC9A01A_BLACK); unsigned long start = micros(); tft.setCursor(0, 0); tft.setTextColor(GC9A01A_WHITE); tft.setTextSize(1); tft.println("Hello World!"); tft.setTextColor(GC9A01A_YELLOW); tft.setTextSize(2); tft.println(1234.56); tft.setTextColor(GC9A01A_RED); tft.setTextSize(3); tft.println(0xDEADBEEF, HEX); tft.println(); tft.setTextColor(GC9A01A_GREEN); tft.setTextSize(5); tft.println("Groop"); tft.setTextSize(2); tft.println("I implore thee,"); tft.setTextSize(1); tft.println("my foonting turlingdromes."); tft.println("And hooptiously drangle me"); tft.println("with crinkly bindlewurdles,"); tft.println("Or I will rend thee"); tft.println("in the gobberwarts"); tft.println("with my blurglecruncheon,"); tft.println("see if I don't!"); return micros() - start; } unsigned long testLines(uint16_t color) { unsigned long start, t; int x1, y1, x2, y2, w = tft.width(), h = tft.height(); tft.fillScreen(GC9A01A_BLACK); yield(); x1 = y1 = 0; y2 = h - 1; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = w - 1; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); t = micros() - start; // fillScreen doesn't count against timing yield(); tft.fillScreen(GC9A01A_BLACK); yield(); x1 = w - 1; y1 = 0; y2 = h - 1; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = 0; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); t += micros() - start; yield(); tft.fillScreen(GC9A01A_BLACK); yield(); x1 = 0; y1 = h - 1; y2 = 0; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = w - 1; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); t += micros() - start; yield(); tft.fillScreen(GC9A01A_BLACK); yield(); x1 = w - 1; y1 = h - 1; y2 = 0; start = micros(); for(x2=0; x2<w; x2+=6) tft.drawLine(x1, y1, x2, y2, color); x2 = 0; for(y2=0; y2<h; y2+=6) tft.drawLine(x1, y1, x2, y2, color); yield(); return micros() - start; } unsigned long testFastLines(uint16_t color1, uint16_t color2) { unsigned long start; int x, y, w = tft.width(), h = tft.height(); tft.fillScreen(GC9A01A_BLACK); start = micros(); for(y=0; y<h; y+=5) tft.drawFastHLine(0, y, w, color1); for(x=0; x<w; x+=5) tft.drawFastVLine(x, 0, h, color2); return micros() - start; } unsigned long testRects(uint16_t color) { unsigned long start; int n, i, i2, cx = tft.width() / 2, cy = tft.height() / 2; tft.fillScreen(GC9A01A_BLACK); n = min(tft.width(), tft.height()); start = micros(); for(i=2; i<n; i+=6) { i2 = i / 2; tft.drawRect(cx-i2, cy-i2, i, i, color); } return micros() - start; } unsigned long testFilledRects(uint16_t color1, uint16_t color2) { unsigned long start, t = 0; int n, i, i2, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(GC9A01A_BLACK); n = min(tft.width(), tft.height()); for(i=n; i>0; i-=6) { i2 = i / 2; start = micros(); tft.fillRect(cx-i2, cy-i2, i, i, color1); t += micros() - start; // Outlines are not included in timing results tft.drawRect(cx-i2, cy-i2, i, i, color2); yield(); } return t; } unsigned long testFilledCircles(uint8_t radius, uint16_t color) { unsigned long start; int x, y, w = tft.width(), h = tft.height(), r2 = radius * 2; tft.fillScreen(GC9A01A_BLACK); start = micros(); for(x=radius; x<w; x+=r2) { for(y=radius; y<h; y+=r2) { tft.fillCircle(x, y, radius, color); } } return micros() - start; } unsigned long testCircles(uint8_t radius, uint16_t color) { unsigned long start; int x, y, r2 = radius * 2, w = tft.width() + radius, h = tft.height() + radius; // Screen is not cleared for this one -- this is // intentional and does not affect the reported time. start = micros(); for(x=0; x<w; x+=r2) { for(y=0; y<h; y+=r2) { tft.drawCircle(x, y, radius, color); } } return micros() - start; } unsigned long testTriangles() { unsigned long start; int n, i, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(GC9A01A_BLACK); n = min(cx, cy); start = micros(); for(i=0; i<n; i+=5) { tft.drawTriangle( cx , cy - i, // peak cx - i, cy + i, // bottom left cx + i, cy + i, // bottom right tft.color565(i, i, i)); } return micros() - start; } unsigned long testFilledTriangles() { unsigned long start, t = 0; int i, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(GC9A01A_BLACK); start = micros(); for(i=min(cx,cy); i>10; i-=5) { start = micros(); tft.fillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, tft.color565(0, i*10, i*10)); t += micros() - start; tft.drawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, tft.color565(i*10, i*10, 0)); yield(); } return t; } unsigned long testRoundRects() { unsigned long start; int w, i, i2, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(GC9A01A_BLACK); w = min(tft.width(), tft.height()); start = micros(); for(i=0; i<w; i+=6) { i2 = i / 2; tft.drawRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(i, 0, 0)); } return micros() - start; } unsigned long testFilledRoundRects() { unsigned long start; int i, i2, cx = tft.width() / 2 - 1, cy = tft.height() / 2 - 1; tft.fillScreen(GC9A01A_BLACK); start = micros(); for(i=min(tft.width(), tft.height()); i>20; i-=6) { i2 = i / 2; tft.fillRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(0, i, 0)); yield(); } return micros() - start; } |
You will need to edit the sketch on line #5 to change DC from 9 to 7, this reflects the way we wired up our Arduino to the display.
As with the Waveshare sample, this file just prints shapes and text to the display. It is quite an easy sketch to understand, especially with the Adafruit documentation.
The sketch finishes by printing some bizarre text on the display. The text is an excerpt from The Hitchhiker’s Guide to the Galaxy by Douglas Adams, and it’s a sample of Vogon poetry, which is considered to be the third-worst in the Galaxy!
GC9A01 with ESP32
An ESP32 is a much faster microcontroller than the Arduino Uno, and it is ideal for use with our circular LCD module.
I used an ESP32 WROVER module for these experiments, but most other ESP32 modules will work as well.
ESP32 Hookup
Here is the hookup for the ESP32 and the GC9A01 display. As with most ESP32 hookup diagrams, it is important to use the correct GPIO numbers instead of physical pins. The diagram shows the WROVER, so if you are using a different module you’ll need to consult its documentation to ensure that you hook it up properly.
You’ll also notice that we have a potentiometer hooked up to GPIO pin 14. We will be using this in one of our experiments.
Setting up the TFT_eSPI Library
After you get everything hooked up, you’ll need to grab a suitable library.
The TFT_eSPI library is ideal for this, and several other, displays. You can install it through your Arduino IDE Library Manager, just search for “TFT_eSPI”.
You will need to make a couple of modifications in order to get the library working, as it was meant for several types of displays and processors.
Here is what you need to do:
- Use your File Manager (Finder on a Mac) to navigate to your Arduino Libraries folder, which is inside your Arduino folder.
- Look for the TFT_eSPI folder. Open it once you find it.
- Inside the folder, you’ll find several files. Look for the User_Setup.h file.
- Open the User_Setup.h file with a text editor.
Once you have opened up the User_Setup.h file, you will need to make the following edits:
- On line 44 comment out (i.e. add “//” in front of the text) the line defining the ILI9341 driver.
- On line 64 uncomment (i.e. remove the “//”) the line defining the GC9A01 driver.
- On lines 204 through 209 comment out all the SPI definitions for the ILI9341.
- On line 215 set MOSI to 23.
- On line 216 set SCLK to 18.
- On line 217 set CS to 22.
- On line 218 set DC to 16.
- On line 219 set RST to 4.
You can now save the file and are ready to use the library with your GC9A01 display.
Running Demo Code
There is a lot of demo code included with the library. Some of it is intended for other display sizes, but there are a few that you can use with your circular display.
To test out the display, you can use the Colour_Test sketch, found inside the Test and Diagnostic menu item inside the library samples. While this sketch was not made for this display, it is a good way to confirm that you have everything hooked up and configured properly.
A great demo code sample is the Animated_dial sketch, which is found inside the Sprites menu item. This demonstration code will produce a “dial” indicator on the display, along with some simulated “data” (really just a random number generator).
In order to run this sketch, you’ll need to install another library. Install the Tjpeg_Decoder Library from Library Manager. Once you do, the sketch will compile, and you can upload it to your ESP32.
It really looks great on our circular display!
Displaying Potentiometer Value
We can modify the Animated_dial sketch to use the potentiometer input instead of the random number. Make the following changes:
- Add a definition for the potentiometer pin – #define POT_PIN 14
- On line 100 you can change the text from “degrees” to whatever you like (this step is optional).
- Remark out or remove the first line in the Loop, line 119. This is the line that connects to the random number generator.
- Add the following line to the beginning of the Loop:
Uint16_t angle = map(analogRead(POT_PIN),0,4095,0,240)
This will map the value from the potentiometer to a range of 0 to 240, and assign it to the angle variable.
Save the sketch under a new name and upload it to your ESP32. Now try the potentiometer, the meter should track it and display the value.
You can use this technique for other measurements as well.
GC9A01 Animated Eyes
One of my favorite sketches is the Animated Eyes sketch, which displays a pair of very convincing eyeballs that move. Although it will work on a single display, it is more effective if you use two.
Hooking up the Animated Eyes
The first thing we need to do is to hook up a second display. To do this, you connect every wire in parallel with the first display, except for the CS (chip select) line.
On the first display, we will leave CS on GPIO 22. The second display will use GPIO 21.
You can also hook up some optional components to manually control the two “eyeballs”. You’ll need an analog joystick and a couple of momentary contact, normally open pushbutton switches.
Once you have it all hooked up, we can look at some code to make our spooky eyeballs!
Animated Eyes Configuration
The Animated Eyes sketch can be found within the sample files for the TFT_eSPI library, under the “generic” folder. Assuming that you have wired up the second GC9A01 display, you’ll want to use the Animated_Eyes_2 sketch.
This is a HUGE sketch, one of the largest ones I’ve seen in quite some time.
To make life easy the developers have included a configuration file, you’ll need to edit this file if you added the joystick and the pushbuttons.
The config.h file is where you will make your changes:
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 |
// Pin selections here are based on the original Adafruit Learning System // guide for the Teensy 3.x project. Some of these pin numbers don't even // exist on the smaller SAMD M0 & M4 boards, so you may need to make other // selections: // GRAPHICS SETTINGS (appearance of eye) ----------------------------------- // If using a SINGLE EYE, you might want this next line enabled, which // uses a simpler "football-shaped" eye that's left/right symmetrical. // Default shape includes the caruncle, creating distinct left/right eyes. //#define SYMMETRICAL_EYELID // Enable ONE of these #includes -- HUGE graphics tables for various eyes: #include "data/defaultEye.h" // Standard human-ish hazel eye -OR- //#include "data/dragonEye.h" // Slit pupil fiery dragon/demon eye -OR- //#include "data/noScleraEye.h" // Large iris, no sclera -OR- //#include "data/goatEye.h" // Horizontal pupil goat/Krampus eye -OR- //#include "data/newtEye.h" // Eye of newt -OR- //#include "data/terminatorEye.h" // Git to da choppah! //#include "data/catEye.h" // Cartoonish cat (flat "2D" colors) //#include "data/owlEye.h" // Minerva the owl (DISABLE TRACKING) //#include "data/naugaEye.h" // Nauga googly eye (DISABLE TRACKING) //#include "data/doeEye.h" // Cartoon deer eye (DISABLE TRACKING) // DISPLAY HARDWARE SETTINGS (screen type & connections) ------------------- #define TFT_COUNT 2 // Number of screens (1 or 2) #define TFT1_CS 22 // TFT 1 chip select pin (set to -1 to use TFT_eSPI setup) #define TFT2_CS 21 // TFT 2 chip select pin (set to -1 to use TFT_eSPI setup) #define TFT_1_ROT 1 // TFT 1 rotation #define TFT_2_ROT 3 // TFT 2 rotation #define EYE_1_XPOSITION 50 // x shift for eye 1 image on display #define EYE_2_XPOSITION 50 // x shift for eye 2 image on display #define DISPLAY_BACKLIGHT -1 // Pin for backlight control (-1 for none) #define BACKLIGHT_MAX 255 // EYE LIST ---------------------------------------------------------------- #define NUM_EYES 2 // Number of eyes to display (1 or 2) #define BLINK_PIN 27 // Pin for manual blink button (BOTH eyes) #define LH_WINK_PIN 26 // Left wink pin (set to -1 for no pin) #define RH_WINK_PIN 14 // Right wink pin (set to -1 for no pin) // This table contains ONE LINE PER EYE. The table MUST be present with // this name and contain ONE OR MORE lines. Each line contains THREE items: // a pin number for the corresponding TFT/OLED display's SELECT line, a pin // pin number for that eye's "wink" button (or -1 if not used), a screen // rotation value (0-3) and x position offset for that eye. #if (NUM_EYES == 2) eyeInfo_t eyeInfo[] = { { TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // LEFT EYE chip select and wink pins, rotation and offset { TFT2_CS, RH_WINK_PIN, TFT_2_ROT, EYE_2_XPOSITION }, // RIGHT EYE chip select and wink pins, rotation and offset }; #else eyeInfo_t eyeInfo[] = { { TFT1_CS, LH_WINK_PIN, TFT_1_ROT, EYE_1_XPOSITION }, // EYE chip select and wink pins, rotation and offset }; #endif // INPUT SETTINGS (for controlling eye motion) ----------------------------- // JOYSTICK_X_PIN and JOYSTICK_Y_PIN specify analog input pins for manually // controlling the eye with an analog joystick. If set to -1 or if not // defined, the eye will move on its own. // IRIS_PIN speficies an analog input pin for a photocell to make pupils // react to light (or potentiometer for manual control). If set to -1 or // if not defined, the pupils will change on their own. // BLINK_PIN specifies an input pin for a button (to ground) that will // make any/all eyes blink. If set to -1 or if not defined, the eyes will // only blink if AUTOBLINK is defined, or if the eyeInfo[] table above // includes wink button settings for each eye. #define JOYSTICK_X_PIN 33 // Analog pin for eye horiz pos (else auto) #define JOYSTICK_Y_PIN 32 // Analog pin for eye vert position (") #define JOYSTICK_X_FLIP // If defined, reverse stick X axis #define JOYSTICK_Y_FLIP // If defined, reverse stick Y axis #define TRACKING // If defined, eyelid tracks pupil #define AUTOBLINK // If defined, eyes also blink autonomously // #define LIGHT_PIN -1 // Light sensor pin #define LIGHT_CURVE 0.33 // Light sensor adjustment curve #define LIGHT_MIN 0 // Minimum useful reading from light sensor #define LIGHT_MAX 1023 // Maximum useful reading from sensor #define IRIS_SMOOTH // If enabled, filter input from IRIS_PIN #if !defined(IRIS_MIN) // Each eye might have its own MIN/MAX #define IRIS_MIN 90 // Iris size (0-1023) in brightest light #endif #if !defined(IRIS_MAX) #define IRIS_MAX 130 // Iris size (0-1023) in darkest light #endif |
- On line 41 define the BLINK_PIN as 27.
- On line 42 define the LH_WINK_PIN as 26.
- On line 43 define the RH_WINK_PIN as 14.
- On line 75 define JOYSTICK_X_PIN as 33.
- On line 76 define JOYSTICK_Y_PIN as 32.
The file is very well documented, and you can make additional edits as you see fit. Once you have edited the file, upload it to the ESP32.
You should be greeted by a pair of spooky eyeballs, which you can control with the joystick and the switches!
Conclusion
The GC9A01 display would make a great indicator or gauge for your next project. Because it is an IPS display, it is very readable from a wide-angle.
I suggest you pick a couple of these up, they are great fun to play with!
Parts List
Here are some components that you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases. This does not increase the cost to you and is a method of supporting this ad-free website.
COMING SOON!
Resources
Waveshare Wiki – The Waveshare Wiki for the GC9A01 circular display.
Adafruit GC9A01A Library – The Adafruit GFX library for this display.
Adafruit GFX Library – Documentation for programming using the Adafruit GFX library.
Adafruit Animated Eyes – Complete documentation for the Animated Eyes sketch.
Love this gadget. Very appropriate for a Halloween trick! Thank you for these great classes. Be blessed.
That’s a really interesting and clear video and article. I’ve just ordered a couple of these from AliExpress, and I’m now looking forward to playing with them when they arrive. 🙂
Sir i have used gc9a01 with esp32 i have setup all the necessary information in user setup file still i am getting nothing on screen, screen blink once only.
I know this is an old post but for anyone else, I had the same problem because pin 16 was not connected on my Dev board so changed it to an unused pin.
I also using RTC library but not know how to connect it with which pin using TFT-eSPI Library and RTC. lib. Please help me
If you try the following:
Uint16_t angle = map(analogRead(POT_PIN),0,4095,0,240)
DO NOT use “U” for Uint. Use “u” for uint.
Thank you for the great Tutorials. I tested the Round LCD GC9A01 with ESP32 Dev V4, Colour_Test sketch OK. But could not compile Animated_dial sketch due to following error:
E:\arduino-1.8.19\portable\sketchbook_1.8.19\libraries\TJpg_Decoder\src/TJpg_Decoder.h:23:26: fatal error: LittleFS.h: No such file or directory
When searched using Library Manager found only LitteleFS_esp32
Could you please help.
Hello and thanks for this great site! I tried to connect this display to the Arduino Nano but nothing happened after loading the sketch on the Nano. I used the same pinning as described above. Do you have any advice for running this display with the nano? Thank you and best regards
yeah, same here. Changed the DC pin to 7 but nothing displays using the Arduino Nano and the code above. Might be good to update this page to remove that part if it is untested. The github and library file makes repeat claims about its lack of possible functioning.
in any case, still Love DroneBotWorkshop, thanks for the amazing guides 🙂
Sir, Thank you for your work If it wouldnt be too much trouble i know you use the Seeed Studio XIAO can you add to this to show how to connect to this display the XIAO also it appears the Seeed Studio is backordered a expansion shield this display can plug into the XIAO i cant wait till i can figure this thing out Xiao Round Display also i am looking for advance use of the display and also the oled display for the XIAO i have learned about all i can on the U8G2 library and looking for more… Read more »
Great tutorial, like all the others you posted. Thanks for your precious work.
In the experiment with ESP32, I centered the eyes’ positions in the 240×240 displays by adjusting the Y position of the draw area as follows:
file “eye_functions.ino” – line 114, replace existing code
tft.setAddrWindow(eye[e].xposition, 0, 128, 128);
with :
tft.setAddrWindow(eye[e].xposition, 60, 128, 128);
Playing with the arguments a bit, trying to enlarge the eyes, I did manage to get two eyes on a single display using:
tft.setAddrWindow(eye[e].xposition, 90, 256, 256);
Sadly seems that the line numbers already changed alot. Lots of compile errors.
I got the sketch to work but also didn’t manage to get some bigger eyes. Tried this file but it resulted in a blank display only : https://github.com/PaulStoffregen/ST7735_t3/blob/master/examples/uncannyEyes_async_st7789_240x240/graphics/default_large.h
Dear all, I am experiencing problems with dimming my background light. I bought the display from Dongker on Amazon (link) and it is quite similar to the second one shown in the youtube video, although I cannot be certain as I cannot see the specifics. I was under the impression that just connecting the BLK to a PWM-IO (D9 / PB1) on my Atmega328p AU and then simply adjust the duty cycle using analogWrite() of the pin to regulate the background light but it is not working. I do not see any difference in brightness even when turning down the… Read more »
Hi,
I have the same display device, but the pins are
GND
VCC
SCL
SDA
RES
DC
CS
BLK
and I can’t figure out how to wire this to the ESP32, or get the code to run.
Any help greatly appreciated!
When I try the code/project both eyes appear, but one eye is flickering and the non-flickering eye has muted colors. I am using this item, “waveshare 1.28inch Round LCD Display Module 65K RGB Colors 240×240 Resolution with SPI Interface Embedded GC9A01 Driver.” Any help would be appreciated.
Thanks,Ken Fellows
I want to run this code with an raspberry pi pico WH, what is the pinout equivalent for it ?
Thanks
Have you figered it out how to make the eyes bigger?
I’m trying to figure out the same. I’m using the seeds rp2040/1.28 circular display. The animation works great, with no lags, very smooth. Looks great. The problem is that the animation only fills approx half the screen, and I need to fill it. The tutorial I followed his fills the screen. I made adjustments to several variables that I assumed were related to eye/sclera size, but it always scrambles the image. I’m starting to wonder if the the actual image files that determines the image size, that to increase size of the image I need to redo the image files… Read more »
how to combine gc9a01 with ov7670?
My dearest thank you to you, friend. Your guide has got me to advance in something I was stuck for some time! Really appreciate it!