« SE4Binome2023-4 » : différence entre les versions

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
 
(44 versions intermédiaires par 3 utilisateurs non affichées)
Ligne 4 : Ligne 4 :
== Ordonnanceur ==
== Ordonnanceur ==


Pour la réalisation de notre pico-ordinateur, nous devons réaliser un ordonnanceur qui permettra de gérer nos différents processus.
Pour la réalisation de notre pico-ordinateur, nous devons réaliser un ordonnanceur qui permettra de gérer nos différents processus en simultané. Parmi les différents processus, il y aura par exemple la lecture et l'écriture sur le port série ou bien un affichage sur une matrice de LED.


=== Réalisation du shield ===
=== Réalisation du shield ===
Ligne 42 : Ligne 42 :


=== Programmation ===
=== Programmation ===
Nous pouvons donc commencer à travailler sur l’ordonnanceur :


* Première étape : Création d'un ordonnanceur avec test sur 2 LED (avec delay_ms) : OK
==== Création de l'ordonnanceur ====
* Deuxième étape : Modification du code afin d'avoir un état "ACTIF" ou "SLEEP" sur une tâche : OK
Maintenant que le shield que nous souhaitons utiliser est fonctionnel, nous pouvons passer à la partie programmation de la carte en commençant par gérer les interruptions avec notre minuteur.
* Troisième étape : Ajout de la lecture et de l'écriture sur le port USB : OK


* Quatrième étape : Gestion de la matrice de LED avec la lecture sur port USB : OK ( voir vidéo )
Il faut que toutes les X ms la fonction d'interruption se lance afin de sauvegarder le contexte de la tâche mais aussi pour passer à la tâche suivante. Pour cela, nous avons commencé par créer le minuteur puis nous avons écrit la fonction d'interruption faisant appel à la fonction ordonnanceur :
{| class="wikitable"
|+
!Fonction Minuteur
!ISR
|-
|<syntaxhighlight lang="c">
void init_timer()
{
TCCR1A = 0;  // No output pin connected, no PWM mode enabled
TCCR1B = 1<<CTC1; // No input pin used, clear timer counter on compare match
#if (PRESCALER==8)
TCCR1B |= (1<<CS11);
#endif
#if (PRESCALER==64)
TCCR1B |= (1<<CS11 | 11<<CS10);
#endif
#if (PRESCALER==256)
TCCR1B |= (1<<CS12);
#endif
#if (PRESCALER==1024)
TCCR1B |= (1<<CS12 | 1<<CS10);
#endif
OCR1A = NB_TICK;
TCNT1 = 0;
TIMSK1 = (1<<OCIE1A); // No overflow mode enabled, no input interrupt, output compare interrupt
}
</syntaxhighlight>
|<syntaxhighlight lang="c">
ISR(TIMER1_COMPA_vect,ISR_NAKED)
{
  /* Sauvegarde du contexte de la tâche interrompue */
  SAVE_REGISTER();
  ListeTaches[courant].SPointeur = SP;
  /* Appel à l'ordonnanceur */
  ordonnanceur();
 
  /* Récupération du contexte de la tâche ré-activée */
  SP = ListeTaches[courant].SPointeur;
  RESTORE_REGISTER();
  asm volatile ("reti");
}
</syntaxhighlight>
|}
 
==== Création des différentes tâches ====
Pour créer une tâche, nous avons fait une structure comprenant l'action à réaliser ainsi que le Stack Pointer attribué à cette tâche ( par la suite, on gèrera également l'état d'une tâche ainsi que la durée de l'état SLEEP si c'est le cas ).
{| class="wikitable"
|+
!Structure d'une tache
|-
|<syntaxhighlight lang="c">
struct Tache{
  void (*fonction)(void);
  uint16_t SPointeur;
  int etat;
  int duree;
  uint8_t raison;
};
 
</syntaxhighlight>
|}
Attention, avant toute chose, il faut penser à initialiser le contexte des tâches pour éviter les problèmes liés à la pile. Pour cela, nous avons fait une fonction qui initialise toutes les tâches présentes avant le lancement :
{| class="wikitable"
|+
!Initialisation des taches
|-
|<syntaxhighlight lang="c">
void init_tache(int N)
{
  int saveSP = SP;
  SP = ListeTaches[N].SPointeur;
  uint16_t adresse = (uint16_t)ListeTaches[N].fonction;
  asm volatile ("push %0 \n\t" : : "r" (adresse & 0x00ff));
  asm volatile ("push %0 \n\t" : : "r" ((adresse & 0xff00) >> 8));
  SAVE_REGISTER();
  ListeTaches[N].SPointeur = SP;
  SP = saveSP;
}
</syntaxhighlight>
|}
On retrouvera l'ensemble des tâches dans le fichier [https://archives.plil.fr/tnave/PICO_Thomas_NAVE_Thibault_DUYCK/blob/master/Ordonnanceur/fonction.c fonction.c]
* '''<u>Première étape :</u>''' Création d'un ordonnanceur avec test sur 2 LED (avec delay_ms) : OK
* '''<u>Deuxième étape </u>:''' Modification du code afin d'avoir un état "ACTIF" ou "SLEEP" sur une tâche : OK
Pour ajouter les états ACTIF et SLEEP, nous avons écrit la fonction delay() qui met une tâche en SLEEP pour une durée déterminée ( afin de remplacer _delay_ms() ).
{| class="wikitable"
|+
!Fonction delay
!Ordonnanceur
|-
|<syntaxhighlight lang="c">
void delay(int ms)
{
  cli();
  ListeTaches[courant].etat = ETAT_SLEEP;
  ListeTaches[courant].raison = DELAY;
  ListeTaches[courant].duree = ms;
  TCNT1 = 0;
  sei();
  TIMER1_COMPA_vect();
}


[[Fichier:MatriceLED.mp4|vignette|Matrice de LED|néant]]
</syntaxhighlight>
|<syntaxhighlight lang="c">
void ordonnanceur()
{
  for(int i=0; i<NBR_TACHE; i++)
    {
      if(ListeTaches[i].etat == ETAT_SLEEP && ListeTaches[i].raison == DELAY)
{
  uint16_t time_ms=20;
  if(TCNT1 != 0)
    {
      time_ms = TCNT1*200/OCR1A/10;
      TCNT1 = 0;
    }
  ListeTaches[i].duree = ListeTaches[i].duree-time_ms;
  if(ListeTaches[i].duree <= 0)
    ListeTaches[i].etat = ETAT_ACTIF;
}
      if(ListeTaches[i].etat == ETAT_SLEEP && ListeTaches[i].raison == ECRITURE && sem_ecriture > 0)
ListeTaches[courant].etat = ETAT_ACTIF;
      if(ListeTaches[i].etat == ETAT_SLEEP && ListeTaches[i].raison == LECTURE && sem_lecture > 0)
      ListeTaches[courant].etat = ETAT_ACTIF;
    }


* Cinquième étape : Gestion du 7-segments avec la lecture sur port USB : OK ( voir vidéo )
  do{          //Changement de tache jusqu'a une tache active
[[Fichier:7seg.mp4|vignette|7-segments|néant]]
    courant++;
    if(courant == NBR_TACHE) courant = 0;
  }
  while(ListeTaches[courant].etat == ETAT_SLEEP);
}
</syntaxhighlight>
|}
De plus, nous avons effectué des modifications au sein de l'ordonnanceur pour ne prendre en compte que les tâches ACTIF ( il y a une tâche "zombie" afin de ne pas bloquer le programme ).
* '''<u>Troisième étape :</u>''' Ajout de la lecture et de l'écriture sur le port USB avec utilisation des sémaphores : on retrouvera le code dans le fichier [https://archives.plil.fr/tnave/PICO_Thomas_NAVE_Thibault_DUYCK/blob/master/Ordonnanceur/USART.c USART.c]
La vidéo ci-dessous montre une lecture sur clavier puis une écriture sur minicom du caractère lu précédemment :
[[Fichier:Demonstration USART.mp4|néant|vignette|Demo USART]]


Nous avons complété l'ordonnanceur avec ses différentes tâches. Cependant, pour que les deux tâches utilisant le bus SPI fonctionnent en simultané, il faudrait ajouter une tache supplémentaire contrôlant ce bus SPI.
* '''<u>Quatrième étape :</u>''' Gestion de la matrice de LED avec la lecture sur port USB : on retrouvera le code dans le fichier [https://archives.plil.fr/tnave/PICO_Thomas_NAVE_Thibault_DUYCK/blob/master/Ordonnanceur/SPI_matrice.c SPI_matrice.c]
== Carte FPGA ==
Voici une vidéo montrant une lecture de caractère sur minicom ainsi que l'affichage avec la matrice LED :[[Fichier:MatriceLED.mp4|vignette|Matrice de LED|néant]]


* '''<u>Cinquième étape</u>''' : Gestion du 7-segments avec la lecture sur port USB : on retrouvera le code dans le fichier [https://archives.plil.fr/tnave/PICO_Thomas_NAVE_Thibault_DUYCK/blob/master/Ordonnanceur/SPI_7seg.c SPI_7seg.c]
Voici une vidéo montrant une lecture de caractère sur minicom ainsi que l'affichage avec l'afficheur 7-segements :[[Fichier:7seg.mp4|vignette|7-segments|néant]]


.......
==== Précisions : ====
* Nous avons complété l'ordonnanceur avec ses différentes tâches. Cependant, pour que les deux tâches utilisant le bus SPI fonctionnent en simultané, il faudrait ajouter une tâche supplémentaire contrôlant ce bus SPI.
* Pour ajouter ou retirer une tâche, il faut penser à la mettre en commentaire dans le tableau regroupant les tâches dans ordonnanceur.c mais il faut aussi penser à modifier NBR_TACHE dans le .h.


== Carte électronique numérique ==
== Carte électronique numérique ==
Ligne 84 : Ligne 218 :




Nous avons constaté par la suite que le Chip Select du SPI devait se trouver sur le PB0 ainsi nous avons ajouter un fil d'étain entre PB0 et PB4 pour régler ce problème
Nous avons constaté par la suite que le Chip Select du SPI devait se trouver sur le PB0 ainsi nous avons ajouté un fil d'étain entre PB0 et PB4 pour régler ce problème.


=== Partie 2 : Programmation ===
=== Partie 2 : Programmation ===
Maintenant que la carte est utilisable, il nous faut passer à la programmation de celle-ci
Maintenant que la carte est utilisable, il nous faut passer à la programmation de celle-ci.
[[Fichier:VideoRNDIS LED.mp4|vignette|0x0px|LED RNDIS]]
 
==== Test Basique ====
 
Nous avons commencer par un code simple allumant la LED de notre carte pour vérifier que l'envoie du code s'effectue correctement sur la carte et que les soudures sont bonnes.[[Fichier:VideoRNDIS LED.mp4|vignette|0x0px|LED RNDIS]]
<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
#include <avr/io.h>
#include <avr/io.h>
Ligne 114 : Ligne 251 :
}
}
</syntaxhighlight>
</syntaxhighlight>
==== Test LUFA RNDIS ====
Nous avons utilisé la démonstration LUFA RNDIS pour que notre carte soit reconnue comme carte RNDIS.
Pour cela, il a fallu réduire la taille de la pile pour adapter la démonstration à notre Atmega16u2. Nous avons fait le choix de réduire la pile en laissant l'Ethernet et ICMP.
Une fois cela effectué, nous pouvons constater que notre carte est bien reconnue comme carte RNDIS et elle possède sa propre interface "usb0".
[[Fichier:Lsusb RNDIS.png|néant|vignette|Test lsusb]]
Par la suite, il faut affecter une adresse IP à notre carte : <syntaxhighlight lang="shell">
ip address add 10.0.0.100/24 dev usb0
</syntaxhighlight>Et "allumer" l'interface usb0 :<syntaxhighlight lang="shell">
ip link set usb0 up
</syntaxhighlight>On obtient avec ip a :
[[Fichier:IPA.png|néant|vignette|Vérification ip a]]
Nous pouvons, suite à cela, ping le réseau de notre carte RNDIS  (10.0.0.2) :
[[Fichier:PING ping.png|néant|vignette|ping 10.0.0.2]]
Dans un premier temps, nous avons fait en sorte que lorsque notre carte reçoit n'importe quel paquet ( paquet d'un ping par exemple ), la LED s'allume ou s'éteint.
[[Fichier:PingLED.mp4|néant|vignette|PingLED]]
=== Création du protocole ===
Il faut ensuite utiliser une application développée sur le PC  pour recevoir ou envoyer des paquets Ethernet sur notre carte : le code de l'application se trouve sur [https://archives.plil.fr/tnave/PICO_Thomas_NAVE_Thibault_DUYCK/blob/master/RNDIS/Programme/Appli/ether.c ether.c]
* Comme premier test, nous avons fait en sorte que l'application ne lise que les paquets sur notre interface usb0 lors d'un ping sur celle ci --> OK (voir vidéo ci-dessous).
[[Fichier:LecturePaquets.mp4|néant|vignette|Lecture des paquets sur l'application]]
* La seconde étape consiste à modifier les fichiers de la LUFA pour créer notre propre protocole. On a choisi pour ce protocole 0x1111.
<syntaxhighlight lang="c">
switch (SwapEndian_16(FrameINHeader->EtherType))
  {
  case ETHERTYPE_ARP:
    RetSize = ARP_ProcessARPPacket(&FrameIN.FrameData[sizeof(Ethernet_Frame_Header_t)],
  &FrameOUT.FrameData[sizeof(Ethernet_Frame_Header_t)]);
    break;
  case ETHERTYPE_IPV4:
    RetSize = IP_ProcessIPPacket(&FrameIN.FrameData[sizeof(Ethernet_Frame_Header_t)],
  &FrameOUT.FrameData[sizeof(Ethernet_Frame_Header_t)]);
    break;
  case ETHERTYPE_MONPROTO:  
    RetSize = MonProto_ProcessPacket(&FrameIN.FrameData[sizeof(Ethernet_Frame_Header_t)],
      &FrameOUT.FrameData[sizeof(Ethernet_Frame_Header_t)]);
    break;
  }
</syntaxhighlight>Une fois créé et ajouté dans le choix des protocoles dans le code du traitement des paquets Ethernet, le protocole est reconnu par notre carte lors d'un envoi de paquet.
Voici la structure de notre protocole, nous nous sommes inspirés de la structure du protocole ARP :<syntaxhighlight lang="c">
typedef struct
{
  uint8_t      Operation;
  MAC_Address_t MAddr_SRC;
  IP_Address_t  PAddr_SRC;
  MAC_Address_t MAddr_DESC;
  IP_Address_t  PAddr_DESC;
  uint8_t      Longueur;
  uint8_t      Data[MAX_DATA];
} Proto_Header_t;
</syntaxhighlight>
* La troisième étape consiste à renvoyer un paquet lorsque la carte en reçoit un --> OK (voir vidéo ci-dessous).
Il faut que, avant d'envoyer le paquet, la carte modifie ce paquet notamment en inversant les adresses sources et destinations. L'intégralité du code de notre protocole se trouve sur [https://archives.plil.fr/tnave/PICO_Thomas_NAVE_Thibault_DUYCK/blob/master/RNDIS/Programme/RNDISEthernet/Lib/Mon_Proto.c Mon_Proto.c]
[[Fichier:Renvoie d'un "écho" du paquet.mp4|néant|vignette|Renvoi d'un "écho" du paquet]]
[[Fichier:Image des paquets.jpg|néant|vignette|Image des paquets]]Nous avons donc bien un écho avec inversion des adresses lorsqu'on utilise notre protocole.
==== Paquet ====
===== Réception =====
Pour créer un fichier sur le PC, nous devons :
* Envoyer avec l'application, un paquet contenant "l'ordre" d'envoyer un fichier (0x03), ce paquet peut aussi contenir une partie de texte à écrire --> Il aurait fallu que cette opération s'effectue grâce au bus SPI et à la carte mère.
* Une fois le paquet reçu, la carte traite ce paquet en ayant la possibilité de rajouter une autre partie de texte à écrire dans le fichier.
* Le paquet est ensuite renvoyé sur l'application et celle-ci ouvre le fichier et écrit dedans à partir des informations du paquet.
Exemple :
Nous envoyons donc notre ordre avec en plus un texte : "Test Envoi :" --> Avec l'application pas avec la carte mère.
[[Fichier:Envoi.png|néant|vignette|Paquet envoyé par l'application]]
La carte reçoit ce paquet, le modifie en échangeant notamment les adresses de destinations et de sources, et ajoute une partie de texte : " Ajout par la carte".
Elle envoie donc le paquet modifié à l'application PC qui crée le fichier (Reception.txt) et écrit dedans à partir de la DATA du paquet.
[[Fichier:Recu.png|néant|vignette|Paquet reçu par l'application]]
Nous avons donc un fichier qui contient le texte : "Test Envoi : Ajout par la carte".
[[Fichier:Recept.png|néant|vignette|Contenu du fichier]]
===== Lecture =====
Pour lire un fichier sur le PC, nous devons:
* Envoyer avec l'application, un paquet contenant "l'ordre" de lire un fichier (0x02) --> Il aurait fallu que cette opération s'effectue grâce au bus SPI et à la carte mère.
* Une fois le paquet reçu, la carte traite le paquet en ayant la possibilité d'ajouter des caractères pour confirmer la lecture. Elle renvoie ensuite le paquet à l'application.
* L'application lit dans le fichier (Lecture.txt) en ayant le texte ajouté par la carte et affiche le paquet modifié --> à envoyer à la carte mère.
== Bilan ==
Nous avons réussi à accomplir :
* Ordonnanceur avec les états SLEEP et ACTIF
* Les tâches concernant le port série
* Les tâches concernant le bus SPI ( Il aurait aussi fallu créer une tache supplémentaire pour "partager" le bus SPI )
* La conception de notre carte RNDIS
* Un début de protocole réseau permettant d'effectuer des échanges entre la carte et l'application
Malheureusement, notre carte ne peut pas être connectée à la carte mère car il n'y a pas les différentes primitives.
Avec plus de temps, nous aurions peut-être pu terminer les fonctions de lecture et de listage des fichiers mais aussi les primitives pour intégrer notre carte fille à la carte mère.

Version actuelle datée du 2 février 2024 à 12:21

GIT

Adresse du GIT : https://archives.plil.fr/tnave/PICO_Thomas_NAVE_Thibault_DUYCK.git

Ordonnanceur

Pour la réalisation de notre pico-ordinateur, nous devons réaliser un ordonnanceur qui permettra de gérer nos différents processus en simultané. Parmi les différents processus, il y aura par exemple la lecture et l'écriture sur le port série ou bien un affichage sur une matrice de LED.

Réalisation du shield

La première étape consiste à réaliser le shield et le connecteur pour la matrice de LED qui nous permettra d'effectuer tous les tests liés au programme de l'ordonnanceur.

Première partie du shield
Shield terminé
Adaptateur LED
Câbles


Nous avons testé les LED de notre ordonnanceur avec un code basique pour vérifier si les soudures étaient bonnes et si on arrivait à utiliser les Chip Select de la carte :

LED Shield
#include <avr/io.h>

void init_led(){
  DDRD=0x92;
  DDRC=0x09;
}

int main(){
  init_led();
  while(1)
    {
	PORTD=0x92;
	PORTC=0x09;
    }
  return 0;
}

Programmation

Création de l'ordonnanceur

Maintenant que le shield que nous souhaitons utiliser est fonctionnel, nous pouvons passer à la partie programmation de la carte en commençant par gérer les interruptions avec notre minuteur.

Il faut que toutes les X ms la fonction d'interruption se lance afin de sauvegarder le contexte de la tâche mais aussi pour passer à la tâche suivante. Pour cela, nous avons commencé par créer le minuteur puis nous avons écrit la fonction d'interruption faisant appel à la fonction ordonnanceur :

Fonction Minuteur ISR
void init_timer()
{
TCCR1A = 0;  // No output pin connected, no PWM mode enabled
TCCR1B = 1<<CTC1; // No input pin used, clear timer counter on compare match
#if (PRESCALER==8)
 TCCR1B |= (1<<CS11);
#endif
#if (PRESCALER==64)
 TCCR1B |= (1<<CS11 | 11<<CS10);
#endif
#if (PRESCALER==256)
 TCCR1B |= (1<<CS12);
#endif
#if (PRESCALER==1024)
 TCCR1B |= (1<<CS12 | 1<<CS10);
#endif
OCR1A = NB_TICK;
TCNT1 = 0;
TIMSK1 = (1<<OCIE1A); // No overflow mode enabled, no input interrupt, output compare interrupt
}
ISR(TIMER1_COMPA_vect,ISR_NAKED)
{
  /* Sauvegarde du contexte de la tâche interrompue */
  SAVE_REGISTER();
  ListeTaches[courant].SPointeur = SP;
  /* Appel à l'ordonnanceur */
  ordonnanceur();

  /* Récupération du contexte de la tâche ré-activée */
  SP = ListeTaches[courant].SPointeur;
  RESTORE_REGISTER();
  asm volatile ("reti");
}

Création des différentes tâches

Pour créer une tâche, nous avons fait une structure comprenant l'action à réaliser ainsi que le Stack Pointer attribué à cette tâche ( par la suite, on gèrera également l'état d'une tâche ainsi que la durée de l'état SLEEP si c'est le cas ).

Structure d'une tache
struct Tache{
  void (*fonction)(void);
  uint16_t SPointeur;
  int etat;
  int duree;
  uint8_t raison;
};

Attention, avant toute chose, il faut penser à initialiser le contexte des tâches pour éviter les problèmes liés à la pile. Pour cela, nous avons fait une fonction qui initialise toutes les tâches présentes avant le lancement :

Initialisation des taches
void init_tache(int N)
{
  int saveSP = SP;
  SP = ListeTaches[N].SPointeur;
  uint16_t adresse = (uint16_t)ListeTaches[N].fonction;
  asm volatile ("push %0 \n\t" : : "r" (adresse & 0x00ff));
  asm volatile ("push %0 \n\t" : : "r" ((adresse & 0xff00) >> 8));
  SAVE_REGISTER();
  ListeTaches[N].SPointeur = SP;
  SP = saveSP;
}

On retrouvera l'ensemble des tâches dans le fichier fonction.c

  • Première étape : Création d'un ordonnanceur avec test sur 2 LED (avec delay_ms) : OK
  • Deuxième étape : Modification du code afin d'avoir un état "ACTIF" ou "SLEEP" sur une tâche : OK

Pour ajouter les états ACTIF et SLEEP, nous avons écrit la fonction delay() qui met une tâche en SLEEP pour une durée déterminée ( afin de remplacer _delay_ms() ).

Fonction delay Ordonnanceur
void delay(int ms)
{
  cli();
  ListeTaches[courant].etat = ETAT_SLEEP;
  ListeTaches[courant].raison = DELAY;
  ListeTaches[courant].duree = ms;
  TCNT1 = 0;
  sei();
  TIMER1_COMPA_vect();
}
void ordonnanceur()
{
  for(int i=0; i<NBR_TACHE; i++)
    {
      if(ListeTaches[i].etat == ETAT_SLEEP && ListeTaches[i].raison == DELAY)
	{
	  uint16_t time_ms=20;
	  if(TCNT1 != 0)
	    {
	      time_ms = TCNT1*200/OCR1A/10;
	      TCNT1 = 0;
	    }
	  ListeTaches[i].duree = ListeTaches[i].duree-time_ms;
	  if(ListeTaches[i].duree <= 0)
	    ListeTaches[i].etat = ETAT_ACTIF;
	}
      if(ListeTaches[i].etat == ETAT_SLEEP && ListeTaches[i].raison == ECRITURE && sem_ecriture > 0)
	ListeTaches[courant].etat = ETAT_ACTIF;
      if(ListeTaches[i].etat == ETAT_SLEEP && ListeTaches[i].raison == LECTURE && sem_lecture > 0)
      ListeTaches[courant].etat = ETAT_ACTIF;
    }

  do{           //Changement de tache jusqu'a une tache active
    courant++;
    if(courant == NBR_TACHE) courant = 0;
  }
  while(ListeTaches[courant].etat == ETAT_SLEEP);
}

De plus, nous avons effectué des modifications au sein de l'ordonnanceur pour ne prendre en compte que les tâches ACTIF ( il y a une tâche "zombie" afin de ne pas bloquer le programme ).

  • Troisième étape : Ajout de la lecture et de l'écriture sur le port USB avec utilisation des sémaphores : on retrouvera le code dans le fichier USART.c

La vidéo ci-dessous montre une lecture sur clavier puis une écriture sur minicom du caractère lu précédemment :

  • Quatrième étape : Gestion de la matrice de LED avec la lecture sur port USB : on retrouvera le code dans le fichier SPI_matrice.c

Voici une vidéo montrant une lecture de caractère sur minicom ainsi que l'affichage avec la matrice LED :

  • Cinquième étape : Gestion du 7-segments avec la lecture sur port USB : on retrouvera le code dans le fichier SPI_7seg.c

Voici une vidéo montrant une lecture de caractère sur minicom ainsi que l'affichage avec l'afficheur 7-segements :

Précisions :

  • Nous avons complété l'ordonnanceur avec ses différentes tâches. Cependant, pour que les deux tâches utilisant le bus SPI fonctionnent en simultané, il faudrait ajouter une tâche supplémentaire contrôlant ce bus SPI.
  • Pour ajouter ou retirer une tâche, il faut penser à la mettre en commentaire dans le tableau regroupant les tâches dans ordonnanceur.c mais il faut aussi penser à modifier NBR_TACHE dans le .h.

Carte électronique numérique

Carte choisie : Carte Réseau RNDIS

Commentaire :

- Problème 1 : Mauvais positionnement de Chip Select dans la conception --> OK

Partie 1 : Réalisation de la carte

Il faut donc effectuer la conception de la carte :

On commence par le schematic ci-joint

Schematic carte RNDIS


Après avoir terminé le schematic, on peut passer au routage de la carte

Routage

Une fois la carte recue, il faut passer à la soudure :

Carte Vide
Carte Vide
Carte Soudée
Carte Soudée


Nous avons constaté par la suite que le Chip Select du SPI devait se trouver sur le PB0 ainsi nous avons ajouté un fil d'étain entre PB0 et PB4 pour régler ce problème.

Partie 2 : Programmation

Maintenant que la carte est utilisable, il nous faut passer à la programmation de celle-ci.

Test Basique

Nous avons commencer par un code simple allumant la LED de notre carte pour vérifier que l'envoie du code s'effectue correctement sur la carte et que les soudures sont bonnes.

#include <avr/io.h>
#include <util/delay.h>

void init_led()
{
  DDRD=0x01;
}

int main(){
  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)
  init_led();
  while(1)
    {
      PORTD=0x01;
      _delay_ms(500);
      PORTD=0x00;
      _delay_ms(500);
    }
  return 0;
}

Test LUFA RNDIS

Nous avons utilisé la démonstration LUFA RNDIS pour que notre carte soit reconnue comme carte RNDIS.

Pour cela, il a fallu réduire la taille de la pile pour adapter la démonstration à notre Atmega16u2. Nous avons fait le choix de réduire la pile en laissant l'Ethernet et ICMP.

Une fois cela effectué, nous pouvons constater que notre carte est bien reconnue comme carte RNDIS et elle possède sa propre interface "usb0".

Test lsusb

Par la suite, il faut affecter une adresse IP à notre carte :

ip address add 10.0.0.100/24 dev usb0

Et "allumer" l'interface usb0 :

ip link set usb0 up

On obtient avec ip a :

Vérification ip a


Nous pouvons, suite à cela, ping le réseau de notre carte RNDIS (10.0.0.2) :

ping 10.0.0.2


Dans un premier temps, nous avons fait en sorte que lorsque notre carte reçoit n'importe quel paquet ( paquet d'un ping par exemple ), la LED s'allume ou s'éteint.

Création du protocole

Il faut ensuite utiliser une application développée sur le PC pour recevoir ou envoyer des paquets Ethernet sur notre carte : le code de l'application se trouve sur ether.c

  • Comme premier test, nous avons fait en sorte que l'application ne lise que les paquets sur notre interface usb0 lors d'un ping sur celle ci --> OK (voir vidéo ci-dessous).
Lecture des paquets sur l'application
  • La seconde étape consiste à modifier les fichiers de la LUFA pour créer notre propre protocole. On a choisi pour ce protocole 0x1111.
	switch (SwapEndian_16(FrameINHeader->EtherType))
	  {
	  case ETHERTYPE_ARP:
	    RetSize = ARP_ProcessARPPacket(&FrameIN.FrameData[sizeof(Ethernet_Frame_Header_t)],
					   &FrameOUT.FrameData[sizeof(Ethernet_Frame_Header_t)]);
	    break;
	  case ETHERTYPE_IPV4:
	    RetSize = IP_ProcessIPPacket(&FrameIN.FrameData[sizeof(Ethernet_Frame_Header_t)],
					   &FrameOUT.FrameData[sizeof(Ethernet_Frame_Header_t)]);
	    break;
	  case ETHERTYPE_MONPROTO:	  
	    RetSize = MonProto_ProcessPacket(&FrameIN.FrameData[sizeof(Ethernet_Frame_Header_t)],
				       &FrameOUT.FrameData[sizeof(Ethernet_Frame_Header_t)]);
	    break;
	  }

Une fois créé et ajouté dans le choix des protocoles dans le code du traitement des paquets Ethernet, le protocole est reconnu par notre carte lors d'un envoi de paquet. Voici la structure de notre protocole, nous nous sommes inspirés de la structure du protocole ARP :

typedef struct
{
  uint8_t       Operation;
  MAC_Address_t MAddr_SRC;
  IP_Address_t  PAddr_SRC;
  MAC_Address_t MAddr_DESC;
  IP_Address_t  PAddr_DESC;
  uint8_t       Longueur;
  uint8_t       Data[MAX_DATA];
} Proto_Header_t;
  • La troisième étape consiste à renvoyer un paquet lorsque la carte en reçoit un --> OK (voir vidéo ci-dessous).

Il faut que, avant d'envoyer le paquet, la carte modifie ce paquet notamment en inversant les adresses sources et destinations. L'intégralité du code de notre protocole se trouve sur Mon_Proto.c

Image des paquets

Nous avons donc bien un écho avec inversion des adresses lorsqu'on utilise notre protocole.

Paquet

Réception

Pour créer un fichier sur le PC, nous devons :

  • Envoyer avec l'application, un paquet contenant "l'ordre" d'envoyer un fichier (0x03), ce paquet peut aussi contenir une partie de texte à écrire --> Il aurait fallu que cette opération s'effectue grâce au bus SPI et à la carte mère.
  • Une fois le paquet reçu, la carte traite ce paquet en ayant la possibilité de rajouter une autre partie de texte à écrire dans le fichier.
  • Le paquet est ensuite renvoyé sur l'application et celle-ci ouvre le fichier et écrit dedans à partir des informations du paquet.


Exemple :

Nous envoyons donc notre ordre avec en plus un texte : "Test Envoi :" --> Avec l'application pas avec la carte mère.

Paquet envoyé par l'application


La carte reçoit ce paquet, le modifie en échangeant notamment les adresses de destinations et de sources, et ajoute une partie de texte : " Ajout par la carte".

Elle envoie donc le paquet modifié à l'application PC qui crée le fichier (Reception.txt) et écrit dedans à partir de la DATA du paquet.

Paquet reçu par l'application


Nous avons donc un fichier qui contient le texte : "Test Envoi : Ajout par la carte".

Contenu du fichier
Lecture

Pour lire un fichier sur le PC, nous devons:

  • Envoyer avec l'application, un paquet contenant "l'ordre" de lire un fichier (0x02) --> Il aurait fallu que cette opération s'effectue grâce au bus SPI et à la carte mère.
  • Une fois le paquet reçu, la carte traite le paquet en ayant la possibilité d'ajouter des caractères pour confirmer la lecture. Elle renvoie ensuite le paquet à l'application.
  • L'application lit dans le fichier (Lecture.txt) en ayant le texte ajouté par la carte et affiche le paquet modifié --> à envoyer à la carte mère.

Bilan

Nous avons réussi à accomplir :

  • Ordonnanceur avec les états SLEEP et ACTIF
  • Les tâches concernant le port série
  • Les tâches concernant le bus SPI ( Il aurait aussi fallu créer une tache supplémentaire pour "partager" le bus SPI )
  • La conception de notre carte RNDIS
  • Un début de protocole réseau permettant d'effectuer des échanges entre la carte et l'application


Malheureusement, notre carte ne peut pas être connectée à la carte mère car il n'y a pas les différentes primitives.

Avec plus de temps, nous aurions peut-être pu terminer les fonctions de lecture et de listage des fichiers mais aussi les primitives pour intégrer notre carte fille à la carte mère.