Artificial Horizon display using a gyro

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.

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

#include 

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

// For S6D02A1 based TFT displays
//#include          // 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           // 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          // 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");


}

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.

Demo_artificial_horizon_7.ino (8.8 KB)

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.

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 :)

@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

Demo_artificial_horizon_9.ino (9.68 KB)

Thank you for everything Bodmer, you the man!

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.

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.

No, 1K and 2.2K" to ground.

5*(2.2/(2.2+1))=3.43V which will be fine and is thing the 3.3V +/-0.3V limit for the driver inputs

Meant to do it like this:

volt divider.jpg

i3dm: Meant to do it like this:

Yes, that is right :)

Hello guys. I'm trying to make it work with the ST7735 controller displays. But I'm getting lots of unwanted pixels on screen as the roll and pitch does its thing. Any sugestions?

@Leonardoxms

Are you using the sketch attached to post #27?

What changes have you made?

Are you using this graphics library?

If so, do all the other example sketches work OK?

Which Arduino are you using?

How have you wired the display to your Arduino?

Hello. I´m using arduino pro mini. I din´t make any changes to the code. I´m also using the sketch and LIB you suggeted.

I´ve wired this way:

sclk -13 mosi -11 cs - 9 dc - 8 rst - 7

The display module that I´m using is this one:

http://forum.arduino.cc/index.php?topic=125034.0

thanks

Run the sketch here.

What does it report?

What voltage are you running the pro mini on?

Have you used series resistors in the data/control lines?

Report:

TFT driver register values:

Register 0x01: 0x00 Register 0x04: 0x7C89F0 Register 0x09: 0x610000 Register 0x0A: 0x08 Register 0x0B: 0x00 Register 0x0C: 0x06 Register 0x0D: 0x00 Register 0x0E: 0x00 Register 0x0F: 0x00 Register 0x2E: 0x281400 Register 0xDA: 0x7C Register 0xDB: 0x89

Register 0xDC: 0xF0

Looks like driver chip is: ST7735 (empirical value)

I´m running on 5V and I didn´t use resistors in the data/control lines.

https://drive.google.com/file/d/0BzKrt8UWkNbRZFJqQlVKQ21GTlk/view?usp=sharing

In the link above, you can see the problem I´m having.

The display driver is not 5V tolerant. Put series resistors in the control and data lines as follows:

pro mini digital pin 7 through a 1K2 resistor to display RST pro mini digital pin 8 through a 1K2 resistor to display DC pro mini digital pin 9 through a 1K2 resistor to display CS pro mini digital pin 11 through a 1K2 resistor to display MOSI pro mini digital pin 13 through a 1K2 resistor to display SCLK

Did all that. Still presenting the same symptoms. Any other suggestions?

Which version of the Arduino IDE are you using?

Do other example sketches that come with the library work OK?

One problem is that your display shows random pixels on two edges, this is indicative of the wrong setup. Try different TAB_COLOUR options in User_Setup.h and find which one gets rid of those bad edge pixels. Edit the file, save, recompile and upload the sketch to try each option.

bodmer:
@Leonardoxms

Are you using the sketch attached to post #27?

What changes have you made?

Are you using this graphics library?

If so, do all the other example sketches work OK?

Which Arduino are you using?

How have you wired the display to your Arduino?

Hi!

I use your sketch from post #27 on Uno with ILI9341 2.8’’ 320x240 and have a similar problem with unwanted pixels. I have successfully changed resolution and orientation in the code, already tried different settings in User_Setup to get rid of them. Graphics test sketch running fine. Adafruit library render good image without any troubles, but really slow.

I hope you could help me. Thanks in advance!

@imolodkin

The redraw technique used works best on small displays but if you make the following changes to the post #27 sketch then it should fix your missing pixels. If it does not then post your sketch so I can run it and see what changes you have made.

Near the start of sketch change the line as follows:

#define HOR 400    // Horizon vector line length ### was 172

Then use this adapted drawHorizon() function:

// #########################################################################
// Draw the horizon with a new roll (angle in range -180 to +180)
// #########################################################################

void drawHorizon(int roll, int pitch)
{
  // Calculate coordinates for line start
  float sx = cos(roll * DEG2RAD);
  float sy = sin(roll * DEG2RAD);

  int16_t x0 = sx * HOR;
  int16_t y0 = sy * HOR;
  int16_t xd = 0;
  int16_t yd = 1;
  int16_t xdn  = 0;
  int16_t ydn = 0;

  if (roll > 45 && roll <  135) {
    xd = -1;
    yd =  0;
  }
  if (roll >=  135)             {
    xd =  0;
    yd = -1;
  }
  if (roll < -45 && roll > -135) {
    xd =  1;
    yd =  0;
  }
  if (roll <= -135)             {
    xd =  0;
    yd = -1;
  }

  if ((roll != last_roll) || (pitch != last_pitch))
  {
    xdn = 6 * xd;
    ydn = 6 * yd;
    tft.drawLine(XC - x0 - xdn, YC - y0 - ydn - pitch, XC + x0 - xdn, YC + y0 - ydn - pitch, SKY_BLUE);
    tft.drawLine(XC - x0 + xdn, YC - y0 + ydn - pitch, XC + x0 + xdn, YC + y0 + ydn - pitch, BROWN);
    xdn = 5 * xd;
    ydn = 5 * yd;
    tft.drawLine(XC - x0 - xdn, YC - y0 - ydn - pitch, XC + x0 - xdn, YC + y0 - ydn - pitch, SKY_BLUE);
    tft.drawLine(XC - x0 + xdn, YC - y0 + ydn - pitch, XC + x0 + xdn, YC + y0 + ydn - pitch, BROWN);
    xdn = 4 * xd;
    ydn = 4 * yd;
    tft.drawLine(XC - x0 - xdn, YC - y0 - ydn - pitch, XC + x0 - xdn, YC + y0 - ydn - pitch, SKY_BLUE);
    tft.drawLine(XC - x0 + xdn, YC - y0 + ydn - pitch, XC + x0 + xdn, YC + y0 + ydn - pitch, BROWN);
    
    xdn = 3 * xd;
    ydn = 3 * yd;
    tft.drawLine(XC - x0 - xdn, YC - y0 - ydn - pitch, XC + x0 - xdn, YC + y0 - ydn - pitch, SKY_BLUE);
    tft.drawLine(XC - x0 + xdn, YC - y0 + ydn - pitch, XC + x0 + xdn, YC + y0 + ydn - pitch, BROWN);
  }
  xdn = 2 * xd;
  ydn = 2 * yd;
  tft.drawLine(XC - x0 - xdn, YC - y0 - ydn - pitch, XC + x0 - xdn, YC + y0 - ydn - pitch, SKY_BLUE);
  tft.drawLine(XC - x0 + xdn, YC - y0 + ydn - pitch, XC + x0 + xdn, YC + y0 + ydn - pitch, BROWN);

  tft.drawLine(XC - x0 - xd, YC - y0 - yd - pitch, XC + x0 - xd, YC + y0 - yd - pitch, SKY_BLUE);
  tft.drawLine(XC - x0 + xd, YC - y0 + yd - pitch, XC + x0 + xd, YC + y0 + yd - pitch, BROWN);

  tft.drawLine(XC - x0, YC - y0 - pitch,   XC + x0, YC + y0 - pitch,   TFT_WHITE);

  last_roll = roll;
  last_pitch = pitch;

}

Since the larger display has ~ 4x the number of pixels to draw I would expect the code to run 4x slower than for a 160 x 128 display and thus the text/numbers will flicker when it is redrawn.