« SE3 2025/2026 EC3 » : différence entre les versions

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche
Aucun résumé des modifications
 
(22 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
<br>
= Objectifs =
<span style="color:#800020;font-size: 1.2em; font-weight: bold;">Jour 1 : Moteur et Boutons</span>
 
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.
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.


<br>
==== Configuration des boutons ====
<span style="color:#800020;font-size: 1.2em; font-weight: bold;">Configuration des boutons</span>


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.
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
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.


<br>
==== Désactivation du buzzer du shield ====
<span style="color:#800020;font-size: 1.2em; font-weight: bold;">Désactivation du buzzer du shield</span>


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.


<br>
==== Génération du signal PWM pour le servo ====
<span style="color:#800020;font-size: 1.2em; font-weight: bold;">Génération du signal PWM pour le servo</span>


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).
* 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). ''"If one or both of the COM0A1:0 bits are set, the OC0A output overrides the normal port functionality of the I/O pin it is connected to."''


É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 :


<math>f = \frac{16\,000\,000}{1024 \times 256} \approx 61 \text{ Hz}</math>
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.


<br>
==== Justification des valeurs matérielles pour le servo Hitec HS-422 ====
<span style="color:#800020;font-size: 1.2em; font-weight: bold;">Justification des valeurs matérielles pour le servo Hitec HS-422</span>
 
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).


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 :
La plage d'impulsion garantie (Pulse Width) s'étend de 900 µs (0,9 ms) à 2100 µs (2,1 ms).
* 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 :'''
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 :
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 :
<math>T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}</math>
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 :
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) :'''
Point central (1,5 ms) : 1,5 / 0,064 = 23,43. En arrondissant, on obtient 23.
<math>\frac{1,5}{0,064} = 23,43</math>. En arrondissant, on obtient notre position de démarrage 23.


* '''Butée minimale constructeur (0,9 ms) :'''
Butée minimale constructeur (0,9 ms) : 0,9 / 0,064 = 14,06. La limite basse est de 14.
<math>\frac{0,9}{0,064} = 14,06</math>. La limite officielle basse est de 14.


* '''Butée maximale constructeur (2,1 ms) :'''
Butée maximale constructeur (2,1 ms) : 2,1 / 0,064 = 32,81. La limite haute est de 33.
<math>\frac{2,1}{0,064} = 32,81</math>. La limite officielle haute est de 33.


'' Dans le code final, les limites ont été étendues de 8 à 38, correspondant environ à 0,5 ms et 2,4 ms pour exploiter le maximum la mécanique du servo.''
Dans le code final, les limites ont été étendues de 8 à 38 pour exploiter le maximum la mécanique du servo.


<br>
=== Jour 2 : Affichage et Timer 2 ===
<span style="color:#800020;font-size: 1.2em; font-weight: bold;">Code du Jour 1</span>


<syntaxhighlight lang="c" line="1">
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield.
#ifndef F_CPU
#define F_CPU 16000000UL // On définit la vitesse de l'horloge
#endif


#include <avr/io.h>
==== 1. Comment on parle à l'afficheur ====
#include <util/delay.h>


int main(void) {
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :
    // On définit les pins comme des sorties (DDR = 1)
 
     DDRD |= (1<<PD6) | (1<<PD3);
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.
 
<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);
      
      
     // On force le buzzer au silence
     digit = (digit + 1) % 3;
    PORTD |= (1<<PD3);  
}


     // On active les résistances internes pour les boutons
int main(void) {
     DDRD |= (1<<LATCH) | (1<<CLOCK) | (1<<PD6) | (1<<PD3);
    DDRB |= (1<<DATA);
    PORTD |= (1<<PD3);
     PORTC |= (1<<PC1) | (1<<PC2);
     PORTC |= (1<<PC1) | (1<<PC2);


     // On crée un signal PWM rapide pour piloter le servo sur la pin D6
     init_timer0_pwm();
     TCCR0A = (1<<COM0A1) | (1<<WGM01) | (1<<WGM00);  
     init_timer2_irq();
     TCCR0B = (1<<CS02) | (1<<CS00); // On ralentit le chrono pour avoir du 61Hz
 
   
     sei();
     uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)
     uint8_t pos = 23;


     while (1) {
     while (1) {
        // Lecture des boutons 
         if (!(PINC & (1<<PC1)) && pos > 8)  pos--;
         if (!(PINC & (1<<PC1)) && pos > 8)  pos--; // Bouton S1 diminue
         if (!(PINC & (1<<PC2)) && pos < 38) pos++;
         if (!(PINC & (1<<PC2)) && pos < 38) pos++; // Bouton S2 augmente
          
          
         OCR0A = pos; // On envoie l'ordre de vitesse au moteur
         OCR0A = pos;
        angle = (uint32_t)(pos - 8) * 180 / 30;
          
          
         _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide
         _delay_ms(100);
     }
     }
}
}
</syntaxhighlight>
</syntaxhighlight>
[[Fichier:Demonstration Finale.mp4]]
[[Fichier:Correction servo.zip]]
=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

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);
    }
}

Fichier:Correction servo.zip

Document utilisés