Go Down

Topic: Artificial Horizon display using a gyro (Read 10292 times) previous topic - next topic

i3dm

Just checked the library and it works fine with a Mini Pro. The only other gotcha I can think of (apart from not using hardware SPI pins and an edit error in User_Setup) is that you are using a really old and outdated version of the IDE. You need to use the latest 1.6.x IDE
im using the latest IDE version, perhaps you could run me through the connections? i cant find where the mistake is.

i3dm

i would like to end up with something similar to this:

https://www.youtube.com/watch?v=ta71EIkgIqo

i3dm

Just checked the library and it works fine with a Mini Pro. The only other gotcha I can think of (apart from not using hardware SPI pins and an edit error in User_Setup) is that you are using a really old and outdated version of the IDE. You need to use the latest 1.6.x IDE
managed to get library working, now could use a bit of help "translating" my code which uses the Adafruit Library, to yours :)

i3dm

bodmer, i changed my code to use your library but i still find it being slow.

here's what im trying to do and what happens:

1. fill the background with grpahics.
2. print a AH line on the existing filled screen at a certain angle.

now, in order to refresh (change line angle) i have to do:
3. fill the background with grpahics.
4. print a AH line on the existing filled screen at a certain (different) angle.

i guess the screen filling takes some time, because this causes a lot of flashing and delay in the screen.

any ideas how to do this quickly without the flashing and delays?
i did manage to print just the line, on a black ground, with a good refresh rate. but as soon as i add the background graphics, it all becomes slow.



david_prentice

Thanks for the video.   A picture is worth a thousand words.
A moving picture is worth even more!

As far as I know,  the ST7735 can not display a separate foreground and background.
Which means that updating the display is going to need some intelligence.

Either by redrawing each object that contains the "new" triangle.
Or by reading the GRAM and replacing the CYAN or GRAY in the new triangles.    Reading GRAM with an AVR is relatively slow.

I can do it both ways but it is fiddly getting the graphics to match.
I will wait until Bodmer comes up with a better method.

David.

i3dm

Thank you, this makes me believe im in the right direction.
i wonder how the guy in the  video did it.... perhaps he used a stronger processor.

so for now i can say i can actually do what i wanted, the only problem i have left is the (slow) speed it takes to fill up the screen with graphics.


bodmer

@i3dm

I have you example sketch running from post #13.  This runs at 8 frames per second (fps).

Most of the flicker comes from clearing the screen to black, then drawing the graphics.

It takes 46ms to clear the screen to black so since the TFT screen is internally refreshed at 70Hz you end up seeing it flashing. This time is determined by the 8MHz SPI rate. Because my library is highly optimised the Pro Mini ATMega 328 is actually managing to send bits at 7.12Mhz to clear the screen in that time, and it cannot go any faster without overclocking it.

There is not need to clear the screen to black since the new graphics image covers the whole screen anyway. Taking out the clear to black bring the frame rate to 13 fps (77ms to redraw the whole screen area with graphics) this shows that with some smart plotting code that you should be able to get the performance you want.

I am almost certain the "guy in the video" is using a processor which has more RAM, the image is being drawn to a "virtual" screen, then sent to the TFT in a burst so only the bits that have moved appear to changed. A 16 bit colour virtual screen would need 40Kbytes of RAM for a 160x128 display, so the Due or perhaps a Teensy could do this. The Due/Teensy can also DMA the virtual image to the display so the processor could be fetching the new Gyro values while the screen image is updateed.

Adafruit are moving towards this virtual screen in RAM approach (they call it a "canvas") which is a smart move as this is a method well suited to higher performance processors which will no doubt be adopted within the Arduino environment at some point in the future.

So to get the display flicker free on a humble Arduino like the Pro Mini is possible but you will need to either simplify the graphics you are plotting in the area that changes, or work out how to change just the tiny bits that need to change.

Alternatively you could use a screen that provides a co-processor to handle the graphics but then things get stupidly expensive compared to these nice little $3 displays.

Formerly Rowboteer (now a broken user profile!)

bodmer

#22
Aug 13, 2016, 11:09 pm Last Edit: Aug 13, 2016, 11:24 pm by bodmer
@i3dm

It dawned on me that I have an arc drawing function that can be used, this makes things much simpler.

So I have bashed together some demo code based on your sketch and have it running on an UNO with a circular horizon window.

It runs at 40 frames per second for 2 degree angle change increments, and about 50 frames per second for 1 degree angle changes which is not bad for a humble UNO!


Video here.

Let me know if this might suit your needs and I will tidy up the code.

Edit: Just done some more tests and it conveniently runs at 60 frames per second when the horizon is near to horizontal (this is because triangles are filled with horizontal line segments in the library).
Formerly Rowboteer (now a broken user profile!)

i3dm

Yes this could help very much, please post the code for testing, thank you.
PS - you also have an email from me.

bodmer

#24
Aug 14, 2016, 10:45 pm Last Edit: Aug 14, 2016, 11:35 pm by bodmer
Here's the "proof of concept" code. You will probably have trouble using it to draw arbitrary angles as there will be a tenancy to leaving small segments that are not correctly redrawn. This is due to rounding errors and a directional asymmetry in the arc drawing algorithm.

I will update the code to make it more suitable for your application and email you a copy.

Edit: There is no need to draw the four "20"'s during refresh, so these can be plotted during setup only. This saves a few milliseconds and then with 2 degree angle changes the UNO can update the screen at an impressive 70 frames per second.

Code: [Select]

// Demo code for artifical horizon display
// Written for a 160 x 128 TFT display

#include <SPI.h>

// Use ONE of these three libraries, comment out other two!

// For S6D02A1 based TFT displays
//#include <TFT_S6D02A1.h>         // Bodmer's graphics and font library for S6D02A1 driver chip
//TFT_S6D02A1 tft = TFT_S6D02A1(); // Invoke library, pins defined in User_Setup.h
//                                    https://github.com/Bodmer/TFT_S6D02A1

// For ST7735 based TFT displays
#include <TFT_ST7735.h>          // Bodmer's graphics and font library for ST7735 driver chip
TFT_ST7735 tft = TFT_ST7735();   // Invoke library, pins defined in User_Setup.h
//                                    https://github.com/Bodmer/TFT_ST7735

// For ILI9341 based TFT displays (note sketch is currently setup for a 160 x 128 display)
//#include <TFT_ILI9341.h>         // Bodmer's graphics and font library for ILI9341 driver chip
//TFT_ILI9341 tft = TFT_ILI9341(); // Invoke library, pins defined in User_Setup.h
//                                    https://github.com/Bodmer/TFT_ILI9341

#define REDRAW_DELAY 17 // minimum delay in milliseconds between display updates

#define HOR 63      // Horizon circle outside radius
#define HIR 24      // Horizon circle inside radius
#define HAW HOR-HIR // Horizon arc width (outside - inside radius)

#define BROWN 0x5140 //0x5960
#define SKY_BLUE   0x02B5 //0x0318 //0x039B //0x34BF
#define DARK_RED   0x8000
#define DARK_GREY  0x39C7

#define XC 64 // x coord of centre of horizon
#define YC 80 // y coord of centre of horizon

#define ANGLE_INC 2 // Angle increment for arc segments

#define DEG2RAD 0.0174532925

int roll_angle = 0;
int roll_delta = 90;

int demo_delta = ANGLE_INC;

unsigned long redrawTime = 0;


// #########################################################################
// Setup, runs once on boot up
// #########################################################################

void setup(void) {
  Serial.begin(115200);

  tft.begin();
  tft.setRotation(0);

  tft.fillScreen(TFT_BLACK);

  // Centre graphics
  // Draw the inner white arc (360 degrees = a circle)
  fillArc(XC, YC, 0, 360 / ANGLE_INC, HIR, HIR, 2, DARK_GREY);

  // Draw the red cross-hairs
  tft.fillRect(XC - 21, YC -  1, 42,  3, DARK_RED);
  tft.fillRect(XC -  1, YC - 21,  3, 42, DARK_RED);

  // Draw the two 180 degree arc segments for sky and ground
  fillArc(XC, YC, -90, 180 / ANGLE_INC, HOR, HOR, HAW, SKY_BLUE);
  fillArc(XC, YC,  90, 180 / ANGLE_INC, HOR, HOR, HAW, BROWN);

  // Draw fixed text
  tft.setTextColor(TFT_GREEN);
  tft.setCursor(10, 0);
  tft.print("SPD  LNAV WNAV PTH");
  tft.setCursor(10, 150);
  tft.print("Lior Z. AHI Display");
}

// #########################################################################
// Main loop, keeps looping around
// #########################################################################

void loop() {

  // Refresh the display at regular intervals
  if (millis() > redrawTime) {
    redrawTime = millis() + REDRAW_DELAY;

    unsigned long drawTime = millis(); // Test only

    // Change to ramp down the horizon angle for the demo and swap the colours for the direction change
    if (roll_angle > (360 - ANGLE_INC)) {
      demo_delta = -ANGLE_INC;
      roll_delta = -90;     // roll_delta is -90 for anticlockwise rotation
      //delay(500);
    }

    // Change to ramp up the horizon angle for the demo and swap the colours for the direction change
    if (roll_angle < ANGLE_INC) {
      demo_delta = ANGLE_INC;
      roll_delta = 90;     // roll_delta is +90 for clockwise rotation
      //delay(500);
    }

    // Draw the new parts of the two arc segments 180 degrees apart
    // If roll_angle is zero = horizontal
    fillArc(XC, YC, roll_angle + roll_delta, 1, HOR, HOR, HAW, SKY_BLUE);
    fillArc(XC, YC, roll_angle - roll_delta, 1, HOR, HOR, HAW, BROWN);

 
    drawInfo();

    // This is for the demo to produce an angle that changes
    roll_angle += demo_delta;
 
    //if (roll_angle == 90) delay(1000);

    if (millis() - drawTime > REDRAW_DELAY) Serial.println(millis() - drawTime); // Test only to see how long an update takes

    //delay(500);
  }
}


// #########################################################################
// Draw a circular or elliptical arc with a defined thickness
// #########################################################################

// x,y == coords of centre of arc
// start_angle = 0 - 359
// seg_count = number of 6 degree segments to draw (60 => 360 degree arc)
// rx = x axis outer radius
// ry = y axis outer radius
// w  = width (thickness) of arc in pixels
// colour = 16 bit colour value
// Note if rx and ry are the same then an arc of a circle is drawn

int fillArc(int x, int y, int start_angle, int seg_count, int rx, int ry, int w, unsigned int colour)
{
  // Fudge factor adjustment for this sketch (so horizon is horizontal when start anngle is 0)
  start_angle--;
 
  byte seg = ANGLE_INC; // Segments are INC degrees wide
  byte inc = ANGLE_INC; // Draw segments every INC degrees, increase for segmented ring

  // Calculate first pair of coordinates for segment start
  float sx = cos((start_angle - 90) * DEG2RAD);
  float sy = sin((start_angle - 90) * DEG2RAD);
  uint16_t x0 = sx * (rx - w) + x;
  uint16_t y0 = sy * (ry - w) + y;
  uint16_t x1 = sx * rx + x;
  uint16_t y1 = sy * ry + y;

  // Draw colour trapezoids every inc degrees
  for (int i = start_angle; i < start_angle + seg * seg_count; i += inc) {

    // Calculate pair of coordinates for segment end
    float sx2 = cos((i + seg - 90) * DEG2RAD);
    float sy2 = sin((i + seg - 90) * DEG2RAD);
    int x2 = sx2 * (rx - w) + x;
    int y2 = sy2 * (ry - w) + y;
    int x3 = sx2 * rx + x;
    int y3 = sy2 * ry + y;

    tft.fillTriangle(x0, y0, x1, y1, x2, y2, colour);
    tft.fillTriangle(x1, y1, x2, y2, x3, y3, colour);

    // Copy segment end to sgement start for next segment
    x0 = x2;
    y0 = y2;
    x1 = x3;
    y1 = y3;
  }
}


// #########################################################################
// Draw the information
// #########################################################################

void drawInfo(void)
{
  // side vertical lines
  tft.fillRect( 15, 30, 5, 100, DARK_GREY);
  tft.fillRect(108, 30, 5, 100, DARK_GREY);

  tft.setTextColor(TFT_WHITE);
  tft.setCursor(2, 30);
  tft.print("20");
  tft.setCursor(2, 122);
  tft.print("20");
  tft.setCursor(2, 50);
  tft.print("10");
  tft.setCursor(2, 102);
  tft.print("10");
  tft.setCursor(115, 30);
  tft.print("20");
  tft.setCursor(115, 122);
  tft.print("20");
  tft.setCursor(115, 50);
  tft.print("10");
  tft.setCursor(115, 102);
  tft.print("10");


}

Formerly Rowboteer (now a broken user profile!)

bodmer

#25
Aug 16, 2016, 12:01 am Last Edit: Aug 16, 2016, 12:05 am by bodmer
New code, new method of drawing. This one gives a full screen display with new graphic design and pitch as well as roll.

Performance is pretty good with a humble UNO and the display is virtually flicker free (video looks worse than it is due to frame rate aliasing with the camera).

Video here.

Sketch attached.
Formerly Rowboteer (now a broken user profile!)

i3dm

New code, new method of drawing. This one gives a full screen display with new graphic design and pitch as well as roll.

Performance is pretty good with a humble UNO and the display is virtually flicker free (video looks worse than it is due to frame rate aliasing with the camera).

Video here.

Sketch attached.
youre the best bodmer - this works amazing.
i still need to see why my display flickers a bit and i should be good to go :)

bodmer

@i3dm

The demo in post #25 only works over the roll range  +/-50 degrees, after that some plotting artifacts are left on the screen.

Attached is the latest that handles +/- 180 degree rolls.  Pitch is specified in pixels, the 10 degree marker line is 20 pixels from the centre, so you have to convert pitch angle to pixels.

This is the last version I will be producing in the near future! It works better than I expected so it will be added as an example to my libraries.


The flickering you are getting is due to brightness changes caused by power line fluctuations, not the graphics drawing.  Power the display from 5V, use 1K series resistors and 2K2 to GND resistor dividers on the logic lines to avoid pumping current into the display.

E.g. Arduino digital pin 13 through a 1K resistor to display SCL, add a 2K2 resistor from display SCL to GND.

Do this for RESET, AO, CS and SDA
Formerly Rowboteer (now a broken user profile!)

i3dm

Thank you for everything Bodmer, you the man!

i3dm

About the voltage divider, if i want to get 3.3V at the logic lines when arduino is suplying 5V, then the series resistor should be the 2.2k and 1k to ground.
that gices about 3.4v on logic line to LCD.

Go Up