Calibrating and Coding Your Arduino Touchscreen
2nd December 2020Adding Bluetooth to Your Arduino Projects
30th January 2021Coding Games on an ILI9341 SPI LCD Touchscreen with Arduino
Breakout Code
#include "SPI.h" #include "Adafruit_GFX.h" #include "Adafruit_ILI9341.h" #include "XPT2046_Touchscreen.h" #include "Math.h" // function declarations boolean checkCollision(int x1, int y1, int width1, int height1, int x2, int y2, int width2, int height2); // For the Adafruit shield, these are the default. #define TFT_CS 10 #define TFT_DC 9 #define TFT_MOSI 11 #define TFT_CLK 13 #define TFT_RST 8 #define TFT_MISO 12 #define TS_CS 7 #define ROTATION 3 // Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC/RST Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST); XPT2046_Touchscreen ts(TS_CS); float xCalM = 0.0, yCalM = 0.0; float xCalC = 0.0, yCalC = 0.0; float xPos = 0.0, yPos = 0.0; float xPosLast = 0.0, yPosLast = 0.0; float xVel = 0.0, yVel = 0.0; int8_t topBorder = 20; int8_t batWidth = 30; int8_t batHeight = 7; int16_t batX = 0, batY = 0; int8_t ballSize = 3; int playerLives = 3; int playerScore = 0; int gameState = 1; // 1=start 2=playing 3=gameover int level; int16_t tftWidth = 0, tftHeight = 0; class ScreenPoint { public: int16_t x; int16_t y; ScreenPoint(){ // default contructor } ScreenPoint(int16_t xIn, int16_t yIn){ x = xIn; y = yIn; } }; class Block { public: int x; int y; int width; int height; int colour; int score; boolean isActive; // default constructor Block(){} Block(int xpos, int ypos, int bwidth, int bheight, int bcol, int bscore){ x = xpos; y = ypos; width = bwidth; height = bheight; colour = bcol; score = bscore; isActive = true; drawBlock(); } void drawBlock(){ tft.fillRect(x,y,width,height,colour); } void removeBlock(){ tft.fillRect(x,y,width,height,ILI9341_BLACK); isActive = false; } boolean isHit(float x1,float y1, int w1,int h1){ if (checkCollision((int)round(x1),(int)round(y1),w1,h1,x,y,width,height)){ return true; } else { return false; } } }; Block blocks[5][16]; ScreenPoint getScreenCoords(int16_t x, int16_t y){ int16_t xCoord = round((x * xCalM) + xCalC); int16_t yCoord = round((y * yCalM) + yCalC); if(xCoord < 0) xCoord = 0; if(xCoord >= tftWidth) xCoord = tftWidth - 1; if(yCoord < 0) yCoord = 0; if(yCoord >= tftHeight) yCoord = tftHeight - 1; return(ScreenPoint(xCoord, yCoord)); } void calibrateTouchScreen(){ TS_Point p; int16_t x1,y1,x2,y2; tft.fillScreen(ILI9341_BLACK); // wait for no touch while(ts.touched()); tft.drawFastHLine(10,20,20,ILI9341_RED); tft.drawFastVLine(20,10,20,ILI9341_RED); while(!ts.touched()); p = ts.getPoint(); x1 = p.x; y1 = p.y; tft.drawFastHLine(10,20,20,ILI9341_BLACK); tft.drawFastVLine(20,10,20,ILI9341_BLACK); delay(500); while(ts.touched()); tft.drawFastHLine(tftWidth - 30,tftHeight - 20,20,ILI9341_RED); tft.drawFastVLine(tftWidth - 20,tftHeight - 30,20,ILI9341_RED); while(!ts.touched()); p = ts.getPoint(); x2 = p.x; y2 = p.y; tft.drawFastHLine(tftWidth - 30,tftHeight - 20,20,ILI9341_BLACK); tft.drawFastVLine(tftWidth - 20,tftHeight - 30,20,ILI9341_BLACK); int16_t xDist = tftWidth - 40; int16_t yDist = tftHeight - 40; // translate in form pos = m x val + c // x xCalM = (float)xDist / (float)(x2 - x1); xCalC = 20.0 - ((float)x1 * xCalM); // y yCalM = (float)yDist / (float)(y2 - y1); yCalC = 20.0 - ((float)y1 * yCalM); Serial.print("x1 = ");Serial.print(x1); Serial.print(", y1 = ");Serial.print(y1); Serial.print("x2 = ");Serial.print(x2); Serial.print(", y2 = ");Serial.println(y2); Serial.print("xCalM = ");Serial.print(xCalM); Serial.print(", xCalC = ");Serial.print(xCalC); Serial.print("yCalM = ");Serial.print(yCalM); Serial.print(", yCalC = ");Serial.println(yCalC); } void setup() { Serial.begin(9600); // avoid chip select contention pinMode(TS_CS, OUTPUT); digitalWrite(TS_CS, HIGH); pinMode(TFT_CS, OUTPUT); digitalWrite(TFT_CS, HIGH); tft.begin(); tft.setRotation(ROTATION); tft.fillScreen(ILI9341_BLACK); tftWidth = tft.width(); tftHeight = tft.height(); ts.begin(); ts.setRotation(ROTATION); calibrateTouchScreen(); batY = tftHeight - batHeight -30; } void initGame(){ tft.fillScreen(ILI9341_BLACK); tft.drawFastHLine(0, topBorder-1, tftWidth, ILI9341_BLUE); tft.setCursor(0,5); tft.setTextSize(1); tft.setTextColor(ILI9341_WHITE); tft.print("SCORE :"); tft.setCursor((tftWidth/2), 5); tft.print("LIVES :"); tft.setCursor(tftWidth - 75, 5); tft.print("LEVEL :"); batY = tftHeight - batHeight -30; playerLives = 3; playerScore = 0; level = 0; drawLives(); drawScore(); drawLevel(); initGameBoard(); } void initGameBoard() { int row, col; int colour, score; clearOldBallPos(); xPosLast = xPos = 0; yPosLast = yPos = 90; xVel = 2; yVel = 2 + (level); gameState = 2; for(row=0; row < 5; row++){ for (col=0; col < 16; col++){ switch(row){ case 0: case 1: colour = ILI9341_BLUE; score = 50; break; case 2: case 3: colour = ILI9341_MAGENTA; score = 30; break; case 4: case 5: colour = ILI9341_YELLOW; score = 10; break; } blocks[row][col] = Block(col*20, (row*10) + 30, 19, 9,colour,score); } } } void clearOldBallPos(){ tft.fillCircle(round(xPosLast), round(yPosLast), ballSize, ILI9341_BLACK); } void moveBall(){ float newX, newY; newX = xPos + xVel; newY = yPos + yVel; if (newX < (float)ballSize){ newX = (float)ballSize; xVel = -xVel; } if (newX > (float)(tftWidth - ballSize - 1)){ newX = (float)(tftWidth - ballSize - 1); xVel = -xVel; } if (newY < topBorder + (float)ballSize){ newY = topBorder + (float)ballSize; yVel = -yVel; } if ((round(newX) != round(xPosLast)) || (round(newY) != round(yPosLast))){ // draw ball clearOldBallPos(); tft.fillCircle(round(newX), round(newY), ballSize, ILI9341_GREEN); xPosLast = newX; yPosLast = newY; } xPos = newX; yPos = newY; } void drawScore(){ // clear old score tft.fillRect(50,5,25,10,ILI9341_BLACK); // print new score tft.setCursor(50,5); tft.setTextSize(1); tft.setTextColor(ILI9341_WHITE); tft.print(playerScore); } void drawLives(){ // clear old lives tft.fillRect((tftWidth/2)+50,5,25,10,ILI9341_BLACK); // print new score tft.setCursor((tftWidth/2)+50,5); tft.setTextSize(1); tft.setTextColor(ILI9341_WHITE); tft.print(playerLives); } void drawLevel(){ // clear old level tft.fillRect(tftWidth-25,5,25,10,ILI9341_BLACK); // print new score tft.setCursor(tftWidth-25,5); tft.setTextSize(1); tft.setTextColor(ILI9341_WHITE); tft.print(level + 1); } void newBall(){ xPos = 0; yPos = 90; xVel = yVel = 2; moveBall(); delay(1000); } boolean checkBallLost(){ if (yPos > tftHeight + ballSize){ return true; } else { return false; } } void moveBat(){ int16_t newBatX; ScreenPoint sp = ScreenPoint(0,0); if (ts.touched()) { TS_Point p = ts.getPoint(); sp = getScreenCoords(p.x, p.y); newBatX = sp.x - (batWidth / 2); if (newBatX < 0) newBatX = 0; if (newBatX >= (tftWidth - batWidth)) newBatX = tftWidth - 1 - batWidth; } if (abs(newBatX - batX) > 4){ tft.fillRect(batX, batY, batWidth, batHeight,ILI9341_BLACK); batX = newBatX; tft.fillRect(batX, batY, batWidth, batHeight,ILI9341_RED); } } // bounding box collision detection boolean checkCollision(int x1, int y1, int width1, int height1, int x2, int y2, int width2, int height2){ boolean hit = false; if ( (((x2 + width2) >= x1) && (x2 <= (x1 + width1))) && (((y2 + height2) >= y1) && (y2 <= (y1 + height1))) ) { hit = true; } return hit; } void checkHitBat(){ // check bat and bottom half of ball float xInc; boolean hit = checkCollision(batX, batY, batWidth, batHeight, (int)round(xPos)-ballSize, (int)round(yPos), ballSize*2, ballSize); if (hit) { // reverse ball y direction but increase speed yVel += 0.05; if (yVel > 5){ yVel = 5; } yVel = -yVel; // rounded bounce xInc = (xPos - (float)(batX + (batWidth / 2))) / (float)batWidth; xVel += 6 * xInc; if (abs(xVel) > 6){ if (xVel < 0) { xVel = -6; } else { xVel = 6; } } // make sure ball not hitting bat yPos = (float)(batY - ballSize -1); } } void checkHitBlock(){ int row, col; for (row=0; row<5; row++){ for (col=0; col<16; col++){ if (blocks[row][col].isActive && blocks[row][col].isHit(xPos,yPos, ballSize*2,ballSize*2)){ blocks[row][col].removeBlock(); playerScore += blocks[row][col].score; drawScore(); yVel = -yVel; return; } } } } boolean checkAllBlocksHit(){ int row, col, actives; actives = 0; for (row=0; row<5; row++){ for (col=0; col<16; col++){ if (blocks[row][col].isActive){ return false; } } } return true; } unsigned long lastFrame = millis(); void loop(void) { // limit frame rate while((millis() - lastFrame) < 20); lastFrame = millis(); switch(gameState){ case 1: // start gameState = 11; break; case 11: // click to play tft.fillRect((tftWidth/2)-100,(tftHeight/2)-20,200,40,ILI9341_GREEN); tft.setCursor((tftWidth/2)-100+25,(tftHeight/2)-20+12); tft.setTextSize(2); tft.setTextColor(ILI9341_WHITE); tft.print("CLICK TO PLAY"); gameState = 12; break; case 12: // wait for click to play if (ts.touched()) { TS_Point p = ts.getPoint(); ScreenPoint sp = getScreenCoords(p.x, p.y); if (checkCollision(sp.x, sp.y,1,1,(tftWidth/2)-50,(tftHeight/2)-20,100,40)){ initGame(); gameState = 2; } } break; case 2: // play moveBall(); moveBat(); checkHitBat(); checkHitBlock(); if (checkBallLost()){ playerLives --; drawLives(); if (playerLives > 0){ newBall(); } else { gameState = 3; // end game } } if (checkAllBlocksHit()){ gameState = 4; } break; case 4: // new blocks delay(1000); level ++; drawLevel(); initGameBoard(); break; case 3: // end tft.fillScreen(ILI9341_BLACK); tft.setCursor((tftWidth/2)-150,50); tft.setTextSize(3); tft.setTextColor(ILI9341_WHITE); tft.print("You Scored "); tft.print(playerScore); gameState = 11; // click to play break; } }
Links
LCD module – Amazon
https://amzn.to/35quUJF
LCD eBay
https://rover.ebay.com/rover/1/710-53481-19255-0/1
ILI9341 Library
https://github.com/adafruit/Adafruit_ILI9341
XPT2046 Library
https://github.com/PaulStoffregen/XPT2046_Touchscreen
Adafruit GFX Library
https://github.com/adafruit/Adafruit-GFX-Library
Adafruit LCD tutorial
https://learn.adafruit.com/adafruit-2-8-and-3-2-color-tft-touchscreen-breakout-v2/spi-wiring-and-test
Adafruit GFX Library Tutorial
https://learn.adaruit.com/adafruit-gfx-graphics-library/overview