1
\$\begingroup\$

Problem Summary

I have a “square” rotating on my 3.5 Inch TFT screen (ILI9486 driver) with dots/circles at each of its 4 corners. It is connected to an Arduino MEGA 2560 There are a few issues with how it is currently implemented that I would like guidance on:

  1. The image (square and dots) is “flashing” due to the constant clearing and redisplaying of the components (lines and dots);

  2. The square is not fully closed (I can minimise this issue by reducing the angle of rotation applied in each step, but it becomes too slow);

Ideally, i'd like a smoothly rotating square that is fully closed and does not flash. A faster rotation speed than what is depicted below would be desirable.

I’m pretty sure I know why these issues are occurring, but I’m not sure how I should best go about fixing them.

More detail on the issues

This code/project is just to familiarise myself with the lower-level theory of rotating images on a screen. I am following 3Blue1Brown’s linear algebra series and became interested in using matrices as transformations. I will eventually work up to the 3D case, but I’d prefer to get the 2D technical issues out of the way first.

  1. Issue 1: The Flashing

As I stated in the problem summary, the square is “flashing”. See below for a video. The flashing is caused by how I clear and display the square. The process is essentially:

Foreach vertex:
    Calculate new vertex positions
    Clear the dot at the previous position of that vertex
    Clear the line connecting that previous dot to its neighbouring vertex
    Display the dot at the new vertex position
    Display the line connecting that dot to its neighbouring vertex 

Here is a video capture showing the flashing I am describing.

Here is a slow motion capture showing how the display is updating according to the process outlined above.

Is there anything I can do to improve this? Increasing the angle of rotation helps, but I run into the second issue, below.

  1. Issue 2: The unconnected line

My second major issue is that the square is not “closed”. It's not apparent in the images above because they had a smaller angle of rotation. This image shows what I’m talking about:

enter image description here

See this video here to see it in action.

I’ve labelled the vertices 1 – 4 in the picture. I know that the issue is caused by how I’m updating the square – the line connecting vertices 1 and 4 is only valid for a very short time interval – it is drawn just after vertex 4 is drawn, after which the position of 1 is immediately updated, so the line is pointing to the old position of vertex 1 for the majority of the time. I’ve tried altering the order in which lines are drawn but this only shifts the problem to different vertices.

Reducing the angle by which the square rotates minimises the issue, but then the square is rotating too slowly for what I want to use it for.

Code

#include <MCUFRIEND_kbv.h>
#include <Adafruit_GFX.h>    // Core graphics library

#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin
#define LCD_CS A3   // Chip Select goes to Analog 3
#define LCD_CD A2  // Command/Data goes to Analog 2
#define LCD_WR A1  // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0

// Assign human-readable names to some common 16-bit color values:
#define  BLACK   0x0000
#define WHITE   0xFFFF

typedef struct {
    float x;
    float y;
    bool draw;
} vertex_2d;

vertex_2d vertices[4]; //a square contains 4 vertices
vertex_2d old_vertices[4]; 

uint8_t point_size = 5;
float angle = 0.2;

MCUFRIEND_kbv tft;
char *msg[] = { "PORTRAIT", "LANDSCAPE", "PORTRAIT_REV", "LANDSCAPE_REV" };
uint8_t aspect = 1;

void rotate_2D(vertex_2d *vert, float angle){
    float old_x = vert->x;
    vert->x = old_x * cos(angle) - vert->y * sin(angle);
    vert->y = old_x * sin(angle) + vert->y * cos(angle);
}

void translate_2d(vertex_2d *vert, int x_trans, int y_trans){
    vert->x = vert->x + x_trans;
    vert->y = vert->y + y_trans;
}

void setup() {
  tft.reset();
  uint16_t identifier = tft.readID();
  tft.begin(identifier); 

  tft.fillScreen(BLACK);
  tft.setRotation(1);

  vertices[0] = {190, 110, true}; //vertex 1
  vertices[1] = {290, 110, true}; //vertex 2
  vertices[2] = {290, 210, true}; //vertex 3
  vertices[3] = {190, 210, true}; //vertex 4

    for (int i = 0; i < 4; i++){
        old_vertices[i] = vertices[i];

    }
}

void loop() {
    for (int i = 0; i < 4; i++){
        //translate back to origin, rotate and then translate back into position
        translate_2d(&vertices[i], -240, -160);
        rotate_2D(&vertices[i], angle);
        translate_2d(&vertices[i], 240, 160);
    }

  for (int i = 0; i < 4; i++){

    if (abs(old_vertices[i].x - vertices[i].x) > 1 || abs(old_vertices[i].y - vertices[i].y) > 1){ //if the dots have moved since the last loop

        //clear the previous position
        tft.fillCircle(old_vertices[i].x, old_vertices[i].y, point_size, BLACK);
        tft.drawLine(old_vertices[i].x, old_vertices[i].y, old_vertices[(i+1)%4].x, old_vertices[(i+1)%4].y, BLACK);

    }
        tft.fillCircle(vertices[i].x, vertices[i].y, point_size, WHITE);
        tft.drawLine(vertices[i].x, vertices[i].y, vertices[(i+1)%4].x, vertices[(i+1)%4].y, WHITE);
  }

  for (int i = 0; i < 4; i++) {
    old_vertices[i] = vertices[i];
  }

}

Screen

The screen is a 3.5” TFT Touch screen shield, connected directly to my Arduino MEGA 2560. It uses an ILI9486 driver. Bought from eBay.

\$\endgroup\$
  • 1
    \$\begingroup\$ Firstly you should precalculate the sin/cos values once when you set your step angle. This will probably double the speed of your code. Second you should write to a local bitmap and blit to the screen. This will stop the flicker caused by erasing the circles. You might also be able to make it work by tracking the lcd tearing line. \$\endgroup\$ – Jon Dec 29 '18 at 8:23
  • \$\begingroup\$ @Jon The square will eventually be rotated by user input, which is why I calculate the angles dynamically instead of creating a lookup table, but I will go ahead and implement this for this test regardless. I'll look into blitting the screen, haven't used the bitmap functionality yet, it seemed more computationally intensive to do it this way as i'll have to dynamically update the bitmap on each step but it will be worth trying regardless. Can you elaborate on tracking the LCD tearing line? I've done a quick google and I can't see how to track it. What would the purpose of this be? \$\endgroup\$ – daviegravee Dec 29 '18 at 9:28
  • \$\begingroup\$ Just precalc once for the user input and cache the values then. Sin in floating point on an avr is painfully slow. Blitting may be a little slower but will stop the flicker. \$\endgroup\$ – Jon Dec 29 '18 at 10:13
  • \$\begingroup\$ Re tearing line. The display is refreshed by scanning the pixels approximately every 1/60 second. You can read the current scan line and basically wait for it to pass your drawing location then work behind it. This gives you ~16ms to clear and redraw an area before the pixels you are working on will actually be sent to the panel. \$\endgroup\$ – Jon Dec 29 '18 at 10:16

Your Answer

By clicking "Post Your Answer", you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.