ILI9341 CAN Display - Gauge rendering on display

Status
Not open for further replies.

Rezo

Well-known member
Hi all,

After spending months getting my automotive CAN BUS display working, I really want to beautify the UI and make it a bit more easier to read.
My ultimate goal is to display two gauges in the display in place of the two main readouts (Boost & AFR) but I am really struggling with code on this part - I am clueless here.

Here is what I have built so far:
IMG_0990.jpg

What I would like to add is two gauge displays as can be seen below - One for boost, ranging from -30 inHg up to 30 PSI. the other is an Air fuel ratio ranging from roughly 8 to 22.
defi.jpg aem_afr.jpg
As you can see, the boost gauge is not linear in its layout (90deg for vacuum, 180deg for boost - air fuel layout is roughly 270-300deg).

Hardware being used is a Teensy 3.2 with a can transceiver and a 2.8" ILI9341 display.
Im using FlexCan and ILI9341_t3 libraries to drive everything.

My questions are:
1. Is ILI9341_t3 capable of doing what I want?
2. Will I be sacrificing process speed for refresh rate?
3. Will the T3.2 be enough or should I upgrade to a T4.0?
4. Can anyone help me with writing the code for the gauges or provide examples that I can modify?


Cheers,
David
 
Hi all,

After spending months getting my automotive CAN BUS display working, I really want to beautify the UI and make it a bit more easier to read.
My ultimate goal is to display two gauges in the display in place of the two main readouts (Boost & AFR) but I am really struggling with code on this part - I am clueless here.

Here is what I have built so far:
View attachment 18310

What I would like to add is two gauge displays as can be seen below - One for boost, ranging from -30 inHg up to 30 PSI. the other is an Air fuel ratio ranging from roughly 8 to 22.
View attachment 18311 View attachment 18312
As you can see, the boost gauge is not linear in its layout (90deg for vacuum, 180deg for boost - air fuel layout is roughly 270-300deg).

Hardware being used is a Teensy 3.2 with a can transceiver and a 2.8" ILI9341 display.
Im using FlexCan and ILI9341_t3 libraries to drive everything.

My questions are:
1. Is ILI9341_t3 capable of doing what I want?
2. Will I be sacrificing process speed for refresh rate?
3. Will the T3.2 be enough or should I upgrade to a T4.0?
4. Can anyone help me with writing the code for the gauges or provide examples that I can modify?


Cheers,
David


I am assuming the gauges work using a voltage from a sensor

you need to output the voltage from your T3.2 to drive them

https://www.arduino.cc/reference/en/language/functions/analog-io/analogwrite/


you need to take the can data and convert it to the correct voltage to drive the gauge


the map function may help you convert from one unit to another

https://www.arduino.cc/reference/en/language/functions/math/map/


how did you get your can data from the ecu - e.g AFR and oil temperature ?
 
I am assuming the gauges work using a voltage from a sensor

you need to output the voltage from your T3.2 to drive them

...


you need to take the can data and convert it to the correct voltage to drive the gauge

...
I think OP has all that sorted and wants to know how to animate the needle and draw a background image to the display... (I can't help with this)

I did find this https://busy.org/@pakganern/oled-display-gauge-meter-using-potentiometer-arduino for a different display but at least some of the major graphics calls look compatable, e.g. .drawCircle()

It doesn't use a background image and I have no idea if it's actually helpful towards what you want to do... anyone?
 
You could do this with Teensy 3.2 by storing the background image in flash and updating the needle with triangle rasterization on top of it. Since you don't need to actually update other than previous and current frame needle positions, it would be pretty fast.
 
I think OP has all that sorted and wants to know how to animate the needle and draw a background image to the display... (I can't help with this)

I did find this https://busy.org/@pakganern/oled-display-gauge-meter-using-potentiometer-arduino for a different display but at least some of the major graphics calls look compatable, e.g. .drawCircle()

It doesn't use a background image and I have no idea if it's actually helpful towards what you want to do... anyone?

ok - understood

you may need to use a nextion display instead


I would be keen to find out how to do this, does anyone have an example
 
The main choke point in pretty pictures would be the display rather than the teensy because of the time taken to send one screen worth of pixels is fixed by the display interface. If you can live with the needle not overlapping the gauge markings the idea would be draw the background image once (or on a very limited rate) and then just draw the black pixels to erase the old needle position and the red pixels for the new position without needing to output a pile of unchanged pixels.

If drawing the needle from first principles you get to revisit your trigonometry. First identify the final angle you want, possibly using some IF statements to handle the divergent scales. Then use sin and cos of that angle times your needle length to get your X and Y end point pixels. To pretty things up a bit and get a wedge shaped needle you can also do things like find the point 3 pixels out from the pivot point at 90 and 270 degrees from your angle, then draw lines from that point to the previously found pointer tip.

Experimenting with something like excel can help get the formulas right without doing lots waiting for code to compile.
 
Or with a Teensy with more memory like T3.6/3.5 or T4, you can use the ILI9341_t3n library and turn on using frame buffer.

With this you can write the code pretty sloppy and for example each time you wish to update the position of the needle you simply:
rewrite the whole screen:
For example have an internal array with the bitmap for the background which you draw, then draw the needle in new position, and then tell the screen to update... Also with this option, you have the option to tell the screen to update using DMA, so you don't have to wait for it to complete to do something else. You may need to wait if you decide you need to update the display again, before you start writing new data to the frame buffer.
 
The main choke point in pretty pictures would be the display rather than the teensy because of the time taken to send one screen worth of pixels is fixed by the display interface. If you can live with the needle not overlapping the gauge markings the idea would be draw the background image once (or on a very limited rate) and then just draw the black pixels to erase the old needle position and the red pixels for the new position without needing to output a pile of unchanged pixels.

If drawing the needle from first principles you get to revisit your trigonometry. First identify the final angle you want, possibly using some IF statements to handle the divergent scales. Then use sin and cos of that angle times your needle length to get your X and Y end point pixels. To pretty things up a bit and get a wedge shaped needle you can also do things like find the point 3 pixels out from the pivot point at 90 and 270 degrees from your angle, then draw lines from that point to the previously found pointer tip.

Experimenting with something like excel can help get the formulas right without doing lots waiting for code to compile.

Thank you for this - it’s exactly my plan: draw the frame in the setup and update the needle position in the loop.
But, my maths has not been put to practice in many years and thats actually where I am stuck.

Ill try some of the examples here plus some others I found via Google.

I just might go for a T4.0 to speed up the display rate.
 
That youtube clip appears to have been done on an arduino, and it is having a hard time driving that display.
I don't have any experience with that driver, but with SSD1309 I have good success at fast fluid displays by avoiding the 'clear.display' and writing the background colour to the Needle's previous pixels and foreground colour to the Needle's new pixels.
My suggestion would be set up an interval timer and refresh/redisplay the needle at a fixed speed preferably at least 10Hz as anything less looks terrible. Use another interval timer to update numerical display (such as the red 7-segment in your picture) at 1Hz. You can play with the intervals and the phasing to find your point of best performance. Do not do any display in the loop, unless you use conditional statements and or flags to ensure you are not doing needless pixel-poking.
 
For the fun of it, I did a quick and dirty version of gauge with a needle using my ILI9341_t3n library, which is done pretty dirty... That is I enable frame buffer to make it easy to not have any flashing...
As such it requires a Teensy that has enough memory T3.5/6 or T4... I ran it on T4..

If I were doing it for something to use, I would take a lot more care. The background image, I took screen shoot from your image, saved it to file, used http://www.rinkydinkelectronics.com/_t_doimageconverter565.php

To convert the file to a 565 format image file, which I included and draw from memory every time....
IMG_1007.jpg

Zip file of sketch included

Sorry my math skills are extremely rusty, so some of this could be done better, but to draw the needle I have:
Code:
const double NEEDLE_LEN = 100;
const double TRI_HALF_WIDTH = 3;
const double CENTER_X = 160;
const double CENTER_Y = 120;
void drawNeedle(int percent, uint16_t color) {
  double rads = HALF_PI + TWO_PI * percent/100.0;
  uint16_t x0 = cos(rads) * NEEDLE_LEN + CENTER_X;
  uint16_t y0 = sin(rads) * NEEDLE_LEN + CENTER_Y;
  uint16_t x1 = cos(rads - HALF_PI) * TRI_HALF_WIDTH + CENTER_X;
  uint16_t y1 = sin(rads - HALF_PI) * TRI_HALF_WIDTH + CENTER_Y;
  uint16_t x2 = cos(rads + HALF_PI) * TRI_HALF_WIDTH + CENTER_X;
  uint16_t y2 = sin(rads + HALF_PI) * TRI_HALF_WIDTH + CENTER_Y;
/*
  Serial.print("Percent: ");Serial.print(percent, 3);
  Serial.print("Rads: ");Serial.print(rads, 3);
  Serial.printf(" (%u, %u) (%u %u) (%u %u)\n", x0, y0, x1, y1, x2, y2);
*/
  tft.fillTriangle(x0, y0, x1, y1, x2, y2, color);
}

The setup code draws a couple of different colored needles as to get an idea of were percentages hit...

Then main loop revs up from 0-75 percent and then back down...
Again quick and dirty as relying on frame buffer to controlling when pixels go to the display to remove flicker...
Code:
void loop(void) {
  Serial.println("Press any key to continue");
  while(Serial.read() == -1);
  while (Serial.read() != -1) ;

  for (int percent=0; percent < 75; percent++) {
    tft.writeRect(40, 0, 240, 240, (const uint16_t*)gauge);
    drawNeedle(percent, ILI9341_RED);
    tft.updateScreen();
  }
  for (int percent=75; percent >= 0; percent--) {
    tft.writeRect(40, 0, 240, 240, (const uint16_t*)gauge);
    drawNeedle(percent, ILI9341_GREEN);
    tft.updateScreen();
  }
  
}
 

Attachments

  • ILI9341_t3n_gauge_pictureEmbed-191218a.zip
    65.7 KB · Views: 143
Just thought I would mention, that I just updated the sketch to also work on our new ST7735/89_t3 library...
 

Attachments

  • ILI9341_t3n_gauge_pictureEmbed-191218b.zip
    65.9 KB · Views: 131
KurtE, this is amazing stuff!!! Thank you for putting time into this - definitely will give me a solid start and the code is simple enough to help me understand how the needle is being calculated and drawn.

We can speed things up by drawing the frame and numbers/ticks outside of the ring in the setup, and then update the previous needle positioning with the background color in the loop - similar to what I have done with my current display to print out all the numbers centred to the decimal point.

I'll also get an order for a T4.0 and give it a test with the ILI9341
 
Just thought I would mention, that I just updated the sketch to also work on our new ST7735/89_t3 library...

@KurtE
Just tried it on a 240x240 ST7789 with no CS pin (yes I had to change the setup). Almost forgot how. Runs great. No real noticeable flicker at all with updating the display as the needle moves.
 
@KurtE @mjs513
can either of you upload a short video or GIF of the gauge/needle sweep
I'll only be able to run this on my hardware next week and I'm really interested in seeing the smoothness of it.

@MatrixRat that's a nice display, but I'm looking to place two gauges + several other numeric parameters on a bigger display (2.8"-3.5")
 
@KurtE

I just placed an order for two T4's - and I am getting the gauge image cleaned up by a graphic designer. So I will have that ready to test hopefully in a few weeks.
Meanwhile, I am preparing for the case that I might need a larger display, perhaps the 3.5" TFT that runs on the ILI9488 - I see you've put together a library for that controller too, but does it also support frame buffering?
 
So a quick update on my project (its moving really slow, have had some family matters to deal with recently).

I was able to add a second gauge render (the one that resembles the 2nd image in my original post), based off of some code I borrowed from an Instructables article.
You can see in this video that the response of the display is pretty darn fast - but I am using an analog input as a data feed.

Later on I added the CAN bus code I had written based on the FlexCan library and fed the data to the display. As you can see here, it seems a bit choppy, even though it takes 1-2ms for the display to update. I believe its due to the high speed of the data coming in from the vehicle and its rate changes too quickly. Tried to think of a way to "smooth out" the data but didn't come across any code that seems's relevant.

I feel that the more data I pull over can and the more I put on the display, the more choppy it will looks (hoping the opposite).

Will continue to update with progress
 
You must use an intermediate variable that, by gradual increments or decrements, allows a smoother or more natural indicator needle to slide between two readings:

Code:
if (Lect2> Lect1) {Velp = Velp + Delta}
if (Lect2 <Lect1) {Velp = Velp-Delta}
 
You must use an intermediate variable that, by gradual increments or decrements, allows a smoother or more natural indicator needle to slide between two readings:

Code:
if (Lect2> Lect1) {Velp = Velp + Delta}
if (Lect2 <Lect1) {Velp = Velp-Delta}

Could you possibly go deeper into a code example?
Lect2 and Lect1 are the sensor readings I assume.. so for every two sensor readings I'll check if the reading is going up or down I assume, then smoothen out the gap between them by calculating the average between them?
Velp and the delta, where are they calculated?
 
Let's say you have the following variables:
Code:
float  gauge_value;  /* Actual gauge value measured; changes rapidly */
float  gauge_shown;  /* Displayed gauge value */

#define  GAUGE_MAX_UP    1.0f  /* Maximum increase per second in displayed gauge value */
#define  GAUGE_MAX_DOWN  1.0f  /* Maximum decrease per second in displayed gauge value */

uint32_t  micros_last;  /* micros() at the start of the last display update */
uint32_t  micros_now;   /* micros() at the start of the current display update */
Based on the elapsed duration (in microseconds, micros_now - micros_last) since the last display update, we can calculate the range the displayed gauge value is allowed to change in:
Code:
const float  gauge_shown_max = gauge_shown + (float)(micros_now - micros_last) * GAUGE_MAX_UP / 1000000.0;
const float  gauge_shown_min = gauge_shown - (float)(micros_now - micros_last) * GAUGE_MAX_DOWN / 1000000.0;

if (gauge_value > gauge_shown_max) {
    /* Actual gauge is rising too fast */
    gauge_shown = gauge_shown_max;
} else
if (gauge_value < gauge_shown_min) {
    /* Actual gauge is falling too fast */
    gauge_shown = gauge_shown_min;
} else {
    /* Actual gauge is within our allowed movement value */
    gauge_shown = gauge_value;
}

This is the same idea TFTLCDCyg expressed, except uses the elapsed time in microseconds to calculate the acceptable delta range, so that it looks smoother even if your display update rate varies a bit. Also, instead of using a single delta, it separates the increase and decrease to separate constants, as usually the rising can occur faster than the lowering can.
 
Let's say you have the following variables:
Code:
float  gauge_value;  /* Actual gauge value measured; changes rapidly */
float  gauge_shown;  /* Displayed gauge value */

#define  GAUGE_MAX_UP    1.0f  /* Maximum increase per second in displayed gauge value */
#define  GAUGE_MAX_DOWN  1.0f  /* Maximum decrease per second in displayed gauge value */

uint32_t  micros_last;  /* micros() at the start of the last display update */
uint32_t  micros_now;   /* micros() at the start of the current display update */
Based on the elapsed duration (in microseconds, micros_now - micros_last) since the last display update, we can calculate the range the displayed gauge value is allowed to change in:
Code:
const float  gauge_shown_max = gauge_shown + (float)(micros_now - micros_last) * GAUGE_MAX_UP / 1000000.0;
const float  gauge_shown_min = gauge_shown - (float)(micros_now - micros_last) * GAUGE_MAX_DOWN / 1000000.0;

if (gauge_value > gauge_shown_max) {
    /* Actual gauge is rising too fast */
    gauge_shown = gauge_shown_max;
} else
if (gauge_value < gauge_shown_min) {
    /* Actual gauge is falling too fast */
    gauge_shown = gauge_shown_min;
} else {
    /* Actual gauge is within our allowed movement value */
    gauge_shown = gauge_value;
}

This is the same idea TFTLCDCyg expressed, except uses the elapsed time in microseconds to calculate the acceptable delta range, so that it looks smoother even if your display update rate varies a bit. Also, instead of using a single delta, it separates the increase and decrease to separate constants, as usually the rising can occur faster than the lowering can.

Thank you Nominal Animal, I'll try and implement something in the upcoming days. I will have to sample the data twice in each loop, correct?
 
Status
Not open for further replies.
Back
Top