Pico-ordinateur
Objectif
Pour les années académiques 2024/2025 nous vous demandons de construire un pico-ordinateur comprenant les éléments suivants :
- un processeur de type microcontrôleur ;
- un clavier ;
- un dispositif d'affichage ;
- un système d'exploitation stocké dans la mémoire flash du microcontrôleur ;
- une mémoire de masse allant au delà de la mémoire flash sus-citée ;
- un dispositif de communication avec l'extérieur.
Pour faire en sorte que tous les éléments puissent communiquer entre eux nous vous demandons d'utiliser un bus série.
Compétences évaluées
Les compétences suivantes doivent être acquises durant ce module.
- Savoir comprendre, ou à défaut être actif en séances pour poser les questions pertinentes aux encadrants.
- Savoir produire un travail régulier et documenté durant la totalité de la période.
- Savoir concevoir et réaliser des cartes électroniques fonctionnelles.
- Savoir concevoir et programmer un circuit FPGA en VHDL.
- Savoir structurer et écrire des pilotes pour cartes électroniques.
- Savoir concevoir et implanter des primitives systèmes et un micro-système d'exploitation.
Organisation du travail
Nous vous demandons de vous coordonner pour réaliser le pico-ordinateur : un seul exemplaire fonctionnel est attendu par groupes de 4 binômes.
Vous allez donc devoir vous organiser pour que chaque binôme réalise une partie de l'ensemble. Cela dit des tâches identiques sont à réaliser par tous les binômes, ces tâches vous permettront de vous exercer mais aussi de tester vos réalisations spécifiques.
Matériel fourni
Pour que chaque binôme puisse avoir un minimum d'autonomie, un matériel de base est fourni :
- un Arduino Uno pour prototyper le coeur d'une carte mère ;
- un bouclier Arduino pour disposer d'une mémoire de masse (carte SD) et de connecteurs vers les cartes filles, voir le schéma et le PCB ci-contre ;
- d'une matrice de LED et d'un afficheur 7-segments tous les deux gérables par SPI pour pouvoir écrire les briques de votre système d'exploitation.
Le projet KiCAD pour le bouclier Arduino est disponible : Média:2023_Bouclier_Pico.zip
Les projets KiCAD pour connecter les périphériques via un connecteur sont disponibles : Média:2023_HE10-periph.zip
Vision de la carte partiellement montée. L'accès à la carte SD est fonctionnel.
Pour la partie programmation concernant la carte SD vous pouvez vous appuyer sur le projet ci-contre utilisant avr-gcc
comme compilateur : Fichier:TestSD avrgcc.zip.
Une seconde version du bouclier a été conçue pour corriger deux erreurs : l'empreinte du lecteur de carte SD ne correspondait pas au composant commandé et les connecteurs HE10/IDC n'avaient pas, non plus, la bonne empreinte ce qui avait conduit à positionner les LED trop près de ces connecteurs. Le nouveau projet KiCAD : Média:2023_Bouclier_Pico_v2.zip.
Réalisations par binôme
Chaque binôme réalise :
- un système d'exploitation basé sur un ordonnanceur ;
- une carte fille FPGA programmée par VHDL, le binôme écrit aussi le code pour que la carte puisse être gérée par le système d'exploitation ;
- une carte mère ou une carte fille à base de microcontrôleur, le binôme écrit aussi le code pour que la carte puisse être gérée par le système d'exploitation, le binôme peut réaliser plusieurs cartes pour maximiser sa note.
Réalisation par groupe de binômes
Par groupe de 4 binômes vous devez réaliser un pico-ordinateur complet :
- une carte mère ;
- une carte fille FPGA/VHDL fonctionnelle et supportée par votre carte mère ;
- au moins 3 cartes filles fonctionnelles et supportées par votre carte mère (clavier, écran, réseau) ;
- vous pouvez ajouter une cinquième carte fille optionnelle de type carte son.
Cahier des charges
Ordonnanceur
Vous n'avez pas besoin d'avoir conçu et réalisé des cartes électroniques pour commencer à écrire le système d'exploitation du pico-ordinateur et en particulier l'ordonnanceur sous-jacent.
Le principe est d'utiliser un minuteur du microcontrôleur pour lancer périodiquement une fonction d'interruption que vous allez transformer en ordonnanceur.
Dans un premier temps vous testerez votre ordonnanceur en créant deux tâches statiques pour faire clignoter, avec des périodes différentes, deux LED du bouclier fourni.
Par la suite vous allez complexifier votre ordonnanceur en ajoutant :
- une tâche de lecture de caractère par le port série (stocké en mémoire partagé) ;
- une tâche d'écriture sur le port série (du caractère lu sur le port série) ;
- une tâche d'écriture sur la matrice (du caractère lu sur le port série) ;
- une tâche d'écriture sur l'afficheur 7 segments (du caractère lu sur le port série).
Les deux dernières tâches sont plus compliquées à réaliser dans la mesure où elles utilisent une ressource commune.
Jusqu'en 2024/2025 les tâches de l'ordonnanceur étaient définies dans un tableau statique de tâches, à partir de 2025/2026 ce tableau va devenir dynamique avec la gestion de l'arrêt des tâches et la possibilité d'en lancer de nouvelles. Pour l'arrêt il faut implanter une fonction exit
qui retire proprement la tâche courante de la liste des tâches à ordonnancer. Pour le lancement, la fonction exec
doit installer la fonction passée en paramètre comme une nouvelle tâche à ordonnancer.
Cartes filles FPGA
Les cartes filles FPGA doivent être programmées en VHDL. Tous les binômes doivent implanter en VHDL :
- un contrôleur de bus série SPI ;
- un décodeur des commandes reçues par SPI (commandes sur un octet avec éventuellement des paramètres à suivre) ;
- un contrôleur d'écran VGA affichant le contenu graphique de la RAM de la carte FPGA.
A minima il faut une commande pour insérer un pixel d'une couleur donnée dans la RAM d'affichage.
Vous pouvez aussi ajouter des fonctionnalités supplémentaires à votre carte FPGA :
- mémoire de masse par accès direct à une carte mémoire SD ;
- carte réseau Ethernet en utilisant un module PHY Ethernet.
Pour les fonctionnalités supplémentaires vous devez prévoir des commandes supplémentaire au niveau du décodeur.
Cartes électroniques
Vous devez réaliser deux type de cartes électroniques à base de microcontrôleur : une carte mère et des cartes filles. La carte FPGA/VHDL décrite dans la section précédente est considérée comme une carte fille.
Les cartes filles sont connectées via un bus série à la carte mère. C'est aussi la carte mère qui alimente les cartes filles. Enfin la carte mère dispose d'une mémoire permanente pour stocker des fichiers.
Les cartes filles peuvent être de différents types :
- carte fille clavier ;
- carte fille d'affichage ;
- carte fille série (se comporte comme un clavier et comme un dispositif d'affichage) ;
- carte fille réseau ;
- carte fille mémoire de masse (alternative à la mémoire de masse de la carte mère) ;
- carte fille son.
Pour un type donné vous avez le choix entre plusieurs technologies pour réaliser votre carte. Par exemple une carte fille clavier peut être implantée par une matrice de touches ou en utilisant un clavier USB.
Programmation système
Le système d'exploitation du pico-ordinateur doit avoir les fonctionnalités d'un DOS (Disk Operating System), à savoir :
- lecture des commandes tapées par l'utilisateur ;
- téléchargement d'exécutables ou de sources via le dispositif de communication extérieur ;
- sauvegarde et chargement d'exécutables ou de sources via la mémoire de masse ;
- permettre l'affichage de chaînes de caractères ;
- comprend un interpréteur micro-python pour faire tourner les sources ;
- les différentes fonctionnalités sont réalisées sous forme de tâches gérées par un ordonnanceur.
Commandes de base
Ces commandes sont les commandes les plus simples à implanter :
Commande | Description |
---|---|
VERSION | Affiche la version du système d'exploitation |
DEVICES | Affiche la liste des cartes filles connectées |
ECHO | Affiche les arguments de la commande |
Commandes liées aux processus
Commande | Description |
---|---|
PS | Liste les processus disponibles et leur état |
SLEEP | Met un processus en sommeil |
WAKE | Relance un processus dormant |
EXEC | Création d'un processus à partir d'un fichier exécutable |
La commande EXEC est complexe à réaliser, elle est optionnelle pour l'année 2023/2024.
Commandes liées au système de fichiers
Commandes pour gérer le système de fichiers :
Commande | Description |
---|---|
FORMAT | Formate le système de fichiers |
LIST | Affiche les fichiers du répertoire principal |
CREATE | Création d'un fichier avec l'entrée standard |
TYPE | Affiche le contenu d'un fichier |
COPY | Copie un fichier |
RENAME | Renomme un fichier |
Commandes liées au réseau
Commande | Description |
---|---|
REMOTE | Liste les fichiers disponibles sur le réseau |
GET | Télécharge un fichier en provenance du réseau |
PUT | Sauve un fichier sur le réseau |
Programmation des pilotes
La programmation c'est aussi l'écriture des sous-systèmes ou des pilotes liés à chaque cartes :
- pour la carte mère, il faut écrire un sous-système de gestion de fichiers ;
- pour la carte fille comportant une matrice de touches, il faut écrire le pilote identifiant les touches actionnées et gérant l'envoi de ces touches à la carte mère ;
- pour la carte fille gérant un clavier USB, il faut utiliser un projet LUFA pour scanner régulièrement le clavier USB et envoyer d'éventuels événements à la carte mère ;
- pour la carte fille gérant un écran LCD, il faut écrire le pilote capable de gérer l'écran et la partie récupérant les données de la carte mère ;
- pour la carte fille gérant une clé USB, il faut utiliser un projet LUFA pour l'accès à la clé et écrire un sous-système de gestion de fichiers ;
- la carte fille gérant une connexion série ne nécessite qu'un modeste pilote de communication avec la carte mère ;
- pour la carte fille permettant une connexion avec le monde extérieur via de simples points d'accès USB de volume (EP Bulk), il faut écrire le pilote pour communication avec la carte mère mais aussi le programme sur le PC permettant la réception ou l'envoi de fichiers ;
- pour la carte fille permettant une connexion Ethernet au dessus d'USB (RNDIS), il faut écrire une micro-pile réseau capable de récupérer et d'envoyer des fichiers au travers de paquets Ethernet mais aussi le programme sur le PC cible capable de comprendre ces paquets Ethernet ;
- pour la carte fille incluant une puce Ethernet, il faut implanter la micro-pile réseau et le programme évoquée ci-dessus mais aussi le pilote capable de gérer la puce Ethernet ;
- pour la carte fille son, il faut écrire la partie permettant de récupérer les échantillons, les copier en mémoire, et les jouer à la vitesse d'échantillonnage préalablement transmise.
Spécifications techniques
Spécification de l'ordonnanceur
Pour réaliser votre ordonnanceur commencez par programmer le minuteur 1 de l'ATMega328p de sorte à ce qu'il génère une interruption toutes les 20ms. L'interruption va déclencher une fonction de gestion d'interruption (ISR pour Interrupt Service Routine). Vous utiliserez une 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 */ ... /* Appel à l'ordonnanceur */ scheduler(); /* Récupération du contexte de la tâche ré-activée */ ... asm volatile ( "reti" ); }
Vous constatez qu'il vous reste à écrire, dans l'ISR, le code pour sauvegarder et récupérer le contexte d'exécution d'une tâche. Pour cela réfléchissez aux éléments d'un microcontrôleur qui sont nécessaires pour l'exécution d'un programme puis réfléchissez à une méthode pour sauver l'état de ces éléments. Attention, il est impératif de sauver directement le contexte dans l'ISR, tout code C préalable à la sauvegarde pourrait modifier l'état des registres.
Au moins dans un premier temps, les tâches seront définies de façon statique. Toutes les tâches doivent être définies dans un tableau global, seront lancées dès le démarrage du microcontrôleur et ne se termineront que lorsque le microcontrôleur sera arrêté ou relancé. Vous devez implanter une table des tâches en utilisant une structure dont un des champs doit être la fonction réalisant la tâche. A vous de définir les autres champs.
Avant de démarrer l'ordonnanceur, il faut prendre soin à ce que la récupération du contexte dans l'ISR fonctionne alors que les tâches n'auront pas encore été lancées. Il faut donc une fonction permettant d'initialiser le contexte pour chaque tâche. Pour poser l'adresse d'une fonction sur la pile vous pouvez utiliser le code ci-dessous.
uint16_t adresse=(uint16_t)fonction; asm volatile("push %0" : : "r" (adresse & 0x00ff) ); asm volatile("push %0" : : "r" ((adresse & 0xff00)>>8) );
Comme première amélioration de votre ordonnanceur, il vous est suggéré de mettre en place un état endormi pour vos processus. Vous pourriez ainsi endormir vos processus pour un certain temps, l'ISR se chargeant de mettre à jour les minuteurs des processus et les réveillant à l'expiration du délai.
Comme seconde amélioration, le tableau des tâches doit être géré de façon plus dynamique. Il n'est pas demandé de pouvoir changer la taille du tableau qui aura toujours une taille maximale définie par une constante. Par contre une tâche doit pouvoir être ajoutée à la liste des tâches sur présentation de la seule fonction à exécuter. Il faut donc calculer les adresses des piles de façon automatique. Pour simplifier, partez de l'adresse de la pile initiale 0x0800
et soustrayiez une constante qui deviendra la taille maximale des piles des tâches. L'initialisation de la pile de la nouvelle tâche devra se faire au vol. Enlever une tâche de la liste des tâches n'est pas particulièrement complexe, vous pouvez même laisser la tâche dans un état "terminé", son emplacement dans le tableau pouvant alors être récupéré par toute nouvelle tâche. Il est cependant demandé de gérer correctement la libération des piles des tâches. Cela signifie que l'adresse de la pile d'une nouvelle tâche n'est calculé en fonction de la plus basse des adresses de pile utilisées que s'il n'existe pas de bloc non utilisé en mémoire plus haute.
Des informations complémentaires sont disponibles dans le document Multitasking on an AVR
Cartes électroniques à microcontrôleur
Le bus série préconisé pour la communication entre la carte mère et les cartes filles est un bus SPI. Au niveau physique, les connexions entre cartes mères et cartes filles doivent être réalisées via des connecteurs HE-10 à 8 broches :
- 2 broches pour l'alimentation ;
- 3 broches pour les signaux SPI (MISO, MOSI, SCK) ;
- 1 broche pour le CS (Chip Select) ;-
- 1 broche pour une éventuelle ligne d'interruption ;
- 1 broche pour une eventuelle réinitialisation de la carte (Reset).
Il est conseillé d'organiser les broches sur les connecteurs HE-10 sur le modèle du bouclier Arduino fourni.
La carte mère est constituée autour d'un ATMega328p et d'une puce mémoire de type AT45DB641E de 8 Mo. Il faut réflechir à l'alimentation sachant que :
- l'alimentation de la carte mère doit être suffisante pour alimenter les cartes filles ;
- l'alimentation du microcontrôleur doit être compatible avec l'alimentation de la puce mémoire sauf à prévoir une conversion de niveaux logiques entre les deux puces ;
- les cartes filles d'un même pico-ordinateur doivent être alimentées avec la même tension.
Voici quelques exemples d'architectures de cartes filles :
- une carte fille SPI avec un ATMega328p et une matrice de touches ;
- une carte fille SPI avec un AT90USB647 pour connexion à un clavier USB ou une clef USB ;
- une carte fille SPI avec un ATMega328p et un écran LCD 2 lignes HD44780 ;
- une carte fille SPI avec un ATMega16u2 pour une connexion série USB ;
- une carte fille SPI avec un ATMega328p et un DAC R-2R comme carte son ;
- une carte fille SPI avec un ATMega16u2 (ou supérieur) pour une connexion réseau (périphérique USB générique EP bulk ou périphérique USB RNDIS) ;
- une carte fille SPI avec un ATMega328p et un ENC28J60 pour une connexion Ethernet 10Mb/s.
Toutes les cartes doivent permettre un accès au port série de leur microcontrôleur pour un déverminage via un FTDI. Vous devez aussi ajouter des points de test pour les lignes importantes : lignes du bus SPI, horloge, alimentations, etc.
Toutes les cartes filles répondent à la commande SPI constituée de l'octet de commande 0x00 et de l'octet de donnée 0x00. Le résultat se lit sur le second octet de réponse et détermine le type de la carte fille. Le chiffre de poid fort de la réponse est un code magique : 0xa. Le chiffre de poid faible est donné par le tableau ci-dessous :
Chiffre | Type de carte |
---|---|
0x1 | Clavier |
0x2 | Affichage |
0x4 | Réseau |
0x8 | Carte son |
Il est bon aussi de prévoir que les cartes filles puissent répondre à une commande SPI permettant de savoir si elles sont occupées ou non. L'octet de commande serait 0xFF et l'octet de donnée 0xFF. En cas de succès les deux octets de réponse sont à 0x55. Les cartes filles doivent prendre soin d'initialiser leur réponse SPI à autre chose que 0x55 lorsqu'elles se lancent dans un traitement autre qu'une écoute SPI.
Electronique et programmation système
Carte mère
La carte mère doit comporter un microcontrôleur ATMega328p, une puce mémoire AT45DB641E et 5 connecteurs HE10 pour connecter une carte fille FPGA et 4 cartes filles à microcontrôleur.
Vous avez aussi quelques points à approfondir.
- La source d'alimentation de votre pico-ordinateur, le plus simple semble être un connecteur USB pour utiliser soit un PC soit une batterie comme source ;
- La tension d'alimentation du microcontrôleur et des cartes filles :
- soit vous optez pour du 5V auquel cas il faut prévoir un régulateur de tension pour la puce mémoire et une adaptation des niveaux logiques pour cette même puce (voir le schéma des boucliers Arduino),
- soit vous partez sur du 3.3V et vos cartes filles doivent tourner en 3.3V, vu que vous aurez probablement un adaptateur 5V il faut quand même prévoir un régulateur 3.3V assez costaud sur la carte mère, de plus vous ne pouvez pas dépasser 8Mhz comme horloge ;
- La méthode de programmation de la carte mère :
- soit vous programmez le microcontrôleur via un connecteur AVR ISP, un programmateur et l'utilitaire
avrdude
; - soit vous ajoutez une puce pour réaliser un pont USB vers série vous pourrez alors programmer le microcontrôleur avec
dfu-programmer
, attention il faut tout de même prévoir des connecteurs AVR ISP pour charger l'amorceur sur l'ATMega328p et l'éventuel ATMega8u2 (vous utilisez cette puce pour l'USB/série plutôt qu'une puce FTDI).
- soit vous programmez le microcontrôleur via un connecteur AVR ISP, un programmateur et l'utilitaire
Du coté programmation vous êtes chargés d'écrire les fonctions d'accès à la mémoire, des primitives systèmes implantant un micro-système de fichiers et des primitives de communication via le bus SPI.
Les fonctions de base d'accès à la mémoire sont facilement trouvables, par exemple sur les sites du bureau d'études systèmes embarqués des PeiP.
Concernant le micro-système de fichiers, les fonctionnalités suivantes doivent être implantées :
- le système de fichiers doit résider dans la mémoire de 8 Mo, les accès à la mémoire se font par blocs de 256 octets ;
- l'accès au système de fichiers se fait par des primitives système ;
- le micro-système de fichiers ne comporte qu'un répertoire : le répertoire principal, ce dernier peut comporter au maximum 64 fichiers, un fichier est caractérisé par un nom de 16 caractères au maximum, sa taille et ses blocs de données, un fichier est décrit par un maximum de 16 blocs de données ;
- le superbloc du système de fichiers comprend, en plus de la description du répertoire racine, une carte bit à bit des blocs de données libres, cette carte nécessite 16 blocs ;
- les différentes primitives à implanter sont :
append
pour créer un fichier si non existant et ajouter des données en fin de fichier, les paramètres sont le nom du fichier, les données à ajouter et la taille des données,read
pour lire des données dans un fichier, les paramètres sont le nom du fichier, le tableau dans lequel stocker les données, la taille du tableau et le déplacement par rapport au début du fichier,remove
pour détruire un fichier, le seul paramètre est le nom du fichier,rename
pour renommer un fichier, les paramètres sont l'ancien et le nouveau noms du fichier,copy
pour copier un fichier, les paramètres sont le nom du fichier original et le nom de la copie.
Les primitives pour le bus SPI doivent être :
- une fonction d'initialisation avec le mode SPI désiré et la vitesse d'horloge souhaitée ;
- une primitive pour envoyer quelques octets via le bus SPI :
- les paramètres de la primitive sont le tableau des octets à envoyer, le tableau des octets à recevoir, le nombre d'octets à envoyer et la spécification de la sortie CS de sélection du périphérique SPI,
- la primitive stocke l'opération dans une file et effectue l'envoi dès que le bus est libre,
- si la file est pleine la primitive peut retourner un code d'erreur sinon la primitive retourne l'identifiant unique de l'opération,
- les octets de réponse du périphérique sont stockés dans le tableau des octets à recevoir, si le pointeur est nul les réponses ne sont pas stockées ;
- une primitive pour attendre la fin d'un envoi SPI, il faut donner en unique paramètre l'identifiant de l'opération, l'attente s'effectue en mettant le processus appelant en mode sommeil et en le réveillant dès que l'opération est effectuée.
Précisons que les tableaux stockés dans la file d'attente (requête et réponse SPI) le sont sous la forme de pointeurs. Ces pointeurs peuvent être librérés une fois l'opération effectuée.
Carte fille clavier "matrice de touches"
La réalisation de la carte est simple, il s'agit de scanner une matrice de touches à l'aide d'un ATMega328p. N'oubliez pas le connecteur HE10 pour la connexion de cette carte fille à une carte mère. Sur le connecteur HE10 il faut prévoir à la fois la ligne de sélection SPI (utilisez la broche SS par défaut sur votre carte) et une ligne pour générer une interruption sur la carte mère. Vous devez faire avec l'alimentation fournie par la carte mère. Regardez aussi la problèmatique sur la programmation décrite dans la section sur la carte mère.
Du coté programmation, c'est plus compliqué, vous êtes déjà chargés d'écrire la fonction de scan des touches :
- le scan des touches consiste juste à passer les lignes de la matrice en sorties, les colonnes en entrées avec résistance de charge et de rentrer dans une boucle pour passer une sortie à GND tandis que les autres restent à VCC, il suffit alors de consulter les entrées pour voir si une touche est appuyée (ou plusieurs) ;
- si une touche est appuyée la touche est stockée dans un tampon et la ligne d'interruption est activée ;
- si une requête SPI de code commande 0x01 est reçue, le nombre de touches dans le tampon est envoyé suivi des touches elles-mêmes.
Ensuite, au niveau de la carte mère vous devez écrire la primitive gérant les claviers. Cette primitive prend en paramètre la ligne de sélection SPI du clavier ciblé et un tampon pour stocker les codes des touches. La taille du tampon est aussi passée en paramètre. La primitive prépare une transmission SPI constituée du seul octet de valeur 0x01 en prenant soin de préciser un tableau pour stocker d'éventuelles touches. Attention, la transmission SPI est constituée de l'octet 0x01 mais aussi d'autant d'octets de valeurs indéterminées qu'il est attendu d'octets dans la réponse SPI. La primitive se met alors en attente de la réponse.
Au niveau du système d'exploitation les comportements suivants sont à implanter :
- à l'initialisation de la carte mère les périphériques SPI doivent être contactés pour déterminer lequels sont des claviers, des commandes SPI 0x00 sont donc envoyées avec attente des réponses SPI ;
- le processus SHELL toujours présent dans le système d'exploitation doit avoir le comportement suivant :
- si une interruption est reçue qui correspond à un périphérique clavier, le SHELL doit utiliser la primitive de lecture clavier pour obtenir la ou les touches appuyées ;
- dans un premier temps, les caractères reçus sont affichés en utilisant la primitive d'affichage (echo) ;
- à terme les lignes de caractères reçues sont analysées pour en extraire des commandes et les exécuter.
Carte fille clavier "USB"
Votre carte électronique doit tourner autour d'un AT90USB647 (ou supérieur). Ce microcontrôleur peut être programmé, en utilisant la bibliothèque LUFA, comme hôte USB. Il est donc possible de gérer, de cette façon un clavier USB et de faire interface avec la carte mère. Le schéma de la carte est très semblable à celui d'une carte comportant un ATMega16u2. Vous pouvez vous appuyer sur ce projet keylogger. Le plus simple est de programmer le microcontrôleur en utilisant l'utilitaire dfu-programmer
, pour cela il faut prévoir, en plus d'un connecteur USB A femelle pour le clavier, un connecteur USB mâle pour la programmation.
La programmation pour la gestion du clavier USB doit se reposer sur la démonstration KeyboardHost
de la bibliothèque LUFA. La conversion des modificateurs et des codes de touches en caractères ASCII sont à votre charge.
Pour le reste de la programmation voir la section sur la carte fille "matrice de touches".
Carte fille écran LCD
Cette carte comporte un ATMega328p et un écran LCD à base de contrôleur HD44780. L'écran LCD nécessite un potentiomètre pour régler la luminosité des cristaux liquides. N'oubliez pas le connecteur HE10 pour la connexion de cette carte fille à une carte mère. Sur le connecteur HE10 il faut prévoir une ligne de sélection SPI. Aucune ligne d'interruption n'est nécessaire pour cette carte. Enfin il vous faut prévoir un connecteur AVR ISP pour programmer le microcontrôleur.
Le microcontrôleur de la carte fille doit être programmé pour gérer le contrôleur HD44780 ou compatible :
- les écrans avec 1, 2, 3 ou 4 lignes doivent être gérés ;
- les caractères ASCII doivent être affichés sur l'écran ;
- les caractères spéciaux
\n
et\r
doivent être pris en compte pour le saut de ligne et le retour chariot ; - quelques codes VT100 doivent être pris en compte, en particulier pour le déplacement du curseur doivent être pris en compte ;
- le contrôleur HD44780 ou compatible doit être configuré en mode défilement automatique.
Les caractères à afficher ou spéciaux (y compris les séquences VT100) sont reçus par le bus SPI par la carte fille préfixés par le code commande 0x01.
Au niveau de la carte mère vous devez écrire la primitive système. Cette primitive prend en paramètre la ligne de sélection SPI de l'écran ciblé et la chaîne des caractères à envoyer. La primitive prépare une transmission SPI constituée de l'octet de commande 0x01 et des octets correspondant aux caractères.
Il ne faut pas demander un affichage si un autre affichage est en cours. C'est possible à réaliser avec la ligne prévue pour une interruption. La carte fille peut mettre cette ligne en état haut quand un affichage est en cours. Du coup la primitive peut se mettre en attente sur un état bas avant d'envoyer la requête SPI. C'est aussi possible de lancer des commandes 0xFF sur la carte jusqu'à obtenir les octets de réponse indiquant que la carte est libre.
Cette primitive est utilisée par le système d'exploitation pour réaliser l'écho des commandes dans le SHELL et aussi par toutes les commandes qui nécessitent un affichage. Tous les écrans doivent être sollicités lors des affichages, il faut donc que le système d'exploitation scanne les périphériques SPI pour trouver les écrans comme il le fait pour les claviers.
Carte fille réseau RNDIS
Le but de cette carte est de permettre au pico-ordinateur de converser avec l'extérieur. Une carte RNDIS permet de faire transister des paquets Ethernet au dessus d'une liaison USB. La carte n'a besoin que d'un microcontrôleur avec capacités USB pour fonctionner. Vous pouvez utiliser un ATMega16u2, un ATMega32u4 ou un AT90USB suivant la taille mémoire dont vous souhaitez disposer. Gardez en mémoire qu'un paquet Ethernet peut comporter jusqu'à 1500 octets. La programmation du microcontrôleur se fera par DFU USB. Il est notable que la programmation du microcontrôleur et la communication vers l'extérieur se fait par le même connecteur USB.
La communication se fait forcément via le PC auquel la carte fille est connectée par USB. Cela dit si ce PC est connecté en réseau, les fichiers auxquels le pico-ordinateur accède peuvent se trouver sur un autre PC. Si votre carte fille est correctement programmée comme carte RNDIS USB, le PC sous Linux va automatiquement créer une interface réseau usb0
. A vous de la démarrer avec la commande ip link set usb0 up
ou une commande du type ip address add @IP/masque dev usb0
.
La base de la programmation de cette carte est la démonstration LUFA RNDIS. Des versions de cette démonstration incluent une implantation de pile TCP/IP. A vous de voir à quel niveau vous souhaitez que votre carte réseau opère :
- si vous visez Ethernet, votre projet LUFA sera assez simple à écrire mais l'application sur le PC sera un peu exotique, il vous faudra utiliser la programmation socket en mode RAW ;
- si vous visez UDP, votre projet LUFA sera plus complexe mais l'application sur le PC pourra se faire avec des sockets en mode non connecté standard ;
- si vous visez TCP, votre projet LUFA sera complexe et nécessitera un microcontrôleur avec pas mal de mémoire, par contre vous pourrez utiliser des applications standard sur le PC (serveur Web ou FTP).
Au niveau de la carte, en plus de votre pile réseau vous devez écrire les fonctions permettant de lister des fichiers distants, de les recevoir et d'en envoyer :
- Si la commande SPI est 0x01, la carte mère vous demande de lister les fichiers distants, vous devez donc former les paquets réseau nécessaires pour envoyer la demande, la commande SPI peut comporter un argument supplémentaire pour préciser la localisation des fichiers. A la suite d'une commande 0x01, la carte mère doit envoyer des commandes 0x11 pour récupérer tous les noms de fichiers, idéalement un nom par commande 0x11 pour ne pas déborder la mémoire du microcontrôleur. Si le fichier n'est pas le dernier la réponse SPI est 0x01 sinon 0x00. Si vous utilisez Ethernet ou UDP, il faut prévoir un délai maximal pour la réception des paquets de sorte à ne pas bloquer la commande. En cas de non réception de paquet, il faut retourner un code SPI d'erreur, par exemple 0x02.
- Si la commande SPI est 0x02, la carte mère souhaite récupérer un fichier. Vous devez former les paquets réseau nécessaires pour demander le fichier précisé dans la suite de la commande SPI. De la même façon que pour la liste, des commandes SPI ultérieures 0x12 doivent être envoyées pour récupérer le contenu du fichier, la carte mère précise dans la commande la taille de données qu'elle est prête à accepter par requête. Même principe pour les réponses SPI que pour les commandes 0x11. Si vous utilisez Ethernet ou UDP, il faut mettre en place le même dispositif de surveillance de perte de paquets que pour la liste.
- Si la commande SPI est 0x03, la carte mère souhaite envoyer un fichier. Vous devez former les paquets réseau nécessaires pour préparer la réception du fichier précisé dans la suite de la commande SPI. De la même façon que pour la liste, des commandes SPI ultérieures 0x13 doivent être envoyées pour passer les fragements de fichier. Si vous utilisez Ethernet ou UDP, les paquets envoyés pour transmettre les fragments comportent un numéro de séquence. La carte mère termine par un fragment vide pour indiquer que le fichier est totalement transféré. Dans ce dernier cas un paquet particulier doit être envoyé pour indiquer la fin du fichier. Une réponse est requise pour ce paquet, cette réponse peut être un succès (tous les fragments ont été reçus) ou échec. Il convient de répercuter cette réponse en tant que réponse SPI : 0x00 pour succès ou 0x02 en cas d'erreur que ce soit une erreur remontée ou une absence de paquet réponse.
Au niveau de la carte mère vous devez écrire les primitives système permettant de préparer et de réaliser les listes, récupération et envois de fichiers. Ces primitives sont de simples encapsulation des commandes SPI décrites ci-dessus avec attente des réponses SPI pour les commandes de chiffre de poid fort 0x1. Il faut cependant s'assurer, avant chaque requête SPI, que la carte RNDIS est libre en utilisant la commande 0xFF.
Ces primitives sont utilisées par le système d'exploitation pour réaliser les commandes de sauvegarde et de chargement de fichiers. Il est supposé qu'il n'existe qu'une carte réseau par pico-ordinateur avec une ligne de sélection de périphérique SPI fixe.
Carte fille réseau "points d'accès de volume"
Cette carte est identique, au niveau électronique, à la carte réseau RNDIS. La seule différence réside dans la façon de faire transiter les paquets réseau de la carte vers le PC auquel elle est connectée en USB. Pour cette carte, il vous est proposé de faire ce transfert en utilisant deux points d'accès USB de type volume (bulk), un entrant et un sortant. Cette fois vous n'aurez pas l'aide du noyau Linux pour la création de l'interface réseau. Vous allez devoir écrire votre propre programme PC pour faire le pont entre la carte et une interface réseau virtuelle.
Sous Linux la création d'une interface virtuelle est possible avec la fonction ci-dessous.
int allocateNetworkDevice(char *name,int flags){ struct ifreq ifr; int fd,err; char *clonedev = "/dev/net/tun"; /* Open the clone device */ if((fd=open(clonedev,O_RDWR))<0) return fd; /* Preparation of the struct ifr, of type "struct ifreq" */ memset(&ifr,0,sizeof(ifr)); ifr.ifr_flags=flags; /* IFF_TUN or IFF_TAP, plus maybe IFF_NO_PI */ if(name!=NULL) strncpy(ifr.ifr_name,name,IFNAMSIZ); /* Try to create the device */ if((err=ioctl(fd,TUNSETIFF,(void *)&ifr))<0){ close(fd); return err; } /* Write back the name of the * interface to the variable "name" */ if(name!=NULL) strcpy(name,ifr.ifr_name); return fd; }
Le descripteur de fichier retourné est utilisable avec read
pour récupérer les paquets Ethernet entrants et avec write
pour envoyer des paquets sur l'interface. Pour transmettre les paquets à la carte ou lire ceux provenant de la carte vous pouvez utiliser la bibliothèque libusb-1.0
.
Pour le reste de la programmation liée à cette carte vous reporter à ce qui est demandé pour la carte réseau RNDIS.
Carte fille réseau Ethernet
Cette version de la carte réseau se connecte à un commutateur Ethernet et non pas à un PC pour communiquer avec le monde extérieur.
Au niveau électronique une puce Ethernet ENC28J60 (ou supérieur) est supervisée par un microcontrôleur ATMega328p. Toutes les précisions concernant les cartes à base d'ATMega328p s'appliquent (voir par exemple la carte fille clavier "matrice de touches). Vous trouverez la documentation de la puce Ethernet à l'adresse http://ww1.microchip.com/downloads/en/devicedoc/39662c.pdf. Un exemple de programmtion de la puce ENC624J600 est disponible à l'adresse https://github.com/2xs/smews/tree/develop/targets/Arduino_ethernet/drivers. Vous pouvez vous en inspirer pour la programmation de la puce ENC28J60.
La programmation spécifique à cette carte concerne l'envoi des paquets à la puce ENC28J60 et la récupération des paquets en provenance de la puce ENC28J60. Comme pour la liaison RNDIS, trois options s'ouvrent à vous pour l'implémentation de votre pile réseau :
- programmation Ethernet, votre projet sera assez simple à écrire mais l'application sur le PC sera un peu exotique, il vous faudra utiliser la programmation socket en mode RAW ;
- programmation UDP, votre projet sera plus complexe mais l'application sur le PC pourra se faire avec des sockets en mode non connecté standard ;
- programmation TCP, votre projet sera plus complexe; par contre vous pourrez utiliser des applications standard sur le PC (serveur Web ou FTP).
Le reste de la programmation (communication avec la carte mère et ajouts dans le système d'exploitation) est identique à ce qui est décrit pour la carte réseau RNDIS.
Carte fille son
D'un point de vue électronique la carte n'est pas très compliquée, elle comporte un ATMega328p comme coeur et sa spécificité est un convertisseur numérique vers analogique dit R-2R utilisant un réseau de résistance pour implanter la conversion. Utilisez au moins un DAC sur 8 bits, vous devez pouvoir pousser jusqu'à 12 bits en utilisant pratiquement toutes les sorties disponibles. En sortie du DAC utilisez un montage Darligton pour commander le haut-parleur. N'oubliez pas le connecteur HE10 pour connecter votre carte à une carte mère. Utilisez la ligne de sélection SPI par défaut pour que la carte mère puisse commander votre carte et prévoyez une ligne d'interruption. Enfin il vous faut prévoir un connecteur AVR ISP pour programmer le microcontrôleur.
Le microcontrôleur de la carte fille doit être programmé pour pouvoir jouer des sons déjà enregistrés :
- des variables globales doivent être configurées pour spécifier les caractéristiques des enregistrements à jouer :
- précision d'échantillonnage (8 à 24 bits même si au-delà de 14 bits vous devez abandonner les bits de poids faibles),
- vitesse d'acquisition (vous trouverez la vitesse maximale par essais et erreurs),
- format des enregistrements (vous vous limiterez à un format PCM entier classique avec représentation petit-boutiste) ;
- une fonction permettant d'envoyer les échantillons, à la bonne vitesse, sur le DAC.
Les échantillons sonores sont reçus par le bus SPI préfixés par le code commande 0x02. Les 2 valeurs de configuration, précision sur un octet et vitesse sur quatre octets sont envoyées préfixés par le code commande 0x01.
Au niveau de la carte mère vous devez écrire les primitives système :
- la première primitive correspond au message SPI de code commande 0x01 et passe les paramètres de configuration à la carte fille ;
- la seconde primitive correspond au message SPI de code commande 0x02 et passe les échantillons sonores à jouer à la carte fille, la primitive s'assure auparavent que la carte n'est pas déjà occupée à jouer des échantillons avec la commande SPI 0xFF, en cas de carte occupée le code 0xFF est retourné.
Ces primitives sont utilisées utilisée par le système d'exploitation pour implanter une nouvelle commande PLAY
permettant de jouer un fichier WAV :
- l'entête du fichier est récupéré avec la primitive
read
; - l'entête WAV est analysée pour récupérer la fréquence et la précision d'échantillonnage, ces données sont passées à la carte son via la première primitive décrite ci-dessus ;
- le nombre de canaux est récupéré dans l'entête, les echantillons du premier canal sont alors lus grâce à
read
et envoyés à la carte son en utilisant la seconde primitive ; - si les échantillons sont envoyés trop vite et donnent lieu à un code d'erreur de la seconde primitive, les mêmes échantillons sont à nouveau envoyés et ainsi de suite ;
- éventuellement, si votre système d'exploitation dispose d'une primitive
usleep
vous pouvez mettre en place un algorithme d'attente après chaque appel à la primitive, ce temps peut être calculé en fonction de la vitesse de restitution et du nombre d'échantillons envoyés.
Sources des figures
Figure du contexte d'exécution : Media:2023_exec_context.svg.
Figure du changement de contexte : Media:2023_ordonne_schema.svg