#include <Arduino.h>
#include <TinyWireM.h>
#include <USI_TWI_Master.h>
#include <Tiny4kOLED.h>
#ifndef SNK85
#define SNK85
#define WIDTH 128
#define HEIGHT 64
#define NUM_PAGES 8
#define NUM_SEGMENTS 13
#define I2C_ADDR 0x3C
#define DATABYTE 0x40
#define COMMANDBYTE 0x00
#define SET_CONTRAST_CONTROL 0x81
#define ENTIRE_DISPLAY_ON_RAM_CONTENT 0xA4
#define ENTIRE_DISPLAY_ON 0xA5
#define NORMAL_DISPLAY 0xA6
#define INVERT_DISPLAY 0xA7
#define DISPLAY_OFF 0xAE
#define DISPLAY_ON 0xAF
#define MEMORY_ADDRESS_MODE 0x20
#define COLUMN_ADDRESS 0x21
#define PAGE_ADDRESS 0x22
#define DISPLAY_START_LINE 0x40
#define CHARGEPUMP 0x8D
#define SEGMENT_REMAP 0xA0
#define MULTIPLEX_RATIO 0xA8
#define DEACTIVATE_SCROLL 0x2E
#define COM_SCAN_INCREASING 0xC0
#define COM_SCAN_DECREASING 0xC8
#define DISPLAY_OFFSET 0xD3
#define CLOCK_DIV 0xD5
#define PRE_CHARGE_PERIOD 0xD9
#define COM_PIN_HW_CONFIG 0xDA
#define V_COMH_DESELECT 0xDB
const uint8_t init_commands_list[] PROGMEM = {
DISPLAY_OFF,
CLOCK_DIV,
0x80,
MULTIPLEX_RATIO,
HEIGHT - 1,
DISPLAY_OFFSET,
0x00,
DISPLAY_START_LINE,
0x00,
CHARGEPUMP,
0x14,
MEMORY_ADDRESS_MODE,
0x00,
SEGMENT_REMAP,
0xA1,
COM_SCAN_DECREASING,
COM_PIN_HW_CONFIG,
0x12,
SET_CONTRAST_CONTROL,
0xCF,
PRE_CHARGE_PERIOD,
0xF1,
V_COMH_DESELECT,
0x40,
ENTIRE_DISPLAY_ON_RAM_CONTENT,
NORMAL_DISPLAY,
DEACTIVATE_SCROLL,
DISPLAY_ON
};
class OLED85
{
private:
void commandList(uint8_t len, const uint8_t init_commands_list []);
void setColAddr(uint8_t addr_start, uint8_t addr_stop);
void setPageAddr(uint8_t addr_start, uint8_t addr_stop);
void sendCommand(uint8_t command);
void sendData(uint8_t data);
public:
OLED85();
void fillScreen(uint8_t data);
void drawBlock(uint8_t x, uint8_t y, uint8_t offset_start, uint8_t offset_stop, uint8_t pattern);
void removeBlock(uint8_t x, uint8_t y);
void drawGrid();
void blinkScreen(uint8_t times);
void drawImage(const uint8_t img[], uint8_t blank_half = 0);
void displayScore(uint8_t score);
};
#endif //SNK85
OLED85::OLED85()
{
TinyWireM.begin();
commandList(sizeof(init_commands_list), init_commands_list);
fillScreen(0x00);
sendCommand(INVERT_DISPLAY);
}
/**
* send a list of commands to the display
* @param len length of the command list
* @param commands_list list of commands to send
*/
void OLED85::commandList(uint8_t len, const uint8_t commands_list [])
{
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(COMMANDBYTE);
uint8_t comm;
for (uint8_t i = 0; i < len; i++)
{
comm = pgm_read_byte(&commands_list[i]);
if (TinyWireM.write(comm) == 0)
{
TinyWireM.endTransmission();
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(COMMANDBYTE);
TinyWireM.write(comm);
}
}
TinyWireM.endTransmission();
}
/**
* send a single command to the display
* @param command commands to be sent to display
*/
void OLED85::sendCommand(uint8_t command)
{
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(COMMANDBYTE);
TinyWireM.write(command);
TinyWireM.endTransmission();
}
/**
* send a data byte to the display
* @param data data byte to write to the display's memory
*/
void OLED85::sendData(uint8_t data)
{
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(DATABYTE);
TinyWireM.write(data);
TinyWireM.endTransmission();
}
/**
* set column address to write to - in range < 0, 127 >
* column address gets incremented after every write operation from start_addr to stop_addr
* @param addr_start starting column
* @param addr_stop last column
*/
void OLED85::setColAddr(uint8_t addr_start, uint8_t addr_stop)
{
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(COMMANDBYTE);
TinyWireM.write(COLUMN_ADDRESS);
TinyWireM.write(addr_start);
TinyWireM.write(addr_stop);
TinyWireM.endTransmission();
}
/**
* set page address to write to - in range < 0, 7 >
* page address gets incremented after every write operation from start_addr to stop_addr
* @param addr_start starting page
* @param addr_stop last page
*/
void OLED85::setPageAddr(uint8_t addr_start, uint8_t addr_stop)
{
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(COMMANDBYTE);
TinyWireM.write(PAGE_ADDRESS);
TinyWireM.write(addr_start);
TinyWireM.write(addr_stop);
TinyWireM.endTransmission();
}
/**
* fill entire screen with the data byte specified
* @param data data byte to fill the screen with
*/
void OLED85::fillScreen(uint8_t data)
{
setPageAddr(0, NUM_PAGES - 1);
setColAddr(0, WIDTH - 1);
for (uint8_t i = 0; i < NUM_PAGES; i++) {
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(DATABYTE);
for (uint8_t j = 0; j < WIDTH ; j++)
{
if (TinyWireM.write(data) == 0)
{
TinyWireM.endTransmission();
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(DATABYTE);
TinyWireM.write(data);
}
}
TinyWireM.endTransmission();
}
}
/**
* draw a 8x8 block on the specified index - in range < 0, 15 >
* @param x horizontal index of the block to draw
* @param y vertical index of the block to draw
* @param offset_start horizontal offset from the start of the block
* @param offset_stop horizontal offset from the end of the block
* @param pattern data byte to fill the block with
*/
void OLED85::drawBlock(uint8_t x, uint8_t y, uint8_t offset_start, uint8_t offset_stop, uint8_t pattern)
{
x *= NUM_PAGES;
setPageAddr(y, y);
setColAddr(x + offset_start, x + 8 - offset_stop);
for (uint8_t j = 0; j < 8; j++)
{
sendData(pattern);
}
}
/**
* remove a 8x8 block from the specified index - in range < 0, 15 >
* @param x horizontal index of the block to draw
* @param y vertical index of the block to draw
*/
void OLED85::removeBlock(uint8_t x, uint8_t y)
{
x *= NUM_PAGES;
setPageAddr(y, y);
setColAddr(x, x + NUM_PAGES);
for (uint8_t i = 0; i < NUM_PAGES; i++)
{
sendData(0x00);
}
/* redraw the grid on the removed block (draw a black dot in the middle of the block) */
setColAddr(x + 4, x + 4);
sendData(0x10);
}
/**
* draw a dot grid across the entire screen
*/
void OLED85::drawGrid()
{
for (uint8_t i = 0; i < NUM_PAGES; i++){
for (uint8_t j = 0; j < WIDTH/ NUM_PAGES; j++) {
drawBlock(j, i, 4, 4, 0x10);
}
}
}
/**
* render a 128 x 64 px bitmap on the screen
* @param img bitmap data
* @param blank_half optional, used for creating the blinking effect of half of the screen
*/
void OLED85::drawImage(const uint8_t img[], uint8_t blank_half = 0)
{
uint32_t counter = 0;
setPageAddr(0, NUM_PAGES - 1);
setColAddr(0, WIDTH - 1);
for (uint8_t i = 0; i < NUM_PAGES; i++)
{
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(DATABYTE);
for (uint8_t j = 0; j < WIDTH ; j++)
{
uint8_t data = pgm_read_byte(&img[counter++]);
if (counter < 265 && blank_half) data = 0x00;
if (TinyWireM.write(data) == 0)
{
TinyWireM.endTransmission();
TinyWireM.beginTransmission(I2C_ADDR);
TinyWireM.write(DATABYTE);
TinyWireM.write(data);
}
}
TinyWireM.endTransmission();
}
}
/**
* display the user's score in a 7-segment format
* @param score the user's score to display
*/
#define LEFT_RIGHT A3
#define UP_DOWN A2
#define BUZZER 1
#define THRESHOLD1 512 // Pour 2,5V (environ la moitié de 1023)
#define THRESHOLD2 615 // Pour 3V (environ 3/5 de 1023)
uint8_t snakeLen;
uint8_t dot[2];
int nextMove[2];
uint8_t snake[100];
/* initialize the oled display */
OLED85 oled85;
void setup()
{
pinMode(LEFT_RIGHT, INPUT);
pinMode(UP_DOWN, INPUT);
pinMode(BUZZER, OUTPUT);
oled.begin();
oled.on();
}
/* emulates the arduino tone() function */
void tinyTune(uint8_t down, uint8_t up, uint8_t times){
for (int i = 0; i < times; i++){
digitalWrite(BUZZER, HIGH);
delay(up);
digitalWrite(BUZZER, LOW);
delay(down);
}
}
/* clean up for a new game */
void reset()
{
/* remove all blocks from the screen and redraw a fresh new grid */
oled85.fillScreen(0x00);
delay(50);
oled85.drawGrid();
snakeLen = 3; /* initial snake size is 3 */
nextMove[0] = 1; /* set og movement to left */
nextMove[1] = 0;
snake[0] = 1, snake[1] = 1, snake[2] = 1, snake[3] = 2, snake[4] = 1, snake[5] = 3; /* set initial snake position */
}
/* check if any of the buttons was pressed */
bool checkButtonStateChange()
{
if ((analogRead(LEFT_RIGHT) < THRESHOLD2) && (analogRead(UP_DOWN) < THRESHOLD2))
{
return true;
}
return false;
}
/* evaluate state of the current game*/
bool gameOver()
{
for (uint8_t i = 1; i < snakeLen; ++i)
{
/* check if you crossed yourself */
if ( (snake[2 * i] == snake[0] && snake[(2 * i) + 1] == snake[1] ) )
{
return true;
}
}
/* check for border positions (left || right || top || bottom) */
if ((snake[0] <= 0) || (snake[0] >= 15) || (snake[1] <= 0) || (snake[1] >= 7))
{
return true;
}
return false;
}
/* put the dot on a random place on the board */
void placeDot()
{
/* select a new random place for the dot */
srand(millis());
uint8_t x = (rand() % 14) + 1;
uint8_t y = (rand() % 6) + 1;
for (int i = 0; i < snakeLen; ++i)
{
/* check if the randomly selected dot is not a snake block */
if ((snake[2 * i] == x) && (snake[(2 * i) + 1] == y))
{
placeDot();
return;
}
}
dot[0] = x; dot[1] = y;
/* place the dot on the screen */
oled85.drawBlock(dot[0], dot[1], 1, 1, 0x7e);
}
/* snake movement animation */
void moveSnake()
{
/* temp variables to store the block to remove */
uint8_t x = snake[2 * snakeLen - 2], y = snake[2 * snakeLen - 1];
/* move everything one block ahead */
for (int i = snakeLen - 1; i > 0; i--)
{
snake[2 * i] = snake[2 * (i - 1)];
snake[(2 * i) + 1] = snake[(2 * (i - 1)) + 1];
}
/* move the head(first) block in the new direction */
snake[0] += nextMove[0];
snake[1] += nextMove[1];
delay(150);
/* make the movement visible on the board */
for (uint8_t i = 0; i < snakeLen; i++)
{
oled85.drawBlock(snake[2 * i], snake[(2 * i) + 1], 1, 1, 0x7e);
}
oled85.removeBlock(x, y);
}
/* read input from buttons and map it to a certain direction */
void changeNextMove()
{
int val1 = analogRead(LEFT_RIGHT);
int val2 = analogRead(UP_DOWN);
/* button 1 = left */
if (val1 > map(2500, 0, 5000, 0, 1023) && (nextMove[0] != -1))
{
nextMove[0] = 1;
nextMove[1] = 0;
delay(150);
}
/* button 2 = right */
else if (val2 > map(2500, 0, 5000, 0, 1023) && (nextMove[1] != -1))
{
nextMove[0] = 0;
nextMove[1] = 1;
delay(150);
}
/* button 3 = up */
else if (val1 > map(2500, 0, 5000, 0, 1023) && (nextMove[0] != 1))
{
nextMove[0] = -1;
nextMove[1] = 0;
delay(150);
}
/* button 4 = down */
else if (val2 > map(2500, 0, 5000, 0, 1023) && (nextMove[1] != 1))
{
nextMove[0] = 0;
nextMove[1] = -1;
delay(150);
}
}
/* flow of the game */
void loop()
{
uint8_t state = 0; /* initial state - used for half - display blinking animation*/
unsigned long before = millis();
delay(100);
/* display the loading screen and wait for user to press any button */
oled.setCursor(40,10);
oled.print("appuyer sur un bouton");
while (checkButtonStateChange())
{
/* blink the 'press any button' without delay */
unsigned long now = millis();
if (now - before >= 500)
{
state = !state;
before = now;
}
}
/* play game begin sound */
for (int i = 4; i >= 1; i--){
tinyTune(i, 1, 25);
}
/* reset the screen and begin the game */
reset();
delay(300);
placeDot();
/* keep repeating until gameover */
while (!gameOver())
{
changeNextMove();
moveSnake();
/* check if you catch the dot */
if ((snake[0] == dot[0]) && (snake[1] == dot[1]))
{
tinyTune(2, 2, 10);
snakeLen++;
placeDot();
}
changeNextMove();
delay(100);
}
/* blink the screen to indicate game over and play a sound on the buzzer */
delay(300);
for (int i = 5; i < 9; i++){
tinyTune(i, 1, 25); /* game over sound */
}
delay(100);
/* wait for a button click and then start a new game */
while (checkButtonStateChange())
;
}