How to Make a Magic 8 Ball With Arduino Nano, MPU6050, and SH1106 OLED Display

by Emorrow in Circuits > Arduino

146 Views, 1 Favorites, 0 Comments

How to Make a Magic 8 Ball With Arduino Nano, MPU6050, and SH1106 OLED Display

Magic 8 Ball

Have you ever wanted to create your own Magic 8 Ball? This project will guide you through the steps to build a fun, interactive Magic 8 Ball using an Arduino Nano, an MPU6050 accelerometer, and an SH1106 OLED display. By shaking the device, you will get random answers displayed on the screen.

Unlike other tutorials that often skip using an OLED screen, this project incorporates one to enhance the user experience. We will use the U8g2lib library instead of the usual ones because the SH1106 library was causing memory issues with the Nano, leading to crashes.

Supplies

1000008760.jpg
1000008761.jpg
1000008759.jpg

Arduino Nano - https://a.co/d/07b2wHT1

MPU6050 Accelerometer - https://a.co/d/0hZL9dk3

SH1106 OLED Display (SPI Interface) - https://a.co/d/0dbZZOZT (I ordered this and got the spi one).

Breadboard and jumper wires

Wiring the Components

20240706_222051.jpg

OLED Screen (SPI Interface)

  • VCC (Power) Pin:
  • Connect the VCC pin of the OLED screen to the 5V pin on the Arduino Nano.
  • GND (Ground) Pin:
  • Connect the GND pin of the OLED screen to one of the GND pins on the Arduino Nano.
  • SCK (Clock) Pin:
  • Connect the SCK pin of the OLED screen to the D13 pin on the Arduino Nano.
  • SDA (Data) Pin:
  • Connect the SDA pin of the OLED screen to the D11 pin on the Arduino Nano.
  • RES (Reset) Pin:
  • Connect the RES pin of the OLED screen to the D9 pin on the Arduino Nano.
  • DC (Data/Command) Pin:
  • Connect the DC pin of the OLED screen to the D8 pin on the Arduino Nano.
  • CS (Chip Select) Pin:
  • Connect the CS pin of the OLED screen to the D10 pin on the Arduino Nano.

MPU6050 (I2C Interface)

  • VCC (Power) Pin:
  • Connect the VCC pin of the MPU6050 to the 5V pin on the Arduino Nano.
  • GND (Ground) Pin:
  • Connect the GND pin of the MPU6050 to one of the GND pins on the Arduino Nano.
  • SCL (Clock) Pin:
  • Connect the SCL pin of the MPU6050 to the A5 pin on the Arduino Nano.
  • SDA (Data) Pin:
  • Connect the SDA pin of the MPU6050 to the A4 pin on the Arduino Nano.


Coding the Arduino

#include <Wire.h>
#include <U8g2lib.h>
#include <MPU6050.h>
#include <SPI.h>


// Define pins for the OLED display
#define OLED_DC 8
#define OLED_CS 10
#define OLED_RST 9


// Initialize the OLED display using hardware SPI
U8G2_SH1106_128X64_NONAME_F_4W_HW_SPI u8g2(U8G2_R0, OLED_CS, OLED_DC, OLED_RST);


// Initialize the MPU6050 sensor
MPU6050 mpu;


// Array of possible answers stored in program memory (flash memory)
const char answers[][30] PROGMEM = {
  "It is certain",
  "It is decidedly so",
  "Without a doubt",
  "Yes, definitely",
  "You may rely on it",
  "As I see it, yes",
  "Most likely",
  "Outlook good",
  "Yes",
  "Signs point to yes",
  "Reply hazy try again",
  "Ask again later",
  "Better not tell you now",
  "Cannot predict now",
  "Concentrate and ask again",
  "Don't count on it",
  "My reply is no",
  "My sources say no",
  "Outlook not so good",
  "Very doubtful"
};


// Threshold for detecting a shake
const int shakeThreshold = 20000;
unsigned long previousMillis = 0; // Stores the last time a shake was detected
bool displayAnswer = false; // Flag to indicate if an answer should be displayed
int num_answers; // Total number of possible answers


void setup() {
  // Initialize serial communication for debugging
  Serial.begin(115200);


  // Initialize I2C and SPI communication
  Wire.begin();
  SPI.begin();


  // Initialize the OLED display
  u8g2.begin();
  u8g2.setFont(u8g2_font_ncenB08_tr);


  // Initialize the MPU6050 sensor
  mpu.initialize();
  
  // Check if the MPU6050 is connected
  if (!mpu.testConnection()) {
    // If not connected, enter an infinite loop
    while (1);
  }


  // Calculate the number of possible answers
  num_answers = sizeof(answers) / sizeof(answers[0]);


  // Display the initial message
  displayText("Shake me!");
}


void loop() {
  // Variables to store acceleration values
  int16_t ax, ay, az;


  // Get acceleration values from the MPU6050 sensor
  mpu.getAcceleration(&ax, &ay, &az);


  // Check if the device is shaking
  if (isShaking(ax, ay, az)) {
    unsigned long currentMillis = millis();
    
    // Check if 500 milliseconds have passed since the last shake
    if (currentMillis - previousMillis >= 500) {
      previousMillis = currentMillis;
      displayAnswer = true;
    }
  }


  // If it's time to display an answer
  if (displayAnswer) {
    displayAnswer = false;


    // Display "Thinking..." for 2 seconds
    displayText("Thinking...");
    delay(2000);


    // Pick a random answer
    int answer_picked = random(0, num_answers);


    // Buffer to hold the selected answer
    char buffer[30];
    
    // Copy the selected answer from flash memory to the buffer
    strcpy_P(buffer, answers[answer_picked]);
    
    // Display the selected answer
    displayText(buffer);
    delay(2000);


    // Revert back to the initial message
    displayText("Shake me!");
  }
}


// Function to check if the device is shaking based on acceleration values
bool isShaking(int16_t ax, int16_t ay, int16_t az) {
  const int16_t deadZone = 8000; // Minimum threshold to ignore small movements
  return (abs(ax) > shakeThreshold && abs(ax) > deadZone) ||
         (abs(ay) > shakeThreshold && abs(ay) > deadZone) ||
         (abs(az) > shakeThreshold && abs(az) > deadZone);
}


// Function to display text on the OLED screen
void displayText(const char* text) {
  u8g2.clearBuffer(); // Clear the display buffer


  // Calculate the width and height of the text
  uint8_t w = u8g2.getStrWidth(text);
  uint8_t h = u8g2.getAscent() - u8g2.getDescent();


  // Calculate positions to center the text on the screen
  uint8_t x = (u8g2.getDisplayWidth() - w) / 2;
  uint8_t y = (u8g2.getDisplayHeight() - h) / 2;


  // Draw the text on the screen
  u8g2.drawStr(x, y + u8g2.getAscent(), text);


  // Send the buffer to the display
  u8g2.sendBuffer();
}


Explanation


Includes and Defines: Necessary libraries are included, and OLED pins are defined.

Global Variables:

  • u8g2: OLED display object.
  • mpu: MPU6050 sensor object.
  • answers: Array of possible answers stored in flash memory.
  • shakeThreshold: Threshold for detecting shaking.
  • previousMillis: Last time a shake was detected.
  • displayAnswer: Flag for whether to display an answer.
  • num_answers: Total number of answers.

Setup Function:

  • Initializes serial communication, I2C, SPI, OLED display, and MPU6050 sensor.
  • Checks MPU6050 connection and calculates the number of answers.
  • Displays initial "Shake me!" message.

Loop Function:

  • Continuously reads acceleration values.
  • Checks for shaking and if enough time has passed since the last shake.
  • Displays "Thinking...", picks a random answer, and displays it.
  • Reverts to "Shake me!" message.

Helper Functions:

  • isShaking: Checks if the device is shaking based on acceleration values.
  • displayText: Displays text on the OLED screen, centered.



Downloads

Uploading the Code

  • Connect your Arduino Nano to your computer using a USB cable.
  • Open the Arduino IDE.
  • Copy and paste the provided code into the Arduino IDE.
  • Select the correct board and port from the Tools menu.
  • Click the upload button to upload the code to your Arduino Nano.

Testing Your Magic 8 Ball

  • After uploading the code, disconnect the Arduino Nano from your computer and connect it to a power source (or use computer power and leave it plugged in)
  • The OLED display should show "Shake me!".
  • Shake the device, and the MPU6050 will detect the motion.
  • The display will show "Thinking..." for a couple of seconds and then display a random answer.
  • After displaying the answer, it will revert back to "Shake me!" ready for the next shake.