« SE3 2025/2026 EC3 » : différence entre les versions
| (9 versions intermédiaires par un autre utilisateur non affichées) | |||
| Ligne 1 : | Ligne 1 : | ||
= Objectifs = | = Objectifs = | ||
Réaliser le programme "capteurs et actionneurs" du cours/TP concernant la programmation en C sur microcontrôleur AVR : | Réaliser le programme "capteurs et actionneurs" du cours/TP concernant la programmation en C sur microcontrôleur AVR : | ||
* contrôlez un servo-moteur en utilisant le minuteur 0 ; | * contrôlez un servo-moteur en utilisant le minuteur 0 ; | ||
| Ligne 20 : | Ligne 19 : | ||
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments . | * 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments . | ||
* 05/03/2026 : Création de la logique d'affichage . | * 05/03/2026 : Création de la logique d'affichage . | ||
* 09/04/2026 : Correction du code et de la mise en page du Wiki. | |||
=Travail réalisé= | == Travail réalisé == | ||
=== Jour 1 : Moteur et Boutons === | |||
Pour cette première étape, j’ai choisi d’avancer progressivement. L’objectif était simplement de lire les boutons S1 et S2 afin de commander la rotation du servo-moteur dans les deux sens. | Pour cette première étape, j’ai choisi d’avancer progressivement. L’objectif était simplement de lire les boutons S1 et S2 afin de commander la rotation du servo-moteur dans les deux sens. | ||
==== Configuration des boutons ==== | |||
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59) | En consultant la datasheet ATmega328P, la section 13.2.1 (page 59) : | ||
Lorsqu’une broche est configurée en entrée (DDR = 0) et que l’on écrit un 1 dans le registre PORT correspondant, la résistance pull-up interne est activée. Cela maintient la broche à 5V au repos. | |||
on a donc : | on a donc : | ||
Bouton non appuyé : niveau 1 | |||
Bouton appuyé : niveau 0 | |||
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui. | C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui. | ||
==== Désactivation du buzzer du shield ==== | |||
Au démarrage, le buzzer du shield émettait un signal sonore en continu. | Au démarrage, le buzzer du shield émettait un signal sonore en continu. Après vérification, celui-ci est piloté par un transistor PNP Q1. Pour le désactiver, il faut imposer un niveau logique haut (5V) sur la broche de commande. D’après la table 13-1 (page 60), cela se fait en configurant la broche en sortie DDR = 1 et en forçant le bit correspondant du registre PORT à 1. J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme. | ||
Après vérification, celui-ci est piloté par un transistor PNP Q1. Pour le désactiver, il faut imposer un niveau logique haut (5V) sur la broche de commande. D’après la table 13-1 (page 60), cela se fait en configurant la broche en sortie DDR = 1 et en forçant le bit correspondant du registre PORT à 1. | |||
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme. | |||
==== Génération du signal PWM pour le servo ==== | |||
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. | Le servo-moteur nécessite un signal PWM d’environ 60 Hz. J’ai utilisé le Timer 0 en mode Fast PWM (Mode 3), en activant les bits WGM01 et WGM00, conformément à la table 14-8 (page 86). Dans ce mode, le compteur évolue de 0 à 255. Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). | ||
J’ai utilisé le Timer 0 en mode Fast PWM (Mode 3), en activant les bits WGM01 et WGM00, conformément à la table 14-8 (page 86). | |||
Étant donné que le microcontrôleur fonctionne à 16 MHz, j’ai appliqué un prescaler de 1024 via les bits CS02 et CS00 (page 87). Le calcul de la fréquence obtenue est le suivant : | Étant donné que le microcontrôleur fonctionne à 16 MHz, j’ai appliqué un prescaler de 1024 via les bits CS02 et CS00 (page 87). Le calcul de la fréquence obtenue est le suivant : | ||
f = 16,000,000 / (1024 * 256) = 61 Hz | |||
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard. | La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard. | ||
==== Justification des valeurs matérielles pour le servo Hitec HS-422 ==== | |||
Le moteur utilisé pour ce projet est un servo | Le moteur utilisé pour ce projet est un servo Hitec HS-422. En étudiant ses spécifications techniques officielles, le constructeur définit la plage de fonctionnement suivante : | ||
Le point central (neutre) est fixé à 1500 µs (1,5 ms). | |||
La plage d'impulsion garantie (Pulse Width) s'étend de 900 µs (0,9 ms) à 2100 µs (2,1 ms). | |||
Calcul des valeurs du registre OCR0A : | |||
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un tic) du Timer 0 est de : | |||
T_tic = 1024 / 16,000,000 = 0,064 ms | |||
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic : | |||
Point central (1,5 ms) : 1,5 / 0,064 = 23,43. En arrondissant, on obtient 23. | |||
Butée minimale constructeur (0,9 ms) : 0,9 / 0,064 = 14,06. La limite basse est de 14. | |||
Butée maximale constructeur (2,1 ms) : 2,1 / 0,064 = 32,81. La limite haute est de 33. | |||
Dans le code final, les limites ont été étendues de 8 à 38 pour exploiter le maximum la mécanique du servo. | |||
=== Jour 2 : Affichage et Timer 2 === | |||
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield | Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. | ||
==== 1. Comment on parle à l'afficheur ==== | |||
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches : | Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches : | ||
DATA (PB0) : pour envoyer les infos bit par bit. | |||
CLOCK (PD7) : pour donner le rythme. | |||
LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé. | |||
==== 2. L'affichage (Multiplexage) ==== | |||
On utilise la persistance rétinienne : on allume chaque chiffre l'un après l'autre très vite. L'œil ne voit pas le clignotement et on a l'impression que tout est allumé. | |||
==== 3. Pourquoi le Timer 2 et le mode CTC ? ==== | |||
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms. | Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms. | ||
J'ai choisi le mode CTC (Clear Timer on Compare Match). Le timer se remet à zéro quand il atteint la cible OCR2A. Pour avoir 1 ms, j'ai mis 249 dans OCR2A. | |||
=== Jour 3 : Calcul de l'angle et logique d'affichage === | |||
==== 1. Calcul de l'angle ==== | |||
* | La valeur pos varie entre 8 (0°) et 38 (180°). La plage est de 30 unités. | ||
La formule mathématique intégrée est : angle = (uint32_t)(pos - 8) * 180 / 30; | |||
L'utilisation de uint32_t permet d'éviter un dépassement de capacité en mémoire avant la division. | |||
==== 2. Communication avec les puces ==== | |||
Le Shield utilise des registres à décalage. On envoie d'abord l'octet qui définit les segments à allumer, puis l'octet pour choisir l'afficheur. Une fois les 16 bits envoyés, on active la broche LATCH. | |||
=== Jour 4 : L'assemblage final et les interruptions === | |||
==== 1. La fonction d'interruption : ISR ==== | |||
Pour | Pour que l'affichage se fasse seul en arrière-plan, j'utilise la fonction ISR(TIMER2_COMPA_vect). Elle met à jour un seul afficheur à chaque appel pour ne pas bloquer le processeur. On utilise une variable static uint8_t digit pour alterner entre les 3 chiffres. | ||
==== 2. Le programme principal libéré ==== | |||
Grâce à cette interruption, la boucle principale while(1) s'occupe uniquement de la logique utilisateur : lecture des boutons et mise à jour de la position. | |||
==== 3. Pourquoi le delay ne fait pas clignoter l'écran ? ==== | |||
Le _delay_ms(100) bloque le programme principal mais pas le Timer 2 qui est matériel. Toutes les 1 ms, l'interruption se déclenche quand même pour rafraîchir l'écran. | |||
=== Bilan et rendu === | |||
Le projet répond à toutes les contraintes (deux timers, Fast PWM, multiplexage et interruptions). | |||
==== 1. Fonctions d'initialisation ==== | |||
J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le main() plus lisible : | |||
J'ai | |||
init_timer0_pwm() : Configure le mode Fast PWM pour le servo. | |||
init_timer2_irq() : Configure le mode CTC pour l'interruption d'affichage. | |||
==== 2. Démonstration ==== | |||
Le système est fonctionnel. Une vidéo et le fichier de code sont joints. | |||
<syntaxhighlight lang="c"> | |||
#define LATCH PD4 | |||
#define CLOCK PD7 | |||
#define DATA PB0 | |||
volatile uint16_t angle = 90; | |||
const uint8_t segments[] = {~0x3F,~0x06,~0x5B,~0x4F,~0x66,~0x6D,~0x7D,~0x07,~0x7F,~0x6F}; | |||
< | void init_timer0_pwm(void) { | ||
TCCR0A = (1<<COM0A1) | (1<<WGM01) | (1<<WGM00); | |||
TCCR0B = (1<<CS02) | (1<<CS00); | |||
} | |||
void init_timer2_irq(void) { | |||
TCCR2A = (1<<WGM21); | |||
TIMSK2 |= (1<<OCIE2A); | |||
OCR2A = 249; | |||
TCCR2B = (1<<CS22); | |||
} | |||
void envoyer(uint8_t val) { | |||
for (uint8_t i = 0; i < 8; i++) { | |||
if (val & (1 << (7 - i))) PORTB |= (1 << DATA); | |||
else PORTB &= ~(1 << DATA); | |||
PORTD |= (1 << CLOCK); | |||
PORTD &= ~(1 << CLOCK); | |||
} | |||
} | |||
ISR(TIMER2_COMPA_vect) { | |||
static uint8_t digit = 0; | |||
uint8_t val = (digit == 0) ? (angle/100) : (digit == 1) ? (angle/10)%10 : (angle%10); | |||
PORTD &= ~(1 << LATCH); | |||
envoyer(segments[val]); | |||
envoyer(1 << digit); | |||
PORTD |= (1 << LATCH); | |||
digit = (digit + 1) % 3; | |||
} | |||
int main(void) { | |||
DDRD |= (1<<LATCH) | (1<<CLOCK) | (1<<PD6) | (1<<PD3); | |||
DDRB |= (1<<DATA); | |||
PORTD |= (1<<PD3); | |||
PORTC |= (1<<PC1) | (1<<PC2); | |||
init_timer0_pwm(); | |||
init_timer2_irq(); | |||
sei(); | |||
uint8_t pos = 23; | |||
while (1) { | |||
if (!(PINC & (1<<PC1)) && pos > 8) pos--; | |||
if (!(PINC & (1<<PC2)) && pos < 38) pos++; | |||
< | |||
OCR0A = pos; | |||
angle = (uint32_t)(pos - 8) * 180 / 30; | |||
_delay_ms(100); | |||
} | |||
if ( | |||
} | |||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
[[Fichier:Demonstration Finale.mp4]] | |||
[[Fichier:Correction servo.zip]] | |||
[[Fichier: | |||
[[Fichier: | |||
=Document utilisés= | =Document utilisés= | ||
Version actuelle datée du 23 juin 2026 à 09:06
Objectifs
Réaliser le programme "capteurs et actionneurs" du cours/TP concernant la programmation en C sur microcontrôleur AVR :
- contrôlez un servo-moteur en utilisant le minuteur 0 ;
- l’utilisateur utilise deux boutons pour changer l’angle ;
- affichez l’angle de rotation sur les 7-segments :
- 3 afficheurs doivent être utilisés ;
- utilisez la persistence rétinienne ;
- utilisez les interruptions générées par un second minuteur.
Matériel nécessaire
- Arduino Uno avec câble USB ;
- bouclier multi-fonctions ;
- servo-moteur.
Historique
- 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.
- 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .
- 05/03/2026 : Création de la logique d'affichage .
- 09/04/2026 : Correction du code et de la mise en page du Wiki.
Travail réalisé
Jour 1 : Moteur et Boutons
Pour cette première étape, j’ai choisi d’avancer progressivement. L’objectif était simplement de lire les boutons S1 et S2 afin de commander la rotation du servo-moteur dans les deux sens.
Configuration des boutons
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59) :
Lorsqu’une broche est configurée en entrée (DDR = 0) et que l’on écrit un 1 dans le registre PORT correspondant, la résistance pull-up interne est activée. Cela maintient la broche à 5V au repos.
on a donc :
Bouton non appuyé : niveau 1
Bouton appuyé : niveau 0
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.
Désactivation du buzzer du shield
Au démarrage, le buzzer du shield émettait un signal sonore en continu. Après vérification, celui-ci est piloté par un transistor PNP Q1. Pour le désactiver, il faut imposer un niveau logique haut (5V) sur la broche de commande. D’après la table 13-1 (page 60), cela se fait en configurant la broche en sortie DDR = 1 et en forçant le bit correspondant du registre PORT à 1. J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.
Génération du signal PWM pour le servo
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. J’ai utilisé le Timer 0 en mode Fast PWM (Mode 3), en activant les bits WGM01 et WGM00, conformément à la table 14-8 (page 86). Dans ce mode, le compteur évolue de 0 à 255. Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84).
Étant donné que le microcontrôleur fonctionne à 16 MHz, j’ai appliqué un prescaler de 1024 via les bits CS02 et CS00 (page 87). Le calcul de la fréquence obtenue est le suivant :
f = 16,000,000 / (1024 * 256) = 61 Hz
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.
Justification des valeurs matérielles pour le servo Hitec HS-422
Le moteur utilisé pour ce projet est un servo Hitec HS-422. En étudiant ses spécifications techniques officielles, le constructeur définit la plage de fonctionnement suivante :
Le point central (neutre) est fixé à 1500 µs (1,5 ms).
La plage d'impulsion garantie (Pulse Width) s'étend de 900 µs (0,9 ms) à 2100 µs (2,1 ms).
Calcul des valeurs du registre OCR0A : Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un tic) du Timer 0 est de : T_tic = 1024 / 16,000,000 = 0,064 ms
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :
Point central (1,5 ms) : 1,5 / 0,064 = 23,43. En arrondissant, on obtient 23.
Butée minimale constructeur (0,9 ms) : 0,9 / 0,064 = 14,06. La limite basse est de 14.
Butée maximale constructeur (2,1 ms) : 2,1 / 0,064 = 32,81. La limite haute est de 33.
Dans le code final, les limites ont été étendues de 8 à 38 pour exploiter le maximum la mécanique du servo.
Jour 2 : Affichage et Timer 2
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield.
1. Comment on parle à l'afficheur
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :
DATA (PB0) : pour envoyer les infos bit par bit.
CLOCK (PD7) : pour donner le rythme.
LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.
2. L'affichage (Multiplexage)
On utilise la persistance rétinienne : on allume chaque chiffre l'un après l'autre très vite. L'œil ne voit pas le clignotement et on a l'impression que tout est allumé.
3. Pourquoi le Timer 2 et le mode CTC ?
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms. J'ai choisi le mode CTC (Clear Timer on Compare Match). Le timer se remet à zéro quand il atteint la cible OCR2A. Pour avoir 1 ms, j'ai mis 249 dans OCR2A.
Jour 3 : Calcul de l'angle et logique d'affichage
1. Calcul de l'angle
La valeur pos varie entre 8 (0°) et 38 (180°). La plage est de 30 unités. La formule mathématique intégrée est : angle = (uint32_t)(pos - 8) * 180 / 30; L'utilisation de uint32_t permet d'éviter un dépassement de capacité en mémoire avant la division.
2. Communication avec les puces
Le Shield utilise des registres à décalage. On envoie d'abord l'octet qui définit les segments à allumer, puis l'octet pour choisir l'afficheur. Une fois les 16 bits envoyés, on active la broche LATCH.
Jour 4 : L'assemblage final et les interruptions
1. La fonction d'interruption : ISR
Pour que l'affichage se fasse seul en arrière-plan, j'utilise la fonction ISR(TIMER2_COMPA_vect). Elle met à jour un seul afficheur à chaque appel pour ne pas bloquer le processeur. On utilise une variable static uint8_t digit pour alterner entre les 3 chiffres.
2. Le programme principal libéré
Grâce à cette interruption, la boucle principale while(1) s'occupe uniquement de la logique utilisateur : lecture des boutons et mise à jour de la position.
3. Pourquoi le delay ne fait pas clignoter l'écran ?
Le _delay_ms(100) bloque le programme principal mais pas le Timer 2 qui est matériel. Toutes les 1 ms, l'interruption se déclenche quand même pour rafraîchir l'écran.
Bilan et rendu
Le projet répond à toutes les contraintes (deux timers, Fast PWM, multiplexage et interruptions).
1. Fonctions d'initialisation
J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le main() plus lisible :
init_timer0_pwm() : Configure le mode Fast PWM pour le servo.
init_timer2_irq() : Configure le mode CTC pour l'interruption d'affichage.
2. Démonstration
Le système est fonctionnel. Une vidéo et le fichier de code sont joints.
#define LATCH PD4
#define CLOCK PD7
#define DATA PB0
volatile uint16_t angle = 90;
const uint8_t segments[] = {~0x3F,~0x06,~0x5B,~0x4F,~0x66,~0x6D,~0x7D,~0x07,~0x7F,~0x6F};
void init_timer0_pwm(void) {
TCCR0A = (1<<COM0A1) | (1<<WGM01) | (1<<WGM00);
TCCR0B = (1<<CS02) | (1<<CS00);
}
void init_timer2_irq(void) {
TCCR2A = (1<<WGM21);
TIMSK2 |= (1<<OCIE2A);
OCR2A = 249;
TCCR2B = (1<<CS22);
}
void envoyer(uint8_t val) {
for (uint8_t i = 0; i < 8; i++) {
if (val & (1 << (7 - i))) PORTB |= (1 << DATA);
else PORTB &= ~(1 << DATA);
PORTD |= (1 << CLOCK);
PORTD &= ~(1 << CLOCK);
}
}
ISR(TIMER2_COMPA_vect) {
static uint8_t digit = 0;
uint8_t val = (digit == 0) ? (angle/100) : (digit == 1) ? (angle/10)%10 : (angle%10);
PORTD &= ~(1 << LATCH);
envoyer(segments[val]);
envoyer(1 << digit);
PORTD |= (1 << LATCH);
digit = (digit + 1) % 3;
}
int main(void) {
DDRD |= (1<<LATCH) | (1<<CLOCK) | (1<<PD6) | (1<<PD3);
DDRB |= (1<<DATA);
PORTD |= (1<<PD3);
PORTC |= (1<<PC1) | (1<<PC2);
init_timer0_pwm();
init_timer2_irq();
sei();
uint8_t pos = 23;
while (1) {
if (!(PINC & (1<<PC1)) && pos > 8) pos--;
if (!(PINC & (1<<PC2)) && pos < 38) pos++;
OCR0A = pos;
angle = (uint32_t)(pos - 8) * 180 / 30;
_delay_ms(100);
}
}
Document utilisés
- https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf
- https://forum.arduino.cc/t/multifunction-shield/1007847/3
- https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf
- https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf