« SE3Binome2023-1 » : différence entre les versions
(30 versions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
[[Fichier:Titre page VOITURE.png|centré|sans_cadre|1116x1116px]] | |||
= Projet voiture Autonome = | = Projet voiture Autonome = | ||
=== Description === | === Description === | ||
Ligne 14 : | Ligne 8 : | ||
- La voiture sera capable de suivre des instructions données par l'utilisateur via USB (Avancer à une vitesse maximale pendant 30 secondes, tourner à droite pendant 10 secondes, reculer lentement pendant 15 secondes, etc) | - La voiture sera capable de suivre des instructions données par l'utilisateur via USB (Avancer à une vitesse maximale pendant 30 secondes, tourner à droite pendant 10 secondes, reculer lentement pendant 15 secondes, etc) | ||
- Le second mode est en autonomie. La voiture avance et lorsqu'elle détecte un mur, puis | - Le second mode est en autonomie. La voiture avance et lorsqu'elle détecte un mur elle recule, puis tourne à droite jusqu'à ce qu'elle puisse avancer à nouveau. | ||
Ce projet mêle tous les domaines des systèmes embarqués de la programmation d'un microcontrôleur au brasage des composants en passant par la gestion de la batterie. | |||
= Conception du PCB = | = Conception du PCB = | ||
== Schématique == | == Schématique == | ||
[[Fichier: | [[Fichier:Schématique finale.pdf|sans_cadre|672x672px]] | ||
== Routage == | == Routage == | ||
[[Fichier: | [[Fichier:Rootage final.pdf|sans_cadre|651x651px]] | ||
== PINOUT == | == PINOUT == | ||
Ligne 40 : | Ligne 28 : | ||
|- | |- | ||
|EMETTEUR | |EMETTEUR | ||
| | |PD4 | ||
|- | |- | ||
|LED2 | |LED2 | ||
Ligne 52 : | Ligne 40 : | ||
|- | |- | ||
|LED5 | |LED5 | ||
| | |PB0 | ||
|- | |- | ||
| | |PWM1 | ||
|PB7 | |PB7 | ||
|- | |- | ||
| | |PWM2 | ||
|PD0 | |PD0 | ||
|- | |- | ||
|AIN1 | |AIN1 | ||
| | |PD2 | ||
|- | |- | ||
|AIN2 | |AIN2 | ||
| | |PD1 | ||
|- | |- | ||
|BIN1 | |BIN1 | ||
Ligne 70 : | Ligne 58 : | ||
|- | |- | ||
|BIN2 | |BIN2 | ||
| | |PD5 | ||
|- | |- | ||
|MISO | |MISO | ||
Ligne 84 : | Ligne 72 : | ||
|RESET | |RESET | ||
|} | |} | ||
= Réalisation du PCB / conception mécanique: = | = Réalisation du PCB / conception mécanique: = | ||
=== Concept === | |||
=== Concept | |||
La voiture ne dispose pas de roue directrice. Le processus d'orientation du véhicule se fait donc via l'utilisation des moteurs dans le sens opposé pour faire tourner le véhicule. | La voiture ne dispose pas de roue directrice. Le processus d'orientation du véhicule se fait donc via l'utilisation des moteurs dans le sens opposé pour faire tourner le véhicule. | ||
Ligne 130 : | Ligne 85 : | ||
- 1 roue libre. | - 1 roue libre. | ||
Ce choix mécaniques permet au véhicule de tourner sans sautiller. | Ce choix mécaniques permet au véhicule de tourner sans sautiller. | ||
De la conception 3D a aussi été utilisée afin de concevoir un support de batterie et un support pour le capteur IR pour éviter que les composants soient en l'air. | |||
== Gestion de l’énergie de la batterie == | == Gestion de l’énergie de la batterie == | ||
Pour gérer la charge de la batterie, nous utilisons le MAX1811ESA+. Ce composant permet de gérer la charge de batterie 3,7V Lithium-ion. | |||
Pour recharger la batterie de la voiture, un autre port USB mini a été prévu uniquement pour cette tâche. | |||
Il faut faire attention à ne pas brancher les 2 connecteurs USB en même temps (même si une diode a été placée pour éviter les retours d'alimentations) et débrancher le cavalier situé près du connecteur de la batterie. | |||
== Brasage des composants sur le PCB == | == Brasage des composants sur le PCB == | ||
=== PCB vide | === PCB vide === | ||
[[Fichier:PCB_Vide.jpg|sans_cadre]] | [[Fichier:PCB_Vide.jpg|sans_cadre]] | ||
=== Brasage | === Brasage atmega16u4, driver moteur et USB sur le PCB === | ||
[[Fichier:Brasage 21-05.jpg|sans_cadre|399x399px]] | |||
=== Montage moteur et roue libre et correction de conception du PCB === | |||
[[Fichier:Montage moteur et roue libre.jpg|sans_cadre]] | |||
=== Fin du brasage de la carte === | |||
[[Fichier:Fin du brasage.jpg|sans_cadre]] | |||
== Conception mécanique == | |||
=== Support de moteur === | |||
[[Fichier:Support de moteur.jpg|sans_cadre]] | |||
=== Roues === | |||
[[Fichier:Roue.jpg|sans_cadre]] | |||
=== Support de batterie === | |||
[[Fichier:Support de batterie 1.jpg|sans_cadre]][[Fichier:Support de batterie 2.jpg|sans_cadre]] | |||
=== Support de capteur IR === | |||
[[Fichier:Capteur 1.jpg|sans_cadre]][[Fichier:Capteur 2.jpg|sans_cadre]] | |||
= Programmation du µC = | = Programmation du µC = | ||
=== | == PWM == | ||
[[Fichier:Pwm.svg|vignette|Signal analogique PWM que notre robot utilise]] | |||
Afin d'éviter à notre robot de ne pouvoir avancer seulement en tout ou rien, le choix a été fait d'utiliser des signaux PWM. | |||
Une Pulse Width Modulation ou Modulation de Largeur d'Impulsion est un signal dont le rapport cyclique varie. Dans notre cas, les signaux PWM nous permettent de modifier la valeur moyenne du signal afin que les moteurs tournent plus ou moins vite. | |||
Pour programmer un signal PWM sur un ATMEGA16u4 il faut : | |||
* définir les broches sur lesquelles les signaux sortiront : | |||
Lors de la datasheet du µC, vous pouvons voir certaines broches du type OCnx (n : un chiffre, x: une lettre), c'est celles-ci que nous recherchons. | |||
Le chiffre correspond au Timer et la lettre au comparateur. | |||
<u>Exemple </u> avec OC0B il s'agit du Timer 0 et sa valeur de comparaison sera OCR0B. | |||
* Configurer les registres : | |||
Chaque Timer a ses spécificités mais nous pouvons remarquer des similitudes entre les différents Timers (il faut croire qu'ils y ont réfléchis). | |||
Dans notre projet nous avons utilisé le Timer0 mais les autres font très bien l'affaire avec une particularité pour le Timer4. | |||
* '''METTRE LES BROCHES OCnx EN SORTIE''' | |||
[[Fichier:Capture d’écran 2024-06-12 19-44-27.png|vignette]] | |||
=== Timer0 === | |||
==== TCCR0A ==== | |||
* Les bits '''WGM00''' et '''WGM01''' permettent de choisir le '''mode du Timer0''' (Table 13-6), pour nous ce sera le '''mode 3, Fast PWM avec WGM01 = 1 et WGM00 = 1''' (et WGM02 = 0 situé dans le registre TCCR0B) | |||
* les bits '''COM0x0''' et '''COM0x1''' servent à définir ce qu'il se passera lorsque le registre '''TCNT0''' sera égal à '''OCR0n.''' | |||
Si l'on regarde la table 13-2, nous pouvons voir les différentes configurations possibles. Dans notre cas, nous seront en '''COM0A1 = 1 et COM0A0 = 0''' de sorte à ce que la tension moyenne soit en corrélation avec la valeur fixé de '''OCR0A'''. | |||
==== TCCR0B ==== | |||
* WGM02 a le même rôle que les autres WGM0n dans le registre TCCR0A. | |||
* '''CS00, CS01 et CS02''' sont des pré diviseurs qui permettent d'avoir une fréquence précise, dans notre cas ce n'est pas nécessaire donc '''CS00 = 1 et les reste à 0.''' | |||
* Nous n'utilisons pas d'interruption donc '''TIMSK0''' n'est pas utilisé. | |||
=== Timer4 === | |||
Le Timer4 est très similaire au Timer0, mais il a ses particularités. | |||
Son registre de compteur de coups d'horloge est sur 10 bits et donc 2 registres '''TCNT4 et TC4H ,''' ce dernier stockant les 2 MSB. | |||
Il possède aussi 4 registres de comparaison '''OCR4A, OCR4B, OCR4C et OCR4D''' cependant OCR4C est fixe et toujours placé à la valeur max possible du Timer4. | |||
Enfin, il existe des sorties complémentées <u>OC4x</u> (barre). '''Mais attention''' '''elles ne fonctionnent pas exactement en inverse''' car il faut ajuster '''t<sub>non-overlap / rising edge</sub>''' et '''t<sub>non-overlap / falling edge</sub>''' grâce à '''DT4H''' et '''DT4L''' (registre '''DT4'''). | |||
La configuration des registres se passe exactement de la même manière que pour le Timer0 (en adaptant le nom des registres bien sûr). | |||
'''<big>Seul élément important : Il faut activé le PWM avec le bit PWM4x dans le registre TCCR4A et/ou TCCR4C (15.12.1 / 15.12.2 de la datasheet).</big>''' | |||
== Conversion Analogique-Numérique == | |||
[[Fichier:ADC atmega.jpg|vignette|372x372px|Fonctionnement d'un ADC]] | |||
Pour utiliser notre capteur IR, une conversion analogique-numérique est nécessaire. | |||
Cela permet de transformer la tension de 0V-5V sur notre broche à une valeur sur 8bits. | |||
Les broches '''ADCx''' permettent cette conversion, dans notre cas nous utilisons la broche '''PD4 (ADC8).'''<syntaxhighlight lang="c" line="1" start="1"> | |||
void Sensor_init(){ | |||
ADMUX = 0x00; | |||
ADMUX |= (1<<REFS0) | (1<<ADLAR); | |||
ADMUX &= ~(1<<REFS1); | |||
DDRD &= ~(1<<PD4); | |||
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // Division de fréquence 128 => 125KHz | |||
ADCSRB |= (1<<MUX5); | |||
} | |||
unsigned int ad_capture(void){ // Acquisition de tension | |||
ADCSRA|=(1<<ADSC); // Début de conversion | |||
while(bit_is_set(ADCSRA, ADSC)); // Attente de la fin de conversion | |||
return ADCH; // Résultat sur 8 bits car ADLAR=1 | |||
} | |||
</syntaxhighlight> | |||
=== Déclaration des registres === | |||
==== ADMUX ==== | |||
* '''REFS1 et REFS0''' : Permettent de gérer la tension de référence de l'ADC | |||
* '''ADLAR''' : Permet de décaler les bits vers la gauche au niveau des registres '''ADCL''' et '''ADCH''' (dans ce mode, ADCH contient les bits de poids les plus forts) | |||
* '''MUX0..4 :''' Permet de choisir sur quelle entrée aura lieu la conversion. Dans notre cas les 4 bits seront nuls (Table 24-4 de la datasheet). | |||
==== ADCSRA ==== | |||
* '''ADEN :''' Autorisation de la conversion | |||
* '''ADSP0..2 :''' Pré diviseurs utilisés pour éviter de faire des mesures trop rapides. | |||
* '''ADSC :''' Activation de la conversion | |||
==== ADCSRB ==== | |||
* '''MUX5''' : Permet de choisir sur quelle entrée aura lieu la conversion. Dans notre cas '''MUX5 = 1.''' | |||
=== Utilisation === | |||
La fonction '''ad_capture()''' permet de convertir sur la broche préalablement configurée. Elle attend que ADSC soit mise à 1 grâce à '''bit_is_set()''' et renvoie la valeur stockée dans le registre ADCH. | |||
Lors de l'utilisation des ADC, il est très importants de faire une conversion à la fois. C'est-à-dire que si plusieurs broches doivent être converties, il faut refaire une déclaration des registres avant de faire la conversions. | |||
''NB : Dans notre code final, nous pouvons laisser '''Sensor_init()''' en dehors de la boucle '''while()''' car nous avons un seul ADC.'' | |||
==== Fonctions | <u>Exemple :</u> <syntaxhighlight lang="c"> | ||
void Sensor_init_ADC8(){ | |||
ADMUX = 0x00; | |||
ADMUX |= (1<<REFS0) | (1<<ADLAR); | |||
ADMUX &= ~(1<<REFS1); | |||
DDRD &= ~(1<<PD4); | |||
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // Division de fréquence 128 => 125KHz | |||
ADCSRB |= (1<<MUX5); | |||
} | |||
void Sensor_init_ADC5(){ | |||
ADMUX = 0x00; | |||
ADMUX |= (1<<REFS0) | (1<<ADLAR) | (1<<MUX2) | (1<<MUX0); | |||
ADMUX &= ~(1<<REFS1); | |||
DDRD &= ~(1<<PF5); | |||
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // Division de fréquence 128 => 125KHz | |||
} | |||
unsigned int ad_capture(void){ // Acquisition de tension | |||
ADCSRA|=(1<<ADSC); // Début de conversion | |||
while(bit_is_set(ADCSRA, ADSC)); // Attente de la fin de conversion | |||
return ADCH; // Résultat sur 8 bits car ADLAR=1 | |||
} | |||
int main() | |||
{ | |||
int valeur_ADC8= 0; | |||
int valeur_ADC5= 0; | |||
while(1) | |||
{ | |||
Sensor_init_ADC5(); | |||
valeur_ADC5=ad_capture(); // conversion et récupération de la valeur de ADC5 | |||
Sensor_init_ADC8(); | |||
valeur_ADC8=ad_capture(); // conversion et récupération de la valeur de ADC8 | |||
... | |||
} | |||
return 0; | |||
} | |||
</syntaxhighlight> | |||
== Mode contrôle USB == | |||
==== Fonctions ==== | |||
- Avancer, choix de la vitesse et de la durée. | - Avancer, choix de la vitesse et de la durée. | ||
Ligne 158 : | Ligne 277 : | ||
- Tourner à gauche, choix de la durée avec activation du clignotant gauche. | - Tourner à gauche, choix de la durée avec activation du clignotant gauche. | ||
== | == Mode autonome == | ||
=== Contrôle des moteurs === | |||
Pour contrôler les moteurs, nous avons codé une fonction prenant 4 paramètres ; | |||
* la vitesse des moteurs | |||
* le sens du moteur gauche | |||
* le sens du moteur droit | |||
* l'activation du frein | |||
<syntaxhighlight lang="c"> | |||
void PWM_init(void) | |||
{ | |||
TCCR0A |= (1<<COM0A1) | (1<<COM0B1); | |||
TCCR0A &= ~((1<<COM0A0) | (1<<COM0B0)); | |||
TCCR0A |= (1<<WGM00); | |||
TCCR0A |= (1<<WGM01); | |||
TCCR0B &= (1<<WGM02); | |||
TCCR0B |= (1<<CS00); | |||
TCCR0B &= ~(1<<CS01); | |||
TCCR0B &= ~(1<<CS02); | |||
DDRB |= (1<< PWM_Moteur_D); | |||
DDRD |= (1<<PWM_Moteur_G) | |||
| (1<<Dir_MD1) | |||
| (1<<Dir_MD2) | |||
| (1<<Dir_MG1) | |||
| (1<<Dir_MG2); | |||
} | |||
void controle_mot(int vitesse, int sens_mot_g, int sens_mot_d, int frein) | |||
{ | |||
if(sens_mot_g == 0) // le moteur gauche va en arrière | |||
{ | |||
PORTD |= (1<<Dir_MG1); | |||
PORTD &= ~(1<<Dir_MG2); | |||
} | |||
else // le moteur gauche va en avant | |||
{ | |||
PORTD &= ~(1<<Dir_MG1); | |||
PORTD |= (1<<Dir_MG2); | |||
} | |||
if(sens_mot_d == 0) // le moteur droit va en arrière | |||
{ | |||
PORTD |= (1<<Dir_MD1); | |||
PORTD &= ~(1<<Dir_MD2); | |||
} | |||
else // le moteur droit va en avant | |||
{ | |||
PORTD &= ~(1<<Dir_MD1); | |||
PORTD |= (1<<Dir_MD2); | |||
} | |||
if(frein != 0) // si le frein est désactivé, je choisis la vitesse moteur | |||
{ | |||
OCR0A = vitesse*25.5; | |||
OCR0B = vitesse*25.5; | |||
} | |||
else // je freine | |||
{ | |||
OCR0A = 0; | |||
OCR0B = 0; | |||
} | |||
} | |||
</syntaxhighlight> | |||
=== Contrôle des LEDs === | |||
Pour contrôler les LEDs, nous avons codé une fonction prenant 3 paramètres ; | |||
* l'activation ou non de la LED gauche | |||
* l'activation ou non de la LED droite | |||
* l'activation ou non de la LED de frein | |||
<syntaxhighlight lang="c"> | |||
void controle_led(int sens_mot_g, int sens_mot_d, int frein) | |||
{ | |||
if(sens_mot_g == 0) PORTC &= ~(1<<Blinker_G); // éteindre la LED gauche | |||
else PORTC |= (1<<Blinker_G); // allumer la LED gauche | |||
if(sens_mot_d == 0) PORTB &= ~(1<<Blinker_D); // éteindre la LED droite | |||
else PORTB |= (1<<Blinker_D); // allumer la LED droite | |||
if(frein == 1) PORTB |= (1<<Brake); // allumer la LED frein | |||
else PORTB &= ~(1<<Brake); // éteindre la LED frein | |||
} | |||
</syntaxhighlight> | |||
=== Détection d'un obstacle === | |||
Le véhicule avance et lorsqu'il détecte un obstacle, il recule, tourne à gauche, et poursuit son chemin. | |||
Il faut savoir que le capteur est sensible à la lumière du jour (le soleil émet des infrarouges). Il est donc fort probable que la voiture détecte des obstacles ne serait-ce qu'en regardant vers une fenêtre. <syntaxhighlight lang="c"> | |||
void Sensor_init(){ | |||
ADMUX = 0x00; | |||
ADMUX |= (1<<REFS0) | (1<<ADLAR); | |||
ADMUX &= ~(1<<REFS1); | |||
DDRD &= ~(1<<PD4); | |||
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // Division de fréquence 128 => 125KHz | |||
ADCSRB |= (1<<MUX5); | |||
} | |||
unsigned int ad_capture(void){ // Acquisition de tension | |||
ADCSRA|=(1<<ADSC); // Début de conversion | |||
while(bit_is_set(ADCSRA, ADSC)); // Attente de la fin de conversion | |||
return ADCH; // Résultat sur 8 bits car ADLAR=1 | |||
} | |||
int main() | |||
{ | |||
PWM_init(); | |||
Sensor_init(); | |||
PORTD |= (1<<Dir_MD1) | (1<<Dir_MG1); | |||
PORTD &= ~((1<<Dir_MD2) | (1<<Dir_MG2)); | |||
int valeur_IR = 0; | |||
while(1) | |||
{ | |||
valeur_IR=ad_capture(); | |||
if(valeur_IR > 240) controle_mot(10,1,1,1); // si pas d'obstacle, on avance | |||
else esquive(); // si obstacle, on esquive | |||
} | |||
return 0; | |||
} | |||
void esquive(){ | |||
// on freine | |||
controle_mot(0,0,0,0); | |||
controle_led(0,0,1); | |||
_delay_ms(500); | |||
controle_led(0,0,0); | |||
// on recule | |||
controle_mot(10,0,0,1); | |||
for(int i=0; i<2; i++) | |||
{ | |||
controle_led(1,1,0); | |||
_delay_ms(250); | |||
controle_led(0,0,0); | |||
_delay_ms(250); | |||
} | |||
// on tourne à gauche | |||
controle_mot(10,1,0,1); | |||
for(int i=0; i<2; i++) | |||
{ | |||
controle_led(1,0,0); | |||
_delay_ms(250); | |||
controle_led(0,0,0); | |||
_delay_ms(250); | |||
} | |||
} | |||
</syntaxhighlight> | |||
= Tests du véhicule = | |||
== Tests en autonomie == | |||
=== Vidéo de test en marche avant === | |||
[[Fichier:Voiture marche avant.mp4|sans_cadre]] | |||
=== Vidéo de test en marche avant puis virage à gauche === | |||
[[Fichier:AVANT PUIS GAUCHE.mp4|sans_cadre]] | |||
=== Vidéo 0 à 100 === | |||
[[Fichier:0-100.mp4|sans_cadre]] | |||
=== Allumage des LEDs en fonction des actions === | |||
[[Fichier:Video LEDs.mp4|sans_cadre]] | |||
Le clignotant gauche s'allume lorsque la voiture tourne à gauche et inversement pour un déplacement vers la droite. Lorsque la voiture recule, les feux de détresse s'actionnent et lorsque le frein est actionné, le feu stop s'allume. | |||
=== Détection d'un obstacle === | |||
[[Fichier:Détection.mp4|sans_cadre]] | |||
La voiture avance et lorsqu'elle détecte un obstacle, recule, tourne vers la gauche et poursuit son chemin jusqu'au prochain obstacle. | |||
''NB : comme on peut le constater dans la vidéo, lors de la rencontre avec le premier obstacle, la voiture effectue bien une marche arrière et une rotation avant de repartir en avant.'' | |||
''Cependant, lors de la rencontre avec le second obstacle, la voiture a effectué deux rotations, en effet après avoir effectué sa première rotation, le capteur était placé face au soleil (certes un petit soleil lillois, mais soleil quand même) qui a donc émit de l'infrarouge dans le capteur qui a donc analysé cela comme un obstacle, d'où la seconde rotation.'' | |||
== Test de la recharge == | |||
[[Fichier:Charge batterie.jpg|sans_cadre]] | |||
La recharge s'effectue parfaitement, pour charger la batterie il faut délier la batterie du reste de la carte en enlevant le cavalier prévu à cet effet puis brancher un câble USB mini sur le port en haut à droite de la carte sur la photo. | |||
= Archive = | = Archive = | ||
GIT : https://archives.plil.fr/vdetrez/DETREZ_CART_programmation_des_systemes_embarques.git | GIT : https://archives.plil.fr/vdetrez/DETREZ_CART_programmation_des_systemes_embarques.git |
Version actuelle datée du 12 juin 2024 à 21:43
Projet voiture Autonome
Description
Réalisation d'une voiture avec deux modes de fonctionnements.
- La voiture sera capable de suivre des instructions données par l'utilisateur via USB (Avancer à une vitesse maximale pendant 30 secondes, tourner à droite pendant 10 secondes, reculer lentement pendant 15 secondes, etc)
- Le second mode est en autonomie. La voiture avance et lorsqu'elle détecte un mur elle recule, puis tourne à droite jusqu'à ce qu'elle puisse avancer à nouveau.
Ce projet mêle tous les domaines des systèmes embarqués de la programmation d'un microcontrôleur au brasage des composants en passant par la gestion de la batterie.
Conception du PCB
Schématique
Routage
PINOUT
NOM | PIN |
---|---|
EMETTEUR | PD4 |
LED2 | PB4 |
LED3 | PB5 |
LED4 | PC6 |
LED5 | PB0 |
PWM1 | PB7 |
PWM2 | PD0 |
AIN1 | PD2 |
AIN2 | PD1 |
BIN1 | PD3 |
BIN2 | PD5 |
MISO | PB3 |
MOSI | PB2 |
SCK | PB1 |
RST | RESET |
Réalisation du PCB / conception mécanique:
Concept
La voiture ne dispose pas de roue directrice. Le processus d'orientation du véhicule se fait donc via l'utilisation des moteurs dans le sens opposé pour faire tourner le véhicule.
Cependant, avec 4 roues ne possédant qu'un degrés de liberté le véhicule risque de sautiller pendant les phases d'orientation.
Il a donc été choisi de placer 3 roues sur le véhicule:
- 2 roues motrices.
- 1 roue libre.
Ce choix mécaniques permet au véhicule de tourner sans sautiller.
De la conception 3D a aussi été utilisée afin de concevoir un support de batterie et un support pour le capteur IR pour éviter que les composants soient en l'air.
Gestion de l’énergie de la batterie
Pour gérer la charge de la batterie, nous utilisons le MAX1811ESA+. Ce composant permet de gérer la charge de batterie 3,7V Lithium-ion.
Pour recharger la batterie de la voiture, un autre port USB mini a été prévu uniquement pour cette tâche.
Il faut faire attention à ne pas brancher les 2 connecteurs USB en même temps (même si une diode a été placée pour éviter les retours d'alimentations) et débrancher le cavalier situé près du connecteur de la batterie.
Brasage des composants sur le PCB
PCB vide
Brasage atmega16u4, driver moteur et USB sur le PCB
Montage moteur et roue libre et correction de conception du PCB
Fin du brasage de la carte
Conception mécanique
Support de moteur
Roues
Support de batterie
Support de capteur IR
Programmation du µC
PWM
Afin d'éviter à notre robot de ne pouvoir avancer seulement en tout ou rien, le choix a été fait d'utiliser des signaux PWM.
Une Pulse Width Modulation ou Modulation de Largeur d'Impulsion est un signal dont le rapport cyclique varie. Dans notre cas, les signaux PWM nous permettent de modifier la valeur moyenne du signal afin que les moteurs tournent plus ou moins vite.
Pour programmer un signal PWM sur un ATMEGA16u4 il faut :
- définir les broches sur lesquelles les signaux sortiront :
Lors de la datasheet du µC, vous pouvons voir certaines broches du type OCnx (n : un chiffre, x: une lettre), c'est celles-ci que nous recherchons.
Le chiffre correspond au Timer et la lettre au comparateur.
Exemple avec OC0B il s'agit du Timer 0 et sa valeur de comparaison sera OCR0B.
- Configurer les registres :
Chaque Timer a ses spécificités mais nous pouvons remarquer des similitudes entre les différents Timers (il faut croire qu'ils y ont réfléchis).
Dans notre projet nous avons utilisé le Timer0 mais les autres font très bien l'affaire avec une particularité pour le Timer4.
- METTRE LES BROCHES OCnx EN SORTIE
Timer0
TCCR0A
- Les bits WGM00 et WGM01 permettent de choisir le mode du Timer0 (Table 13-6), pour nous ce sera le mode 3, Fast PWM avec WGM01 = 1 et WGM00 = 1 (et WGM02 = 0 situé dans le registre TCCR0B)
- les bits COM0x0 et COM0x1 servent à définir ce qu'il se passera lorsque le registre TCNT0 sera égal à OCR0n.
Si l'on regarde la table 13-2, nous pouvons voir les différentes configurations possibles. Dans notre cas, nous seront en COM0A1 = 1 et COM0A0 = 0 de sorte à ce que la tension moyenne soit en corrélation avec la valeur fixé de OCR0A.
TCCR0B
- WGM02 a le même rôle que les autres WGM0n dans le registre TCCR0A.
- CS00, CS01 et CS02 sont des pré diviseurs qui permettent d'avoir une fréquence précise, dans notre cas ce n'est pas nécessaire donc CS00 = 1 et les reste à 0.
- Nous n'utilisons pas d'interruption donc TIMSK0 n'est pas utilisé.
Timer4
Le Timer4 est très similaire au Timer0, mais il a ses particularités.
Son registre de compteur de coups d'horloge est sur 10 bits et donc 2 registres TCNT4 et TC4H , ce dernier stockant les 2 MSB.
Il possède aussi 4 registres de comparaison OCR4A, OCR4B, OCR4C et OCR4D cependant OCR4C est fixe et toujours placé à la valeur max possible du Timer4.
Enfin, il existe des sorties complémentées OC4x (barre). Mais attention elles ne fonctionnent pas exactement en inverse car il faut ajuster tnon-overlap / rising edge et tnon-overlap / falling edge grâce à DT4H et DT4L (registre DT4).
La configuration des registres se passe exactement de la même manière que pour le Timer0 (en adaptant le nom des registres bien sûr).
Seul élément important : Il faut activé le PWM avec le bit PWM4x dans le registre TCCR4A et/ou TCCR4C (15.12.1 / 15.12.2 de la datasheet).
Conversion Analogique-Numérique
Pour utiliser notre capteur IR, une conversion analogique-numérique est nécessaire.
Cela permet de transformer la tension de 0V-5V sur notre broche à une valeur sur 8bits.
Les broches ADCx permettent cette conversion, dans notre cas nous utilisons la broche PD4 (ADC8).
void Sensor_init(){
ADMUX = 0x00;
ADMUX |= (1<<REFS0) | (1<<ADLAR);
ADMUX &= ~(1<<REFS1);
DDRD &= ~(1<<PD4);
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // Division de fréquence 128 => 125KHz
ADCSRB |= (1<<MUX5);
}
unsigned int ad_capture(void){ // Acquisition de tension
ADCSRA|=(1<<ADSC); // Début de conversion
while(bit_is_set(ADCSRA, ADSC)); // Attente de la fin de conversion
return ADCH; // Résultat sur 8 bits car ADLAR=1
}
Déclaration des registres
ADMUX
- REFS1 et REFS0 : Permettent de gérer la tension de référence de l'ADC
- ADLAR : Permet de décaler les bits vers la gauche au niveau des registres ADCL et ADCH (dans ce mode, ADCH contient les bits de poids les plus forts)
- MUX0..4 : Permet de choisir sur quelle entrée aura lieu la conversion. Dans notre cas les 4 bits seront nuls (Table 24-4 de la datasheet).
ADCSRA
- ADEN : Autorisation de la conversion
- ADSP0..2 : Pré diviseurs utilisés pour éviter de faire des mesures trop rapides.
- ADSC : Activation de la conversion
ADCSRB
- MUX5 : Permet de choisir sur quelle entrée aura lieu la conversion. Dans notre cas MUX5 = 1.
Utilisation
La fonction ad_capture() permet de convertir sur la broche préalablement configurée. Elle attend que ADSC soit mise à 1 grâce à bit_is_set() et renvoie la valeur stockée dans le registre ADCH.
Lors de l'utilisation des ADC, il est très importants de faire une conversion à la fois. C'est-à-dire que si plusieurs broches doivent être converties, il faut refaire une déclaration des registres avant de faire la conversions.
NB : Dans notre code final, nous pouvons laisser Sensor_init() en dehors de la boucle while() car nous avons un seul ADC.
Exemple :
void Sensor_init_ADC8(){
ADMUX = 0x00;
ADMUX |= (1<<REFS0) | (1<<ADLAR);
ADMUX &= ~(1<<REFS1);
DDRD &= ~(1<<PD4);
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // Division de fréquence 128 => 125KHz
ADCSRB |= (1<<MUX5);
}
void Sensor_init_ADC5(){
ADMUX = 0x00;
ADMUX |= (1<<REFS0) | (1<<ADLAR) | (1<<MUX2) | (1<<MUX0);
ADMUX &= ~(1<<REFS1);
DDRD &= ~(1<<PF5);
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // Division de fréquence 128 => 125KHz
}
unsigned int ad_capture(void){ // Acquisition de tension
ADCSRA|=(1<<ADSC); // Début de conversion
while(bit_is_set(ADCSRA, ADSC)); // Attente de la fin de conversion
return ADCH; // Résultat sur 8 bits car ADLAR=1
}
int main()
{
int valeur_ADC8= 0;
int valeur_ADC5= 0;
while(1)
{
Sensor_init_ADC5();
valeur_ADC5=ad_capture(); // conversion et récupération de la valeur de ADC5
Sensor_init_ADC8();
valeur_ADC8=ad_capture(); // conversion et récupération de la valeur de ADC8
...
}
return 0;
}
Mode contrôle USB
Fonctions
- Avancer, choix de la vitesse et de la durée.
- Freiner, arrêt du véhicule avec activation du feux stop.
- Reculer, choix de la vitesse et de la durée avec activation des feux de détresses.
- Tourner à droite, choix de la durée avec activation du clignotant droit.
- Tourner à gauche, choix de la durée avec activation du clignotant gauche.
Mode autonome
Contrôle des moteurs
Pour contrôler les moteurs, nous avons codé une fonction prenant 4 paramètres ;
- la vitesse des moteurs
- le sens du moteur gauche
- le sens du moteur droit
- l'activation du frein
void PWM_init(void)
{
TCCR0A |= (1<<COM0A1) | (1<<COM0B1);
TCCR0A &= ~((1<<COM0A0) | (1<<COM0B0));
TCCR0A |= (1<<WGM00);
TCCR0A |= (1<<WGM01);
TCCR0B &= (1<<WGM02);
TCCR0B |= (1<<CS00);
TCCR0B &= ~(1<<CS01);
TCCR0B &= ~(1<<CS02);
DDRB |= (1<< PWM_Moteur_D);
DDRD |= (1<<PWM_Moteur_G)
| (1<<Dir_MD1)
| (1<<Dir_MD2)
| (1<<Dir_MG1)
| (1<<Dir_MG2);
}
void controle_mot(int vitesse, int sens_mot_g, int sens_mot_d, int frein)
{
if(sens_mot_g == 0) // le moteur gauche va en arrière
{
PORTD |= (1<<Dir_MG1);
PORTD &= ~(1<<Dir_MG2);
}
else // le moteur gauche va en avant
{
PORTD &= ~(1<<Dir_MG1);
PORTD |= (1<<Dir_MG2);
}
if(sens_mot_d == 0) // le moteur droit va en arrière
{
PORTD |= (1<<Dir_MD1);
PORTD &= ~(1<<Dir_MD2);
}
else // le moteur droit va en avant
{
PORTD &= ~(1<<Dir_MD1);
PORTD |= (1<<Dir_MD2);
}
if(frein != 0) // si le frein est désactivé, je choisis la vitesse moteur
{
OCR0A = vitesse*25.5;
OCR0B = vitesse*25.5;
}
else // je freine
{
OCR0A = 0;
OCR0B = 0;
}
}
Contrôle des LEDs
Pour contrôler les LEDs, nous avons codé une fonction prenant 3 paramètres ;
- l'activation ou non de la LED gauche
- l'activation ou non de la LED droite
- l'activation ou non de la LED de frein
void controle_led(int sens_mot_g, int sens_mot_d, int frein)
{
if(sens_mot_g == 0) PORTC &= ~(1<<Blinker_G); // éteindre la LED gauche
else PORTC |= (1<<Blinker_G); // allumer la LED gauche
if(sens_mot_d == 0) PORTB &= ~(1<<Blinker_D); // éteindre la LED droite
else PORTB |= (1<<Blinker_D); // allumer la LED droite
if(frein == 1) PORTB |= (1<<Brake); // allumer la LED frein
else PORTB &= ~(1<<Brake); // éteindre la LED frein
}
Détection d'un obstacle
Le véhicule avance et lorsqu'il détecte un obstacle, il recule, tourne à gauche, et poursuit son chemin.
Il faut savoir que le capteur est sensible à la lumière du jour (le soleil émet des infrarouges). Il est donc fort probable que la voiture détecte des obstacles ne serait-ce qu'en regardant vers une fenêtre.
void Sensor_init(){
ADMUX = 0x00;
ADMUX |= (1<<REFS0) | (1<<ADLAR);
ADMUX &= ~(1<<REFS1);
DDRD &= ~(1<<PD4);
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); // Division de fréquence 128 => 125KHz
ADCSRB |= (1<<MUX5);
}
unsigned int ad_capture(void){ // Acquisition de tension
ADCSRA|=(1<<ADSC); // Début de conversion
while(bit_is_set(ADCSRA, ADSC)); // Attente de la fin de conversion
return ADCH; // Résultat sur 8 bits car ADLAR=1
}
int main()
{
PWM_init();
Sensor_init();
PORTD |= (1<<Dir_MD1) | (1<<Dir_MG1);
PORTD &= ~((1<<Dir_MD2) | (1<<Dir_MG2));
int valeur_IR = 0;
while(1)
{
valeur_IR=ad_capture();
if(valeur_IR > 240) controle_mot(10,1,1,1); // si pas d'obstacle, on avance
else esquive(); // si obstacle, on esquive
}
return 0;
}
void esquive(){
// on freine
controle_mot(0,0,0,0);
controle_led(0,0,1);
_delay_ms(500);
controle_led(0,0,0);
// on recule
controle_mot(10,0,0,1);
for(int i=0; i<2; i++)
{
controle_led(1,1,0);
_delay_ms(250);
controle_led(0,0,0);
_delay_ms(250);
}
// on tourne à gauche
controle_mot(10,1,0,1);
for(int i=0; i<2; i++)
{
controle_led(1,0,0);
_delay_ms(250);
controle_led(0,0,0);
_delay_ms(250);
}
}
Tests du véhicule
Tests en autonomie
Vidéo de test en marche avant
Vidéo de test en marche avant puis virage à gauche
Vidéo 0 à 100
Allumage des LEDs en fonction des actions
Le clignotant gauche s'allume lorsque la voiture tourne à gauche et inversement pour un déplacement vers la droite. Lorsque la voiture recule, les feux de détresse s'actionnent et lorsque le frein est actionné, le feu stop s'allume.
Détection d'un obstacle
La voiture avance et lorsqu'elle détecte un obstacle, recule, tourne vers la gauche et poursuit son chemin jusqu'au prochain obstacle.
NB : comme on peut le constater dans la vidéo, lors de la rencontre avec le premier obstacle, la voiture effectue bien une marche arrière et une rotation avant de repartir en avant.
Cependant, lors de la rencontre avec le second obstacle, la voiture a effectué deux rotations, en effet après avoir effectué sa première rotation, le capteur était placé face au soleil (certes un petit soleil lillois, mais soleil quand même) qui a donc émit de l'infrarouge dans le capteur qui a donc analysé cela comme un obstacle, d'où la seconde rotation.
Test de la recharge
La recharge s'effectue parfaitement, pour charger la batterie il faut délier la batterie du reste de la carte en enlevant le cavalier prévu à cet effet puis brancher un câble USB mini sur le port en haut à droite de la carte sur la photo.
Archive
GIT : https://archives.plil.fr/vdetrez/DETREZ_CART_programmation_des_systemes_embarques.git