Proiectul a presupus realizarea unui robot care să urmărească o linie neagră pe un fundal alb.
Pentru că este foarte ușor de trasat linia de ghidaj acest tip de robot poate fi folosit la automatizări care necesită o flexibilitate foarte mare. Un astfel de sistem ar putea, de exemplu, ghida un robot care depozitează/sortează/găsește diferite pachete într-un depozit aparținând unei companii de distribuție.
Am vrut să realizez un robot compact care să urmărească linia cu viteză mare. Specificațiile inițiale erau:
Am folosit placa realizată la prima etapă a proiectului. Schema acesteia se găsește aici. Senzorii și puntea H se conectează pe pinii de extensie ai plăcii.
Robotul dispune de 5 senzori asemănători celor folosiți la Pololu 3pi.
Senzorii se bazează pe posibilitatea de a schimba direcția pinilor uC-ului (input/output) și pe durata diferită de descărcare a unui condensator în funcție de nivelul încărcării. Un terminal al condensatorului este conectat în permanență la +5V, iar celălalt este conectat atât pe un pin al ADC-ului cât și la masă printr-un fototranzistor. Pentru a încărca condensatorul se setează pinul ca output și valoarea logică 1 (+5V). Pentru a citi valoarea se setează pinul ca input și se măsoara timpul până când tensiunea măsurată de ADC ajunge la 0. Dacă fototranzistorul este ”deschis” condensatorul se va descărca rapid, fiind conectat ”aproape” la masa. Pentru a controla fototranzistorul suprafața este luminată de un LED. În funcție de câtă lumină se reflectă (mai multă pe suprafețe dechise la culoare - de exemplu alb) fototranzistorul va descărca condensatorul mai repede sau mai lent.
Pentru că motoarele au nevoie de un curent relativ mare (~500mA cu sarcină normală; peste 1,5A când e blocat) am folosit o punte H L298N care suportă maxim 2A pe fiecare motor. Pinii ENABLE sunt comandați de uC folosind un semnal PWM pentru a regla turația. Pentru direcție se trimit valori 0/1 pe pinii INPUT.
Se folosesc 2 surse separate de alimentare:
Senzorii și logica punții H sunt alimentate cu +5V de la stabilizatorul care alimentează și uC-ul.
Nu știu ce caracteristici au motoarele, dar am măsurat un curent de ~500mA cu sarcină normală (inclusiv reductorul și roata) și peste 1,5A când e blocat la o tensiune de 6V. Pentru a crește cuplulul am montat un reductor ~4:1.
Șasiul este construit din plexiglass și placa de sticlotextolid.
Algoritmul de urmărire a liniei este unul banal pentru că nu am avut cu ce să testez altul (s-a ars puntea H; vezi Concluzii). Se iau decizii in funcție de intervalul rezultatului întors de funcția get_position() (mai multe detalii la descrierea acestei funcții).
/* ... */ /* loop forever */ while (1) { position = get_position(); if (position > 1000 && position < 3000) { /* centered */ SET_RIGHT_SPEED(200); SET_LEFT_SPEED(200); } else if (position < 1000) { /* turn left */ SET_RIGHT_SPEED(200); SET_LEFT_SPEED(0); } else { /* turn right */ SET_RIGHT_SPEED(0); SET_LEFT_SPEED(200); } } /* ... */
Pentru a citi un senzor se urmează următoarea procedură:
Îmbunătățiri:
#define MAX_SENSOR_TIME 1500 #define SENSOR_RESET_TIME 50 /* ms */ typedef enum { SENSOR_LLEFT = PA7, SENSOR_LEFT = PA6, SENSOR_CENTER = PA5, SENSOR_RIGHT = PA4, SENSOR_RRIGHT = PA3 } sensor; /* ... */ static inline void discharge_sensors() { /* set sensors ports as input (discharge capacitors) */ DDRA &= ~(_BV(PA7)); PORTA &= ~(_BV(PA7)); DDRA &= ~(_BV(PA6)); PORTA &= ~(_BV(PA6)); DDRA &= ~(_BV(PA5)); PORTA &= ~(_BV(PA5)); DDRA &= ~(_BV(PA4)); PORTA &= ~(_BV(PA4)); _delay_ms(SENSOR_RESET_TIME); } uint16_t read_sensor(sensor s) { uint16_t last_time, adc = 0, time = 0, i = 0; discharge_sensors(); /* +5V to charge the capacitor */ DDRA |= _BV(s); PORTA |= _BV(s); /* give it time to charge */ _delay_ms(SENSOR_RESET_TIME); /* turn port to input */ DDRA &= ~(_BV(s)); PORTA &= ~(_BV(s)); /* count discharge time */ last_time = TCNT1; adc = get_adc(s); while (adc > 30 && time < MAX_SENSOR_TIME) { uint16_t delta_time = TCNT1 - last_time; last_time += delta_time; time += delta_time; adc = get_adc(s); i++; } return time; }
Funcția întoarce un întreg care are semnificația descrisă în comentariu. Constantele *_WHITE vor fi determinate rulând funcția calibrate_sensors() (ATENȚIE: trebuie conectată placa la un terminal de serială și trebuie recompilat codul cu noile constante). Nu ar trebui să se modifice cele implicite, dar nu se știe.
/** * sensor max values for WHITE * (use calibration function to get these) */ #define SENSOR_LLEFT_WHITE 500 #define SENSOR_LEFT_WHITE 500 #define SENSOR_CENTER_WHITE 500 #define SENSOR_RIGHT_WHITE 500 #define SENSOR_RRIGHT_WHITE 500 /* ... */ /** * 0 - 4000 interval * 0 - 1000 = on the left side * 1000 - 3000 = on the line * 3000 - 4000 = on the right side */ unsigned int get_position() { static unsigned old_pos = 0; uint16_t s1, s2, s3, s4, s5; int sp, s; s1 = (read_sensor(SENSOR_LLEFT) > SENSOR_LLEFT_WHITE); s2 = (read_sensor(SENSOR_LEFT) > SENSOR_LEFT_WHITE); s3 = (read_sensor(SENSOR_CENTER) > SENSOR_CENTER_WHITE ); s4 = (read_sensor(SENSOR_RIGHT) > SENSOR_RIGHT_WHITE); s5 = (read_sensor(SENSOR_RRIGHT) > SENSOR_RRIGHT_WHITE); sp = (s1 * 0 + s2 * 1000 + s3 * 2000 + s4 * 3000 + s5 * 4000); s = (s1 + s2 + s3 + s4 + s5); if (sp == 0 && s1 == 0) { return old_pos; } else { old_pos = s == 0 ? 0 : (sp / s); return old_pos; } }
#define SET_DIR_LEFT_FORWARD \ do { \ PORTC |= _BV(PC0); \ PORTC &= ~(_BV(PC1)); \ } while(0) #define SET_DIR_LEFT_BACKWARD \ do { \ PORTC |= _BV(PC1); \ PORTC &= ~(_BV(PC0)); \ } while(0) #define SET_DIR_RIGHT_FORWARD \ do { \ PORTC |= _BV(PC3); \ PORTC &= ~(_BV(PC2)); \ } while(0) #define SET_DIR_RIGHT_BACKWARD \ do { \ PORTC |= _BV(PC2); \ PORTC &= ~(_BV(PC3)); \ } while(0) #define MOTOR_RIGHT_DISABLE \ do { \ TCCR0 &= ~(_BV(COM01)); \ PORTD &= ~(_BV(PD7)); \ } while(0) #define MOTOR_RIGHT_ENABLE TCCR0 |= _BV(COM01) #define MOTOR_LEFT_DISABLE \ do { \ TCCR2 &= ~(_BV(COM21)); \ PORTD &= ~(_BV(PD7)); \ } while(0) #define MOTOR_LEFT_ENABLE TCCR2 |= _BV(COM21) #define SET_RIGHT_SPEED(x) OCR0 = x #define SET_LEFT_SPEED(x) OCR2 = x
Mediu de dezvoltare: AVR Studio 4
Robotul a funcționat ~30s după care s-a ars jumătate din puntea H (sau cel puțin așa cred; PWM-ul și semnalele de comandă ajung la pinii L298N, dar la motor nu mai ajunge nimic).
Nu am reușit să testez dacă urmărește linia, dar am testat individual senzorii și par să funcționeze bine.
Ce nu a mers bine:
Ce a mers bine:
Ce am învățat:
Recomandări:
Codul sursă și proiectele Eagle pentru puntea H și senzori se găsesc în următoarea arhivă: 334cb_dumitrescu_vlad_pm2010_linef.zip