Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 22 of 22

Thread: Analog gauge meter

  1. #1

    Analog gauge meter

    Hi,
    Im working with a speedometer and have have not the right knowledge how to code analog meter on ILI9341 TFT with Teensy 3.1 with sinus/co-sinus code.
    How can I draw a analog meter with a needle with simple coding? If you have a tutorial I will be thankful for the advices.

    Best regards
    Toxic

  2. #2
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    13,932
    I've not seen anything - nor done this myself. FrankB did post code of a nice bar graph equalizer he did once.

    Ideally draw one line at a time in the foreground color (if a prior line was drawn - redraw that in background color first). [This will make the line flash from On>Off>On - so refinement later may be needed - especially if it swings widely between updates.]

    The upper end of that line would follow an arc bounded by screen the top and edges. So the 240 wide display might have say 200 valid points. The base of a needle would be a single central point - for flair the need could extend below that fixed point but might only sweep 20 pixels and would be best added later if not done carefully the axis point will jump to either side of the axis as the line displays making a weird jitter with rounding.

    >So pick your low central point for the axis.
    >Then draw the line to a point in that upper arc/circle. [with preceding redraw as noted]
    > The location of any label will need to be above or below the 'needle' or it will lose pixels unless redrawn

    To get the upper point take the analog value scaled from 1 to 200 and do the needed math to find where on that arc that upper point would lie. With good math and coding that equation should pop out.

    That's my first guess - maybe there are better ideas?

  3. #3
    Hi,

    Thank you for the guideline but I’m looking code example. I’m not the best in this type of math in this level but if I get some tutorial it would be easier to go forward.
    I tried to search in this forum about FrankB example but I did not find it. If you have the link it would be great..

    Best regards
    Toxic

  4. #4
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    13,932
    Do you have the unit powered up and working? https://www.pjrc.com/store/display_ili9341.html.

    Open the GraphicsTest sample and look it over - maybe isolate out the line draw elements. Doing that will - if not get you closer - prepare for working with whatever sample may be provided later.

    I just did a search and found this full circle gauge versus what I described: https://www.youtube.com/watch?v=o6QjVqq1lXM it doesn't show sample code - but a similar one might.

    There is an active GoCart project on the Forum - not sure if added dial gauges?

    http://www.instructables.com/id/Ardu...hic-/?ALLSTEPS
    Last edited by defragster; 09-01-2015 at 08:39 PM.

  5. #5
    Hi,
    I have working TFT but I’m interested in tutorial or example from simple code single pixel drawing so I can learn for later project.

    Best regards
    Toxic

  6. #6
    Senior Member Wozzy's Avatar
    Join Date
    Jan 2013
    Location
    Philadelphia, Pennsylvania USA
    Posts
    354
    Here's another example running on a DUE
    http://www.instructables.com/id/Ardu...eter-graphic-/

    It looks like it needs some customized ILI9341 libraries by Alan Senior, which are linked to in the Instructables article

  7. #7
    Hi,
    Thank you

    Still interested simple example. Draw circle is no problem but fix the needle is interesting. Tutorial?

    Best regards
    Toxic

  8. #8
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    13,932
    Same instructable I linked. I'd look through it and port it to the code used on Teensy - not drag in a library they use as in the end you shouldn't need it.

    If you look over the examples "Teensy\hardware\teensy\avr\libraries\ILI9341_t3\examples\graphicstest" you'll find code for doing most of anything the device can do. Each subgroup of testing is a unique function - start by taking the most sub-function most like what you want to do and make it work in a stand alone sketch and then add parts that do more like what you want.

    If that works to get you started you are good to go - if that doesn't go so smooth - start with some other examples and figure out the environment or look for other tutorials that speak to you: http://www.bing.com/search?q=ili9341...abcf26ba9dc5c6

    If nothing else start simpler - learn to control debug text spew of your analog value and draw some lines then as that will be part of your project development

  9. #9
    Senior Member Wozzy's Avatar
    Join Date
    Jan 2013
    Location
    Philadelphia, Pennsylvania USA
    Posts
    354
    I took the example above and stripped out the special library functions.
    It's not very elegant, but it works and it's a good starting point.

    Here it is working on a Teensy 3.1 using the highly optimized ILI9341_t3 library:
    https://github.com/PaulStoffregen/ILI9341_t3

    With fonts from here:
    https://github.com/PaulStoffregen/ILI9341_fonts

    Click image for larger version. 

Name:	Teensy Meter.jpg 
Views:	2280 
Size:	169.3 KB 
ID:	5025

    Here is the working code:
    Code:
    /*
    Based on VU Meter Sketch From Instructables by Bodmer
    http://www.instructables.com/id/Arduino-sketch-for-a-retro-analogue-meter-graphic-/
    
     An example analogue meter using a ILI9341 TFT LCD screen
     This example uses the hardware SPI only
     Alan Senior 23/2/2015
    
    Stripped out the special library functions
    Wozzy 09/01/2015
     */
    
    #include "SPI.h"
    #include "ILI9341_t3.h"
    
     #include "font_DroidSans.h"
     #include "font_DroidSans_Bold.h"
    
    
    #define TFT_RST 8
    #define TFT_DC  9
    #define TFT_CS 10
    ILI9341_t3 tft = ILI9341_t3(TFT_CS, TFT_DC, TFT_RST);
    #define RGB(r,g,b) (r<<11|g<<5|b)  //  R = 5 bits 0 to 31, G = 6 bits 0 to 63, B = 5 bits 0 to 31
    
    #define ILI9341_GREY 0x5AEB
    
    float ltx = 0;    // Saved x coord of bottom of needle
    uint16_t osx = 120, osy = 120; // Saved x & y coords
    uint32_t updateTime = 0;       // time for next update
    
    int old_analog =  -999; // Value last displayed
    int old_digital = -999; // Value last displayed
    
    
    void setup(void) {
      tft.begin();
      tft.setRotation(2);
    
      tft.fillScreen(ILI9341_BLACK);
    
      analogMeter(); // Draw analogue meter
      
      updateTime = millis(); // Next update time
    }
    
    
    void loop() {
      if (updateTime <= millis()) {
        updateTime = millis() + 500;
    
        int reading = 0;
        reading = random(0, 100); // Test with random value
      //reading = map(analogRead(A0),0,1023,0,100); // Test with value form Analogue 0  
        plotNeedle(reading, 8); // Update analogue meter, 8ms delay per needle increment
      }
    }
    
    // #########################################################################
    //  Draw the analogue meter on the screen
    // #########################################################################
    void analogMeter()
    {
      // Meter outline
       tft.setRotation(1);
       tft.fillRect(0, 0, 239, 126, ILI9341_GREY);
       tft.fillRect(5, 3, 230, 119, ILI9341_WHITE);
       
      
       tft.setTextColor(ILI9341_BLACK);  // Text colour
       tft.setRotation(1);  
      
      // Draw ticks every 5 degrees from -50 to +50 degrees (100 deg. FSD swing)
      for (int i = -50; i < 51; i += 5) {
        // Long scale tick length
        int tl = 15;
        
        // Coodinates of tick to draw
        float sx = cos((i - 90) * 0.0174532925);
        float sy = sin((i - 90) * 0.0174532925);
        uint16_t x0 = sx * (100 + tl) + 120;
        uint16_t y0 = sy * (100 + tl) + 140;
        uint16_t x1 = sx * 100 + 120;
        uint16_t y1 = sy * 100 + 140;
        
        // Coordinates of next tick for zone fill
        float sx2 = cos((i + 5 - 90) * 0.0174532925);
        float sy2 = sin((i + 5 - 90) * 0.0174532925);
        int x2 = sx2 * (100 + tl) + 120;
        int y2 = sy2 * (100 + tl) + 140;
        int x3 = sx2 * 100 + 120;
        int y3 = sy2 * 100 + 140;
    
        // White zone limits
        //if (i >= -50 && i < -45) {
        //  tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_WHITE);
        //  tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_WHITE);
        //}
          
        // Green zone limits
        if (i >= -50 && i < 30) {
          tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_GREEN);
          tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_GREEN);
        }
        
        // Yellow zone limits
        if (i >= 30 && i < 40) {
          tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_YELLOW);
          tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_YELLOW);
        }
    
        // Orange zone limits
        if (i >= 40 && i < 45) {
          tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_ORANGE);
          tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_ORANGE);
        }
    
        // Red zone limits
        if (i >= 45 && i < 50) {
          tft.fillTriangle(x0, y0, x1, y1, x2, y2, ILI9341_RED);
          tft.fillTriangle(x1, y1, x2, y2, x3, y3, ILI9341_RED);
        }
    
        
        // Short scale tick length
        if (i % 25 != 0) tl = 8;
        
        // Recalculate coords incase tick lenght changed
        x0 = sx * (100 + tl) + 120;
        y0 = sy * (100 + tl) + 140;
        x1 = sx * 100 + 120;
        y1 = sy * 100 + 140;
        
        // Draw tick
        tft.drawLine(x0, y0, x1, y1, ILI9341_BLACK);
    
     tft.setFont(DroidSans_12);
     tft.setCursor(25,50);
     tft.print("0");
     tft.setCursor(60,20);
     tft.print("25");
     tft.setCursor(110,9);
     tft.print("50");
     tft.setCursor(160,20);
     tft.print("75");
     tft.setCursor(202,50);
     tft.print("100");
     tft.setFont(DroidSans_14_Bold);
     tft.setCursor(8,100);
     tft.print("TEMP");
     tft.setCursor(210,100);
     tft.print("F");
    
        
        // Now draw the arc of the scale
        sx = cos((i + 5 - 90) * 0.0174532925);
        sy = sin((i + 5 - 90) * 0.0174532925);
        x0 = sx * 100 + 120;
        y0 = sy * 100 + 140;
        // Draw scale arc, don't draw the last part
        if (i < 50) tft.drawLine(x0, y0, x1, y1, ILI9341_BLACK);
      }
    
      tft.drawRect(5, 3, 230, 119, ILI9341_BLACK); // Draw bezel line
      
      plotNeedle(0,0); // Put meter needle at 0
    }
    
    // #########################################################################
    // Update needle position
    // This function is blocking while needle moves, time depends on ms_delay
    // 10ms minimises needle flicker if text is drawn within needle sweep area
    // Smaller values OK if text not in sweep area, zero for instant movement but
    // does not look realistic... (note: 100 increments for full scale deflection)
    // #########################################################################
    void plotNeedle(int value, byte ms_delay)
    {
      tft.setTextColor(ILI9341_BLACK, ILI9341_WHITE);
      char buf[8]; dtostrf(value, 4, 0, buf);
     // tft.drawRightString(buf, 40, 119 - 20, 2);
    
      if (value < -10) value = -10; // Limit value to emulate needle end stops
      if (value > 110) value = 110;
    
      // Move the needle util new value reached
      while (!(value == old_analog)) {
        if (old_analog < value) old_analog++;
        else old_analog--;
        
        if (ms_delay == 0) old_analog = value; // Update immediately id delay is 0
        
        float sdeg = map(old_analog, -10, 110, -150, -30); // Map value to angle 
        // Calcualte tip of needle coords
        float sx = cos(sdeg * 0.0174532925);
        float sy = sin(sdeg * 0.0174532925);
    
        // Calculate x delta of needle start (does not start at pivot point)
        float tx = tan((sdeg+90) * 0.0174532925);
        
        // Erase old needle image
        tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, ILI9341_WHITE);
        tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, ILI9341_WHITE);
        tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, ILI9341_WHITE);
        
        // Re-plot text under needle
        tft.setTextColor(ILI9341_BLACK);
      //  tft.drawCentreString("%RH", 120, 70, 4); // // Comment out to avoid font 4
        
        // Store new needle end coords for next erase
        ltx = tx;
        osx = sx * 98 + 120;
        osy = sy * 98 + 140;
        
        // Draw the needle in the new postion, magenta makes needle a bit bolder
        // draws 3 lines to thicken needle
        tft.drawLine(120 + 20 * ltx - 1, 140 - 20, osx - 1, osy, ILI9341_RED);
        tft.drawLine(120 + 20 * ltx, 140 - 20, osx, osy, ILI9341_MAGENTA);
        tft.drawLine(120 + 20 * ltx + 1, 140 - 20, osx + 1, osy, ILI9341_RED);
        
        // Slow needle down slightly as it approaches new postion
        if (abs(old_analog - value) < 10) ms_delay += ms_delay / 5;
        
        // Wait before next update
        delay(ms_delay);
      }
    }
    Teensy 3.1
    ILI9341 SPI Display 320x240
    Arduino 1.6.5_R5
    Teensyduino 1.25_B2
    Last edited by Wozzy; 09-01-2015 at 11:50 PM.

  10. #10
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    13,932
    Good work Wozzy - my ILI9341 is offline - as I am mostly running house on generator since Sat 8/29. Was hoping toxic might be up to starting on that easy to find tutorial.

  11. #11
    Senior Member Wozzy's Avatar
    Join Date
    Jan 2013
    Location
    Philadelphia, Pennsylvania USA
    Posts
    354
    I was sitting here playing with the new font library when I noticed the post on the forums, so I thought "What the heck! I'll give it a try"

  12. #12
    Hi,
    You are amazing
    But I should had describe it more. Sorry.
    I’m interesting to do like this:
    https://www.sparkfun.com/tutorial/Sp...PIC7_LARGE.JPG
    https://www.sparkfun.com/tutorial/Sp...IC6_SMALL.jpeg

    Now some sleep

    Best regards
    Toxic
    Last edited by toxic; 09-02-2015 at 01:47 AM.

  13. #13
    Senior Member Wozzy's Avatar
    Join Date
    Jan 2013
    Location
    Philadelphia, Pennsylvania USA
    Posts
    354
    Toxic,
    If you look at the code I posted, You'll see that you can modify it to produce a speedometer display similar to what you want.

  14. #14
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    13,932
    Awesome looks like a complete tutorial with a 90% solution. [ Hopefully this is a personal quest and not some assignment ]. Depending on how that sample code was written - there should be a simple translation that will zoom out giving the larger range shown in the crude picture just posted. If you show some work and get stuck perhaps further suggestions would be in order.

  15. #15
    Senior Member pictographer's Avatar
    Join Date
    May 2013
    Location
    San Jose, CA
    Posts
    701
    Your pictures from Spark Fun are nice and simple. How about a simple warmup exercise? Let's draw the spokes of a wagon wheel. For now, we're not going to worry about erasing anything or plotting a value based on analogRead(), etc.

    The spokes all start in the middle of the display and radiate outward. The middle of the display has a horizontal coordinate of the width of the display divided in half and a vertical coordinate of the height of the display divided in half. So far, so good? Let's call the starting point (x0, y0).

    Now for the other ends of the spokes, we could get out the old graph paper and do some counting, but if there are lots and lots of spokes, there's a quicker way. For this purpose, it is inconvenient that the way we draw a line is by specifying the two end points of the line. What we'd really like is one end point, a direction and a distance. If you search the web for 'convert rectangular to polar coordinates', you should find what you need to make a more convenient line drawing command.

    This is where the sine and cosine functions come in handy. We'd like to know (x1, y1) where the distance between (x0, y0) and (x1, y1) is fixed at some value r and the direction varies. Just for fun, let's set r to 1/4 of the smaller of the width and the height. If the display is 240 pixels in the smaller dimension that makes r = 80.

    Referring to your second Spark Fun image, there's an angle indicated. Let's call it d for direction. The sine function is the ratio of the side opposite the angle to the hypotenuse (long side) of the triangle. This is just what we want. In the picture the side opposite the angle is the horizontal leg of the triangle. So, x1 = r * sinf(d). The cosine function is the ratio of the side adjacent the angle to the hypotenuse. So, y1 = r * cosf(d).

    Click image for larger version. 

Name:	PIC6_SMALL.jpeg 
Views:	255 
Size:	10.6 KB 
ID:	5030

    If you were to plot all the spokes in 15 degree steps, you might discover a little catch. The sinf() and cosf() functions expect the angle in radians, not degrees, which simply means you need to multiply degrees by 2 * pi radians and divide by 360 degrees.

    The code to draw wagon spokes might look something like this (note: I haven't compiled this):
    Code:
    for (float ddeg = 0.0f; ddeg < 360.0f; ddeg += 15.0f) {
      float drad = ddeg * 2.0f * 3.142f / 360.0f;
      float x1 = x0 + r * cosf(drad);
      float y1 = y0 + r * sinf(drad);
      display.drawLine(x0, y0, x1, y1, ILI9341_WHITE);
    }
    If you're paying close attention, you might have noticed that I swapped sinf and cosf between the text and the code. That's because the angle indicated in the picture is not the conventional one. The convention is to measure the angle from horizontal moving counter-clockwise. From three o'clock measuring back towards two o'clock.
    Last edited by pictographer; 09-03-2015 at 03:51 AM. Reason: Added copy of PIC6_SMALL.jpeg from Spark Fun

  16. #16
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    13,932
    Nice summary pictographer - I could work with that - hopefully toxic can. I'd say a wagon wheel was pretty olde schoole - but these days so is a clock with the hand things.

  17. #17
    Hi,
    Thank you for your guidens. According to https://www.sparkfun.com/tutorials/123 they have code like X Length = sin(MPH * 2.7) * 20 and Y Length = cos(MPH * 2.7) * 20
    Im dont thinking as every one else but where do you add speed value as input to this code?

    Best regards
    Toxic

  18. #18
    Senior Member Wozzy's Avatar
    Join Date
    Jan 2013
    Location
    Philadelphia, Pennsylvania USA
    Posts
    354
    Quote Originally Posted by toxic View Post
    Hi,
    but where do you add speed value as input to this code?
    Toxic
    Ultimately you need to assign the speed into the variable MPH in the equation you provided.
    What are you using for a speed sensor?
    It's hard to help you if you only tell us little bits and pieces of what you are trying to accomplish.
    Also see the "Forum Rule" and post the code blocks that that you are trying to make work on the teensy.
    It's always a good idea to start out with little steps.
    I'm not sure what your experience level is, but maybe a good starting point is the teensy tutorials and examples.
    http://www.pjrc.com/teensy/tutorial.html
    Last edited by Wozzy; 09-03-2015 at 03:00 PM.

  19. #19
    Hi,
    Input to speedometer is from rear axle, 48 pulses/ 1 turn 15" wheel.
    Yes, add this questions to make a tutorial and it would spare some questions.

    Best regards
    Toxic

  20. #20
    Senior Member pictographer's Avatar
    Join Date
    May 2013
    Location
    San Jose, CA
    Posts
    701
    Toxic, maybe you've got the wrong impression of how this works.

    We're eager to help anyone with an earnest interest in learning and solving problems. It's rewarding to help someone make that lightbulb go on, so to speak. But we can't spare you the frustration of working through challenges yourself. Even if we could, you'd miss out on the learning and pride of doing it yourself.

    After you've tried everything you can think of to solve your problem, tell us in detail what you tried and where you're stuck. We'll help if we can.

  21. #21
    Hi,
    Thanks for the advice. Dont worry I will solve it. I have working with electronics since I was 15 year old. This is new to me so I learn, Iḿ trying to understand method and Im not expert on every area. I always ask the stupid question what people miss and I learn new things this way to.

    I be back and show some progress

    Best regards
    Toxic

  22. #22
    Senior Member+ Theremingenieur's Avatar
    Join Date
    Feb 2014
    Location
    Colmar, France
    Posts
    2,600

    Cool

    Uhm.... Isn't coordinate transformation 8th grade and rotary movement equations 9th grade stuff?
    The gauge needle has always the same given fixed length (l_needle), a given fixed origin (x0, y0) and an end point which moves on a circle segment between two angles, alpha_min and alpha_max.
    The current angle is a function of the current speed to display (there are minus signs, since mathematically, increasing angles go counterclockwise, while increasing speed will move the needle clockwise):
    (1) alpha_curr = alpha_min - (alpha_min - alpha_max) * speed_curr / speed_max
    From this, we can immediately calculate the end point coordinates:
    (2a) x1 = x0 + l_needle * cos(alpha_curr)
    (2b) y1 = y0 + l_needle * sin(alpha_curr)
    Now we'll still have to determinate the given start values:
    Let's assume that we have a 320 pixel wide and 240 pixel high display, than we choose the center point as a fixed point for the needle:
    (3a) x0 = 160
    (3b) y0 = 120
    The needle should leave some space for numbers around that speedometer, so, instead of the maximum length of 120px (with which it would touch the upper border of the display) we choose 100px which leaves space for a 12px font numbering and some spacing:
    (4) l_needle = 100px
    In most speedometers, the needle describes a 270 or 3pi/2 angle, thus
    (5) alpha_max = alpha_min - 3pi/2
    Thus there remains an opening of 90 or pi/2 downwards centered around the vertical axis:
    (6) alpha_min = 5pi/4, thus with (5): alpha_max = -pi/4
    And here we are. The rest is drawing routine.

    Notice: The coordinate equations apply for a mathematical coordinate system, where the point (0,0) is in the lower left corner. Some display drivers locate it in the upper left corner instead. To compensate that, it is sufficient to mirror the y1 coordinate with the modified equation (2b), the others remain unchanged:
    (2b) y1 = y0 - l_needle * sin(alpha_curr)

    Questions?

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •