Tilt snake on an 8x8 matrix, on an Arduino Uno
Tip an MPU-6050 on A4 and A5 to steer a snake round an 8x8 MAX7219 matrix (DIN 13, CLK 7, CS 8): eat the blinking dot to grow, and do not bite your own tail. The whole build, an Arduino Uno and 2 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate About 30 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $37. On Mokxi they're free.
What you'll learn
- Reading accelerometer registers with
Wire - Turning a tilt into a direction
- Driving an 8x8 matrix with
LedControl - Game state in arrays
- Wrapping around edges with the modulo operator
- Difficulty: Intermediate
- Time: About 30 minutes
- Board: Arduino Uno R3
- Parts: 4
How it works
An MPU-6050 on A4 and A5 measures which way gravity pulls on the board. The sketch reads its X and Y values over I2C. If the lean is large enough (more than LEAN, about 20 degrees), that sets the snake's next direction.
A MAX7219 8x8 matrix on pins 13, 7 and 8 shows the snake and a blinking food dot. The snake steps once every 400 ms and wraps around the edges. It cannot turn straight back on itself.
Eating the dot makes the snake longer. Every fourth length makes it a little faster. If it hits its own tail, the matrix fills up and a new game starts.
Parts list
4 parts, plus the jumper wires. They retail for about $37; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 1 × LED matrix, 8x8
- 1 × Accelerometer and gyroscope, MPU6050In Desk Buddy Kit, Laser Sword Kit, Escape Box Kit.
Kits that can build it
This starter kit has every part in the circuit. Each kit's page lists what else it can build.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| LED matrix, 8x8 pin DIN | Arduino Uno R3 pin 13 |
| LED matrix, 8x8 pin CS | Arduino Uno R3 pin 8 |
| LED matrix, 8x8 pin CLK | Arduino Uno R3 pin 7 |
| LED matrix, 8x8 pin VCC | Arduino Uno R3 pin 5V |
| Accelerometer and gyroscope, MPU6050 pin VCC | Arduino Uno R3 pin 5V |
| LED matrix, 8x8 pin GND | Arduino Uno R3 pin GND |
| Accelerometer and gyroscope, MPU6050 pin GND | Arduino Uno R3 pin GND |
| Accelerometer and gyroscope, MPU6050 pin SDA | Arduino Uno R3 pin A4 |
| Accelerometer and gyroscope, MPU6050 pin SCL | Arduino Uno R3 pin A5 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Tilt snake: steer a snake round an 8x8 matrix by tipping the board.
//
// MAX7219 DIN -> pin 13 CLK -> pin 7 CS -> pin 8
// MPU-6050 (GY-521) SDA -> A4 SCL -> A5 VCC -> 5V GND -> GND
//
// The MPU-6050 measures which way gravity pulls. Lie it flat and gravity is
// all on its Z axis; tip it forward or back and some of it moves onto Y, tip
// it left or right and some moves onto X. The sketch reads X and Y straight
// from the chip's registers with Wire, and a large enough lean picks the
// snake's next direction. It cannot turn straight back on itself.
//
// The snake steps once every 400 ms and comes out of the opposite edge when
// it leaves the matrix. Eat the blinking dot to grow; every fourth dot makes
// it a little faster. Run into your own tail and the matrix fills up, then a
// new game starts.
//
// Drag the module's pitch or roll to steer.
#include <Wire.h>
#include <LedControl.h>
const int MPU = 0x68;
const int LEAN = 6000; // about 20 degrees, out of 16384 for a full 1 g
LedControl lc = LedControl(13, 7, 8, 1); // DIN, CLK, CS, modules
byte snakeX[64];
byte snakeY[64];
int length = 2;
int dx = 1, dy = 0;
byte foodX, foodY;
int stepMs = 400;
unsigned long lastStep = 0;
bool foodOn = true;
unsigned long lastBlink = 0;
void readTilt(int &ax, int &ay) {
Wire.beginTransmission(MPU);
Wire.write(0x3B); // ACCEL_XOUT_H, then XOUT_L, YOUT_H, YOUT_L
Wire.endTransmission(false);
Wire.requestFrom(MPU, 4);
ax = Wire.read() << 8 | Wire.read();
ay = Wire.read() << 8 | Wire.read();
}
bool onSnake(byte x, byte y) {
for (int i = 0; i < length; i++) {
if (snakeX[i] == x && snakeY[i] == y) return true;
}
return false;
}
void placeFood() {
do {
foodX = random(8);
foodY = random(8);
} while (onSnake(foodX, foodY));
}
void newGame() {
length = 2;
snakeX[0] = 1; snakeY[0] = 4;
snakeX[1] = 0; snakeY[1] = 4;
dx = 1; dy = 0;
stepMs = 400;
placeFood();
Serial.println("new game");
}
void draw() {
lc.clearDisplay(0);
for (int i = 0; i < length; i++) lc.setLed(0, snakeY[i], snakeX[i], true);
if (foodOn) lc.setLed(0, foodY, foodX, true);
}
void steer() {
int ax, ay;
readTilt(ax, ay);
int ndx = dx, ndy = dy;
if (abs(ay) > abs(ax) && abs(ay) > LEAN) {
ndx = 0;
ndy = ay > 0 ? -1 : 1;
} else if (abs(ax) > LEAN) {
ndx = ax > 0 ? -1 : 1;
ndy = 0;
}
if (ndx == -dx && ndy == -dy) return; // no turning back on yourself
if (ndx != dx || ndy != dy) {
dx = ndx;
dy = ndy;
Serial.println(dy < 0 ? "turn up" : dy > 0 ? "turn down" : dx < 0 ? "turn left" : "turn right");
}
}
void gameOver() {
Serial.print("game over, length ");
Serial.println(length);
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
lc.setLed(0, row, col, true);
delay(15);
}
}
delay(500);
newGame();
}
void step() {
int x = (snakeX[0] + dx + 8) % 8;
int y = (snakeY[0] + dy + 8) % 8;
bool eats = x == foodX && y == foodY;
// The tail moves out of the way this step unless the snake grows.
for (int i = 0; i < length - (eats ? 0 : 1); i++) {
if (snakeX[i] == x && snakeY[i] == y) {
gameOver();
return;
}
}
if (eats && length < 64) length++;
for (int i = length - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
snakeX[0] = x;
snakeY[0] = y;
if (eats) {
Serial.print("ate a dot, length ");
Serial.println(length);
if (length % 4 == 0 && stepMs > 150) stepMs -= 50;
if (length < 64) placeFood();
}
}
void setup() {
Serial.begin(9600);
Wire.begin();
Wire.beginTransmission(MPU);
Wire.write(0x6B); // PWR_MGMT_1: wake the chip up
Wire.write(0);
Wire.endTransmission();
lc.shutdown(0, false);
lc.setIntensity(0, 6);
lc.clearDisplay(0);
randomSeed(analogRead(A0));
Serial.println("Mokxi Uno: tilt snake. Tip the board to steer.");
newGame();
}
void loop() {
steer();
if (millis() - lastBlink >= 150) {
lastBlink = millis();
foodOn = !foodOn;
}
if (millis() - lastStep >= (unsigned long)stepMs) {
lastStep = millis();
step();
}
draw();
delay(20);
}
Code walkthrough
The key functions of the sketch, and what each one does.
readTilt sketch.ino
Asks the MPU-6050 for four bytes starting at register 0x3B, which hold the X and Y readings. It joins each pair of bytes into one signed number.
void readTilt(int &ax, int &ay) {
Wire.beginTransmission(MPU);
Wire.write(0x3B); // ACCEL_XOUT_H, then XOUT_L, YOUT_H, YOUT_L
Wire.endTransmission(false);
Wire.requestFrom(MPU, 4);
ax = Wire.read() << 8 | Wire.read();
ay = Wire.read() << 8 | Wire.read();
}steer sketch.ino
Reads the tilt and picks the stronger axis. If it is beyond LEAN, it sets a new direction. A move that would reverse the snake is ignored, and a real turn is printed to the monitor.
void steer() {
int ax, ay;
readTilt(ax, ay);
int ndx = dx, ndy = dy;
if (abs(ay) > abs(ax) && abs(ay) > LEAN) {
ndx = 0;
ndy = ay > 0 ? -1 : 1;
} else if (abs(ax) > LEAN) {
ndx = ax > 0 ? -1 : 1;
ndy = 0;
}
if (ndx == -dx && ndy == -dy) return; // no turning back on yourself
if (ndx != dx || ndy != dy) {
dx = ndx;
dy = ndy;
Serial.println(dy < 0 ? "turn up" : dy > 0 ? "turn down" : dx < 0 ? "turn left" : "turn right");
}
}step sketch.ino
Works out the next head position with wraparound and checks for a hit on the body. If the snake eats, it grows, speeds up every fourth length, and places new food. Then every segment shifts one place toward the head.
void step() {
int x = (snakeX[0] + dx + 8) % 8;
int y = (snakeY[0] + dy + 8) % 8;
bool eats = x == foodX && y == foodY;
// The tail moves out of the way this step unless the snake grows.
for (int i = 0; i < length - (eats ? 0 : 1); i++) {
if (snakeX[i] == x && snakeY[i] == y) {
gameOver();
return;
}
}
if (eats && length < 64) length++;
for (int i = length - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
snakeX[0] = x;
snakeY[0] = y;
if (eats) {
Serial.print("ate a dot, length ");
Serial.println(length);
if (length % 4 == 0 && stepMs > 150) stepMs -= 50;
if (length < 64) placeFood();
}
}draw sketch.ino
Clears the matrix and lights each snake segment. The food dot is drawn only while it is in the on part of its blink.
void draw() {
lc.clearDisplay(0);
for (int i = 0; i < length; i++) lc.setLed(0, snakeY[i], snakeX[i], true);
if (foodOn) lc.setLed(0, foodY, foodX, true);
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- The snake never turns This project
Check SDA on A4 and SCL on A5, and tip the board further thanLEAN. - The matrix stays dark This project
DIN must go to pin 13, CLK to pin 7 and CS to pin 8, matching theLedControlline. - Left and right feel swapped This project
Flip the signs insteer, or turn the sensor around. - The matrix is blank after upload. LED matrix
On power up the MAX7219 is in shutdown mode with intensity 0. Wake it, set intensity, set scan limit to all 8 digits, and clear the display. - Dim, flickering, or wrong rows. LED matrix
Set the ISET resistor right for your LEDs (about 10 kohm for 40 mA). Power from 5 V with a 0.1 uF capacitor, and chain DOUT to the next DIN. - The sensor is not found or reads all zeros. Accelerometer and gyroscope
Check SDA and SCL (A4 and A5 on an Uno), power, and ground. The I2C address is 0x68, or 0x69 if AD0 is high. The chip starts in sleep mode, so write 0 to the power register PWR_MGMT_1. - The values look wrong or the angles drift. Accelerometer and gyroscope
Raw readings are counts, not g or degrees per second. Divide by the scale for your range (16384 per g at 2 g). Gyro angles drift, so blend with the accelerometer using a filter. - Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.