Pushing 17.5 FPS from an ESP32-CAM and GC9A01 round display

This is a follow up to a previous page on how to get the ESP32-CAM and round GC9A01 TFT display to work. This revision lowers the frame time from 77ms to 57ms, thus increasing the frame rate to 17.5 FPS.

As always, in the interests of openness and honesty: these changes were suggested by u/the_3d6 on Reddit.

As the frame size of the camera and the display are both 240×240, it is possible to cast a pointer when creating the sprite then, effectively point both pointers at each other and let it rip. (As you can tell, I don’t understand pointers much…).

There is no change to the User_Setup.h file which you can find over at the original page.

The revised code is as follows, it looks mostly the same, and that’s because it is. 72% of the sketch code is just setting up the camera:

#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;

//////////////////////////////////////////////////////////
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, 0);     // -2 to 2
  s->set_contrast(s, 0);       // -2 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);

}
//////////////////////////////////
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

}

Rendering 3D Graphics Using an Arduino Nano.

This started as the result of misreading the title of a Reddit post by u/Trotztd and lead to rendering 3D graphics on an Arduino.

The title of the post was “Raymarching on Arduino Uno OLED 128×64 with dithering. 15 seconds per frame.” which I erroneously read as 15 frames per second.

I had been previously looking at raytracing in the hope of inspiration of making a simple 3D game. I also happened to have a SSD1306 OLED display running (alongside a ST7920 LCD), so thought I would give it a try.

To be honest, I only realised that I had misread the title of the post when the display didn’t show anything for 15 seconds and froze for 17-20 seconds after each frame.

I also noticed that the Arduino outputted the time taken between frames to the serial monitor, so I captured some data, slapped it in excel and created an x-y scatter plot.

Please excuse the erroneous title on the graph; the unit of measurement on the Y-axis is milliseconds (ms), not seconds (s).

Interestingly, the graph produced a sinusoidal wave that was in phase with the rotation of the torus on the display.

In a comment reply from u/Trotztd , they suggested using an ESP32 for a faster experience. So, I pulled out a 38 Pin ESP32 DEV Kit, wiggled the wires in to place and uploaded the code.

With the added computational power, the refresh rate increased from 0.05Hz to 4.6Hz. That’s a 9200% improvement.

The blistering 4.6Hz display can be seen in this video.

Again, the time taken to render each frame varied in phase with the rotation of the torus; albeit a lot faster.

I did consider the time taken by the display driver chip may be the cause of the rhythmic timing as it will take longer to address a greater number of pixels (or vice versa) so I played around with the code.

In the first instance I commented out the outputMatrix( ); function call; however, this buggered the program and the serial monitor just spat out a random list of 0’s and 1’s.

The second attempt to disable the display was to comment out the ssd1306_lcd.next_page(); command within the outputMatrix( ); function. However, this appeared to change absolutely nothing. So my theory remains nothing more than a theory.

Using SMD Resistors on Vero/Strip Board

When prototyping, my preferred protoboard is Verobaord/Stripboard as it has handy 2mm wide copper busses set at a 2.54mm (0.1inch) pitch.

As a result, there are a few sizes of SMD/SMT components that fit nicely:

  • 1206 (metric 3216) and 1210 (metric 3225) are good for jumping to the adjacent bus.
  • 1206 (metric 3216) are good for jumps over breaks. The 1210 is a bit too wide.
  • 2010 (metric 5024) are able to jump two busses.
Original image courtesy of: Resistor Packing Specification DIP SMD Resistor Sizes and Packages【Synton-Tech】

The image shows how the various sized components can be positioned on the veroboard / stripboard.

In order to create a new user-interface panel for the bark blind, the below board uses six 1206/3216 size SMD/SMT resistors, along with through-hole components.

There are three 330 ohm resistors for the 1.8mm LEDs, and a resistor network comprised of a 4K7 and two 1K ohm resistors. The resistor network allows all three buttons to be interfaced with a single pin.

My process was as follows:

  • Cut the board to size and also cut all the breaks in any of the copper traces. I used a fresh Stanley knife and a steady hand.
  • Test with a multimeter.
  • Wet the copper traces with solder where the pads of the SMD components are to sit.
  • Then wick away most of the solder. The components should sit flat.
  • Apply a small amount of solder flux paste on the ‘pads’.
  • Place the SMD components. The flux paste should help keep the component in place.
  • Use a hot air gun to then reflow the components to the solder pads. I used 400 oC temperature, 3/8ths air flow and a narrow nozzle (circa 4mm).
  • Test the components with a multimeter.
  • There was an instance where I had to add some solder; so I placed it against the joint and used the hot air station to melt the solder in to the joint.
  • Then it was a case of doing the rest of the through hole components.

Overall, I’m really happy with the outcome. The initial design was a bit of a head-scratcher and the soldering was a bit more challenging, but it fully worked first time and was stable than than the breadboarded version.

I’m aware that the next step is for PCB design & manufacture. I would like to experiment with home-brew-PCBs by using the SLA 3D printer to expose photoresist FR2 copper clad board.

One step at a time.

Printing TPU Filament with a Stock* Ender 3 & Bowden Tube

Sometimes the best way to learn a new thing is to use it in a new project.

This little project was a short and successful foray into the world of printing TPU flexible on a stock* Ender 3 Pro.

*This in reference to a Bowden type extruder. In the interests of openness and honesty the printer has been upgraded with a metal extruder, Capricorn tube & yellow bed springs.

Due to the odd shape of the walls in our upper floor, the roller blinds over the window let in a lot of light. This project was to create a simple magnetic clip to retain a blind against a wall to prevent light leaking around the side.

The flexible TPU filament is perfect for this application and the total print time was circa 15mins.

the settings that were used:

  • Nozzle temp = 230 oC
  • Bed temp = OFF / 0 oC (keep the work area cool)
  • Print speed = 20 mm/s
  • Part cooling = low for first couple of layers, then high.
  • Retraction = OFF (yes, you will get stringing, but a stringy print is better than a failed print)
  • Your settings may vary depending on many factors.

Tips and Notes:

  • Any retraction movements will cause the extruder gear to create teeth marks in the filament. These marks lead to greater amounts of friction within the Bowden tube and this has a higher chance of jamming.
  • The material is soft, so ease off the pressure on the extruder idler.
  • Some people note that the bed should be 30 oC, however, I think this warms the filament and makes it softer. That could be superstition.
  • A legit Capricorn tube from Creality will help – the inner bore is greater precision with a smoother surface.

Best of luck!

More Microwave Madness

Work begets work.

For example; if you send an email, you’ll likely get a reply which needs actioning. Or, if you finish your work early then you’ll be given more work.

The true is same if you fix a microwave. Once word gets out about the successful repair then more broken microwaves will appear.

This Panasonic model was dead on arrival. The mains plug fuse was checked and tested good (<1 ohm resistance). Therefore it’s time to open the case.

Past experience of microwaves have shown that the majority of issues are due to blown internal fuses. These are the fast-blow fuse, more sensitive than the type in the wall plug.

The customer/client/microwave-owner had reported that their main breaker had gone a few days prior, which gave further credence to a blown fuse.

Upon testing, it was clear that the internal fuse had gone pop. Interestingly, the fuse was difficult to remove from the holder because of the “spot welds” on the terminal – must have been a hell of a surge!

Thankfully, a simple fuse change worked wonders and fixed the issue. Better still, we were able to offer the previous microwave as a courtesy microwave in the meantime.

The incoming power control board. Note small fuse.
Resistance check reading >2Mohm. ie., open circuit.

New Addition: Photon Mono SE

After much deliberating, I’ve taken the plunge and invested in an SLA resin printer. Sourced a second-hand Anycubic Photon Mono SE printer, complete with the Wash & Cure station for a reasonable sum.

The first test print was a replacement spray can nozzle. The design was provided courtesy of Darren Allen via Thingiverse and can be viewed to the right.

The nozzle diameter is 0.8mm – it would be interesting to see if an FDM printer could achieve the same results.

Fresh out of the printer!
Test video of the nozzle!

Overall, a resounding success. Looking forward to using this printer for future projects.

Three More Side Quests

Instead of concentrating on the proper projects, I’ve devoted my time to literature, recycling and wood working as detailed below.

Taking procrastination to a new level.

literature

Repairing microwaves is easy; writing poetry about them is more challenging.

recycling

Upgrading a battery powered LED light with parts salvaged from e-waste.

wood working

Created a book binding punch cradle. Burning wood with a laser is fun.

Side Quest: Outside Light Holder

Due to the acquisition of new patio furniture; we needed to move the ground mounted light spike. Cue 3D printing a pair of holders to enable the light to be wall mounted.

The design was influenced by the focus-column holder for the laser cutter. You can play with the 3D model below.

Experiment Time:

This model will fail; but how long will it last?

These parts are printed in PLA and will face a range of different weather conditions; from wind and rain to direct sunshine.

The following data will be measured over time:

DateDoes it still work?
Y/N
Comments
08/07/2023YInstalled at 8pm

Side Quest: Laser Cutter / Engraver

After much deliberation over many months, the workshop has now been upgraded with a Sculpfun S30 Pro laser cutter/engraver. This particular model features a 10W diode laser, air assist and X-Y limit switches and a linear rail on the X-axis.

Currently in the early stages of testing, but the machine seems to be very competent and able to cut through 11mm thick plywood! Below is a video showing the first test for engraving and cutting 3mm basswood.

This addition to the workshop provides the ability to cut and mark various sheet materials in many thicknesses. Technically this is subtractive manufacturing, so should complement the additive manufacturing of the 3D printer.

Obviously, it wouldn’t be a true addition to the workshop without some sort of modification. Check out this 3D printed focal column holder.

As more is learned; more information will be added to the specific resource page for the laser settings.