« SE4Binome2024-4 » : différence entre les versions
mAucun résumé des modifications |
|||
Ligne 20 : | Ligne 20 : | ||
====Test | ====Test carte SD :==== | ||
Nous avons placé la carte SD dans le lecteur puis nous avons utilisé le programme info-carte sur l'IDE Arduino pour vérifier | Nous avons placé la carte SD dans le lecteur puis nous avons utilisé le programme info-carte sur l'IDE Arduino pour vérifier si le lecteur est fonctionnel. | ||
[[Fichier:Test du lecteur de carte SD.png|vignette|Capture d'écran de l'IDE Arduino lors du test du lecteur de carte SD|centré]] | [[Fichier:Test du lecteur de carte SD.png|vignette|Capture d'écran de l'IDE Arduino lors du test du lecteur de carte SD|centré]] | ||
Ligne 29 : | Ligne 29 : | ||
====Test Port He-10 : ==== | ====Test Port He-10 : ==== | ||
Pour tester les ports He-10, nous avons connecté un afficheur 7- | Pour tester les ports He-10, nous avons connecté un afficheur 7-segments pour vérifier qu'ils fonctionnent. | ||
[[Fichier:Video de test des ports HE-10.mp4|vignette|Video de test d'un port HE-10|centré]] | [[Fichier:Video de test des ports HE-10.mp4|vignette|Video de test d'un port HE-10|centré]] | ||
Malgré des difficultés à connecter les afficheurs 7-segments à cause | Malgré des difficultés à connecter les afficheurs 7-segments à cause du défaut de conception, les 5 ports HE-10 sont fonctionnels. | ||
===Ordonnanceur :=== | ===Ordonnanceur :=== | ||
Ligne 41 : | Ligne 41 : | ||
Le principe de l'ordonnanceur est de simuler une | Le principe de l'ordonnanceur est de simuler une exécution en parallèle des tâches. | ||
Ligne 49 : | Ligne 49 : | ||
Lorsqu'une tâche précédemment interrompue se réexécute, elle reprend là où elle en était dans son processus. | Lorsqu'une tâche précédemment interrompue se réexécute, elle reprend là où elle en était dans son processus. | ||
Ici la fonction d'interruption ISR fait clignoter les | Ici la fonction d'interruption ISR fait clignoter les LEDs lors d'une interruption, la fonction sei() active les interruptions.<syntaxhighlight lang="c" line="1" start="1"> | ||
#include <avr/io.h> | #include <avr/io.h> | ||
#include <avr/interrupt.h> | #include <avr/interrupt.h> | ||
Ligne 206 : | Ligne 206 : | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Ensuite, nous avons séparé le programme de clignotement des | Ensuite, nous avons séparé le programme de clignotement des LEDs en 5 programmes, 1 pour chaque LED. Et les programmes sont appelés par le scheduler chacun leur tour. | ||
<syntaxhighlight lang="c" line="1" start="1"> | <syntaxhighlight lang="c" line="1" start="1"> | ||
void Led1(void) | void Led1(void) | ||
Ligne 261 : | Ligne 261 : | ||
Cprocess=0; | Cprocess=0; | ||
} | } | ||
</syntaxhighlight>Nous avons aussi | </syntaxhighlight>Nous avons aussi fait une fonction d'initialisation des piles d'exécution des programmes qui seront lancés après la première interruption.<syntaxhighlight lang="c" line="1" start="1"> | ||
void InitialisationPile(int Cprocess){ | void InitialisationPile(int Cprocess){ | ||
Ligne 319 : | Ligne 319 : | ||
Maintenant, on veut pouvoir endormir des tâches pendant une durée définie. Ces tâches doivent être ignorées par le scheduler. | Maintenant, on veut pouvoir endormir des tâches pendant une durée définie. Ces tâches doivent être ignorées par le scheduler. | ||
Pour cela, nous avons | Pour cela, nous avons rajouté une durée d'endormissement dans la structure tab_process et un programme qui réduit le temps de sommeil à chaque interruption puis un programme qui endort la tâche ciblée. | ||
<syntaxhighlight lang="c" line="1" start="1"> | <syntaxhighlight lang="c" line="1" start="1"> | ||
typedef struct tab_process{ | typedef struct tab_process{ | ||
Ligne 349 : | Ligne 349 : | ||
tab_process[process].sleep=time; | tab_process[process].sleep=time; | ||
} | } | ||
</syntaxhighlight>Nous avons aussi | </syntaxhighlight>Nous avons aussi modifié le scheduler :<syntaxhighlight lang="c" line="1" start="1"> | ||
void scheduler(void) | void scheduler(void) | ||
{ | { | ||
Ligne 363 : | Ligne 363 : | ||
} | } | ||
} | } | ||
</syntaxhighlight>On a rajouté une condition au passage à la tâche suivante : | </syntaxhighlight>On a rajouté une condition au passage à la tâche suivante : si la prochaine tâche est éveillée, on passe la tâche endormie et on exécute celle là. Si la tâche encore d'après n'est pas endormie aussi et ainsi de suite. | ||
On peut repérer un problème : que se passe-t-il si toutes les tâches sont endormies ? On pourrait faire confiance à l'utilisateur en priant pour qu'il n'endorme pas tout le monde mais nous avons préféré ne pas lui faire confiance. | On peut repérer un problème : que se passe-t-il si toutes les tâches sont endormies ? On pourrait faire confiance à l'utilisateur en priant pour qu'il n'endorme pas tout le monde mais nous avons préféré ne pas lui faire confiance. | ||
Nous avons donc | Nous avons donc crée un processus qui est naturellement endormi mais qui se réveille lorsque tout le monde dort, pour éviter une boucle infinie.<syntaxhighlight lang="c" line="1"> | ||
void Anti_sleep_boucle(void) | void Anti_sleep_boucle(void) | ||
{ | { | ||
Ligne 410 : | Ligne 410 : | ||
Bonus : | Bonus : | ||
Nous avons amélioré le makefile donné par Mr Boé l'année | Nous avons amélioré le makefile donné par Mr Boé l'année dernière :<syntaxhighlight lang="c"> | ||
// modification pour un nettoyer plus efficace | // modification pour un nettoyer plus efficace | ||
clean: | clean: |
Version du 27 novembre 2024 à 11:18
Lien du GIT : https://gitea.plil.fr/jwacquet/Pico_Binome4_Justin_Ibrahim.git
Shield Arduino
Projet Kicad : Fichier:Projet Kicad Shield.zip
Le shield va nous permettre de tester nos cartes filles sans la carte mère du Pico. Pour faire nos tests, le shield doit posséder un lecteur de carte SD pour tester la mémoire, des LEDs pour tester des processus et des ports He-10 pour y brancher des cartes filles.
Carte Shield Soudé :
Petite erreur de conception, la carte a été routée à l'envers... Ce n'est pas catastrophique (un peu quand même) et c'est rattrapable en rajoutant des rallonges pour avoir assez d'espace pour brancher les He-10.
Maintenant que la carte est soudé nous devons tester les différents éléments de la carte.
Test LED :
Pour tester les Leds nous avons réalisé un programme sur arduino qui allume les Leds.
Test carte SD :
Nous avons placé la carte SD dans le lecteur puis nous avons utilisé le programme info-carte sur l'IDE Arduino pour vérifier si le lecteur est fonctionnel.
Test Port He-10 :
Pour tester les ports He-10, nous avons connecté un afficheur 7-segments pour vérifier qu'ils fonctionnent.
Malgré des difficultés à connecter les afficheurs 7-segments à cause du défaut de conception, les 5 ports HE-10 sont fonctionnels.
Ordonnanceur :
Ordonnanceur (No Naked):
Le principe de l'ordonnanceur est de simuler une exécution en parallèle des tâches.
Pour cela, on génère un minuteur qui va nous donner une durée d'exécution d'un programme avant que ce-dernier ne se fasse interrompre pour qu'une autre tâche s'exécute.
Lorsqu'une tâche précédemment interrompue se réexécute, elle reprend là où elle en était dans son processus.
Ici la fonction d'interruption ISR fait clignoter les LEDs lors d'une interruption, la fonction sei() active les interruptions.
#include <avr/io.h>
#include <avr/interrupt.h>
#define CTC1 WGM12
#define PERIODE 1000
ISR(TIMER1_COMPA_vect){ // Procédure d'interruption
int led1=(PORTC & 0x0f);
led1 >>= 1; if(led1==0) led1=0b00001001;
PORTC &= 0xf0; PORTC |= led1;
int led2=(PORTD & 0x0f);
led2 >>= 1; if(led2==0) led2=0b10010010;
PORTD &= 0xf0; PORTD |= led2;
}
void init_minuteur(int diviseur,long periode){
TCCR1A=0; // Le mode choisi n'utilise pas ce registre
TCCR1B=(1<<CTC1); // Réinitialisation du minuteur sur expiration
switch(diviseur){
case 8: TCCR1B |= (1<<CS11); break;
case 64: TCCR1B |= (1<<CS11 | 11<<CS10); break;
case 256: TCCR1B |= (1<<CS12); break;
case 1024: TCCR1B |= (1<<CS12 | 1<<CS10); break;
}
// Un cycle prend 1/F_CPU secondes.
// Un pas de compteur prend diviseur/F_CPU secondes.
// Pour une periode en millisecondes, il faut (periode/1000)/(diviseur/F_CPU) pas
// soit (periode*F_CPU)/(1000*diviseur)
OCR1A=F_CPU/1000*periode/diviseur; // Calcul du pas
TCNT1=0; // Compteur initialisé
TIMSK1=(1<<OCIE1A); // Comparaison du compteur avec OCR1A
}
int main(){
init_minuteur(1024,PERIODE);
DDRC |= 0b00001001;
DDRD |= 0b10010010;
PORTC &= ~0b00001001;
PORTD &= ~0b10010010;
init_minuteur(256,PERIODE);
sei(); // Autorisation des interruptions
while(1);
}
Ordonnanceur Naked en Round-Robin ( Tourniquet en patois ) :
Maintenant, nous allons utiliser l'ISR en mode Naked, c'est-à-dire qu'il ne gère plus la sauvegarde et la restauration du contexte des tâches lors de leur interruption.
Pour commencer, nous avons écrit le bout de code qui sauvegarde le contexte sur la pile et celui qui le restaure.
#define portSAVE_REGISTER() \
asm volatile ( \
"push r0 \n\t" \
"in r0, __SREG__ \n\t" \
"push r0 \n\t" \
"push r1 \n\t" \
"push r2 \n\t" \
"push r3 \n\t" \
"push r4 \n\t" \
"push r5 \n\t" \
"push r6 \n\t" \
"push r7 \n\t" \
"push r8 \n\t" \
"push r9 \n\t" \
"push r10 \n\t" \
"push r11 \n\t" \
"push r12 \n\t" \
"push r13 \n\t" \
"push r14 \n\t" \
"push r15 \n\t" \
"push r16 \n\t" \
"push r17 \n\t" \
"push r18 \n\t" \
"push r19 \n\t" \
"push r20 \n\t" \
"push r21 \n\t" \
"push r22 \n\t" \
"push r23 \n\t" \
"push r24 \n\t" \
"push r25 \n\t" \
"push r26 \n\t" \
"push r27 \n\t" \
"push r28 \n\t" \
"push r29 \n\t" \
"push r30 \n\t" \
"push r31 \n\t" \
);
#define portRESTORE_REGISTER() \
asm volatile ( \
"pop r31 \n\t" \
"pop r30 \n\t" \
"pop r29 \n\t" \
"pop r28 \n\t" \
"pop r27 \n\t" \
"pop r26 \n\t" \
"pop r25 \n\t" \
"pop r24 \n\t" \
"pop r23 \n\t" \
"pop r22 \n\t" \
"pop r21 \n\t" \
"pop r20 \n\t" \
"pop r19 \n\t" \
"pop r18 \n\t" \
"pop r17 \n\t" \
"pop r16 \n\t" \
"pop r15 \n\t" \
"pop r14 \n\t" \
"pop r13 \n\t" \
"pop r12 \n\t" \
"pop r11 \n\t" \
"pop r10 \n\t" \
"pop r9 \n\t" \
"pop r8 \n\t" \
"pop r7 \n\t" \
"pop r6 \n\t" \
"pop r5 \n\t" \
"pop r4 \n\t" \
"pop r3 \n\t" \
"pop r2 \n\t" \
"pop r1 \n\t" \
"pop r0 \n\t" \
"out __SREG__, r0 \n\t" \
"pop r0 \n\t" \
);
ISR(TIMER1_COMPA_vect,ISR_NAKED){ // Procédure d'interruption
portSAVE_REGISTER();
scheduler();
portRESTORE_REGISTER();
asm volatile ( "reti" );
}
Puis, nous avons créé une structure de processus, pour que chaque processus soit décrit par un pointeur de fonction, un StackPointer et un état (fini ou en cours).
Afin de pouvoir créer un tableau de processus en global. De plus, on initialise une variable globale "Cprocess" qui représente le processus courant.
typedef struct tab_process{
uint16_t StackPointer;
void (*process_add)(void);
uint8_t state;
}
tab_process_t;
tab_process_t tab_process[NB_PROCESS];
uint8_t Cprocess=0;
Ensuite, nous avons séparé le programme de clignotement des LEDs en 5 programmes, 1 pour chaque LED. Et les programmes sont appelés par le scheduler chacun leur tour.
void Led1(void)
{
while (1)
{
PORTC ^= 0b00000001;
_delay_ms(100);
}
}
void Led2(void)
{
while (1)
{
PORTD ^= 0b00000010;
_delay_ms(200);
}
}
void Led3(void)
{
while (1)
{
PORTC ^= 0b00001000;
_delay_ms(300);
}
}
void Led4(void)
{
while (1)
{
PORTD ^= 0b00010000;
_delay_ms(400);
}
}
void Led5(void)
{
while (1)
{
PORTD ^= 0b10000000;
_delay_ms(500);
}
}
void scheduler(void)
{
if (Cprocess < NB_PROCESS-1)
Cprocess+=1;
else
Cprocess=0;
}
Nous avons aussi fait une fonction d'initialisation des piles d'exécution des programmes qui seront lancés après la première interruption.
void InitialisationPile(int Cprocess){
int save = SP;
SP = tab_process[Cprocess].StackPointer;
uint16_t address = (uint16_t)tab_process[Cprocess].process_add;
asm volatile("push %0" : : "r" (address & 0x00ff) );
asm volatile("push %0" : : "r" ((address & 0xff00)>>8) );
portSAVE_REGISTER();
tab_process[Cprocess].StackPointer = SP;
SP = save;
}
Il ne reste plus qu'à faire le setup, associer un processus à un emplacement du tableau de processus; lui associer une adresse de StackPointer et un état, démarrer le minuteur, mettre en place les entrées/sorties , initialiser les piles puis activer les interruptions .
Finalement, on peut lancer la première tâche.
Avec la façon dont nous avons organisé le scheduler, l'ordonnanceur fonctionne en tourniquet.
void setup()
{
tab_process[0].process_add = Led1;
tab_process[1].process_add = Led2;
tab_process[2].process_add = Led3;
tab_process[3].process_add = Led4;
tab_process[4].process_add = Led5;
tab_process[0].StackPointer = 0x6ff;
tab_process[1].StackPointer = 0x680;
tab_process[2].StackPointer = 0x5ff;
tab_process[3].StackPointer = 0x580;
tab_process[4].StackPointer = 0x4ff;
tab_process[0].state = 0;
tab_process[1].state = 0;
tab_process[2].state = 0;
tab_process[3].state = 0;
tab_process[4].state = 0;
init_minuteur(1024,PERIODE);
DDRC |= 0b00001001;
DDRD |= 0b10010010;
InitialisationPile(1);
InitialisationPile(2);
InitialisationPile(3);
InitialisationPile(4);
sei(); // Autorisation des interruptions
}
int main()
{
setup();
SP = tab_process[Cprocess].StackPointer;
tab_process[Cprocess].process_add();
}
Ordonnanceur Naked avec fonction d'endormissement des tâches :
Maintenant, on veut pouvoir endormir des tâches pendant une durée définie. Ces tâches doivent être ignorées par le scheduler.
Pour cela, nous avons rajouté une durée d'endormissement dans la structure tab_process et un programme qui réduit le temps de sommeil à chaque interruption puis un programme qui endort la tâche ciblée.
typedef struct tab_process{
uint16_t StackPointer;
void (*process_add)(void);
uint8_t state;
uint16_t sleep;
}
tab_process_t;
#define TIME_INTERRUPT 20
void DecreaseSleep(void)
{
int nb_sleep = 0;
for (uint8_t i=1;i<NB_PROCESS-1;i++)
{
if (tab_process[i].sleep > 0)
{
tab_process[i].sleep -= TIME_INTERRUPT;
nb_sleep++;
}
}
}
void setTimeDelay(uint16_t time, uint8_t process)
{
tab_process[process].sleep=time;
}
Nous avons aussi modifié le scheduler :
void scheduler(void)
{
startScheduler:
if (Cprocess < NB_PROCESS-1)
Cprocess+=1;
else
Cprocess=1;
if (tab_process[Cprocess].sleep > 0)
{
// scheduler();
goto startScheduler;
}
}
On a rajouté une condition au passage à la tâche suivante : si la prochaine tâche est éveillée, on passe la tâche endormie et on exécute celle là. Si la tâche encore d'après n'est pas endormie aussi et ainsi de suite.
On peut repérer un problème : que se passe-t-il si toutes les tâches sont endormies ? On pourrait faire confiance à l'utilisateur en priant pour qu'il n'endorme pas tout le monde mais nous avons préféré ne pas lui faire confiance.
Nous avons donc crée un processus qui est naturellement endormi mais qui se réveille lorsque tout le monde dort, pour éviter une boucle infinie.
void Anti_sleep_boucle(void)
{
while(1);
}
// ajoute à setup
void setup()
{
tab_process[NB_PROCESS-1].process_add = Anti_sleep_boucle;
tab_process[NB_PROCESS-1].sleep = 1;
tab_process[NB_PROCESS-1].StackPointer = 0x3ff;
tab_process[NB_PROCESS-1].state = 1;
}
// ajoute à DecreaseSleep
if (nb_sleep == NB_PROCESS-2)
{
tab_process[NB_PROCESS-1].sleep = 0;
}
else
tab_process[NB_PROCESS-1].sleep = 1;
On accorde les autres programmes à Anti_sleep_boucle, on a décidé qu'il sera le dernier processus par soucis de simplicité.
Bonus :
Nous avons amélioré le makefile donné par Mr Boé l'année dernière :
// modification pour un nettoyer plus efficace
clean:
rm -f *.o
rm -f *.hex
rm -f *.elf
// modification pour pouvoir upload directement les modfications sur la carte
upload: $(TARGET).hex
stty -F $(TERM) hupcl # reset
avrdude -p atmega328p -c arduino -P /dev/ttyUSB0 \ -b 115200 -D -U flash:w:ordonnanceur.hex:i
Carte Réseau
Projet Kicad:Fichier:Kicad CReseau.zip
Conception
Nous allons réaliser la carte fille réseau. Nous avons décidé de partir sur la carte RNDIS, avec un AT90USB1287 car il a une plus grande capacité de stockage que l'AtMega32u4. La carte communiquera avec l'extérieur par mini USB et pourra être alimenté de deux manières différentes: Soit par la carte mère ou soit par le port USB directement grâce à un jumper. On a également ajouté des LEDs qui vont servir de témoins lorsqu'il y a communication.
Bilan de puissance
Suite à une demande de la part de l'équipe Mère, nous allons effectuer un bilan de puissance de notre carte :
Le composant majeur de notre carte est le microprocesseur, l'AT90USB.
Avec un tension d'entrée de 5V et une fréquence de 16MHz, le microprocesseur consomme : 0,030A x 5V =0,15 W.