acid-drop

- Hacking the planet from a LilyGo T-Deck using custom firmware
git clone git://git.acid.vegas/acid-drop.git
Log | Files | Refs | Archive | README | LICENSE

TFT_ring_meter.ino (9396B)

      1 /*
      2  An example showing 'ring' analogue meter on a TFT
      3  colour screen
      4 
      5  Needs Fonts 2, 4 and 7 (also Font 6 if using a large size meter)
      6  */
      7 
      8 // Meter colour schemes
      9 #define RED2RED 0
     10 #define GREEN2GREEN 1
     11 #define BLUE2BLUE 2
     12 #define BLUE2RED 3
     13 #define GREEN2RED 4
     14 #define RED2GREEN 5
     15 
     16 #define TFT_GREY 0x2104 // Dark grey 16 bit colour
     17 
     18 #include "Alert.h" // Out of range alert icon
     19 
     20 #include <TFT_eSPI.h> // Hardware-specific library
     21 #include <SPI.h>
     22 
     23 TFT_eSPI tft = TFT_eSPI(); // Invoke custom library with default width and height
     24 
     25 uint32_t runTime = -99999;       // time for next update
     26 
     27 int reading = 0; // Value to be displayed
     28 int d = 0; // Variable used for the sinewave test waveform
     29 bool range_error = 0;
     30 int8_t ramp = 1;
     31 
     32 void setup(void) {
     33   tft.begin();
     34   //Serial.begin(9600);
     35   tft.setRotation(1);
     36 
     37   tft.fillScreen(TFT_BLACK);
     38 }
     39 
     40 
     41 void loop() {
     42   if (millis() - runTime >= 0L) { // Execute every TBD ms
     43     runTime = millis();
     44 
     45     // Test with a slowly changing value from a Sine function
     46     //d += 4; if (d >= 360) d = 0;
     47 
     48     // Set the the position, gap between meters, and inner radius of the meters
     49     int xpos = 0, ypos = 5, gap = 4, radius = 52;
     50 
     51     // Draw meter and get back x position of next meter
     52 
     53     // Test with Sine wave function, normally reading will be from a sensor
     54     //reading = 250 + 250 * sineWave(d+0);
     55     //xpos = gap + ringMeter(reading, 0, 500, xpos, ypos, radius, "mA", GREEN2RED); // Draw analogue meter
     56 
     57     //reading = 20 + 30 * sineWave(d+60);
     58     //xpos = gap + ringMeter(reading, -10, 50, xpos, ypos, radius, "degC", BLUE2RED); // Draw analogue meter
     59 
     60     //reading = 50 + 50 * sineWave(d + 120);
     61     //ringMeter(reading, 0, 100, xpos, ypos, radius, "%RH", BLUE2BLUE); // Draw analogue meter
     62 
     63 
     64     // Draw two more larger meters
     65     //xpos = 20, ypos = 115, gap = 24, radius = 64;
     66 
     67     //reading = 1000 + 150 * sineWave(d + 90);
     68     //xpos = gap + ringMeter(reading, 850, 1150, xpos, ypos, radius, "mb", BLUE2RED); // Draw analogue meter
     69 
     70     //reading = 15 + 15 * sineWave(d + 150);
     71     //xpos = gap + ringMeter(reading, 0, 30, xpos, ypos, radius, "Volts", GREEN2GREEN); // Draw analogue meter
     72 
     73     // Draw a large meter
     74     xpos = 480/2 - 160, ypos = 0, gap = 15, radius = 170;
     75     reading +=(ramp);
     76     if (reading>98) ramp = -1;
     77     if (reading<0) ramp = 1;
     78     // Comment out above meters, then uncomment the next line to show large meter
     79     ringMeter(reading,0,100, xpos,ypos,radius," Watts",GREEN2RED); // Draw analogue meter
     80     if (reading<0) delay(1000);
     81   }
     82 }
     83 
     84 // #########################################################################
     85 //  Draw the meter on the screen, returns x coord of righthand side
     86 // #########################################################################
     87 int ringMeter(int value, int vmin, int vmax, int x, int y, int r, const char *units, byte scheme)
     88 {
     89   // Minimum value of r is about 52 before value text intrudes on ring
     90   // drawing the text first is an option
     91   
     92   x += r; y += r;   // Calculate coords of centre of ring
     93 
     94   int w = r / 3;    // Width of outer ring is 1/4 of radius
     95   
     96   int angle = 150;  // Half the sweep angle of meter (300 degrees)
     97 
     98   int v = map(value, vmin, vmax, -angle, angle); // Map the value to an angle v
     99 
    100   byte seg = 3; // Segments are 3 degrees wide = 100 segments for 300 degrees
    101   byte inc = 6; // Draw segments every 3 degrees, increase to 6 for segmented ring
    102 
    103   // Variable to save "value" text colour from scheme and set default
    104   int colour = TFT_BLUE;
    105  
    106   // Draw colour blocks every inc degrees
    107   for (int i = -angle+inc/2; i < angle-inc/2; i += inc) {
    108     // Calculate pair of coordinates for segment start
    109     float sx = cos((i - 90) * 0.0174532925);
    110     float sy = sin((i - 90) * 0.0174532925);
    111     uint16_t x0 = sx * (r - w) + x;
    112     uint16_t y0 = sy * (r - w) + y;
    113     uint16_t x1 = sx * r + x;
    114     uint16_t y1 = sy * r + y;
    115 
    116     // Calculate pair of coordinates for segment end
    117     float sx2 = cos((i + seg - 90) * 0.0174532925);
    118     float sy2 = sin((i + seg - 90) * 0.0174532925);
    119     int x2 = sx2 * (r - w) + x;
    120     int y2 = sy2 * (r - w) + y;
    121     int x3 = sx2 * r + x;
    122     int y3 = sy2 * r + y;
    123 
    124     if (i < v) { // Fill in coloured segments with 2 triangles
    125       switch (scheme) {
    126         case 0: colour = TFT_RED; break; // Fixed colour
    127         case 1: colour = TFT_GREEN; break; // Fixed colour
    128         case 2: colour = TFT_BLUE; break; // Fixed colour
    129         case 3: colour = rainbow(map(i, -angle, angle, 0, 127)); break; // Full spectrum blue to red
    130         case 4: colour = rainbow(map(i, -angle, angle, 70, 127)); break; // Green to red (high temperature etc)
    131         case 5: colour = rainbow(map(i, -angle, angle, 127, 63)); break; // Red to green (low battery etc)
    132         default: colour = TFT_BLUE; break; // Fixed colour
    133       }
    134       tft.fillTriangle(x0, y0, x1, y1, x2, y2, colour);
    135       tft.fillTriangle(x1, y1, x2, y2, x3, y3, colour);
    136       //text_colour = colour; // Save the last colour drawn
    137     }
    138     else // Fill in blank segments
    139     {
    140       tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREY);
    141       tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREY);
    142     }
    143   }
    144   // Convert value to a string
    145   char buf[10];
    146   byte len = 3; if (value > 999) len = 5;
    147   dtostrf(value, len, 0, buf);
    148   buf[len] = ' '; buf[len+1] = 0; // Add blanking space and terminator, helps to centre text too!
    149   // Set the text colour to default
    150   tft.setTextSize(1);
    151 
    152   if (value<vmin || value>vmax) {
    153     drawAlert(x,y+90,50,1);
    154   }
    155   else {
    156     drawAlert(x,y+90,50,0);
    157   }
    158 
    159   tft.setTextColor(TFT_WHITE, TFT_BLACK);
    160   // Uncomment next line to set the text colour to the last segment value!
    161   tft.setTextColor(colour, TFT_BLACK);
    162   tft.setTextDatum(MC_DATUM);
    163   // Print value, if the meter is large then use big font 8, othewise use 4
    164   if (r > 84) {
    165     tft.setTextPadding(55*3); // Allow for 3 digits each 55 pixels wide
    166     tft.drawString(buf, x, y, 8); // Value in middle
    167   }
    168   else {
    169     tft.setTextPadding(3 * 14); // Allow for 3 digits each 14 pixels wide
    170     tft.drawString(buf, x, y, 4); // Value in middle
    171   }
    172   tft.setTextSize(1);
    173   tft.setTextPadding(0);
    174   // Print units, if the meter is large then use big font 4, othewise use 2
    175   tft.setTextColor(TFT_WHITE, TFT_BLACK);
    176   if (r > 84) tft.drawString(units, x, y + 60, 4); // Units display
    177   else tft.drawString(units, x, y + 15, 2); // Units display
    178 
    179   // Calculate and return right hand side x coordinate
    180   return x + r;
    181 }
    182 
    183 void drawAlert(int x, int y , int side, bool draw)
    184 {
    185   if (draw && !range_error) {
    186     drawIcon(alert, x - alertWidth/2, y - alertHeight/2, alertWidth, alertHeight);
    187     range_error = 1;
    188   }
    189   else if (!draw) {
    190     tft.fillRect(x - alertWidth/2, y - alertHeight/2, alertWidth, alertHeight, TFT_BLACK);
    191     range_error = 0;
    192   }
    193 }
    194 
    195 // #########################################################################
    196 // Return a 16 bit rainbow colour
    197 // #########################################################################
    198 unsigned int rainbow(byte value)
    199 {
    200   // Value is expected to be in range 0-127
    201   // The value is converted to a spectrum colour from 0 = blue through to 127 = red
    202 
    203   byte red = 0; // Red is the top 5 bits of a 16 bit colour value
    204   byte green = 0;// Green is the middle 6 bits
    205   byte blue = 0; // Blue is the bottom 5 bits
    206 
    207   byte quadrant = value / 32;
    208 
    209   if (quadrant == 0) {
    210     blue = 31;
    211     green = 2 * (value % 32);
    212     red = 0;
    213   }
    214   if (quadrant == 1) {
    215     blue = 31 - (value % 32);
    216     green = 63;
    217     red = 0;
    218   }
    219   if (quadrant == 2) {
    220     blue = 0;
    221     green = 63;
    222     red = value % 32;
    223   }
    224   if (quadrant == 3) {
    225     blue = 0;
    226     green = 63 - 2 * (value % 32);
    227     red = 31;
    228   }
    229   return (red << 11) + (green << 5) + blue;
    230 }
    231 
    232 // #########################################################################
    233 // Return a value in range -1 to +1 for a given phase angle in degrees
    234 // #########################################################################
    235 float sineWave(int phase) {
    236   return sin(phase * 0.0174532925);
    237 }
    238 
    239 
    240 //====================================================================================
    241 // This is the function to draw the icon stored as an array in program memory (FLASH)
    242 //====================================================================================
    243 
    244 // To speed up rendering we use a 64 pixel buffer
    245 #define BUFF_SIZE 64
    246 
    247 // Draw array "icon" of defined width and height at coordinate x,y
    248 // Maximum icon size is 255x255 pixels to avoid integer overflow
    249 
    250 void drawIcon(const unsigned short* icon, int16_t x, int16_t y, int8_t width, int8_t height) {
    251 
    252   uint16_t  pix_buffer[BUFF_SIZE];   // Pixel buffer (16 bits per pixel)
    253 
    254   tft.startWrite();
    255 
    256   // Set up a window the right size to stream pixels into
    257   tft.setAddrWindow(x, y, width, height);
    258 
    259   // Work out the number whole buffers to send
    260   uint16_t nb = ((uint16_t)height * width) / BUFF_SIZE;
    261 
    262   // Fill and send "nb" buffers to TFT
    263   for (int i = 0; i < nb; i++) {
    264     for (int j = 0; j < BUFF_SIZE; j++) {
    265       pix_buffer[j] = pgm_read_word(&icon[i * BUFF_SIZE + j]);
    266     }
    267     tft.pushColors(pix_buffer, BUFF_SIZE);
    268   }
    269 
    270   // Work out number of pixels not yet sent
    271   uint16_t np = ((uint16_t)height * width) % BUFF_SIZE;
    272 
    273   // Send any partial buffer left over
    274   if (np) {
    275     for (int i = 0; i < np; i++) pix_buffer[i] = pgm_read_word(&icon[nb * BUFF_SIZE + i]);
    276     tft.pushColors(pix_buffer, np);
    277   }
    278 
    279   tft.endWrite();
    280 }
    281