SE3 2025/2026 EC3

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche

Objectifs

Réaliser le programme du devoir surveillé 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 ;
  • 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);
    }
}

Fichier:Correction servo.zip

Document utilisés