« SE4Binome2024-7 » : différence entre les versions

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
Ligne 61 : Ligne 61 :


Nous avons un pointeur destiné à pointer vers la fonction à exécuter pour cette tâche. Le pointeur SPointer sert à sauvegarder la position du pointeur de pile pour cette fonction à l'interruption, chaque fonction ayant sa propre pile d'exécution. Cela est vital car lorsque l'on voudra continuer l'exécution de cette fonction par la suite, nous devons savoir l'état dans lequel elle s'était arrêtée.  L'état permet d'intégrer une fonction sleep par la suite, qui rendra la tâche inactive pendant le delai delay contenu dans data.
Nous avons un pointeur destiné à pointer vers la fonction à exécuter pour cette tâche. Le pointeur SPointer sert à sauvegarder la position du pointeur de pile pour cette fonction à l'interruption, chaque fonction ayant sa propre pile d'exécution. Cela est vital car lorsque l'on voudra continuer l'exécution de cette fonction par la suite, nous devons savoir l'état dans lequel elle s'était arrêtée.  L'état permet d'intégrer une fonction sleep par la suite, qui rendra la tâche inactive pendant le delai delay contenu dans data.
Finalement, nous ajoutons cette ligne :
currentTask = (currentTask + 1) % NB_TASKS;
dans l'ordonnanceur, ce qui permet de passer d'une tâche à la suivante à chaque interruption.


==== CLIGNOTEMENT DES LEDs ====
==== CLIGNOTEMENT DES LEDs ====
Ligne 74 : Ligne 80 :


==== FONCTION DELAY ====
==== FONCTION DELAY ====
Pour cette fonction delay, nous changeons l'état de la tâche à endormir en SLEEPING, puis assignons la valeur time donnée en paramètre au delay de la tâche. Nous remettons le compteur à 0 et appelons l'ISR pour être sûr que le delay soit le bon(la fonction delay s'éxecute pendant une éxecution de tâche, on verra juste après pourquoi cela risque de fausser le delay).
void delay(int time)
{
  TaskList[currentTask].state = SLEEPING;
  TaskList[currentTask].data.data.delay = time;
  TCNT1 = 0;
  TIMER1_COMPA_vect();
}
Ensuite, nous allons changer l'ordonnanceur pour qu'il puisse gérer l'état SLEEPING.
<nowiki>*</nowiki>Si la currentTask est en mode SLEEPING:
<nowiki>**</nowiki>Si son delay est positif, nous soustrayons une durée elapsed_time fixe correspondant à une période d'éxecution au delay de cette tâche. C'est ici qu'on voit l'intérêt de reset le timer et d'appeler l'ISR : puisque elapsed_time est fixe et indépendant du timer TCNT1, il ne prend pas en compte le temps auquel s'est éxecuté le delay (le timer n'était pas à un multiple fixe de la période).
<nowiki>**</nowiki>Si son delay est négatif ou nul, nous restaurons l'état de tâche à ACTIVE.
Puis, dans la sélection de tâche à éxecuter ensuite, nous testons si l'état de la tâche est SLEEPING. Si c'est le cas, nous regardons la suivante, jusqu'à tomber sur une tâche en mode ACTIVE, que l'on va alors éxecuter.


==== COMMUNICATION SERIE ====
==== COMMUNICATION SERIE ====

Version du 18 novembre 2024 à 10:24

Code Source et autre programmes

TESTS PRELIMINAIRES

Vérification des connecteurs HE-10

Nous avons testé un 7-segments sur tous les connecteurs HE-10. Vous pouvez le voir dans la sous-section COMMUNICATION SPI de la section ORDONNANCEUR.

Lecture Carte SD

Cette image montre la detection de la carte SD sur le Shield par l'ordinateur. On peut observer son type ou sa taille et d'autres informations.

CarteSD.png

Fonctionnement des LEDs

Avant de nous aventurer dans les méandres de l'ordonnanceur, nous devons vérifier que toutes les LEDs sont bien connectées. Nous n'avons pas de photos du dit test mais nous vous invitons à aller voir la sous-section CLIGNOTEMENT DES LEDs dans la section ORDONNANCEUR. Vous pouvez même observer une différence de fréquence entre les clignotements.

ORDONNANCEUR

SQUELETTE BASIQUE DE L'ORDONNANCEUR

Le but de l'ordonnanceur est de répartir l'éxecution des multiples tâches que doit exécuter l'arduino, et que celles-ci nous semblent simultanées. Pour faire cela, à intervalle régulier (puisque nous fonctionnons en Round Robin),l'Interrupt Service Routine (ISR) va sauvegarder l'état des registres, interrompre la tâche en cours, puis appeler l'ordonnanceur qui va choisir quelle tâche doit maintenant s'exécuter, puis restaurer l'état des registres.

Pour réaliser l'ordonnanceur, nous commençons par initialiser un minuteur qui va définir la fréquence d'interruption (diviseur), le mode de la minuterie (CTC1)) et la période (periode)).

Ensuite, nous définissons le comportement qu'il va adopter à chaque interruption. Cela est géré par la fonction ISR en mode NAKED, ce qui implique que l'on va devoir gérer les sauvegardes

des registres nous mêmes (lors d'une interruption, nous commençons toujours par sauvegarder l'état des registres et les restaurons à la fin, sans quoi nous perdons tout le contexte la précédant).

Nous définissons donc en parallèle deux macros SAVE_REGISTERS() et RESTORE_REGISTERS() que nous appelons respectivement au début et à la fin de chaque interruption.

Puis nous créons notre fonction ordonnanceur, qui est pour l'instant vide, puisque les tâches n'ont pas encore été construites, et c'est ce à quoi nous allons maintenant nous atteler.

STRUCTURE DES TÂCHES

La structure des tâches est la suivante :

typedef struct Task {

   void (*fonction)(void);    // Pointeur vers une fonction prenant aucun paramètre et ne retournant rien

   uint16_t SPointer;         // Pointeur de pile

   int state;                 // État de la tâche

   struct prog_data data;     // Données de la tâche

} Task;

struct prog_data {

   int Type;

   union {

       int delay;  // On peut ajouter d'autres types de données ici si nécessaire

   } data;

};

Nous avons un pointeur destiné à pointer vers la fonction à exécuter pour cette tâche. Le pointeur SPointer sert à sauvegarder la position du pointeur de pile pour cette fonction à l'interruption, chaque fonction ayant sa propre pile d'exécution. Cela est vital car lorsque l'on voudra continuer l'exécution de cette fonction par la suite, nous devons savoir l'état dans lequel elle s'était arrêtée. L'état permet d'intégrer une fonction sleep par la suite, qui rendra la tâche inactive pendant le delai delay contenu dans data.

Finalement, nous ajoutons cette ligne :

currentTask = (currentTask + 1) % NB_TASKS;

dans l'ordonnanceur, ce qui permet de passer d'une tâche à la suivante à chaque interruption.

CLIGNOTEMENT DES LEDs

Nous avons utilisé cette structure pour créer deux tâches de clignotement de LEDs, task_led1 et task_led2, dans ordonnanceur1.c à des fréquences premières entre elles. Vous trouverez ci-dessous une vidéo du résultat. Cependant, il y a un léger problème concernant la manière dont ces tâches sont programmées : nous utilisons la fonction _delay_ms de la bibliothèque delay.h, plutôt que d'endormir la tâche. Cela signifie que la tâche est quand même exécutée et ne fait qu'attendre pendant son temps d'exécution. Plutôt que de faire ça, on va créer une fonction delay, qui va endormir la tâche pendant le temps désiré et qui permettra de libérer ce temps de travail inutile pour plutôt exécuter d'autres tâches à la place.


FONCTION DELAY

Pour cette fonction delay, nous changeons l'état de la tâche à endormir en SLEEPING, puis assignons la valeur time donnée en paramètre au delay de la tâche. Nous remettons le compteur à 0 et appelons l'ISR pour être sûr que le delay soit le bon(la fonction delay s'éxecute pendant une éxecution de tâche, on verra juste après pourquoi cela risque de fausser le delay).

void delay(int time)

{

  TaskList[currentTask].state = SLEEPING;

  TaskList[currentTask].data.data.delay = time;

  TCNT1 = 0;

  TIMER1_COMPA_vect();

}

Ensuite, nous allons changer l'ordonnanceur pour qu'il puisse gérer l'état SLEEPING.

*Si la currentTask est en mode SLEEPING:

**Si son delay est positif, nous soustrayons une durée elapsed_time fixe correspondant à une période d'éxecution au delay de cette tâche. C'est ici qu'on voit l'intérêt de reset le timer et d'appeler l'ISR : puisque elapsed_time est fixe et indépendant du timer TCNT1, il ne prend pas en compte le temps auquel s'est éxecuté le delay (le timer n'était pas à un multiple fixe de la période).

**Si son delay est négatif ou nul, nous restaurons l'état de tâche à ACTIVE.

Puis, dans la sélection de tâche à éxecuter ensuite, nous testons si l'état de la tâche est SLEEPING. Si c'est le cas, nous regardons la suivante, jusqu'à tomber sur une tâche en mode ACTIVE, que l'on va alors éxecuter.

COMMUNICATION SERIE

COMMUNICATION SPI

7SegmentPicture.jpg

Schéma, PCB & KiCAD

Shield

Photos et vidéos description
Carte-mère soudée.jpg Le shield soudée avec juste le BootLoader dans le microP

Carte RNDIS

SCHEMATIQUE de la Carte RNDIS
PCB de la carte RNDIS
Connecteur USB permettant l'ALIM de la carte en solo et le transfert DATA en réseau avec un ordinateur connecté à Internet
Connecteur USB permettant l'ALIM de la carte en solo (sans shield et carte-mère) et le transfert de DATA

en réseau avec un ordinateur connecté à Internet

PINOUT
PIN UTILITY PIN NAME
ChipSelect PB0
CLK/SCK PB1
MOSI PB2
MISO PB3
Pin d'Interruption PB4
LED[1-4] PC[0-3]

NOTES

deux ports USB pour la carte fille : un pour la connexion en ISP/Réseau à l'ordinateur et un pour la programmation. Possibilité de faire une connexion SPI avec un câble RJ45

Ressources & Sources

Lien Git : https://gitea.plil.fr/rboursau/S8-Pico-Binome-7