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

SpriteRotatingCube.ino (11451B)

      1 // TFT_eSPI library demo, principally for STM32F processors with DMA:
      2 // https://en.wikipedia.org/wiki/Direct_memory_access
      3 
      4 // Tested with ESP32, Nucleo 64 STM32F446RE and Nucleo 144 STM32F767ZI
      5 // TFT's with SPI can use DMA, the sketch also works with 8 bit
      6 // parallel TFT's (tested with ILI9341 and ILI9481)
      7 
      8 // The sketch will run on processors without DMA and also parallel
      9 // interface TFT's. Comment out line 29 for no DMA.
     10 
     11 // Library here:
     12 // https://github.com/Bodmer/TFT_eSPI
     13 
     14 // Adapted by Bodmer 18/12/19 from "RotatingCube" by Daniel Eichhorn.
     15 // See MIT License at end of sketch.
     16 
     17 // The rotating cube is drawn into a 128 x 128 Sprite and then this is
     18 // rendered to screen. The Sprite need 32Kbytes of RAM and DMA buffer the same
     19 // so processors with at least >64Kbytes RAM free will be required.
     20 
     21 // Define to use DMA for Sprite transfer to SPI TFT, comment out to use no DMA
     22 // (Tested with Nucleo 64 STM32F446RE and Nucleo 144 STM32F767ZI)
     23 // STM32F767 27MHz SPI 50% processor load: Non DMA  52 fps, with DMA 101 fps
     24 // STM32F767 27MHz SPI  0% processor load: Non DMA  97 fps, with DMA 102 fps
     25 
     26 // ESP32     27MHz SPI  0% processor load: Non DMA  90 fps, with DMA 101 fps
     27 // ESP32     40MHz SPI  0% processor load: Non DMA 127 fps, with DMA 145 fps
     28 // NOTE: FOR SPI DISPLAYS ONLY
     29 #define USE_DMA_TO_TFT
     30 
     31 // Show a processing load does not impact rendering performance
     32 // Processing load is simple algorithm to calculate prime numbers
     33 //#define PRIME_NUMBER_PROCESSOR_LOAD 491 // 241 = 50% CPU load for 128 * 128 and STM32F466 Nucleo 64
     34                                           // 491 = 50% CPU load for 128 * 128 and STM32F767 Nucleo 144
     35 
     36 // Color depth has to be 16 bits if DMA is used to render image
     37 #define COLOR_DEPTH 16
     38 
     39 // 128x128 for a 16 bit colour Sprite (32Kbytes RAM)
     40 // Maximum is 181x181 (64Kbytes) for DMA -  restricted by processor design
     41 #define IWIDTH  128
     42 #define IHEIGHT 128
     43 
     44 // Size of cube image
     45 // 358 is max for 128x128 sprite, too big and pixel trails are drawn...
     46 #define CUBE_SIZE 358
     47 
     48 #include <TFT_eSPI.h>
     49 
     50 // Library instance
     51 TFT_eSPI    tft = TFT_eSPI();         // Declare object "tft"
     52 
     53 // Create two sprites for a DMA toggle buffer
     54 TFT_eSprite spr[2] = {TFT_eSprite(&tft), TFT_eSprite(&tft) };
     55 
     56 // Toggle buffer selection
     57 bool sprSel = 0;
     58 
     59 // Pointers to start of Sprites in RAM
     60 uint16_t* sprPtr[2];
     61 
     62 // Define the cube face colors
     63 uint16_t palette[] = {TFT_WHITE,  // 1
     64                       TFT_GREENYELLOW,    // 2
     65                       TFT_YELLOW, // 3
     66                       TFT_PINK,  // 4
     67                       TFT_MAGENTA, // 5
     68                       TFT_CYAN  // 6
     69                      };
     70 
     71 // Used for fps measuring
     72 uint16_t counter = 0;
     73 long startMillis = millis();
     74 uint16_t interval = 100;
     75 
     76 // size / 2 of cube edge
     77 float d = 15;
     78 float px[] = {
     79   -d,  d,  d, -d, -d,  d,  d, -d
     80 };
     81 float py[] = {
     82   -d, -d,  d,  d, -d, -d,  d,  d
     83 };
     84 float pz[] = {
     85   -d, -d, -d, -d,  d,  d,  d,  d
     86 };
     87 
     88 // Define the triangles
     89 // The order of the vertices MUST be CCW or the
     90 // shoelace method won't work to detect visible edges
     91 int  faces[12][3] = {
     92   {0, 1, 4},
     93   {1, 5, 4},
     94   {1, 2, 5},
     95   {2, 6, 5},
     96   {5, 7, 4},
     97   {6, 7, 5},
     98   {3, 4, 7},
     99   {4, 3, 0},
    100   {0, 3, 1},
    101   {1, 3, 2},
    102   {2, 3, 6},
    103   {6, 3, 7}
    104 };
    105 
    106 // mapped coordinates on screen
    107 float p2x[] = {
    108   0, 0, 0, 0, 0, 0, 0, 0
    109 };
    110 float p2y[] = {
    111   0, 0, 0, 0, 0, 0, 0, 0
    112 };
    113 
    114 // rotation angle in radians
    115 float r[] = {
    116   0, 0, 0
    117 };
    118 
    119 // Frames per second
    120 String fps = "0fps";
    121 
    122 // Sprite draw position
    123 int16_t xpos = 0;
    124 int16_t ypos = 0;
    125 
    126 // Prime number initial value
    127 int prime_max = 2;
    128 
    129 // 3 axis spin control
    130 bool spinX = true;
    131 bool spinY = true;
    132 bool spinZ = true;
    133 
    134 // Min and max of cube edges, "int" type used for compatibility with original sketch min() function
    135 int xmin,ymin,xmax,ymax;
    136 
    137 /////////////////////////////////////////////////////////// setup ///////////////////////////////////////////////////
    138 void setup() {
    139 
    140   Serial.begin(115200);
    141 
    142   tft.init();
    143 
    144   tft.fillScreen(TFT_BLACK);
    145 
    146   xpos = 0;
    147   ypos = (tft.height() - IHEIGHT) / 2;
    148 
    149   // Define cprite colour depth
    150   spr[0].setColorDepth(COLOR_DEPTH);
    151   spr[1].setColorDepth(COLOR_DEPTH);
    152 
    153   // Create the 2 sprites
    154   sprPtr[0] = (uint16_t*)spr[0].createSprite(IWIDTH, IHEIGHT);
    155   sprPtr[1] = (uint16_t*)spr[1].createSprite(IWIDTH, IHEIGHT);
    156 
    157   // Define text datum and text colour for Sprites
    158   spr[0].setTextColor(TFT_BLACK);
    159   spr[0].setTextDatum(MC_DATUM);
    160   spr[1].setTextColor(TFT_BLACK);
    161   spr[1].setTextDatum(MC_DATUM);
    162 
    163 #ifdef USE_DMA_TO_TFT
    164   // DMA - should work with ESP32, STM32F2xx/F4xx/F7xx processors
    165   // NOTE: >>>>>> DMA IS FOR SPI DISPLAYS ONLY <<<<<<
    166   tft.initDMA(); // Initialise the DMA engine (tested with STM32F446 and STM32F767)
    167 #endif
    168 
    169   // Animation control timer
    170   startMillis = millis();
    171 
    172 }
    173 
    174 /////////////////////////////////////////////////////////// loop ///////////////////////////////////////////////////
    175 void loop() {
    176   uint32_t updateTime = 0;       // time for next update
    177   bool bounce = false;
    178   int wait = 0; //random (20);
    179 
    180   // Random movement direction
    181   int dx = 1; if (random(2)) dx = -1;
    182   int dy = 1; if (random(2)) dy = -1;
    183 
    184   // Grab exclusive use of the SPI bus
    185   tft.startWrite();
    186 
    187   // Loop forever
    188   while (1) {
    189 
    190     // Pull it back onto screen if it wanders off
    191     if (xpos < -xmin) {
    192       dx = 1;
    193       bounce = true;
    194     }
    195     if (xpos >= tft.width() - xmax) {
    196       dx = -1;
    197       bounce = true;
    198     }
    199     if (ypos < -ymin) {
    200       dy = 1;
    201       bounce = true;
    202     }
    203     if (ypos >= tft.height() - ymax) {
    204       dy = -1;
    205       bounce = true;
    206     }
    207 
    208     if (bounce) {
    209       // Randomise spin
    210       if (random(2)) spinX = true;
    211       else spinX = false;
    212       if (random(2)) spinY = true;
    213       else spinY = false;
    214       if (random(2)) spinZ = true;
    215       else spinZ = false;
    216       bounce = false;
    217       //wait = random (20);
    218     }
    219 
    220     if (updateTime <= millis())
    221     {
    222       // Use time delay so sprtie does not move fast when not all on screen
    223       updateTime = millis() + wait;
    224       xmin = IWIDTH / 2; xmax = IWIDTH / 2; ymin = IHEIGHT / 2; ymax = IHEIGHT / 2;
    225       drawCube();
    226 
    227 #ifdef USE_DMA_TO_TFT
    228       if (tft.dmaBusy()) prime_max++; // Increase processing load until just not busy
    229       tft.pushImageDMA(xpos, ypos, IWIDTH, IHEIGHT, sprPtr[sprSel]);
    230       sprSel = !sprSel;
    231 #else
    232   #ifdef PRIME_NUMBER_PROCESSOR_LOAD
    233       prime_max = PRIME_NUMBER_PROCESSOR_LOAD;
    234   #endif
    235       spr[sprSel].pushSprite(xpos, ypos); // Blocking write (no DMA) 115fps
    236 #endif
    237 
    238       counter++;
    239       // only calculate the fps every <interval> iterations.
    240       if (counter % interval == 0) {
    241         long millisSinceUpdate = millis() - startMillis;
    242         fps = String((int)(interval * 1000.0 / (millisSinceUpdate))) + " fps";
    243         Serial.println(fps);
    244         startMillis = millis();
    245       }
    246 #ifdef PRIME_NUMBER_PROCESSOR_LOAD
    247       // Add a processor task
    248       uint32_t pr = computePrimeNumbers(prime_max);
    249       Serial.print("Biggest = "); Serial.println(pr);
    250 #endif
    251       // Change coord for next loop
    252       xpos += dx;
    253       ypos += dy;
    254     }
    255   } // End of forever loop
    256 
    257   // Release exclusive use of SPI bus ( here as a reminder... forever loop prevents execution)
    258   tft.endWrite();
    259 }
    260 
    261 /**
    262   Detected visible triangles. If calculated area > 0 the triangle
    263   is rendered facing towards the viewer, since the vertices are CCW.
    264   If the area is negative the triangle is CW and thus facing away from us.
    265 */
    266 int shoelace(int x1, int y1, int x2, int y2, int x3, int y3) {
    267   // (x1y2 - y1x2) + (x2y3 - y2x3)
    268   return x1 * y2 - y1 * x2 + x2 * y3 - y2 * x3 + x3 * y1 - y3 * x1;
    269 }
    270 
    271 /**
    272   Rotates and renders the cube.
    273 **/
    274 void drawCube()
    275 {
    276   double speed = 90;
    277   if (spinX) r[0] = r[0] + PI / speed; // Add a degree
    278   if (spinY) r[1] = r[1] + PI / speed; // Add a degree
    279   if (spinZ) r[2] = r[2] + PI / speed; // Add a degree
    280 
    281   if (r[0] >= 360.0 * PI / 90.0) r[0] = 0;
    282   if (r[1] >= 360.0 * PI / 90.0) r[1] = 0;
    283   if (r[2] >= 360.0 * PI / 90.0) r[2] = 0;
    284 
    285   float ax[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    286   float ay[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    287   float az[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    288 
    289   // Calculate all vertices of the cube
    290   for (int i = 0; i < 8; i++)
    291   {
    292     float px2 = px[i];
    293     float py2 = cos(r[0]) * py[i] - sin(r[0]) * pz[i];
    294     float pz2 = sin(r[0]) * py[i] + cos(r[0]) * pz[i];
    295 
    296     float px3 = cos(r[1]) * px2 + sin(r[1]) * pz2;
    297     float py3 = py2;
    298     float pz3 = -sin(r[1]) * px2 + cos(r[1]) * pz2;
    299 
    300     ax[i] = cos(r[2]) * px3 - sin(r[2]) * py3;
    301     ay[i] = sin(r[2]) * px3 + cos(r[2]) * py3;
    302     az[i] = pz3 - 150;
    303 
    304     p2x[i] = IWIDTH / 2 + ax[i] * CUBE_SIZE / az[i];
    305     p2y[i] = IHEIGHT / 2 + ay[i] * CUBE_SIZE / az[i];
    306   }
    307 
    308   // Fill the buffer with colour 0 (Black)
    309   spr[sprSel].fillSprite(TFT_BLACK);
    310 
    311   for (int i = 0; i < 12; i++) {
    312 
    313     if (shoelace(p2x[faces[i][0]], p2y[faces[i][0]], p2x[faces[i][1]], p2y[faces[i][1]], p2x[faces[i][2]], p2y[faces[i][2]]) > 0) {
    314       int x0 = p2x[faces[i][0]];
    315       int y0 = p2y[faces[i][0]];
    316       int x1 = p2x[faces[i][1]];
    317       int y1 = p2y[faces[i][1]];
    318       int x2 = p2x[faces[i][2]];
    319       int y2 = p2y[faces[i][2]];
    320 
    321       xmin = min(xmin, x0);
    322       ymin = min(ymin, y0);
    323       xmin = min(xmin, x1);
    324       ymin = min(ymin, y1);
    325       xmin = min(xmin, x2);
    326       ymin = min(ymin, y2);
    327       xmax = max(xmax, x0);
    328       ymax = max(ymax, y0);
    329       xmax = max(xmax, x1);
    330       ymax = max(ymax, y1);
    331       xmax = max(xmax, x2);
    332       ymax = max(ymax, y2);
    333 
    334       spr[sprSel].fillTriangle(x0, y0, x1, y1, x2, y2, palette[i / 2]);
    335       if (i % 2) {
    336         int avX = 0;
    337         int avY = 0;
    338         for (int v = 0; v < 3; v++) {
    339           avX += p2x[faces[i][v]];
    340           avY += p2y[faces[i][v]];
    341         }
    342         avX = avX / 3;
    343         avY = avY / 3;
    344       }
    345     }
    346   }
    347 
    348   //spr[sprSel].drawString(fps, IWIDTH / 2, IHEIGHT / 2, 4);
    349   //delay(100);
    350 }
    351 
    352 // This is to provide a processing load to see the improvement DMA gives
    353 uint32_t computePrimeNumbers(int32_t n) {
    354   if (n<2) return 1;
    355 
    356   int32_t i, fact, j, p = 0;
    357 
    358   //Serial.print("\nPrime Numbers are: \n");
    359   for (i = 1; i <= n; i++)
    360   {
    361     fact = 0;
    362     for (j = 1; j <= n; j++)
    363     {
    364       if (i % j == 0)
    365         fact++;
    366     }
    367     if (fact == 2) {
    368       p = i;//Serial.print(i); Serial.print(", ");
    369     }
    370   }
    371   //Serial.println();
    372   return p; // Biggest
    373 }
    374 
    375 /*
    376   Original licence:
    377   The MIT License (MIT)
    378   Copyright (c) 2017 by Daniel Eichhorn
    379   Permission is hereby granted, free of charge, to any person obtaining a copy
    380   of this software and associated documentation files (the "Software"), to deal
    381   in the Software without restriction, including without limitation the rights
    382   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    383   copies of the Software, and to permit persons to whom the Software is
    384   furnished to do so, subject to the following conditions:
    385   The above copyright notice and this permission notice shall be included in all
    386   copies or substantial portions of the Software.
    387   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    388   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    389   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    390   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    391   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    392   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    393   SOFTWARE.
    394 */