ESP32-CAM with OV7725 VGA Camera and GC9A01 Display

Typically the AI-thinker ESP32-CAM is paired to the Omnivision OV2640 2MP camera, however, I received a message via the contact page asking about using the Omnivision OV7725 VGA camera module so I bought one from eBay to try out.

This is a short write up of how to display the picture from an OV7725 on to a round GC9A01 TFT display; including all wiring and codes.


Hardware

There are three main components to this set up; the ESP32-CAM module with OV7725 camera sensor, a 1.28inch GC9A01 round TFT display, and an FTDI adapter for programming. Please note that for this display to work, the SD card cannot be used.

The pink flying lead is used to ground GPIO 0, to put the ESP32 in to boot mode. The green flying lead resets the device when shorted to the pin labelled GND/R.

Pinout – GC9A01 Display:

  • GC9A01 GND -> ESP32-CAM GND
  • GC9A01 VCC -> ESP32-CAM 3.3V
  • GC9A01 SCL-> ESP32-CAM GPIO 14
  • GC9A01 SDA -> ESP32-CAM GPIO 13
  • GC9A01 RES -> ESP32-CAM GPIO 4
  • GC9A01 DC -> ESP32-CAM GPIO 12
  • GC9A01 CS -> ESP32-CAM GPIO 2
  • GC9A01 BLK -> [not connected]

Pinout – FTDI Adapter:

  • ESP32-CAM GND -> FTDI GND
  • ESP32-CAM 5V -> FTDI VCC (note: the voltage jumper on the adapter should be to the 5V pin)
  • ESP32-CAM UOR -> FTDI TX
  • ESP32-CAM UOT -> FTDI RX

Software

As with the previous experiments with displays and ESP32’s, this program uses the TFT_eSPI library by Bodmer. Therefore, the code is done in two parts.

Part 1: User_Setup.h

It’s imperative that the User_Setup.h file is configured correctly. Any fault here will cause the screen to fail. If you’re fault finding with a display, the majority of the time it’s down to the User_Setup.h file. As me how I know.

The below needs to be copied, pasted and saved within the TFT_eSPI library folder. Please note that I’ve cut away some of the more niche hidden features. Also, if you change the SPI_FREQUENCY to 40000000 then you’ll increase the frame rate to around 22 FPS!

#define USER_SETUP_INFO "User_Setup"
#define GC9A01_DRIVER
#define TFT_SDA_READ      // This option is for ESP32 ONLY, tested with ST7789 and GC9A01 display only
#define TFT_HEIGHT 240 // GC9A01 240 x 240
#define TFT_MOSI 13 // In some display driver board, it might be written as "SDA" and so on.	
#define TFT_SCLK 14
#define TFT_CS   2  // Chip select control pin
#define TFT_DC   12  // Data Command control pin
#define TFT_RST  4  // Reset pin (could connect to Arduino RESET pin)
//#define TFT_BL   22  // LED back-light
#define TOUCH_CS 21     // Chip select pin (T_CS) of touch screen
#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
#define LOAD_FONT6  // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
#define LOAD_FONT7  // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
#define LOAD_FONT8  // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
#define SMOOTH_FONT
#define SPI_FREQUENCY  27000000
#define SPI_READ_FREQUENCY  20000000
#define SPI_TOUCH_FREQUENCY  2500000
#define USE_HSPI_PORT
#define SUPPORT_TRANSACTIONS

Part 2: Main Sketch

The main sketch is split in to three parts: the global definitions, setup, and loop. The code is on the left and a short description of each part is on the right.

#include "esp_camera.h"
#include <TFT_eSPI.h> // Hardware-specific library
#include <SPI.h>

#define CAMERA_MODEL_AI_THINKER

#define PWDN_GPIO_NUM     32
#define RESET_GPIO_NUM    -1
#define XCLK_GPIO_NUM      0
#define SIOD_GPIO_NUM     26
#define SIOC_GPIO_NUM     27
#define Y9_GPIO_NUM       35
#define Y8_GPIO_NUM       34
#define Y7_GPIO_NUM       39
#define Y6_GPIO_NUM       36
#define Y5_GPIO_NUM       21
#define Y4_GPIO_NUM       19
#define Y3_GPIO_NUM       18
#define Y2_GPIO_NUM        5
#define VSYNC_GPIO_NUM    25
#define HREF_GPIO_NUM     23
#define PCLK_GPIO_NUM     22

TFT_eSPI tft = TFT_eSPI();       // Invoke custom library
TFT_eSprite spr = TFT_eSprite(&tft);

camera_config_t config;

uint16_t *scr;

This section of code includes all the libraries necessary for the display and the camera, followed with all of the pin definitions.

The global variables and constructors are below. A sprite is created to speed up the display.

void setup() {

  Serial.begin(115200);

  tft.init();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  scr = (uint16_t*)spr.createSprite(240, 240);

  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sscb_sda = SIOD_GPIO_NUM;
  config.pin_sscb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;
  config.frame_size = FRAMESIZE_240X240;
  config.pixel_format = PIXFORMAT_RGB565; 
  config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location = CAMERA_FB_IN_PSRAM;    
  config.jpeg_quality = 12;
  config.fb_count = 1;

  esp_err_t err = esp_camera_init(&config);

  sensor_t * s = esp_camera_sensor_get();
  s->set_brightness(s, -1);     // -2 to 2   //changed from 0 to -1
  s->set_contrast(s, -2);       // -2 to 2   //changed from 0 to -2
  s->set_saturation(s, 0);     // -2 to 2
  s->set_special_effect(s, 0); // 0 to 6 (0 - No Effect, 1 - Negative, 2 - Grayscale, 3 - Red Tint, 4 - Green Tint, 5 - Blue Tint, 6 - Sepia)
  s->set_whitebal(s, 1);       // 0 = disable , 1 = enable
  s->set_awb_gain(s, 1);       // 0 = disable , 1 = enable    
  s->set_wb_mode(s, 0);        // 0 to 4 - if awb_gain enabled (0 - Auto, 1 - Sunny, 2 - Cloudy, 3 - Office, 4 - Home)
  s->set_exposure_ctrl(s, 1);  // 0 = disable , 1 = enable    
  s->set_aec2(s, 0);           // 0 = disable , 1 = enable
  s->set_ae_level(s, 0);       // -2 to 2    
  s->set_aec_value(s, 300);    // 0 to 1200
  s->set_gain_ctrl(s, 1);      // 0 = disable , 1 = enable
  s->set_agc_gain(s, 0);       // 0 to 30
  s->set_gainceiling(s, (gainceiling_t)0);  // 0 to 6
  s->set_bpc(s, 0);            // 0 = disable , 1 = enable
  s->set_wpc(s, 1);            // 0 = disable , 1 = enable    
  s->set_raw_gma(s, 1);        // 0 = disable , 1 = enable
  s->set_lenc(s, 1);           // 0 = disable , 1 = enable
  s->set_hmirror(s, 0);        // 0 = disable , 1 = enable
  s->set_vflip(s, 0);          // 0 = disable , 1 = enable
  s->set_dcw(s, 1);            // 0 = disable , 1 = enable
  s->set_colorbar(s, 0);       // 0 = disable , 1 = enable

  tft.setTextColor(TFT_WHITE);
  tft.drawString("Loading...", 105, 105, 2);
  delay(1000);

}

The setup function begins serial communications. The serial monitor is simply for returning the frame time in milliseconds. Therefore, can be deleted with no consequence.

The display and the sprite are initialised.

The camera pins are configured as well as many sensor parameters.

Have a play with the settings. I’ve found that reducing the contrast to -2 helps to bring the colours out.

However, there is still a strange colour and pulsing of brightness. I’ve played with a few of the other settings to no avail.

void loop() {

  //take picture
  camera_fb_t  * fb = NULL;
  fb = esp_camera_fb_get();
  unsigned long initalTime = millis();

  for (size_t i = 0; i < 57600; i++) {    //240x240px = 57600
    //create 16 bit colour from two bytes. 
    byte first_byte = fb->buf[i * 2];
    byte second_byte = fb->buf[i * 2 + 1];
    scr[i] = (second_byte << 8) + first_byte;   
  }

  spr.pushSprite(0,0);
  unsigned long frameTime = millis() - initalTime;
  Serial.print("Frame time(ms) = "); Serial.println(frameTime);
  esp_camera_fb_return(fb);   //return the frame buffer back to the driver for reuse

}

The loop is short and sweet; basically capturing a frame from the camera sensor.

The camera and display both use 240×240 pixels in 16-bit colour, so the ‘for’ loop takes two bytes from the frame buffer, swaps them around and places it in a display buffer.

The display buffer is then written to the display, the camera frame buffer is deleted and the process starts again.

If you’ve been successful in the above, then you should get the following result. Best of luck!


Page created: 24/08/2024
Last updated: 24/08/2024