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

Animated_dial.ino (7794B)

      1 // This example draws an animated dial with a rotating needle.
      2 
      3 // The dial is a jpeg image, the needle is created using a rotated
      4 // Sprite. The example operates by reading blocks of pixels from the
      5 // TFT, thus the TFT setup must support reading from the TFT CGRAM.
      6 
      7 // The sketch operates by creating a copy of the screen block where
      8 // the needle will be drawn, the needle is then drawn on the screen.
      9 // When the needle moves, the original copy of the screen area is
     10 // pushed to the screen to over-write the needle graphic. A copy
     11 // of the screen where the new position will be drawn is then made
     12 // before drawing the needle in the new position. This technique
     13 // allows the needle to move over other screen graphics.
     14 
     15 // The sketch calculates the size of the buffer memory required and
     16 // reserves the memory for the TFT block copy.
     17 
     18 // Created by Bodmer 17/3/20 as an example to the TFT_eSPI library:
     19 // https://github.com/Bodmer/TFT_eSPI
     20 
     21 #define NEEDLE_LENGTH 35  // Visible length
     22 #define NEEDLE_WIDTH   5  // Width of needle - make it an odd number
     23 #define NEEDLE_RADIUS 90  // Radius at tip
     24 #define NEEDLE_COLOR1 TFT_MAROON  // Needle periphery colour
     25 #define NEEDLE_COLOR2 TFT_RED     // Needle centre colour
     26 #define DIAL_CENTRE_X 120
     27 #define DIAL_CENTRE_Y 120
     28 
     29 // Font attached to this sketch
     30 #include "NotoSansBold36.h"
     31 #define AA_FONT_LARGE NotoSansBold36
     32 
     33 #include <TFT_eSPI.h>
     34 TFT_eSPI tft = TFT_eSPI();
     35 TFT_eSprite needle = TFT_eSprite(&tft); // Sprite object for needle
     36 TFT_eSprite spr    = TFT_eSprite(&tft); // Sprite for meter reading
     37 
     38 // Jpeg image array attached to this sketch
     39 #include "dial.h"
     40 
     41 // Include the jpeg decoder library
     42 #include <TJpg_Decoder.h>
     43 
     44 uint16_t* tft_buffer;
     45 bool      buffer_loaded = false;
     46 uint16_t  spr_width = 0;
     47 uint16_t  bg_color =0;
     48 // =======================================================================================
     49 // This function will be called during decoding of the jpeg file
     50 // =======================================================================================
     51 bool tft_output(int16_t x, int16_t y, uint16_t w, uint16_t h, uint16_t* bitmap)
     52 {
     53   // Stop further decoding as image is running off bottom of screen
     54   if ( y >= tft.height() ) return 0;
     55 
     56   // This function will clip the image block rendering automatically at the TFT boundaries
     57   tft.pushImage(x, y, w, h, bitmap);
     58 
     59   // Return 1 to decode next block
     60   return 1;
     61 }
     62 
     63 // =======================================================================================
     64 // Setup
     65 // =======================================================================================
     66 void setup()   {
     67   Serial.begin(115200); // Debug only
     68 
     69   // The byte order can be swapped (set true for TFT_eSPI)
     70   TJpgDec.setSwapBytes(true);
     71 
     72   // The jpeg decoder must be given the exact name of the rendering function above
     73   TJpgDec.setCallback(tft_output);
     74 
     75   tft.begin();
     76   tft.setRotation(0);
     77   tft.fillScreen(TFT_BLACK);
     78 
     79   // Draw the dial
     80   TJpgDec.drawJpg(0, 0, dial, sizeof(dial));
     81   tft.drawCircle(DIAL_CENTRE_X, DIAL_CENTRE_Y, NEEDLE_RADIUS-NEEDLE_LENGTH, TFT_DARKGREY);
     82 
     83   // Load the font and create the Sprite for reporting the value
     84   spr.loadFont(AA_FONT_LARGE);
     85   spr_width = spr.textWidth("777"); // 7 is widest numeral in this font
     86   spr.createSprite(spr_width, spr.fontHeight());
     87   bg_color = tft.readPixel(120, 120); // Get colour from dial centre
     88   spr.fillSprite(bg_color);
     89   spr.setTextColor(TFT_WHITE, bg_color, true);
     90   spr.setTextDatum(MC_DATUM);
     91   spr.setTextPadding(spr_width);
     92   spr.drawNumber(0, spr_width/2, spr.fontHeight()/2);
     93   spr.pushSprite(DIAL_CENTRE_X - spr_width / 2, DIAL_CENTRE_Y - spr.fontHeight() / 2);
     94 
     95   // Plot the label text
     96   tft.setTextColor(TFT_WHITE, bg_color);
     97   tft.setTextDatum(MC_DATUM);
     98   tft.drawString("(degrees)", DIAL_CENTRE_X, DIAL_CENTRE_Y + 48, 2);
     99 
    100   // Define where the needle pivot point is on the TFT before
    101   // creating the needle so boundary calculation is correct
    102   tft.setPivot(DIAL_CENTRE_X, DIAL_CENTRE_Y);
    103 
    104   // Create the needle Sprite
    105   createNeedle();
    106 
    107   // Reset needle position to 0
    108   plotNeedle(0, 0);
    109 
    110   delay(2000);
    111 }
    112 
    113 // =======================================================================================
    114 // Loop
    115 // =======================================================================================
    116 void loop() {
    117   uint16_t angle = random(241); // random speed in range 0 to 240
    118 
    119   // Plot needle at random angle in range 0 to 240, speed 40ms per increment
    120   plotNeedle(angle, 30);
    121 
    122   // Pause at new position
    123   delay(2500);
    124 }
    125 
    126 // =======================================================================================
    127 // Create the needle Sprite
    128 // =======================================================================================
    129 void createNeedle(void)
    130 {
    131   needle.setColorDepth(16);
    132   needle.createSprite(NEEDLE_WIDTH, NEEDLE_LENGTH);  // create the needle Sprite
    133 
    134   needle.fillSprite(TFT_BLACK); // Fill with black
    135 
    136   // Define needle pivot point relative to top left corner of Sprite
    137   uint16_t piv_x = NEEDLE_WIDTH / 2; // pivot x in Sprite (middle)
    138   uint16_t piv_y = NEEDLE_RADIUS;    // pivot y in Sprite
    139   needle.setPivot(piv_x, piv_y);     // Set pivot point in this Sprite
    140 
    141   // Draw the red needle in the Sprite
    142   needle.fillRect(0, 0, NEEDLE_WIDTH, NEEDLE_LENGTH, TFT_MAROON);
    143   needle.fillRect(1, 1, NEEDLE_WIDTH-2, NEEDLE_LENGTH-2, TFT_RED);
    144 
    145   // Bounding box parameters to be populated
    146   int16_t min_x;
    147   int16_t min_y;
    148   int16_t max_x;
    149   int16_t max_y;
    150 
    151   // Work out the worst case area that must be grabbed from the TFT,
    152   // this is at a 45 degree rotation
    153   needle.getRotatedBounds(45, &min_x, &min_y, &max_x, &max_y);
    154 
    155   // Calculate the size and allocate the buffer for the grabbed TFT area
    156   tft_buffer =  (uint16_t*) malloc( ((max_x - min_x) + 2) * ((max_y - min_y) + 2) * 2 );
    157 }
    158 
    159 // =======================================================================================
    160 // Move the needle to a new position
    161 // =======================================================================================
    162 void plotNeedle(int16_t angle, uint16_t ms_delay)
    163 {
    164   static int16_t old_angle = -120; // Starts at -120 degrees
    165 
    166   // Bounding box parameters
    167   static int16_t min_x;
    168   static int16_t min_y;
    169   static int16_t max_x;
    170   static int16_t max_y;
    171 
    172   if (angle < 0) angle = 0; // Limit angle to emulate needle end stops
    173   if (angle > 240) angle = 240;
    174 
    175   angle -= 120; // Starts at -120 degrees
    176 
    177   // Move the needle until new angle reached
    178   while (angle != old_angle || !buffer_loaded) {
    179 
    180     if (old_angle < angle) old_angle++;
    181     else old_angle--;
    182 
    183     // Only plot needle at even values to improve plotting performance
    184     if ( (old_angle & 1) == 0)
    185     {
    186       if (buffer_loaded) {
    187         // Paste back the original needle free image area
    188         tft.pushRect(min_x, min_y, 1 + max_x - min_x, 1 + max_y - min_y, tft_buffer);
    189       }
    190 
    191       if ( needle.getRotatedBounds(old_angle, &min_x, &min_y, &max_x, &max_y) )
    192       {
    193         // Grab a copy of the area before needle is drawn
    194         tft.readRect(min_x, min_y, 1 + max_x - min_x, 1 + max_y - min_y, tft_buffer);
    195         buffer_loaded = true;
    196       }
    197 
    198       // Draw the needle in the new position, black in needle image is transparent
    199       needle.pushRotated(old_angle, TFT_BLACK);
    200 
    201       // Wait before next update
    202       delay(ms_delay);
    203     }
    204 
    205     // Update the number at the centre of the dial
    206     spr.setTextColor(TFT_WHITE, bg_color, true);
    207     spr.drawNumber(old_angle+120, spr_width/2, spr.fontHeight()/2);
    208     spr.pushSprite(120 - spr_width / 2, 120 - spr.fontHeight() / 2);
    209 
    210     // Slow needle down slightly as it approaches the new position
    211     if (abs(old_angle - angle) < 10) ms_delay += ms_delay / 5;
    212   }
    213 }
    214 
    215 // =======================================================================================