« SE4Binome2024-4 » : différence entre les versions
Ligne 350 : | Ligne 350 : | ||
tab_process[NB_PROCESS-1].sleep=1; | tab_process[NB_PROCESS-1].sleep=1; | ||
} | } | ||
</syntaxhighlight>Nous avons aussi modifier le scheduler | </syntaxhighlight>| | ||
Nous avons aussi modifier le scheduler :[[Fichier:Video des LEDs ordonnancé.mp4|vignette | |||
<syntaxhighlight lang="c" line="1" start="1"> | |||
void scheduler(void) | void scheduler(void) | ||
{ | { | ||
Ligne 364 : | Ligne 367 : | ||
} | } | ||
} | } | ||
</syntaxhighlight>Video des LEDs | </syntaxhighlight>Video des LEDs ordonnancées|centré]] | ||
== Carte Réseau == | == Carte Réseau == |
Version du 27 novembre 2024 à 09:30
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 cart 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 que le lecteur était fonctionnel.
Test Port He-10 :
Pour tester les ports He-10, nous avons connecté un afficheur 7-segment pour vérifier qu'ils fonctionnent.
Malgré des difficultés à connecter les afficheurs 7-segments à cause de défaut de conceptions, les 5 ports HE-10 sont fonctionnels.
Ordonnanceur :
Ordonnanceur (No Naked):
Le principe de l'ordonnanceur est de simuler une execution en parallele 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 dû créer 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 rajouter dans la structure tab_process, une durée d'endormissement. Ainsi qu'un programme qui réduit le temps de sommeil à chaque interruption.
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++;
}
}
if (nb_sleep == NB_PROCESS-2)
{
tab_process[NB_PROCESS-1].sleep = 0;
}
else
tab_process[NB_PROCESS-1].sleep=1;
}
| Nous avons aussi modifier le scheduler :
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.