NEW: GC9A01A Display driver for Teensy 3.x and Teensy 4.x

I found this online

Code:
In the Adafruit_SSD1306.h file look for the lines:

#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8

Change these lines to say:

#define SSD1306_COMSCANINC 0xC8
#define SSD1306_COMSCANDEC 0xC0

this supposed to flip the image on the y-Axis.

but i'm not so experienced to know where in the teensy GC9A01A library code to set these

@mjs513 likely to help?

EDIT: My bad, that's for a different library!
 
I found this online

Code:
In the Adafruit_SSD1306.h file look for the lines:

#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8

Change these lines to say:

#define SSD1306_COMSCANINC 0xC8
#define SSD1306_COMSCANDEC 0xC0

this supposed to flip the image on the y-Axis.

but i'm not so experienced to know where in the teensy GC9A01A library code to set these

@mjs513 likely to help?

EDIT: My bad, that's for a different library!

You might want to take a look at this https://github.com/Bodmer/TFT_eSPI/issues/96. Its for the ILI9341 but use of madctl is similar. HINT: Look at about line 1337 in the .cpp file.
 
That helped. I added this to your case structure and it rotated it. Getting somewhere. There are some faint lines on the image but at least i'm on the right path. THanks!
Oh, I also had to change to MADCTL_RGB format


Code:
  case 4:
      writedata8_last(0x70 | MADCTL_RGB);
      _width = GC9A01A_TFTHEIGHT;
      _height = GC9A01A_TFTWIDTH;
      break;
 
That helped. I added this to your case structure and it rotated it. Getting somewhere. There are some faint lines on the image but at least i'm on the right path. THanks!
Oh, I also had to change to MADCTL_RGB format


Code:
  case 4:
      writedata8_last(0x70 | MADCTL_RGB);
      _width = GC9A01A_TFTHEIGHT;
      _height = GC9A01A_TFTWIDTH;
      break;

This might help as well from the GC spec sheet

Capture2.PNG
 
That helped. I added this to your case structure and it rotated it. Getting somewhere. There are some faint lines on the image but at least i'm on the right path. THanks!
Oh, I also had to change to MADCTL_RGB format


Code:
  case 4:
      writedata8_last(0x70 | MADCTL_RGB);
      _width = GC9A01A_TFTHEIGHT;
      _height = GC9A01A_TFTWIDTH;
      break;

Your case 4 should be:
Code:
  case 4:
    writedata8_last(0x00 | MADCTL_MY | 0x00 | MADCTL_BGR);
    _width = GC9A01A_TFTHEIGHT;
    _height = GC9A01A_TFTWIDTH;
    break;

Just tried it
 
On the 2" 240x320 LCD from waveshare that change produced garbled image.

Actually for a true mirror its should be:
Code:
  case 4:
    writedata8_last(MADCTL_MX | MADCTL_MY | 0x00 | MADCTL_BGR);
    _width = GC9A01A_TFTHEIGHT;
    _height = GC9A01A_TFTWIDTH;
    break;
  }

A simple test of just using tft.print() shows that its being mirrored. Case 4 is the mirror of case 0 (setRotation(0)).

Do you have a sample sketch that shows the problem
 
https://www.waveshare.com/wiki/2inch_LCD_Module

My problem was i was using the round LCD all this time and once I switched to the rectangular everything was flipped and the colours were off.
The code as I have it in "Case 4:" made it behave normal.

I hate to tell you this then why are you using the GC9A01A library!!! The link is to a display using the ST7789 display chip. You should be using the ST7780 display library! Sorry, since you posted on this thread using the GC rectangular version. Try the other library.
 
A little too late lol. I took a look on the current ST8880 library we have, missing a few stuff I use in the GC9A01A that was modified. I had to adjust the gamma and such and it's looking/working perfectly now. And it's fast!
 
Thanks @mjs513 and @KurtE for this great library. I've been using it to good effect with my updated version of the Uncanny Eyes codebase(s). I've recently started taking a look to see if I can improve the rendering speed though and it turns out updateScreenAsync() consistently takes a fraction over 32ms to update a single 240x240 GC9A01A screen. That means a maximum possible frame rate of ~31 FPS. In practice I'm getting around 20-24 FPS due to the time it takes to update the framebuffer before calling updateScreenAsync(). I realise I could add double-buffering to absorb most of the rendering time, but that would still max out at 31 FPS, use a lot more memory, and not help so much with two active screens (since the rendering effort is doubled).

I feel like I must be missing something or doing something wrong. Is there anything that can be done to speed up the screen updates/DMA times? Change some SPI settings maybe? Other drivers (e.g. https://github.com/vindar/ILI9341_T4) seem to get much higher frame rates over SPI compared to what I'm seeing.
 
A lot of this simply depends on the SPI clock speed:
The display begin method has options to change the default 30mhz.
Code:
#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) // Teensy 4.x
#define GC9A01A_SPICLOCK 30000000u
#define GC9A01A_SPICLOCK_READ 2000000
...
  void begin(uint32_t spi_clock = GC9A01A_SPICLOCK,
             uint32_t spi_clock_read = GC9A01A_SPICLOCK_READ);
You can try bumping up the clock speed and see if it works or not. This often depends on the displays and the wiring and the...

And if the two eyes are on two different SPI busses, the DMA updates should be able to be done concurrently.

There may be other tricks that can be done as well, like trying to do continuous updates and update the top of the display's memory when it is outputting the bottom and likewise the bottom part when it is displaying the upper part...

There may also be games that can be played where you try to only update those portions of the screen that changed. I think in some of the drivers I added some support for that, in the updateScreen usage but not in the DMA async code.
 
Ahh, thank you! I hadn't spotted the begin() parameters. Bumping spi_clock up to 48,000,000 increased the framerate to around 30+ FPS per screen. 60,000,000 hits as high as 40+ FPS, 90,000,000 even touches on 50 FPS sometimes, though it's extremely variable as the code becomes far more CPU bound as the DMA time reduces. 100,000,000 starts showing artifacts/corruption at times. Yes the eyes are on separate SPI buses and concurrent DMA is working just fine.

Unfortunately I render the eyes column by column because it simplifies the eyelid calculations, so chasing the raster (so to speak!) isn't really viable unless I do something like rotate the displays and render sideways. I'm going to try adding a third buffer instead so I can update that even while both DMA transfers are still underway. I'd also seen the code in the driver keeping track of min/max X/Y but hadn't realised the DMA doesn't take note of that, it's very helpful to know. Maybe it won't be too hard for me to tweak the DMA so it takes into account the minY/maxY values at least, I imagine minX/maxX probably isn't so feasible.

Thanks for the very helpful response, it gives me plenty of things to go try.

[edit]: I forgot to mention - I'm seeing a compiler warning when trying to delete a GC9A01A_t3n object. I'm not much of a C/C++ coder but I think this indicates a bug in the GC9A01A_t3n code?

"Delete called on non-final 'GC9A01A_t3n' that has virtual functions but non-virtual destructor"
 
I played with this a little to see if adding the audio library was feasible--just a crude proof of concept. I did not shorten the long wires that came with the GC9A01A displays from waveshare and the displays seemed ok at 50,000,000 but not 70,000,000. So my wiring is not as good as yours. The displays looked pretty acceptably fast while playing an m4a audio file from the sd card on a teensy 4.1.
Code:
// Define if you wish to debug memory usage.  Only works on T4.x
//#define DEBUG_MEMORY

#include <SPI.h>
#include <array>
#include <Wire.h>
#include <FastTouch.h>
#include <Streaming.h>

/*
  https://forum.pjrc.com/threads/57423-Teensy-4-0-I2S-without-Audio-Shield-and-flash-memory-question?p=214254&viewfull=1#post214254

  Modified to eliminate the audio shield, and use Max98357A mono I2S chip.
  https://smile.amazon.com/gp/product/B07PS653CD/ref=ppx_yo_dt_b_asin_title_o00_s00?ie=UTF8&psc=1

  Pins:    Teensy 4.0  Teensy 3.x

  LRCLK:  Pin 20/A6 Pin 23/A9
  BCLK:   Pin 21/A7 Pin 9
  DIN:    Pin 7   Pin 22/A8
  Gain:   see below N/C
  Shutdown: N/C   N/C
  Ground: Ground    Ground
  VIN:   VIN = 5V

  Gain setting:

  15dB  if a 100K resistor is connected between GAIN and GND
  12dB  if GAIN is connected directly to GND

   9dB  if GAIN is not connected to anything (this is the default)
   6dB  if GAIN is conneted directly to Vin
   3dB  if a 100K resistor is connected between GAIN and Vin.  */

#include <Audio.h>
#include <play_sd_mp3.h> //mp3 decoder
#include <play_sd_aac.h> // AAC decoder
#include <SD.h>

#include "config.h"
#include "util/logging.h"
#include "sensors/LightSensor.h"
#include "sensors/PersonSensor.h"

const int chipSelect = BUILTIN_SDCARD;
File root;
File song;

AudioPlaySdMp3           playMp31;
AudioPlaySdAac           playAac1;

// GUItool: begin automatically generated code
AudioPlaySdWav           playSdWav1;     //xy=72.16667175292969,98.00000762939453
AudioMixer4              mixer2;         //xy=230.1666717529297,199
AudioMixer4              mixer1;         //xy=234.1666717529297,76.00000762939453
//AudioAmplifier           amp1;           //xy=416.16668701171875,408
AudioMixer4              mixer3;         //xy=424.1666717529297,132.1666717529297
AudioAnalyzeFFT256       fft256_1;       //xy=577.1666870117188,267
AudioAnalyzePeak         peak1;          //xy=579.1666870117188,201
AudioSynthWaveformDc     dc1;            //xy=439.1666717529297,34.16667175292969
AudioEffectMultiply      multiply1;      //xy=599.1666717529297,74.16667175292969
AudioOutputI2S           i2s1;           //xy=779.1666870117188,126
AudioConnection          patchCord11a(playMp31, 0, mixer1, 1);
AudioConnection          patchCord13m(playMp31, 1, mixer2, 1);
AudioConnection          patchCord14m(playAac1, 0, mixer1, 2);
AudioConnection          patchCord16(playAac1, 1, mixer2, 2);
AudioConnection          patchCord3(mixer2, 0, mixer3, 1);
AudioConnection          patchCord4(mixer1, 0, mixer3, 0);
AudioConnection          patchCord5(mixer3, peak1);
AudioConnection          patchCord6(mixer3, fft256_1);
AudioConnection          patchCord7(mixer3, 0, multiply1, 1);
AudioConnection          patchCord8(dc1, 0, multiply1, 0);
AudioConnection          patchCord9(multiply1, 0, i2s1, 0);
//AudioControlSGTL5000     sgtl5000_1;     //xy=573.1666870117188,400
// GUItool: end automatically generated code
char rootFiles[][33] = { "A Banda.mp3", "BuffaloGals.m4a", "BugleCallRag.m4a", "Cast Your Fate To the Wind.mp3", "Celestial Soda Pop.mp3",
         "DuelingBanjos.mp3", "Hallelujah.mp3", "Livin' On A Prayer.mp3", "Oops.mp3", "The Thought Stayed Free.mp3", "The Train And The River.mp3",
          "We Don't Talk About Bruno.mp3"};
uint64_t songStart;
boolean shortSwitch = false;

// The index of the currently selected eye definitions
static uint32_t defIndex{0};

LightSensor lightSensor(LIGHT_PIN);
PersonSensor personSensor;

bool hasBlinkButton() {
  return BLINK_PIN >= 0;
}

bool hasLightSensor() {
  return LIGHT_PIN >= 0;
}

bool hasJoystick() {
  return JOYSTICK_X_PIN >= 0 && JOYSTICK_Y_PIN >= 0;
}

bool hasPersonSensor() {
  return PERSON_SENSOR_PRESENT;
}
void stopPlaying() {
  song.close();
  if (playSdWav1.isPlaying()) { playSdWav1.stop(); }
  if (playMp31.isPlaying()) { playMp31.stop(); }
  if (playAac1.isPlaying()) { playAac1.stop(); }
}
boolean playing () {
  //Serial<<"shortSwitch: "<<shortSwitch<<"    duration: "<<millis()-songStart<<endl;
  if ( shortSwitch & ((millis() - songStart ) >20000)  ) {stopPlaying();/*Serial<<"timed out"<<endl;*/}
  return (playSdWav1.isPlaying() | playMp31.isPlaying() | playAac1.isPlaying() );
}

void adjustVolume(float vol) {
      dc1.amplitude(vol);           //with the max98357, mixer.gain does not adjust volume (don't know why)
}

void playFile(String curfile) {
    char charry[120];
    String fullFile = root.name();
    if (fullFile.length() > 2) fullFile.append('/');  //avoid "//" at root
    fullFile.append(curfile);
    fullFile.toCharArray(charry,119);
    Serial<<" fullFile: "<<fullFile;
    curfile.toUpperCase();
    songStart = millis();   //play just 20 seconds of song -- see playing() above
    //look for MP3 or AAC files
    if (curfile.lastIndexOf(".MP3") >0) playMp31.play(charry);
    if (curfile.lastIndexOf(".WAV") >0) playSdWav1.play(charry);
    if (curfile.lastIndexOf(".AAC") >0) playAac1.play(charry);
    if (curfile.lastIndexOf(".MP4") >0) playAac1.play(charry);
    if (curfile.lastIndexOf(".M4A") >0) playAac1.play(charry);
    delay(100);
    if (! playing()) {
      Serial<<"  Song did not start?? size="<<song.size();
    }
    Serial<<endl;
    //Serial<<"Playing file: "<<charry;
    //lightsOption = random(6);  //one of lightsAction,rainbowSpin1, rainbowSpin2, comet, twinkle, rainbow3
    //lightsOption = 5;   //when testing just set one
    //Serial<<"lightsOption: "<<lightsOption<<endl;
 }

/// INITIALIZATION -- runs once at startup ----------------------------------
void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 2000);
  delay(200);
  DumpMemoryInfo();
  Serial.println("Init");
  Serial.flush();
  randomSeed(analogRead(A3)); // Seed random() from floating analog input
  AudioMemory (160);
  adjustVolume(0.3f);
  SD.begin(chipSelect);
  if (hasBlinkButton()) {
    pinMode(BLINK_PIN, INPUT_PULLUP);
  }

  if (hasJoystick()) {
    pinMode(JOYSTICK_X_PIN, INPUT);
    pinMode(JOYSTICK_Y_PIN, INPUT);
  }

  if (hasPersonSensor()) {
    Wire.begin();
    personSensor.enableID(false);
    personSensor.enableLED(false);
    personSensor.setMode(PersonSensor::Mode::Continuous);
  }

  initEyes(!hasJoystick(), !hasBlinkButton(), !hasLightSensor());
}

void nextEye() {
  defIndex = (defIndex + 1) % eyeDefinitions.size();
  eyes->updateDefinitions(eyeDefinitions.at(defIndex));
}

/// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
  // Switch eyes periodically
  static elapsedMillis eyeTime{};
  if (fastTouchRead(41)> 23 && eyeTime > EYE_DURATION_MS) {   //jrr
    nextEye();
    eyeTime = 0;
  } else {
    //Serial<<"FastTouchRead(41): "<<fastTouchRead(41)<<"eyeTime: "<<eyeTime<<endl;
  }
  if (fastTouchRead(40)>23 && !playing()) {
    root=SD.open("/");
    playFile(rootFiles[1]);   //obviously too simple--see if it works
  }else {
    //Serial<<"FastTouchRead(40): "<<fastTouchRead(40)<<"playing: "<<playing()<<endl;
  }

  // Blink on button press
  if (hasBlinkButton() && digitalRead(BLINK_PIN) == LOW) {
    eyes->blink();
  }

  // Move eyes with an analog joystick
  if (hasJoystick()) {
    auto x = analogRead(JOYSTICK_X_PIN);
    auto y = analogRead(JOYSTICK_Y_PIN);
    eyes->setPosition((x - 512) / 512.0f, (y - 512) / 512.0f);
  }

  if (hasLightSensor()) {
    lightSensor.readDamped([](float value) {
      eyes->setPupil(value);
    });
  }

  if (hasPersonSensor() && personSensor.read()) {
    // Find the closest face that is facing the camera, if any
    int maxSize = 0;
    person_sensor_face_t maxFace{};

    for (int i = 0; i < personSensor.numFacesFound(); i++) {
      const person_sensor_face_t face = personSensor.faceDetails(i);
      if (face.is_facing && face.box_confidence > 150) {
        int size = (face.box_right - face.box_left) * (face.box_bottom - face.box_top);
        if (size > maxSize) {
          maxSize = size;
          maxFace = face;
        }
      }
    }

    if (maxSize > 0) {
      eyes->setAutoMove(false);
      float targetX = (static_cast<float>(maxFace.box_left) + static_cast<float>(maxFace.box_right - maxFace.box_left) / 2.0f) / 127.5f - 1.0f;
      float targetY = (static_cast<float>(maxFace.box_top) + static_cast<float>(maxFace.box_bottom - maxFace.box_top) / 3.0f) / 127.5f - 1.0f;
      eyes->setTargetPosition(targetX, targetY);
    } else if (personSensor.timeSinceFaceDetectedMs() > 1'000) {
      eyes->setAutoMove(true);
    }
  }

  eyes->renderFrame();
}
 
Probably better to start a new thread about PCM1860, since it's an audio codec chip and this conversation is about displays.

When you start that new thread, best to show what you've already tried (if anything) especially whatever I2C code to detect and initialize the chip has been tried. Odds are much better of starting a good conversation that way, compared to just asking if anyone can do it without showing any of the effort you've already tried.
 
I played with this a little to see if adding the audio library was feasible--just a crude proof of concept. I did not shorten the long wires that came with the GC9A01A displays from waveshare and the displays seemed ok at 50,000,000 but not 70,000,000. So my wiring is not as good as yours. The displays looked pretty acceptably fast while playing an m4a audio file from the sd card on a teensy 4.1.

You might want to take a gander at my thread on running the eyes on pair of GC9A010 eyes (waveshare has one form factor and there is another factor where the display has 7 male header pins soldered into it). In partciular, KenHahn talked about her/their/his experiences in post #28, h4yn0nnym0u5e replied in post #34 where they/he/she started to delve into the issue.

I had refactored the original code to allow adding various defaults and such, including using using the audio library to play mono RAW sounds that had been converted to by in .h files. I also added code to do neopixel LEDs.

I also got the person sensor from Sparcfun and tried their code for the ST7793 displays with the sensor.

In addition, Chris.nz posted (#13, #30 and others) about his rewrite of the whole code, which I've gotten and started using, but I haven't done much except get it to work the Arduino IDE. Chris has taken the code from the latest Adafruit sources to add more eyes to the display and is in the process of reworking it.


One thought that I've had is since the issue seems to be reading the audio files from the SD card, that perhaps on a Teensy 4.1 with PSram is to read the whole file from the SD card and stage it into PSram (or possibly a flash memory filesystem). This should eliminate some of the latency issues in the original code.

I've been away from tinkering with the eyes code for a bit. I've been down the rabbit hole of redesigning the wiring of prototyping breakout boards to allow me to switch running the different teensies with the different displays. And the winter holidays and some personal health issues have slowed things down.

<edit>
I did rebase my branch to Chris's latest. It adds the leopard eyes, and also beginning support for person sensor.
 
Last edited:



  • Thanks for all those links! I had not found all of them, especially your git repository. I had worked off Chris's git repository and was up to date with leopard eyes. I had seen the zip link you posted a few months ago but did not use that. Almost all of the changes I needed to get it to compile using arduino IDE 2 were in main.cpp which is all I listed. Today I will stick the little speaker I used to demonstrate the that sound could work without slowing the eyes unacceptably into a cardboard box so that it is more audible. Yesterday I just had it hanging loose so the front and back of the speaker were fighting each other. I do have an extra RAM chip soldered onto the 4.1 board but so far have not needed it. I don't have "Night on Bald Mountain" on the SD card yet, either.
 
[QUOTEToday I will stick the little speaker I used to demonstrate the that sound could work without slowing the eyes unacceptably into a cardboard box so that it is more audible. Yesterday I just had it hanging loose so the front and back of the speaker were fighting each other. [/QUOTE]

The sound is pretty acceptable with an eye definition without lids (I think it's fisheye, although I don't have the names printing out right now). Sound gets pretty stuttery with the eyes with lids right now. I was able to put the SPI parameter up to 55,000,000 but not 60,000,000.

My hearing is not very good, suppose I should put my hearing aids in before I critique this further or ask someone else to listen. Of course with a 1"x3" speaker in a random sized cardboard box without acoustuff, one can't expect much.

I wonder if yield() s in some crucial spots would help the stuttering, though?

with ALL of Chris's eye definitions (including leopard) and the audio stuff, there seems to be lots of memory left:
Code:
Memory Usage on Teensy 4.1:
  FLASH: code:191760, data:3106380, headers:9004   free for files:4819320
   RAM1: variables:85216, code:186920, padding:9688   free for local variables:242464
   RAM2: variables:54496  free for malloc/new:469792
 
The sound is pretty acceptable with an eye definition without lids (I think it's fisheye, although I don't have the names printing out right now). Sound gets pretty stuttery with the eyes with lids right now. I was able to put the SPI parameter up to 55,000,000 but not 60,000,000.
Without audio, I can go to 90,000,000.

My hearing is not very good, suppose I should put my hearing aids in before I critique this further or ask someone else to listen. Of course with a 1"x3" speaker in a random sized cardboard box without acoustuff, one can't expect much.

I wonder if yield() s in some crucial spots would help the stuttering, though?
I imagine so. Either that or as we've discussed, return out of loop more often, which may mean restructuring the code.

with ALL of Chris's eye definitions (including leopard) and the audio stuff, there seems to be lots of memory left:
Code:
Memory Usage on Teensy 4.1:
  FLASH: code:191760, data:3106380, headers:9004   free for files:4819320
   RAM1: variables:85216, code:186920, padding:9688   free for local variables:242464
   RAM2: variables:54496  free for malloc/new:469792

With the Teensy 4.0, I can only fit 8 eyes without audio, and presumably less with audio.

Now that the eye array is moved to config.h instead of being split between config.h and main.cpp, I may want to look at doing a single eye support for the Teensy 4.0 to replace the Hallowing M4 in my wizard's staff:
2022-09-11-17-57-023-staff.jpg
 
Good morning, I'm a teacher here in Brazil and I'm enthusiastic about technology. I like arduino and works related to esp8266, esp32. I recently saw on aliesxpress the display with 7 pines and I saw a picture that I think is yours. I need help getting two 7-pin displays from aliexpress to work. I have ESP8266 and I have arduino. I have an esp32 board but I don't know if it works. What do I need to make the two eyes work on these displays???parts, wiring diagram and everything, coded in the arduino IDE???help me....thank you

https://pt.aliexpress.com/item/1005...l_item.16.35ea4c7fWVbuho&gatewayAdapt=glo2bra this is the seven pin display i have

alerogeriob@gmail.com email
 
This library is fantastic for fast display with GC9A01 screens. Has anyone had any success in getting this driver to work in LVGL with DMA?
 
Back
Top