TFT display with Uno: Shift (scroll) sensor reading graph to the left

Hi,

I am experimenting with Arduino Uno and a 1.17" TFT display.

I went through the TFTGraph example and now I want to modify my code a bit (not sure if 'bit' is really 'bit' though) to achieve something else.

I want to read a voltage at analogRead(A0), draw it as a vertical line at location TFTscreen.width() and then propagate it (shift it or scroll it, if you wish alternatively) towards left of the screen, while a new sensor value is read and so on. (The operation is like a mini-oscilloscope, but with the graph moving from right-to-left?).

For a LCD screen environment, I noticed that there is a handy command named "scrollDisplayLeft()', which is close (although quite different) to what I want to implement on my TFT screen too. However, within the TFT library there is no such a command at all.

Is there any other library I could access to draw a 'scroll graph' on a TFT display?

I have already attempted to write some code around this, but it never seems to become finalized as I get lost with the 'for' and 'xpos--' statements.. As an alternative approach, I am thinking to simplify the problem by drawing a single vertical line at xpos = TFTscreen.wifth() and then make it scroll to the left..

Cheers.

I am not familiar with the library but if no function exists to do what you want then you will need to write your own.

One approach would be to store the data in an array then to write it to the screen at the appropriate X position. If you already have some code that does not work then why not post it for advice ?

That's my code so far (sorry, it's unfinished.. :slightly_frowning_face: )
Also, sorry for the heavy commenting, but it always helps me a lot to remember things.
(already spotted an error after the erase instance, as I am erasing the 'copied' version)

#include <TFT.h>  // Arduino LCD library
#include <SPI.h>
 
 // pin definition for the Uno
#define cs   10
#define dc   9
#define rst  8  

TFT TFTscreen = TFT(cs, dc, rst);

  //I define TFTscreen.width() as wTFT
int wTFT = TFTscreen.width();  // or wTFT=160

  //I define TFTscreen.height() as hTFT
int hTFT = TFTscreen.height(); //or hTFT=120

void setup(){
  // initialize the serial port
  Serial.begin(9600);

  // initialize the display
  TFTscreen.begin();

  // set TFT background colour to white
  TFTscreen.background(255, 255, 255);

    //set colour of lines to be drawn
TFTscreen.stroke(0,0,255);

  }

void loop() {
for (wTFT=160; wTFT>=1; wTFT--)
  {
  //read value of analog sensor A0 and 
  //map it to TFT screen height (e.g.128px)
int drawheight = map(analogRead(A0), 0, 1023, 0, hTFT );

  //Draw first (vertical) line at the right edge of TFT screen, i.e. at x=wTFT.
  //This line is effectively the 'generator' line, that 
  //will propagate towards the left of TFT screen.
TFTscreen.line( xpos, hTFT - drawHeight, xpos, hTFT );

 //wait for a moment until you read the next sample at analog sensor A0
delay(50);

  //move to the next x-position towards left
wTFT--;

  //copy the first vertical line drawn from location x = wTFT to location x = wTFT-1 
TFTscreen.line(wTFT, hTFT - drawHeight, wTFT, hTFT );

  //erase(clear) vertical line at starting poing x = wTFT
TFTscreen.stroke(0,0,255);
TFTscreen.line( wTFT, 0, wTFT, hTFT );

  //move to the beginning of 'for' loop and repeat

  }

}

UKHeliBob:
One approach would be to store the data in an array then to write it to the screen at the appropriate X position.

Thanks for the tip. I am currently working on this and I will declare an array to start with.

I guess two kind of routines need to be specified (?). One for filling (updating) the array with the analogRead(A0) values, and then one more routine which, 'copies' the values of the array and draws lines at appropriate x-positions. Between each copy action an 'erase' process needs to be defined to make sure that lines (which is the sensor value) are erased prior to the drawing of the new ones ..

I will post my updated (attempted..) sketch shortly.

Update.1 : I decided to alter drawing direction so that graph propagates from left to right of TFT display, as I noticed that this simplifies somehow counting and sketching..

A real headache to deal with this 'scroll graph' for something like 7hours or so for me (first time programmer..), but the following code seemed to work I think the way I wanted.
I've uploaded it into Uno and I will refine it.
There is significant 'flicker' I noticed, but I will deal with this at a later stage. I might just convert the line-graph into a 'points-graph' in a hope to accelerate 'scrolling'(?).

Again, sorry for the heavy commenting. When I become more experienced I promise to minimise them! :slight_smile:

#include <TFT.h>  // Arduino LCD library
#include <SPI.h>
 
 // pin definition for the Uno
#define cs   10
#define dc   9
#define rst  8  

TFT TFTscreen = TFT(cs, dc, rst);

  //I define TFTscreen.width() as wTFT
int wTFT = TFTscreen.width();  // or wTFT=160 for my TFT screen

  //I define TFTscreen.height() as hTFT
int hTFT = TFTscreen.height(); // or hTFT=120 for my TFT screen

int count;
int sensorArray[161];

void setup(){
  // initialize the serial port
Serial.begin(9600);

  // initialize the display
TFTscreen.begin();

  // set TFT background colour to white
TFTscreen.background(255, 255, 255);

  //set colour of lines to be drawn (red)
TFTscreen.stroke( 255, 0, 0 );

  //Declare an array with 160 elements
  //for storing sensor values across the TFT screen width
//int sensorArray[161];

  //initialize the array by zeroing all its elements
for (int count=0; count<=159; count++)  //counts up from 0 to 159
 {
	sensorArray[count] = 0; //this 'zeros' all array elements(?)
 }

}
  
void loop() {


  //read the current value of analog sensor A0 and 
  //map it to TFT screen height (e.g.128px)
int drawHeight = map(analogRead(A0), 0, 1023, 0, hTFT );

  //store this current sensor value within the array for future retrieval 
  //at the 1st element of the array (note: arrays are zero indexed)
sensorArray[0] = drawHeight; //this stores current drawHeight() value on the 1st element of array
  
 
  //----routine to start the drawing of 160 vertical lines across TFT display----
  //reminder:  wTFT = 160  
  //'read' all sensor values from the array and draw them
  //at the appropriate x-locations using the values stored in the array

  //draw 160 lines!
for ( count; count <= wTFT; count++ )  //this counts up from 1 to 160
  {
TFTscreen.line(count, hTFT - sensorArray[count-1], count, hTFT );
  }  //end of 'for' loop
 
 
  //----routine for Shift Process within the array----
  //within the array, shift elements to the right by one step, by copying the adjacent 
  //values found on the left of each element. Start the copying process 
  // from the right(last) element of the array!!! Leave the first [0] element intact!!!
for (count=wTFT; count>=2; count--) // count down from 160 to 2 
 {
sensorArray[count-1] = sensorArray[count-2]; //count=160, count=159 , ..., ..., count=3 , count=2
							     //159<-158 , 158<-157  , ..., ..., 2<-1    , 1<-0
				//explanation: value of element 158 is written to element 159, and so on...
 }
  
    //'freeze' graph for 1 sec for the viewer to enjoy!
delay(100);
  
  //erase(clear) ALL lines, (alternatively you can define a rect box 
  //which 'clears' the graph only, if other text has to remain on screen)
TFTscreen.background( 250, 250, 250);
 
  //go to start of loop and repeat graph drawing
  
  //other idea: draw a points instead of line to accelerate drawing process???
  
}

I have not looked in detail but this stood out

for ( count; count <= wTFT; count++ )  //this counts up from 1 to 160

Is there any reason why you don't initialise count in the for loop ? It is dangerous to assume that it will be 1 when the for loop starts and you could easily exceed the array limits or cause the for loop to start in the wrong place.

Another thing. You don't need to shift the array contents, which takes time. Just have a variable holding the index of next of the place to put data in the array (the tail of the data) and another one holding the index of where the start of valid data is (the head of the data) and adjust them when more data is added. When either exceeds the limits of the array (too high or too low) then reset it to index the other end of the array and carry on.

UKHeliBob:
I have not looked in detail but this stood out

for ( count; count <= wTFT; count++ )  //this counts up from 1 to 160

Is there any reason why you don't initialise count in the for loop ?

omg, you're very right, looking back to it I don't even know why it should start from 1 actually... :o

Even the removal of 'delay(100)' does not eliminate flickering..
The display erases and rewrites the graph making the entire screen to blink momentarily around each loop.

I am taking a note of your constructive comments, cheers. Programming it's like 'Chinese' to me at the moment, but I will give it a go.

I will update this post, hopefully soon ::slight_smile:

  //'freeze' graph for 1 sec for the viewer to enjoy!
  delay(100);

This is another example of the code not matching the comments.

How often do you want to read the input and update the display ?

Oh, thanks! I have been experimenting with different delays and I somehow managed to forget
changing the aforementioned comment, sorry.

Ideally, the sensor needs to read the analogRead(A0) about every 100-200msec or so.
I am definitely talking about sampling the input at least 4-5 times per second, not less.

I noticed that my code is spoiled by the background erase command.
When I remove completely this (seen at the end of the code)line:

TFTscreen.background( 250, 250, 250);

the graph draws smoothly, but of course this makes the code do something completely different from I what I want. So, I need to get rid of this in some way and find an alternative way to update the graph on the TFT display.

Update.1: Just thought of drawing two lines aligned on the same column: one with the background colour (which will effectively simulate the erase process, but on a column-basis) and the other one with the colour graph (sorry if I hadn't explained this well). Going to code it and check whether this would work..

The TFTscreen.background command was removed completely and was 'simulated' by drawing two lines of different colours on the same column instead, as I stated on my previous comment.

The new code is shown below. It has been successfully uploaded into Uno R3. Drawing is still not as fast as I was expecting (about 5-6 samples/sec - 'measured' with my eye(!), with each sample 1px wide), but it is much faster than before and there is no flickering whatsoever! It now almost fulfils my requirements.

As it is indicated within a comment at the end of the code, it is possible to replace lines with points, but I noticed that during fast sensor fluctuations (i.e. fast voltage changes) the line appears annoyingly 'dotted'..so better draw solid lines to get better aesthetics.

I really do not know if it is possible to code this sketch in a manner that can result in a faster drawing responses, otherwise I will keep on updating this post and share my results here.

/*
This program reads the voltage applied on the sensor analogRead(A0)
and draws a graph on a TFT screen proportional to the sensor voltage
(0-5V). The graph propagates (scrolls) from left
to right as the sensor reading is updated.

The sketch was written and tested around a TFT display sized 160x128 px
(Arduino LDC module with an Arduino Uno R3).
It can however be scaled into different TFT displays with
the appropriate amendments within the code (not difficult to do!).

I noticed that the scrolling rate of the graph is not
fast, about 4-5 pixels per second.
So, for faster applications this sketch won't yield graphs with accurate
results (fast flactuations appearing at analogRead(A0) 
won't be captured). But, the sketch can provide the basis
for further development and improvement around graph drawing.
*/

#include <TFT.h>  // Arduino LCD library
#include <SPI.h>
 
 // pin definition for the Uno
#define cs   10
#define dc   9
#define rst  8  

TFT TFTscreen = TFT(cs, dc, rst);

  //I define TFTscreen.width() as wTFT
int wTFT = TFTscreen.width();  // or wTFT=160 for my TFT screen

  //I define TFTscreen.height() as hTFT
int hTFT = TFTscreen.height(); // or hTFT=120 for my TFT screen

int count;
int sensorArray[161];

void setup(){
  // initialize the serial port
Serial.begin(9600);

  // initialize the display
TFTscreen.begin();

  // set TFT background colour to yellow
TFTscreen.background(255, 255, 0);

  //initialize the array by zeroing all its elements
for (int count=0; count<=159; count++)  //counts up from 0 to 159
 {
	sensorArray[count] = 0; //this 'zeros' all array elements(?)
 }

}
  
void loop() {

  //read the current value of analog sensor A0 and 
  //map it to TFT screen height (e.g.128px)
int drawHeight = map(analogRead(A0), 0, 1023, 0, hTFT );

  //store this current sensor value within the array for future retrieval 
  //at the 1st element of the array (note: arrays are zero indexed)
sensorArray[0] = drawHeight; //this stores current drawHeight() value on the 1st element of array
  
 
  //----routine to start the drawing of 160 vertical lines across TFT display----
  //reminder:  wTFT = 160  
  //'read' all sensor values from the array and draw them
  //at the appropriate x-locations using the values stored in the array

  //draw 160 lines!
for ( int count = 1; count <= wTFT; count++ )  //this counts up from 1 to 160
  {
  //set colour of lines to be drawn (red)

TFTscreen.stroke( 255, 0, 0 ); //draw red colour first
TFTscreen.line(count, hTFT - sensorArray[count-1], count, hTFT );

TFTscreen.stroke( 255, 255, 0 ); //draw yellow colour lines to simulate the erase process
TFTscreen.line(count, 1, count, hTFT-sensorArray[count-1] );
  }  //end of 'for' loop
 
 
  //----routine for Shift Process within the array----
  //within the array, shift elements to the right by one step, by copying the adjacent 
  //values found on the left of each element. Start the copying process 
  // from the right(last) element of the array!!! Leave the first [0] element intact!!!
for (count=wTFT; count>=2; count--) // count down from 160 to 2 
 {
sensorArray[count-1] = sensorArray[count-2]; //count=160, count=159 , ..., ..., count=3 , count=2
							     //159<-158 , 158<-157  , ..., ..., 2<-1    , 1<-0
				//explanation: value of element 158 is written to element 159, and so on...
 }
  
    //'freeze' graph for a while for the viewer to enjoy!
//delay(100);
  
  //erase(clear) ALL lines, (alternatively you can define a rect box 
  //which 'clears' the graph only, if other text has to remain on screen)
//TFTscreen.background( 255, 255, 0);
  
  //other idea: draw a points instead of lines to accelerate drawing process???
  
}