Skip to content

Instantly share code, notes, and snippets.

@wdevore
Created May 26, 2020 21:43
Show Gist options
  • Save wdevore/adef84d7708b021ce58f6bcfce3457db to your computer and use it in GitHub Desktop.
Save wdevore/adef84d7708b021ce58f6bcfce3457db to your computer and use it in GitHub Desktop.
Sprites on the pybadge
/* PyGamer/PyBadge Sprite Routines - see http://www.technoblogy.com/show?33W0
David Johnson-Davies - www.technoblogy.com - 1st May 2020
For Adafruit PyGamer/PyBadge ST7735 TFT displays
CC BY 4.0
Licensed under a Creative Commons Attribution 4.0 International license:
http://creativecommons.org/licenses/by/4.0/
*/
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include <SPI.h>
#include <Wire.h>
// Adafruit PyBadge/PyGamer
#define TFT_CS 44 // Chip select
#define TFT_RST 46 // Display reset
#define TFT_DC 45 // Display data/command select
#define TFT_BACKLIGHT 47 // Display backlight pin
#define TFT_MOSI 41 // Data out
#define TFT_SCLK 42 // Clock out
class Technoblogy_ST7735 : public Adafruit_ST7735 {
public:
Technoblogy_ST7735(int8_t cs, int8_t dc, int8_t mosi, int8_t sclk, int8_t rst);
uint16_t getPixel(uint16_t x, uint16_t y);
void xorPixel(uint16_t x, uint16_t y, uint16_t color);
void xorSprite(uint16_t x, uint16_t y, uint64_t sprite, uint16_t color);
bool hitSprite(uint16_t x, uint16_t y, uint64_t sprite, uint16_t color);
void moveSprite(uint16_t x, uint16_t y, uint64_t sprite, int dx, int dy, uint16_t color);
private:
uint16_t readCol16();
void readInit();
};
Technoblogy_ST7735::Technoblogy_ST7735(int8_t cs, int8_t dc, int8_t mosi, int8_t sclk, int8_t rst)
: Adafruit_ST7735(cs, dc, mosi, sclk, rst) {}
Technoblogy_ST7735 tft = Technoblogy_ST7735(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
// Helper functions
void Technoblogy_ST7735::readInit () {
writeCommand(ST77XX_RAMRD);
pinMode(TFT_MOSI, INPUT);
pinMode(TFT_SCLK, OUTPUT);
for (int i=0; i<9; i++) {
digitalWrite(TFT_SCLK, HIGH);
digitalWrite(TFT_SCLK, LOW);
}
}
uint16_t Technoblogy_ST7735::readCol16 () {
uint32_t col;
for (int i=0; i<24; i++) {
digitalWrite(TFT_SCLK, HIGH);
col = col<<1 | digitalRead(TFT_MOSI);
digitalWrite(TFT_SCLK, LOW);
}
return ((col & 0xf80000)>>8 | (col & 0xfc00)>>5 | (col & 0xf8)>>3);
}
// Return the colour of the pixel at x, y
uint16_t Technoblogy_ST7735::getPixel (uint16_t x, uint16_t y) {
uint16_t col16;
startWrite();
setAddrWindow(x, y, 1, 1);
readInit();
col16 = readCol16();
pinMode(TFT_MOSI, OUTPUT);
endWrite();
return col16;
}
// Plot a pixel at x, y by exclusive-ORing the colour with the display
void Technoblogy_ST7735::xorPixel (uint16_t x, uint16_t y, uint16_t color) {
uint16_t lastcolor = getPixel(x, y);
if ((x >= 0) && (x < _width) && (y >= 0) && (y < _height)) {
startWrite();
writeCommand(ST77XX_RAMWR);
SPI_WRITE16(color ^ lastcolor);
endWrite();
}
}
// Plot an 8x8 sprite at x, y by exclusive-ORing the colour with the display
void Technoblogy_ST7735::xorSprite (uint16_t x0, uint16_t y0, uint64_t sprite, uint16_t color) {
uint16_t row[8];
bool bit;
if ((x0 >= 0) && (x0+7 < _width) && (y0 >= 0) && (y0+7 < _height)) {
for (int y=0; y<8; y++) {
startWrite();
setAddrWindow(x0, y0+y, 8, 1);
readInit();
for (int x=0; x<8; x++) row[x] = readCol16();
pinMode(TFT_MOSI, OUTPUT);
endWrite();
startWrite();
writeCommand(ST77XX_RAMWR);
for (int x=0; x<8; x++) {
bit = sprite>>(63 - x - y*8) & 1;
if (bit) SPI_WRITE16(row[x] ^ color);
else SPI_WRITE16(row[x]);
}
endWrite();
}
}
}
// Collision detection between an 8x8 sprite at x, y with a specified colour
bool Technoblogy_ST7735::hitSprite (uint16_t x0, uint16_t y0, uint64_t sprite, uint16_t color) {
uint16_t row[8];
uint32_t col16;
bool bit;
if ((x0 >= 0) && (x0+7 < _width) && (y0 >= 0) && (y0+7 < _height)) {
startWrite();
setAddrWindow(x0, y0, 8, 8);
readInit();
for (int y=0; y<8; y++) {
for (int x=0; x<8; x++) {
col16 = readCol16();
bit = sprite>>(63 - x - y*8) & 1;
if (bit && col16 == color) {
pinMode(TFT_MOSI, OUTPUT);
endWrite();
return true;
}
}
}
pinMode(TFT_MOSI, OUTPUT);
endWrite();
}
return false;
}
// Move a sprite from x, y by one pixel in any direction by exclusive-ORing only the changed pixels with the display
void Technoblogy_ST7735::moveSprite(uint16_t x0, uint16_t y0, uint64_t sprite, int dx, int dy, uint16_t color) {
uint16_t row[10];
int oldbit, newbit;
if ((x0 >= 0) && (x0+7 < _width) && (y0 >= 0) && (y0+7 < _height)) {
dx = (dx > 0) - (dx < 0); dy = (dy > 0) - (dy < 0);
for (int y=0; y<10; y++) {
startWrite();
setAddrWindow(x0-1, y0+y-1, 10, 1);
readInit();
for (int x=0; x<10; x++) row[x] = readCol16();
pinMode(TFT_MOSI, OUTPUT);
endWrite();
startWrite();
writeCommand(ST77XX_RAMWR);
for (int x=0; x<10; x++) {
// Sprite's previous position
int xs = x - 1, ys = y - 1;
if (xs >= 0 && xs <= 7 && ys >=0 && ys <= 7) {
oldbit = sprite>>(63 - ys*8 - xs) & 1;
} else oldbit = 0;
// Sprite's new position
int xn = x - 1 - dx, yn = y - 1 - dy;
if (xn >= 0 && xn <= 7 && yn >=0 && yn <= 7) {
newbit = sprite>>(63 - yn*8 - xn) & 1;
} else newbit = 0;
if (oldbit != newbit) SPI_WRITE16(row[x] ^ color);
else SPI_WRITE16(row[x]);
}
endWrite();
}
}
}
// Simple rolling ball maze using the PyGamer/PyBadge accelerometer **********************************************
// Accelerometer (LIS3DH)
const int LIS3DH = 0x19;
// Set sample rate; n = 2 is 10Hz
void lis3dhRate (int n) {
Wire.beginTransmission(LIS3DH);
Wire.write(0x20);
Wire.write(n<<4 | 7);
Wire.endTransmission(true);
}
// Read register; r = 0 is X, 1 is Y, 2 is Z
int lis3dhXYZ (int r) {
Wire.beginTransmission(LIS3DH);
Wire.write(0x28 + 2*r + 0x80);
Wire.endTransmission(false);
Wire.requestFrom(LIS3DH, 4);
uint8_t lo = Wire.read();
uint8_t hi = Wire.read();
int d = lo | hi<<8;
return d - ((d & 0x8000)<<1);
}
// The rolling ball maze
const int Sens = 1024; // Accelerometer sensitivity
const uint16_t Maze = ST77XX_MAGENTA;
const uint16_t Ball = ST77XX_WHITE;
const uint16_t Board = ST77XX_BLACK;
const uint64_t Sprite = 0b\
00111100\
01111110\
11111111\
11111111\
11111111\
11111111\
01111110\
00111100;
void setup() {
tft.initR(INITR_BLACKTAB);
tft.setRotation(1);
pinMode(TFT_BACKLIGHT, OUTPUT);
digitalWrite(TFT_BACKLIGHT, HIGH);
tft.fillScreen(ST77XX_BLACK);
// Draw ball maze
for (int c=4; c>=0; c--) {
tft.fillCircle(80, 64, (c*14+7), Maze);
if (c) tft.fillCircle(80, 64, (c*14+5), Board);
}
// Gaps
tft.fillCircle(80, 112, 6, Board);
tft.fillCircle(32, 64, 6, Board);
tft.fillCircle(56, 88, 6, Board);
tft.fillCircle(94, 50, 6, Board);
Wire.begin();
lis3dhRate(2);
}
void loop() {
int x = 77, y = 115, dx, dy, x1, y1;
tft.xorSprite(x, y, Sprite, Ball);
for(;;) {
// Move ball?
int sx = lis3dhXYZ(0) / Sens;
int sy = lis3dhXYZ(1) / Sens;
// Set dx and dy to -1, 0, or +1
dx = (sx > 0) - (sx < 0);
dy = (sy > 0) - (sy < 0);
if (tft.hitSprite(x+dx, y, Sprite, Maze)) dx = 0;
if (tft.hitSprite(x, y+dy, Sprite, Maze)) dy = 0;
tft.moveSprite(x, y, Sprite, dx, dy, Ball);
x = x + dx; y = y + dy;
delay(10);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment