This shows you the differences between two versions of the page.
|
pm:prj2023:adarmaz:tiltthemaze [2023/05/26 22:23] adumitrescu2708 [Descriere generală] |
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 9: | Line 11: | ||
| Jucătorul primește un controller format dintr-un Arduino Nano la care este conectat un joystick. Arduino Nano va transmite prin intefața serială USART mesaje către un alt Arduino Uno, comunicându-i datele transmise prin joystick. Arduino Uno în funcție de datele primite acționeaza cele 2 servo motoare care deplasează cadranul și labirintul pe 2 axe. La începutul jocului, utilizatorul are o interfață grafică pe ecranul LCD în care trebuie să își selecteze modul jocului dorit. Fiecare gaură din interiorul labirintului are un senzor magnetic Hall care va detecta dacă bila a căzut în gaura potrivită sau nu și va decide rezultatul partidei pe care îl va afișa pe LCD împreună cu timpul cronometrat.\\ | Jucătorul primește un controller format dintr-un Arduino Nano la care este conectat un joystick. Arduino Nano va transmite prin intefața serială USART mesaje către un alt Arduino Uno, comunicându-i datele transmise prin joystick. Arduino Uno în funcție de datele primite acționeaza cele 2 servo motoare care deplasează cadranul și labirintul pe 2 axe. La începutul jocului, utilizatorul are o interfață grafică pe ecranul LCD în care trebuie să își selecteze modul jocului dorit. Fiecare gaură din interiorul labirintului are un senzor magnetic Hall care va detecta dacă bila a căzut în gaura potrivită sau nu și va decide rezultatul partidei pe care îl va afișa pe LCD împreună cu timpul cronometrat.\\ | ||
| {{:pm:prj2023:adarmaz:schema-bloc-pm-dumi.png?600|}} \\ | {{:pm:prj2023:adarmaz:schema-bloc-pm-dumi.png?600|}} \\ | ||
| - | ==== Schema circuitului ==== | + | |
| - | {{:pm:prj2023:adarmaz:untitled_sketch_2_bb.png?600|}} | + | |
| ===== Hardware Design ===== | ===== Hardware Design ===== | ||
| Componentele necesare realizării proiectului sunt | Componentele necesare realizării proiectului sunt | ||
| Line 27: | 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 45: | 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 53: | 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 59: | 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 66: | 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]] \\ | ||