La finalul laboratorului veți putea:
NXP Cup este un concurs de robotică în care echipele construiesc și programează o mașină autonomă la scară mică. Obiectivul tipic este parcurgerea unui traseu marcat pe sol, cât mai rapid și cât mai stabil, fără intervenție umană.
Într-o rundă de concurs, mașina trebuie să:
O mașină NXP Cup conține, în general, următoarele blocuri:
| Componentă | Rol | Exemple de probleme practice |
|---|---|---|
| Microcontroller | Rulează codul de control în timp real | frecvență de eșantionare, latență, limitări de memorie |
| Senzori de linie | Măsoară poziția liniei față de mașină | zgomot, calibrare, lumină ambientală, pierderea liniei |
| Servo direcție | Virează roțile față | limitare mecanică, saturație, răspuns neliniar |
| Motor / driver motor | Controlează viteza mașinii | inerție, derapaj, limitare de curent |
| Encoder / feedback viteză | Estimează viteza roților | impulsuri lipsă, cuantizare, perioade de eșantionare |
| Baterie | Alimentează sistemul | scădere de tensiune, variație de performanță |
În fiecare iterație a buclei de control, mașina execută aproximativ pașii de mai jos:
1. Citește senzorii de linie. 2. Calculează poziția liniei față de centrul mașinii. 3. Calculează eroarea: error = target_position - measured_position. 4. Aplică un controller PID pe eroare. 5. Transformă ieșirea PID într-o comandă pentru servo. 6. Alege viteza motorului în funcție de stabilitate și de curbură. 7. Trimite comenzile către actuatoare. 8. Repetă la următorul pas de timp.
Ideea importantă este că mașina nu „știe” traseul dinainte. Ea reacționează continuu la măsurători. Dacă linia este la stânga, trebuie să vireze stânga; dacă linia este la dreapta, trebuie să vireze dreapta; dacă linia este aproape de centru, trebuie să meargă drept.
Un controller PID folosește trei termeni:
| Termen | Formulă conceptuală | Efect |
|---|---|---|
| P - proporțional | depinde de eroarea curentă | reacționează rapid la abaterea față de linie |
| I - integral | acumulează eroarea în timp | corectează erori persistente |
| D - derivativ | depinde de variația erorii | reduce oscilațiile și anticipează schimbările rapide |
Formula discretă folosită în laborator este:
integral = integral + error * dt; derivative = (error - previous_error) / dt; output = kp * error + ki * integral + kd * derivative; previous_error = error;
După calcul, ieșirea se limitează la intervalul acceptat de servo:
if (output > max_output) output = max_output; if (output < min_output) output = min_output;
O implementare simplă pentru urmărirea liniei poate folosi următoarea arhitectură:
sensors.c -> citire și calibrare senzori line_position.c -> estimarea poziției liniei pid.c -> controller PID steering.c -> conversie PID output -> comandă servo speed.c -> control viteză main.c -> bucla principală de control
Pentru laborator nu aveți nevoie să modificați tot codul mașinii. Veți lucra pe exerciții izolate, cu teste automate, iar la final veți încărca pe mașină doar binarul pentru exercițiul de PID.
Veți primi:
Nu veți primi codul complet al mașinii. Scopul este să lucrați pe interfețe clare, asemănător cu situațiile în care integrați un modul într-un sistem existent.
Reglați parametrii kp, ki și kd astfel încât mașina să urmărească linia cât mai stabil.
pid_config.txt cu parametrii editabili;flash_car.sh;Exemplu de fișier de configurare:
kp=0.35 ki=0.00 kd=0.08 speed=0.40
ki = 0.kp până când mașina începe să urmărească linia, dar fără oscilații mari.kd pentru a reduce oscilațiile.ki doar dacă mașina are o abatere persistentă într-o parte../flash_car.sh pid_config.txt
După încărcare, testați mașina pe traseu și completați tabelul:
| Încercare | kp | ki | kd | speed | Observații |
|---|---|---|---|---|---|
| 1 | |||||
| 2 | |||||
| 3 | |||||
| 4 |
kp este prea mic?kp este prea mare?kd?Implementați o funcție care primește valorile senzorilor de linie și returnează poziția estimată a liniei față de centrul mașinii.
Acest exercițiu se face pe laptop și nu necesită mașina.
Implementați funcția:
float estimate_line_position(const int sensors[], int n);
Valorile din sensors sunt intensități normalizate între 0 și 1000, unde o valoare mai mare înseamnă că senzorul vede mai puternic linia.
Pozițiile senzorilor sunt distribuite uniform în intervalul [-1.0, 1.0]. Pentru n = 5, pozițiile sunt:
senzor: 0 1 2 3 4 poziție: -1.0 -0.5 0.0 0.5 1.0
Poziția liniei se calculează ca medie ponderată:
position = sum(sensor_value[i] * sensor_position[i]) / sum(sensor_value[i])
Dacă suma valorilor este 0, funcția trebuie să returneze 0.0.
| Intrare | Rezultat aproximativ | Explicație |
|---|---|---|
[0, 0, 1000, 0, 0] | 0.0 | linia este în centru |
[1000, 0, 0, 0, 0] | -1.0 | linia este complet la stânga |
[0, 0, 0, 0, 1000] | 1.0 | linia este complet la dreapta |
[0, 500, 1000, 500, 0] | 0.0 | linia este centrată |
[0, 0, 500, 1000, 0] | aproximativ 0.333 | linia este ușor la dreapta |
Un posibil testbench:
#include <assert.h> #include <math.h> float estimate_line_position(const int sensors[], int n); static void almost_equal(float a, float b) { assert(fabsf(a - b) < 0.02f); } int main(void) { int centered[] = {0, 0, 1000, 0, 0}; int left[] = {1000, 0, 0, 0, 0}; int right[] = {0, 0, 0, 0, 1000}; int symmetric[] = {0, 500, 1000, 500, 0}; int slight_right[] = {0, 0, 500, 1000, 0}; int missing[] = {0, 0, 0, 0, 0}; almost_equal(estimate_line_position(centered, 5), 0.0f); almost_equal(estimate_line_position(left, 5), -1.0f); almost_equal(estimate_line_position(right, 5), 1.0f); almost_equal(estimate_line_position(symmetric, 5), 0.0f); almost_equal(estimate_line_position(slight_right, 5), 0.333f); almost_equal(estimate_line_position(missing, 5), 0.0f); return 0; }
Compilare și rulare:
gcc -Wall -Wextra -std=c11 line_position.c test_line_position.c -lm -o test_line_position ./test_line_position
Dacă programul se termină fără eroare, testele au trecut.
Implementați conversia dintre ieșirea controllerului PID și comanda trimisă către servo.
Acest exercițiu se face pe laptop și nu necesită mașina.
Controllerul PID produce o valoare abstractă, de exemplu în intervalul [-1.0, 1.0]:
-1.0 înseamnă viraj maxim stânga;0.0 înseamnă roți drepte;1.0 înseamnă viraj maxim dreapta.Servo-ul primește un impuls PWM în microsecunde. În acest exercițiu folosim convenția:
| Comandă | PWM |
|---|---|
| viraj maxim stânga | 1000 |
| centru | 1500 |
| viraj maxim dreapta | 2000 |
Implementați funcția:
int pid_output_to_servo_us(float pid_output);
Funcția trebuie să:
pid_output la intervalul [-1.0, 1.0];[1000, 2000];Formula recomandată:
servo_us = 1500 + pid_output * 500
| pid_output | Rezultat așteptat |
|---|---|
-1.0 | 1000 |
-0.5 | 1250 |
0.0 | 1500 |
0.5 | 1750 |
1.0 | 2000 |
2.0 | 2000 |
-2.0 | 1000 |
Un posibil testbench:
#include <assert.h> int pid_output_to_servo_us(float pid_output); int main(void) { assert(pid_output_to_servo_us(-1.0f) == 1000); assert(pid_output_to_servo_us(-0.5f) == 1250); assert(pid_output_to_servo_us(0.0f) == 1500); assert(pid_output_to_servo_us(0.5f) == 1750); assert(pid_output_to_servo_us(1.0f) == 2000); assert(pid_output_to_servo_us(2.0f) == 2000); assert(pid_output_to_servo_us(-2.0f) == 1000); return 0; }
Compilare și rulare:
gcc -Wall -Wextra -std=c11 steering.c test_steering.c -o test_steering ./test_steering
Dacă programul se termină fără eroare, testele au trecut.
La finalul laboratorului încărcați:
pid_config.txt cu cea mai bună combinație găsită;estimate_line_position;pid_output_to_servo_us;| Componentă | Punctaj |
|---|---|
| Explicații despre reglarea PID și observații din testarea pe mașină | 30% |
| Parametri PID funcționali pe traseu | 25% |
| Exercițiul 2: estimarea poziției liniei | 20% |
| Exercițiul 3: conversie comandă servo | 15% |
| Claritate, cod simplu, teste rulate local | 10% |
kp sau creșteți ușor kd.kp.