Table of Contents

Digital Dice

Author: Voicu Teodora-Andreea, 333CA

Introducere

Descriere Generala

  1. Dispozitivul e zguduit → accelerometrul detecteaza miscare peste SHAKE_THRESHOLD
  2. Stare SHAKING → afiseaza “Shake!” si porneste redarea DICE.WAV pe buzzer
  3. Dupa SHAKE_TIMEOUT fara miscare → opreste WAV, genereaza doua valori random (1–6) și le afiseaza pe OLED
  4. Dupa RESULT_HOLD → revine la “READY!” și asteapta urmatorul shake
  

Diagramă Arduino UNO + MPU-605

Hardware Design

Lista Componente:

Functional Overview:

Circuit

* OLED SSD1306 (I²C)

Sursa de alimentare a circuitului intern şi a driver-ului display-ului.

Reţeaua de masă comună.

Linia de date I²C: aici circulă octeţi de la Arduino către pixelii OLED-ului.

Linia de ceas I²C: sincronizează transferul de date pe SDA.

De ce OLED? – Afișează mesaje şi rezultatul zarurilor într-un format grafic compact, cu consum redus de energie.

* Modul SD-card (SPI)

Alimentează convertorul de nivel şi interfaţa SPI.

Referinţa de masă pentru comunicaţia SPI.

Selectează modulul SD pe magistrala SPI (când e LOW, modulul răspunde).

Trimite datele din Arduino spre SD (de exemplu, comenzi de citire).

Primeşte datele de la SD spre Arduino (conţinutul fişierului WAV).

Ceasul pentru sincronizarea transferului de biţi MOSI/MISO.

De ce SD-card? – Permite stocarea fişierului WAV de sunet de zaruri, fără să încarce memoria internă a plăcii.

* Buzzer pasiv

Pinul PWM generează semnalul audio propriu-zis (tonuri variabile conform valorilor citite din WAV).

Redă amplitudinea semnalului PWM către buzzer.

Întoarcerea curentului şi referinţa de masă.

Ce face PWM? – Pulse Width Modulation modulează lăţimea impulsurilor pentru a crea frecvenţe audio perceptibile ca sunet.

* Modul LIS2DH12 (I²C)

Alimentează senzorul cu tensiune stabilă de 3.3 V.

Stabilirea referinţei zero pentru măsurări.

Linia de date I²C: transmite valorile brute de acceleraţie.

Linia de ceas I²C pentru sincronizarea transferului de date.

Configurează adresa I²C la 0x19 (pull-up intern), astfel evitând conflicte pe bus.

CS rămâne HIGH; senzorul rămâne mereu activ pe I²C.

De ce accelerometru LIS2DH12? – Detectează gestul de „shake” cu precizie, fără butoane suplimentare, şi are consum redus.

Software Design

/*
 *  DIGITAL DICE  - Arduino UNO
 *  — Shake → pornește DICE.WAV pe pin 9
 *  — Stop Shake → afișează două numere pe OLED
 *
 *  HARDWARE:
 *    LIS2DH12 (I²C)        | SDA/SCL = A4/A5
 *    SSD1306 OLED 128×64   | SDA/SCL = A4/A5
 *    Micro-SD (SPI)        | CS = 10
 *    Buzzer pasiv          | pin 9 (Timer1 PWM)
 */

#include <Wire.h> // permite comunicarea I2C
#include "SparkFun_LIS2DH12.h" // driverul pentru accelerometru
#include <SPI.h> // pentru SD (comunicarea SPI)
#include <SD.h> // pt citire/scrie fisiere pe cardul SD
#include <TMRpcm.h> //  redarea fisierelor WAV mono printr-un PWM pe buzzer
#include <U8g2lib.h> // desene pe OLED

#define SD_CS_PIN        10 // chip select pt modulul SD
#define SPEAKER_PIN       9 // buzzerul pasiv conectat pe pinul 9 (pwm timer)

#define SHAKE_THRESHOLD 2000     // daca diferenta de citiri depaseste pragul e considerat shake
#define SHAKE_TIMEOUT    300     // ms fără miscare = aruncare terminata
#define RESULT_HOLD     1500     // ms pastram rez pe ecran inainte sa schimbam iar la Ready

const char WAV_NAME[] = "DICE.WAV";

SPARKFUN_LIS2DH12 accel; // cream accelerometrul
TMRpcm             audio; // playerul care genereaza pwm ul pt a reda fisierul wav pe buzzer
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); // obiectul care controleaza ecranul

enum class Stare : uint8_t { GATA, SHAKING, REZULTAT }; // logicile posibile
Stare stareCurenta = Stare::GATA; // starea de start

unsigned long tStartStare; // mom cand s-a schimbat ultima data starea
int16_t lastX, lastY, lastZ; // valorile de pe fiecare axa a accelerometrului


// primeste un sir si un font, si scrie acel text centrat orizontal la linia y pe ecran
void drawCentered(const char* text, uint8_t y, const uint8_t* font) {
  u8g2.setFont(font); // font select
  int w = u8g2.getStrWidth(text); // latime txt
  u8g2.firstPage();  do { u8g2.drawStr((128 - w) / 2, y, text); } while (u8g2.nextPage()); // plaseaza incep in centrul ecranului
}

void setup() {
  // Serial.begin(9600); // pt debug
  if (!accel.begin()) while (1);   // start accelerometru si se blocheaza daca lipseste
  u8g2.begin(); // initializeaza ecranul
  drawCentered("READY!", 32, u8g2_font_ncenB14_tr); // afis ready pt inceput

  audio.speakerPin = SPEAKER_PIN; // spune playerului pe ce pin scoate semnal pwm catre buzzer
  audio.CSPin      = SD_CS_PIN; // ce pin e chipselect pt sd

  if (!SD.begin(SD_CS_PIN))  while (1);    // stop dacă SD fail
  if (!SD.exists(WAV_NAME))  while (1);    // stop dacă lipseste DICE.wav

  audio.setVolume(5);          // 0-6, 5 a fost optim
  audio.quality(1);            // pt sunet mai clar

  randomSeed(analogRead(A0)); // foloseste zgomot de pe pinul A0 ca baza pt generarea numerelor aleatoare
  lastX = accel.getRawX();  lastY = accel.getRawY();  lastZ = accel.getRawZ(); // citeste si salveaza primele valori de la accelro
}

void loop() {
  int16_t x = accel.getRawX(),  y = accel.getRawY(),  z = accel.getRawZ(); // cit acceleratia pe fiec axa
  int16_t dX = abs(x - lastX),  dY = abs(y - lastY),  dZ = abs(z - lastZ); // dif fata de ultima data
  lastX = x;  lastY = y;  lastZ = z; // actualizare valori

  switch (stareCurenta) { // algem ce stare urm in fct de starea curenta

  case Stare::GATA:
    if (dX > SHAKE_THRESHOLD || dY > SHAKE_THRESHOLD || dZ > SHAKE_THRESHOLD) { // am detectat miscarea
      stareCurenta = Stare::SHAKING; // deci trecem in starea shaking
      tStartStare  = millis();
      drawCentered("Shake!", 32, u8g2_font_ncenB14_tr); // afisam shake
      audio.play(WAV_NAME); // si redam sunetul DICE
    }
    break;

  case Stare::SHAKING:
    if (dX > SHAKE_THRESHOLD || dY > SHAKE_THRESHOLD || dZ > SHAKE_THRESHOLD) { // daca ince se scturua accelreo
      tStartStare = millis();                 // resetam timeoutul
    }
    if (millis() - tStartStare > SHAKE_TIMEOUT) {   // daca de la ultima miscare a trecut mai mult de shake timeout
      audio.stopPlayback();                   // oprim sunetul
      uint8_t d1 = random(1,7), d2 = random(1,7); // afisam 2 nre random
      char buf[6];  snprintf(buf, sizeof(buf), "%d %d", d1, d2);
      drawCentered(buf, 50, u8g2_font_logisoso32_tn);

      stareCurenta = Stare::REZULTAT; // trecem la starea de result
      tStartStare  = millis();
    }
    break;

  case Stare::REZULTAT:
    if (millis() - tStartStare > RESULT_HOLD) { // daca a trecut timpul de cat tb tinut rez pe ecran
      drawCentered("READY!", 32, u8g2_font_ncenB14_tr); // afisam din nou starea de ready pt urmatoare miscare si
      stareCurenta = Stare::GATA; // actualizam si starea
    }
    break;
  }

  delay(5);                 // pauza pt CPU
}

Biblioteci externe utilizate

comunicare I²C cu accelerometrul şi OLED

driver pentru citirea acceleraţiilor de la senzorul ‘‘LIS2DH12’’

suport hardware ‘‘SPI’’ pentru modulul SD

acces la sistemul de fişiere ‘‘FAT’’ de pe cardul micro-SD

redare fişiere ‘‘WAV’’ mono prin ‘‘PWM’’ (Timer1) pe buzzerul pasiv

comandă şi desen pe ‘‘OLED SSD1306’’ 128×64

Componente ale codului

Codul este împărţit în mai multe componente:

iniţializare şi citire continuă a accelerometrului ‘‘LIS2DH12’’

	‘‘GATA’’ – aşteaptă un shake şi afişează “READY!”
	‘‘SHAKING’’ – redă sunetul şi afişează “Shake!” cât timp mişcarea continuă
	‘‘REZULTAT’’ – generează două valori aleatorii, le afişează pe OLED, apoi revine la ‘‘GATA’’
	‘‘randomSeed(analogRead(A0))’’ pentru entropie la pornire
	‘‘random(1,7)’’ pentru fiecare zar
	‘‘drawCentered(text, y, font)’’ – calculează lăţimea textului şi îl plasează centrat

Fluxul aplicaţiei

→ La pornire, ‘‘setup()’’ iniţializează senzorul, OLED-ul, SD-ul, audio şi RNG-ul, apoi pe ecran apare “READY!”.

→ În ‘‘loop()’’ se citesc acceleraţiile şi se calculează delta faţă de ultima citire. Dacă ‘‘delta > SHAKE_THRESHOLD’’, starea trece în ‘‘SHAKING’’.

→ În ‘‘SHAKING’’ se porneşte ‘‘audio.play(“DICE.WAV”)’’ şi se afişează “Shake!”. La fiecare citire de mişcare, timer-ul este resetat.

→ După ‘‘SHAKE_TIMEOUT’’ ms fără mişcare, ‘‘audio.stopPlayback()’’. Se generează două numere (1–6) şi se afişează cu font mare; starea devine ‘‘REZULTAT’’.

→ După ‘‘RESULT_HOLD’’ ms, ecranul revine la “READY!” iar starea revine în ‘‘GATA’’, aşteptând un nou shake.

Detalii de implementare

→ Praguri configurabile:

→ Audio:

→ Desen pe OLED:

→ Random seed:

Optimizări

Used labs

Lab0: GPIO

Lab3: Timere & PWM

Lab5: SPI

Lab6: I²C (TWI)

Lab1: USART

Rezultate Obţinute

GitHub Repository

Demo Video

Shake

dice_res

Concluzii

Acest proiect a reprezentat o ocazie excelentă de a vedea în practică conceptele teoretice şi de a le pune în aplicare pentru a crea ceva distractiv.

Bibliografie/Resurse

Cirkit Studio

Pagina oficială Arduino

Adafruit Learning System

Export to PDF