<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
	<id>https://wiki-se.plil.fr/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Yherelli</id>
	<title>wiki-se.plil.fr - Contributions [fr]</title>
	<link rel="self" type="application/atom+xml" href="https://wiki-se.plil.fr/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Yherelli"/>
	<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php/Sp%C3%A9cial:Contributions/Yherelli"/>
	<updated>2026-08-07T02:18:18Z</updated>
	<subtitle>Contributions</subtitle>
	<generator>MediaWiki 1.39.1</generator>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12899</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12899"/>
		<updated>2026-04-09T17:01:05Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Historique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
* 09/04/2026 : Correction du code et de la mise en page du Wiki.&lt;br /&gt;
&lt;br /&gt;
== Travail réalisé ==&lt;br /&gt;
&lt;br /&gt;
=== Jour 1 : Moteur et Boutons ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Configuration des boutons ====&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59) :&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
&lt;br /&gt;
Bouton non appuyé : niveau 1&lt;br /&gt;
&lt;br /&gt;
Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
==== Désactivation du buzzer du shield ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Génération du signal PWM pour le servo ====&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
f = 16,000,000 / (1024 * 256) = 61 Hz&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
==== Justification des valeurs matérielles pour le servo Hitec HS-422 ====&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
&lt;br /&gt;
Le point central (neutre) est fixé à 1500 µs (1,5 ms).&lt;br /&gt;
&lt;br /&gt;
La plage d'impulsion garantie (Pulse Width) s'étend de 900 µs (0,9 ms) à 2100 µs (2,1 ms).&lt;br /&gt;
&lt;br /&gt;
Calcul des valeurs du registre OCR0A :&lt;br /&gt;
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 :&lt;br /&gt;
T_tic = 1024 / 16,000,000 = 0,064 ms&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
Point central (1,5 ms) : 1,5 / 0,064 = 23,43. En arrondissant, on obtient 23.&lt;br /&gt;
&lt;br /&gt;
Butée minimale constructeur (0,9 ms) : 0,9 / 0,064 = 14,06. La limite basse est de 14.&lt;br /&gt;
&lt;br /&gt;
Butée maximale constructeur (2,1 ms) : 2,1 / 0,064 = 32,81. La limite haute est de 33.&lt;br /&gt;
&lt;br /&gt;
Dans le code final, les limites ont été étendues de 8 à 38 pour exploiter le maximum la mécanique du servo.&lt;br /&gt;
&lt;br /&gt;
=== Jour 2 : Affichage et Timer 2 ===&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield.&lt;br /&gt;
&lt;br /&gt;
==== 1. Comment on parle à l'afficheur ====&lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
==== 2. L'affichage (Multiplexage) ====&lt;br /&gt;
&lt;br /&gt;
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é.&lt;br /&gt;
&lt;br /&gt;
==== 3. Pourquoi le Timer 2 et le mode CTC ? ====&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Jour 3 : Calcul de l'angle et logique d'affichage ===&lt;br /&gt;
&lt;br /&gt;
==== 1. Calcul de l'angle ====&lt;br /&gt;
&lt;br /&gt;
La valeur pos varie entre 8 (0°) et 38 (180°). La plage est de 30 unités.&lt;br /&gt;
La formule mathématique intégrée est : angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
L'utilisation de uint32_t permet d'éviter un dépassement de capacité en mémoire avant la division.&lt;br /&gt;
&lt;br /&gt;
==== 2. Communication avec les puces ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Jour 4 : L'assemblage final et les interruptions ===&lt;br /&gt;
&lt;br /&gt;
==== 1. La fonction d'interruption : ISR ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== 2. Le programme principal libéré ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== 3. Pourquoi le delay ne fait pas clignoter l'écran ? ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Bilan et rendu ===&lt;br /&gt;
&lt;br /&gt;
Le projet répond à toutes les contraintes (deux timers, Fast PWM, multiplexage et interruptions).&lt;br /&gt;
&lt;br /&gt;
==== 1. Fonctions d'initialisation ====&lt;br /&gt;
&lt;br /&gt;
J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le main() plus lisible :&lt;br /&gt;
&lt;br /&gt;
init_timer0_pwm() : Configure le mode Fast PWM pour le servo.&lt;br /&gt;
&lt;br /&gt;
init_timer2_irq() : Configure le mode CTC pour l'interruption d'affichage.&lt;br /&gt;
&lt;br /&gt;
==== 2. Démonstration ====&lt;br /&gt;
&lt;br /&gt;
Le système est fonctionnel. Une vidéo et le fichier de code sont joints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#define LATCH PD4&lt;br /&gt;
#define CLOCK PD7&lt;br /&gt;
#define DATA  PB0&lt;br /&gt;
&lt;br /&gt;
volatile uint16_t angle = 90;&lt;br /&gt;
const uint8_t segments[] = {~0x3F,~0x06,~0x5B,~0x4F,~0x66,~0x6D,~0x7D,~0x07,~0x7F,~0x6F};&lt;br /&gt;
&lt;br /&gt;
void init_timer0_pwm(void) {&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00);&lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void init_timer2_irq(void) {&lt;br /&gt;
    TCCR2A = (1&amp;lt;&amp;lt;WGM21);&lt;br /&gt;
    TIMSK2 |= (1&amp;lt;&amp;lt;OCIE2A);&lt;br /&gt;
    OCR2A = 249;&lt;br /&gt;
    TCCR2B = (1&amp;lt;&amp;lt;CS22);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
    for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
        if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; DATA);&lt;br /&gt;
        else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; DATA);&lt;br /&gt;
        &lt;br /&gt;
        PORTD |= (1 &amp;lt;&amp;lt; CLOCK);&lt;br /&gt;
        PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; CLOCK);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER2_COMPA_vect) {&lt;br /&gt;
    static uint8_t digit = 0;&lt;br /&gt;
    uint8_t val = (digit == 0) ? (angle/100) : (digit == 1) ? (angle/10)%10 : (angle%10);&lt;br /&gt;
    &lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; LATCH);&lt;br /&gt;
    envoyer(segments[val]);&lt;br /&gt;
    envoyer(1 &amp;lt;&amp;lt; digit);&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; LATCH);&lt;br /&gt;
    &lt;br /&gt;
    digit = (digit + 1) % 3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;LATCH) | (1&amp;lt;&amp;lt;CLOCK) | (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    DDRB |= (1&amp;lt;&amp;lt;DATA);&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    init_timer0_pwm();&lt;br /&gt;
    init_timer2_irq();&lt;br /&gt;
&lt;br /&gt;
    sei();&lt;br /&gt;
    uint8_t pos = 23;&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--;&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++;&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos;&lt;br /&gt;
        angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Demonstration Finale.mp4]]&lt;br /&gt;
[[Fichier:Correction servo.zip]]&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=Fichier:Correction_servo.zip&amp;diff=12898</id>
		<title>Fichier:Correction servo.zip</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=Fichier:Correction_servo.zip&amp;diff=12898"/>
		<updated>2026-04-09T16:55:58Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : Yherelli a téléversé une nouvelle version de Fichier:Correction servo.zip&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;correction du programme&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12897</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12897"/>
		<updated>2026-04-09T16:55:08Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
== Travail réalisé ==&lt;br /&gt;
&lt;br /&gt;
=== Jour 1 : Moteur et Boutons ===&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Configuration des boutons ====&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59) :&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
&lt;br /&gt;
Bouton non appuyé : niveau 1&lt;br /&gt;
&lt;br /&gt;
Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
==== Désactivation du buzzer du shield ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== Génération du signal PWM pour le servo ====&lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
f = 16,000,000 / (1024 * 256) = 61 Hz&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
==== Justification des valeurs matérielles pour le servo Hitec HS-422 ====&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
&lt;br /&gt;
Le point central (neutre) est fixé à 1500 µs (1,5 ms).&lt;br /&gt;
&lt;br /&gt;
La plage d'impulsion garantie (Pulse Width) s'étend de 900 µs (0,9 ms) à 2100 µs (2,1 ms).&lt;br /&gt;
&lt;br /&gt;
Calcul des valeurs du registre OCR0A :&lt;br /&gt;
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 :&lt;br /&gt;
T_tic = 1024 / 16,000,000 = 0,064 ms&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
Point central (1,5 ms) : 1,5 / 0,064 = 23,43. En arrondissant, on obtient 23.&lt;br /&gt;
&lt;br /&gt;
Butée minimale constructeur (0,9 ms) : 0,9 / 0,064 = 14,06. La limite basse est de 14.&lt;br /&gt;
&lt;br /&gt;
Butée maximale constructeur (2,1 ms) : 2,1 / 0,064 = 32,81. La limite haute est de 33.&lt;br /&gt;
&lt;br /&gt;
Dans le code final, les limites ont été étendues de 8 à 38 pour exploiter le maximum la mécanique du servo.&lt;br /&gt;
&lt;br /&gt;
=== Jour 2 : Affichage et Timer 2 ===&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield.&lt;br /&gt;
&lt;br /&gt;
==== 1. Comment on parle à l'afficheur ====&lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
==== 2. L'affichage (Multiplexage) ====&lt;br /&gt;
&lt;br /&gt;
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é.&lt;br /&gt;
&lt;br /&gt;
==== 3. Pourquoi le Timer 2 et le mode CTC ? ====&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Jour 3 : Calcul de l'angle et logique d'affichage ===&lt;br /&gt;
&lt;br /&gt;
==== 1. Calcul de l'angle ====&lt;br /&gt;
&lt;br /&gt;
La valeur pos varie entre 8 (0°) et 38 (180°). La plage est de 30 unités.&lt;br /&gt;
La formule mathématique intégrée est : angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
L'utilisation de uint32_t permet d'éviter un dépassement de capacité en mémoire avant la division.&lt;br /&gt;
&lt;br /&gt;
==== 2. Communication avec les puces ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Jour 4 : L'assemblage final et les interruptions ===&lt;br /&gt;
&lt;br /&gt;
==== 1. La fonction d'interruption : ISR ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== 2. Le programme principal libéré ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
==== 3. Pourquoi le delay ne fait pas clignoter l'écran ? ====&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
=== Bilan et rendu ===&lt;br /&gt;
&lt;br /&gt;
Le projet répond à toutes les contraintes (deux timers, Fast PWM, multiplexage et interruptions).&lt;br /&gt;
&lt;br /&gt;
==== 1. Fonctions d'initialisation ====&lt;br /&gt;
&lt;br /&gt;
J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le main() plus lisible :&lt;br /&gt;
&lt;br /&gt;
init_timer0_pwm() : Configure le mode Fast PWM pour le servo.&lt;br /&gt;
&lt;br /&gt;
init_timer2_irq() : Configure le mode CTC pour l'interruption d'affichage.&lt;br /&gt;
&lt;br /&gt;
==== 2. Démonstration ====&lt;br /&gt;
&lt;br /&gt;
Le système est fonctionnel. Une vidéo et le fichier de code sont joints.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
#define LATCH PD4&lt;br /&gt;
#define CLOCK PD7&lt;br /&gt;
#define DATA  PB0&lt;br /&gt;
&lt;br /&gt;
volatile uint16_t angle = 90;&lt;br /&gt;
const uint8_t segments[] = {~0x3F,~0x06,~0x5B,~0x4F,~0x66,~0x6D,~0x7D,~0x07,~0x7F,~0x6F};&lt;br /&gt;
&lt;br /&gt;
void init_timer0_pwm(void) {&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00);&lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void init_timer2_irq(void) {&lt;br /&gt;
    TCCR2A = (1&amp;lt;&amp;lt;WGM21);&lt;br /&gt;
    TIMSK2 |= (1&amp;lt;&amp;lt;OCIE2A);&lt;br /&gt;
    OCR2A = 249;&lt;br /&gt;
    TCCR2B = (1&amp;lt;&amp;lt;CS22);&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
    for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
        if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; DATA);&lt;br /&gt;
        else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; DATA);&lt;br /&gt;
        &lt;br /&gt;
        PORTD |= (1 &amp;lt;&amp;lt; CLOCK);&lt;br /&gt;
        PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; CLOCK);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER2_COMPA_vect) {&lt;br /&gt;
    static uint8_t digit = 0;&lt;br /&gt;
    uint8_t val = (digit == 0) ? (angle/100) : (digit == 1) ? (angle/10)%10 : (angle%10);&lt;br /&gt;
    &lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; LATCH);&lt;br /&gt;
    envoyer(segments[val]);&lt;br /&gt;
    envoyer(1 &amp;lt;&amp;lt; digit);&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; LATCH);&lt;br /&gt;
    &lt;br /&gt;
    digit = (digit + 1) % 3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;LATCH) | (1&amp;lt;&amp;lt;CLOCK) | (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    DDRB |= (1&amp;lt;&amp;lt;DATA);&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    init_timer0_pwm();&lt;br /&gt;
    init_timer2_irq();&lt;br /&gt;
&lt;br /&gt;
    sei();&lt;br /&gt;
    uint8_t pos = 23;&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--;&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++;&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos;&lt;br /&gt;
        angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Demonstration Finale.mp4]]&lt;br /&gt;
[[Fichier:Correction servo.zip]]&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12825</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12825"/>
		<updated>2026-04-02T21:24:29Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
[[Fichier:Demonstration Finale.mp4]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
[[Fichier:Correction servo.zip]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12824</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12824"/>
		<updated>2026-04-02T21:22:53Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
[[Fichier:Demonstration Finale.mp4]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
fichier corriger&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=Fichier:Correction_servo.zip&amp;diff=12823</id>
		<title>Fichier:Correction servo.zip</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=Fichier:Correction_servo.zip&amp;diff=12823"/>
		<updated>2026-04-02T21:21:30Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;correction du programme&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12822</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12822"/>
		<updated>2026-04-02T21:16:12Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
[[Fichier:Demonstration Finale.mp4]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12821</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12821"/>
		<updated>2026-04-02T21:13:19Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
[Demonstration Finale.mp4]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12820</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12820"/>
		<updated>2026-04-02T21:12:35Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
[[Fichier:Demonstration Finale.mp4|vignette]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=Fichier:Demonstration_Finale.mp4&amp;diff=12819</id>
		<title>Fichier:Demonstration Finale.mp4</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=Fichier:Demonstration_Finale.mp4&amp;diff=12819"/>
		<updated>2026-04-02T21:12:26Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Vidéo du fonctionnement servo&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12818</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12818"/>
		<updated>2026-04-02T21:04:08Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
[[Demonstration_Finale.mp4]]&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12817</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12817"/>
		<updated>2026-04-02T21:03:53Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;div style=&amp;quot;text-align: center;&amp;quot;&amp;gt;&lt;br /&gt;
[[Demonstration_Finale.mp4]]&lt;br /&gt;
'''Démonstration'''&lt;br /&gt;
&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12816</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12816"/>
		<updated>2026-04-02T21:01:40Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Demonstration_Finale.mp4.mp4|vignette]]&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12815</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12815"/>
		<updated>2026-04-02T21:01:23Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Demonstration_Finale.mp4|vignette]]&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12814</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12814"/>
		<updated>2026-04-02T21:00:03Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Demonstration Finale.mp4|vignette]]&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12813</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12813"/>
		<updated>2026-04-02T20:59:18Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026: Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P, la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Fonctions d'initialisation&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    J'ai extrait la configuration des registres dans deux fonctions dédiées afin de rendre le &amp;lt;b&amp;gt;main()&amp;lt;/b&amp;gt; plus lisible :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer0_pwm()&amp;lt;/b&amp;gt; : Configure le mode Fast PWM (61Hz) pour le servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;&amp;lt;b&amp;gt;init_timer2_irq()&amp;lt;/b&amp;gt; : Configure le mode CTC (1ms) pour l'interruption d'affichage.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Démonstration&amp;lt;/span&amp;gt;&lt;br /&gt;
&amp;lt;p&amp;gt;&lt;br /&gt;
    Le système est désormais 100% fonctionnel. Une vidéo a été jointe pour démontrer :&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&amp;lt;ul&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;La rotation fluide du servo.&amp;lt;/li&amp;gt;&lt;br /&gt;
    &amp;lt;li&amp;gt;L'affichage stable de l'angle (0-180°) sur les 7-segments.&amp;lt;/li&amp;gt;&lt;br /&gt;
&amp;lt;/ul&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p&amp;gt;[[Fichier:Demonstration_Finale.mp4|vignette]]&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=Fichier:Main.zip&amp;diff=12800</id>
		<title>Fichier:Main.zip</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=Fichier:Main.zip&amp;diff=12800"/>
		<updated>2026-03-08T21:11:46Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12799</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12799"/>
		<updated>2026-03-08T21:11:33Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026:Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P,la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.zip|vignette]]&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12798</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12798"/>
		<updated>2026-03-08T21:09:53Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026:Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P,la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.5em; font-weight: bold;&amp;quot;&amp;gt;Jour 4 : L'assemblage final et les interruptions&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, c'était l'étape la plus délicate : réunir le code du moteur et le code de l'affichage . Le défi principal était de faire fonctionner les deux en même temps. Si le programme est occupé à faire des pauses pour le moteur, comment peut-il rafraîchir l'écran assez vite pour que l'œil ne voie pas de clignotement ? C'est là que les interruptions entrent en jeu.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. La fonction d'interruption : ISR&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage se fasse tout seul en arrière-plan, j'utilise une fonction appelée `ISR(TIMER2_COMPA_vect)`. &lt;br /&gt;
Ce nom est imposé par la librairie `&amp;lt;avr/interrupt.h&amp;gt;` : il indique au microcontrôleur de lancer cette fonction précisément quand le Timer 2 atteint la cible que j'ai fixée (249) pour faire 1 milliseconde.&lt;br /&gt;
&lt;br /&gt;
À chaque fois que cette fonction s'allume, elle ne met à jour qu'un seul des 3 afficheurs pour ne pas perdre de temps. J'ai utilisé une variable `static uint8_t digit = 0;`. Le mot `static` permet à la variable de garder en mémoire l'afficheur actuel (0, 1 ou 2) entre chaque appel de la fonction.&lt;br /&gt;
&lt;br /&gt;
Voici les actions de l'interruption en une fraction de milliseconde :&lt;br /&gt;
* Elle bloque l'écran en mettant le LATCH à 0 pour qu'on ne voie pas les bits bouger.&lt;br /&gt;
* Elle envoie le code des segments via la fonction envoyer().&lt;br /&gt;
* Elle envoie un deuxième code pour choisir le bon afficheur.&lt;br /&gt;
* Elle débloque l'écran en remettant le LATCH à 1 : le chiffre apparaît.&lt;br /&gt;
* Elle prépare le prochain tour en faisant digit = (digit + 1) % 3. (le modulo permet de faire tourner le compteur 0, 1, 2, 0...).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. Le programme principal libéré&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Grâce à cette fonction ISR qui s'occupe du multiplexage en tâche de fond, ma boucle principale while(1) est devenue très simple à gérer. &lt;br /&gt;
&lt;br /&gt;
Dans cette boucle, le programme s'occupe uniquement de la logique utilisateur :&lt;br /&gt;
* Il vérifie si les boutons S1 ou S2 sont pressés.&lt;br /&gt;
* Si oui, il augmente ou diminue la variable pos.&lt;br /&gt;
* Il calcule le nouvel angle en degrés sur 32 bits pour le préparer pour l'affichage.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le delay ne fait pas clignoter l'écran ?&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Dans ma boucle principale, j'ai ajouté un _delay_ms(100) à la fin. Ce délai est obligatoire, sans lui, si je reste appuyé sur le bouton, la variable 'pos' augmente tellement vite que le moteur saute d'un coup à la fin de sa course. Le délai permet de ralentir cette lecture.&lt;br /&gt;
&lt;br /&gt;
Cependant, la fonction `delay` &amp;quot;bloque&amp;quot; le microcontrôleur pendant 100 millisecondes. &lt;br /&gt;
* Le problème : Si j'avais géré l'affichage dans cette même boucle principale, l'écran resterait figé pendant ces 100 ms à chaque tour. Le multiplexage serait détruit et l'écran clignoterait atrocement.&lt;br /&gt;
* La solution du Timer 2 : Comme l'affichage est géré par l'interruption, il est totalement indépendant. Même quand le programme principal est bloqué par le `delay`, le Timer 2 (qui est matériel) continue de tourner. Toutes les 1 ms, il force le microcontrôleur à faire une pause dans son attente, à changer le chiffre de l'afficheur, et à reprendre son délai. L'écran reste donc parfaitement fluide !&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;4. Bilan et rendu&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le projet est maintenant fonctionnel et répond à toutes les contraintes du sujet (utilisation de deux timers, signal Fast PWM, multiplexage, et interruptions). L'intégralité de mon code commenté se trouve dans le fichier '''`main.c`''' que j'ai déposé avec ce compte-rendu.&lt;br /&gt;
&lt;br /&gt;
[[Fichier:Main.c|vignette]]&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12768</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12768"/>
		<updated>2026-03-05T20:51:26Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Historique */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
* 02/03/2026:Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
* 03/03/2026 : Configuration du Timer 2 et étude de l'afficheur 7-segments .&lt;br /&gt;
* 05/03/2026 : Création de la logique d'affichage .&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P,la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12767</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12767"/>
		<updated>2026-03-05T20:45:01Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
*02/03/2026:Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P,la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 3 : Calcul de l'angle et logique d'affichage  &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Maintenant que le Timer 2 est prêt à rafraîchir l'écran, j'ai travaillé sur la partie logique : comment transformer la position du moteur en un angle lisible (0 à 180°) et comment l'envoyer physiquement aux afficheurs.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Calcul de l'angle et logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
 &lt;br /&gt;
Dans le code du Jour 1, j'utilisais une variable pos pour le registre OCR0A. Cette valeur varie entre 8 (0°) et 38 (180°).&lt;br /&gt;
&lt;br /&gt;
On a une plage de 30 unités (38 - 8 = 30).&lt;br /&gt;
&lt;br /&gt;
Chaque unité ajoutée à pos représente donc 180 / 30 = 6 degrés.&lt;br /&gt;
&lt;br /&gt;
La formule mathématique que j'ai intégrée est :&lt;br /&gt;
angle = (uint32_t)(pos - 8) * 180 / 30;&lt;br /&gt;
&lt;br /&gt;
Pourquoi (uint32_t) ?&lt;br /&gt;
J'ai forcé la conversion en entier 32 bits  car lors de la multiplication par 180, le résultat intermédiaire peut monter jusqu'à 5400 (30 * 180). L'ATmega328P étant un microcontrôleur 8 bits , utiliser (uint32_t) permet d'éviter un dépassement de capacité (overflow) en mémoire avant que la division par 30 ne réduise le résultat final.&lt;br /&gt;
&lt;br /&gt;
Pour afficher un angle, le microcontrôleur doit envoyer les chiffres un par un. J'utilise Modulo (%) pour isoler chaque chiffre :&lt;br /&gt;
&lt;br /&gt;
Centaines : On divise l'angle par 100.&lt;br /&gt;
&lt;br /&gt;
Dizaines : On divise par 10, puis on prend le reste de la division par 10 (angle / 10) % 10.&lt;br /&gt;
&lt;br /&gt;
Unités : On prend le reste de la division par 10 angle % 10.&lt;br /&gt;
&lt;br /&gt;
C'est une étape essentielle car chaque afficheur du Shield est indépendant.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt; 3. Communication avec les puces &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le Shield utilise des registres à décalage. Pour envoyer les données, j'ai créé une fonction qui respecte le protocole série :&lt;br /&gt;
&lt;br /&gt;
On envoie d'abord l'octet qui définit quels segments allumer (A, B, C, D, E, F, G). J'utilise un tableau segments pour savoir quel bit correspond à quel chiffre.&lt;br /&gt;
&lt;br /&gt;
On envoie ensuite un deuxième octet pour choisir quel afficheur doit s'allumer parmi les quatre disponibles.&lt;br /&gt;
&lt;br /&gt;
Pour chaque bit, on fait passer la broche CLOCK de 0 à 1 pour pousser la donnée dans la puce.&lt;br /&gt;
&lt;br /&gt;
Une fois les 16 bits envoyés, on active la broche LATCH pour confirmer l'affichage.&lt;br /&gt;
&lt;br /&gt;
Tout ce processus (découpage du nombre et envoi des bits) prend du temps. Avec le Timer 2, l'affichage devient automatique.&lt;br /&gt;
Le programme principal peut continuer de lire les boutons et de bouger le moteur sans s'arrêter. Grâce à la vitesse du Timer 2, l'œil humain ne voit pas le multiplexage et l'affichage semble fixe et brillant. C'est le principe de la persistance rétinienne.&lt;br /&gt;
&lt;br /&gt;
Comme pour le moteur, j'ai vérifié dans la Table 13-1 (page 60) de la datasheet que les broches de communication (PB0, PD7 et PD4) sont configurées en sortie dans le registre DDR. Sans cela, aucun signal ne parviendrait aux puces de l'afficheur.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Extrait de code : Logique d'affichage &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Voici la partie du code qui gère la transformation des chiffres en signaux électriques.&lt;br /&gt;
&lt;br /&gt;
J'ai d'abord créé un tableau qui contient les codes pour chaque chiffre (0-9) en suivant le schéma des segments (avec le symbole ~ car le shield s'allume avec du 0V) :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Codes pour afficher les chiffres 0 à 9 sur le shield&lt;br /&gt;
const uint8_t segments[] = {&lt;br /&gt;
~0x3F, ~0x06, ~0x5B, ~0x4F, ~0x66, ~0x6D, ~0x7D, ~0x07, ~0x7F, ~0x6F&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Ensuite, la fonction suivante permet d'envoyer ces codes aux puces bit par bit :&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
// On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))) PORTB |= (1 &amp;lt;&amp;lt; PB0); // DATA = 1 (5V)&lt;br /&gt;
else PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; PB0);                     // DATA = 0 (0V)&lt;br /&gt;
&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; PD7);  // Coup de CLOCK (Haut)&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; PD7); // Coup de CLOCK (Bas)&lt;br /&gt;
}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12745</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12745"/>
		<updated>2026-03-03T20:13:50Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
*02/03/2026:Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P,la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} = 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12744</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12744"/>
		<updated>2026-03-03T20:08:34Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
*02/03/2026:Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P,la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} \approx 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 2 : Affichage et Timer 2 &amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Aujourd'hui, j'ai travaillé sur l'afficheur 7-segments du shield. C'est plus dur que le moteur parce qu'il faut gérer plusieurs chiffres en même temps avec peu de fils.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;1. Comment on parle à l'afficheur &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Le shield utilise deux puces (74HC595) pour contrôler les segments. On utilise seulement 3 broches :&lt;br /&gt;
&lt;br /&gt;
*DATA (PB0) : pour envoyer les infos bit par bit.&lt;br /&gt;
&lt;br /&gt;
*CLOCK (PD7) : pour donner le rythme.&lt;br /&gt;
&lt;br /&gt;
*LATCH (PD4) : pour dire à l'écran d'afficher ce qu'on a envoyé.&lt;br /&gt;
&lt;br /&gt;
Comme il faut envoyer du courant vers l'afficheur, j'ai mis ces broches en Sortie dans le registre DDR (Table 13-1, page 60).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;2. L'affichage (Multiplexage) &amp;lt;/span&amp;gt; &lt;br /&gt;
 &lt;br /&gt;
On ne peut pas allumer les 3 chiffres en même temps normalement. 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é.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;3. Pourquoi le Timer 2 et le mode CTC ? &amp;lt;/span&amp;gt; &lt;br /&gt;
&lt;br /&gt;
Pour que l'affichage soit propre, j'utilise le Timer 2 pour faire une interruption toutes les 1 ms.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le Timer 2 ?&lt;br /&gt;
&lt;br /&gt;
*J'ai laissé le Timer 0 car il gère déjà le moteur.&lt;br /&gt;
&lt;br /&gt;
*Je n'ai pas pris le Timer 1 car c'est un timer 16 bits très puissant. L'affichage est une tâche simple.&lt;br /&gt;
&lt;br /&gt;
Pourquoi le mode CTC ?&lt;br /&gt;
&lt;br /&gt;
J'ai choisi le mode CTC (Clear Timer on Compare Match) au lieu du mode normal.&lt;br /&gt;
&lt;br /&gt;
En mode normal, le timer compte jusqu'à 255 et on ne peut pas régler la durée précisément.&lt;br /&gt;
&lt;br /&gt;
En mode CTC, le timer se remet à zéro pile quand il atteint la cible qu'on lui donne dans le registre OCR2A.&lt;br /&gt;
&lt;br /&gt;
Pour avoir 1 ms, j'ai mis 249 dans OCR2A. Comme le compteur commence à 0, ça fait exactement 250 étapes. Avec le prescaler de 64, ça nous donne un rafraîchissement parfait.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
// Timer 2 pour l'affichage (toutes les 1ms)&lt;br /&gt;
TCCR2A = (1 &amp;lt;&amp;lt; WGM21);  // Mode CTC&lt;br /&gt;
TCCR2B = (1 &amp;lt;&amp;lt; CS22);   // Prescaler 64&lt;br /&gt;
OCR2A = 249;            // 1ms&lt;br /&gt;
TIMSK2 |= (1 &amp;lt;&amp;lt; OCIE2A); // Active l'interruption&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12731</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12731"/>
		<updated>2026-03-02T22:52:14Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme du devoir surveillé concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments : &lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
** utilisez la persistence rétinienne ;&lt;br /&gt;
** utilisez les interruptions générées par un second minuteur.&lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
=Historique=&lt;br /&gt;
*02/03/2026:Branchement du matériel (Arduino,Shield et servo-moteur) et code pour faire bouger le moteur avec les deux boutons.&lt;br /&gt;
&lt;br /&gt;
=Travail réalisé=&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P,la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} \approx 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Document utilisés=&lt;br /&gt;
* https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf&lt;br /&gt;
*https://forum.arduino.cc/t/multifunction-shield/1007847/3&lt;br /&gt;
*https://ww1.microchip.com/downloads/en/DeviceDoc/AVR-InstructionSet-Manual-DS40002198.pdf&lt;br /&gt;
*https://cdn.sparkfun.com/assets/1/1/f/a/3/HCS_HS-422_Specsheet_v2.2__102623.pdf&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12730</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12730"/>
		<updated>2026-03-02T22:31:21Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Jour 1 : Moteur et Boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Configuration des boutons&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
En consultant la datasheet ATmega328P,la section 13.2.1 (page 59), &lt;br /&gt;
&lt;br /&gt;
* 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.&lt;br /&gt;
&lt;br /&gt;
on a donc :&lt;br /&gt;
* Bouton non appuyé : niveau  1&lt;br /&gt;
* Bouton appuyé : niveau 0&lt;br /&gt;
&lt;br /&gt;
C’est pour cette raison que, dans la boucle principale, je teste un état logique bas 0 pour détecter un appui.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Désactivation du buzzer du shield&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Au démarrage, le buzzer du shield émettait un signal sonore en continu.&lt;br /&gt;
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.&lt;br /&gt;
J’ai donc configuré la broche PD3 en sortie et fixé son état à 1 dès l’initialisation du programme.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Génération du signal PWM pour le servo&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Le servo-moteur nécessite un signal PWM d’environ 60 Hz. &lt;br /&gt;
&lt;br /&gt;
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).&lt;br /&gt;
* Dans ce mode, le compteur évolue de 0 à 255.&lt;br /&gt;
* Pour que le signal soit transmis sur la broche PD6 (OC0A), j’ai activé le bit COM0A1 (table 14-3, page 84). ''&amp;quot;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.&amp;quot;''&lt;br /&gt;
&lt;br /&gt;
É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 :&lt;br /&gt;
&lt;br /&gt;
&amp;lt;math&amp;gt;f = \frac{16\,000\,000}{1024 \times 256} \approx 61 \text{ Hz}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
La fréquence obtenue est donc d’environ 61 Hz, ce qui est compatible avec les exigences d’un servo-moteur standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Justification des valeurs matérielles pour le servo Hitec HS-422&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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 :&lt;br /&gt;
* Le point central (neutre) est fixé à '''1500 µs (1,5 ms)'''.&lt;br /&gt;
* La plage d'impulsion garantie (Pulse Width) s'étend de '''900 µs (0,9 ms) à 2100 µs (2,1 ms)'''.&lt;br /&gt;
&lt;br /&gt;
'''Calcul des valeurs du registre OCR0A :'''&lt;br /&gt;
Notre horloge Arduino fonctionne à 16 MHz. Avec un prescaler de 1024, la durée d'une seule incrémentation (un &amp;quot;tic&amp;quot;) du Timer 0 est de :&lt;br /&gt;
&amp;lt;math&amp;gt;T_{tic} = \frac{1024}{16\,000\,000} = 0,000064 \text{ s} = 0,064 \text{ ms}&amp;lt;/math&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pour trouver la valeur technique à envoyer au registre, on divise le temps d'impulsion du constructeur par la durée de notre tic :&lt;br /&gt;
&lt;br /&gt;
* '''Point central (1,5 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{1,5}{0,064} = 23,43&amp;lt;/math&amp;gt;. En arrondissant, on obtient notre position de démarrage 23.&lt;br /&gt;
&lt;br /&gt;
* '''Butée minimale constructeur (0,9 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{0,9}{0,064} = 14,06&amp;lt;/math&amp;gt;. La limite officielle basse est de 14.&lt;br /&gt;
&lt;br /&gt;
* '''Butée maximale constructeur (2,1 ms) :''' &lt;br /&gt;
&amp;lt;math&amp;gt;\frac{2,1}{0,064} = 32,81&amp;lt;/math&amp;gt;. La limite officielle haute est de 33.&lt;br /&gt;
&lt;br /&gt;
'' 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.''&lt;br /&gt;
&lt;br /&gt;
&amp;lt;br&amp;gt;&lt;br /&gt;
&amp;lt;span style=&amp;quot;color:#800020;font-size: 1.2em; font-weight: bold;&amp;quot;&amp;gt;Code du Jour 1&amp;lt;/span&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes pour les boutons&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; // Valeur de démarrage (moteur à l'arrêt)&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1 diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2 augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour que le réglage du servo ne soit pas trop rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12716</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12716"/>
		<updated>2026-03-01T21:18:22Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments :&lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
**utilisez la persistence rétinienne ;&lt;br /&gt;
**utilisez les interruptions générées par un second minuteur. &lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
= Historique =&lt;br /&gt;
&lt;br /&gt;
= Travail réalisé =&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;span style=&amp;quot;color:#800020;font-weight: bold;&amp;quot;&amp;gt;1. Youssef_servo.c &amp;lt;/span&amp;gt; ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define LATCH PD4  // Signal de validation &lt;br /&gt;
#define CLOCK PD7  // Signal d'horloge &lt;br /&gt;
#define DATA  PB0  // Fil de données &lt;br /&gt;
&lt;br /&gt;
volatile uint16_t angle = 90; &lt;br /&gt;
const uint8_t segments[] = {~0x3F,~0x06,~0x5B,~0x4F,~0x66,~0x6D,~0x7D,~0x07,~0x7F,~0x6F}; &lt;br /&gt;
//Le symbole '~' inverse les bits car l'afficheur s'allume avec du 0V&lt;br /&gt;
&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
    for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
        // On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
        if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))){&lt;br /&gt;
            PORTB |= (1 &amp;lt;&amp;lt; DATA);  // On envoie un 1 (5V)&lt;br /&gt;
        } else {&lt;br /&gt;
            PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; DATA); // On envoie un 0 (0V)&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        // On actionne l'horloge  manuellement pour faire avancer le bit&lt;br /&gt;
        PORTD |= (1 &amp;lt;&amp;lt; CLOCK); &lt;br /&gt;
        PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; CLOCK);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER2_COMPA_vect) {&lt;br /&gt;
    // static permet de garder en mémoire quel digit (0, 1 ou 2) on doit allumer&lt;br /&gt;
    static uint8_t digit = 0;&lt;br /&gt;
    &lt;br /&gt;
    // On extrait le bon chiffre du nombre total Centaines, Dizaines et Unités&lt;br /&gt;
    uint8_t val = (digit == 0) ? (angle/100) : (digit == 1) ? (angle/10)%10 : (angle%10);&lt;br /&gt;
&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; LATCH);      // On bloque l'affichage le temps de préparer les données&lt;br /&gt;
    envoyer(segments[val]);       // chiffre&lt;br /&gt;
    envoyer(1 &amp;lt;&amp;lt; digit);          // digit&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; LATCH);       // On déverrouille pour afficher le résultat final&lt;br /&gt;
&lt;br /&gt;
    // On prépare le tour suivant &lt;br /&gt;
    digit = (digit + 1) % 3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;LATCH) | (1&amp;lt;&amp;lt;CLOCK) | (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    DDRB |= (1&amp;lt;&amp;lt;DATA);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    // On règle une alerte toutes les 1 milliseconde pour changer de chiffre&lt;br /&gt;
    TCCR2A = (1&amp;lt;&amp;lt;WGM21);           // Mode CTC &lt;br /&gt;
    TIMSK2 |= (1&amp;lt;&amp;lt;OCIE2A);         // On active l'alerte&lt;br /&gt;
    OCR2A = 249;                   // Cible &lt;br /&gt;
    TCCR2B = (1&amp;lt;&amp;lt;CS22);            &lt;br /&gt;
&lt;br /&gt;
    sei(); // On autorise les alertes à fonctionner&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1  diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2  augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        // Calcul pour transformer la valeur technique en un chiffre lisible (0-180)&lt;br /&gt;
        angle = (uint32_t)(pos - 8) * 180 / 30; &lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour le réglage du servo soit pas rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Documents Rendus =&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12715</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12715"/>
		<updated>2026-03-01T21:15:39Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments :&lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
**utilisez la persistence rétinienne ;&lt;br /&gt;
**utilisez les interruptions générées par un second minuteur. &lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
= Historique =&lt;br /&gt;
&lt;br /&gt;
= Travail réalisé =&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;span style=&amp;quot;color:#800020;font-weight: bold;&amp;quot;&amp;gt;1. blink.c &amp;lt;/span&amp;gt; ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
#ifndef F_CPU&lt;br /&gt;
#define F_CPU 16000000UL // On définit la vitesse de l'horloge &lt;br /&gt;
#endif&lt;br /&gt;
&lt;br /&gt;
#include &amp;lt;avr/io.h&amp;gt;&lt;br /&gt;
#include &amp;lt;avr/interrupt.h&amp;gt;&lt;br /&gt;
#include &amp;lt;util/delay.h&amp;gt;&lt;br /&gt;
&lt;br /&gt;
#define LATCH PD4  // Signal de validation &lt;br /&gt;
#define CLOCK PD7  // Signal d'horloge &lt;br /&gt;
#define DATA  PB0  // Fil de données &lt;br /&gt;
&lt;br /&gt;
volatile uint16_t angle = 90; &lt;br /&gt;
const uint8_t segments[] = {~0x3F,~0x06,~0x5B,~0x4F,~0x66,~0x6D,~0x7D,~0x07,~0x7F,~0x6F}; &lt;br /&gt;
//Le symbole '~' inverse les bits car l'afficheur s'allume avec du 0V&lt;br /&gt;
&lt;br /&gt;
void envoyer(uint8_t val) {&lt;br /&gt;
    for (uint8_t i = 0; i &amp;lt; 8; i++) {&lt;br /&gt;
        // On vérifie chaque bit de l'octet du plus grand au plus petit&lt;br /&gt;
        if (val &amp;amp; (1 &amp;lt;&amp;lt; (7 - i))){&lt;br /&gt;
            PORTB |= (1 &amp;lt;&amp;lt; DATA);  // On envoie un 1 (5V)&lt;br /&gt;
        } else {&lt;br /&gt;
            PORTB &amp;amp;= ~(1 &amp;lt;&amp;lt; DATA); // On envoie un 0 (0V)&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        // On actionne l'horloge  manuellement pour faire avancer le bit&lt;br /&gt;
        PORTD |= (1 &amp;lt;&amp;lt; CLOCK); &lt;br /&gt;
        PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; CLOCK);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
ISR(TIMER2_COMPA_vect) {&lt;br /&gt;
    // static permet de garder en mémoire quel digit (0, 1 ou 2) on doit allumer&lt;br /&gt;
    static uint8_t digit = 0;&lt;br /&gt;
    &lt;br /&gt;
    // On extrait le bon chiffre du nombre total Centaines, Dizaines et Unités&lt;br /&gt;
    uint8_t val = (digit == 0) ? (angle/100) : (digit == 1) ? (angle/10)%10 : (angle%10);&lt;br /&gt;
&lt;br /&gt;
    PORTD &amp;amp;= ~(1 &amp;lt;&amp;lt; LATCH);      // On bloque l'affichage le temps de préparer les données&lt;br /&gt;
    envoyer(segments[val]);       // chiffre&lt;br /&gt;
    envoyer(1 &amp;lt;&amp;lt; digit);          // digit&lt;br /&gt;
    PORTD |= (1 &amp;lt;&amp;lt; LATCH);       // On déverrouille pour afficher le résultat final&lt;br /&gt;
&lt;br /&gt;
    // On prépare le tour suivant &lt;br /&gt;
    digit = (digit + 1) % 3;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
int main(void) {&lt;br /&gt;
&lt;br /&gt;
    // On définit les pins comme des sorties (DDR = 1)&lt;br /&gt;
    DDRD |= (1&amp;lt;&amp;lt;LATCH) | (1&amp;lt;&amp;lt;CLOCK) | (1&amp;lt;&amp;lt;PD6) | (1&amp;lt;&amp;lt;PD3);&lt;br /&gt;
    DDRB |= (1&amp;lt;&amp;lt;DATA);&lt;br /&gt;
    &lt;br /&gt;
    // On force le buzzer au silence&lt;br /&gt;
    PORTD |= (1&amp;lt;&amp;lt;PD3); &lt;br /&gt;
&lt;br /&gt;
    // On active les résistances internes&lt;br /&gt;
    PORTC |= (1&amp;lt;&amp;lt;PC1) | (1&amp;lt;&amp;lt;PC2);&lt;br /&gt;
&lt;br /&gt;
    // On crée un signal PWM rapide pour piloter le servo sur la pin D6&lt;br /&gt;
    TCCR0A = (1&amp;lt;&amp;lt;COM0A1) | (1&amp;lt;&amp;lt;WGM01) | (1&amp;lt;&amp;lt;WGM00); &lt;br /&gt;
    TCCR0B = (1&amp;lt;&amp;lt;CS02) | (1&amp;lt;&amp;lt;CS00); // On ralentit le chrono pour avoir du 61Hz&lt;br /&gt;
    &lt;br /&gt;
    // On règle une alerte toutes les 1 milliseconde pour changer de chiffre&lt;br /&gt;
    TCCR2A = (1&amp;lt;&amp;lt;WGM21);           // Mode CTC &lt;br /&gt;
    TIMSK2 |= (1&amp;lt;&amp;lt;OCIE2A);         // On active l'alerte&lt;br /&gt;
    OCR2A = 249;                   // Cible &lt;br /&gt;
    TCCR2B = (1&amp;lt;&amp;lt;CS22);            &lt;br /&gt;
&lt;br /&gt;
    sei(); // On autorise les alertes à fonctionner&lt;br /&gt;
    &lt;br /&gt;
    uint8_t pos = 23; &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    while (1) {&lt;br /&gt;
        // Lecture des boutons  &lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC1)) &amp;amp;&amp;amp; pos &amp;gt; 8)  pos--; // Bouton S1  diminue&lt;br /&gt;
        if (!(PINC &amp;amp; (1&amp;lt;&amp;lt;PC2)) &amp;amp;&amp;amp; pos &amp;lt; 38) pos++; // Bouton S2  augmente&lt;br /&gt;
        &lt;br /&gt;
        OCR0A = pos; // On envoie l'ordre de vitesse au moteur&lt;br /&gt;
        &lt;br /&gt;
        // Calcul pour transformer la valeur technique en un chiffre lisible (0-180)&lt;br /&gt;
        angle = (uint32_t)(pos - 8) * 180 / 30; &lt;br /&gt;
        &lt;br /&gt;
        _delay_ms(100); // Délai pour le réglage du servo soit pas rapide&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Documents Rendus =&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12714</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12714"/>
		<updated>2026-03-01T21:14:40Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments :&lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
**utilisez la persistence rétinienne ;&lt;br /&gt;
**utilisez les interruptions générées par un second minuteur. &lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
= Historique =&lt;br /&gt;
&lt;br /&gt;
= Travail réalisé =&lt;br /&gt;
&lt;br /&gt;
= Documents Rendus =&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12713</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12713"/>
		<updated>2026-03-01T21:14:29Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments :&lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
**utilisez la persistence rétinienne ;&lt;br /&gt;
**utilisez les interruptions générées par un second minuteur. &lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
= Historique =&lt;br /&gt;
&lt;br /&gt;
= Travail réalisé =&lt;br /&gt;
[[Fichier:Youssef_servo.c|vignette|Code finale ]]&lt;br /&gt;
&lt;br /&gt;
= Documents Rendus =&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12712</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12712"/>
		<updated>2026-03-01T21:14:15Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments :&lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
**utilisez la persistence rétinienne ;&lt;br /&gt;
**utilisez les interruptions générées par un second minuteur. &lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
= Historique =&lt;br /&gt;
&lt;br /&gt;
= Travail réalisé =&lt;br /&gt;
[[Fichier:Yousef servo.c|vignette|Code finale ]]&lt;br /&gt;
&lt;br /&gt;
= Documents Rendus =&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12711</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12711"/>
		<updated>2026-03-01T21:12:53Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : /* Travail réalisé */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments :&lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
**utilisez la persistence rétinienne ;&lt;br /&gt;
**utilisez les interruptions générées par un second minuteur. &lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
= Historique =&lt;br /&gt;
&lt;br /&gt;
= Travail réalisé =&lt;br /&gt;
[[Fichier:Yoousef servo.c|vignette|Code finale ]]&lt;br /&gt;
&lt;br /&gt;
= Documents Rendus =&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
	<entry>
		<id>https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12710</id>
		<title>SE3 2025/2026 EC3</title>
		<link rel="alternate" type="text/html" href="https://wiki-se.plil.fr/mediawiki/index.php?title=SE3_2025/2026_EC3&amp;diff=12710"/>
		<updated>2026-03-01T21:07:46Z</updated>

		<summary type="html">&lt;p&gt;Yherelli : &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Objectifs =&lt;br /&gt;
&lt;br /&gt;
Réaliser le programme &amp;quot;capteurs et actionneurs&amp;quot; du cours/TP concernant la programmation en C sur microcontrôleur AVR :&lt;br /&gt;
&lt;br /&gt;
* contrôlez un servo-moteur en utilisant le minuteur 0 ;&lt;br /&gt;
* l’utilisateur utilise deux boutons pour changer l’angle ;&lt;br /&gt;
* affichez l’angle de rotation sur les 7-segments :&lt;br /&gt;
**3 afficheurs doivent être utilisés ;&lt;br /&gt;
**utilisez la persistence rétinienne ;&lt;br /&gt;
**utilisez les interruptions générées par un second minuteur. &lt;br /&gt;
&lt;br /&gt;
= Matériel nécessaire =&lt;br /&gt;
&lt;br /&gt;
* Arduino Uno avec câble USB ;&lt;br /&gt;
* bouclier multi-fonctions ;&lt;br /&gt;
* servo-moteur.&lt;br /&gt;
&lt;br /&gt;
= Historique =&lt;br /&gt;
&lt;br /&gt;
= Travail réalisé =&lt;br /&gt;
&lt;br /&gt;
= Documents Rendus =&lt;/div&gt;</summary>
		<author><name>Yherelli</name></author>
	</entry>
</feed>