And my luck is continuing. I now have moved Uncanny Eyes code into a separate library that supports both the ST7789 (240x240 square display) and GC9A01A (240x240 round display). After I got the 2nd Adafruit display and I verified both of my Adafruit displays now work, I dug out the 2 non-CS boards that I had and I attached them, changing the CS to be -1.
One display works great. The other display corrupts the bottom 1/2 of the screen. But with the working display, I verified that it does work also.
So I now have separate .ino sketches that pull in the library:
- 2 GC9A01A round displays;
- 1 GC9A01A round display;
- 2 ST7789 square displays, both with a CS pin;
- 2 ST7789 square displays, one with a CS pin, one without;
- 2 ST7789 square displays, both without a CS pin;
- 1 ST7789 square display with a CS pin; (and)
- 1 ST7789 square display without a CS pin.
I discovered that the arguments to GC9A01A's displayType constructor are in a different order to the ST7789 displayType constructor. This means if you call the ST7789 class constructor with GC9A01A argument order, it will crash when initializing the displays:
Code:
// Initialize eye objects based on eyeInfo list in config.h:
for (e = 0; e < NUM_EYES; e++) {
Serial.print("Create display #"); Serial.println(e);
#if USE_GC9A01A
//eye[e].display = new displayType(&TFT_SPI, eyeInfo[e].cs,
// DISPLAY_DC, -1);
//for SPI
//(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
eye[e].display = new displayType(eyeInfo[e].cs, eyeInfo[e].dc, eyeInfo[e].rst,
eyeInfo[e].mosi, eyeInfo[e].sck);
#endif
#if USE_ST7789
//eye[e].display = new displayType(&TFT_SPI, eyeInfo[e].cs,
// DISPLAY_DC, -1);
//for SPI
//(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
eye[e].display = new displayType(eyeInfo[e].cs, eyeInfo[e].dc,
eyeInfo[e].mosi, eyeInfo[e].sck, eyeInfo[e].rst);
// ...
}
Similarly, how you start up the display is different:
Code:
// After all-displays reset, now call init/begin func for each display:
for (e = 0; e < NUM_EYES; e++) {
#if USE_GC9A01A
eye[e].display->begin();
Serial.printf("Init GC9A01A display #%d, rotation %d\n",
e, eyeInfo[e].rotation);
#endif
#if USE_ST7789
// Try to handle the ST7789 displays without CS PINS.
if (eyeInfo[e].cs < 0)
eye[e].display->init(240, 240, SPI_MODE2);
else
eye[e].display->init();
Serial.printf("Init ST7789 display #%d, rotation %d\n",
e, eyeInfo[e].rotation);
#endif
eye[e].display->setRotation(eyeInfo[e].rotation);
}
Serial.println("done");
Originally in creating the library, I moved the main parts of the code into separate .cpp files. But I discovered it doesn't work, because a lot of the font functions will get duplicate function messages from the linker, as each of the drivers wants to pull in the library functions, and each has the same name. Of course, I'm probably the one person crazy enough to want two separate displays in a program that have different drivers. So I just made the code into a .h file, and the .ino sketch using #include to bring it in.
One other thing I learned (which is obvious once I thought about it). Originally, I had all of the constants (sizes, pin numbers, etc.) as 'extern const' instead of just using '#define' or normal const so that I could set these in the .ino file and call library. But it makes the code a little slower, since the compiler can't do the constant optimization. So when I moved the drivers back to being in .h files, and removed the 'extern const' declarations, it ran a little faster (by 1-2 fps).
The GC9A01A display is faster than the ST7789 display. I suspect this is due to the GC9A01a driver not sending pixels that won't be displayed.
Another thing that I noticed is if you run 2 eyes, the fps rate is about double as with 1 eye, That is because on the Teensy 4.x it uses asynchronous updates with DMA, and the Teensy can go and work on the setting up for the second eye display while the data is still being transferred to the first display. With a single eye, it has to wait for the eye to finish before returning from loop.