« SE4Binome2024-4 » : différence entre les versions
Aucun résumé des modifications |
Aucun résumé des modifications |
||
Ligne 1 : | Ligne 1 : | ||
Lien du GIT : https://gitea.plil.fr/jwacquet/Pico_Binome4_Justin_Ibrahim.git | Lien du GIT : https://gitea.plil.fr/jwacquet/Pico_Binome4_Justin_Ibrahim.git | ||
Ligne 5 : | Ligne 4 : | ||
Projet Kicad : [[Fichier:Projet Kicad Shield.zip|vignette|Projet Kicad Shield Compressé]] | Projet Kicad : [[Fichier:Projet Kicad Shield.zip|vignette|Projet Kicad Shield Compressé]] | ||
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 | 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é :==== | ==== Carte Shield Soudé :==== | ||
[[Fichier:Carte shield soudée face .jpg|vignette|Carte shield soudée face |centré]] | [[Fichier:Carte shield soudée face .jpg|vignette|Carte shield soudée face |centré]] | ||
Ligne 399 : | Ligne 398 : | ||
[[Fichier:Pico brase.jpg|vignette|PCB plus en manque de composants.|centré|337x337px]] | [[Fichier:Pico brase.jpg|vignette|PCB plus en manque de composants.|centré|337x337px]] | ||
=== Programmation de la carte === | |||
Cette section est dédiée à la programmation de la carte. Tout d'abord, en utilisant les codes RNDIS de la LUFA, la carte est maintenant détectée comme carte réseau. | |||
[[Fichier:Ip a.jpg|vignette|ip a où l'on voit la carte réseau (son addresse IP est 21.21.21.21)]] |
Version du 6 janvier 2025 à 14:23
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 r31 \n\t" \
);
#define portRESTORE_REGISTER() \
asm volatile ( \
"pop r31 \n\t" \
"pop r30 \n\t" \
"pop r29 \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 endormie, on l'ignore et on regarde l'état de la prochaine jusqu'à avoir une tache éveillée.
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.
Brasure
Le PCB de la Pipelette v1 est arrivée, on peut donc aller braser sans plus tarder.
La brasure se passe bien sauf au moment où l'on a remarqué qu'on a confondu l'empreinte du jumper avec celle d'un connecteur de batterie... On a rattrapé l'erreur en brasant un interupteur à l'arrière de la carte.
Mise à part ce soucis, tout est en ordre et la carte est brasée.
Programmation de la carte
Cette section est dédiée à la programmation de la carte. Tout d'abord, en utilisant les codes RNDIS de la LUFA, la carte est maintenant détectée comme carte réseau.