Smart Tennis Racket este un dispozitiv electronic montabil pe racheta de tenis care detectează automat loviturile și oferă feedback obiectiv jucătorului în timp real. Concret, sistemul:
Antrenament obiectiv pentru jucătorii amatori. Autoevaluarea unui jucător amator se bazează pe simțuri (“a sunat bine”, “a fost puternic”), nu pe date măsurate. Dispozitivul transformă o ședință de antrenament într-un set de metrici concrete: număr lovituri, raport forehand/backhand, distribuția intensităților, viteză medie de swing — fără cost ridicat și fără dependență de un antrenor sau de o aplicație telefonică.
Soluțiile comerciale (Babolat Pop, Sony Smart Tennis Sensor, Zepp Tennis) sunt scumpe, închise ca platformă și au fost discontinuate de mai multe ori, lăsând utilizatorii fără suport. Plecând de la observația că un IMU 6-DOF de tipul MPU6050 acoperă cea mai mare parte a senzoricii necesare, ne-am propus să construim un echivalent open, ieftin și deschis pentru extensii.
Sistemul este construit în jurul plăcii de dezvoltare ATmega328P Xplained Mini, care joacă rolul de coordonator central. Microcontrollerul citește continuu date inerțiale de la senzorul MPU6050 prin magistrala I2C, le procesează printr-o pipeline simplă de detecție și clasificare, după care actualizează simultan două canale de output: ecranul TFT (text și statistici) prin SPI și LED-ul RGB (feedback luminos) prin trei canale PWM. Un buton conectat ca GPIO permite reset-ul contorului.
twi_start / twi_write / twi_read_ack / twi_read_nack / twi_stop din driverul de la Lab 6.HITS: 24SHOT: FOREHANDPOWER: HIGHSWING: 320 deg/s| Modul | Sursă | Descriere |
twi.c/.h | Lab 6 (preluat, F_CPU adaptat la 16 MHz) | driver I2C low-level |
mpu6050.c/.h | scris (după pattern-ul mpl3115a2 din Lab 6) | init, citire accel/gyro burst, calibrare offset |
spi.c/.h | Lab 5 (preluat) | driver SPI low-level |
st7735.c/.h | Lab 5 (preluat, adaptat pentru pini noi) | driver TFT 1.8” |
timers.c/.h | Lab 3 (preluat, extins) | configurare PWM 2 canale pe Timer 0 + Timer 2 |
rgb.c/.h | scris | wrapper PWM pentru LED RGB (R+G) cu efecte (solid / blink / fade) |
swing_detect.c/.h | scris | detecție swing și clasificare |
main.c | scris | bucla principală, integrare module, FSM |
0x68. Rolul lui: achiziția semnăturii inerțiale a loviturii — accelerometrul detectează startul și sfârșitul unei lovituri prin praguri de magnitudine, iar giroscopul oferă sensul de rotație pentru clasificarea forehand/backhand.Microcontrolerul ATmega328P are un număr limitat de pini cu funcții speciale (I2C, SPI, PWM, întreruperi). Alegerea fiecărui pin nu este arbitrară, ci impusă de funcția hardware de care avem nevoie.
| Semnal | Pin AVR | Componentă | Justificarea alegerii |
|---|---|---|---|
| SDA | PC4 | MPU6050 | Pin hardware TWI al ATmega328P — singura opțiune pentru I2C hardware. |
| SCL | PC5 | MPU6050 | Pin hardware TWI — pereche cu SDA. |
| SCK | PB5 | Display | Pin hardware SPI SCK. |
| MOSI (SDA) | PB3 | Display | Pin hardware SPI MOSI. |
| CS | PB2 | Display | Pinul hardware SS; trebuie configurat ca ieșire, altfel modulul SPI cade în mod slave. |
| DC | PB1 | Display | GPIO liber, adiacent fizic pinilor SPI — cablaj scurt. |
| RST | PB0 | Display | GPIO liber, adiacent pinului DC. |
| Rosu (R) | PD3 | LED RGB | Ieșire OC2B — PWM hardware generat de Timer 2. |
| Verde (G) | PD5 | LED RGB | Ieșire OC0B — PWM hardware generat de Timer 0. |
| Buton | PD2 | Buton | Pin INT0 — întrerupere externă dedicată, cu pull-up intern activat în software. |
De ce I2C pe PC4/PC5: magistrala I2C hardware (TWI) a ATmega328P este fixată pe acești doi pini. Nu există alternativă dacă vrem comunicare I2C hardware, fără emulare software.
De ce SPI pe portul B: modulul SPI hardware folosește obligatoriu PB3 (MOSI) și PB5 (SCK). Pinii de control specifici ST7735 (CS, DC, RST) pot fi GPIO oarecare; i-am ales pe PB0–PB2 pentru a fi grupați lângă pinii SPI, ceea ce scurtează cablajul și reduce riscul de erori.
De ce PWM pe PD3 și PD5: pe ATmega328P, pinul PD3 este ieșirea comparatorului OC2B (Timer 2), iar PD5 este OC0B (Timer 0). Acestea sunt ieșirile de PWM hardware disponibile, care generează semnalul fără a încărca procesorul.
De ce butonul pe PD2: pinul PD2 corespunde întreruperii externe INT0. Resetarea contorului este un eveniment punctual, ideal de tratat într-o rutină de întrerupere, fără a fi nevoie de verificare repetată (polling) în bucla principală.
Lipsa conflictelor: cele trei magistrale folosesc porturi diferite — I2C pe portul C, SPI pe portul B, PWM-ul LED-ului pe portul D — deci nu există suprapuneri. Singura observație: PB5 este folosit și de LED-ul utilizator de pe placă, care va pâlpâi în timpul transmisiilor SPI; acest efect este pur cosmetic și nu afectează funcționarea.
Imaginea arată sistemul asamblat pe breadboard: placa ATmega328P Xplained Mini alimentată prin USB, senzorul MPU6050, display-ul ST7735, LED-ul RGB cu cei doi rezistori de limitare și butonul de reset. Alimentarea este distribuită prin șinele laterale ale breadboard-ului.
^ # ^ Componentă ^ Cantitate ^ Rol în proiect ^
| 1 | ATmega328P Xplained Mini (placă dezvoltare) | 1 | coordonator central, programator on-board |
| 2 | MPU6050 (modul GY-521) — accelerometru + giroscop 3 axe | 1 | senzor inerțial pentru detecția mișcării (I2C, adresa 0x68) |
| 3 | LED RGB 5 mm, 4 pini, anod comun, opac | 5 | feedback vizual (PWM, doar canalele R+G); restul ca rezervă |
| 4 | Display TFT color 1.8”, controller ST7735, interfață SPI | 1 | afișare statistici și tip lovitură |
| 5 | Kit rezistențe 1/4 W, 30 valori, 600 buc, 10 Ω – 1 MΩ | 1 | limitare curent LED (2× 220 Ω pe R și G); pull-up I2C dacă e necesar |
| 6 | Breadboard MB-102, 830 puncte | 1 | suport prototipare |
| 7 | Fire dupont 20 cm, mamă-mamă, set 40 | 1 | conexiuni breadboard ↔ Xplained Mini ↔ module |
| 8 | Fire dupont 20 cm, tată-mamă, set 40 | 1 | conexiuni mixte (pini Xplained ↔ module) |
| 9 | Suport baterie 9V cu jack 5.5×2.1 mm | 1 | alimentare portabilă |
| 10 | Buton tactil PCB mini 6×6×5, 2 pini | 1 | reset contor lovituri |
Proiectul a fost dezvoltat folosind PlatformIO cu toolchain-ul avr-gcc, integrat în Visual Studio Code. Compilatorul țintă este avr-gcc cu flagul -mmcu=atmega328p și -DF_CPU=16000000UL. Deoarece codul folosește sqrt() din <math.h>, au fost adăugate flagurile de linker pentru floating point:
build_flags = -Wl,-u,vfprintf -lprintf_flt -lm
Depanarea s-a realizat prin interfața UART la 9600 baud, cu mesaje trimise către un terminal serial (Serial Monitor din PlatformIO sau PuTTY).
| Modul | Fișier sursă | Origine | Rol |
|---|---|---|---|
| Driver I2C/TWI | twi.c / twi.h | Preluat din Laboratorul 6, adaptat (F_CPU = 16 MHz, timeout adăugat) | Comunicare I²C cu MPU6050 |
| Driver SPI | spi.c / spi.h | Preluat din Laboratorul 5 | Comunicare SPI cu display-ul ST7735 |
| Driver TFT | st7735.c / st7735.h | Preluat din Laboratorul 5, adaptat pentru pinii ATmega328P Xplained Mini | Afișare text și grafice pe ecranul 1.8” |
| Loop principal + logică | main.c | Scris integral în cadrul proiectului | Inițializare, FSM detecție swing, clasificare, UART, LED RGB, buton |
| AVR LibC | <avr/io.h>, <avr/interrupt.h>, <util/delay.h>, <math.h> | Standard AVR-LibC | Acces registre, întreruperi, delay, sqrt() |
Nu au fost folosite biblioteci externe de tip Arduino sau framework-uri de nivel înalt — întreaga stivă este bare-metal C pe AVR.
Funcția mpu_read_gyro() realizează o citire în rafală (burst read) a 6 octeți consecutivi începând de la registrul 0x43 (GYRO_XOUT_H). Secvența respectă protocolul I²C descris în Laboratorul 6:
twi_start(SLA_W) → twi_write(REG_GYRO_X) → twi_start(SLA_R) → twi_read(ACK) x5 → twi_read(NACK) → twi_stop()
Cei 6 octeți sunt recombinați în trei valori int16_t (gx, gy, gz) prin shift și OR pe biți:
*gx = (int16_t)((b[0] << 8) | b[1]);
Pentru a obține un scalar independent de orientarea rachetei, se calculează norma euclidiană a vectorului angular:
mag = sqrt(gx² + gy² + gz²)
Aceasta este implementată în gyro_magnitude() folosind sqrt() din <math.h> aplicat pe double. Valorile intermediare sunt calculate pe int32_t pentru a evita overflow-ul pe 16 biți:
int32_t sx = (int32_t)gx * gx;
Avantajul magnitudinii față de o singură axă este robustețea la montaj: indiferent cum este fixat senzorul pe rachetă, o mișcare rapidă va produce o valoare mare.
Detecția loviturii folosește un filtru cu histerezis pe două praguri, pentru a evita declanșările false datorită zgomotului sau vibrațiilor mici:
Starea IDLE: dacă mag > GYRO_THR_START (5000), se intră în starea IN_HIT și se înregistrează primul vârf.
Starea IN_HIT: se urmărește valoarea maximă (peak_gyro). Dacă mag < GYRO_THR_END (1500), lovitura s-a terminat → clasificare și reset.
Cooldown: după fiecare lovitură detectată, un contor de 1500 ms blochează detecțiile noi, pentru a lăsa timp rachetei să revină la poziția inițială fără a genera lovituri fantomă.
Separarea pragului de intrare (5000) față de cel de ieșire (1500) formează un gap de histerezis de ~3500 unități, eliminând oscilațiile în jurul pragului.
La finalul unui swing, valoarea peak_gyro este comparată cu pragul GYRO_GOOD_THR (20000):
if (peak_gyro >= GYRO_GOOD_THR) → BUNA (strong_hits++) else → SLABA (weak_hits++)
Funcția led_from_peak() mapează peak_gyro din intervalul [GYRO_MIN, GYRO_MAX] = [5000, 30000] pe un factor f ∈ [0, 255]:
f = (peak - GYRO_MIN) * 255 / (GYRO_MAX - GYRO_MIN) green = f; red = 255 - f;
Valorile sunt scalate la 80% din maxim (* 4 / 5) pentru a nu orbi utilizatorul. LED-ul are anod comun, deci duty cycle-ul este inversat: OCRxB = 255 - valoare, conform macrourilor LED_SET_R() și LED_SET_G(). Semnalele PWM hardware sunt generate de Timer 0 (OC0B → verde, PD5) și Timer 2 (OC2B → roșu, PD3), fără intervenție software în buclă — exact modul Fast PWM studiat în Laboratorul 3.
Butonul pe PD2 este tratat în întreruperea externă INT0_vect. Debouncing-ul se realizează prin compararea unui contor global de tick-uri (global_tick, incrementat la fiecare 20 ms în bucla principală) cu momentul ultimei apăsări valide:
if ((uint16_t)(global_tick - last_press_tick) > 3) // > 3 × 20ms = 60ms
Această abordare non-blocantă este echivalentă cu fereastra de 30–50 ms recomandată în Laboratorul 2, fără a bloca execuția din main.
Driver-ul st7735.c folosește interfața SPI hardware (PB5=SCK, PB3=MOSI, PB2=CS) cu pini de control GPIO (PB0=DC, PB1=RST). Fontul 5×7 este stocat în memoria Flash (PROGMEM) și citit cu pgm_read_byte(), economisind RAM-ul limitat al ATmega328P. Actualizarea ecranului se face selectiv — doar zona numerelor (coordonate fixe) — pentru a minimiza traficul SPI și latența vizibilă.
Implementarea software este completă și funcțională. Toate modulele sunt integrate și validate pe hardware real:
Inițializare și citire continuă MPU6050 prin I²C la 100 kHz Detecție automată lovituri cu histerezis și cooldown Clasificare binară slab/bun bazată pe peak-ul giroscopului Afișare live pe TFT ST7735 (total, slabe, bune) Feedback LED RGB cu degradeu roșu→verde proporțional cu intensitatea Reset prin buton fizic cu debouncing software Logging serial UART la 9600 baud pentru depanare și calibrare
S-a optat deliberat pentru o arhitectură bare-metal, fără Arduino sau alte framework-uri de nivel înalt, din următoarele motive:
Control precis al resurselor hardware: ATmega328P are 2 KB RAM și 32 KB Flash. Un framework de tip Arduino adaugă sute de octeți overhead pentru funcționalități nefolosite.
Familiaritate cu laboratoarele cursului: driver-ele twi.c, spi.c și st7735.c sunt extensii directe ale scheletelor din Laboratoarele 5 și 6, asigurând o înțelegere profundă a protocolelor.
Latență determinabilă: în sisteme de detecție în timp real, buclele de polling cu _delay_ms() și întreruperile hardware oferă timinguri predictibile, spre deosebire de un RTOS sau scheduler complex.
Singura funcție din bibliotecă externă (AVR-LibC) cu potențial overhead este sqrt() pe double — acceptabil deoarece este apelată o singură dată per eșantion (la 20 ms), deci ~50 apeluri/secundă, neglijabil față de ciclurile CPU disponibile la 16 MHz.
Față de implementările academice tipice care testează un singur senzor izolat, acest proiect integrează simultan patru periferice hardware diferite pe același microcontroller (I²C + SPI + PWM × 2 + UART + GPIO interrupt), cu o logică de procesare a semnalului în timp real care produce output multimodal (vizual + serial) în același ciclu de 20 ms. Degradeul de culoare continuu al LED-ului (nu simplu roșu/verde binar) și algoritmul de histerezis cu cooldown adaptiv sunt elemente care aduc valoare practică față de o implementare banală cu prag simplu.
| Funcționalitate | Laborator sursă | Utilizare în proiect |
|---|---|---|
| UART / printf redirecționat | Laboratorul 1 | Logging serial: magnitudine giroscop, tip lovitură, contor — esențial pentru calibrarea pragurilor |
| Întrerupere externă INT0 + debouncing | Laboratorul 2 | Reset contor prin buton fizic, non-blocant |
| PWM hardware Fast PWM, Timer 0 + Timer 2 | Laboratorul 3 | Control intensitate LED RGB cu anod comun pe două canale independente |
| SPI hardware + driver ST7735 | Laboratorul 5 | Afișare statistici live pe TFT 1.8” color |
| I²C / TWI + citire registre senzor | Laboratorul 6 | Citire burst gyroscop MPU6050, același pattern ca MPL3115A2 din laborator |
Bucla principală din main() rulează la o perioadă fixă de SAMPLE_MS = 20 ms și urmează un pipeline liniar:
[Buton ISR] ─────────────────────────────────► reset_flag
│
main loop (20ms tick): ▼
1. Verifică reset_flag → resetează contoare și ecran
2. mpu_read_gyro() ──I²C──► MPU6050 ──► gx, gy, gz
3. gyro_magnitude() ──────────────────► mag (int32_t)
4. FSM histerezis:
IDLE ──mag > 5000──► IN_HIT (track peak)
IN_HIT ──mag < 1500──► clasificare + LED + ecran update
5. led_from_peak() ──PWM──► Timer0/Timer2 ──► LED RGB
6. screen_update_counts() ──SPI──► ST7735
7. Debug UART la fiecare 25 iterații (~500 ms)
8. _delay_ms(20) + global_tick++
Validarea funcționării s-a realizat în două etape:
Validare prin UART: cu senzorul în repaus, mesajele gyro_mag=XXX confirmau valori < 500. Scuturând senzorul brusc, valorile depășeau 10000–30000, confirmând că pragurile GYRO_THR_START = 5000 separă corect zgomotul de fundal de un swing real.
Validare vizuală: LED-ul schimba culoarea consistent cu intensitatea mișcării (roșu pentru mișcări ușoare, verde/galben pentru swing-uri puternice), iar ecranul TFT incrementa contorul corect la fiecare lovitură detectată.
MPU6050 a fost folosit în configurația implicită (fără calibrare offset a giroscopului), deoarece:
Detecția se bazează pe magnitudini relative (este swing vs. nu este swing), nu pe valori absolute. Driftul de offset al giroscopului (tipic 1–3 LSB/°C) este neglijabil față de valorile de 5000–30000 atinse în swing. Pragurile au fost determinate empiric prin UART logging: sistemul a fost conectat la un terminal serial și senzorul a fost mișcat cu intensități variate. S-au observat valorile tipice:
Repaus / vibrații mici: mag ≈ 100–400 Mișcare lentă a mâinii: mag ≈ 800–2000 Swing moderat: mag ≈ 5000–12000 Swing puternic: mag ≈ 15000–28000
Pe baza acestor observații s-au stabilit: GYRO_THR_START = 5000, GYRO_THR_END = 1500, GYRO_GOOD_THR = 20000.
| Optimizare | Unde | Motivație |
|---|---|---|
Font stocat în Flash (PROGMEM) | st7735.c | Fontul 5×7 ASCII ocupă 475 octeți; dacă ar fi în RAM, ar consuma ~23% din cei 2KB disponibili |
| Actualizare selectivă a ecranului | screen_update_counts() | Se rescrie doar zona cu numere (3 stringuri de 5 caractere), nu întregul ecran; reduce traficul SPI cu ~95% față de st7735_fill() complet |
Padding cu spații în sprintf | main.c | sprintf(buf, ”%-5u”, val) suprascrie cifrele vechi fără a șterge și redesena fundalul — evită flickering |
| Histerezis cu cooldown | main.c | Fără cooldown, un singur swing ar genera 5–10 detecții false; cooldown-ul de 1500 ms garantează maximum 1 lovitură per swing |
Calcul magnitudine pe int32_t | gyro_magnitude() | Produsele gx*gx pe int16_t ar produce overflow (max 32767² = 1.07×10⁹ > 32767); cast la int32_t înainte de înmulțire previne silently wrong results |
| Debouncing non-blocant | ISR(INT0_vect) | Compararea tick-urilor nu blochează ISR-ul și nici bucla principală, față de un _delay_ms() în ISR care ar dezactiva alte întreruperi |