پروژه: ساخت یک برج تاس هوشمند با آردوینو (دستیار بازی‌های رومیزی)

نویسنده

دسته بندی

فهرست مطالب

مقدمه

حتما تا به حال با برد گیم‌ها بازی کرده اید. همانطور که می­‌دانید در بسیاری از بازی‌های صفحه‌ای (برد گیم) از تاس استفاده می‌شود. بازی کردن با تاس همواره هیجان زیادی دارد، اما گاهی اوقات انداختن تاس با مشکلاتی همراه است. مثلا در صورت زیاد بودن تعداد بازیکنان امکان فراموش شدن نوبت وجود دارد. آیا تا به حال به حل این مشکل فکر کرده اید؟ شاید استفاده از الکترونیک بتواند ما را در حل این مشکل یاری کند! در این آموزش قصد داریم با استفاده از یک نمایشگر LCD TFT، آردوینو، ماژول صوت KY-037 و LED RGB یک دستیار بازی هوشمند بسازیم.

آنچه در این آموزش یاد می گیرید

  • برج تاس یا دایس تاور چیست و چگونه یک دایس تاور بسازیم؟
  • استفاده از سنسور صوت برای تشخیص افتادن تاس
  • استفاده از LED RGB برای نمایش نوبت و رقص نور
  • استفاده از LCD برای نمایش اطلاعات بیشتر و ساختن یک تاس مجازی

برج تاس یا دایس تاور (Dice Tower) چیست؟

در بسیاری از برد گیم‌ها از تاس استفاده می­‌شود. در دست گرفتن تاس و انداختن آن برای کسانی که انجام بازی‌های واقعی را به بازی­‌های کامپیوتری ترجیح می‌­دهند، بسیار حائز اهمیت است. اما در این مورد همیشه عدالت رعایت نمی‌­شود و امکان تقلب وجود دارد! همچنین بعضی از بازی­‌های صفحه‌ای دارای تعداد زیادی تاس می‌­باشد که انداختن آن­‌ها به صورت همزمان کمی دشوار است و امکان گم شدن تاس‌­ها همواره وجود دارد. استفاده از دایس تاور ایده خوبی برای حل این مشکلات است. دایس تاور یک برج کوچک است که تا‌س‌­ها از بالای آن وارد می‌شوند و هنگام سقوط به دیواره‌های داخلی برج برخورد کرده و پس از چرخش‌­های متعدد در محفظه­ پایین آن آرام می‌­گیرند.

بازی‌های رومیزی: جذابیت بیشتر، تقلب کمتر!

در تمام بازی‌ها امکان خطا و تقلب وجود دارد. بازی‌هایی که با تاس انجام می‌­شوند هم از این قاعده مستثنی نیستند. در برخی موارد نیز ممکن است هیجان و سرعت زیاد بازی باعث شود نوبت‌ها فراموش شود. بنابرین در این مواقع وجود یک داور می‌­تواند بسیار مفید باشد. با کمی خلاقیت و استفاده از الکترونیک می‌­توان دایس تاوری ساخت که بتواند این کار را برایمان انجام دهد و نوبت‌ها را با نورهای رنگی نمایش دهد.

شاید برای شما پیش آمده باشد که هنگام بازی متوجه شوید تاس شما گم شده است. حتما در این شرایط با خود فکر کرده اید ای کاش بشود از یک شیء دیگر (مثلا یک سنگ!) بجای تاس استفاده کرد. Dice Tower این امکان را به شما می‌دهد. حتی اگر هیچ شیء دیگری در دسترس نداشتید باز هم جای نگرانی نیست، چون دایس تاور ما یک تاس مجازی هم دارد.

لوازمی که به آن احتیاج دارید

قطعات مورد نیاز

برد آردوینو UNO R3 × 1
شیلد و ماژول نمایشگر لمسی 2.4 اینچ × 1
ماژول سنسور صوت - سنسور صدا KY-037 × 1
ال ای دی 4 پین رنگی × 3
جا باتری نیم قلمی 3 تایی درب دار × 1
باتری نیم قلمی آلکالاین پلاس چهارعددی کملیون × 3
مقاومت 330 اهم × 3

نرم افزار های مورد نیاز

Arduino IDE

سرهم کردن بدنه دایس تاور

برای ساخت دایس تاور هوشمند نیاز داریم ابتدا بدنه آن را بسازیم. جنس بدنه می­تواند از چوب MDF و یا پلکسی به ضخامت 2.8 میلیمتر باشد. برای ساخت آن فایل نقشه برش لیزر را از اینجا دانلود کنید.

این دایس تاور دارای 58 قطعه می­باشد که باید مطابق ویدئوی زیر با چسب قطره ای به یکدیگر متصل شوند.
نقشه سه بعدی زیر نیز می تواند به شما کمک کند:

توجه

تمام قطعات نقشه فوق مورد استفاده قرار نمی­‌گیرند. برخی از قطعات اضافه هستند.

سیم بندی

برای راه اندازی LCD کافیست آن را روی برد آردوینو قرار دهیم (به محل قرارگیری پایه ها و جهت قرارگیری ماژول LCD دقت کنید). برای راه اندازی ماژول صوت KY-037 کافیست تغذیه ماژول را به پایه های 5v و GND آردوینو وصل کرده و پایه خروجی دیجیتال آن را به پایه D13 آردوینو وصل کنیم. برای روشن کردن LED RGB نیز، کاتد آن را به پایه GND آردوینو و پایه‌های قرمز، سبز و آبی آن را به ترتیب به پایه­‌های D5،  D6 و D7 آردوینو متصل می‌­کنیم.

استفاده از سنسور صوت برای تشخیص افتادن تاس

برای تشخیص افتادن تاس می توان از سنسور ضربه و یا سنسور صدا استفاده کرد. با توجه به اینکه ضربه ناشی از سقوط تاس معمولا کمتر از حدی است که بتواند سنسور ضربه را در زیر صفحه های داخلی دایس تاور تحریک کند و همچنین به دلیل آسیب پذیر بودن سنسور ضربه نمی توان از آن در مسیر تاس استفاده کرد، بنابراین استفاده از سنسور صوت گزینه مناسب تری است. راه اندازی ماژول صوت KY-037 بسیار ساده است. برای این کار کافیست پایه های آن مطابق تصویر 4 به آردوینو متصل شود. از آنجا که در این پروژه تنها 1 نوع صدا برای تشخیص وجود دارد، تنها از پایه دیجیتال ماژول صوت استفاده شده است. برای تشخیص صدای تاس کافیست پایه D13 آردوینو را که به پایه خروجی دیجیتال ماژول صوت وصل شده است با دستور digitalRead() بخوانیم. پایه دیجیتال این ماژول pullup شده است و در صورت تحریک شدن ولتاژ آن صفر می­شود. با چرخاندن پتانسیومتر قرارگرفته روی برد ماژول، می­توان آستانه تحریک سنسور را تغییر داد. به منظور تنظیم دقیق این سنسور متناسب با صدای افتادن تاس، مراحل زیر را دنبال کنید:
  1. ابتدا کد زیر را روی آردوینو آپلود کنید:
void setup() {
Serial.begin(9600); // setup serial
pinMode(13,INPUT);
}
void loop() {
Serial.println(digitalRead(13));
delay(100);
}

2. پنجره ی سریال (Serial Monitor) را باز کنید. سپس تاس را به تناوب از بالای دایس تاور رها کنید. همزمان پتانسیومتر مولتی ترن را آن قدر بچرخانید تا عدد نمایش داده شده صفر شود. چندین بار تاس را بیندازید تا مطمئن شوید ماژول به درستی تنظیم شده است و همچنین آن را به گونه ای تنظیم کنید که با صداهای دیگری که در محیط وجود دارد تحریک نشود تا تشخیص تاس به درستی اتفاق بیوفتد.

برای دریافت اطلاعات بیشتر در مورد نحوه استفاده از ماژول KY-037 به اینجا مراجعه کنید.

استفاده از LED RGB برای نمایش نوبت و رقص نور

LED RGB انواع مختلفی دارد. در این پروژه از نوع 4 پین کاتد مشترک استفاده شده است. برای روشن کردن این LED باید پایه کاتد آن به GND متصل شود و با HIGH کردن ولتاژ هر یک از پایه­های دیگر یکی از رنگ­های قرمز، سبز و یا آبی روشن می­شود. همچنین اگر ولتاژ هر سه پایه با هم HIGH شود، LED به رنگ سفید در می‌­آید.
نکته

می­توان از سیگنال PWM و دستور analogWrite در آردوینو برای تنظیم نور هر یک از پایه­ها استفاده کرد، به این وسیله می­توان طیف مختلفی از رنگ­ها را با ترکیب نورهای قرمز، سبز و آبی نمایش داد.

با استفاده از کد زیر می­توان LED RGB را به تناوب به رنگ های قرمز، سبز، آبی و سفید درآورد:

#define rLED 10
#define rLED 11
#define rLED 12
Void setup(){
 pinMode(rLED,OUTPUT);
 pinMode(gLED,OUTPUT);
 pinMode(bLED,OUTPUT);
void loop(){
//LED equalizer
  digitalWrite(rLED,HIGH);digitalWrite(gLED,LOW);digitalWrite(bLED,LOW);
  delay(100);
  digitalWrite(rLED,LOW);digitalWrite(gLED,HIGH);digitalWrite(bLED,LOW);
  delay(100);
  digitalWrite(rLED,LOW);digitalWrite(gLED,LOW);digitalWrite(bLED,HIGH);
 delay(100);
 digitalWrite(rLED,HIGH);digitalWrite(gLED,HIGH);digitalWrite(bLED,HIGH);
  delay(100);
}

استفاده از LCD برای نمایش اطلاعات بیشتر و ساختن تاس مجازی

برای اینکه بتوانیم نوبت هر بازیکن را با رنگ LED RGB نشان دهیم، نیاز داریم تعداد بازیکن­ها و رنگ مربوط به هر یک را بدانیم. به این منظور می­توانیم از یک TFT LCD به همراه تاچ اسکرین استفاده کنیم. یکی از راحت ترین و مناسب ترین گزینه­‌ها، استفاده از شیلد LCD 2.4 اینچی Adafruit می­‌باشد.

روش راه­اندازی و کار با این LCD به صورت کامل در این لینک موجود است.

در کد زیر هنگام راه اندازی LCD، ابتدا تعداد بازیکنان پرسیده می­شود و کاربر می­تواند با لمس هر یک از تاس های 1 تا 4 تعداد بازیکنان را انتخاب کند. پس از آن بسته به تعداد بازیکنان رنگ مورد نظر هر بازیکن پرسیده می­‌شود و هر یک از بازکنان باید یکی از رنگ­های سفید، قرمز، سبز یا آبی را انتخاب کنند. در انتها یک بار ترتیب رنگ ها نمایش داده شده و سپس بازی آغاز می‌­شود.

*/
  Smart Dice Tower – main code
  modified on 25 May 2019
  by Arash Abarghooei
  https://electropeak.com/learn/
*/
#include <Adafruit_GFX.h>
#include <Adafruit_TFTLCD.h>
#include <TouchScreen.h>
 
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4

#define YP A3  
#define XM A2  
#define YM 9   
#define XP 8 

#define rLED 5
#define gLED 6
#define bLED 7
#define ss 13

TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

//find these number through “SDT_TS_calibration.ino” code
#define TSx_dices         600
#define Tsy_1st_dice      825
#define Tsy_2nd_dice      625
#define Tsy_3th_dice      425
#define Tsy_4th_dice      225
#define Tsy_virtual_dice  760

#define BLACK       0x0000
#define BLUE        0x001F
#define RED         0xF800
#define GREEN       0x07E0
#define YELLOW      0xFFE0
#define WHITE       0xFFFF
 #define PURPLE      0x780F

Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

int n=1,a=1,t=0,count=0;
int c[4];
bool pres=false,vd=false;
 
void setup() {
 
  Serial.begin(9600);
  Serial.println(F(“TFT LCD test”));

 tft.reset();
 
#ifdef USE_ADAFRUIT_SHIELD_PINOUT
  Serial.println(F(“Using Adafruit 2.4\” TFT Arduino Shield Pinout”));
#else
  Serial.println(F(“Using Adafruit 2.4\” TFT Breakout Board Pinout”));
#endif
  Serial.print(“TFT size is “);
  Serial.print(tft.width());
  Serial.print(“x");
  Serial.println(tft.height());
 
  tft.reset();
 delay(1000);
  uint16_t identifier = tft.readID();
 
  if (identifier == 0x9325){
    Serial.println(F(“Found ILI9325 LCD driver”));
  } else if (identifier == 0x9328){
    Serial.println(F(“Found ILI9328 LCD driver”));
  } else if (identifier == 0x7575){
    Serial.println(F(“Found HX8347G LCD driver”));
  } else if (identifier == 0x9341){
    Serial.println(F(“Found ILI9341 LCD driver”));
  } else if (identifier == 0x8357){
    Serial.println(F(“Found HX8357D LCD driver”));
  } else {
    Serial.print(F(“Unknown LCD driver chip: “));
    Serial.println(identifier, HEX);
    Serial.println(F(“If using the Adafruit 2.4\” TFT Arduino shield, the line:”));
    Serial.println(F(“  #define USE_ADAFRUIT_SHIELD_PINOUT”));
    Serial.println(F(“should appear in the library header (Adafruit_TFT.h).”));
    Serial.println(F(“If using the breakout board, it should NOT be #defined!”));
    Serial.println(F(“Also if using the breakout, double-check that all wiring”));
    Serial.println(F(“matches the tutorial.”));
    return;
  }

  pinMode(13, OUTPUT);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  
  tft.begin(identifier);
 
  Serial.println(F(“Benchmark                Time (microseconds)”));
//-------------------------------------------------------------------------------------
//LCD starting:
  tft.fillScreen(PURPLE);
  tft.setTextColor(YELLOW);
  tft.setRotation(1);
  tft.setCursor(30, 100);
  tft.setTextSize(4);
  tft.println(“Electropeak”);
  tft.setCursor(60, 150);
  tft.setTextSize(2);
  tft.println(“Smart Dice Tower”);
  delay(1000);
//-------------------------------------------------------------------------------------
//asking number of player
  tft.fillScreen(PURPLE);
  tft.setRotation(1);
  tft.setCursor(5, 50);
  tft.setTextSize(3);
  tft.println(“number of player?”)
  delay(200);
  tft.fillRoundRect(20,110,60,60,5,WHITE);
  tft.fillCircle(50,140,10,RED);
  
  tft.fillRoundRect(90,110,60,60,5,WHITE);
  tft.fillCircle(120,125,8,BLACK);
  tft.fillCircle(120,155,8,BLACK);

  tft.fillRoundRect(160,110,60,60,5,WHITE);
  tft.fillCircle(170,120,8,BLACK);
  tft.fillCircle(190,140,8,BLACK);
  tft.fillCircle(210,160,8,BLACK);

  tft.fillRoundRect(230,110,60,60,5,WHITE);
  tft.fillCircle(245,125,8,BLACK);
  tft.fillCircle(275,125,8,BLACK);
  tft.fillCircle(245,155,8,BLACK);
  tft.fillCircle(275,155,8,BLACK);

while(!pres){
  digitalWrite(13, HIGH);
  TSPoint p = ts.getPoint();
  digitalWrite(13, LOW);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  if(p.z > ts.pressureThreshhold){
  pres= true;
 // Serial.print(p.x); Serial.print(“ “); Serial.println(p.y);
   
    if (p.x>TSx_dices-100 && p.x<TSx_dices+100){
      if(p.y>Tsy_1st_dice-75 && p.y<Tsy_1st_dice+75)
      n=1;
      else if(p.y>Tsy_2nd_dice-75 && p.y<Tsy_2nd_dice+75)
      n=2;
      else if(p.y>Tsy_3th_dice-75 && p.y<Tsy_3th_dice+75)
      n=3;
      else if(p.y>Tsy_4th_dice-75 && p.y<Tsy_4th_dice+75)
      n=4;
  }
}
}
//-------------------------------------------------------------------------------------
//asking players colors
for(int j=0;j<n;j++){
  pres=false;
  tft.fillScreen(PURPLE);
  tft.setRotation(1);
  tft.setCursor(20, 50);
  tft.setTextSize(3);
  tft.print(“player”);tft.print(j+1);tft.print(“ color?”);
  delay(200);
  tft.fillRoundRect(20,110,60,60,5,WHITE);
  tft.fillCircle(50,140,10,BLACK);
  
  tft.fillRoundRect(90,110,60,60,5,RED);
  tft.fillCircle(120,140,10,BLACK);

  tft.fillRoundRect(160,110,60,60,5,GREEN);
  tft.fillCircle(190,140,10,BLACK);

  tft.fillRoundRect(230,110,60,60,5,BLUE);
  tft.fillCircle(260,140,10,BLACK);

  while(!pres){
    digitalWrite(13, HIGH);
    TSPoint p = ts.getPoint();
    digitalWrite(13, LOW);
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);
    if(p.z > ts.pressureThreshhold){
      pres= true;
        if (p.x>TSx_dices-100 && p.x<TSx_dices+100){
        if(p.y>Tsy_1st_dice-75 && p.y<Tsy_1st_dice+75)
        c[j]=WHITE;
        else if(p.y>Tsy_2nd_dice-75 && p.y<Tsy_2nd_dice+75)
        c[j]=RED;
        else if(p.y>Tsy_3th_dice-75 && p.y<Tsy_3th_dice+75)
        c[j]=GREEN;
        else if(p.y>Tsy_4th_dice-75 && p.y<Tsy_4th_dice+75)
        c[j]=BLUE;
    }
  }
}
}
  for(int i=0;i<n;i++){
    tft.fillScreen(c[i]);
    delay(200);
}
}

پس از آنکه تعداد بازیکنان و رنگ­های آن ها مشخص شد قسمت اصلی برنامه آغاز می­شود. در این قسمت با استفاده از ماژول صوتی KY-037 تعداد دفعاتی که تاس ریخته شده است محسابه می­شود و در متغیر count ذخیره می­شود. با محاسبه باقیمانده­ی این عدد بر تعداد بازیکنان مشخص می­شود که نوبت کدام بازیکن است که باید تاس را بیندازد. سپس صفحه LCD به رنگ آن بازیکن درمی­آید و نوبت را نمایش می­دهد. همچنین RGB LED نیز به رنگ آن بازیکن در می­آید و تا وقتی که بازیکن تاس را نینداخته رنگ آن ثابت می­ماند. به منظور زیباتر کردن برج، هر بار که تاس ریخته می­شود RGB LED چندبار به رنگ­های مختلف درمی­آید و همچنین روی LCD انیمیشن تاس به صورت رندوم نمایش داده می­شود.

در قسمت راست تصویر LCD نیز تعداد دفعاتی که تاس ریخته شده است نمایش داده می‌­شود.


int j=1;
void loop() {
  //calculation of players turn
  t=count%n;
  tft.fillScreen(c[t]);
  tft.setTextColor(YELLOW);
    tft.setRotation(1);
    tft.setCursor(0, 50);
    tft.setTextSize(3);
    tft.print("it's player");tft.print(t+1);tft.print(" turn");
  
  //showing number of dice fallen
  tft.fillRoundRect(225,110,60,60,5,BLACK);
    tft.setTextColor(YELLOW);
    tft.setRotation(1);
    tft.setTextSize(2);
    tft.setCursor(245, 135);
    tft.print(count);
//sensing with microphone
while(j==0){
 pinMode(13,INPUT);
 int a=digitalRead(ss);
  if(a==0)j=1;
//showing the color of player whose turn is
  if(c[t]==RED){
  digitalWrite(rLED,HIGH);digitalWrite(gLED,LOW);digitalWrite(bLED,LOW);}
  
  if(c[t]==BLUE){
  digitalWrite(rLED,LOW);digitalWrite(gLED,LOW);digitalWrite(bLED,HIGH);}
  
  if(c[t]==GREEN){
  digitalWrite(rLED,LOW);digitalWrite(gLED,HIGH);digitalWrite(bLED,LOW);}
  
  if(t==WHITE){
  digitalWrite(rLED,HIGH);digitalWrite(gLED,HIGH);digitalWrite(bLED,HIGH);}
  }
  //LED equalizer
  if(j==1){
  for(int i=0;i<6;i==){
  digitalWrite(rLED,LOW);digitalWrite(gLED,HIGH);digitalWrite(bLED,HIGH);
  delay(100);
  digitalWrite(rLED,HIGH);digitalWrite(gLED,LOW);digitalWrite(bLED,HIGH);
  delay(100);
  digitalWrite(rLED,HIGH);digitalWrite(gLED,HIGH);digitalWrite(bLED,LOW);
  delay(100);
  digitalWrite(rLED,LOW);digitalWrite(gLED,LOW);digitalWrite(bLED,LOW);
  delay(100);
  //LCD dice animation
  tft.setTextColor(YELLOW);
    tft.setRotation(1);
    tft.setCursor(20, 200);
    tft.setTextSize(2);
    tft.print("Dice is falling...”);
  tft.fillRoundRect(130,110,60,60,5,WHITE);
  delay(200);
  Dice(random(6)+1);
  }
  a=1;
  j=0;
  } 
count++;
}
//-------------------------------------------------------------------------------------
//dice drawing function
void Dice(int d){
  if(d==1){
  tft.fillRoundRect(130,110,60,60,5,WHITE);
  tft.drawRoundRect(130,110,60,60,5,BLACK);
  tft.fillCircle(160,140,10,RED);
}
  else if(d==2){
  tft.fillRoundRect(130,110,60,60,5,WHITE);
  tft.drawRoundRect(130,110,60,60,5,BLACK);
  tft.fillCircle(160,125,8,BLACK);
  tft.fillCircle(160,155,8,BLACK);
  }
  else if(d==3){
  tft.fillRoundRect(130,110,60,60,5,WHITE);
  tft.drawRoundRect(130,110,60,60,5,BLACK);
  tft.fillCircle(140,120,8,BLACK);
  tft.fillCircle(160,140,8,BLACK);
  tft.fillCircle(180,160,8,BLACK);
}
  else if(d==4){
  tft.fillRoundRect(130,110,60,60,5,WHITE);
  tft.drawRoundRect(130,110,60,60,5,BLACK);
  tft.fillCircle(145,125,8,BLACK);
  tft.fillCircle(175,125,8,BLACK);
  tft.fillCircle(145,155,8,BLACK);
  tft.fillCircle(175,155,8,BLACK);
  }
  else if(d==5){
  tft.fillRoundRect(130,110,60,60,5,WHITE);
  tft.drawRoundRect(130,110,60,60,5,BLACK);
  tft.fillCircle(145,125,8,BLACK);
  tft.fillCircle(175,125,8,BLACK);
  tft.fillCircle(145,155,8,BLACK);
  tft.fillCircle(175,155,8,BLACK);
  tft.fillCircle(160,140,8,BLACK);
  }
  else if(d==6){
  tft.fillRoundRect(130,110,60,60,5,WHITE);
  tft.drawRoundRect(130,110,60,60,5,BLACK);
  tft.fillCircle(145,125,6,BLACK);
  tft.fillCircle(175,125,6,BLACK);
  tft.fillCircle(145,140,6,BLACK);
  tft.fillCircle(175,140,6,BLACK);
  tft.fillCircle(145,155,6,BLACK);
  tft.fillCircle(175,155,6,BLACK);
}
}

در این کد به وسیله تابع Dice(number) تاس با عدد مورد نظر نمایش داده می­شود.

برای جذاب­تر شدن بازی، به جای استفاده از رنگ ها برای نمایش نوبت می­توانید از تصاویر استفاده کنید. به این منظور تصاویر دلخواه خود را روی کارت SD micro ذخیره کرده و آن را درون درگاه نمایشگر قرار دهید. با استفاده از کد زیر می­توانید تصاویر را از درون کارت حافظه فراخوانی کنید.

void bmpDraw(String filename, int x, int y) {
  File     bmpFile;
  int      bmpWidth, bmpHeight;   // W+H in pixels
  uint8_t  bmpDepth;              // Bit depth (currently must be 24)
  uint32_t bmpImageoffset;        // Start of image data in file
  uint32_t rowSize;               // Not always = bmpWidth; may have padding
  uint8_t  sdbuffer[3 * BUFFPIXEL]; // pixel in buffer (R+G+B per pixel)
  uint16_t lcdbuffer[BUFFPIXEL];  // pixel out buffer (16-bit per pixel)
  uint8_t  buffidx = sizeof(sdbuffer); // Current position in sdbuffer
  boolean  goodBmp = false;       // Set to true on valid header parse
  boolean  flip    = true;        // BMP is stored bottom-to-top
  int      w, h, row, col;
  uint8_t  r, g, b;
  uint32_t pos = 0, startTime = millis();
  uint8_t  lcdidx = 0;
  boolean  first = true;
  if ((x >= tft.width()) || (y >= tft.height())) return;
  Serial.println();
  Serial.print(F("Loading image  ‘ ‘’));
  Serial.print(filename);
  Serial.println(‘\’’);
  // Open requested file on SD card
  if ((bmpFile = SD.open(filename)) == NULL){
    Serial.println(F("File not found”));
    return;
  }
  // Parse BMP header
  if (read16(bmpFile) == 0x4D42) { // BMP signature
    Serial.println(F("File size: ")); Serial.println(read32(bmpFile));
    (void)read32(bmpFile); // Read & ignore creator bytes
    bmpImageoffset = read32(bmpFile); // Start of image data
    Serial.print(F("Image Offset: ")); Serial.println(bmpImageoffset, DEC);
    // Read DIB header
    Serial.print(F("Header size: ")); Serial.println(read32(bmpFile));
    bmpWidth  = read32(bmpFile);
    bmpHeight = read32(bmpFile);
    if (read16(bmpFile) == 1) { // # planes -- must be '1'
      bmpDepth = read16(bmpFile); // bits per pixel
      Serial.print(F("Bit Depth: ")); Serial.println(bmpDepth);
      if ((bmpDepth == 24) && (read32(bmpFile) == 0)) { // 0 = uncompressed
        goodBmp = true; // Supported BMP format -- proceed!
        Serial.print(F("Image size:”));
        Serial.print(bmpWidth);
        Serial.print('x’);
        Serial.println(bmpHeight);
        // BMP rows are padded (if needed) to 4-byte boundary
        rowSize = (bmpWidth * 3 + 3) & ~3;
        // If bmpHeight is negative, image is in top-down order.
        // This is not canon but has been observed in the wild.
        if (bmpHeight < 0){
          bmpHeight = -bmpHeight;
          flip      = false;
        }
        // Crop area to be loaded
        w = bmpWidth;
        h = bmpHeight;
        if ((x + w - 1) >= tft.width())  w = tft.width()  - x;
        if ((y + h - 1) >= tft.height()) h = tft.height() - y;

        // Set TFT address window to clipped image bounds
        tft.setAddrWindow(x, y, x + w - 1, y + h – 1);
        for (row = 0; row < h; row++) { // For each scanline...
          // Seek to start of scan line.  It might seem labor-
          // intensive to be doing this on every line, but this
          // method covers a lot of gritty details like cropping
          // and scanline padding.  Also, the seek only takes
          // place if the file position actually needs to change
 //   (avoids a lot of cluster math in SD library).
          if (flip) // Bitmap is stored bottom-to-top order (normal BMP)
            pos = bmpImageoffset + (bmpHeight - 1 - row) * rowSize;
          else     // Bitmap is stored top-to-bottom
            pos = bmpImageoffset + row * rowSize;
          if (bmpFile.position() != pos) { // Need seek?
            bmpFile.seek(pos);
            buffidx = sizeof(sdbuffer); // Force buffer reload
          }
          for (col = 0; col < w; col++) { // For each column...
             // Time to read more pixel data?
            if (buffidx >= sizeof(sdbuffer)) { // Indeed
              	// Push LCD buffer to the display first
              if (lcdidx > 0) {
                tft.pushColors(lcdbuffer, lcdidx, first);
                lcdidx = 0;
                first  = false;
              }
              bmpFile.read(sdbuffer, sizeof(sdbuffer));
              buffidx = 0; // Set index to beginning
            }
            // Convert pixel from BMP to TFT format
            b = sdbuffer[buffidx++];
            g = sdbuffer[buffidx++];
            r = sdbuffer[buffidx++];
            lcdbuffer[lcdidx++] = tft.color565(r, g, b);
          }// end pixel
         }// end scanline
         // Write any remaining data to LCD
        if (lcdidx > 0){
          tft.pushColors(lcdbuffer, lcdidx, first);
        }
        Serial.print(F("Loaded in “));
        Serial.print(millis() – startTime);
        Serial.println(" ms”);
     }// end goodBmp
    {
  {
  bmpFile.close();
  if (!goodBmp) Serial.println(F("BMP format not recognized.”));
}
// These read 16- and 32-bit types from the SD card file.
// BMP data is stored little-endian, Arduino is little-endian too.
// May need to reverse subscript order if porting elsewhere.
uint16_t read16(File f) {
  uint16_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
 ((uint8_t *)&result)[1] = f.read(); // MSB
  return result;
}
uint32_t read32(File f) {
  uint32_t result;
  ((uint8_t *)&result)[0] = f.read(); // LSB
  ((uint8_t *)&result)[1] = f.read();
  ((uint8_t *)&result)[2] = f.read();
  ((uint8_t *)&result)[3] = f.read(); // MSB
  return result;
}

برای استفاده از این کد باید تابع فوق را به انتهای کد اصلی اضافه کنید و سپس با جایگزینی دستور bmpDraw(c[t], 0, 0); به جای دستور tft.fillScreen(c[t]); تصاویر را بجای رنگ ها فراخوانی کنید. در این صورت در هر نوبت به جای نمایش رنگ، تصویر مربوط به هر بازیکن نمایش داده شود.

در این حالت متغیر c[t] باید از نوع string با حداقل طول 10 تعریف شود که حاوی نام تصاویر ذخیره شده مربوط به هر بازیکن در کارت حافظه می­باشد.

تصاویر مورد نظر باید همگی به ابعاد 320 در 240 پیکسل و با فرمت bitmap 24bit باشد. به طور مثال می­توانید تصاویر موجود این فایل را درون کارت micro SD کپی کنید.

افزودن تاس مجازی و آپلود کد نهایی

حال که می­توانیم تاس­های مختلف را روی صفحه نمایش نشان دهیم، می­توانیم یک تاس مجازی نیز داشته باشیم. برای این کار کافیست کد زیر را در ابتدای void loop() وارد کنیم:

//Virtual Dice option
  if(vd){
    tft.fillRoundRect(35,110,60,60,5,YELLOW);
    tft.setTextColor(BLACK);
    tft.setRotation(1);
    tft.setTextSize(1);
    tft.setCursor(45, 130);
    tft.print("Virtual”);
    tft.setCursor(55, 145);
    tft.print("Dice”);}
  else{
    tft.fillRoundRect(35,110,60,60,5,BLACK);
    tft.setTextColor(YELLOW);
    tft.setRotation(1);
    tft.setTextSize(1);
    tft.setCursor(45, 130);
    tft.print("Virtual”);
    tft.setCursor(55, 145);
    tft.print("Dice”);
   }
  //sensing with microphone 
while(j==0){
  pinMode(13,INPUT);
  int a=digitalRead(ss);
  //Serial.println(a);
   if(a==0)j=1;
   pinMode(13,OUTPUT);
   digitalWrite(13, HIGH);
    TSPoint p = ts.getPoint();
    digitalWrite(13, LOW);
    pinMode(XM, OUTPUT);
    pinMode(YP, OUTPUT);
    if(p.z > ts.pressureThreshhold){

    if(p.x>TSx_dices-100 && p.x<TSx_dices+100 && p.y>TSy_virtual_dice-90 && p.y<TSy_virtual_dice+90){vd=!vd; pres=true;}
    }
    //Virtual Dice option
    if(pres){
      if(vd){
    tft.fillRoundRect(35,110,60,60,5,YELLOW);
    tft.setTextColor(BLACK);
 //   tft.setRotation(1); 
 //   tft.setTextSize(1);
    tft.setCursor(45, 130);
    tft.print("Virtual");
    tft.setCursor(55, 145);
    tft.print("Dice");}
  else{
    tft.fillRoundRect(35,110,60,60,5,BLACK);
    tft.setTextColor(YELLOW);
 //   tft.setRotation(1); 
 //   tft.setTextSize(1);
    tft.setCursor(45, 130);
    tft.print("Virtual");
    tft.setCursor(55, 145);
    tft.print("Dice");
    }
pres=false;
}

با اضافه کردن این کد به void loop() سمت چپ LCD یک کلید ظاهر می­شود که روی آن کلمه virtual dice نوشته شده است. با لمس کردن این کلید رنگ آن روشن و حالت تاس مجازی فعال می­شود. در این حالت تاس نشان داده شده در وسط LCD به صورت رندوم تغییر می­کند و برای مدت 2 ثانیه ثابت می­ماند. برای ایجاد این وقفه باید کد بخش LCD dice animation با کد زیر جایگزین شود.

//LCD dice animation
  tft.setTextColor(YELLOW);
    tft.setRotation(1);
    tft.setCursor(20, 200);
    tft.setTextSize(2);
    tft.print("Dice is falling...”);
  tft.fillRoundRect(130,110,60,60,5,WHITE);
  delay(200);
  Dice(random(6)+1);
  }
  if(vd){
    tft.fillRect(10,180,250,60,c[t]);
    tft.setCursor(20, 200);
    tft.print("Dice fell!”);
    delay(2000);
    }
  a=1;
  j=0;
}
Note

کد کامل دایس تاور هوشمند را می توانید از اینجا دانلود و سپس روی برد آردوینو آپلود کنید. اما پیش از آن لازم است صفحه تاچ LCD را کالیبره کنید.

کالیبره کردن صفحه نمایشگر لمسی

برای کالیبره کردن صفحه نمایشگر لمسی می­توانید از کد زیر استفاده کنید.

در این کد نقاطی که موقعیت آن ها برای استفاده از صفحه تاچ در برنامه اصلی مهم هستند، نشان داده شده و از کاربر خواسته می­شود تا آن ها را لمس کند. سپس مختصات موقعیت مشخص شده نمایش داده می­شود تا در کد اصلی جایگزین شود.

  Smart Dice Tower - touchscreen calibration code
  modified on 25 May 2019
  by Arash Abarghooei
  https://electropeak.com/learn/
*/
 
#include 
#include 
#include 
 
#define LCD_CS A3
#define LCD_CD A2
#define LCD_WR A1
#define LCD_RD A0
#define LCD_RESET A4

#define YP A3  
#define XM A2  
#define YM 9   
#define XP 8 

#define rLED 10
#define gLED 11
#define bLED 12
#define ss 13

TouchScreen ts = TouchScreen(XP, YP, XM, YM, 300);

int TSx_dices;
int TSy_1st_dice;
int TSy_2nd_dice;
int TSy_3th_dice;
int TSy_4th_dice;
int TSy_virtual_dice;

#define BLACK       0x0000
#define RED         0xF800
#define WHITE       0xFFFF

Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

bool pres=false;
 
void setup(){
 
  Serial.begin(9600);
  Serial.println(F("TFT LCD test”));

 tft.reset();
 
#ifdef USE_ADAFRUIT_SHIELD_PINOUT
  Serial.println(F("Using Adafruit 2.4\" TFT Arduino Shield Pinout”));
#else
  Serial.println(F("Using Adafruit 2.4\" TFT Breakout Board Pinout”));
#endif
  Serial.print("TFT size is “);
  Serial.print(tft.width());
  Serial.print("x”);
  Serial.println(tft.height());
 
  tft.reset();
 delay(1000);
  uint16_t identifier = tft.readID();
 
  if (identifier == 0x9325){
    Serial.println(F("Found ILI9325 LCD driver”));
 } else if (identifier == 0x9328){
    Serial.println(F("Found ILI9328 LCD driver”));
  } else if (identifier == 0x7575){
    Serial.println(F("Found HX8347G LCD driver”));
  } else if (identifier == 0x9341){
    Serial.println(F("Found ILI9341 LCD driver”));
  } else if (identifier == 0x8357) {
    Serial.println(F("Found HX8357D LCD driver”));
  } else {
    Serial.print(F("Unknown LCD driver chip: “));
    Serial.println(identifier, HEX);
    Serial.println(F("If using the Adafruit 2.4\" TFT Arduino shield, the line:”));
    Serial.println(F("  #define USE_ADAFRUIT_SHIELD_PINOUT”));
    Serial.println(F("should appear in the library header (Adafruit_TFT.h).”));
    Serial.println(F("If using the breakout board, it should NOT be #defined!”));
    Serial.println(F("Also if using the breakout, double-check that all wiring”));
    Serial.println(F("matches the tutorial.”));
    return;
}

  pinMode(13, OUTPUT);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  
  tft.begin(identifier);
 
  Serial.println(F("Benchmark                Time (microseconds)”));

  tft.fillScreen(BLACK);
  tft.setRotation(1);
  tft.setCursor(0, 50);
  tft.setTextSize(2);
  tft.println("Smart Dice Tower touchscreen calibration”);
  delay(1000);
  Serial.println("touchscreen calibration started”);
  tft.println("please open Serial monitor”);
  delay(1000);
  tft.setTextColor(RED);
  tft.setCursor(0, 100);
  tft.println("please touch the red areas”);
  Serial.println("please touch the red areas”);

for (uint16_t a=0; a<5; a++){
    tft.drawFastHLine(0, 140+a, 320, RED);}
while(!pres){
  digitalWrite(13, HIGH);
  TSPoint p = ts.getPoint();
  digitalWrite(13, LOW);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  if(p.z > ts.pressureThreshhold){
  pres= true;
  Serial.print("TSx_dices is  “);
  Serial.println(p.x);
  TSx_dices=p.x; 
}
}
for (uint16_t a=0; a<5; a++){
tft.drawFastHLine(0, 140+a, 320, BLACK);}
delay(500);
tft.fillCircle(50,140,10,RED);
pres=false;
while(!pres){
  digitalWrite(13, HIGH);
  TSPoint p = ts.getPoint();
  digitalWrite(13, LOW);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  if(p.z > ts.pressureThreshhold){
  pres= true;
  Serial.print("TSy_1st_dice is “);
  Serial.println(p.y);
  TSy_1st_dice=p.y; 
}
}
tft.fillCircle(50,140,10,BLACK);
delay(500);
tft.fillCircle(120,140,10,RED);
pres=false;
while(!pres){
  digitalWrite(13, HIGH);
  TSPoint p = ts.getPoint();
  digitalWrite(13, LOW);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  if(p.z > ts.pressureThreshhold){
  pres= true;
  Serial.print("TSy_2nd_dice is  “);
  Serial.println(p.y);
  TSy_2nd_dice=p.y; 
  }
}
tft.fillCircle(120,140,10,BLACK);
delay(500);
tft.fillCircle(190,140,10,RED);
pres=false;
while(!pres){
  digitalWrite(13, HIGH);
  TSPoint p = ts.getPoint();
  digitalWrite(13, LOW);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  if(p.z > ts.pressureThreshhold){
  pres= true;
  Serial.print("TSy_3th_dice is  “);
  Serial.println(p.y);
  TSy_3th_dice=p.y; 
  }
}
tft.fillCircle(190,140,10,BLACK);
delay(500);
tft.fillCircle(260,140,10,RED);
pres=false;
while(!pres){
  digitalWrite(13, HIGH);
  TSPoint p = ts.getPoint();
  digitalWrite(13, LOW);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  if(p.z > ts.pressureThreshhold){
  pres= true;
  Serial.print("TSy_4th_dice is  “);
  Serial.println(p.y);
  TSy_4th_dice=p.y; 
}
}
tft.fillCircle(260,140,10,BLACK);
delay(500);
tft.fillCircle(65,140,10,RED);
pres=false;
while(!pres){
  digitalWrite(13, HIGH);
  TSPoint p = ts.getPoint();
  digitalWrite(13, LOW);
  pinMode(XM, OUTPUT);
  pinMode(YP, OUTPUT);
  if(p.z > ts.pressureThreshhold){
  pres= true;
  Serial.print("TSy_virtal_dice is  “);
  Serial.println(p.y);
  TSy_virtual_dice=p.y; 
}
}
tft.fillCircle(65,140,10,BLACK);
delay(500);
tft.setTextSize(3);
tft.setTextColor(WHITE);
tft.println("please waite....”);
delay(1000);
tft.fillScreen(BLACK);
tft.setCursor(0, 20);
tft.setTextSize(3);
tft.println("DONE!”);
tft.setTextSize(3);
tft.println("put these data to main code”);
tft.setTextSize(2);
tft.print("TSx_dices is: “);
tft.println(TSx_dices);

tft.print("TSy_1st_dice is: “);
tft.println(TSy_1st_dice);

tft.print("TSy_2nd_dice is: “);
tft.println(TSy_2nd_dice);

tft.print("TSy_3th_dice is: “);
tft.println(TSy_3th_dice);

tft.print("TSy_4th_dice is: “);
tft.println(TSy_4th_dice);

tft.print("TSy_virtual_dice is: “);
tft.println(TSy_virtual_dice);
}
void loop(){}
کد فوق را روی برد آردوینو آپلود کنید سپس نقاط نمایش داده شده به رنگ قرمز را لمس کنید. اعدادی که در صفحه سریال مانیتور و یا در مرحله آخر روی LCD مشاهده می­کنید را با اعداد درنظر گرفته شده برای متغیرهای TSx_dices، TSy_1st_dice، TSy_2nd_dice، TSy_3th_dice، TSy_4th_dice، TSy_virtual_dice در ابتدای کد اصلی جایگزین کنید.

یک گام جلوتر

  • برنامه تاس مجازی را به گونه ای تغییر دهید که اگر یکی از بازیکنان تاس 6 را بیاورد بتواند تاس جایزه خود را بیاندازد و نوبت تغییر نکند.

از این مطلب خوشتان آمد؟

جدیدترین آموزش ها را از بهترین‌ها دریافت کنید

بیشتر بخوانید

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *