Mirror Display on Arduino Nano Every AZ-Delivery 1,28" round, GC9A01A

Gentleman,

Arduino Nano Every
AZ display 1,28" round
Adafruit GC9A01A driver
Adafruit GFX library

For a head up display I need to mirror the display.
Will anyone be so kind and enlighten me....

My knowledge:
Basic, so if it could be a bit more detailed on how to do- would help.. :slight_smile:
many txs in advance
Wolfgang

I don’t think the screen talks back on the SPI bus, so you might be able to just wire two GC9A01A displays in parallel, and they will both show the same image if wired correctl (ie look at all the cables going out of your Arduino and to the screen and duplicate them to the other screen).

Worth trying maybe

many txs for your quick response, but won`t help.
I think it will double the output.

The screen should look like the lower part, to be displayed correct in the semi transparent mirror.
b.r.
Wolfgang

ah OK - when you wrote "mirror", you meant in the sense of create the reversed image. I understood you wanted to have the same one duplicated.

As you have commands going into the display, "mirroring" won't be just a wiring thingy. You'll need to be able to handle 2 screens in the code and every time you issue a screen command to display something you'll have to calculate the mirrored command for the other screen.

That would appear to be a simple transformation. The new value of any X coordinate (that is a coordinate on the horizontal axis) is the screen width minus the old X coordinate. Have you a code sample which writes to the display ? There may be somewhere in the library that you can hack such a change.

#define SCREEN_PRODUCT_ID 6178  // GC9A01A 240x240 round display

#define TFT_CS 10  // To display chip-select pin
#define TFT_RST 8  // To display reset pin
#define TFT_DC 7   // To display data/command pin

#include <SPI.h>
#include <Adafruit_GFX.h>              // Core graphics library
#include <Fonts/FreeSansBold18pt7b.h>  // A custom font
#include "Adafruit_GC9A01A.h"
Adafruit_GC9A01A display(TFT_CS, TFT_DC, TFT_RST);

#define BLACK 0x0000    // Definition black color
#define BLUE 0x001F     // Definition blue color
#define RED 0xF800      // Definition red color
#define WHITE 0xFFFF    // Definition white color
#define GREY 0x4A49     // Definition grey color
#define GREEN 0x07E0    // Definition green color
#define CYAN 0x07FF     // Definition cyan color
#define MAGENTA 0xF81F  // Definition magenta color
#define YELLOW 0xFFE0   // Definition yellow color
int colsel;             // Variable colorselect

#define degrad 0.0174532925   // Variable with the value in radians of 1 degree of circumference.
float pi = 3.1415926535;      // Constant PI

int cent_x = 120;             // Variable with the X coordinate value of the center of the dial for the IAS.
int cent_y = 120;             // Variable with the Y coordinate value of the center of the dial for the IAS.
int radius = 100;             // Variable dial radius
int IASold = 0;               // Variable IAS old
int IASrec = 0;               // Variable IAS received
char IASout[4];               // Character string IASout from IASold
String(IASoutold);            // String IASold for display print
int i = 0;                    // Counter

float arco_x;                 // Variable with the X outer coordinate of the arc for IAS display.
float arco_y;                 // Variable with the Y outer coordinate of the arc for IAS display.
float arci_x;                 // Variable with the X inner coordinate of the arc for IAS display.
float arci_y;                 // Variable with the Y inner coordinate of the arc for IAS display.
float angle = 0;    // Variable for the distance between the values on the IAS dial.
float IAS;                    // Variable for the IAS value dial.

// setup() RUNS ONCE AT PROGRAM STARTUP ------------------------------------

void setup() {
  
  Serial.begin(115200);     // Initializes the Serial Monitor.
  #define CORNER_RADIUS 45  // All ILI9341 & GC9A01A TFTs (320x240)/(240x240)
  display.begin();          // Initialize display hardware
  create_dial_IAS();        // Call to the “create_dial_IAS” method to draw the IAS and humidity values scales.
}

// MAIN LOOP, REPEATS FOREVER ----------------------------------------------

void loop() {
  IASrec = 230;
  delay(200);
  IASrec = 0;
  for (i = 0; i < 115; i++) {
    IASrec = IASrec + 2;
    create_pointer_IAS();
  }
  i = 0;
  for (i = 115; i > 0; i--) {
    IASrec = IASrec - 2;
    create_pointer_IAS();
  }
  i = 0;
}

void create_dial_IAS() {                                // Method to draw the IAS values scale.

  display.setRotation(0);                               // No screen rotation setting (0 degrees).
  display.fillScreen(BLACK);                            // Fill display black

  for (IAS = 0; IAS <= 235; IAS += 5) {                 // Loop to display divisions every 5 degrees on the IAS scale.
    if (IAS <= 77) colsel = MAGENTA;                    // Assign color to speed
    if (IAS > 77 && IAS <= 140) colsel = WHITE;         // Assign color to speed
    if (IAS > 140 && IAS <= 185) colsel = GREEN;        // Assign color to speed
    if (IAS > 185 && IAS <= 222) colsel = YELLOW;       // Assign color to speed
    if (IAS > 222) colsel = RED;                        // Assign color to speed
    angle = (IAS * degrad - 4);                         // Needle positioning angle for a IAS value in radians.
    arco_x = (cent_x + ((radius + 6) * cos(angle)));    // Calculation to know the outer position on the X axis of the circumference of the radian value obtained.
    arco_y = (cent_y + ((radius + 6) * sin(angle)));    // Calculation to know the outer position on the Y axis of the circumference of the radian value obtained.
    display.fillCircle(arco_x, arco_y, 1, colsel);      // A circle of size 1 is drawn at the obtained X and Y coordinates.
  }

  for (IAS = 0; IAS <= 235; IAS += 20) {                // Loop to display divisions every 20 degrees on the IAS scale.
    if (IAS <= 77) colsel = MAGENTA;
    if (IAS > 77 && IAS <= 140) colsel = WHITE;
    if (IAS > 140 && IAS <= 185) colsel = GREEN;
    if (IAS > 185 && IAS <= 222) colsel = YELLOW;
    if (IAS > 222) colsel = RED;
    angle = (IAS * degrad - 4);                                 // Needle positioning angle for a IAS value, must be converted to position in radians.
    arco_x = (cent_x + ((radius + 6) * cos(angle)));  // Calculation to know the outer position on the X axis of the circumference of the radian value obtained.
    arco_y = (cent_y + ((radius + 6) * sin(angle)));  // Calculation to know the outer position on the Y axis of the circumference of the radian value obtained.
    display.fillCircle(arco_x, arco_y, 3, colsel);               // A red circle of size 2 is drawn at the obtained X and Y coordinates.
    angle = (IAS * degrad - 4);                                 // Needle positioning angle for a IAS value, must be converted to position in radians.
    arci_x = (cent_x + ((radius + 2) * cos(angle)));  // Calculation to know the inner position on the X axis of the circumference of the radian value obtained.
    arci_y = (cent_y + ((radius + 2) * sin(angle)));  // Calculation to know the inner position on the Y axis of the circumference of the radian value obtained.
    display.fillCircle(arci_x, arci_y, 3, colsel);
  }

  for (IAS = 222; IAS < 225; IAS++) {  // Loop to display divisions every 40 degrees on the IAS scale.
    colsel = RED;
    angle = (IAS * degrad - 4);                                 // Needle positioning angle for a IAS value, must be converted to position in radians.
    arco_x = (cent_x + ((radius + 6) * cos(angle)));  // Calculation to know the outer position on the X axis of the circumference of the radian value obtained.
    arco_y = (cent_y + ((radius + 6) * sin(angle)));  // Calculation to know the outer position on the Y axis of the circumference of the radian value obtained.
    angle = (IAS * degrad - 4);                                  // Needle positioning angle for a IAS value, must be converted to position in radians.
    arci_x = (cent_x + ((radius - 20) * cos(angle)));  // Calculation to know the inner position on the X axis of the circumference of the radian value obtained.
    arci_y = (cent_y + ((radius - 20) * sin(angle)));  // Calculation to know the inner position on the Y axis of the circumference of the radian value obtained.
    display.drawLine(arci_x, arci_y, arco_x, arco_y, colsel);
  }

  display.setTextColor(WHITE);
  display.setTextSize(3);       // Text size 3.
  display.setCursor(85, 90);    // Coordinates of the first letter of the text to be written.
  display.print("IAS");         // Text to be written on the screen.
  display.setTextSize(2);       // Text size 2.
  display.setCursor(110, 180);  // Coordinates of the first letter of the text to be written.
  display.print("km/h");        // Text to be written on the screen.

  display.setTextColor(YELLOW);
  display.setCursor(160, 66);   // Coordinates of the first character of the next number to be written.
  display.print("200");         // The number 200 will be written on the screen.
  display.setTextColor(GREEN);
  display.setCursor(130, 38);   // Coordinates of the first character of the next number to be written.
  display.print("160");         // The number 160 will be written on the screen.
  display.setCursor(75, 38);
  display.setTextColor(WHITE);
  display.print("120");
  display.setCursor(47, 66);
  display.print("80");
  display.setTextColor(MAGENTA);
  display.setCursor(35, 127);
  display.print("40");
  display.setCursor(60, 175);
  display.print("0");

  display.drawRect(85, 117, 110, 56, WHITE);
  display.drawRect(84, 116, 112, 58, WHITE);
  delay(500);
}

void create_pointer_IAS() {
  while (IASold != IASrec) {
    if (IASold <= IASrec) {
      if (IASold <= 77) colsel = MAGENTA;
      if (IASold > 77 && IASold <= 140) colsel = WHITE;
      if (IASold > 140 && IASold <= 185) colsel = GREEN;
      if (IASold > 185 && IASold <= 222) colsel = YELLOW;
      if (IASold > 222) colsel = RED;
      angle = (IASold * degrad - 4);                               // Needle positioning angle for a IAS value, must be converted to position in radians.
      arco_x = (cent_x + ((radius + 17) * cos(angle)));  // Calculation to know the outer position on the X axis of the circumference of the radian value obtained.
      arco_y = (cent_y + ((radius + 17) * sin(angle)));  // Calculation to know the outer position on the Y axis of the circumference of the radian value obtained.
      display.fillCircle(arco_x, arco_y, 6, colsel);                // A white circle of size 2 is drawn at the obtained X and Y coordinates.
      dtostrf(IASold + 1, 3, 0, IASout);
      display.setTextSize(5);      // Text size 2.
      display.setCursor(97, 127);  // Coordinates of the first letter of the text to be written.
      display.setTextColor(BLACK);
      display.print(IASoutold);
      display.setCursor(97, 127);  // Coordinates of the first letter of the text to be written.
      display.setTextColor(colsel);
      display.print(IASout);
      IASoutold = (IASout);
      IASold++;
    }
    if (IASold > IASrec) {
      if (IASold <= 77) colsel = MAGENTA;
      if (IASold > 77 && IASold <= 140) colsel = WHITE;
      if (IASold > 140 && IASold <= 185) colsel = GREEN;
      if (IASold > 185 && IASold <= 222) colsel = YELLOW;
      if (IASold > 222) colsel = RED;

      angle = (IASold * degrad - 4);                               // Needle positioning angle for a IAS value, must be converted to position in radians.
      arco_x = (cent_x + ((radius + 17) * cos(angle)));  // Calculation to know the outer position on the X axis of the circumference of the radian value obtained.
      arco_y = (cent_y + ((radius + 17) * sin(angle)));  // Calculation to know the outer position on the Y axis of the circumference of the radian value obtained.
      display.fillCircle(arco_x, arco_y, 6, BLACK);                      // A white circle of size 2 is drawn at the obtained X and Y coordinates.
      dtostrf(IASold - 1, 3, 0, IASout);
      display.setTextSize(5);      // Text size 2.
      display.setCursor(97, 127);  // Coordinates of the first letter of the text to be written.
      display.setTextColor(BLACK);
      display.print(IASoutold);
      display.setCursor(97, 127);  // Coordinates of the first letter of the text to be written.
      display.setTextColor(colsel);
      display.print(IASout);
      IASoutold = (IASout);
      IASold--;
    }
    /*
    Serial.println(IASoutold);
    Serial.println(IASout);
    Serial.println(IASrec);
    Serial.println("");
    */
  }
}

Txs for your remark,
here is the code....
b.r.
Wolfgang

and thats what it does

Where?

You will need to modify the Adafruit GFX library to display characters and lines in mirror-reversed positions.

above the pictures.....
Yep, modifying the library seems a good idea- but how- I have no clue what to change... :slight_smile:
b.r.
Wolfgang

You will need to study the GFX library code to make sure, but replacing the following function, according to the suggestion by @6v6gt in post #5, would be a place to start.

      writePixel(y0, x0, color);

That won't work for plots that make use of the functions drawFastRawVLine and drawFastRawHLine.

so it would be:
WritePixel(240-y0,x0,color);
Hell of work to do it manually, there must be an easier way by bit juggling.
but txs anyway,

Could you avoid changing the library code by mirroring the display optically?

By adding a second mirror (in addition to the mirror which is the inside surface of the windscreen) the original image will not need to be drawn as a mirror image.

OK. Then I'd look at the following two functions declared in Adafruit_GC9A01A.h

void setRotation(uint8_t r);
void invertDisplay(bool i);

Maybe a combination of both can achieve mirroring.

I don't think so. invertDisplay() Enables/Disables display color inversion, it has nothing to do with screen orientation and just rotating it won't cut it obviously.


Looking at the datasheet, the GC9A01A controller does support mirroring on the hardware side. Explore the doc and the Memory Access Control register PAGE 127

BIT 6 (MX) is the column Address Order which would drive an horizontal mirror

BIT 7 (MY) is the Row Address Order which would drive a vertical mirror

The library does not expose modifying those bits but does have the setRotation() function that accesses that register

Note that rotation 0 does use m = (MADCTL_MX | MADCTL_BGR); so does set the MADCTL_MX bit, which flips the screen horizontally compared to the controller’s native scan direction. This is likely intentional to match the physical orientation and wiring of the display panel so the image appears upright ➜ the library’s “rotation 0” includes an horizontal flip as its baseline.

Since rotation does play with mirroring, you could add a variant to the library's rotation function with more parameters

Try something like

void Adafruit_GC9A01A::setRotationAndMirror(uint8_t r, bool xMirror, bool yMirror) {
  rotation = r % 4;
  uint8_t madctl = MADCTL_BGR;

  switch (rotation) {
    case 0:
      madctl |= MADCTL_MX;
      _width  = GC9A01A_TFTWIDTH;
      _height = GC9A01A_TFTHEIGHT;
      break;
    case 1:
      madctl |= MADCTL_MV;
      _width  = GC9A01A_TFTHEIGHT;
      _height = GC9A01A_TFTWIDTH;
      break;
    case 2:
      madctl |= MADCTL_MY;
      _width  = GC9A01A_TFTWIDTH;
      _height = GC9A01A_TFTHEIGHT;
      break;
    case 3:
      madctl |= MADCTL_MX | MADCTL_MY | MADCTL_MV;
      _width  = GC9A01A_TFTHEIGHT;
      _height = GC9A01A_TFTWIDTH;
      break;
  }

  if (xMirror)
    madctl |= MADCTL_MX;
  else
    madctl &= ~MADCTL_MX;

  if (yMirror)
    madctl |= MADCTL_MY;
  else
    madctl &= ~MADCTL_MY;

  sendCommand(GC9A01A_MADCTL, &madctl, 1);
}

and in your source code you have the create_dial_IAS() function

void create_dial_IAS() {                                // Method to draw the IAS values scale.
  display.setRotation(0);                               // No screen rotation setting (0 degrees).
...

change it to

void create_dial_IAS() {                                // Method to draw the IAS values scale.
  display.setRotation(0, true, false);                  // No screen rotation setting (0 degrees) but mirror on X
...

play with the 2 extra params to match the mirroring you want (X, Y or both)

2 Likes

Many Txs for your comprehensive explanation.
Was looking at this damned MADCTL, but had no clue how to change for the better.
Will give it a try tommorow, already too tired tonight..
b.r.
Wolfgang

Nah, it is trivial. A single text editor command can globally change all function call instances in the library of "WritePixel(" to "WritePixelMirrored("

Then write and add that tiny new function to the library, which could look something like this

WritePixelMirrored (int x, int y, int color) {
WritePixel(240-x, y, color);
}

Dear Jackson,
Following error code:
C:\Users\wfeic\Desktop\Arduino_GC9A01A_Dial\Arduino_GC9A01A_Dial.ino: In function 'void create_dial_IAS()':
C:\Users\wfeic\Desktop\Arduino_GC9A01A_Dial\Arduino_GC9A01A_Dial.ino:75:10: error: 'class Adafruit_GC9A01A' has no member named 'setRotationAndMirror'; did you mean 'setRotation'?
display.setRotationAndMirror(0, true, false);
^~~~~~~~~~~~~~~~~~~~
setRotation
exit status 1

Compilation error: 'class Adafruit_GC9A01A' has no member named 'setRotationAndMirror'; did you mean 'setRotation'?

As being a pretty noob, I tried to make a class, but failed again...

Might be something here ?

Mirrored

Txs,
have read it many times, could not get a clue out of it...