Using ChatGPT to create OLED animations w/ ESP32

  1. Why an OLED?
  2. Wiring the SSD 1306 OLED to the ESP32
  3. Installing the relevant libraries
  4. First Example Program #1, Default Demo
  5. Example Demo #2, Using ChatGPT
  6. 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.

Image12

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:

Image17
GNDGoes to (-) negative of your battery
SCLGoes to SCL pin of ESP32.
SDAGoes to SDA pin of ESP32.
VCCGoes to 5V or USB or 3.3V on the ESP32 board.
Image9
Image15
Image20

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.

Image21

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

Image19

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!

Image11
Image7
Image1

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!

Image16

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

Image18

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.

Image14

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:

Image10

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).

Image2

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.

Image22

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.

Image6
Image13

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:

Image8

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.

Image4

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…

Image3
Image5

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. =)

AppLab Bricks open in background with actual brick

Arduino AppLab Bricks → Marketing Garbage or New Powerful Interface?

Arduino Ventuno single board computer - top side

New Ventuno Q Dual Brain Single Board Computer

AppLab Pip Install

How to Add Python Packages in Arduino AppLab (No pip install needed)

Arduino Power Section Schematic

Kit-on-a-Shield Schematic Review

Just how random is the ESP32 random number generator?

Just how random is the ESP32 random number generator?

Leave a Comment