Using ChatGPT to create OLED animations w/ ESP32
- Why an OLED?
- Wiring the SSD 1306 OLED to the ESP32
- Installing the relevant libraries
- First Example Program #1, Default Demo
- Example Demo #2, Using ChatGPT
- Still need help?
The introduction of ChatGPT to the programming world has revolutionized the entire software industry. Life is easier for beginner programmers, yet it also threatens the jobs of many skilled programmers. For me, programming was always secondary to my hardware skills. So, it saves me days of work on all of my client projects.
That said, ChatGPT is still not advanced enough to do all the programming for your project. At least not yet. The current state of AI is like how the internet was in 1989 (for those old rough to remember!). Anyway, it’s time that you start learning to implement ChatGPT as another tool to help you achieve success in your projects.
The following tutorial will demonstrate how to wire up an OLED (in this case the SSD 1306) to an ESP32, and show you how to use ChatGPT to create artistic animations for it.
The last example will show you how things can go disastrously wrong when using AI, and how best to work around it.
Why an OLED?
With an OLED, or liquid crystal display, your project can display sensor data, animated smiling faces, an MP3 player interface, a menu system, or be used to create a handheld video game console.
If you’re like me, an engineer weak in artistic ability… or a skilled artist unable to program your artistic ideas… then this project is for you! I will show you how to use ChatGPT to create tiny little animations that will fit and look nice on your OLED.
So, let’s get started!
Getting Started with Hardware
Knowing that serial OLEDs can get complicated, I specifically choose an OLED that’s controllable via I2C (link to I2C tutorial here). And it’s dirt cheap.
For this project, I chose the
SSD 1306 OLED (Amazon)
- Resolution: 128 x 64
- View angle: > 160°
- Support voltage: 3.3V-5V DC
- Power consumption: 0.04W during normal operation, full screen lit 0.08W
Plenty larger I2C controllable OLEDs available out there, and they will generally work with this tutorial. But, you’ll have to figure out the relevant libraries for your device to make it compatible.

Wiring the SSD 1306 OLED to the ESP32
In terms of wiring, this project can’t get any simpler. You got 4 wires going to your ESP32 – 5V / USB and ground, plus two wires for UART. Finished!
Note: You can use 3.3V instead of 5V / USB if preferred.
Note: SCL and SDA pins may be different on your ESP32 model. Make note of this when it comes time to write the software.
Wiring chart + diagram as follows:

| GND | Goes to (-) negative of your battery |
| SCL | Goes to SCL pin of ESP32. |
| SDA | Goes to SDA pin of ESP32. |
| VCC | Goes to 5V or USB or 3.3V on the ESP32 board. |



Installing the relevant libraries
Now that you got your hardware wired up, let’s get onto the software side of things.
Load up your Arduino IDE, and go to:
Tools => Manage Libraries
and type in SSD 1306 into search.
Download and install the Adafruit library as below. There is no particular reason I chose this over the other libraries. It was the first I tried, and it worked right away without a hitch.

Note: check out the Adafruit_SSD1306 library on Github for more info. I am not going to go into any of its features, as this tutorial primarily focuses on doing animations via ChatGPT.
First Example Program #1, Default Demo
Let’s try out the first built in example program that comes with the library. This will confirm your wiring and let you see its capabilities, and then later we’ll do more advanced examples.
This following example demo program switches between a list of various commands that can be called to draw circles, squares, stars, text, etc.
Go to:
File => Examples => Adafruit SSD1306 => ssd1306_128x32_i2c

Compile and run it. If you see nothing on the screen, either your wiring isn’t correct, or more likely, you will need to make some changes to the demo code.
I’m using the Huzzah32, so I had to add the below fixes to the first demo to get it to work.
At the top of your code, look for this:
#define LOGO_HEIGHT 16 #define LOGO_WIDTH 16
Add these two _PIN defines right above it. Confirm the pin numbers match your ESP32 setup.
// I2C pins for Huzzah32 Feather #define SDA_PIN 23 #define SCL_PIN 22 #define LOGO_HEIGHT 16 #define LOGO_WIDTH 16
Next, scroll down to setup()and look for this line:
Serial.begin(9600);
Change it to this below code:
Serial.begin(115200); // Initialize I2C with the specified pins Wire.begin(SDA_PIN, SCL_PIN);
Compile and run again. You should see various animations on your screen.
Feel free to scroll through the example code of this first example, as it’ll show you how to manually create animations. However, this tutorial will show you how to use ChatGPT to draw your animations without having to be bothered with all that crazy code. Read on!



Example Demo #2, Using ChatGPT
ChatGPT gets you 90% there, but usually needs a bit of finesse to get it completely working. AI models are constantly being updated, so your results may vary dramatically from mine over time.
I start out with a problem statement, and I try to be detailed on everything significant. Typically ChatGPT gets the pins wrong, so I’ve defined the important pins.
Copy this into ChatGPT:
| I’m using the SSD1306 display with the Huzzah32 Feather (ESP32) and Arduino IDE. Write me a program that displays an animation of an archer shooting an arrow. I2C pins should be: #define SDA_PIN 23 #define SCL_PIN 22 |
It gives me code, which I insert into ArduinoIDE to run.
BUT….Upon compiling, I immediately get this error:
| Compilation error: ‘SSD1306_I2C_ADDRESS’ was not declared in this scope |
I copy/paste that error into ChatGPT, and then it generates new “fixed” code. From that point, ChatGPT fails to identify the error. No matter what I tell it, it repeats the bad code with the same error.
So, I’m forced to manually fix the code…. After quickly scanning the code, I see these two lines that are relevant to the error:
code snippet
#define I2C_ADDRESS 0x3C
...
if (!display.begin(SSD1306_I2C_ADDRESS, I2C_ADDRESS)) {
The mistake is now obvious – there is no SSD1306_I2C_ADDRESS defined anywhere. I type this into ChatGPT:
| SSD1306_I2C_ADDRESS is not defined. |
ChatGPT then “realizes” it’s error, and finally gives me code which compiles and runs on the OLED display.
However, it looks nothing like an archer!

The next step is to help it understand better what an archer should look like. So, I type in the below info into ChatGPT:
| That compiled. However, it looks absolutely nothing like an archer. Try again. This time make a stick figure that’s standing and holding the bow outward. One hand holds the bow and the other hand holds the arrow. Have the bow bend as it’s pulled back by the archer, and the bow bends back when the arrow is released. |
I recompile and run the new code it gives me, and the new animation looks like this:
https://drive.google.com/file/d/1iLHiUQeKCdil_YvWRQGRkP07SjKQXKah/view?usp=sharing
It’s certainly way better, but still needed a LOT of work. About 45 minutes of more of finessing the AI (mostly hair pulling), I get this below animation. It’s not quite right, but good enough:
https://drive.google.com/file/d/1i15AuVM5dfA2NqV8X8JHjMnBACnRIaP8/view?usp=sharing

The below code generated my final result.
SSD_1306_archer_demo.ino
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); // No reset pin
// I2C pins for Huzzah32 Feather
#define SDA_PIN 23
#define SCL_PIN 22
int arrowX = 35; // Arrow position
bool arrowFlying = false;
void setup() {
Serial.begin(115200);
while (!Serial) {
delay(10); // Wait for Serial Monitor
}
Serial.println(F("Starting archer animation..."));
Wire.begin(SDA_PIN, SCL_PIN);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.clearDisplay();
}
void drawArcher(int phase) {
// Draw the archer's body
display.drawCircle(20, 25, 5, SSD1306_WHITE); // Head
display.drawLine(20, 30, 20, 50, SSD1306_WHITE); // Body
display.drawLine(20, 50, 10, 63, SSD1306_WHITE); // Left leg
display.drawLine(20, 50, 30, 63, SSD1306_WHITE); // Right leg
// Arms and bow positions based on phase
switch (phase) {
case 1: // Grabbing the string
display.drawLine(20, 35, 30, 40, SSD1306_WHITE); // Right arm holding bow
display.drawLine(20, 35, 25, 40, SSD1306_WHITE); // Left arm grabbing string
break;
case 2: // Pulling the string
display.drawLine(20, 35, 30, 40, SSD1306_WHITE); // Right arm holding bow
display.drawLine(20, 35, 10, 35, SSD1306_WHITE); // Left arm pulling string back
break;
case 3: // Release the string
display.drawLine(20, 35, 30, 40, SSD1306_WHITE); // Right arm holding bow
display.drawLine(20, 35, 10, 35, SSD1306_WHITE); // Left arm stays back
break;
}
}
void drawBow(int phase) {
int bowTopX = 40, bowTopY = 20; // Top of the bow
int bowBottomX = 40, bowBottomY = 60; // Bottom of the bow
int bowMiddleX = 40, bowMiddleY = 40; // Middle of the bow (center)
switch (phase) {
case 1: // Resting bow (no pull)
// Bow is straight, just a simple "D" shape
display.drawLine(bowTopX, bowTopY, bowMiddleX + 8, bowMiddleY - 10, SSD1306_WHITE);
display.drawLine(bowMiddleX + 8, bowMiddleY - 10, bowMiddleX + 8, bowMiddleY + 10, SSD1306_WHITE);
display.drawLine(bowMiddleX + 8, bowMiddleY + 10, bowBottomX, bowBottomY, SSD1306_WHITE);
display.drawLine(bowTopX, bowTopY, bowBottomX, bowBottomY, SSD1306_WHITE); // String
break;
case 2: // Pulled bow (curving)
// Bow is bent, top and bottom move to the left
display.drawLine(bowTopX, bowTopY, bowMiddleX + 12, bowMiddleY - 8, SSD1306_WHITE);
display.drawLine(bowMiddleX + 12, bowMiddleY - 8, bowMiddleX + 12, bowMiddleY + 8, SSD1306_WHITE);
display.drawLine(bowMiddleX + 12, bowMiddleY + 8, bowBottomX, bowBottomY, SSD1306_WHITE);
display.drawLine(bowTopX, bowTopY, bowBottomX, bowBottomY, SSD1306_WHITE); // String
break;
case 3: // Released bow (straightens)
// Bow is back to a straight line, similar to the resting shape
display.drawLine(bowTopX, bowTopY, bowMiddleX + 8, bowMiddleY - 10, SSD1306_WHITE);
display.drawLine(bowMiddleX + 8, bowMiddleY - 10, bowMiddleX + 8, bowMiddleY + 10, SSD1306_WHITE);
display.drawLine(bowMiddleX + 8, bowMiddleY + 10, bowBottomX, bowBottomY, SSD1306_WHITE);
display.drawLine(bowTopX, bowTopY, bowBottomX, bowBottomY, SSD1306_WHITE); // String
break;
}
}
void drawArrow() {
if (arrowFlying) {
display.drawLine(arrowX, 40, arrowX + 10, 40, SSD1306_WHITE); // Arrow shaft
display.drawLine(arrowX, 39, arrowX + 3, 40, SSD1306_WHITE); // Arrowhead top
display.drawLine(arrowX, 41, arrowX + 3, 40, SSD1306_WHITE); // Arrowhead bottom
arrowX += 5;
if (arrowX > SCREEN_WIDTH) {
arrowX = 35;
arrowFlying = false;
}
} else {
display.drawLine(25, 40, 30, 40, SSD1306_WHITE); // Arrow ready on string
}
}
void loop() {
// Phase 1: Grab the string
display.clearDisplay();
drawArcher(1); // Archer grabbing the string
drawBow(1); // Bow resting
drawArrow(); // Arrow ready
display.display();
delay(500);
// Phase 2: Pull the string back
display.clearDisplay();
drawArcher(2); // Archer pulling the string
drawBow(2); // Bow bends
drawArrow(); // Arrow ready
display.display();
delay(500);
// Phase 3: Release the string
display.clearDisplay();
drawArcher(3); // Archer releases the string
drawBow(3); // Bow straightens
arrowFlying = true; // Arrow starts flying
display.display();
delay(200);
// Arrow in flight
while (arrowFlying) {
display.clearDisplay();
drawArcher(3); // Archer stays in release position
drawBow(3); // Bow straight
drawArrow(); // Animate the arrow in flight
display.display();
delay(100);
}
}
Example Demo #3, Running Pikachu
Now we’re going to try a different animation, a running Pikachu.
The purpose of this third example is to resolve the issue when the AI isn’t drawing what you want. In short, a debugging tutorial for when AI completely fails.
Copy / paste the following into ChatGPT. Feel free to change “Pikachu” to whatever you want.
| I’m using the SSD1306 128×32 display with the Huzzah32 Feather (ESP32) and Arduino IDE. Write me a program that displays an animation of Pikachu running across the screen. I2C pins should be: #define SDA_PIN 23 #define SCL_PIN 22 Be sure to define all variables, including for the I2C address. |
In the previous example #2, ChatGPT failed to successfully define the I2C address. I added the very last line to resolve the issue.
The code ran and compiled on the first try. However, it looked absolutely *nothing* like a Pikachu. This is where the first challenge comes in.

So now we go to the finesse stage to see if we can get it to look a little more “kawaii.”
First, let’s see if ChatGPT even knows what Pikachu looks like.
To test, I asked the AI to draw Pikachu for me:

That AI drawing of Pikachu is so bad my liver hurts. If AI can’t even draw a Pikachu, how could it ever take over the world???
I then spent an hour struggling to understand why the AI can’t do such a simple request, eventually determining why it was being so obtuse (retarded).

I humor the AI and let it generate the suggested image, but as a SVG vector graphics file. It then gives me this “Pikachu”. My liver hurts again.

Note: I was able to view the SVG code using this SVG online viewer.
I tried uploading a Pikachu image to use as an example, without telling it that it’s a Pikachu, but it failed to ever generate anything useful for an OLED.
So, I searched the internet for an ASCII image of Pikachu. I then copy / pasted it into ChatGPT. I asked it to redraw it for me to confirm it understood the ASCII art.


It’s not quite a Pikachu, but I’m at my wit’s end and that looks good enough. So, I tell it to give me new code with this ASCII art:

The code it gives me compiles, but the OLED screen just shows up blank. Whenever anything doesn’t work with AI, just ask “are you sure?” I usually just ask before even trying the code. Half the time, ChatGPT will immediately notice its mistake. I must include “the wiring is correct”, as otherwise it will just tell me the wiring is wrong.

It then gave me the following code:
pikachu_demo.ino
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Define I2C pins
#define SDA_PIN 23
#define SCL_PIN 22
// Define the I2C address of the SSD1306 display
#define SCREEN_ADDRESS 0x3C
// Define display dimensions
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
// Create an instance of the display
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// Function to draw Pikachu's face
void drawPikachu(bool eyesOpen) {
display.clearDisplay();
// Head (big circle)
display.drawCircle(64, 16, 12, WHITE);
// Ears
display.drawLine(52, 5, 58, 11, WHITE); // Left ear
display.drawLine(76, 5, 70, 11, WHITE); // Right ear
// Cheeks
display.fillCircle(54, 20, 2, WHITE); // Left cheek
display.fillCircle(74, 20, 2, WHITE); // Right cheek
// Mouth
display.drawLine(62, 22, 66, 22, WHITE);
display.drawPixel(63, 23, WHITE);
display.drawPixel(65, 23, WHITE);
// Eyes (Blinking Animation)
if (eyesOpen) {
display.fillCircle(60, 14, 2, WHITE); // Left eye open
display.fillCircle(68, 14, 2, WHITE); // Right eye open
} else {
display.drawLine(58, 14, 62, 14, WHITE); // Left eye closed
display.drawLine(66, 14, 70, 14, WHITE); // Right eye closed
}
display.display();
}
void setup() {
Wire.begin(SDA_PIN, SCL_PIN);
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;);
}
display.display();
delay(2000); // Pause for 2 seconds
}
void loop() {
drawPikachu(true); // Eyes open
delay(2000);
drawPikachu(false); // Blink
delay(300);
drawPikachu(true); // Eyes open again
delay(2000);
}
The following two images are the animation resulting from the above code. Not really a running Pikachu, but if you pull out enough of your hair, you’ll eventually get something close to what you want! I give up for now…
As an after-thought, I can probably trick the AI to draw me a squirrel, but do it in such a way that it’ll end up looking like a Pikachu. Well, that’s for another ‘jail breaking AI’ tutorial…


Still need help?
If you want to use other OLEDs with your ESP32, and the above tutorial isn’t working, write in to us and maybe we’ll expand this tutorial with your suggestion. =)