News:

Let's find out together what makes a PIC Tick!

Main Menu

ANALOGUE ANALOGIES

Started by SeanG_65, Apr 17, 2024, 03:23 PM

Previous topic - Next topic

SeanG_65

Here's something that MUST have been done, but I cant see where.

Does an ANALOGUE METER SIMULATION for a GLCD exist anywhere?

How about some genius (that's me out of the running) creates a library of GLCD meters?

MaxVal will be FSD and anything in between is a function of MaxVal/Steps, where steps is the number of points the meter can "address".

The usr could design their own backgrounds using Windows Paint of the like, in say 320 X 240 and use this as the background, the pointer being the only "dynamic" element of the whole thing.

Other things like Peak Hold, dual pointers for stereo or to use one as the peak hold indicator.

This could add some real ZAP, to GLCD projects. Could have a digital "screen in screen" version like a analogue / digital watch type thingy.

THIS type of thing https://www.instructables.com/Arduino-sketch-for-a-retro-analogue-meter-graphic-/

Anyone want to take on this challenge?

keytapper

I've tried to translate the Arduino code some time (Wiring I may say). Maybe AI would challenge the task.
Ignorance comes with a cost

SeanG_65

I would have thought our resident graphics genius Atomix would be able to give us some pointers (GROAN) ;D

trastikata

Ok, I will make an article later today how to design and make it easily yourself.


top204

#4
The graphics requirements are quite straightforward, in creating a line and filling triangles etc. So the graphic LCD library will be excellent for it.

It is the trigonometry with all the sin and cos that create the arc of the needle's movement and the top display that will take the time to convert. Especially with the bloated code that is C++.

I created a similar analogue needle many years ago for the compiler using the old 64x128 graphic LCD and it worked well. Because it had so few X pixels (128), the arc positions were held in a flash memory table to make it faster to operate.

trastikata

#5



Hello,

sorry for the delay. Something, requiring my attention came up this weekend and I couldn't write the article as I said I'll ...anyway, here it is.

It's actually no so difficult to make any Analog-like meter yourself with the Atomix' libraries which support 8bit PICs. I can place my code, but there's not much commenting, the TFT libraries are my design and differ much from Atomix's and most important they've been written for 16-bit PIC and they are not optimized for 8-bit.

But the design process is the same, so let's see it ...

I. Background
- Print the entire background and then start populating the screen with squares and circles and so on to form the meter.

- If we want nice backgrounds, we can place a gradient background color, this is done by printing lines changing the color for each line, however this creates some additional requirements, like transparent font printing, pixel-read to restore needle background, color breaking to RGB components etc.

I don't know if pixel-reading and transparent fonts are supported by Atomix's library, thus for simplicity use a single color background, if you are interested how to do all that nice colorful stuff, let me know and I'll dive more in details.

II. Scale
- Using some easy math we can find the function of the circle to which the arc belongs.

- Once we know the circle formula we can calculate any point along that circle or any point belonging to any circle parallel to it - thus we know how to calculate the arc points.

Note that the math takes quite the processing time so we have several options how to proceed:

Option1: We embed all math functions in the code and we calculate the arc points on-the-fly. This will give us a lot of flexibility, however it requires a lot of processing power. 

Option2: We embed all math functions in the code and we calculate the arc points at boot time and create RAM arrays which we populate with the points. We have less flexibility, however this will give us speed of execution, this however is offset with higher RAM requirements.

Option3: We calculate all arc points in Excel and we embed them in the code as look-up tables. This has less RAM requirements, good execution speed but no flexibility.

Thus, here comes the scary part - Math  ;D  ... no, it's actually not difficult. 

An arc! Any arc can be seen as part of a circumference, and therefore it can be fit in to a circle. Therefore the arc has the following parameters*:

Note: My display origin is top-left, going right-down means X goes from 0 to 319 and Y goes negative hence 0 to -239
- A start point Ps with coordinates Xs and Ys (for our example assume Xs = 30; Ys = -80)
- An end point Pe with coordinates Xe and Ye (for our example assume Xe = 290; Ye = -80)
- Radius (from the circle to which the arc belongs) -  R - this has to be more than the half of the distance between the Ps and Pe (otherwise it can't connect the two points) - (for our example assume  R = 200)
- Circle center point Pc with coordinates Cx and Cy. We don't know them, but we can calculate those coordinates (for our example Cx;Cy = ?)
- Circular sector angle (we call it Angle) that will form the arc - this is the angle between the radius lines connecting the center and Ps and Pe and not the angle as seen from the center axes (for our example Angle = ?).

*This is just one example possibility, in this example the arc is symmetrical around the screen (320x240) vertical center, but it does not have to be.

Step1: Find the distance between D the two points:
D = ((Xs-Xe)^2+(Ys-Ye)^2)^0.5 --> D = ((30-290)^2+(-80-(-80))^2)^0.5 = 260

Step2:** Find the circle center x-coordinate: 
Cx = (Xs+Xe)/2 +- ((Ys-Ye)/2)*(-1 + (2*R/D)^2)^0.5) --> Cx = (30+290)/2 - ((-80-(-80))/2)*(-1 + (2*200/260)^2)^0.5) = 160

Step3:** Find the circle center x-coordinate: 
Cx = (Ys+Ye)/2 -+ ((Xs-Xe)/2)*(-1 + (2*R/D)^2)^0.5) --> Cy = (-80+ (-80))/2 + ((30-290)/2)*(-1 + (2*200/260)^2)^0.5) = -232

Step4:*** Find the arc angle (in radians):
Angle = Acos((2*R^2 - D^2)/(2*R^2)) --> Angle = Acos((2*200^2 - 260^2)/(2*200^2)) = 1.415168873

** -+ or +- depends if the center of circle is above or bellow the arc, depending on if we have a concave or convex arc or arc direction - thus which part of the circle is our arc because two points divide the same circle in two different arcs. This can be checked mathematically by verifying the Start and End points, but we want things to be simple so easiest would be just to draw and see either one option  :) 
*** Angle is in radians, not degrees, therefore we will work in radians

Step5****: Draw the arc scale:
... this I'll continue tomorrow, not much time now to write down everything.


SeanG_65

#6
GORDON BENNETT, This man is a freaking GENIUS!!

Now all I have to do is figure out how to make the needle full length. I'd run this with a parallel interface.

I was thinking it might be better to "pre draw" the meter and store it in EEPROM or FLASH RAM then simply pull it out when needed.

trastikata

Quote from: SeanG_65 on Apr 23, 2024, 12:06 AMNow all I have to do is figure out how to make the needle full length. I'd run this with a parallel interface.

Sorry, a lot is going around here this week, so plotting the scale, scale major and minor grids, plotting and moving the needle - later today in Part 2.  :)

trastikata

Part2.

Step5: Draw the arc scale

Any point on the arc (and on any arc, parallel to this one) can be calculated using the desired radius and the Angle. So we calculated our Angle = 1.415168873. Thus to draw our arc, we sleep the angle from (1.570796327 + Angle/2) to (1.570796327 - Angle/2) and calculate the points along it.

You would think, why not get all the X coordinates from start to end (in our example 30 to 290) and calculate the Y coordinates - because this will give us only one arc set and we want more than that, comes next.

Divide the angle sweep in steps, how many steps depends if you want to represent the arc as bunch of connected straight lines, or you want to draw each point. I choose to draw each point, so I divide the angle in just a little bit more steps than the distance D, say 280 steps - this is because displays work with integer points and rounding floating point values could miss some points, so I'd rather have few repeating points than missing points.

Step = (Stop Sweep Angle - Start Sweep Angle)/ # of Steps --> Example: Step = (0.863211890069541 - 2.27838076352025) / 280 = -0.00505417454803825

So the coordinates for any point, call them Point A, B, C, D, E ... lets call it Pn where n is the point number (1st, 2nd, 3rd, 4th ...) along the arc can be calculated as follow:

PnX = R * Cos(Start Sweep Angle + n*Step) + Cx --> example: P3X = (200*Cos(2.27838076352025 + 3*(-0.00505417454803825))) + 160 = 32

PnY = R * Sin(Start Sweep Angle + n*Step) + Cy --> example: P3Y = (200*Sin(2.27838076352025 + 3*(-0.00505417454803825))) + (-232) = -78

So if we sweep the entire angle from Start Sweep Angle to Stop Sweep Angle we can draw each point of our arc.

Now, do you remember I said it's better calculating the arc this way instead of using X points from example: 30 to 290? This is because if we change the radius in those formulae you can get any point from any parallel arc!

Thus our top arc of the scale has Radius=200, well we can do in the same time the lower arc of the scale, just offset the arc by changing the radius. So we want another arc 35 points bellow the top arc, then calculate the points using LowerArcRadius = Radius - 35 ...

Ok, we have the two arcs of the scale, lets connect the ends ... well calculate the starting points of the lower and upper arc and connect the two points with a line. Same for the end points.

Step6: Paint the scale

Using the same principle you can divide the Angle in 3,4,5 etc parts and starting from Start Sweep Angle + 1*Step to next scale bracket you sweep the calculations using the corresponding number of steps and moving along the Radius from top arc -1 to bottom arc + 1 and paint each point with the required color.

Alternatively you can calculate only the points using the Radius of the lower arc + 1 and the Radius from top arc -1 and connect those points with a line. However due to floating point rounding there might be some missing pixels as end result.

Step7: Minor and Major grid

Using the same principle I just described, for the Minor grid we divide the Angle let's say into 20 parts. Then loop through the angle and calculate the points for each angle using the lower arc radius and a "virtual arc" radius 25 points bellow the upper arc, then at each iteration connect those two point sets with a line - this will give you the Minor grid. You can use any color.

for the Major grid we divide the angle let's say into 4 parts. Then loop through the angle and calculate the points for each angle using the lower arc radius and a "virtual arc" radius 10 points bellow the upper arc, then at each iteration connect those two point sets with a line - this will give you the Major grid. Paint it with any color, but keep in mind this will overwrite the minor grid at these position, this is common sense.

Step8: Draw the needle

Using the same method as earlier calculate the starting point of the needle. However this time the needle will move around a new arc with different center point. You can see in the videos in my case the needle moves around the brown circle in the middle. The center of this circle is set on the same center line of the top arc (thus same X) however it has been offset upwards to a new Y coordinate to make it visible.

Using Start Sweep Angle calculate the point coordinates on this new virtual arc with an offset center and Radius something, and the point coordinates on the original arc center and with radius somewhere few points bellow the lower scale arc.

Now connect these two points with a line. To make the needle better visible, make the line 2 or 3 pixels thick. Note it is the line that has to be thicker, the starting and ending coordinates don't change.

Step 9: Move the needle

First initiate two sets of two coordinate points each to hold the new position (NewSet) and the old position (OldSet) of the needle.

To move the needle first decide how many steps the meter scale will have and what would be the minimum and the maximum of the scale. Then divide the Angle to that amount of steps and keep the Step value as a variable. Now prorate the total scale (max-min) to the number of steps.

Example: I want scale from 0% to 100% with a movement step of 1, thus my ScaleFactor = (StopSweepAngle - StartSweepAngle) / 100 = -1.415168873  / 100 = -0.01415168873

When a new value to be visualized arrives, calculate to which Angle position this value corresponds, let's call it TempAngle:

Example value of 40 arrives: TempAngle = StartSweepAngle + NewValue * ScaleFactor --> TempAngle = 2.278380764 + 40*(-0.01415168873) = 1.712313215

Next, transfer the current needle coordinates to the OldSet and calculate the new needle coordinates.

Last re-draw the old needle with the background color (line between OldSet coordinates) and draw the new needle position using the new set of coordinates.


Part 3 Gradient color backgrounds.

Gradient color as background looks much better than mono color backgrounds, however to move the needle over it you need to be able to read each pixel and restore the color after the needle has moved based on the horizontal (or vertical) neighboring pixels, this requires higher transfer speed and some extra processing. But if you have it on parts where the needle is not moving or you can live with withdrawing the background, then it could give a nice touch.

I am assuming 16-bit color scheme or RGB565 which means 5-bit Red, 6-bit Green, 5-bit Blue. But it is the same principle for any other color scheme.

For gradient color background we have a StartColor and StopColor, assume from MAGENTA = 0xF81F to DARKCYAN = 0x03EF.

MAGENTA = 0xF81F  --> R|G|B =  1 1 1 1 1 ⏐ 0 0 0 0 0 0 ⏐ 1 1 1 1 1
DARKCYAN = 0x03EF --> R|G|B =  0 0 0 0 0 ⏐ 0 1 1 1 1 1 ⏐ 0 1 1 1 1

Step 1: For the start color extract each value for the Red, Green, Blue component and place it in a variable. This is easily achievable with simple shift operations

Step 2: For the end color extract each value for the Red, Green, Blue component and place it in a variable. This is easily achievable with simple shift operations. Just an example code:

Proc BreakColor()
    Dim bTemp As Byte
    Dim wTemp As Word
   
    'Break the colors to Red-Green-Blue
    'Blue
    bTemp = StartColor.Byte0
    bTemp = bTemp << 3
    bBs = bTemp >> 3
    fTempB = bBs
    bTemp = EndColor.Byte0
    bTemp = bTemp << 3
    bBe = bTemp >> 3
    'Green
    wTemp =  StartColor << 5
    bGs = wTemp >> 10
    fTempG = bGs
    wTemp =  EndColor << 5
    bGe = wTemp >> 10
    'Red
    bRs = StartColor >> 11
    fTempR = bRs
    bRe = EndColor >> 11
EndProc

Dim StartColor As Word          'Start gradient color
Dim EndColor As Word            'End gradient color

Dim bRs As Byte                 'Red
Dim bGs As Byte                 'Green
Dim bBs As Byte                 'Blue
Dim bRe As Byte                 'Red
Dim bGe As Byte                 'Green
Dim bBe As Byte                 'Blue
Dim wColor As Word              '16b RGB565
Dim fGradientR As Float         'Gradient scale Red
Dim fGradientG As Float         'Gradient scale Green
Dim fGradientB As Float         'Gradient scale Blue
Dim fTempR As Float             'Holds the current Red value
Dim fTempG As Float             'Holds the current Green value
Dim fTempB As Float             'Holds the current Blue value


Step 3: Calculate the difference between the start and end value of each Red, Green, Blue component. Prorate that difference for each each Red, Green, Blue component to the number of gradient steps. Example usage:

    fGradientR = (bRe - fTempR) / ScreenHeight
    fGradientG = (bGe - fTempG) / ScreenHeight
    fGradientB = (bBe - fTempB) / ScreenHeight


Step 4: Start drawing your background as set of consecutive horizontal (or vertical) lines while in each line iteration change the line color by building a new color where to each Red, Green, Blue component the gradient factor has been added. Then create the new color value from the individual R-G-B values. Example:

Proc BuidColor()
    MyColor = bGs
    MyColor = MyColor << 5
    bRs = bRs << 3
    MyColor.Byte1 = MyColor.Byte1 | bRs
    MyColor.Byte0 = MyColor.Byte0 | bBs
   
    'Shift each channel in the color
    fTempR = fTempR + fGradientR
    bRs = Abs(fTempR)
    fTempG = fTempG + fGradientG
    bGs = Abs(fTempG)
    fTempB = fTempB + fGradientB
    bBs = Abs(fTempB)
EndProc

Main:
   For MyCounter = 0 To (ScreenHeight - 1)
        'Draw a line stating at point with coordinates (0; MyCounter) and finishing at (ScreenWidth, MyCounter), with color MyColor                               
        TftLine(0, MyCounter, (ScreenWidth - 1), MyCounter, MyColor)   
        BuidColor()
    Next

I hope this long post has been interesting and useful to you.



top204

#9
Excellent texts and it looks like an excellent project. Please release the source code for the LCD meter?

I can then transfer your posts to the WIKI section of the forum.

trastikata

Thank you Les.

The problem is it was written in hurry only to show the concept behind the meter. I did it on an old test board with a dsPIC which has lots of memory and speed.

So I did not want to pay attention to variables and types. Also as I mentioned I did everything in code just as proof of concept and to keep things simple there are loops with lots of trigonometry which can be pre-calculated ... Another thing is I place little to none comments, besides I've written the TFT commands some time ago, they are not commented out nor really efficient.

Point being posting that mess would be of little help to anyone. If someone is willing to make decent optimized libraries, which would be suitable for 8-bit PICs too, I can help if something is not clear in my text, but to be honest I am not good at all writing libraries.

SeanG_65

#11
I found this little beauty. Look SO real!

https://www.youtube.com/watch?v=36if5NYsPBs&t=0s

top204

#12
Impressive, but as he states, it is, essentially, a cartoon of the meter, and each meter change is pulling an image off an SD card that will have many thousands of ".bmp" files on it, or a procedure to read the images from a ".mov" file with dynamic RGB565 conversion. A clever mechanism.

Very easy to do, but will need a fast microcontroller to get the image up to the LCD fast enough without too much flicker, and it is very wasteful.

I did something similar many years ago when I first started writing code for a colour LCD and an SD card for PIC24 devices. Where I converted a cartoon file into hundreds of ".bmp" images on an SD card and a loop read each ".bmp" file in sequence and uploaded it to the LCD. It looked very impressive seeing donald duck on the ILI9xxx LCD, but I couldn't actually find a use for it. But maybe, I will get into it again and see what I can do. :-)

John Drew

I suppose for best results it should use an 8bit interface?
I suspect SPI would be too slow.
John

SeanG_65

IF FOSC frequency is 64MHz(external 16MHz crystal with PLL on) so based on the PIC manual the maximum SPI speed should be FOSC/4 = 16MHz. Given you can run an SPI interface at up to 60Mbps you might be better off using a parallel to serial interface in an FPGA.