This shows you the differences between two versions of the page.
pm:prj2023:adarmaz:tiltthemaze [2023/05/26 20:48] adumitrescu2708 |
pm:prj2023:adarmaz:tiltthemaze [2023/05/30 02:58] (current) adumitrescu2708 [Implementare] |
||
---|---|---|---|
Line 1: | Line 1: | ||
====== Tilt the maze - Dumitrescu Alexandra 333CA ====== | ====== Tilt the maze - Dumitrescu Alexandra 333CA ====== | ||
+ | ===== Demo ===== | ||
+ | {{url>https://drive.google.com/file/d/1b7s5bXKdAkVjzZ-PGNajw2d06Bly8HcU/preview}} | ||
===== Introducere ===== | ===== Introducere ===== | ||
Proiectul constă într-un labirint ce poate fi rotit de jucător pe 2 axe OX și OY printr-un joystick. Motivația proiectului meu a constat în dorința automatizării unui joc din copilărie. Labirintul dispune 5 găuri, iar jucătorului îi este oferită o singură bilă magnetică. Jocul propune următoarele 2 moduri: | Proiectul constă într-un labirint ce poate fi rotit de jucător pe 2 axe OX și OY printr-un joystick. Motivația proiectului meu a constat în dorința automatizării unui joc din copilărie. Labirintul dispune 5 găuri, iar jucătorului îi este oferită o singură bilă magnetică. Jocul propune următoarele 2 moduri: | ||
Line 26: | Line 28: | ||
- [[pm:lab:lab3-2023| Laborator 6.]] I2C pentru ecranul LCD | - [[pm:lab:lab3-2023| Laborator 6.]] I2C pentru ecranul LCD | ||
- [[pm:lab:lab3-2023| Laborator 3.]] PWM pentru servomotoare | - [[pm:lab:lab3-2023| Laborator 3.]] PWM pentru servomotoare | ||
+ | === Schema circuitului === | ||
+ | {{:pm:prj2023:adarmaz:untitled_sketch_2_bb.png?600|}} | ||
===== Software Design ===== | ===== Software Design ===== | ||
==== Descriere generală ==== | ==== Descriere generală ==== | ||
Line 44: | Line 48: | ||
- în cazul în care a fost selectat modul de joc //Save the ball//, Arduino-ul generează random la fiecare 10s o poziție pe OX și pe OY în care să se deplaseze labirintul.\\ | - în cazul în care a fost selectat modul de joc //Save the ball//, Arduino-ul generează random la fiecare 10s o poziție pe OX și pe OY în care să se deplaseze labirintul.\\ | ||
Putem să le luăm pe rănd, în ordinea cronologică începerii jocului \\ \\ | Putem să le luăm pe rănd, în ordinea cronologică începerii jocului \\ \\ | ||
- | - Prima dată, am setat un **JOYSTICK_DEADZONE** pentru a asigura că nu există niciun fel de zgomot în momentul în care labirintul stagnează în **SERVO_DEFAULT_POSITION** (35°), practic atunci când jucătorul nu mișcă joystick-ul sau îl mișcă suficient de puțin, labirintul trebuie să sea pe loc pentru a reduce zgomotele. Dacă datele depășesc **JOYSTICK_DEADZONE**, atunci vom mapa datele primite pe OX și OY în intervalul [25°, 45°).\\ | + | - Prima dată, am setat un **JOYSTICK_DEADZONE** pentru a asigura că nu există niciun fel de zgomot în momentul în care labirintul stagnează în **SERVO_DEFAULT_POSITION** (35°), practic atunci când jucătorul nu mișcă joystick-ul sau îl mișcă suficient de puțin, labirintul trebuie să sea pe loc pentru a reduce zgomotele. Dacă datele depășesc **JOYSTICK_DEADZONE**, atunci vom mapa datele primite pe OX și OY în intervalul [25°, 45°). \\ |
- | - Variabila **IS_START_GAME** detectează dacă jocul a început sau nu. Pentru a monitoriza acest aspect, contorizăm folosind întreruperea hardware externă generată de apăsarea butonului joystickului, de câte ori a fost apăsat butonul. Prima apăsare anunță începerea jocului, iar a doua selectarea modului de joc. \\ | + | <spoiler Implementarea deadzone-ului și poziționarea servomotoarelor.> |
- | - Pentru a nu aglomera comunicarea pe interfața serială (deoarece ar putea interveni desincronizări în primirea/trimiterea datelor în Receiver / Transmitter sau datele ar putea fi primite cu o întârziere care nu este necesară), am propus o soluție în care pe interfață se comunică doar la începutul jocului căte un **singur byte la un moment dat, în care biții codifică starea actuală a joystickului**. Nu ne interesează decăt up/low/pressed pentru că interfața de pe LCD permite alegerea game modului prin swipe up/low \\ | + | <code C> |
+ | int joystick_deadzone = 200; | ||
+ | (...) | ||
+ | void loop() { | ||
+ | int ox = analogRead(A3); | ||
+ | int oy = analogRead(A2); | ||
+ | |||
+ | /* check and adjust deadzone */ | ||
+ | if (abs(ox - 512) < joystick_deadzone) { | ||
+ | ox = 512; | ||
+ | } | ||
+ | |||
+ | if (abs(oy - 512) < joystick_deadzone) { | ||
+ | oy = 512; | ||
+ | } | ||
+ | |||
+ | int servo_ox_pos = map(ox, 0, 1023, 25, 45); | ||
+ | int servo_oy_pos = map(oy, 0, 1023, 25, 45); | ||
+ | } | ||
+ | </code> | ||
+ | </spoiler> | ||
+ | - Variabila **IS_START_GAME** detectează dacă jocul a început sau nu. Pentru a monitoriza acest aspect, contorizăm folosind întreruperea hardware externă generată de apăsarea butonului joystickului, de câte ori a fost apăsat butonul. Prima apăsare anunță începerea jocului, iar a doua selectarea modului de joc. Deoarece pot apărea probleme la apăsare continuă a butonului, în sensul că întreruperea externă se poate activa de mai multe ori pentru o singură apăsare, am implementat o soluție în care se asigură că butonul este apăsat, apoi eliberat și abia apoi o altă apăsare este validată. //Jocul începe o dată ce s-au trimit cele 2 apăsări de buton, iar Arduino 2. trimite înapoi modul de joc selectat// \\ | ||
+ | <spoiler Întreruperea externă pentru detectarea apăsării butonului și încheierea conexiunii.> | ||
+ | <code C> | ||
+ | void loop() { | ||
+ | if(mySerial.available() >= 1) { | ||
+ | byte finished = mySerial.read(); | ||
+ | is_start_game = true; | ||
+ | chosen_game_mode = finished; | ||
+ | } | ||
+ | } | ||
+ | (...) | ||
+ | ISR(INT0_vect) { | ||
+ | int value = PIND & (1 << JOYSTICK_BUTTON); | ||
+ | if(is_button_pressed == true && old_value != 0) { | ||
+ | is_button_pressed_second = true; | ||
+ | } | ||
+ | if(value == 0) { | ||
+ | is_button_pressed = true; | ||
+ | } | ||
+ | old_value = value; | ||
+ | } | ||
+ | </code> | ||
+ | </spoiler> | ||
+ | - Pentru a nu aglomera comunicarea pe interfața serială (deoarece ar putea interveni desincronizări în primirea/trimiterea datelor în Receiver / Transmitter sau datele ar putea fi primite cu o întârziere care nu este necesară), am propus o soluție în care pe interfață se comunică doar la începutul jocului căte un **singur byte la un moment dat, în care biții codifică starea actuală a joystickului**. Nu ne interesează decăt up/low/pressed pentru că interfața de pe LCD permite alegerea game modului prin swipe up/low. **Între cele Arduinouri se stabilește un protocol de comunicație. Ambele părți trebuie să cunoască decizia de game mode luat și, implicit încheierea conexiunii. Din punctul alegerii jocului, Arduino-urile au taskuri separate specializate pe modul de joc. **\\ | ||
^ button[0:7] ^ Semnification ^ | ^ button[0:7] ^ Semnification ^ | ||
| button[0] | Button Pressed | | | button[0] | Button Pressed | | ||
Line 52: | Line 100: | ||
| button[2] | Low | | | button[2] | Low | | ||
| button[3] | 2nd Button Pressed| \\ | | button[3] | 2nd Button Pressed| \\ | ||
+ | {{:pm:prj2023:adarmaz:protocol2.png?500| Protocolul de comunicație între Arduino-uri}}\\ | ||
- O dată ce game modeul a fost setat se așteaptă un byte înapoi de sincronizare între cele 2 Arduino-uri, care semnifică game modeul selectat, după care începe jocul, pornind servomotoarele și dăndu-le ca direcție datele mapate [25°, 45°) de la joystick. Pentru a asigura o viteză acceptabilă astfel încât să nu se miște brusc/dezordonat labirintul am lasat un delay pentru fiecare unghi în care se deplasează servomotorul.\\ \\ | - O dată ce game modeul a fost setat se așteaptă un byte înapoi de sincronizare între cele 2 Arduino-uri, care semnifică game modeul selectat, după care începe jocul, pornind servomotoarele și dăndu-le ca direcție datele mapate [25°, 45°) de la joystick. Pentru a asigura o viteză acceptabilă astfel încât să nu se miște brusc/dezordonat labirintul am lasat un delay pentru fiecare unghi în care se deplasează servomotorul.\\ \\ | ||
Line 58: | Line 107: | ||
- Primul mod, //Save the Ball// așteaptă ca pentru fiecare gaură să detecteze senzorul magnetic bila magnetică. Dacă bila a fost detectată, se oprește și afișează timpul jocului. \\ | - Primul mod, //Save the Ball// așteaptă ca pentru fiecare gaură să detecteze senzorul magnetic bila magnetică. Dacă bila a fost detectată, se oprește și afișează timpul jocului. \\ | ||
- Al doilea mod, //Send The Ball//, generează random o gaură, implicit pornește LED-ul găurii respective, pentru a anunța jucătorul unde trebuie să trimită mingea, și așteaptă ca senzorii să detecteze fie dacă mingea a picat în gura corectă, fie nu. Va afișa rezultatul **FAIL/PASS** și durata jocului. | - Al doilea mod, //Send The Ball//, generează random o gaură, implicit pornește LED-ul găurii respective, pentru a anunța jucătorul unde trebuie să trimită mingea, și așteaptă ca senzorii să detecteze fie dacă mingea a picat în gura corectă, fie nu. Va afișa rezultatul **FAIL/PASS** și durata jocului. | ||
+ | <spoiler Parsarea datelor primite prin protocolul de comunicare de Arduino2.> | ||
+ | <code C> | ||
+ | void loop() { | ||
+ | if(mySerial.available() >= 2) { | ||
+ | /* extract info from protocol */ | ||
+ | byte received = mySerial.read(); | ||
+ | byte button_pressed = (received & 0x01); | ||
+ | byte upper_cursor = (received >> 1) & 0x01; | ||
+ | byte lower_cursor = (received >> 2) & 0x01; | ||
+ | byte second_button_pressed = (received >> 3) & 0x01; | ||
+ | |||
+ | if(button_pressed && !start_game) { | ||
+ | start_game = true; | ||
+ | set_LCD_start_game(); | ||
+ | } | ||
+ | |||
+ | if(start_game && !set_game_mode && (upper_cursor != 0 || lower_cursor != 0)) { | ||
+ | /* choose game mode */ | ||
+ | last_game_picked = upper_cursor != 0 ? 1 : 2; | ||
+ | byte cursor_LCD = upper_cursor != 0 ? 1 : 0; | ||
+ | if(cursor_LCD != previous_cursor_state) { | ||
+ | previous_cursor_state = cursor_LCD; | ||
+ | parse_cursor(cursor_LCD); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | </spoiler> | ||
==== Rezultate obținute ==== | ==== Rezultate obținute ==== | ||
Consider că partea care mi-a provocat cele mai multe probleme a fost fizica și dinamica jocului. Au apărut dificultăți atât în montarea platformei și a senzorilor și a ledurilor, cât și în fizica deplasării labirintului (viteză prea mare, mișcare bruscă etc.). În final, am reușit să implementez ce mi-am propus. Multe probleme au apărut și din cauza bibliotecilor folosite, primordial SoftwareSerial.h, care mi-a limitat posibilitatea lucrului cu regiștrii. \\ | Consider că partea care mi-a provocat cele mai multe probleme a fost fizica și dinamica jocului. Au apărut dificultăți atât în montarea platformei și a senzorilor și a ledurilor, cât și în fizica deplasării labirintului (viteză prea mare, mișcare bruscă etc.). În final, am reușit să implementez ce mi-am propus. Multe probleme au apărut și din cauza bibliotecilor folosite, primordial SoftwareSerial.h, care mi-a limitat posibilitatea lucrului cu regiștrii. \\ | ||
Line 65: | Line 142: | ||
{{:pm:prj2023:adarmaz:thumbnail_img_3955.jpg?400|}} | {{:pm:prj2023:adarmaz:thumbnail_img_3955.jpg?400|}} | ||
---- | ---- | ||
+ | ==== Concluzii ==== | ||
+ | Mă bucur că am reușit să duc la bun final proiectul pe care mi l-am propus, contrar numeroaselor probleme care s-au ivit. Țin să menționez că deși am reușit să implementez tot ce mi-am propus există câteva lipsuri ale proiectului, pe care cu siguranță le voi adăuga în viitor și care ar face experiența jocului mai bună. O problemă de luat în calcul este că atunci când doresc resetarea jocului, trebuie re-rulat codul, deoarece, la cum a fost gândită soluția inițial, conexiunea pe interfața serială între cele două Arduino-uri se finalizează o dată cu începerea jocului. Overall, mi s-a părut fun și mă consider mulțumită de proiectul obținut, mai ales că este primul proiect la care lucrez. \\ | ||
+ | Am învățat ca folosirea bibliotecilor (mai ales Software Serial) poate provoca numeroase probleme, precum limitarea flexibilității și a lucrului cu regiștrii. | ||
+ | ===== Download ===== | ||
+ | <note important>**Codul sursă pentru cele două Arduino-uri poate fi găsit la următorul repository de Github | ||
+ | [[https://github.com/adumitrescu2708/TiltTheMaze]].**</note> | ||
+ | ===== Bibliografie ===== | ||
+ | Pentru realizarea proiectului am folosit următoarele tool-uri, pentru schema bloc [[https://miro.com/index/|Miro]], pentru schema circuitului electric [[https://fritzing.org/|Fritzing]], iar pentru implementare am urmărit laboratoarele și m-am folosit de articolele \\ | ||
+ | - [[https://diyodemag.com/projects/arduino_uno_servo-operated_ball_maze_marble_mayh|https://diyodemag.com/projects/arduino_uno_servo-operated_ball_maze_marble_mayh]] \\ | ||
+ | - [[https://www.hackster.io/michael-engel/automatic-marble-labyrinth-solver-d54ad3|https://www.hackster.io/michael-engel/automatic-marble-labyrinth-solver-d54ad3]] \\ | ||
+ | - [[https://ocw.cs.pub.ro/courses/_media/pm/atmel-7810-automotive-microcontrollers-atmega328p_datasheet.pdf|https://ocw.cs.pub.ro/courses/_media/pm/atmel-7810-automotive-microcontrollers-atmega328p_datasheet.pdf]] \\ | ||
+ | - [[https://forum.arduino.cc/t/joystick-huge-outer-deadzone/939904|https://forum.arduino.cc/t/joystick-huge-outer-deadzone/939904]] \\ |