Hardware components | ||||||
![]() |
| × | 1 | |||
| × | 1 | ||||
![]() |
| × | 2 | |||
![]() |
| × | 2 | |||
Software apps and online services | ||||||
![]() |
| |||||
Yet another TicTacToe!
This is a very simple game to code and to understand. I decided to build it in the easiest way, using just two push buttons.
I also added a Serial Monitor version if you are not interested to build anything (or you haven't an OLED display right now) but you want to take a look to the code and to learn how it works.
Since it's a tutorial, instead to write a more compact code, I decided to develop it as clear as possible. It's not elegant, maybe, but it's easy to understand, and the code is fully described.
The idea is to keep it as easy as possible in order to help beginners to make the first steps in the Arduino world. (note: the Serial Monitor code lacks some comments, but you can compare it with the OLED code, that is almost identical - except for the display management - and it's fully described).
OLED version troubleshooting: if your display refuses to light up (no life signs) download the Source Code for the display (see below in the Code Section, two files) and add both files to the sketch folder.
Note for the OLED version: I released here two different code and schematics: one that use two pulldown resistors, and one without resistors.
These two pulldown resistors are used to keep the pins #2 and #3 at a LOW state (this is why they're called pulldown resistors), connecting them to the ground, until the related button isn't pressed. When you press the button, the +5v flows directly to the pin, changing its state to HIGH (or "1"). The pulldown (or pullup) resistors are needed to keep the pin to a known state. If you don't use any pulldown or pullup resistor, the pin value can randomly vary, leading to unpredictable results.
But there's a simpler way to do that, without to use any resistor at all: using the Arduino internal pullup resistors - and changing the code. Arduino has its own built-in pullup resistors, so we can use them, simplifying the hardware.
The Arduino internal pullup resistors will keep the pins HIGH, until you "pull" (set) them LOW connecting to GND by the buttons. That means the buttons will be connected to GND instead of +5v
To use the internal pullup resistors, you must to declare the two buttons as INPUT_PULLUP instead of INPUT in the setup() function. You also need to slightly change the code, considering the pins as normally HIGH and check when they goes LOW - and also changing the button wiring, connected them to GND instead of +5V, as explained above.
Note: I could have built the "resistors version" that way too, using the external resistors as pullup resistors - connecting them to +5V - and connect the buttons to GND. That way, the resistors simply substituted the internal pullup resistors. I didn't do that deliberately: since it's a tutorial, I wished to show how to use pulldown resistors too.
-----------------------------
About the A.I. that plays for the CPU
The CPU A.I. is actually really simple. It's based on this three easy steps:
Step 1: can the CPU win at the next move? YES, place the winning move, NO, go to step 2.
Step 2: can the player win at his/her next move? YES, prevent it. NO, go to step 3.
Step 3: Nobody can win at the next move; let's put an "X" in a random, free cell.
It's quite a simple and apparently stupid way to play, but you will be surprised how much good is the CPU. Actually you can only beat it using the strategy, I mean, trying to create two winning moves at the same time.
This is a little video of the OLED version (with resistors). Sorry for the flickering (it's just a camera issue) and the two colors display (I haven't right now a single color display to use for this demo).
- If you want to use the OLED display version, you need to install the Adafruit_GFX and Adafruit_SSD1306 libraries. To do that, open the Arduino IDE, enter the menu Tools -> Manage Libraries, and search and install the above libraries. Then open the sketch and upload it.
- If you want to use the Serial Monitor version, just upload the code into the Arduino Board and then open the Serial Monitor from Tools>SerialMonitor. Set the speed to 9600 (if you see garbage from inside the monitor, it means there's a speed issue). It should appear the grid. Use the 1...9 digits and press enter to make your move.
Schematics for the OLED version

/**************************************************************************
*
* TicTacToe - Oled
*
* It requires an Arduino Nano, Uno, Mini Pro
*
* Hardware connections:
* DISPLAY - Arduino Nano/Uno
* GND - GND
* VDD - 3.3V depending on your model
* SCL - A5
* SDA - A4
*
* Arduino pin 2 <-- button MOVE <--+----- +3.3V or +5V
* Arduino pin 3 <-- button OK <--+
*
* Note: connect two 10...100 kohm pulldown resistors: pin 2 -> GND; pin 3 -> GND
*
* Install the Adafruit SSD1306 libraries
* by Arduino IDE, menu Tools -> Manage Libraries
*
* apr/2022, Giovanni Verrua
**************************************************************************/
//including the needed libraries for the OLED display
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define BUTTON_MOVE 2
#define BUTTON_OK 3
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET);
int gameStatus = 0;
int whosplaying = 0; //0 = Arduino, 1 = Human
int winner = -1; //-1 = Playing, 0 = Draw, 1 = Human, 2 = CPU
int board[]={0,0,0,
0,0,0,
0,0,0}; //0 = blank, 1 = human (circle), 2 = computer (cross)
//--------------------------------------------------------------------------------------------------------
void playhuman() {
int humanMove = 0;
bool stayInLoop = true;
bool showDot = false;
long timerPos = millis()-1000;
while (stayInLoop) { //stay in loop until the player makes his/her choice hitting the OK button.
//If the current "?" position isn't avaliable (the cell value is 1 or 2), this loop will
//move the "?" to the next free cell.
//NOTE: there must be at least one empty cell (or it will never exit from this loop [deadlock]).
//This is granted, because if all the cells are used, there's a winner or it's a draft.
//(the calling function [loop function] check it before to continue).
while (board[humanMove] != 0) { //looking for an empty cell.
humanMove ++;
if (humanMove >8) humanMove = 0;
}
//--------------------------------------------------\-
//this makes the flashing "?" possible. Every 200 milliseconds the IF condition becomes true and it will toogle the
//showDot variable between True and False (and reset the timerPos value at the current millis() value.
if (timerPos + 200 < millis()) {
timerPos = millis();
showDot = !showDot;
playhuman_showpos( humanMove, showDot); //calling the function that will draw (or delete) the "?"
}
//--------------------------------------------------/-
if (digitalRead(BUTTON_MOVE)==HIGH) { //the player hit the MOVE button.
playhuman_showpos( humanMove, false); //delete the marker
humanMove ++; //move the "?" to the next cell.
while (digitalRead(BUTTON_MOVE)==HIGH); //debounce
bool showDot = false; //this two lines make sure the "?" is displayed
long timerPos =-1000; //at the first round.
}
if (digitalRead(BUTTON_OK )==HIGH) stayInLoop = false; //the player hit the OK button and made his/her choice.
delay(100); //required for a correct display.
}
board[humanMove] = 1; //let's assign the chosen cell to the player.
}
//------------------------------------------------------------------
void playhuman_showpos(int humanMove, bool showDot) { //this function draw a flashing "?" (white = draw, black = delete)
display.setTextSize(2);
if (humanMove == 0) display.setCursor( 5, 5);
else if (humanMove == 1) display.setCursor(25, 5);
else if (humanMove == 2) display.setCursor(45, 5);
else if (humanMove == 3) display.setCursor( 5,25);
else if (humanMove == 4) display.setCursor(25,25);
else if (humanMove == 5) display.setCursor(45,25);
else if (humanMove == 6) display.setCursor( 5,45);
else if (humanMove == 7) display.setCursor(25,45);
else if (humanMove == 8) display.setCursor(45,45);
//if (showDot) {display.setTextColor(WHITE);display.print("?");} else {display.setTextColor(BLACK);display.print("?");}
if (showDot) display.setTextColor(WHITE); else display.setTextColor(BLACK);
display.print("?");
display.display();
}
//--------------------------------------------------------------------------------------------------------
void playcpu() {
//NOTE: The player has almost no chance to win, since the cpu will check every possible move.
// Actually the only way to beat the cpu is to have two winning move at the same time.
//The CPU has no real strategy, actually; it just prevents the player to win and put an "X" if it has
//a possible winning move. If no winning move are possible, it just put an "X" into a random place.
//It could seems a stupid AI, however you will see the CPU will play rather well and it will be
//hard to beat it.
int cpumove = checkboard(2); //2 = cpu let's check if there's a cpu's winner move
if (cpumove >=0) {
board[cpumove] = 2; //cpu's winner move
}
else {
cpumove = checkboard(1); //1=player check if the player has a chance to win (2 circles and an empty cell in a row)
if (cpumove >=0) {
board[cpumove] = 2; //this move will break the player's winner move
}
//there's no possible winner move neither for the cpu, nor for the human;: the CPU will put an "X" in a random cell
while (cpumove < 0) { //looking for a random, empty cell.
int randomMove = random(10);
if (randomMove >=0 && randomMove <=8 && board[randomMove] == 0) {
cpumove = randomMove;
}
}
board[cpumove] = 2; //let's assign the empty cell to the CPU
}
}
//--------------------------------------------------------------------------------------------------------
int checkboard(int x){ //x = 1 -> player, x = 2 -> cpu
//this function checks if the next move can be the winning move and return the cell that will
//win the game. It's used by the CPU to decide if it can win or if the player is going to win
//(and placing an "X" to prevent this chance).
//if no move wins the game, it returns -1
//the board[] index is 0 1 2
// 3 4 5
// 6 7 8
if (board[0]==0 && board[1]==x && board[2]==x) return 0; // 0 1 1
// . . .
// . . .
else if (board[0]==x && board[1]==0 && board[2]==x) return 1; // 1 0 1
// . . .
// . . .
else if (board[0]==x && board[1]==x && board[2]==0) return 2; // 1 1 0
// . . .
// . . .
//-------------------------------------------------
else if (board[3]==0 && board[4]==x && board[5]==x) return 3; // . . .
// 0 1 1
// . . .
else if (board[3]==x && board[4]==0 && board[5]==x) return 4; // . . .
// 1 0 1
// . . .
else if (board[3]==x && board[4]==x && board[5]==0) return 5; // . . .
// 1 1 0
// . . .
//-------------------------------------------------
else if (board[6]==0 && board[7]==x && board[8]==x) return 6; // . . .
// . . .
// 0 1 1
else if (board[6]==x && board[7]==0 && board[8]==x) return 7; // . . .
// . . .
// 1 0 1
else if (board[6]==x && board[7]==x && board[8]==0) return 8; // . . .
// . . .
// 1 1 0
//-------------------------------------------------
else if (board[0]==0 && board[3]==x && board[6]==x) return 0; // 0 . .
// 1 . .
// 1 . .
else if (board[0]==x && board[3]==0 && board[6]==x) return 3; // 1 . .
// 0 . .
// 1 . .
else if (board[0]==x && board[3]==x && board[6]==0) return 6; // 1 . .
// 1 . .
// 0 . .
//-------------------------------------------------
else if (board[1]==0 && board[4]==x && board[7]==x) return 1; // . 0 .
// . 1 .
// . 1 .
else if (board[1]==x && board[4]==0 && board[7]==x) return 4; // . 1 .
// . 0 .
// . 1 .
else if (board[1]==x && board[4]==x && board[7]==0) return 7; // . 1 .
// . 1 .
// . 0 .
//-------------------------------------------------
else if (board[2]==0 && board[5]==x && board[8]==x) return 2; // . . 0
// . . 1
// . . 1
else if (board[2]==x && board[5]==0 && board[8]==x) return 5; // . . 1
// . . 0
// . . 1
else if (board[2]==x && board[5]==x && board[8]==0) return 8; // . . 1
// . . 1
// . . 0
//-------------------------------------------------
else if (board[0]==0 && board[4]==x && board[8]==x) return 0; // 0 . .
// . 1 .
// . . 1
else if (board[0]==x && board[4]==0 && board[8]==x) return 4; // 1 . .
// . 0 .
// . . 1
else if (board[0]==x && board[4]==x && board[8]==0) return 8; // 1 . .
// . 1 .
// . . 0
//-------------------------------------------------
else if (board[2]==0 && board[4]==x && board[6]==x) return 2; // . . 0
// . 1 .
// 1 . .
else if (board[2]==x && board[4]==0 && board[6]==x) return 4; // . . 1
// . 0 .
// 1 . .
else if (board[2]==x && board[4]==x && board[6]==0) return 6; // . . 1
// . 1 .
// 0 . .
else return -1;
}
//--------------------------------------------------------------------------------------------
void checkWinner() { //check the board to see if there is a winner
winner = 3; //3=draft, 1= winner->player, 2=winner->cpu
// circles win?
if (board[0]==1 && board[1]==1 && board[2]==1) winner=1;
else if (board[3]==1 && board[4]==1 && board[5]==1) winner=1;
else if (board[6]==1 && board[7]==1 && board[8]==1) winner=1;
else if (board[0]==1 && board[3]==1 && board[6]==1) winner=1;
else if (board[1]==1 && board[4]==1 && board[7]==1) winner=1;
else if (board[2]==1 && board[5]==1 && board[8]==1) winner=1;
else if (board[0]==1 && board[4]==1 && board[8]==1) winner=1;
else if (board[2]==1 && board[4]==1 && board[6]==1) winner=1;
// crosses win?
else if (board[0]==2 && board[1]==2 && board[2]==2) winner=2;
else if (board[3]==2 && board[4]==2 && board[5]==2) winner=2;
else if (board[6]==2 && board[7]==2 && board[8]==2) winner=2;
else if (board[0]==2 && board[3]==2 && board[6]==2) winner=2;
else if (board[1]==2 && board[4]==2 && board[7]==2) winner=2;
else if (board[2]==2 && board[5]==2 && board[8]==2) winner=2;
else if (board[0]==2 && board[4]==2 && board[8]==2) winner=2;
else if (board[2]==2 && board[4]==2 && board[6]==2) winner=2;
if (winner == 3) {
for(int i=0;i<9;i++) if (board[i]==0) winner=0; //there are some empty cells yet.
}
}
//--------------------------------------------------------------------------------------------------------------
void resetGame() {
for(int i=0;i<9;i++) board[i]=0; //Resetting the board. 0 = empty cell, 1 = player circle, 2 = CPU cross
winner = 0;
gameStatus = 0;
}
//--------------------------------------------------------------------------------------------------------------
void boardDrawing() {
display.clearDisplay();
display.setTextColor(WHITE);
display.drawFastHLine(0, 21, 64, WHITE); //horizontal lines
display.drawFastHLine(0, 42, 64, WHITE);
display.drawFastVLine(21, 0, 64, WHITE); //vertical lines
display.drawFastVLine(42, 0, 64, WHITE);
//drawing the content of the nine cells: " ", "o", "x"
display.setTextSize(2);
display.setCursor( 5, 5); display.print(charBoard(0)); display.setCursor(25, 5); display.print(charBoard(1)); display.setCursor(45, 5); display.print(charBoard(2));
display.setCursor( 5,25); display.print(charBoard(3)); display.setCursor(25,25); display.print(charBoard(4)); display.setCursor(45,25); display.print(charBoard(5));
display.setCursor( 5,45); display.print(charBoard(6)); display.setCursor(25,45); display.print(charBoard(7)); display.setCursor(45,45); display.print(charBoard(8));
display.display();
delay(200); //DON'T REMOVE!!!! needed for correct refresh and further flashing "?" when it's the player turn!!!
}
//--------------------------------------------------------------------------------------------------------------
String charBoard(int x) {
if (board[x] == 0) return " ";
if (board[x] == 1) return "o";
if (board[x] == 2) return "x";
return "?"; //error trap; but it's impossible it can return an "?" because the board[] array is all initialized = 0
}
//--------------------------------------------------------------------------------------------------------------
void setup() { //function executed once, at every boot or reset.
randomSeed(analogRead(0)); //resetting the random function behavior.
pinMode(BUTTON_MOVE,INPUT); //Declaring the pin #2 as input (connected to the button move)
pinMode(BUTTON_OK ,INPUT); //Declaring the pin #3 as input (connected to the button ok)
//display.begin(); //if the display doesn't work with the below instruction,
//display.begin(SSD1306_SWITCHCAPVCC, 0x3D); //try one of these two ones.
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
delay(500); //needed for display correct initializing
display.clearDisplay(); //clearing the display
display.setTextColor(WHITE); //setting the display color
display.display(); //executing the above instructions. The SSD1306 dislay will not execute any command until to use the ::display() command.
whosplaying = 2; //deciding who's the first player. Set = 2 to force it entering in the following while loop.
while ( whosplaying <0 || whosplaying > 1) whosplaying = random(2); //it will stay in the loop until whosplaying isn't = 0 or = 1. Probably there's no
//need for a loop, since random(2) should return 0 or 1, but I'm an old programmer,
//I've seen many strange things in my programmer life and I like to be sure ;-)
}
//--------------------------------------------------------------------------------------------------------------
void loop() { //main loop. Endlessly executed by Arduino.
if (gameStatus == 0){ //this is where I always put a menu in the Arduino games I wrote. We don't need a menu here, so it's just a reset step.
resetGame();
boardDrawing(); //drawing an empty board
gameStatus = 1; //starting the game (see below)
winner = 0; //no winner for now (winner = 1: player, winner = 2: cpu).
}
//---------------------------------------------
if (gameStatus == 1){ //starting the game
while (winner == 0) { //game main loop: loop until no one wins the match.
display.setTextSize(2);
if (whosplaying == 0) { //whosplaying = 0: cpu turn
display.setCursor( 72,25); display.print("CPU");
display.display();
delay(1000);
playcpu(); //in this function the CPU play its move.
whosplaying =1; //changing the turn.
}
else {
display.setCursor( 72,25); display.print("You");
display.display();
playhuman(); //in this function the player makes his/her move.
whosplaying =0; //changing the turn.
}
boardDrawing(); //refreshing the board with all the moves already done.
delay(500);
checkWinner(); //this will check if there's a winner and assign the winner variable
if (winner > 0) {
if (winner == 3) {
display.setTextSize(2); display.setCursor( 68, 25);
display.print("Draft");
}
else {
//showing who's the winner
display.setTextSize(2); display.setCursor( 72, 25);
if (winner == 1) { Serial.println(F("You")); display.print("You"); display.setCursor( 72, 45); display.print("win"); }
else { Serial.println(F("CPU")); display.print("CPU"); display.setCursor( 72, 45); display.print("wins");}
}
display.display();
delay(1000);
//debounce loop. It will not proceed until both buttons are unpressed.
while (digitalRead(BUTTON_MOVE)==LOW && digitalRead(BUTTON_OK )==LOW);
}
//display.display();
}
//swap the first move for the next match between CPU and human (one per match)
if (whosplaying == 0) whosplaying =1; else whosplaying =0;
gameStatus = 0; //entering the reset step
delay(1000); //just wait a second
}
}
TicTacToe - Serial Monitor version
Arduino/**************************************************************************
*
* TicTacToe - Serial monitor version.
* Note: the OLED version code is better described; if you have any
* doubt about any instruction, please take a look to that version.
*
* It only requires an Arduino (tested with Nano and Uno, but it
* should work with (almost?) any Arduino and other clones
*
* To use it, you need to connect the Arduino to USB and call the
* Serial monitor from inside Arduino IDE: Menu tools -> serial monitor
* (or pressing Ctrl + Shift + M)
*
* Move using the 1...9 keys (plus Enter) from inside the Serial Monitor:
*
* 1 | 2 | 3
* ---+---+---
* 4 | 5 | 6
* ---+---+---
* 7 | 8 | 9
*
* Giovanni Verrua, april 2022
**************************************************************************/
int gameStatus = 0;
int whosplaying = 0; //0 = Arduino, 1 = Human
int winner = -1; //-1 = Playing, 0 = Draw, 1 = Human, 2 = CPU
int board[]={0,0,0,
0,0,0,
0,0,0}; //0 = blank, 1 = human (circle), 2 = computer (cross)
//--------------------------------------------------------------------------------------------------------
void playhuman() {
int humanMove = -1;
bool stayInLoop = true;
while (stayInLoop) {
Serial.println(F("\nMake your move\n"));
while (Serial.available() && Serial.read()); // empty buffer
while (!Serial.available()); // wait for data
if (Serial.available()) { //data available.
int humanMove = Serial.read() - 48; //serial.read() return the ascii value of the pressed key. The "1" ascii value is 49.
Serial.println(humanMove);
if (humanMove >=0 && humanMove <=9) {
if (board[humanMove-1] !=0) {
Serial.println(F("\nError - Cell already in use"));
stayInLoop = true;
}
else {
stayInLoop = false;
board[humanMove-1] = 1; //remember: the player uses the 1..9 keys, but the array index starts by 0 (9 cells = 0..8)
}
}
else {
Serial.println(F("\ntype error (just 1 to 9)"));
stayInLoop = true;
}
}
}
}
//--------------------------------------------------------------------------------------------------------
void playcpu() {
int cpumove = checkboard(2); //2 = cpu let's check if there's a cpu's winner move
if (cpumove >=0) {
board[cpumove] = 2; //cpu's winner move
}
else {
cpumove = checkboard(1); //1=player check if the player has a chance to win (2 circles and an empty cell in a row)
if (cpumove >=0) {
board[cpumove] = 2; //this move will break the player's winner move
}
//there's no possible winner move neither for the cpu, nor for the human, I will put an "X" in a random cell
while (cpumove < 0) {
int randomMove = random(10);
if (randomMove >=0 && randomMove <=8 && board[randomMove] == 0) {
cpumove = randomMove;
}
}
board[cpumove] = 2;
}
}
//--------------------------------------------------------------------------------------------------------
int checkboard(int x){ //x = 1 -> player, x = 2 -> cpu
//full case
if (board[0]==0 && board[1]==x && board[2]==x) return 0; // 0 1 1
// . . .
// . . .
else if (board[0]==x && board[1]==0 && board[2]==x) return 1; // 1 0 1
// . . .
// . . .
else if (board[0]==x && board[1]==x && board[2]==0) return 2; // 1 1 0
// . . .
// . . .
//-------------------------------------------------
else if (board[3]==0 && board[4]==x && board[5]==x) return 3; // . . .
// 0 1 1
// . . .
else if (board[3]==x && board[4]==0 && board[5]==x) return 4; // . . .
// 1 0 1
// . . .
else if (board[3]==x && board[4]==x && board[5]==0) return 5; // . . .
// 1 1 0
// . . .
//-------------------------------------------------
else if (board[6]==0 && board[7]==x && board[8]==x) return 6; // . . .
// . . .
// 0 1 1
else if (board[6]==x && board[7]==0 && board[8]==x) return 7; // . . .
// . . .
// 1 0 1
else if (board[6]==x && board[7]==x && board[8]==0) return 8; // . . .
// . . .
// 1 1 0
//-------------------------------------------------
else if (board[0]==0 && board[3]==x && board[6]==x) return 0; // 0 . .
// 1 . .
// 1 . .
else if (board[0]==x && board[3]==0 && board[6]==x) return 3; // 1 . .
// 0 . .
// 1 . .
else if (board[0]==x && board[3]==x && board[6]==0) return 6; // 1 . .
// 1 . .
// 0 . .
//-------------------------------------------------
else if (board[1]==0 && board[4]==x && board[7]==x) return 1; // . 0 .
// . 1 .
// . 1 .
else if (board[1]==x && board[4]==0 && board[7]==x) return 4; // . 1 .
// . 0 .
// . 1 .
else if (board[1]==x && board[4]==x && board[7]==0) return 7; // . 1 .
// . 1 .
// . 0 .
//-------------------------------------------------
else if (board[2]==0 && board[5]==x && board[8]==x) return 2; // . . 0
// . . 1
// . . 1
else if (board[2]==x && board[5]==0 && board[8]==x) return 5; // . . 1
// . . 0
// . . 1
else if (board[2]==x && board[5]==x && board[8]==0) return 8; // . . 1
// . . 1
// . . 0
//-------------------------------------------------
else if (board[0]==0 && board[4]==x && board[8]==x) return 0; // 0 . .
// . 1 .
// . . 1
else if (board[0]==x && board[4]==0 && board[8]==x) return 4; // 1 . .
// . 0 .
// . . 1
else if (board[0]==x && board[4]==x && board[8]==0) return 8; // 1 . .
// . 1 .
// . . 0
//-------------------------------------------------
else if (board[2]==0 && board[4]==x && board[6]==x) return 2; // . . 0
// . 1 .
// 1 . .
else if (board[2]==x && board[4]==0 && board[6]==x) return 4; // . . 1
// . 0 .
// 1 . .
else if (board[2]==x && board[4]==x && board[6]==0) return 6; // . . 1
// . 1 .
// 0 . .
else return -1;
}
//--------------------------------------------------------------------------------------------
void checkWinner() { //check the board to see if there is a winner
winner = 3; //3=draft, 1= winner->player, 2=winner->cpu
// circles win?
if (board[0]==1 && board[1]==1 && board[2]==1) winner=1;
else if (board[3]==1 && board[4]==1 && board[5]==1) winner=1;
else if (board[6]==1 && board[7]==1 && board[8]==1) winner=1;
else if (board[0]==1 && board[3]==1 && board[6]==1) winner=1;
else if (board[1]==1 && board[4]==1 && board[7]==1) winner=1;
else if (board[2]==1 && board[5]==1 && board[8]==1) winner=1;
else if (board[0]==1 && board[4]==1 && board[8]==1) winner=1;
else if (board[2]==1 && board[4]==1 && board[6]==1) winner=1;
// crosses win?
else if (board[0]==2 && board[1]==2 && board[2]==2) winner=2;
else if (board[3]==2 && board[4]==2 && board[5]==2) winner=2;
else if (board[6]==2 && board[7]==2 && board[8]==2) winner=2;
else if (board[0]==2 && board[3]==2 && board[6]==2) winner=2;
else if (board[1]==2 && board[4]==2 && board[7]==2) winner=2;
else if (board[2]==2 && board[5]==2 && board[8]==2) winner=2;
else if (board[0]==2 && board[4]==2 && board[8]==2) winner=2;
else if (board[2]==2 && board[4]==2 && board[6]==2) winner=2;
if (winner == 3) {
for(int i=0;i<9;i++) if (board[i]==0) winner=0; //there are some empty cell yet.
}
}
//--------------------------------------------------------------------------------------------------------------
void resetGame() {
Serial.println("Resetting game...");
for(int i=0;i<9;i++) board[i]=0;
winner = 0;
gameStatus = 0;
}
//--------------------------------------------------------------------------------------------------------------
void boardDrawing() {
Serial.println("");
Serial.print(F(" ")); Serial.print(charBoard(0)); Serial.print(F(" | ")); Serial.print(charBoard(1)); Serial.print(F(" | ")); Serial.println(charBoard(2));
Serial.println(F(" ---+---+---"));
Serial.print(F(" ")); Serial.print(charBoard(3)); Serial.print(F(" | ")); Serial.print(charBoard(4)); Serial.print(F(" | ")); Serial.println(charBoard(5));
Serial.println(F(" ---+---+---"));
Serial.print(F(" ")); Serial.print(charBoard(6)); Serial.print(F(" | ")); Serial.print(charBoard(7)); Serial.print(F(" | ")); Serial.println(charBoard(8));
Serial.println("");
}
//--------------------------------------------------------------------------------------------------------------
String charBoard(int x) {
if (board[x] == 0) return " ";
if (board[x] == 1) return "o";
if (board[x] == 2) return "x";
return "?"; //error trap; it shouldn't pass here.
}
//--------------------------------------------------------------------------------------------------------------
void setup() {
Serial.begin(9600);
randomSeed(analogRead(0)); //resetting the random function.
}
//--------------------------------------------------------------------------------------------------------------
void loop() {
if (gameStatus == 0){
Serial.println(F("Let's begin..."));
whosplaying = 2;
while ( whosplaying <0 || whosplaying > 1) {
whosplaying = random(2);
}
gameStatus = 1;
winner = 0;
}
//---------------------------------------------
if (gameStatus == 1){ //start
if (whosplaying == 1) boardDrawing();
while (winner == 0) { //game main loop
if (whosplaying == 0) {
Serial.println("CPU turn:");
playcpu();
whosplaying =1;
}
else {
Serial.println("Player turn:");
playhuman();
whosplaying =0;
}
boardDrawing();
checkWinner(); //this will assign the winner variable
if (winner > 0) {
Serial.println("");
if (winner == 3) Serial.print(F("Draft!"));
else {
Serial.print(F("The winner is "));
if (winner == 1) Serial.println(F("the human")); else Serial.println(F("the CPU"));
}
}
}
resetGame();
Serial.println("");
delay(2000);
Serial.println("");
}
}
/**************************************************************************
*
* TicTacToe - Oled - No pulldown resistors version
*
* It requires an Arduino Nano, Uno, Mini Pro, ...
*
* Hardware connections:
* DISPLAY - Arduino Nano/Uno
* GND - GND
* VDD - 3.3V depending on your model
* SCL - A5
* SDA - A4
*
* Arduino pin 2 <-- button MOVE <--+----- GND
* Arduino pin 3 <-- button OK <--+
*
* Note: No need to use resistors, we use the internal pullup resistors
*
* Install the Adafruit SSD1306 libraries
* by Arduino IDE, menu Tools -> Manage Libraries
*
* apr/2022, Giovanni Verrua
**************************************************************************/
//including the needed libraries for the OLED display
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define BUTTON_MOVE 2
#define BUTTON_OK 3
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET);
int gameStatus = 0;
int whosplaying = 0; //0 = Arduino, 1 = Human
int winner = -1; //-1 = Playing, 0 = Draw, 1 = Human, 2 = CPU
int board[]={0,0,0,
0,0,0,
0,0,0}; //0 = blank, 1 = human (circle), 2 = computer (cross)
//--------------------------------------------------------------------------------------------------------
void playhuman() {
int humanMove = 0;
bool stayInLoop = true;
bool showDot = false;
long timerPos = millis()-1000;
while (stayInLoop) { //stay in loop until the player makes his/her choice hitting the OK button.
//If the current "?" position isn't avaliable (the cell value is 1 or 2), this loop will
//move the "?" to the next free cell.
//NOTE: there must be at least one empty cell (or it will never exit from this loop [deadlock]).
//This is granted, because if all the cells are used, there's a winner or it's a draft.
//(the calling function [loop function] check it before to continue).
while (board[humanMove] != 0) { //looking for an empty cell.
humanMove ++;
if (humanMove >8) humanMove = 0;
}
//--------------------------------------------------\-
//this makes the flashing "?" possible. Every 200 milliseconds the IF condition becomes true and it will toogle the
//showDot variable between True and False (and reset the timerPos value at the current millis() value.
if (timerPos + 200 < millis()) {
timerPos = millis();
showDot = !showDot;
playhuman_showpos( humanMove, showDot); //calling the function that will draw (or delete) the "?"
}
//--------------------------------------------------/-
if (digitalRead(BUTTON_MOVE)==LOW) { //the player hit the MOVE button.
playhuman_showpos( humanMove, false); //delete the marker
humanMove ++; //move the "?" to the next cell.
while (digitalRead(BUTTON_MOVE)==LOW); //debounce
bool showDot = false; //this two lines make sure the "?" is displayed
long timerPos =-1000; //at the first round.
}
if (digitalRead(BUTTON_OK )==LOW) stayInLoop = false; //the player hit the OK button and made his/her choice.
delay(100); //required for a correct display.
}
board[humanMove] = 1; //let's assign the chosen cell to the player.
}
//------------------------------------------------------------------
void playhuman_showpos(int humanMove, bool showDot) { //this function draw a flashing "?" (white = draw, black = delete)
display.setTextSize(2);
if (humanMove == 0) display.setCursor( 5, 5);
else if (humanMove == 1) display.setCursor(25, 5);
else if (humanMove == 2) display.setCursor(45, 5);
else if (humanMove == 3) display.setCursor( 5,25);
else if (humanMove == 4) display.setCursor(25,25);
else if (humanMove == 5) display.setCursor(45,25);
else if (humanMove == 6) display.setCursor( 5,45);
else if (humanMove == 7) display.setCursor(25,45);
else if (humanMove == 8) display.setCursor(45,45);
//if (showDot) {display.setTextColor(WHITE);display.print("?");} else {display.setTextColor(BLACK);display.print("?");}
if (showDot) display.setTextColor(WHITE); else display.setTextColor(BLACK);
display.print("?");
display.display();
}
//--------------------------------------------------------------------------------------------------------
void playcpu() {
//NOTE: The player has almost no chance to win, since the cpu will check every possible move.
// Actually the only way to beat the cpu is to have two winning move at the same time.
//The CPU has no real strategy, actually; it just prevents the player to win and put an "X" if it has
//a possible winning move. If no winning move are possible, it just put an "X" into a random place.
//It could seems a stupid AI, however you will see the CPU will play rather well and it will be
//hard to beat it.
int cpumove = checkboard(2); //2 = cpu let's check if there's a cpu's winner move
if (cpumove >=0) {
board[cpumove] = 2; //cpu's winner move
}
else {
cpumove = checkboard(1); //1=player check if the player has a chance to win (2 circles and an empty cell in a row)
if (cpumove >=0) {
board[cpumove] = 2; //this move will break the player's winner move
}
//there's no possible winner move neither for the cpu, nor for the human;: the CPU will put an "X" in a random cell
while (cpumove < 0) { //looking for a random, empty cell.
int randomMove = random(10);
if (randomMove >=0 && randomMove <=8 && board[randomMove] == 0) {
cpumove = randomMove;
}
}
board[cpumove] = 2; //let's assign the empty cell to the CPU
}
}
//--------------------------------------------------------------------------------------------------------
int checkboard(int x){ //x = 1 -> player, x = 2 -> cpu
//this function checks if the next move can be the winning move and return the cell that will
//win the game. It's used by the CPU to decide if it can win or if the player is going to win
//(and placing an "X" to prevent this chance).
//if no move wins the game, it returns -1
//the board[] index is 0 1 2
// 3 4 5
// 6 7 8
if (board[0]==0 && board[1]==x && board[2]==x) return 0; // 0 1 1
// . . .
// . . .
else if (board[0]==x && board[1]==0 && board[2]==x) return 1; // 1 0 1
// . . .
// . . .
else if (board[0]==x && board[1]==x && board[2]==0) return 2; // 1 1 0
// . . .
// . . .
//-------------------------------------------------
else if (board[3]==0 && board[4]==x && board[5]==x) return 3; // . . .
// 0 1 1
// . . .
else if (board[3]==x && board[4]==0 && board[5]==x) return 4; // . . .
// 1 0 1
// . . .
else if (board[3]==x && board[4]==x && board[5]==0) return 5; // . . .
// 1 1 0
// . . .
//-------------------------------------------------
else if (board[6]==0 && board[7]==x && board[8]==x) return 6; // . . .
// . . .
// 0 1 1
else if (board[6]==x && board[7]==0 && board[8]==x) return 7; // . . .
// . . .
// 1 0 1
else if (board[6]==x && board[7]==x && board[8]==0) return 8; // . . .
// . . .
// 1 1 0
//-------------------------------------------------
else if (board[0]==0 && board[3]==x && board[6]==x) return 0; // 0 . .
// 1 . .
// 1 . .
else if (board[0]==x && board[3]==0 && board[6]==x) return 3; // 1 . .
// 0 . .
// 1 . .
else if (board[0]==x && board[3]==x && board[6]==0) return 6; // 1 . .
// 1 . .
// 0 . .
//-------------------------------------------------
else if (board[1]==0 && board[4]==x && board[7]==x) return 1; // . 0 .
// . 1 .
// . 1 .
else if (board[1]==x && board[4]==0 && board[7]==x) return 4; // . 1 .
// . 0 .
// . 1 .
else if (board[1]==x && board[4]==x && board[7]==0) return 7; // . 1 .
// . 1 .
// . 0 .
//-------------------------------------------------
else if (board[2]==0 && board[5]==x && board[8]==x) return 2; // . . 0
// . . 1
// . . 1
else if (board[2]==x && board[5]==0 && board[8]==x) return 5; // . . 1
// . . 0
// . . 1
else if (board[2]==x && board[5]==x && board[8]==0) return 8; // . . 1
// . . 1
// . . 0
//-------------------------------------------------
else if (board[0]==0 && board[4]==x && board[8]==x) return 0; // 0 . .
// . 1 .
// . . 1
else if (board[0]==x && board[4]==0 && board[8]==x) return 4; // 1 . .
// . 0 .
// . . 1
else if (board[0]==x && board[4]==x && board[8]==0) return 8; // 1 . .
// . 1 .
// . . 0
//-------------------------------------------------
else if (board[2]==0 && board[4]==x && board[6]==x) return 2; // . . 0
// . 1 .
// 1 . .
else if (board[2]==x && board[4]==0 && board[6]==x) return 4; // . . 1
// . 0 .
// 1 . .
else if (board[2]==x && board[4]==x && board[6]==0) return 6; // . . 1
// . 1 .
// 0 . .
else return -1;
}
//--------------------------------------------------------------------------------------------
void checkWinner() { //check the board to see if there is a winner
winner = 3; //3=draft, 1= winner->player, 2=winner->cpu
// circles win?
if (board[0]==1 && board[1]==1 && board[2]==1) winner=1;
else if (board[3]==1 && board[4]==1 && board[5]==1) winner=1;
else if (board[6]==1 && board[7]==1 && board[8]==1) winner=1;
else if (board[0]==1 && board[3]==1 && board[6]==1) winner=1;
else if (board[1]==1 && board[4]==1 && board[7]==1) winner=1;
else if (board[2]==1 && board[5]==1 && board[8]==1) winner=1;
else if (board[0]==1 && board[4]==1 && board[8]==1) winner=1;
else if (board[2]==1 && board[4]==1 && board[6]==1) winner=1;
// crosses win?
else if (board[0]==2 && board[1]==2 && board[2]==2) winner=2;
else if (board[3]==2 && board[4]==2 && board[5]==2) winner=2;
else if (board[6]==2 && board[7]==2 && board[8]==2) winner=2;
else if (board[0]==2 && board[3]==2 && board[6]==2) winner=2;
else if (board[1]==2 && board[4]==2 && board[7]==2) winner=2;
else if (board[2]==2 && board[5]==2 && board[8]==2) winner=2;
else if (board[0]==2 && board[4]==2 && board[8]==2) winner=2;
else if (board[2]==2 && board[4]==2 && board[6]==2) winner=2;
if (winner == 3) {
for(int i=0;i<9;i++) if (board[i]==0) winner=0; //there are some empty cells yet.
}
}
//--------------------------------------------------------------------------------------------------------------
void resetGame() {
for(int i=0;i<9;i++) board[i]=0; //Resetting the board. 0 = empty cell, 1 = player circle, 2 = CPU cross
winner = 0;
gameStatus = 0;
}
//--------------------------------------------------------------------------------------------------------------
void boardDrawing() {
display.clearDisplay();
display.setTextColor(WHITE);
display.drawFastHLine(0, 21, 64, WHITE); //horizontal lines
display.drawFastHLine(0, 42, 64, WHITE);
display.drawFastVLine(21, 0, 64, WHITE); //vertical lines
display.drawFastVLine(42, 0, 64, WHITE);
//drawing the content of the nine cells: " ", "o", "x"
display.setTextSize(2);
display.setCursor( 5, 5); display.print(charBoard(0)); display.setCursor(25, 5); display.print(charBoard(1)); display.setCursor(45, 5); display.print(charBoard(2));
display.setCursor( 5,25); display.print(charBoard(3)); display.setCursor(25,25); display.print(charBoard(4)); display.setCursor(45,25); display.print(charBoard(5));
display.setCursor( 5,45); display.print(charBoard(6)); display.setCursor(25,45); display.print(charBoard(7)); display.setCursor(45,45); display.print(charBoard(8));
display.display();
delay(200); //DON'T REMOVE!!!! needed for correct refresh and further flashing "?" when it's the player turn!!!
}
//--------------------------------------------------------------------------------------------------------------
String charBoard(int x) {
if (board[x] == 0) return " ";
if (board[x] == 1) return "o";
if (board[x] == 2) return "x";
return "?"; //error trap; but it's impossible it can return an "?" because the board[] array is all initialized = 0
}
//--------------------------------------------------------------------------------------------------------------
void setup() { //function executed once, at every boot or reset.
randomSeed(analogRead(0)); //resetting the random function behavior.
//using the internal pullup resistor, so the button will be LOW until a button is pressed.
pinMode(BUTTON_MOVE,INPUT_PULLUP); //Declaring the pin #2 as input (connected to the button move)
pinMode(BUTTON_OK ,INPUT_PULLUP); //Declaring the pin #3 as input (connected to the button ok)
//display.begin(); //if the display doesn't work with the beHIGH instruction,
//display.begin(SSD1306_SWITCHCAPVCC, 0x3D); //try one of these two ones.
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
delay(500); //needed for display correct initializing
display.clearDisplay(); //clearing the display
display.setTextColor(WHITE); //setting the display color
display.display(); //executing the above instructions. The SSD1306 dislay will not execute any command until to use the ::display() command.
whosplaying = 2; //deciding who's the first player. Set = 2 to force it entering in the folHIGHing while loop.
while ( whosplaying <0 || whosplaying > 1) whosplaying = random(2); //it will stay in the loop until whosplaying isn't = 0 or = 1. Probably there's no
//need for a loop, since random(2) should return 0 or 1, but I'm an old programmer,
//I've seen many strange things in my programmer life and I like to be sure ;-)
}
//--------------------------------------------------------------------------------------------------------------
void loop() { //main loop. Endlessly executed by Arduino.
if (gameStatus == 0){ //this is where I always put a menu in the Arduino games I wrote. We don't need a menu here, so it's just a reset step.
resetGame();
boardDrawing(); //drawing an empty board
gameStatus = 1; //starting the game (see beHIGH)
winner = 0; //no winner for now (winner = 1: player, winner = 2: cpu).
}
//---------------------------------------------
if (gameStatus == 1){ //starting the game
while (winner == 0) { //game main loop: loop until no one wins the match.
display.setTextSize(2);
if (whosplaying == 0) { //whosplaying = 0: cpu turn
display.setCursor( 72,25); display.print("CPU");
display.display();
delay(1000);
playcpu(); //in this function the CPU play its move.
whosplaying =1; //changing the turn.
}
else {
display.setCursor( 72,25); display.print("You");
display.display();
playhuman(); //in this function the player makes his/her move.
whosplaying =0; //changing the turn.
}
boardDrawing(); //refreshing the board with all the moves already done.
delay(500);
checkWinner(); //this will check if there's a winner and assign the winner variable
if (winner > 0) {
if (winner == 3) {
display.setTextSize(2); display.setCursor( 68, 25);
display.print("Draft");
}
else {
//showing who's the winner
display.setTextSize(2); display.setCursor( 72, 25);
if (winner == 1) { Serial.println(F("You")); display.print("You"); display.setCursor( 72, 45); display.print("win"); }
else { Serial.println(F("CPU")); display.print("CPU"); display.setCursor( 72, 45); display.print("wins");}
}
display.display();
delay(1000);
//debounce loop. It will not proceed until both buttons are unpressed.
while (digitalRead(BUTTON_MOVE)==HIGH && digitalRead(BUTTON_OK )==HIGH);
}
//display.display();
}
//swap the first move for the next match between CPU and human (one per match)
if (whosplaying == 0) whosplaying =1; else whosplaying =0;
gameStatus = 0; //entering the reset step
delay(1000); //just wait a second
}
}
Display Source Code (.cpp)
Arduino/*********************************************************************
This is a library for our Monochrome OLEDs based on SSD1306 drivers
Pick one up today in the adafruit shop!
------> http://www.adafruit.com/category/63_98
These displays use SPI to communicate, 4 or 5 pins are required to
interface
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
BSD license, check license.txt for more information
All text above, and the splash screen below must be included in any redistribution
*********************************************************************/
#ifdef __AVR__
#include <avr/pgmspace.h>
#elif defined(ESP8266) || defined(ESP32)
#include <pgmspace.h>
#else
#define pgm_read_byte(addr) (*(const unsigned char *)(addr))
#endif
#if !defined(__ARM_ARCH) && !defined(ENERGIA) && !defined(ESP8266) && !defined(ESP32) && !defined(__arc__)
#include <util/delay.h>
#endif
#include <stdlib.h>
#include <Wire.h>
#include <SPI.h>
#include "Adafruit_GFX.h"
#include "Adafruit_SSD1306.h"
// the memory buffer for the LCD
static uint8_t buffer[SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH / 8] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x80, 0x80, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xF8, 0xE0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80,
0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0xFF,
#if (SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH > 96*16)
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00,
0x80, 0xFF, 0xFF, 0x80, 0x80, 0x00, 0x80, 0x80, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x80, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x8C, 0x8E, 0x84, 0x00, 0x00, 0x80, 0xF8,
0xF8, 0xF8, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xE0, 0xE0, 0xC0, 0x80,
0x00, 0xE0, 0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xC7, 0x01, 0x01,
0x01, 0x01, 0x83, 0xFF, 0xFF, 0x00, 0x00, 0x7C, 0xFE, 0xC7, 0x01, 0x01, 0x01, 0x01, 0x83, 0xFF,
0xFF, 0xFF, 0x00, 0x38, 0xFE, 0xC7, 0x83, 0x01, 0x01, 0x01, 0x83, 0xC7, 0xFF, 0xFF, 0x00, 0x00,
0x01, 0xFF, 0xFF, 0x01, 0x01, 0x00, 0xFF, 0xFF, 0x07, 0x01, 0x01, 0x01, 0x00, 0x00, 0x7F, 0xFF,
0x80, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFF,
0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0x0F, 0x3F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xC7, 0xC7, 0x8F,
0x8F, 0x9F, 0xBF, 0xFF, 0xFF, 0xC3, 0xC0, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFC, 0xFC,
0xFC, 0xFC, 0xFC, 0xFC, 0xFC, 0xF8, 0xF8, 0xF0, 0xF0, 0xE0, 0xC0, 0x00, 0x01, 0x03, 0x03, 0x03,
0x03, 0x03, 0x01, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01,
0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x03, 0x03, 0x01, 0x01, 0x03, 0x03, 0x00, 0x00,
0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x03, 0x03, 0x03, 0x03, 0x03, 0x01, 0x00, 0x00, 0x00, 0x01, 0x03, 0x01, 0x00, 0x00, 0x00, 0x03,
0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
#if (SSD1306_LCDHEIGHT == 64)
0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x1F, 0x0F,
0x87, 0xC7, 0xF7, 0xFF, 0xFF, 0x1F, 0x1F, 0x3D, 0xFC, 0xF8, 0xF8, 0xF8, 0xF8, 0x7C, 0x7D, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x3F, 0x0F, 0x07, 0x00, 0x30, 0x30, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xC0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xC0, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, 0x3F, 0x1F,
0x0F, 0x07, 0x1F, 0x7F, 0xFF, 0xFF, 0xF8, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xF8, 0xE0,
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0x00, 0x00,
0x00, 0xFC, 0xFE, 0xFC, 0x0C, 0x06, 0x06, 0x0E, 0xFC, 0xF8, 0x00, 0x00, 0xF0, 0xF8, 0x1C, 0x0E,
0x06, 0x06, 0x06, 0x0C, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0xFC,
0xFE, 0xFC, 0x00, 0x18, 0x3C, 0x7E, 0x66, 0xE6, 0xCE, 0x84, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0x06,
0x06, 0xFC, 0xFE, 0xFC, 0x0C, 0x06, 0x06, 0x06, 0x00, 0x00, 0xFE, 0xFE, 0x00, 0x00, 0xC0, 0xF8,
0xFC, 0x4E, 0x46, 0x46, 0x46, 0x4E, 0x7C, 0x78, 0x40, 0x18, 0x3C, 0x76, 0xE6, 0xCE, 0xCC, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x07, 0x0F, 0x1F, 0x1F, 0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x0F, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00,
0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x03, 0x07, 0x0E, 0x0C,
0x18, 0x18, 0x0C, 0x06, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x01, 0x0F, 0x0E, 0x0C, 0x18, 0x0C, 0x0F,
0x07, 0x01, 0x00, 0x04, 0x0E, 0x0C, 0x18, 0x0C, 0x0F, 0x07, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00,
0x00, 0x0F, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x0F, 0x00, 0x00, 0x00, 0x07,
0x07, 0x0C, 0x0C, 0x18, 0x1C, 0x0C, 0x06, 0x06, 0x00, 0x04, 0x0E, 0x0C, 0x18, 0x0C, 0x0F, 0x07,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
#endif
#endif
};
#define ssd1306_swap(a, b) { int16_t t = a; a = b; b = t; }
// the most basic function, set a single pixel
void Adafruit_SSD1306::drawPixel(int16_t x, int16_t y, uint16_t color) {
if ((x < 0) || (x >= width()) || (y < 0) || (y >= height()))
return;
// check rotation, move pixel around if necessary
switch (getRotation()) {
case 1:
ssd1306_swap(x, y);
x = WIDTH - x - 1;
break;
case 2:
x = WIDTH - x - 1;
y = HEIGHT - y - 1;
break;
case 3:
ssd1306_swap(x, y);
y = HEIGHT - y - 1;
break;
}
// x is which column
switch (color)
{
case WHITE: buffer[x+ (y/8)*SSD1306_LCDWIDTH] |= (1 << (y&7)); break;
case BLACK: buffer[x+ (y/8)*SSD1306_LCDWIDTH] &= ~(1 << (y&7)); break;
case INVERSE: buffer[x+ (y/8)*SSD1306_LCDWIDTH] ^= (1 << (y&7)); break;
}
}
Adafruit_SSD1306::Adafruit_SSD1306(int8_t SID, int8_t SCLK, int8_t DC, int8_t RST, int8_t CS) : Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT) {
cs = CS;
rst = RST;
dc = DC;
sclk = SCLK;
sid = SID;
hwSPI = false;
}
// constructor for hardware SPI - we indicate DataCommand, ChipSelect, Reset
Adafruit_SSD1306::Adafruit_SSD1306(int8_t DC, int8_t RST, int8_t CS) : Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT) {
dc = DC;
rst = RST;
cs = CS;
hwSPI = true;
}
// initializer for I2C - we only indicate the reset pin!
Adafruit_SSD1306::Adafruit_SSD1306(int8_t reset) :
Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT) {
sclk = dc = cs = sid = -1;
rst = reset;
}
void Adafruit_SSD1306::begin(uint8_t vccstate, uint8_t i2caddr, bool reset) {
_vccstate = vccstate;
_i2caddr = i2caddr;
// set pin directions
if (sid != -1){
pinMode(dc, OUTPUT);
pinMode(cs, OUTPUT);
#ifdef HAVE_PORTREG
csport = portOutputRegister(digitalPinToPort(cs));
cspinmask = digitalPinToBitMask(cs);
dcport = portOutputRegister(digitalPinToPort(dc));
dcpinmask = digitalPinToBitMask(dc);
#endif
if (!hwSPI){
// set pins for software-SPI
pinMode(sid, OUTPUT);
pinMode(sclk, OUTPUT);
#ifdef HAVE_PORTREG
clkport = portOutputRegister(digitalPinToPort(sclk));
clkpinmask = digitalPinToBitMask(sclk);
mosiport = portOutputRegister(digitalPinToPort(sid));
mosipinmask = digitalPinToBitMask(sid);
#endif
}
if (hwSPI){
SPI.begin();
#ifdef SPI_HAS_TRANSACTION
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
#else
SPI.setClockDivider (4);
#endif
}
}
else
{
// I2C Init
Wire.begin();
#ifdef __SAM3X8E__
// Force 400 KHz I2C, rawr! (Uses pins 20, 21 for SDA, SCL)
TWI1->TWI_CWGR = 0;
TWI1->TWI_CWGR = ((VARIANT_MCK / (2 * 400000)) - 4) * 0x101;
#endif
}
if ((reset) && (rst >= 0)) {
// Setup reset pin direction (used by both SPI and I2C)
pinMode(rst, OUTPUT);
digitalWrite(rst, HIGH);
// VDD (3.3V) goes high at start, lets just chill for a ms
delay(1);
// bring reset low
digitalWrite(rst, LOW);
// wait 10ms
delay(10);
// bring out of reset
digitalWrite(rst, HIGH);
// turn on VCC (9V?)
}
// Init sequence
ssd1306_command(SSD1306_DISPLAYOFF); // 0xAE
ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV); // 0xD5
ssd1306_command(0x80); // the suggested ratio 0x80
ssd1306_command(SSD1306_SETMULTIPLEX); // 0xA8
ssd1306_command(SSD1306_LCDHEIGHT - 1);
ssd1306_command(SSD1306_SETDISPLAYOFFSET); // 0xD3
ssd1306_command(0x0); // no offset
ssd1306_command(SSD1306_SETSTARTLINE | 0x0); // line #0
ssd1306_command(SSD1306_CHARGEPUMP); // 0x8D
if (vccstate == SSD1306_EXTERNALVCC)
{ ssd1306_command(0x10); }
else
{ ssd1306_command(0x14); }
ssd1306_command(SSD1306_MEMORYMODE); // 0x20
ssd1306_command(0x00); // 0x0 act like ks0108
ssd1306_command(SSD1306_SEGREMAP | 0x1);
ssd1306_command(SSD1306_COMSCANDEC);
#if defined SSD1306_128_32
ssd1306_command(SSD1306_SETCOMPINS); // 0xDA
ssd1306_command(0x02);
ssd1306_command(SSD1306_SETCONTRAST); // 0x81
ssd1306_command(0x8F);
#elif defined SSD1306_128_64
ssd1306_command(SSD1306_SETCOMPINS); // 0xDA
ssd1306_command(0x12);
ssd1306_command(SSD1306_SETCONTRAST); // 0x81
if (vccstate == SSD1306_EXTERNALVCC)
{ ssd1306_command(0x9F); }
else
{ ssd1306_command(0xCF); }
#elif defined SSD1306_96_16
ssd1306_command(SSD1306_SETCOMPINS); // 0xDA
ssd1306_command(0x2); //ada x12
ssd1306_command(SSD1306_SETCONTRAST); // 0x81
if (vccstate == SSD1306_EXTERNALVCC)
{ ssd1306_command(0x10); }
else
{ ssd1306_command(0xAF); }
#endif
ssd1306_command(SSD1306_SETPRECHARGE); // 0xd9
if (vccstate == SSD1306_EXTERNALVCC)
{ ssd1306_command(0x22); }
else
{ ssd1306_command(0xF1); }
ssd1306_command(SSD1306_SETVCOMDETECT); // 0xDB
ssd1306_command(0x40);
ssd1306_command(SSD1306_DISPLAYALLON_RESUME); // 0xA4
ssd1306_command(SSD1306_NORMALDISPLAY); // 0xA6
ssd1306_command(SSD1306_DEACTIVATE_SCROLL);
ssd1306_command(SSD1306_DISPLAYON);//--turn on oled panel
}
void Adafruit_SSD1306::invertDisplay(uint8_t i) {
if (i) {
ssd1306_command(SSD1306_INVERTDISPLAY);
} else {
ssd1306_command(SSD1306_NORMALDISPLAY);
}
}
void Adafruit_SSD1306::ssd1306_command(uint8_t c) {
if (sid != -1)
{
// SPI
#ifdef HAVE_PORTREG
*csport |= cspinmask;
*dcport &= ~dcpinmask;
*csport &= ~cspinmask;
#else
digitalWrite(cs, HIGH);
digitalWrite(dc, LOW);
digitalWrite(cs, LOW);
#endif
fastSPIwrite(c);
#ifdef HAVE_PORTREG
*csport |= cspinmask;
#else
digitalWrite(cs, HIGH);
#endif
}
else
{
// I2C
uint8_t control = 0x00; // Co = 0, D/C = 0
Wire.beginTransmission(_i2caddr);
Wire.write(control);
Wire.write(c);
Wire.endTransmission();
}
}
// startscrollright
// Activate a right handed scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void Adafruit_SSD1306::startscrollright(uint8_t start, uint8_t stop){
ssd1306_command(SSD1306_RIGHT_HORIZONTAL_SCROLL);
ssd1306_command(0X00);
ssd1306_command(start);
ssd1306_command(0X00);
ssd1306_command(stop);
ssd1306_command(0X00);
ssd1306_command(0XFF);
ssd1306_command(SSD1306_ACTIVATE_SCROLL);
}
// startscrollleft
// Activate a right handed scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void Adafruit_SSD1306::startscrollleft(uint8_t start, uint8_t stop){
ssd1306_command(SSD1306_LEFT_HORIZONTAL_SCROLL);
ssd1306_command(0X00);
ssd1306_command(start);
ssd1306_command(0X00);
ssd1306_command(stop);
ssd1306_command(0X00);
ssd1306_command(0XFF);
ssd1306_command(SSD1306_ACTIVATE_SCROLL);
}
// startscrolldiagright
// Activate a diagonal scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void Adafruit_SSD1306::startscrolldiagright(uint8_t start, uint8_t stop){
ssd1306_command(SSD1306_SET_VERTICAL_SCROLL_AREA);
ssd1306_command(0X00);
ssd1306_command(SSD1306_LCDHEIGHT);
ssd1306_command(SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL);
ssd1306_command(0X00);
ssd1306_command(start);
ssd1306_command(0X00);
ssd1306_command(stop);
ssd1306_command(0X01);
ssd1306_command(SSD1306_ACTIVATE_SCROLL);
}
// startscrolldiagleft
// Activate a diagonal scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void Adafruit_SSD1306::startscrolldiagleft(uint8_t start, uint8_t stop){
ssd1306_command(SSD1306_SET_VERTICAL_SCROLL_AREA);
ssd1306_command(0X00);
ssd1306_command(SSD1306_LCDHEIGHT);
ssd1306_command(SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL);
ssd1306_command(0X00);
ssd1306_command(start);
ssd1306_command(0X00);
ssd1306_command(stop);
ssd1306_command(0X01);
ssd1306_command(SSD1306_ACTIVATE_SCROLL);
}
void Adafruit_SSD1306::stopscroll(void){
ssd1306_command(SSD1306_DEACTIVATE_SCROLL);
}
// Dim the display
// dim = true: display is dimmed
// dim = false: display is normal
void Adafruit_SSD1306::dim(boolean dim) {
uint8_t contrast;
if (dim) {
contrast = 1; //0; // Dimmed display - If = zero, the display is off.
} else {
if (_vccstate == SSD1306_EXTERNALVCC) {
contrast = 0x9F;
} else {
contrast = 0xCF;
}
}
// the range of contrast to too small to be really useful
// it is useful to dim the display
ssd1306_command(SSD1306_SETCONTRAST);
ssd1306_command(contrast);
}
void Adafruit_SSD1306::display(void) {
ssd1306_command(SSD1306_COLUMNADDR);
ssd1306_command(0); // Column start address (0 = reset)
ssd1306_command(SSD1306_LCDWIDTH-1); // Column end address (127 = reset)
ssd1306_command(SSD1306_PAGEADDR);
ssd1306_command(0); // Page start address (0 = reset)
#if SSD1306_LCDHEIGHT == 64
ssd1306_command(7); // Page end address
#endif
#if SSD1306_LCDHEIGHT == 32
ssd1306_command(3); // Page end address
#endif
#if SSD1306_LCDHEIGHT == 16
ssd1306_command(1); // Page end address
#endif
if (sid != -1)
{
// SPI
#ifdef HAVE_PORTREG
*csport |= cspinmask;
*dcport |= dcpinmask;
*csport &= ~cspinmask;
#else
digitalWrite(cs, HIGH);
digitalWrite(dc, HIGH);
digitalWrite(cs, LOW);
#endif
for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++) {
fastSPIwrite(buffer[i]);
}
#ifdef HAVE_PORTREG
*csport |= cspinmask;
#else
digitalWrite(cs, HIGH);
#endif
}
else
{
// save I2C bitrate
#ifdef TWBR
uint8_t twbrbackup = TWBR;
TWBR = 12; // upgrade to 400KHz!
#endif
//Serial.println(TWBR, DEC);
//Serial.println(TWSR & 0x3, DEC);
// I2C
for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++) {
// send a bunch of data in one xmission
Wire.beginTransmission(_i2caddr);
WIRE_WRITE(0x40);
for (uint8_t x=0; x<16; x++) {
WIRE_WRITE(buffer[i]);
i++;
}
i--;
Wire.endTransmission();
}
#ifdef TWBR
TWBR = twbrbackup;
#endif
}
}
// clear everything
void Adafruit_SSD1306::clearDisplay(void) {
memset(buffer, 0, (SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8));
}
inline void Adafruit_SSD1306::fastSPIwrite(uint8_t d) {
if(hwSPI) {
(void)SPI.transfer(d);
} else {
for(uint8_t bit = 0x80; bit; bit >>= 1) {
#ifdef HAVE_PORTREG
*clkport &= ~clkpinmask;
if(d & bit) *mosiport |= mosipinmask;
else *mosiport &= ~mosipinmask;
*clkport |= clkpinmask;
#else
digitalWrite(sclk, LOW);
if(d & bit) digitalWrite(sid, HIGH);
else digitalWrite(sid, LOW);
digitalWrite(sclk, HIGH);
#endif
}
}
}
void Adafruit_SSD1306::drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
boolean bSwap = false;
switch(rotation) {
case 0:
// 0 degree rotation, do nothing
break;
case 1:
// 90 degree rotation, swap x & y for rotation, then invert x
bSwap = true;
ssd1306_swap(x, y);
x = WIDTH - x - 1;
break;
case 2:
// 180 degree rotation, invert x and y - then shift y around for height.
x = WIDTH - x - 1;
y = HEIGHT - y - 1;
x -= (w-1);
break;
case 3:
// 270 degree rotation, swap x & y for rotation, then invert y and adjust y for w (not to become h)
bSwap = true;
ssd1306_swap(x, y);
y = HEIGHT - y - 1;
y -= (w-1);
break;
}
if(bSwap) {
drawFastVLineInternal(x, y, w, color);
} else {
drawFastHLineInternal(x, y, w, color);
}
}
void Adafruit_SSD1306::drawFastHLineInternal(int16_t x, int16_t y, int16_t w, uint16_t color) {
// Do bounds/limit checks
if(y < 0 || y >= HEIGHT) { return; }
// make sure we don't try to draw below 0
if(x < 0) {
w += x;
x = 0;
}
// make sure we don't go off the edge of the display
if( (x + w) > WIDTH) {
w = (WIDTH - x);
}
// if our width is now negative, punt
if(w <= 0) { return; }
// set up the pointer for movement through the buffer
register uint8_t *pBuf = buffer;
// adjust the buffer pointer for the current row
pBuf += ((y/8) * SSD1306_LCDWIDTH);
// and offset x columns in
pBuf += x;
register uint8_t mask = 1 << (y&7);
switch (color)
{
case WHITE: while(w--) { *pBuf++ |= mask; }; break;
case BLACK: mask = ~mask; while(w--) { *pBuf++ &= mask; }; break;
case INVERSE: while(w--) { *pBuf++ ^= mask; }; break;
}
}
void Adafruit_SSD1306::drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
bool bSwap = false;
switch(rotation) {
case 0:
break;
case 1:
// 90 degree rotation, swap x & y for rotation, then invert x and adjust x for h (now to become w)
bSwap = true;
ssd1306_swap(x, y);
x = WIDTH - x - 1;
x -= (h-1);
break;
case 2:
// 180 degree rotation, invert x and y - then shift y around for height.
x = WIDTH - x - 1;
y = HEIGHT - y - 1;
y -= (h-1);
break;
case 3:
// 270 degree rotation, swap x & y for rotation, then invert y
bSwap = true;
ssd1306_swap(x, y);
y = HEIGHT - y - 1;
break;
}
if(bSwap) {
drawFastHLineInternal(x, y, h, color);
} else {
drawFastVLineInternal(x, y, h, color);
}
}
void Adafruit_SSD1306::drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h, uint16_t color) {
// do nothing if we're off the left or right side of the screen
if(x < 0 || x >= WIDTH) { return; }
// make sure we don't try to draw below 0
if(__y < 0) {
// __y is negative, this will subtract enough from __h to account for __y being 0
__h += __y;
__y = 0;
}
// make sure we don't go past the height of the display
if( (__y + __h) > HEIGHT) {
__h = (HEIGHT - __y);
}
// if our height is now negative, punt
if(__h <= 0) {
return;
}
// this display doesn't need ints for coordinates, use local byte registers for faster juggling
register uint8_t y = __y;
register uint8_t h = __h;
// set up the pointer for fast movement through the buffer
register uint8_t *pBuf = buffer;
// adjust the buffer pointer for the current row
pBuf += ((y/8) * SSD1306_LCDWIDTH);
// and offset x columns in
pBuf += x;
// do the first partial byte, if necessary - this requires some masking
register uint8_t mod = (y&7);
if(mod) {
// mask off the high n bits we want to set
mod = 8-mod;
// note - lookup table results in a nearly 10% performance improvement in fill* functions
// register uint8_t mask = ~(0xFF >> (mod));
static uint8_t premask[8] = {0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE };
register uint8_t mask = premask[mod];
// adjust the mask if we're not going to reach the end of this byte
if( h < mod) {
mask &= (0XFF >> (mod-h));
}
switch (color)
{
case WHITE: *pBuf |= mask; break;
case BLACK: *pBuf &= ~mask; break;
case INVERSE: *pBuf ^= mask; break;
}
// fast exit if we're done here!
if(h<mod) { return; }
h -= mod;
pBuf += SSD1306_LCDWIDTH;
}
// write solid bytes while we can - effectively doing 8 rows at a time
if(h >= 8) {
if (color == INVERSE) { // separate copy of the code so we don't impact performance of the black/white write version with an extra comparison per loop
do {
*pBuf=~(*pBuf);
// adjust the buffer forward 8 rows worth of data
pBuf += SSD1306_LCDWIDTH;
// adjust h & y (there's got to be a faster way for me to do this, but this should still help a fair bit for now)
h -= 8;
} while(h >= 8);
}
else {
// store a local value to work with
register uint8_t val = (color == WHITE) ? 255 : 0;
do {
// write our value in
*pBuf = val;
// adjust the buffer forward 8 rows worth of data
pBuf += SSD1306_LCDWIDTH;
// adjust h & y (there's got to be a faster way for me to do this, but this should still help a fair bit for now)
h -= 8;
} while(h >= 8);
}
}
// now do the final partial byte, if necessary
if(h) {
mod = h & 7;
// this time we want to mask the low bits of the byte, vs the high bits we did above
// register uint8_t mask = (1 << mod) - 1;
// note - lookup table results in a nearly 10% performance improvement in fill* functions
static uint8_t postmask[8] = {0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F };
register uint8_t mask = postmask[mod];
switch (color)
{
case WHITE: *pBuf |= mask; break;
case BLACK: *pBuf &= ~mask; break;
case INVERSE: *pBuf ^= mask; break;
}
}
}
Display Source Code (.h)
Arduino/*********************************************************************
This is a library for our Monochrome OLEDs based on SSD1306 drivers
MODIFIED by Giovanni Verrua, SEE BELOW
Pick one up today in the adafruit shop!
------> http://www.adafruit.com/category/63_98
These displays use SPI to communicate, 4 or 5 pins are required to
interface
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
BSD license, check license.txt for more information
All text above, and the splash screen must be included in any redistribution
*********************************************************************/
#ifndef _Adafruit_SSD1306_H_
#define _Adafruit_SSD1306_H_
#if ARDUINO >= 100
#include "Arduino.h"
#define WIRE_WRITE Wire.write
#else
#include "WProgram.h"
#define WIRE_WRITE Wire.send
#endif
#if defined(__SAM3X8E__)
typedef volatile RwReg PortReg;
typedef uint32_t PortMask;
#define HAVE_PORTREG
#elif defined(ARDUINO_ARCH_SAMD)
// not supported
#elif defined(ESP8266) || defined(ESP32) || defined(ARDUINO_STM32_FEATHER) || defined(__arc__)
typedef volatile uint32_t PortReg;
typedef uint32_t PortMask;
#elif defined(__AVR__)
typedef volatile uint8_t PortReg;
typedef uint8_t PortMask;
#define HAVE_PORTREG
#else
// chances are its 32 bit so assume that
typedef volatile uint32_t PortReg;
typedef uint32_t PortMask;
#endif
#include <SPI.h>
#include <Adafruit_GFX.h>
#define BLACK 0
#define WHITE 1
#define INVERSE 2
/*=========================================================================
SSD1306 Displays
-----------------------------------------------------------------------
The driver is used in multiple displays (128x64, 128x32, etc.).
Select the appropriate display below to create an appropriately
sized framebuffer, etc.
SSD1306_128_64 128x64 pixel display
SSD1306_128_32 128x32 pixel display
SSD1306_96_16
-----------------------------------------------------------------------*/
//------------------------------------------------------------------------------------|
//modified by Giovanni Verrua. Change it if the display doesn't work
#define SSD1306_I2C_ADDRESS 0x3C // 011110+SA0+RW - 0x3C or 0x3D
// Address for 128x32 is 0x3C
// Address for 128x64 is 0x3D (default) or 0x3C (if SA0 is grounded)
#define SSD1306_128_64
// #define SSD1306_128_32
// #define SSD1306_96_16
//------------------------------------------------------------------------------------|
/*=========================================================================*/
#if defined SSD1306_128_64 && defined SSD1306_128_32
#error "Only one SSD1306 display can be specified at once in SSD1306.h"
#endif
#if !defined SSD1306_128_64 && !defined SSD1306_128_32 && !defined SSD1306_96_16
#error "At least one SSD1306 display must be specified in SSD1306.h"
#endif
#if defined SSD1306_128_64
#define SSD1306_LCDWIDTH 128
#define SSD1306_LCDHEIGHT 64
#endif
#if defined SSD1306_128_32
#define SSD1306_LCDWIDTH 128
#define SSD1306_LCDHEIGHT 32
#endif
#if defined SSD1306_96_16
#define SSD1306_LCDWIDTH 96
#define SSD1306_LCDHEIGHT 16
#endif
#define SSD1306_SETCONTRAST 0x81
#define SSD1306_DISPLAYALLON_RESUME 0xA4
#define SSD1306_DISPLAYALLON 0xA5
#define SSD1306_NORMALDISPLAY 0xA6
#define SSD1306_INVERTDISPLAY 0xA7
#define SSD1306_DISPLAYOFF 0xAE
#define SSD1306_DISPLAYON 0xAF
#define SSD1306_SETDISPLAYOFFSET 0xD3
#define SSD1306_SETCOMPINS 0xDA
#define SSD1306_SETVCOMDETECT 0xDB
#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
#define SSD1306_SETPRECHARGE 0xD9
#define SSD1306_SETMULTIPLEX 0xA8
#define SSD1306_SETLOWCOLUMN 0x00
#define SSD1306_SETHIGHCOLUMN 0x10
#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_MEMORYMODE 0x20
#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR 0x22
#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8
#define SSD1306_SEGREMAP 0xA0
#define SSD1306_CHARGEPUMP 0x8D
#define SSD1306_EXTERNALVCC 0x1
#define SSD1306_SWITCHCAPVCC 0x2
// Scrolling #defines
#define SSD1306_ACTIVATE_SCROLL 0x2F
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
class Adafruit_SSD1306 : public Adafruit_GFX {
public:
Adafruit_SSD1306(int8_t SID, int8_t SCLK, int8_t DC, int8_t RST, int8_t CS);
Adafruit_SSD1306(int8_t DC, int8_t RST, int8_t CS);
Adafruit_SSD1306(int8_t RST = -1);
void begin(uint8_t switchvcc = SSD1306_SWITCHCAPVCC, uint8_t i2caddr = SSD1306_I2C_ADDRESS, bool reset=true);
void ssd1306_command(uint8_t c);
void clearDisplay(void);
void invertDisplay(uint8_t i);
void display();
void startscrollright(uint8_t start, uint8_t stop);
void startscrollleft(uint8_t start, uint8_t stop);
void startscrolldiagright(uint8_t start, uint8_t stop);
void startscrolldiagleft(uint8_t start, uint8_t stop);
void stopscroll(void);
void dim(boolean dim);
void drawPixel(int16_t x, int16_t y, uint16_t color);
virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
private:
int8_t _i2caddr, _vccstate, sid, sclk, dc, rst, cs;
void fastSPIwrite(uint8_t c);
boolean hwSPI;
#ifdef HAVE_PORTREG
PortReg *mosiport, *clkport, *csport, *dcport;
PortMask mosipinmask, clkpinmask, cspinmask, dcpinmask;
#endif
inline void drawFastVLineInternal(int16_t x, int16_t y, int16_t h, uint16_t color) __attribute__((always_inline));
inline void drawFastHLineInternal(int16_t x, int16_t y, int16_t w, uint16_t color) __attribute__((always_inline));
};
#endif /* _Adafruit_SSD1306_H_ */










Comments