SE4Binome2024-5
Introduction
Ce wiki documente le développement d’un pico-ordinateur modulaire. Ce dernier est constitué d’une carte principale, composée d’une carte Shield permettant la connexion des cartes filles, et d’une carte mère (initialement simulée par une Arduino Uno avant sa conception finale). Plusieurs cartes filles sont prévues, chacune remplissant une fonction spécifique : gestion du clavier, affichage, port série, réseau, mémoire de masse et son.
Notre binôme est spécifiquement chargé du développement de la carte fille dédiée au clavier.
Toutes les ressources nécessaires, incluant les codes sources et les schémas, sont disponibles sur le dépôt Git suivant : Lien du Git
Carte Shield
Test
Allumage de LEDS sur Programmateur AVR
Avant de programmer directement sur le shield, nous avons allumé des LED pour tester la carte. Nous avons directement utilisé le code exemple fourni par l'IDE arduino en adaptant simplement les pins correspondant aux leds. Code du test
Lecture de la carte SD
Avant de programmer directement sur le shield, nous avons tester si la carte SD était bien détectée et lue. On a utilisé le code test présent dans l'IDE arduino en adaptant le port utilisé.Code du test
Connecteurs IDC HE10
Afin de tester nos connecteur HE10, nous avons utilisé un afficher 7 segments (Sparkfun 7 segments display). Pour pouvoir vérifier leurs fonctionnements nous avons branché l'afficheur sur chaque connecteur tour à tour. Le programme implanté est un simple connecteur à afficher sur l'afficheur. Voici une vidéo du compteur fonctionnant sur un des 5 connecteurs HE10. (Code ici (section : Example 2 SPI))
Ordonnanceur
Pour les interruptions de notre ordonnanceur nous avons utilisé la procédure d'initialisation du minuteur avec le Timer1 disponible dans les cours de Mr.Redon en renseignant une période de 20ms. Nous l'avons disposé dans le fichier minuteur.c car nous n'allons plus le modifier pour le reste du projet. Dans un autre fichier process.c (avec process.h), nous allons y mettre nos structures pour les process et leurs états puis une union pour le temps qui le caractérise ainsi que toutes les fonctions associés.
Processus
Structure
typedef struct {
uint16_t adresseDepart; //adresse de la fonction de départ
uint16_t adressePile; //adresse de la pile d'exécution
Etat etat;
} Process;
Notre structure caractérisant un processus est définie par les trois champs ci-contre.
Une adresse de départ correspondant à la fonction du processus associé, l'adresse où se situe le processus dans la pile puis l'état de la tâche. Nous avons décidé de faire une structure à part entière compte tenu des multitudes d'états que nous pourrons ajouter pendant la progression du projet.
Etat d'un processus
typedef union {
int sleepingtime;
} Time;
typedef struct {
int id;
Time time;
} Etat ;
L'état d'un processus est alors une structure à deux champs (pour l'instant), l'id doit correspondre à un '#DEFINE' pour avoir un sens tandis que nous avons choisi de faire une union pour le temps qui caractérise notre état afin qu'il puisse avoir un nom et type de variable cohérent avec son état.(Ici seulement sleepingtime car seul l'état endormi existe pour le moment)
Etat | ACTIVE | SLEEPY | ... | ... |
---|---|---|---|---|
Id | 0 | 1 | ... | ... |
Procédures principales
ISR
ISR(TIMER1_COMPA_vect, ISR_NAKED) // Procédure d'interruption
{
TCNT1=0;//reset timer
/* Sauvegarde du contexte de la tâche interrompue */
portSAVE_REGISTERS();
table_process[indice_tache].adressePile = SP;
/* Appel à l'ordonnanceur */
selectProcess();
/* Récupération du contexte de la tâche ré-activée */
SP = table_process[indice_tache].adressePile;
portRESTORE_REGISTERS();
asm volatile ( "reti" );
}
L'ISR est ce qu'il va être executer lors des interruptions toutes les 20 millisecondes. Cela consiste à sauvegarder tous les registres de la tâche qui était en cours avant l'appel de l'ISR pour ensuite passer à la tâche suivante valide (c'est à dire active) sans oublier de charger les registres de la nouvelle tâche.
Nous devons également, à chaque fois avant de changer de tâche, enregistrer le pointeur de pile dans le processus afin de pouvoir revenir là ou elle en est la prochaine fois qu'elle sera appelée. Respectivement, une fois la nouvelle tâche choisie, le pointeur de pile doit prendre l'adresse de là ou en était la tâche avant qu'elle soit interrompue.
Séléction de processus
Lors de la séléction de processus, nous devons parcourir tous les processus jusqu'au moment où nous en trouvons un actif. Ainsi, il deviendra la tâche que nous traiterons lors du prochain intervalle de 20 ms. A ce stade, à chaque fois que la procédure selectProcess est appelée, nous devons réduire le temps de repos de chaque process endormi de 20 ms. D'autres conditions pourrons être ajoutée si ajout d'état a lieu. (voir l'ajout de selectProcess dans l'ISR)
Test
Clignotement de deux LED à fréquences indivisibles entre elles
Afin de vérifier la réussite de notre ordonnanceur à fonctionnement tourniquet, nous avons programmer deux simples tâches changeant l'état d'une LED. Une à une fréquence 200ms et l'autre 300ms (avec un _delay_ms).
void tache1(void){
while (1){
PORTC ^= (1 << PC0);
_delay_ms(300);}}
void tache2(void){
while (1){
PORTD ^= (1 << PD7);
_delay_ms(200);}}
Clignotement grâce à l'état endormi
Nous ajoutons l'état endormi afin que nos tâches ne mettent pas en pause l'ordonnanceur avec un _delay_ms. Pour cela, nous incorporons une fonction makeSleep qui permet de mettre la tâche qui l'appelle dans cet état.
void makeSleep(int t){
table_process[indice_tache].etat.id = SLEEPY;
table_process[indice_tache].etat.time.sleepingtime = t;
TIMER1_COMPA_vect();}
void tache1(void){
while (1){
PORTC ^= (1 << PC0);
_delay_ms(300);}}
void tache3(void){
while (1){
PORTC ^= (1 << PC3);
//_delay_ms(1000);
makeSleep(1000);}}
void tache2(void){
while (1){
PORTD ^= (1 << PD7);
//_delay_ms(1000);
makeSleep(700);}}
A noter que la première tâche executée ne doit pas être endormie.
Communication série et SPI
A l'aide du cours de M.Redon et de ressources sur internet nous avons pu confectionner deux librairies, une pour la communication SPI et une pour série (dans le répertoire Ordonnanceur/OldLibs car elle seront remplacées par celles données par M.Redon plus tard). Nous pouvons allons faire deux tâche distinctes dans notre ordonnanceur, une pour la communication série qui va récupérer le caractère d'une touche pressée pour l'écrire dans une variable globale tandis que la tâche SPI va lire cette variable globale et l'afficher sur un afficheur 7 segments.
void serial(void){
while(1){
data = USART_Receive();
USART_sendChar(data);
makeSleep(20);
}
}
void aff7segments(void){
while(1){
aff_activer();
aff_echange(data);
aff_desactiver();
makeSleep(1000);
effacer_affichage();
}
}
Carte Fille Matrice de touches
Explications générales sur la réalisation de la carte
Pour concevoir la carte fille dédiée au clavier, nous avons réutilisé un ancien clavier DELL équipé d’une matrice déjà fonctionnelle. L’idée consiste à connecter les sorties de cette matrice directement à notre carte fille à l’aide d’un connecteur adapté.
Cependant, un défi se pose : la matrice du clavier comprend un total de 26 sorties (sans compter les LED et autres signaux). Cela dépasse largement les capacités en nombre de broches d’un ATmega328P.
Pour remédier à ce problème, nous avons mis en place une solution d’encodage. Nous avons utilisé un décodeur (3 vers 8) pour gérer les lignes et trois encodeurs (8 vers 3) pour gérer les colonnes. Les encodeurs sont montés en cascade pour réduire significativement le nombre de broches nécessaires. Cette solution permet de minimiser l'utilisation des broches du microcontrôleur.
Nous avons opté pour le décodeur 74HC138D (qui va envoyer un 0 parmi des 1) et trois encodeurs CD4532BM (qui cherche le premier 1 parmi des 0). On sent quand même les problèmes venir petit à petit. Nous avons étudié la datasheet et comment la mise en cascade fonctionne.
On voit qu'un encodeur possède un bus de sortie de 5 bits. GS -> à l'état haut quand le composant reçoit un 1, 0 sinon. E0 -> à l'état haut quand il reçoit seulement des 0, 1 sinon. Puis Q2, Q1 ,Q0 la représentation binaire de l'entrée donnée.
Afin de les mettre en cascade, il faut relié le E0 d'un premier encodeur au Ei de l'autre. Ensuite, nous allons faire un "OU" entre les GS afin de savoir si un des encodeurs reçoit un 1, puis grâce aux GS des deux premiers encodeurs nous sauront quelle encodeur à reçu le 1 (si les deux sont & 0 alors c'est le troisième). De même, faire une porte "OU" sur Q2, Q1, Q0 afin de réduire le nombre d'entrées/sorties nécessaires.
Par ailleurs, étant donné que nous utilisons directement le clavier Dell, il a été nécessaire de concevoir la carte avec des dimensions strictement identiques à celles de la carte d’origine, notamment en ce qui concerne sa largeur. Nous avons également dû ajouter des trous aux emplacements précis pour permettre de visser correctement la carte. Nous avons également dû créer l'empreinte KiCad du connecteur de la matrice, car celle-ci n’était pas disponible dans les bibliothèques existantes.Empreinte du connecteur matrice. Pour les connecteurs HE-10 et AVR-ISP, nous avons veillé à les positionner sur la face opposée au connecteur de la matrice. Cela permet de les rendre directement accessibles une fois tous les composants branchés, facilitant ainsi les connexions et les manipulations.
Détails des composants
ATmega328P
- Micro-contrôleur
74HC138D
- Décodeur 3->8: envoie un 0 parmi des 1.
CD4532BM
- Encodeur 8->3
74LS32
- Porte logique Or pour la mise en cascade des encodeurs.
Connecteur HE10
- Connecteur pour la liaison PicoShield-CarteFille
Connecteur de la matrice
- Simplement une empreinte que l'on a créé Lien vers la bibliothèque présent sur le git
AVR-ISP-6
- Programmateur AVR
LED_SMD
- 3 LEDs
Hardware
Kicad
Pour réaliser la carte fille, nous avions dans un premier temps conçu une carte avec une matrice de touches directement implantée sur celle-ci, mais jugeant que cela était trop simple nous avons été redirigé vers l'utilisation d'une matrice déjà réalisé. Ainsi nous avons avons réutiliser celle d'un vieux clavier dell en créant les empreintes à la bonne taille pour se connecter sur la matrice.
Différents Problèmes rencontrés
Avant même la réception de la carte, alors que nous commençons a essayer d'écrire un premier programme, nous nous rendons compte qu'envoyer un 0 parmi des 1 et de rechercher un 1 parmi des 0 ne fonctionnera pas (logique). Nous avons alors pris la décision de changer le décodeur car il était pour nous plus simple de changer un composant que les trois encodeurs. Nous avons opté pour lui de la même famille, le 74HTC238D.
Dès la réception de la carte, nous avons effectué un premier test avec une simple programmation de LED. Nous nous sommes rapidement aperçus que nous avions configuré deux noms d'étiquettes de reset distinctes sur KiCad : une pour l'AVR (RST) et le connecteur HE-10 (RST) puis un autre pour le MicroP et le bouton (RESET). Pour résoudre ce problème, nous avons simplement connecté le RST de l'AVR-ISP au RESET du bouton reset.
Lors de l'examen initial de notre carte, nous avons également constaté une erreur que nous avions commise. En effet, comme nous avions initialement conçu une matrice de touches sans encodeurs ni décodeurs, il n'était donc pas nécessaire d'intégrer des résistances de pull-up ou de pull-down. Nous avons alors juste rajouter ces composants, or il en résulte que lorsqu'une touche sera pressée, un 1 sera bien envoyé mais pas parmi des 0, parmi des états indéterminé. En général, les états indéterminés, on aime pas trop, surtout quand cela empêche ta carte de pouvoir fonctionner comme voulu. Il a donc été nécessaire d'ajouter des résistances de pull-down sur chaque colonne de notre clavier. Afin de simplifier cette modification, nous avons utilisé des résistances CMS que nous avons positionnées comme des dominos (verticalement), ce qui nous a permis de faire passer un fil de masse directement au-dessus des résistances.
Une fois tous ces problèmes de base résolus, nous avons enfin pu nous attaquer à la programmation du clavier. Pour vérifier que toutes les touches étaient correctement détectées, nous avons réalisé un petit programme de test (Code du test). Ce programme nous a permis d’associer chaque LED à un décodeur différent via la broche GS des décodeurs. Ainsi, nous avons pu identifier quelles touches ne répondaient pas. Premièrement, beaucoup de touches ne répondaient pas, moins d'une dizaine. Nous avons alors fait de multiples tests au multimètre sur notre carte mais aussi sur une autre matrice de touches similaire. Une des hypothèses fut qu'il fallait bien étamer les pistes pour permettre la conduction entre la carte et la matrice. Nous avons alors rajouter de l'étain, puis + de touches commençaient à fonctionner. Il fallait alors un nouveau programme de test pour détecter quelles lignes ou colonnes n'étaient pas bien étamés afin de permettre une conduction (Code de détection). Il suffisait ainsi de rajouter de l'étain sur les colonnes et les lignes qui ne répondaient pas. Enfin, toutes les touches sont détectées lors de d'une pression !
Programmation de la carte
Programme de test
Lorsqu'une touche est pressée, le contact mécanique rebondit, générant plusieurs signaux rapides. Pour éviter que ces rebonds ne soient interprétés comme plusieurs pressions, nous avons simplement ajouté un délai de 100 millisecondes dans notre programme, ce qui permet d’ignorer ces variations et de ne retenir qu’un signal stable.
Afin de tester chaque touche de notre matrice, nous avons décidé de faire le programme le plus simple possible en utilisant les broches GS de nos encodeurs. Il n'y a pas d'histoire de ligne ou de colonnes ici, si une touche est pressée alors le GS de l'encodeur ou sa colonne est reliée passera à l'état haut. Une manière très simple de savoir si une touche est utilisable et si nos trois encodeurs sont opérationnels également. Nous avons trois leds pour trois encodeurs, pour chaque encodeur, s'allumera une led spécifique si il reçoit un 1 parmi des 0.
Afin de mettre en place ce système, il nous faut un programme capable de balayer toutes les lignes de notre matrice via un décodeur.
int tab_outputs[8][3] = {
{0, 0, 0},
{1, 0, 0},
{0, 1, 0},
{1, 1, 0},
{0, 0, 1},
{1, 0, 1},
{0, 1, 1},
{1, 1, 1},
};
void maj_output(int lig){
for (int i = 0 ; i < 3 ; i++){
if (tab_outputs[lig][i] == 0){
PORTC &= ~(1 << i);
}
else if (tab_outputs[lig][i] == 1){
PORTC |= (1 << i);
}
}
}
Ici, grâce à l'initialisation d'un tableau de la représentation en binaire des chiffres 0 à 7, il suffit de faire une procédure qui prend en paramètre le numéro de ligne souhaitée puis de vérifier dans le tableau quelle sortie mettre à 0 et 1. Nous avions remarqué que mettre les sorties pour les décodeurs sur le même port côte à côte facilite la programmation.
Programme de détection
Dans le projet, nous avons eu des problèmes de lignes et colonnes qui n'était pas conduit entre notre carte et la matrice. Pour les identifier, nous avons mis en place un programme de détection qui lors de la pression d'une touche, va afficher grâce aux leds, de quel encodeur sa colonne régit puis quelle ligne et quelle colonne est activée. Ce programme permet ainsi de diagnostiquer rapidement les problèmes de conduction entre la carte et la matrice en identifiant précisément les correspondances défectueuses.
Pour cela, nous avons imaginer deux fonctions show_ligne et show_colonne, permettant d'afficher sur nos trois leds la ligne et la colonne. Si les leds affichent : double clignotements de la led du milieu (allumée, éteint, allumé) puis (éteint, allumée, allumée) alors la ligne L6 (voir KiCad) et la 4e colonne du deuxième encodeur sont activées.
void show_ligne(int i){
turn_off_leds();
for (int bit = 0; bit < 3; bit++){
if (tab_outputs[i][bit] == 1) {
PORTC |= (1 << (bit+LED1));
}
else
PORTC &= ~(1 << (bit+LED1));
}
_delay_ms(SHOW_TIME);
turn_off_leds();
}}
}
void show_colonne(){
turn_off_leds();
for (int i = Q0; i < Q2 + 1 ; i++){
if (((inputs >> i) & 1) == 1) {
PORTC |= (1 << (i+LED1));
}
else
PORTC &= ~(1 << (i+LED1));
}
_delay_ms(SHOW_TIME);
turn_off_leds();
}
Les deux fonctions se ressemblent beaucoup, la différence étant que show_ligne va prendre un paramètre un entier selon la ligne que nous voulons afficher alors que colonne va directement regarde dans le bus d'entrées la représentation binaire de la colonne activée. A noter que le fait que les LEDs et Q2, Q1, Q0 sont successivement placés sur le même port rend la programmation plus simple à réaliser.
A noter la présence d'un 'uint8_t inputs;' où l'on va assigner PIND à chaque début d'une boucle, en effet pendant l'afficher l'utilisateur ne reste pas forcément la touche enfoncées ce qui changera les valeurs du bus d'entrée. Il est alors important de sauvegarder l'état de ce bus avant de l'afficher.
Lors de la pression de la touche M, on voit que l'encodeur touché est le deuxième puis on voit un 3 en binaire ce qui donne la 4e ligne soit L4 puis un 7 en binaire ce qui donne la 8e ligne du 2e encodeur soit C16. (voir les étiquettes sur la shématique KiCad)
De même pour la touche E, 3ème encodeur, L3 et C18. Décalage de 1 car nos étiquettes commencent à C1 et L1 et non à C0 et L0.
Ping pong
A l'aide des ressources envoyées par M.Redon, nous avons de quoi communiquer entre le shield et notre carte fille. Pour cela, nous avons du modifié un tout petit peu les programmes. En effet, nous avons ajouter un #define pour le Chip Select de notre clavier (le bon connecteur HE10 sur lequel nous allons le connecter) afin que ce soit bien lui qui sera sélectionné lors du spi_select et spi_deselect. Le shield va envoyer un 1 en SPI pour indiquer au clavier qu'il est sur écoute et qu'il attend un envoi de la carte fille.
Cette communication va nous permettre de bien mapper toutes nos touches.
Nous avons décidé de faire un tableau en 3 dimensions afin de faciliter le mapping. Pourquoi 3 dimensions ? Nous avons une première dimension pour le numéro de ligne (0 à 7), pour le numéro de colonne (0 à 7) puis pour le numéro d'encodeur (0 à 2). Le bus d'entrée nous donnant un numéro de ligne entre 0 et 7, nous avons trouvé plus facile de procéder de cette manière. Voici un exemple.
char tab_touches[8][8][3] = {
{{'\0'/*AltGr*/, '\0', '\0'}, {'\0', '\0', '\0'}, {'-', ' ', '\0'}, {'\0'/*Fleche gauche*/, 'b', '\0'}, {'\0'/*Fleche droite*/, '\0', '\0'}, {'\0'/*Fleche bas*/, '\0', '\0'}, {'\0', '\0', '\0'}, {'\0', '!', '\0'}},
{{'\0', 'x', 'w'}, {'\0', 'c', '\0'/*CTRL Droit*/}, {'*', '\n', '\0'}, {'\0'/*Pause*/, 'v', '\0'}, {'/', ',', '\0'}, {'\0'/*Verr Num*/, ';', '\0'}, {'\0'/*Touch window droite*/, ':', '\0'}, {'\0'/*Shift droit*/, '*', '\0'}},
{{'\0'/*Arret*/, 'z', 'a'}, {'\0'/*Options*/, 'e', '\0'}, {'9', '\0', '\0'}, {'\0', 'r', '\0'}, {'8', 'u', '\0'}, {'7', 'i', '\0'}, {'\0', 'o', '\0'}, {'*', 'p', '\0'}},
{{'\0', 's', 'q'}, {'\0'/*Touche window gauche*/, 'd', '\0'}, {'3', '\0', '\0'}, {'\n', 'f', '\0'}, {'2', 'j', '\0'}, {'1', 'k', '\0'}, {'\0', 'l', '\0'}, {'\0', 'm', '\0'}},
{{'\0', '\0', '\0'}, {'\0', '\0', '\0'}, {'\0', '\0', '\0'}, {'\0', '\0', '\0'}, {'\0', '\0', '\0'}, {'\0', '\0', 'w'}, {'\0', '\0', '\0'}, {'\0', '\0', '\0'}},
{{'\0', '<', '\0'}, {'\0', '\0', '\0'}, {'.', '\0', '\0'}, {'\0', 'g', '\0'}, {'0', 'h', '\0'}, {'\0', '\0', '\0'}, {'\0', '\0', '\0'}, {'\0', '%', '\0'}},
{{'\0', '\0'/*MAJ*/, '\0'/*tab*/}, {'\0', '\0'/*F3*/, '\0'}, {'\0', 8, '\0'}, {'\0', 't', '\0'}, {'5', 'y', '\0'}, {'4', '$', '\0'}, {'\0', '\0'/*F7*/, '\0'}, {'\0'/*Shift gauche*/, '^', '\0'}},
{{'\0', '\0'/*F1*/, '\0'/*œ*/}, {'\0', '\0'/*F2*/, '\0'/*CTRL Gauche*/}, {'\0'/*Page haut*/, '\0'/*F9*/, '\0'}, {'\0'/*Debut*/, '(', '\0'}, {'\0'/*Insert*/, '-', '\0'}, {'\0'/*Supp*/, '=', '\0'}, {'t', '\n'/*F8*/, '\0'}, {'g', ')', '\0'}}
};
Notre clavier ne supporte pas l'appui de plusieurs touches en simultané à cause de la présence de décodeurs et d'encodeurs, le shift devient inutilisable, seulement maj est opérationnel. Afin de permettre l'utilisation de altgr, il fonctionnera comme le maj mais les deux ne pourront pas être activés en même temps.
Nous avons tout d'abord mis des valeurs au hasard mais unique dans le tableau, puis nous avons identifié les touches pour une case dans le tableau, nous avons fait ce processus plusieurs fois afin de trouver toutes les cases des touches du clavier dans le tableau. Pour permettre l'utilisation du maj et du altgr, nous avons créer deux autres tableau comme ça, un chacun avec ses touches assignées. Lors de l'envoi, il faut vérifier dans quel mode nous nous trouvons pour envoyer le caractère dans le bon tableau (alt, maj ou classique).
while(1)
{
spi_select();
uint8_t r=spi_exch(1);
if (r != 1) printf("%c",r);
spi_deselect();
_delay_ms(20);
}
void envoyer(){
printf("Clavier attente\n");
uint8_t message=spi_wait();
printf("Clavier reçu %d\n",message);
if (message == 1)
spi_sendback(temp);
printf("Clavier retour préparé %d\n",temp);
}
Ici le shield (à gauche) va envoyer un 1 en SPI pour dire au clavier qu'il est prêt à l'écouter, si il reçoit autre chose qu'un 1 (son propre envoi) alors il l'affiche. Le clavier (à droite) va envoyer son dernier caractère dont la touche a été pressée lorsqu'il reçoit ce fameux 1.
Implémentation dans l'ordonnanceur
Pour pouvoir utiliser le clavier avec l'ordonnanceur, il suffit de faire de notre while(1) précédent, une tâche a part entière de notre ordonnanceur. Sur cet exemple on voit l'utilisation du alt et du maj.
Conclusion
Ce projet de pico-ordinateur modulaire a permis de concevoir une architecture évolutive combinant une carte principale (Shield et carte mère) et plusieurs cartes filles spécialisées. Notre travail sur la carte fille dédiée au clavier s’intègre dans cet écosystème.
L'ordonnancement permet de gérer efficacement les interruptions et la planification des processus avec un intervalle de 20 ms. Nous assurons la sauvegarde et la restauration du contexte des tâches, permettant une exécution fluide. Le système est flexible et peut facilement être étendu pour intégrer potentiellement de nouveaux états et processus.
Pour la carte fille, nous avons du faire face à plusieurs problèmes comme les résistances manquantes et lui qui nous a pris le plus de temps à trouver l'origine : conduction entre matrice et carte fille dû au manque d'étain.
Finalement, nous avons fait face à ces problèmes et réussi à faire fonctionner notre carte fille avec l'ordonnanceur.
Quelques points d'améliorations : nous aurions pu essayer d'améliorer le programme de la carte fille pour permettre des pressions plus rapides des touches. Il aurait également été très intéressant d'essayer notre clavier avec la carte mère conçu par le binôme de Louis et Antoine afin de conclure ce projet jusqu'au bout.
NB : nous avons corrigé la schématique Kicad (RST et RESET puis les résistances manquantes)