SE4Binome2024-9

De projets-se.plil.fr
Révision datée du 3 janvier 2025 à 14:34 par Ahouduss (discussion | contributions)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigation Aller à la recherche

Pico-ordinateur

Lien GIT

https://gitea.plil.fr/ahouduss/pico_v2.git

Cahier des charges

L'objectif est de réaliser un pico-ordinateur. Pour cela, on a besoin d'une carte mère et de cartes filles.

Nous avons conçu trois cartes :

- Un shield qui combiné à l'arduino sert de carte mère temporaire afin d'avancer sur l'ordonnanceur.

- Une carte mère

- Une carte fille, ici la carte réseau.

Shield

Description

Réalisation du shield qui combiné à l'Arduino Uno sert de première carte mère.

- Alimentation : en mini USB B 5V puis abaissement à 3,3V pour les cartes filles à l'aide d'adaptateurs de niveaux logiques.

- Bouclier arduino uno et communication AVR-ISP.

- Mémoires : la mémoire de masse sera gérée par une carte SD et la mémoire vive par la puce mémoire AT45DB161D-SU.

- Connecteurs HE10 : pour connecter les cartes filles.

Hardware

Schématique et routage

shield_sch
shield_rtg

Brasure

brasure_shield

Dû à une erreur présente sur la schématique où l'on a inversé MISO et MISO_1 au niveau du releveur de niveau, quelques modifications se sont avérées nécessaires : ajout de fils, limage des pistes erronées et rajout de vernis.

Software

Test de la carte shield avec le programme usuel de clignotement des leds (rapide à coder en arduino).

leds_shield
leds_shield

Vérification fonctionnement carte SD

Malgré les vérifications de l'état de réalisation des soudures, du routage et des divers niveaux de tensions au multimètre, la carte SD n'est pas détecté par le programme arduino. Pour comprendre le problème, il faudrait passer en revue chaque signal à l'oscilloscope ce qui est chronophage. Il est donc plus judicieux de penser finir l'ordonnanceur sur la carte mère.

Carte mère

Description

Réalisation de la carte mère :

- Alimentation : en USB 5V puis abaissement à 3,3V pour les cartes filles à l'aide d'adaptateurs de niveaux logiques.

- Micro-contrôleur : ATMega16u2 afin d'être compatible avec l'USB. Utilisation de dfu-programmer pour la programmation du microcontrôleur. On ajoutera un programmateur AVR-ISP afin d'intégrer le bootloader.

- Mémoires : la mémoire de masse sera gérée par une carte SD et la mémoire vive par la puce mémoire AT45DB161D-SU.

- Connecteurs HE10 : pour connecter les cartes filles.

Hardware

Schématique et routage

mere_sch
mere_rtg

Brasure

mère_brasé
mère_brasé

Software

Programmation de l'ordonnanceur, voir la section suivante.

Ordonnanceur

Description

L'ordonnanceur permet à la carte mère d'allouer du temps processeur aux différentes cartes filles en suivant la méthode de partage round-robin.

Programmation

Pour commencer, on initialise le minuteur afin qu'il génére une interruption au bout d'un certain nombre de coups d'horloge, en l'occurence la valeur du registre OCR1A. On utilise également un prescaler afin que la durée des tâches soit suffisamment longue face au temps alloué aux interruptions.

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 cf DS p 133
  // décale le bit 1 de CTC1 ie 0x03 pr activer WGM12
  switch(diviseur){
    case   64: TCCR1B |= (1<<CS11 | 11<<CS10); break;
    case  256: TCCR1B |= (1<<CS12); break; //prescaler 256 => TCCR1B |= 0x04 cf DS
  }
  OCR1A=F_CPU/1000*periode/diviseur; 
  //OCR1A=(periode/1000)/(diviseur/F_CPU)
  //OCR1A correspond au nb d'impulsions avant de declencher une interruption
  //OCR1A = periode voulue (en ms) / periode timer
  //periode timer = prescaler /f_CPU
  TCNT1=0;                //compteur du minuteur initialisé
  TIMSK1=(1<<OCIE1A);
  //le bit OCIE1A (Output Compare A Match Interrupt Enable) ds registre TIMSK1 déclenche interruption qd compteur du timer TCNT1 atteint OCR1A.
}

En faisant des tâches simples telle que le clignotement d'une led, on se rend compte que notre microprocesseur ne détecte pas notre quartz comme étant du 16MHz. Au lieu de manipuler les fuses, on décide de modifier les registres de l'horloge dans l'en-tête de notre programme.

//config horloge externe programmateur a 16MHz
void init_clock(void){
  CLKSEL0 = 0b00010101;   // sélection de l'horloge externe
  CLKSEL1 = 0b00001111;   // minimum de 8Mhz
  CLKPR = 0b10000000;     // modification du diviseur d'horloge (CLKPCE=1)
  CLKPR = 0;              // 0 pour pas de diviseur (diviseur de 1)
}

La fonction ci-dessous permet d'initialiser la pile (registres, pointeur de pile).

void init_pile(int N){
  uint16_t tempSP = SP;
  SP = task[N].stackPointer;
  uint16_t adresse=(uint16_t)task[N].taskAddress;
  asm volatile("push %0" : : "r" (adresse & 0x00ff) );
  asm volatile("push %0" : : "r" ((adresse & 0xff00)>>8) );
  SAVE_REGISTERS();
  task[N].stackPointer = SP;
  SP = tempSP;
}

On définit une structure pour les tâches de l'ordonnanceur.

//structure pour les tâches de l'ordonnanceur
typedef struct{
  void (*taskAddress)(void);  // adresse de la tache  
  uint16_t stackPointer;    // pointeur de pile
  bool state;               // etat (1:actif 0:sommeil)
  //uint16_t sleepTime;       // tps restant en sommeil (ms)
  //uint16_t reason;          // raison de suspension
} process;

On liste les tâches à exécuter dans un tabeau, pour l'instant des simples tâches de clignotement de leds.

int currentTask=0;

process task[NB_TASKS] = {
  {led1_blink,0x1C0,1},
  {led2_blink,0x180,1}
 // {led3_blink,0x384,1},
 // {led4_blink,0x320,1},
};

A chaque interruption, on sauvegarde le contexte de la tâche précédente, on appelle la fonction scheduler afin de savoir quelle est la prochaine tâche à exécuter et on récupère son contexte. Les fonctions assembleurs pour le contexte des tâches proviennent de : https://tvantroys.plil.fr/IMA4/system/Multitasking%20on%20an%20AVR.pdf.

// procédure d'interruption
// appelée chaque fois que TCNT1 = OCR1A (compteur du timer = nb d'impulsions voulues)
// ISR nue pour éviter les interférences du compilateur sur la pile d'exécution
ISR(TIMER1_COMPA_vect, ISR_NAKED){
  /* Sauvegarde du contexte de la tâche interrompue */
  SAVE_REGISTERS();
  task[currentTask].stackPointer = SP;
  /* Appel à l'ordonnanceur */
  scheduler();
  /* Récupération du contexte de la tâche ré-activée */
  SP = task[currentTask].stackPointer;
  RESTORE_REGISTERS();
  asm volatile ( "reti" ); //end of isr, restore registers
}

Notre ordonnanceur fait clignoter la led D2 puis appelle le tableau des tâches à exécuter.

void scheduler(void){
   PORTC ^= (1 << PC4); //D2
  currentTask++;
  if (currentTask == NB_TASKS) 
    currentTask = 0; //boucle apres derniere tache
}

SPI et série

On teste la connexion série et spi à l'aide d'un afficheur 7 segments à l'emplacement d'une des cartes filles.

spi
spi

Ajout de la communication série et SPI sur le code de l'ordonnanceur.

Carte réseau

Description

Réalisation de la carte réseau RNDIS :

- Micro-contrôleur : MicroAT90USB afin d'être suffisamment puissant. Utilisation de capacités de découplage : 3 de 100 nF et une de 4,7 µF en regardant d'autres schématiques utilisant le µc AT90USB.

- Leds : pour les futurs tests réseaux.

- AVR ISP

- Connecteur HE10 : pour connecter à la carte mère

- Port USB

Hardware

Schématique et routage

reseau_sch
reseau_rtg

Brasure

réseau_brasé
réseau_brasé

Test leds

Software

Pour programmer notre carte réseau, on va télécharger la LUFA de la libusb https://www.lufa-lib.org/LUFA.php, et copier le répertoire Demos/Device/LowLevel/RNDISEthernet dans un dossier Polytech où l'on va modifier le code pour l'adapter à notre carte.

Dans le fichier RNDISEthernet.c, on doit modifier les fonctions RNDIS_Task et Ethernet_Task.