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

boing_ball.ino (6727B)

      1 // 'Boing' ball demo
      2 
      3 // STM32F767 55MHz SPI 170 fps without DMA
      4 // STM32F767 55MHz SPI 227 fps with DMA
      5 // STM32F446 55MHz SPI 110 fps without DMA
      6 // STM32F446 55MHz SPI 187 fps with DMA
      7 // STM32F401 55MHz SPI  56 fps without DMA
      8 // STM32F401 55MHz SPI 120 fps with DMA
      9 
     10 // STM32F767 27MHz SPI  99 fps without DMA
     11 // STM32F767 27MHz SPI 120 fps with DMA
     12 // STM32F446 27MHz SPI  73 fps without DMA
     13 // STM32F446 27MHz SPI  97 fps with DMA
     14 // STM32F401 27MHz SPI  51 fps without DMA
     15 // STM32F401 27MHz SPI  90 fps with DMA
     16 
     17 // Blue Pill - 36MHz SPI *no* DMA 36 fps
     18 // Blue Pill - 36MHz SPI with DMA 67 fps
     19 // Blue Pill overclocked to 128MHz *no* DMA - 32MHz SPI  64 fps
     20 // Blue Pill overclocked to 128MHz with DMA - 32MHz SPI 116 fps
     21 
     22 // ESP32     - 8 bit parallel     110 fps (no DMA)
     23 // ESP32     - 40MHz SPI *no* DMA  93 fps
     24 // ESP32     - 40MHz SPI with DMA 112 fps
     25 
     26 #define SCREENWIDTH 320
     27 #define SCREENHEIGHT 240
     28 
     29 #include "graphic.h"
     30 
     31 #include <TFT_eSPI.h> // Hardware-specific library
     32 
     33 TFT_eSPI tft = TFT_eSPI();       // Invoke custom library
     34 
     35 #define BGCOLOR    0xAD75
     36 #define GRIDCOLOR  0xA815
     37 #define BGSHADOW   0x5285
     38 #define GRIDSHADOW 0x600C
     39 #define RED        0xF800
     40 #define WHITE      0xFFFF
     41 
     42 #define YBOTTOM  123  // Ball Y coordinate at bottom
     43 #define YBOUNCE -3.5  // Upward velocity on ball bounce
     44 
     45 // Ball coordinates are stored floating-point because screen refresh
     46 // is so quick, whole-pixel movements are just too fast!
     47 float ballx     = 20.0, bally     = YBOTTOM, // Current ball position
     48       ballvx    =  0.8, ballvy    = YBOUNCE, // Ball velocity
     49       ballframe = 3;                         // Ball animation frame #
     50 int   balloldx  = ballx, balloldy = bally;   // Prior ball position
     51 
     52 // Working buffer for ball rendering...2 scan lines that alternate,
     53 // one is rendered while the other is transferred via DMA.
     54 uint16_t renderbuf[2][SCREENWIDTH];
     55 
     56 uint16_t palette[16]; // Color table for ball rotation effect
     57 
     58 uint32_t startTime, frame = 0; // For frames-per-second estimate
     59 
     60 void setup() {
     61   Serial.begin(115200);
     62 //  while(!Serial);
     63 
     64   tft.begin();
     65   tft.setRotation(3); // Landscape orientation, USB at bottom right
     66   tft.setSwapBytes(false);
     67   // Draw initial frame buffer contents:
     68   //tft.setBitmapColor(GRIDCOLOR, BGCOLOR);
     69   tft.fillScreen(BGCOLOR);
     70 
     71   tft.initDMA();
     72 
     73   tft.drawBitmap(0, 0, (const uint8_t *)background, SCREENWIDTH, SCREENHEIGHT, GRIDCOLOR);
     74 
     75   startTime = millis();
     76 }
     77 
     78 void loop() {
     79 
     80   balloldx = (int16_t)ballx; // Save prior position
     81   balloldy = (int16_t)bally;
     82   ballx   += ballvx;         // Update position
     83   bally   += ballvy;
     84   ballvy  += 0.06;          // Update Y velocity
     85   if((ballx <= 15) || (ballx >= SCREENWIDTH - BALLWIDTH))
     86     ballvx *= -1;            // Left/right bounce
     87   if(bally >= YBOTTOM) {     // Hit ground?
     88     bally  = YBOTTOM;        // Clip and
     89     ballvy = YBOUNCE;        // bounce up
     90   }
     91 
     92   // Determine screen area to update.  This is the bounds of the ball's
     93   // prior and current positions, so the old ball is fully erased and new
     94   // ball is fully drawn.
     95   int16_t minx, miny, maxx, maxy, width, height;
     96   // Determine bounds of prior and new positions
     97   minx = ballx;
     98   if(balloldx < minx)                    minx = balloldx;
     99   miny = bally;
    100   if(balloldy < miny)                    miny = balloldy;
    101   maxx = ballx + BALLWIDTH  - 1;
    102   if((balloldx + BALLWIDTH  - 1) > maxx) maxx = balloldx + BALLWIDTH  - 1;
    103   maxy = bally + BALLHEIGHT - 1;
    104   if((balloldy + BALLHEIGHT - 1) > maxy) maxy = balloldy + BALLHEIGHT - 1;
    105 
    106   width  = maxx - minx + 1;
    107   height = maxy - miny + 1;
    108 
    109   // Ball animation frame # is incremented opposite the ball's X velocity
    110   ballframe -= ballvx * 0.5;
    111   if(ballframe < 0)        ballframe += 14; // Constrain from 0 to 13
    112   else if(ballframe >= 14) ballframe -= 14;
    113 
    114   // Set 7 palette entries to white, 7 to red, based on frame number.
    115   // This makes the ball spin
    116   for(uint8_t i=0; i<14; i++) {
    117     palette[i+2] = ((((int)ballframe + i) % 14) < 7) ? WHITE : RED;
    118     // Palette entries 0 and 1 aren't used (clear and shadow, respectively)
    119   }
    120 
    121   // Only the changed rectangle is drawn into the 'renderbuf' array...
    122   uint16_t c, *destPtr;
    123   int16_t  bx  = minx - (int)ballx, // X relative to ball bitmap (can be negative)
    124            by  = miny - (int)bally, // Y relative to ball bitmap (can be negative)
    125            bgx = minx,              // X relative to background bitmap (>= 0)
    126            bgy = miny,              // Y relative to background bitmap (>= 0)
    127            x, y, bx1, bgx1;         // Loop counters and working vars
    128   uint8_t  p;                       // 'packed' value of 2 ball pixels
    129   int8_t bufIdx = 0;
    130 
    131   // Start SPI transaction and drop TFT_CS - avoids transaction overhead in loop
    132   tft.startWrite();
    133 
    134   // Set window area to pour pixels into
    135   tft.setAddrWindow(minx, miny, width, height);
    136 
    137   // Draw line by line loop
    138   for(y=0; y<height; y++) { // For each row...
    139     destPtr = &renderbuf[bufIdx][0];
    140     bx1  = bx;  // Need to keep the original bx and bgx values,
    141     bgx1 = bgx; // so copies of them are made here (and changed in loop below)
    142     for(x=0; x<width; x++) {
    143       if((bx1 >= 0) && (bx1 < BALLWIDTH) &&  // Is current pixel row/column
    144          (by  >= 0) && (by  < BALLHEIGHT)) { // inside the ball bitmap area?
    145         // Yes, do ball compositing math...
    146         p = ball[by][bx1 / 2];                // Get packed value (2 pixels)
    147         c = (bx1 & 1) ? (p & 0xF) : (p >> 4); // Unpack high or low nibble
    148         if(c == 0) { // Outside ball - just draw grid
    149           c = background[bgy][bgx1 / 8] & (0x80 >> (bgx1 & 7)) ? GRIDCOLOR : BGCOLOR;
    150         } else if(c > 1) { // In ball area...
    151           c = palette[c];
    152         } else { // In shadow area...
    153           c = background[bgy][bgx1 / 8] & (0x80 >> (bgx1 & 7)) ? GRIDSHADOW : BGSHADOW;
    154         }
    155       } else { // Outside ball bitmap, just draw background bitmap...
    156         c = background[bgy][bgx1 / 8] & (0x80 >> (bgx1 & 7)) ? GRIDCOLOR : BGCOLOR;
    157       }
    158       *destPtr++ = c<<8 | c>>8; // Store pixel colour
    159       bx1++;  // Increment bitmap position counters (X axis)
    160       bgx1++;
    161     }
    162 
    163     tft.pushPixelsDMA(&renderbuf[bufIdx][0], width); // Push line to screen
    164 
    165     // Push line to screen (swap bytes false for STM/ESP32)
    166     //tft.pushPixels(&renderbuf[bufIdx][0], width);
    167 
    168     bufIdx = 1 - bufIdx;
    169     by++; // Increment bitmap position counters (Y axis)
    170     bgy++;
    171   }
    172   //if (random(100) == 1) delay(2000);
    173   tft.endWrite();
    174   //delay(5);
    175   // Show approximate frame rate
    176   if(!(++frame & 255)) { // Every 256 frames...
    177     uint32_t elapsed = (millis() - startTime) / 1000; // Seconds
    178     if(elapsed) {
    179       Serial.print(frame / elapsed);
    180       Serial.println(" fps");
    181     }
    182   }
    183 }