giobbino
Published © GPL3+

Easiest TicTacToe (with and without an OLED display)

An easy TicTacToe project to start with Arduino. Fully self-describing code, easy to assemble, with a "serial monitor only" version too.

BeginnerFull instructions provided7,737
Easiest TicTacToe (with and without an OLED display)

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
You can use Nano, Uno, Mini Pro. Other flavors could need some code change.
×1
i2c 0.96" OLED display (SSD1306)
Needed for the OLED version only
×1
Through Hole Resistor, 100 kohm
Through Hole Resistor, 100 kohm
Needed for the OLED version only. Not needed if you use the "no resistors" version.
×2
Pushbutton Switch, Momentary
Pushbutton Switch, Momentary
Needed for the OLED version only (both versions, with and without resistors)
×2

Software apps and online services

Arduino IDE
Arduino IDE
Used to compile and upload - If you're using the serial monitor version, you can use the Arduino IDE Serial Monitor to play.

Story

Read more

Schematics

Schematics for the OLED version

This is the OLED version schematics. You don't need to build it if you just want to use the Serial Monitor version. In this case, you only need to upload the appropriate sketch and play using the Serial Monitor.

Schematics for the OLED version (no resistors)

This is the schematics version that uses the Arduino internal pullup resistors

Code

TicTacToe - OLED version

Arduino
This is the sketch for the OLED display 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
This sketch requires the Arduino board only. You can play using the Arduino IDE serial monitor
/**************************************************************************
 *  
 *  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 version (no resistors)

Arduino
This uses the Arduino internal pullup resistors
/**************************************************************************
 *  
 *  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
download and add these two files to the sketch folder if you display doesn't work.
/*********************************************************************
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
download and add these two files to the sketch folder if you display doesn't work.
/*********************************************************************
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_ */

Credits

giobbino
4 projects • 4 followers

Comments