186 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			186 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			C++
		
	
	
	
// 'Boing' ball demo
 | 
						|
 | 
						|
// ESP32     110 fps (no DMA)
 | 
						|
// STM32F767 55MHz SPI 170 fps without DMA
 | 
						|
// STM32F767 55MHz SPI 227 fps with DMA
 | 
						|
// STM32F446 55MHz SPI 110 fps without DMA
 | 
						|
// STM32F446 55MHz SPI 187 fps with DMA
 | 
						|
// STM32F401 55MHz SPI  56 fps without DMA
 | 
						|
// STM32F401 55MHz SPI 120 fps with DMA
 | 
						|
 | 
						|
// STM32F767 27MHz SPI  99 fps without DMA
 | 
						|
// STM32F767 27MHz SPI 120 fps with DMA
 | 
						|
// STM32F446 27MHz SPI  73 fps without DMA
 | 
						|
// STM32F446 27MHz SPI  97 fps with DMA
 | 
						|
// STM32F401 27MHz SPI  51 fps without DMA
 | 
						|
// STM32F401 27MHz SPI  90 fps with DMA
 | 
						|
 | 
						|
// Blue Pill - 36MHz SPI *no* DMA 36 fps
 | 
						|
// Blue Pill - 36MHz SPI with DMA 67 fps
 | 
						|
// Blue Pill overclocked to 128MHz *no* DMA - 32MHz SPI  64 fps
 | 
						|
// Blue Pill overclocked to 128MHz with DMA - 32MHz SPI 116 fps
 | 
						|
 | 
						|
#define SCREENWIDTH 320
 | 
						|
#define SCREENHEIGHT 240
 | 
						|
 | 
						|
#include "graphic.h"
 | 
						|
 | 
						|
#include <TFT_eSPI.h> // Hardware-specific library
 | 
						|
 | 
						|
TFT_eSPI tft = TFT_eSPI();       // Invoke custom library
 | 
						|
 | 
						|
#define BGCOLOR    0xAD75
 | 
						|
#define GRIDCOLOR  0xA815
 | 
						|
#define BGSHADOW   0x5285
 | 
						|
#define GRIDSHADOW 0x600C
 | 
						|
#define RED        0xF800
 | 
						|
#define WHITE      0xFFFF
 | 
						|
 | 
						|
#define YBOTTOM   123  // Ball Y coord at bottom
 | 
						|
 #define YBOUNCE -3.5  // Upward velocity on ball bounce
 | 
						|
 | 
						|
// Ball coordinates are stored floating-point because screen refresh
 | 
						|
// is so quick, whole-pixel movements are just too fast!
 | 
						|
float ballx     = 20.0, bally     = YBOTTOM, // Current ball position
 | 
						|
      ballvx    =  0.8, ballvy    = YBOUNCE, // Ball velocity
 | 
						|
      ballframe = 3;                         // Ball animation frame #
 | 
						|
int   balloldx  = ballx, balloldy = bally;   // Prior ball position
 | 
						|
 | 
						|
// Working buffer for ball rendering...2 scanlines that alternate,
 | 
						|
// one is rendered while the other is transferred via DMA.
 | 
						|
uint16_t renderbuf[2][SCREENWIDTH];
 | 
						|
 | 
						|
uint16_t palette[16]; // Color table for ball rotation effect
 | 
						|
 | 
						|
uint32_t startTime, frame = 0; // For frames-per-second estimate
 | 
						|
 | 
						|
void setup() {
 | 
						|
  Serial.begin(115200);
 | 
						|
//  while(!Serial);
 | 
						|
 | 
						|
  // Turn on backlight (required on PyPortal)
 | 
						|
 | 
						|
 | 
						|
  tft.begin();
 | 
						|
  tft.setRotation(3); // Landscape orientation, USB at bottom right
 | 
						|
  tft.setSwapBytes(false);
 | 
						|
  // Draw initial framebuffer contents:
 | 
						|
  //tft.setBitmapColor(GRIDCOLOR, BGCOLOR);
 | 
						|
  tft.fillScreen(BGCOLOR);
 | 
						|
 | 
						|
  tft.initDMA();
 | 
						|
 | 
						|
  delay(2000);
 | 
						|
  
 | 
						|
  tft.drawBitmap(0, 0, (const uint8_t *)background, SCREENWIDTH, SCREENHEIGHT, GRIDCOLOR);
 | 
						|
 | 
						|
  startTime = millis();
 | 
						|
}
 | 
						|
 | 
						|
void loop() {
 | 
						|
 | 
						|
  balloldx = (int16_t)ballx; // Save prior position
 | 
						|
  balloldy = (int16_t)bally;
 | 
						|
  ballx   += ballvx;         // Update position
 | 
						|
  bally   += ballvy;
 | 
						|
  ballvy  += 0.06;          // Update Y velocity
 | 
						|
  if((ballx <= 15) || (ballx >= SCREENWIDTH - BALLWIDTH))
 | 
						|
    ballvx *= -1;            // Left/right bounce
 | 
						|
  if(bally >= YBOTTOM) {     // Hit ground?
 | 
						|
    bally  = YBOTTOM;        // Clip and
 | 
						|
    ballvy = YBOUNCE;        // bounce up
 | 
						|
  }
 | 
						|
 | 
						|
  // Determine screen area to update.  This is the bounds of the ball's
 | 
						|
  // prior and current positions, so the old ball is fully erased and new
 | 
						|
  // ball is fully drawn.
 | 
						|
  int16_t minx, miny, maxx, maxy, width, height;
 | 
						|
  // Determine bounds of prior and new positions
 | 
						|
  minx = ballx;
 | 
						|
  if(balloldx < minx)                    minx = balloldx;
 | 
						|
  miny = bally;
 | 
						|
  if(balloldy < miny)                    miny = balloldy;
 | 
						|
  maxx = ballx + BALLWIDTH  - 1;
 | 
						|
  if((balloldx + BALLWIDTH  - 1) > maxx) maxx = balloldx + BALLWIDTH  - 1;
 | 
						|
  maxy = bally + BALLHEIGHT - 1;
 | 
						|
  if((balloldy + BALLHEIGHT - 1) > maxy) maxy = balloldy + BALLHEIGHT - 1;
 | 
						|
 | 
						|
  width  = maxx - minx + 1;
 | 
						|
  height = maxy - miny + 1;
 | 
						|
 | 
						|
  // Ball animation frame # is incremented opposite the ball's X velocity
 | 
						|
  ballframe -= ballvx * 0.5;
 | 
						|
  if(ballframe < 0)        ballframe += 14; // Constrain from 0 to 13
 | 
						|
  else if(ballframe >= 14) ballframe -= 14;
 | 
						|
 | 
						|
  // Set 7 palette entries to white, 7 to red, based on frame number.
 | 
						|
  // This makes the ball spin
 | 
						|
  for(uint8_t i=0; i<14; i++) {
 | 
						|
    palette[i+2] = ((((int)ballframe + i) % 14) < 7) ? WHITE : RED;
 | 
						|
    // Palette entries 0 and 1 aren't used (clear and shadow, respectively)
 | 
						|
  }
 | 
						|
 | 
						|
  // Only the changed rectangle is drawn into the 'renderbuf' array...
 | 
						|
  uint16_t c, *destPtr;
 | 
						|
  int16_t  bx  = minx - (int)ballx, // X relative to ball bitmap (can be negative)
 | 
						|
           by  = miny - (int)bally, // Y relative to ball bitmap (can be negative)
 | 
						|
           bgx = minx,              // X relative to background bitmap (>= 0)
 | 
						|
           bgy = miny,              // Y relative to background bitmap (>= 0)
 | 
						|
           x, y, bx1, bgx1;         // Loop counters and working vars
 | 
						|
  uint8_t  p;                       // 'packed' value of 2 ball pixels
 | 
						|
  int8_t bufIdx = 0;
 | 
						|
 | 
						|
  // Start SPI transaction and drop TFT_CS - avoids transaction overhead in loop
 | 
						|
  tft.startWrite();
 | 
						|
 | 
						|
  // Set window area to pour pixels into
 | 
						|
  tft.setAddrWindow(minx, miny, width, height);
 | 
						|
 | 
						|
  // Draw line by line loop
 | 
						|
  for(y=0; y<height; y++) { // For each row...
 | 
						|
    destPtr = &renderbuf[bufIdx][0];
 | 
						|
    bx1  = bx;  // Need to keep the original bx and bgx values,
 | 
						|
    bgx1 = bgx; // so copies of them are made here (and changed in loop below)
 | 
						|
    for(x=0; x<width; x++) {
 | 
						|
      if((bx1 >= 0) && (bx1 < BALLWIDTH) &&  // Is current pixel row/column
 | 
						|
         (by  >= 0) && (by  < BALLHEIGHT)) { // inside the ball bitmap area?
 | 
						|
        // Yes, do ball compositing math...
 | 
						|
        p = ball[by][bx1 / 2];                // Get packed value (2 pixels)
 | 
						|
        c = (bx1 & 1) ? (p & 0xF) : (p >> 4); // Unpack high or low nybble
 | 
						|
        if(c == 0) { // Outside ball - just draw grid
 | 
						|
          c = background[bgy][bgx1 / 8] & (0x80 >> (bgx1 & 7)) ? GRIDCOLOR : BGCOLOR;
 | 
						|
        } else if(c > 1) { // In ball area...
 | 
						|
          c = palette[c];
 | 
						|
        } else { // In shadow area...
 | 
						|
          c = background[bgy][bgx1 / 8] & (0x80 >> (bgx1 & 7)) ? GRIDSHADOW : BGSHADOW;
 | 
						|
        }
 | 
						|
      } else { // Outside ball bitmap, just draw background bitmap...
 | 
						|
        c = background[bgy][bgx1 / 8] & (0x80 >> (bgx1 & 7)) ? GRIDCOLOR : BGCOLOR;
 | 
						|
      }
 | 
						|
      *destPtr++ = c<<8 | c>>8; // Store pixel color
 | 
						|
      bx1++;  // Increment bitmap position counters (X axis)
 | 
						|
      bgx1++;
 | 
						|
    }
 | 
						|
 | 
						|
    tft.pushPixelsDMA(&renderbuf[bufIdx][0], width); // Push line to screen
 | 
						|
 | 
						|
    // Push line to screen (swap bytes false for STM/ESP32)
 | 
						|
    //tft.pushPixels(&renderbuf[bufIdx][0], width);
 | 
						|
 | 
						|
    bufIdx = 1 - bufIdx;
 | 
						|
    by++; // Increment bitmap position counters (Y axis)
 | 
						|
    bgy++;
 | 
						|
  }
 | 
						|
  //if (random(100) == 1) delay(2000);
 | 
						|
  tft.endWrite();
 | 
						|
  //delay(5);
 | 
						|
  // Show approximate frame rate
 | 
						|
  if(!(++frame & 255)) { // Every 256 frames...
 | 
						|
    uint32_t elapsed = (millis() - startTime) / 1000; // Seconds
 | 
						|
    if(elapsed) {
 | 
						|
      Serial.print(frame / elapsed);
 | 
						|
      Serial.println(" fps");
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 |