« SE4Binome2023-6 » : différence entre les versions
(21 versions intermédiaires par un autre utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
== <big>Réalisation du bouclier de test</big> == | |||
Parmi les activités disponibles, j'ai réalisé les composants suivants: | |||
Le premier type de carte permet de contrôler une matrice de LED à grâce aux signaux SPI. | |||
[[Fichier:Carte1.png|gauche|vignette|J'ai soudé quatre de ces cartes, j'en ai gardé une pour moi puis j'ai donné les cartes en plus aux binômes 3, 7 et 10.]] | |||
<p style="clear: both;" /> | |||
Les câbles permettent de faire la liaison entre les connecteurs HE10 afin de connecter le bouclier de test aux deux autres types de carte. | |||
[[Fichier:2Cable.png|gauche|vignette|J'ai fait deux câbles comme ceci]] | |||
<p style="clear: both;" /> | |||
Le deuxième type de carte a une fonction similaire au premier mais permet de contrôler des afficheurs 7 segments avec des signaux SPI. | |||
[[Fichier:LED_afficheur.png|gauche|vignette|J'ai soudé quatre de ces cartes pour afficheurs LED, j'en ai gardé une pour moi puis j'ai donné les cartes en plus aux binômes 1, 4 et 10.]] | |||
<p style="clear: both;" />Par la suite, j'ai reçu directement un bouclier de test pour continuer le projet. | |||
== <big>Ordonnanceur / système d'exploitation</big> == | == <big>Ordonnanceur / système d'exploitation</big> == | ||
Je vais commencer l'ordonnanceur en faisant alterner deux tâches, tout d'abord je vais faire clignoter deux LED. Les deux tâches vont agir sur les LED du bouclier liées à CS2 et CS3. Chacune a une fonction d'initialisation au démarrage et une fonction qui sera exécutée par l'ordonnanceur. Je me suis inspiré du TP que j'avais fait l'année dernière sur l'ordonnancement sur AVR.<syntaxhighlight lang="c"> | |||
void init_task_led_cs3() | |||
{ | |||
DDRC |= led_bit_cs3; //mettre PC3 en sortie | |||
PORTC |= 0x00; | |||
} | |||
void task_led_cs3() | |||
{ | |||
PORTC ^= led_bit_cs3; | |||
} | |||
void init_task_led_cs2() | |||
{ | |||
DDRC |= led_bit_cs2; //mettre PC0 en sortie | |||
PORTC |= 0x00; | |||
} | |||
void task_led_cs2() | |||
{ | |||
PORTC ^= led_bit_cs2; | |||
} | |||
</syntaxhighlight>Dans la table des processus, j'ai mis les deux tâches task_led_cs2 et task_led_cs3. A chaque fois que le TIMER1 s'active, l'ISR est appelée et change le contexte en reprenant là où il s'était arrêté dans le processus d'avant.<syntaxhighlight lang="c"> | |||
[ | //table des taches | ||
scheduler_t processus[MAX_PROCS]={ | |||
{0x500,task_led_cs2}, | |||
{0x600,task_led_cs3}, | |||
}; | |||
/* Fonction qui determine la prochaine tache a executer */ | |||
void scheduler() | |||
{ | |||
pid=(pid+1)%MAX_PROCS; | |||
} | |||
= | ISR(TIMER1_COMPA_vect, ISR_NAKED) | ||
{ | |||
SAVE_REGISTER(); | |||
processus[pid].pile=SP; | |||
= | scheduler(); | ||
SP = processus[pid].pile; | |||
RESTORE_REGISTER(); | |||
asm volatile("reti"); | |||
} | |||
void init_proc(int i) | |||
{ | |||
uint16_t old = SP; | |||
== | SP = processus[i].pile; | ||
int adresse=(int)processus[i].fonction; | |||
asm volatile("push %0" : : "r" (adresse & 0x00ff) ); | |||
asm volatile("push %0" : : "r" ((adresse & 0xff00)>>8) ); | |||
SAVE_REGISTER(); | |||
processus[i].pile = SP; | |||
SP = old; | |||
} | |||
[ | int main(void) | ||
{ | |||
init_task_led_cs2(); //initialiser les taches | |||
init_task_led_cs3(); | |||
init_timer(); //initialiser le timer | |||
for(int i=0;i<MAX_PROCS;i++) init_proc(i); | |||
sei(); | |||
processus[0].fonction(); | |||
while(1) | |||
{ | |||
} | |||
return 0; | |||
} | |||
</syntaxhighlight>J'ai réussi à faire fonctionner l'ordonnanceur avec les deux tâches de clignotement de LED qui s'alternent entre elles. | |||
[[Fichier: | [[Fichier:OrdonnanceurPartie1.mp4|gauche|Les deux tâches(allumer puis éteindre LED) s'exécutent en même temps grâce à l'ordonnanceur]] | ||
<p style="clear: both;" /> | <p style="clear: both;" /> | ||
== <big>Réalisation de la carte mère</big> == | == <big>Réalisation de la carte mère</big> == | ||
=== <big>Type de carte choisie</big> === | |||
J'ai choisi de réaliser la '''carte mère''' du pico ordinateur sur laquelle on va venir brancher les cartes filles. Voici les choix que j'ai fait pour la conception de cette carte: | |||
* Alimentation 5V provenant de l'USB (j'utilise un connecteur mini USB B). | |||
* Programmation de la carte avec dfu-programmer grâce à un FTDI (FT232RL) mais connecteur AVR ISP inclus aussi. | |||
* Régulateur de tension pour la puce mémoire et une adaptation des niveaux logiques pour cette dernière. | |||
* ATmega328p traversant. | |||
* Possibilité de brancher 5 cartes filles grâce à 5 connecteurs HE10. | |||
=== Schematic de la carte mère === | === Schematic de la carte mère === | ||
Tout d'abord, il a fallu réaliser le schématic de la carte sur KiCAD. Je me suis inspiré du Shield de test et ai inclus les nouveaux composants tels que le FTDI, le connecteur mini USB B ... | Tout d'abord, il a fallu réaliser le schématic de la carte sur KiCAD. Je me suis inspiré du Shield de test et ai inclus les nouveaux composants tels que le FTDI, le connecteur mini USB B ... | ||
J'ai ajouté des condensateurs de découplages pour chaque composant important | J'ai ajouté des condensateurs de découplages pour chaque composant important afin d'éliminer les parasites produits par les pistes. Lors de la réalisation du PCB, il faudra faire en sorte qu'ils soient physiquement proches pour être efficaces. Je me suis renseigné sur internet pour savoir comment connecter le FTDI et le mini USB B. [[Fichier:Schematic carte mere B6.png|gauche|vignette|Schematic réalisé pour la carte mère]] | ||
<p style="clear: both;" /> | <p style="clear: both;" /> | ||
Ligne 56 : | Ligne 138 : | ||
<p style="clear: both;" /> | <p style="clear: both;" /> | ||
Par la suite, j'ai commencé à souder les composants | Par la suite, j'ai commencé à souder les composants, cependant, j'ai rencontré plusieurs problèmes: | ||
J'ai dû refaire une autre carte mère avec un FTDI qui fonctionne car je ne suis pas parvenu à inverser D+ et D- sur la carte originale. | * les pistes D+ et D- du FTDI sont inversées | ||
Après avoir mis un bootloader sur l'ATMEGA328P, je suis parvenu à téléverser des programmes à travers le FTDI en utilisant l'utilitaire avrdude. | * Les connecteurs HE10 couvrent légèrement certains composants et la LED pour certains | ||
Le programme fait clignoter la LED à la bonne vitesse, cela signifie donc que le cristal 8MHz a été soudé correctement et que la carte fonctionne sans programmeur. | * La carte n'est pas reconnue en périphérique USB si on fait un '''lsusb'''. | ||
* J'ai dû refaire une autre carte mère avec un FTDI qui fonctionne car je ne suis pas parvenu à inverser D+ et D- sur la carte originale. | |||
Par la suite, je vais souder la carte SD et les connecteurs HE10 manquants. | * Après avoir mis un bootloader sur l'ATMEGA328P, je suis parvenu à téléverser des programmes à travers le FTDI en utilisant l'utilitaire avrdude. | ||
* Le programme fait clignoter la LED à la bonne vitesse, cela signifie donc que le cristal 8MHz a été soudé correctement et que la carte fonctionne sans programmeur. | |||
* Par la suite, je vais souder la carte SD et les connecteurs HE10 manquants. | |||
=== Carte finale soudée === | === Carte finale soudée === | ||
Ci-dessous, la carte mère finale. | |||
[[Fichier:CarteMereSoudee.jpg|vignette|gauche]] | [[Fichier:CarteMereSoudee.jpg|vignette|gauche]] | ||
<p style="clear: both;" /> | <p style="clear: both;" />J'ai dû retirer le connecteur ICSP car il m'empêchait de souder le dernier connecteur HE10, ce n'est pas grave puisque le FTDI me permet de programmer directement avec avr-dude. J'ai rencontré beaucoup de problèmes en raison de mauvaises soudures et de faux contacts. Cependant, après plusieurs tests et corrections sur la carte, elle est finalement fonctionnelle. | ||
== <big>Programmation de la carte mère</big> == | == <big>Programmation de la carte mère</big> == | ||
Le but de cette partie est de réaliser un micro système de fichier. Pour cela, je dois implémenter les fonctions d'accès à la mémoire de la carte SD ainsi que les primitives qui permettront de manipuler les fichiers. | Le but de cette partie est de réaliser un micro système de fichier. Pour cela, je dois implémenter les fonctions d'accès à la mémoire de la carte SD ainsi que les primitives qui permettront de manipuler les fichiers. | ||
=== <big>Ecriture et lecture sur la carte SD</big> === | |||
=== | |||
Maintenant que la carte est terminée, il reste la partie accès mémoire pour ensuite écrire les fichiers du Pico ordinateur. J'ai rencontré des problèmes lors de l'écriture et lecture de la carte SD mais après avoir soudé à nouveau l'adaptateur de niveau et vérifié les tensions et les masses, je suis parvenu à écrire "0xac", "0xbc" et "0xcc" sur un bloc de la carte SD et récupérer ces informations pour les afficher sur le port série. Pour la suite du projet, je vais réutiliser ces fonctions de base d'accès à la carte SD pour écrire et lire les fichiers ainsi que les manipuler. | Maintenant que la carte est terminée, il reste la partie accès mémoire pour ensuite écrire les fichiers du Pico ordinateur. J'ai rencontré des problèmes lors de l'écriture et lecture de la carte SD mais après avoir soudé à nouveau l'adaptateur de niveau et vérifié les tensions et les masses, je suis parvenu à écrire "0xac", "0xbc" et "0xcc" sur un bloc de la carte SD et récupérer ces informations pour les afficher sur le port série. Pour la suite du projet, je vais réutiliser ces fonctions de base d'accès à la carte SD pour écrire et lire les fichiers ainsi que les manipuler. | ||
Ligne 89 : | Ligne 166 : | ||
<p style="clear: both;" /> | <p style="clear: both;" /> | ||
=== <big> | === <big>Micro système de fichiers</big> === | ||
==== <big>Fonctions d'accès à la mémoire</big> ==== | ==== <big>Fonctions d'accès à la mémoire</big> ==== | ||
Ligne 101 : | Ligne 178 : | ||
* Un string (tableau de char) qui contiendra les données lues dans le bloc | * Un string (tableau de char) qui contiendra les données lues dans le bloc | ||
* La taille des données à lire | * La taille des données à lire | ||
<syntaxhighlight lang="c" line="1" start="28"> | |||
void readBlock(SD_info *sd, unsigned int numBlock, int offset, char *storage, int size) { | |||
readData(sd,numBlock,offset, size, (uint8_t*) storage); | |||
} | |||
La fonction '''writeBlock''' est similaire et prend exactement les mêmes arguments mais je ne suis pas parvenu à avoir un offset fonctionnel. | </syntaxhighlight>La fonction '''writeBlock''' est similaire et prend exactement les mêmes arguments mais je ne suis pas parvenu à avoir un offset fonctionnel. | ||
* Un SD_info* (structure renvoyée par la fonction d'initialisation de la carte SD) - Un numéro de bloc (celui à modifier) | * Un SD_info* (structure renvoyée par la fonction d'initialisation de la carte SD) - Un numéro de bloc (celui à modifier) | ||
Ligne 108 : | Ligne 190 : | ||
* Un string (tableau de char) qui contiendra les données à écrire dans le bloc | * Un string (tableau de char) qui contiendra les données à écrire dans le bloc | ||
* La taille des données à écrire | * La taille des données à écrire | ||
<syntaxhighlight lang="c" line="1" start="33"> | |||
void writeBlock(SD_info *sd, unsigned int numBlock, int offset, char *source, int size) { | |||
problèmes rencontrés: Pour la fonction writeBlock, il n'y avait pas d'offset dans la fonction d'écriture de base dans Sd2Card.c, j'ai tenté de reproduire un offset avec des décalages mais cela fonctionne pas, je reçois des erreurs mémoires qui réinitialisent la carte. J'ai donc dû adapter la façon dont je stocke mes données en mémoire. | char buffer_write[BLOCK_SIZE]; | ||
readData(sd, numBlock, 0, size, (uint8_t *)buffer_write); | |||
if(offset + size > 512 ) | |||
{ | |||
printf("data to write is too big\n"); | |||
return; | |||
} | |||
for (int i = size - 1; i >= 0; i--) { | |||
source[i + offset] = source[i]; | |||
} | |||
// Ajouter les zéros au début de la chaîne | |||
for (int j = 0; j < offset; j++) { | |||
source[j] = buffer_write[j]; | |||
} | |||
writeBlockSD(sd,numBlock, (uint8_t *)source); | |||
} | |||
</syntaxhighlight>problèmes rencontrés: Pour la fonction writeBlock, il n'y avait pas d'offset dans la fonction d'écriture de base dans Sd2Card.c, j'ai tenté de reproduire un offset avec des décalages mais cela fonctionne pas en pratique, je reçois des erreurs mémoires qui réinitialisent la carte. J'ai donc dû adapter la façon dont je stocke mes données en mémoire. | |||
==== <big>Organisation de la mémoire</big> ==== | ==== <big>Organisation de la mémoire</big> ==== | ||
Ligne 134 : | Ligne 236 : | ||
Voici les fonction que j'ai implémenté: | Voici les fonction que j'ai implémenté: | ||
La fonction '''LS''' (list): pour lister les fichiers actuels dans le répertoire principal. | La fonction '''LS''' (list): pour lister les fichiers actuels dans le répertoire principal.<syntaxhighlight lang="c" line="1" start="54"> | ||
void LS(SD_info *sd) { | |||
char buffer[MAX_FILENAME_LENGTH]; | |||
int fileCount = 0; | |||
printf("\n"); | |||
// // Parcourir les blocs multiples de 16 à partir de 0 jusqu'à MAX_BLOCKS_IN_SUPERBLOCK | |||
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) { | |||
readBlock(sd,blockNum, 0, buffer, MAX_FILENAME_LENGTH); | |||
// Vérifier si le nom de fichier est vide | |||
if (buffer[0] != 0) { | |||
// Afficher le nom du fichier | |||
printf("%s\n", buffer); | |||
fileCount++; | |||
} | |||
} | |||
if (fileCount == 0) { | |||
printf("\nAucun fichier trouvé.\n"); | |||
} | |||
} | |||
La fonction ''' | </syntaxhighlight>La fonction '''TYPE''' (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 et les données à ajouter.<syntaxhighlight lang="c" line="1" start="241"> | ||
void TYPE(SD_info *sd, char *filename, char *data) { | |||
size_t sizeFilename = strlen(filename); | |||
char buffer[MAX_FILENAME_LENGTH]; | |||
int placeFound = -1; | |||
int index_description_block = 1; // indice du bloc de descrption | |||
int block_counter = 1; //compteur de blocs utilisés | |||
int index_in_descrBlock = 0; | |||
int offset; | |||
int lock_block = 0; | |||
int append_command = -1; | |||
int blockNum_buffer = 0; | |||
if (sizeFilename > MAX_FILENAME_LENGTH) { | |||
printf("\nImpossible de créer le fichier, nom trop long\n"); | |||
return; | |||
} | |||
// Parcours des blocs réservés pour la description des fichiers (superbloc) | |||
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK+16; blockNum += 16) { | |||
readBlock(sd,blockNum, 0, buffer, MAX_FILENAME_LENGTH); | |||
// Vérifier si le bloc est vide (pas de nom de fichier) | |||
if (buffer[0] == 0 && lock_block == 0){ | |||
//printf("\nbloc potentiel trouvé\n"); | |||
blockNum_buffer = blockNum; //au cas où on trouve un bloc libre | |||
lock_block = 1;//on conserve cette place au cas où il faut créer le fichier | |||
} | |||
if (strncmp(buffer,filename,strlen(filename))==0) //si même nom de fichier alors on concatenne les data | |||
{ | |||
append_command = 1; | |||
char append_description_buffer[CHUNK_SIZE]; // récupère le numéro du bloc dans la description | |||
char append_buffer[CHUNK_SIZE]; //récupère les données finales à écrire dans le bloc de donnée | |||
readBlock(sd, blockNum+1, 0, append_description_buffer, 2); | |||
int dataBlockNum = reconstituteNumber(append_description_buffer); | |||
readBlock(sd, dataBlockNum, 0, append_buffer, CHUNK_SIZE); | |||
strcat(append_buffer, data); //former les données finales | |||
writeBlock(sd, dataBlockNum, 0, append_buffer, CHUNK_SIZE); // écrire dans bloc de donnée, rien à fiare dans description | |||
break; | |||
} | |||
if (blockNum == MAX_BLOCKS_IN_SUPERBLOCK) { //si le fichier n'existe pas on le crée | |||
if (sizeFilename < MAX_FILENAME_LENGTH) { | |||
sizeFilename += 1; // Ajouter '\0' s'il y a de la place | |||
} | |||
writeBlock(sd,blockNum_buffer, 0, filename, sizeFilename); | |||
placeFound = blockNum_buffer; | |||
//chercher où mettre les données du | |||
int dataBlockNum = findAvailableBlock(sd); // Premier bloc de données à partir du bloc 1040 | |||
char dataBlockNumBuffer[CHUNK_SIZE]; | |||
readBlock(sd, dataBlockNum, 0, dataBlockNumBuffer, CHUNK_SIZE); | |||
while (dataBlockNumBuffer[0] != 0) | |||
{ | |||
dataBlockNum++; | |||
readBlock(sd, dataBlockNum, 0, dataBlockNumBuffer, CHUNK_SIZE); | |||
} | |||
int blockSizeUsed = 0; // Compteur d'octets dans le bloc actuel | |||
==== Démonstration ==== | char chunkBuffer[CHUNK_SIZE]; | ||
size_t bytesRead; | |||
bytesRead = strlen(data); | |||
strcpy(chunkBuffer,data); | |||
// Écrire le chunk dans le bloc de données | |||
writeBlock(sd,dataBlockNum, blockSizeUsed, chunkBuffer, bytesRead); | |||
setBlockAvailability(sd,dataBlockNum, 1); | |||
// Écrire le numéro du bloc actuel dans la description du fichier | |||
char blockNumberBuffer[64]; | |||
createNumberBuffer(dataBlockNum,blockNumberBuffer); | |||
blockSizeUsed += bytesRead; | |||
// Si le bloc actuel est plein, passer au bloc suivant OU si c'est le premier tour dans la boucle | |||
// Ecriture du numéro de bloc utilisé dans les blocs de description | |||
if (index_description_block == 0) { | |||
offset = MAX_FILENAME_LENGTH; | |||
} else { | |||
offset = 0; | |||
} | |||
writeBlock(sd,placeFound + index_description_block, offset + index_in_descrBlock*2, blockNumberBuffer, 2); | |||
index_in_descrBlock++; | |||
dataBlockNum = findAvailableBlock(sd); // Passer au bloc suivant | |||
blockSizeUsed = 0; // Réinitialiser la taille utilisée | |||
// Passage au bloc de description suivant | |||
if (block_counter == (BLOCK_SIZE/2-offset)) { | |||
index_description_block++; | |||
index_in_descrBlock=0; | |||
} | |||
// Compteur de nombre de blocs utilisés | |||
block_counter++; | |||
// Vérifie si on a atteint le nombre maximal de blocs par fichier | |||
if (block_counter >= 2040) { | |||
printf("\nLe fichier a atteint sa taille maximale\n"); | |||
return; | |||
} | |||
break; // Fichier créé, sortir de la boucle | |||
} | |||
} | |||
if (placeFound != -1) { | |||
printf("\nLe fichier \"%s\" a été créé avec succès.\n", filename); | |||
} else { | |||
if(append_command == 1) printf("\nLes données ont été concaténées dans le fichier %s\n",filename); | |||
else printf("\nPlus de place dans le système de fichier pour créer ce fichier.\n"); | |||
} | |||
} | |||
</syntaxhighlight>La fonction '''CAT''' (read): pour afficher le contenu d'un fichier, les paramètres sont le nom du fichier à lire. <syntaxhighlight lang="c" line="1" start="359"> | |||
void CAT(SD_info *sd, char *filename) { | |||
char filenameBuffer[MAX_FILENAME_LENGTH]; | |||
char byteBuffer[CHUNK_SIZE]; | |||
char dataBuffer[CHUNK_SIZE]; | |||
// Parcours des blocs réservés pour la description des fichiers (superbloc) | |||
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) { | |||
readBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH); | |||
// Vérifier si le bloc contient le nom du fichier recherché | |||
if (strncmp(filenameBuffer, filename, MAX_FILENAME_LENGTH) == 0) { | |||
// Le nom du fichier a été trouvé | |||
// Parcours les blocs de description | |||
for (int descrBlockNum=1; descrBlockNum<MAX_BLOCKS_PER_FILE_DESCRIPTION; descrBlockNum++) { | |||
// Lecture des octets deux par deux | |||
readBlock(sd,descrBlockNum+blockNum, 0, byteBuffer, 2); | |||
if (byteBuffer[0] == 0 && byteBuffer[1]== 0) return; | |||
// Lire les numéros de blocs associés à ce fichier depuis les blocs de description | |||
int dataBlockNum = reconstituteNumber(byteBuffer); | |||
// Lire et afficher le contenu du bloc de données | |||
readBlock(sd,dataBlockNum, 0, dataBuffer, CHUNK_SIZE); | |||
printf("\n%s\n",dataBuffer); | |||
//} | |||
//} | |||
} | |||
return; // Fichier affiché, sortie de la fonction | |||
} | |||
} | |||
// Si le fichier n'a pas été trouvé | |||
printf("\nLe fichier \"%s\" n'a pas été trouvé.\n", filename); | |||
} | |||
</syntaxhighlight>La fonction '''MV''' (rename): pour renommer un fichier, les paramètres sont le nom du fichier à renommer et le nouveau nom. <syntaxhighlight lang="c" line="1" start="164"> | |||
void MV(SD_info *sd, char *old_filename, char *new_filename) { | |||
size_t sizeNew_filename = strlen(new_filename); | |||
char filenameBuffer[MAX_FILENAME_LENGTH]; | |||
int fileFound = 0; | |||
// Parcourir les blocs réservés pour la description des fichiers (superbloc) | |||
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 1) { | |||
readBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH); | |||
// Vérifier si le bloc contient le nom du fichier recherché | |||
if (strncmp(filenameBuffer, old_filename, MAX_FILENAME_LENGTH) == 0) { | |||
if (sizeNew_filename < MAX_FILENAME_LENGTH) { | |||
sizeNew_filename+=1; | |||
} | |||
// Écrire le nom du fichier dans l'emplacement | |||
writeBlock(sd,blockNum, 0, new_filename, sizeNew_filename); | |||
fileFound=1; | |||
break; // Nom modifié, sortir de la boucle | |||
} | |||
} | |||
if (fileFound == 1) { | |||
printf("\nLe nom du fichier \"%s\" a été renommé avec succès.\n", old_filename); | |||
} else { | |||
printf("\nLe fichier \"%s\" n'a pas été trouvé.\n", old_filename); | |||
} | |||
} | |||
</syntaxhighlight>La fonction '''CP''' (copy): pour copier un fichier et mettre ses données dans un nouveau fichier, les paramètres sont le nom du fichier copié et le nom du nouveau fichier.<syntaxhighlight lang="c" line="1" start="403"> | |||
void CP(SD_info *sd, char *source_filename, char *destination_filename) { | |||
char source_filenameBuffer[MAX_FILENAME_LENGTH]; | |||
char destination_filenameBuffer[MAX_FILENAME_LENGTH]; | |||
char descriptionBuffer[CHUNK_SIZE]; | |||
char numBuffer[64]; | |||
int destination_offset; | |||
int source_offset; | |||
int numDataBlock; | |||
int newDataBlock; | |||
// Recherche du fichier source | |||
source_offset = -1; | |||
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) { | |||
readBlock(sd,blockNum, 0, source_filenameBuffer, MAX_FILENAME_LENGTH); | |||
// Vérifier si le bloc contient le nom du fichier source | |||
if (strncmp(source_filenameBuffer, source_filename, MAX_FILENAME_LENGTH) == 0) { | |||
source_offset = blockNum; | |||
break; | |||
} | |||
} | |||
if (source_offset == -1) { | |||
printf("\nLe fichier source \"%s\" n'a pas été trouvé.\n", source_filename); | |||
return; | |||
} | |||
// Recherche d'un emplacement libre pour le fichier destination | |||
destination_offset = -1; | |||
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) { | |||
readBlock(sd,blockNum, 0, destination_filenameBuffer, MAX_FILENAME_LENGTH); | |||
// Vérifier si le bloc est vide (pas de nom de fichier) | |||
if (destination_filenameBuffer[0] == 0) { | |||
destination_offset = blockNum; | |||
break; | |||
} | |||
} | |||
if (destination_offset == -1) { | |||
printf("\nPlus de place dans le système de fichier pour créer la copie de \"%s\".\n", source_filename); | |||
return; | |||
} | |||
// Copie du nom de la copie dans le bloc de description | |||
writeBlock(sd,destination_offset, 0, destination_filename, strlen(destination_filename)); | |||
// Lecture des numéros de bloc dans les blocs de description | |||
readBlock(sd,source_offset+1, 0, numBuffer, 2); | |||
numDataBlock = reconstituteNumber(numBuffer); | |||
//si aucune donnée à copier | |||
if (numDataBlock == 0) { | |||
printf("\nLa copie de \"%s\" sous le nom \"%s\" a été créée avec succès.\n", source_filename, destination_filename); | |||
return; | |||
} | |||
// On stocke le bloc de données associé au fichier source dans descriptionBuffer | |||
readBlock(sd,numDataBlock, 0, descriptionBuffer, CHUNK_SIZE); | |||
// Trouver un nouveau bloc de données disponible | |||
newDataBlock = findAvailableBlock(sd); | |||
char dataBlockNumBuffer[CHUNK_SIZE]; | |||
readBlock(sd, newDataBlock, 0, dataBlockNumBuffer, CHUNK_SIZE); | |||
while (dataBlockNumBuffer[0] != 0) | |||
{ | |||
newDataBlock++; | |||
readBlock(sd, newDataBlock, 0, dataBlockNumBuffer, CHUNK_SIZE); | |||
} | |||
// Mise à jour de la carte de disponibilités | |||
setBlockAvailability(sd,newDataBlock, 1); | |||
// Ecriture du numéro de bloc dans la description du fichier | |||
createNumberBuffer(newDataBlock,numBuffer); | |||
writeBlock(sd,destination_offset+1, 0, numBuffer, 2); | |||
// Ecriture du bloc de données dans le premier bloc disponible | |||
writeBlock(sd,newDataBlock, 0, descriptionBuffer, CHUNK_SIZE); | |||
printf("\nLa copie de \"%s\" sous le nom \"%s\" a été créée avec succès.\n", source_filename, destination_filename); | |||
} | |||
</syntaxhighlight>La fonction '''RM''' (remove): pour effacer un fichier et ses données, les paramètres sont le nom du fichier à supprimer. <syntaxhighlight lang="c" line="1" start="115"> | |||
void RM(SD_info *sd, char *filename) { | |||
int fileFound = -1; | |||
int offset; | |||
char fileBuffer[CHUNK_SIZE]; | |||
// Parcourir les blocs réservés pour la description des fichiers (superbloc) | |||
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) { | |||
char filenameBuffer[MAX_FILENAME_LENGTH]; | |||
readBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH); | |||
// Vérifier si le bloc contient le nom du fichier recherché | |||
if (strncmp(filenameBuffer, filename, MAX_FILENAME_LENGTH) == 0) { | |||
// Effacer le nom du fichier dans le superbloc | |||
memset(filenameBuffer, 0, MAX_FILENAME_LENGTH); | |||
writeBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH); | |||
fileFound = 1; | |||
offset = blockNum; | |||
break; | |||
} | |||
} | |||
// Fin de fonction si fichier inexistant | |||
if (fileFound == -1) { | |||
printf("\nLe fichier \"%s\" n'a pas été trouvé.\n", filename); | |||
return; | |||
} | |||
for (int blockNum = offset+1; blockNum < offset + 16; blockNum++) { | |||
int chunkSize = 0; | |||
int chunkStart = 0; | |||
readBlock(sd,blockNum, 0, fileBuffer, chunkSize); | |||
int blockNumData = reconstituteNumber(fileBuffer); | |||
if (blockNumData == 0) { | |||
writeBlock(sd,blockNum, chunkStart, fileBuffer, chunkSize); | |||
printf("\nLe fichier \"%s\" a été supprimé avec succès.\n", filename); | |||
return; // Sortir des boucles | |||
} | |||
else setBlockAvailability(sd,blockNumData, 0); // Marquer le bloc comme disponible | |||
writeBlock(sd,blockNum, chunkStart, fileBuffer, chunkSize); | |||
//} | |||
} | |||
printf("\nLe fichier \"%s\" a été supprimé avec succès.\n", filename); | |||
} | |||
// Fonction qui modifie le nom du fichier | |||
void MV(SD_info *sd, char *old_filename, char *new_filename) { | |||
size_t sizeNew_filename = strlen(new_filename); | |||
char filenameBuffer[MAX_FILENAME_LENGTH]; | |||
int fileFound = 0; | |||
// Parcourir les blocs réservés pour la description des fichiers (superbloc) | |||
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 1) { | |||
readBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH); | |||
// Vérifier si le bloc contient le nom du fichier recherché | |||
if (strncmp(filenameBuffer, old_filename, MAX_FILENAME_LENGTH) == 0) { | |||
if (sizeNew_filename < MAX_FILENAME_LENGTH) { | |||
sizeNew_filename+=1; | |||
} | |||
// Écrire le nom du fichier dans l'emplacement | |||
writeBlock(sd,blockNum, 0, new_filename, sizeNew_filename); | |||
fileFound=1; | |||
break; // Nom modifié, sortir de la boucle | |||
} | |||
} | |||
if (fileFound == 1) { | |||
printf("\nLe nom du fichier \"%s\" a été renommé avec succès.\n", old_filename); | |||
} else { | |||
printf("\nLe fichier \"%s\" n'a pas été trouvé.\n", old_filename); | |||
} | |||
} | |||
// Renvoie le premier bloc de donné disponible | |||
int findAvailableBlock(SD_info *sd) { | |||
char availabilityBuffer[BLOCK_SIZE]; | |||
int offset; | |||
// Lire la carte de disponibilité (à partir du bloc 1) | |||
for (int blockNum = FIRST_DISPONIBILITY_CARD_BLOCK; blockNum < FIRST_DATA_BLOCK; blockNum++) { | |||
readBlock(sd,blockNum, 0, availabilityBuffer, BLOCK_SIZE); | |||
if (blockNum==FIRST_DISPONIBILITY_CARD_BLOCK) { | |||
offset=FIRST_DATA_BLOCK/8; | |||
} else { | |||
offset=0; | |||
} | |||
// Parcourir les octets de la carte de disponibilité | |||
for (int byteIndex = offset; byteIndex < BLOCK_SIZE; byteIndex++) { | |||
char byte = availabilityBuffer[byteIndex]; | |||
// Parcourir les bits de chaque octet | |||
for (int bitIndex = 0; bitIndex < 8; bitIndex++) { | |||
// Vérifier si le bit est à 0 (bloc disponible) | |||
if ((byte & (1 << bitIndex)) == 0) { | |||
// Calculer l'indice du bloc en fonction du bloc et du bit | |||
int blockIndex = byteIndex * 8 + bitIndex; | |||
return blockIndex; | |||
} | |||
} | |||
} | |||
} | |||
// Aucun bloc disponible trouvé | |||
return -1; | |||
} | |||
</syntaxhighlight>La fonction '''FORMAT''' : pour supprimer toutes les données de la carte SD. | |||
Pour tout supprimer il existe une fonction dans Sd2Card.h appelée "erase", il suffit de l'appeler dès que l'utilisateur entre la commande FORMAT pour tout effacer. | |||
==== Programme principal du système de fichier ==== | |||
Après avoir initialisé la communication, pour faire fonctionner le système de fichier, le programme attend en permanence que l'utilisateur entre quelque chose au clavier à travers le port série à l'aide d'un utilitaire comme minicom. Une fois qu'il a entré la commande souhaitée et qu'il appuie sur "entrer", je vais comparer les premiers caractères entrés avec les différentes primitives disponible. Si la commande ne correspond à aucune connue, je renvoie une erreur, sinon on exécute la commande. A l'aide de strncmp, strtok... je suis capable de déterminer les arguments et la fonction voulue.<syntaxhighlight lang="c" line="1" start="496"> | |||
int main(int argc, char *argv[]) { | |||
char user_data; | |||
char current_cmd[CMD_SIZE +1]; | |||
SD_info sd; | |||
init_printf(); | |||
spi_init(); | |||
sd_init(&sd); | |||
printf("Pico ordinateur OK\n\n"); | |||
current_cmd[0] = '\0'; // Initialise la chaîne vide | |||
printf("PicoOrdi>"); | |||
while (1) { | |||
user_data = get_serial(); | |||
if (user_data == '\b'){ | |||
current_cmd[strlen(current_cmd) - 1] = '\0'; // retirer le dernier charactère de la chaine actuelle | |||
printf("%c", user_data); | |||
} | |||
else { | |||
printf("%c", user_data); | |||
strncat(current_cmd, &user_data, 1); // Ajoute le caractère à la fin de la chaîne | |||
if (user_data == '\r') { | |||
char delim = ' '; // espace délimite les arguments | |||
current_cmd[strlen(current_cmd) - 1] = '\0'; // Retire le retour à la ligne | |||
if (strncmp(current_cmd, "FORMAT", 6) == 0) { | |||
erase(&sd, 0, 15000); //supprimer toutes les données | |||
printf("\nToutes les données ont été supprimées\n"); | |||
} | |||
else if (strncmp(current_cmd, "LS", 2) == 0) { // Compare les deux premiers caractères | |||
LS(&sd); //lister les fichiers | |||
} else if (strncmp(current_cmd, "RM", 2) == 0){ | |||
char filenameRm[MAX_FILENAME_LENGTH]; | |||
char * tokenRm = strtok(current_cmd, &delim); | |||
tokenRm = strtok(NULL, &delim); //token prend la valeur du nom de fichier | |||
strcpy(filenameRm, tokenRm); | |||
RM(&sd, filenameRm); // efface le fichier | |||
} else if (strncmp(current_cmd, "TYPE", 4) == 0){ | |||
char filenameType[MAX_FILENAME_LENGTH]; | |||
char dataType[CHUNK_SIZE]; | |||
char * tokenType = strtok(current_cmd, &delim); | |||
tokenType = strtok(NULL, &delim); | |||
strcpy(filenameType, tokenType); | |||
delim = '/'; | |||
tokenType = strtok(NULL, &delim); | |||
strcpy(dataType, tokenType); | |||
TYPE(&sd, filenameType,dataType); //crée le fichier | |||
} else if (strncmp(current_cmd, "MV", 2) == 0){ | |||
char filename_old[MAX_FILENAME_LENGTH]; | |||
char filename_new[MAX_FILENAME_LENGTH]; | |||
char * tokenMv = strtok(current_cmd, &delim); | |||
tokenMv = strtok(NULL, &delim); //prend ancien nom | |||
strcpy(filename_old, tokenMv); | |||
tokenMv = strtok(NULL, &delim); //prend nouveau nom | |||
strcpy(filename_new, tokenMv); | |||
MV(&sd,filename_old, filename_new); // renommer le fichier | |||
} else if (strncmp(current_cmd, "CAT", 3) == 0){ | |||
char filenameCat[MAX_FILENAME_LENGTH]; | |||
char * tokenCat = strtok(current_cmd, &delim); | |||
tokenCat = strtok(NULL, &delim); //prend nouveau nom | |||
strcpy(filenameCat, tokenCat); | |||
CAT(&sd,filenameCat); //afficher le fichier | |||
} else if (strncmp(current_cmd, "CP", 2) == 0){ | |||
char filename_origin[MAX_FILENAME_LENGTH]; | |||
char filename_copy[MAX_FILENAME_LENGTH]; | |||
char * tokenCp = strtok(current_cmd, &delim); | |||
tokenCp = strtok(NULL, &delim); //prend ancien nom | |||
strcpy(filename_origin, tokenCp); | |||
tokenCp = strtok(NULL, &delim); //prend nouveau nom | |||
strcpy(filename_copy, tokenCp); | |||
CP(&sd,filename_origin, filename_copy); // copier le fichier | |||
} | |||
else | |||
{ | |||
printf("\nLa commande entrée %s, n'a pas été reconnue\n", current_cmd); | |||
} | |||
printf("\nPicoOrdi>"); | |||
current_cmd[0] = '\0'; // Réinitialise la chaîne | |||
} | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== <big>Démonstration</big> ==== | |||
Afin d’interagir avec mon pico ordinateur, j'utilise '''minicom''' : | Afin d’interagir avec mon pico ordinateur, j'utilise '''minicom''' : | ||
Ligne 183 : | Ligne 807 : | ||
MV permet de renommer un fichier, je ne fais que changer le nom dans le superbloc: | MV permet de renommer un fichier, je ne fais que changer le nom dans le superbloc: | ||
[[Fichier:Demo MV.png|gauche|vignette|démonstration de MV (rename), la copie de hello.txt s'appelle maintenant nouveau_hello]] | [[Fichier:Demo MV.png|gauche|vignette|démonstration de MV (rename), la copie de hello.txt s'appelle maintenant nouveau_hello]] | ||
<p style="clear: both;" />FORMAT permet de supprimer toutes les données de la carte SD: | |||
[[Fichier:Demo FORMAT.png|gauche|vignette|démonstration de FORMAT, tous les fichiers sont supprimés.]] | |||
<p style="clear: both;" /> | <p style="clear: both;" /> | ||
Ligne 189 : | Ligne 815 : | ||
Le programme principal se trouve dans un fichier appelé '''picofs.c''' et est disponible dans le gitlab du projet dans le dossier appelé "SD". Il suffit de faire un "make upload" pour téléverser le programme dans la carte et d'ouvrir minicom pour interagir avec le système de fichier. | Le programme principal se trouve dans un fichier appelé '''picofs.c''' et est disponible dans le gitlab du projet dans le dossier appelé "SD". Il suffit de faire un "make upload" pour téléverser le programme dans la carte et d'ouvrir minicom pour interagir avec le système de fichier. | ||
== Primitives du bus SPI == | === Primitives du bus SPI === | ||
Afin de pouvoir communiquer avec les cartes filles, la carte mère doit être capable de communiquer via le bus SPI. | |||
== Bilan sur le projet == | |||
Travailler en monôme sur le projet du pico-ordinateur n'a pas été simple, mais cela m'a permis de mettre en pratique ce que j'ai appris et de réaliser un vrai PCB. Le module dédié à KiCAD et la réalisation de PCB a été très théorique l'année dernière, et on n'a pas pu réaliser de carte. Ce projet m'a permis de passer par toutes les étapes et d'aller plus loin puisque j'ai dû faire le design, la réalisation et la programmation. | |||
J'ai perdu beaucoup de temps dans la réalisation de la carte, car j'ai fait des erreurs à la fois sur le schéma, mais aussi sur la soudure des composants par manque d'expérience. Néanmoins, c'était aussi l'une des parties les plus satisfaisantes, car j'ai pu apprendre de mes erreurs et faire une nouvelle carte mère fonctionnelle. | |||
L'ordonnanceur fonctionne, mais je n'ai pas eu le temps de tester autre chose que les LED. La programmation de la carte mère a été un vrai défi, car il a fallu adapter un code qui n'est pas le mien et qui est très différent en termes d'accès à la mémoire, tout en prenant en compte les problèmes liés à la carte SD. | |||
Par manque de temps, je n'ai pas pu faire la programmation du FPGA et je n'ai pas pu gérer la partie communication avec les cartes filles. Cependant, je suis satisfait du travail que j'ai réalisé, car la carte mère finale fonctionne correctement. De plus, j'ai réussi à implémenter toutes les primitives du système de fichiers malgré les limitations de la carte SD et les ai testées en communiquant avec la carte à travers le port série. | |||
== lien gitlab du projet == | == lien gitlab du projet == | ||
https://gitlab.univ-lille.fr/dylan.ling.etu/projet_pico_ordi_b6.git | https://gitlab.univ-lille.fr/dylan.ling.etu/projet_pico_ordi_b6.git |
Version actuelle datée du 2 février 2024 à 16:21
Réalisation du bouclier de test
Parmi les activités disponibles, j'ai réalisé les composants suivants:
Le premier type de carte permet de contrôler une matrice de LED à grâce aux signaux SPI.
Les câbles permettent de faire la liaison entre les connecteurs HE10 afin de connecter le bouclier de test aux deux autres types de carte.
Le deuxième type de carte a une fonction similaire au premier mais permet de contrôler des afficheurs 7 segments avec des signaux SPI.
Par la suite, j'ai reçu directement un bouclier de test pour continuer le projet.
Ordonnanceur / système d'exploitation
Je vais commencer l'ordonnanceur en faisant alterner deux tâches, tout d'abord je vais faire clignoter deux LED. Les deux tâches vont agir sur les LED du bouclier liées à CS2 et CS3. Chacune a une fonction d'initialisation au démarrage et une fonction qui sera exécutée par l'ordonnanceur. Je me suis inspiré du TP que j'avais fait l'année dernière sur l'ordonnancement sur AVR.
void init_task_led_cs3()
{
DDRC |= led_bit_cs3; //mettre PC3 en sortie
PORTC |= 0x00;
}
void task_led_cs3()
{
PORTC ^= led_bit_cs3;
}
void init_task_led_cs2()
{
DDRC |= led_bit_cs2; //mettre PC0 en sortie
PORTC |= 0x00;
}
void task_led_cs2()
{
PORTC ^= led_bit_cs2;
}
Dans la table des processus, j'ai mis les deux tâches task_led_cs2 et task_led_cs3. A chaque fois que le TIMER1 s'active, l'ISR est appelée et change le contexte en reprenant là où il s'était arrêté dans le processus d'avant.
//table des taches
scheduler_t processus[MAX_PROCS]={
{0x500,task_led_cs2},
{0x600,task_led_cs3},
};
/* Fonction qui determine la prochaine tache a executer */
void scheduler()
{
pid=(pid+1)%MAX_PROCS;
}
ISR(TIMER1_COMPA_vect, ISR_NAKED)
{
SAVE_REGISTER();
processus[pid].pile=SP;
scheduler();
SP = processus[pid].pile;
RESTORE_REGISTER();
asm volatile("reti");
}
void init_proc(int i)
{
uint16_t old = SP;
SP = processus[i].pile;
int adresse=(int)processus[i].fonction;
asm volatile("push %0" : : "r" (adresse & 0x00ff) );
asm volatile("push %0" : : "r" ((adresse & 0xff00)>>8) );
SAVE_REGISTER();
processus[i].pile = SP;
SP = old;
}
int main(void)
{
init_task_led_cs2(); //initialiser les taches
init_task_led_cs3();
init_timer(); //initialiser le timer
for(int i=0;i<MAX_PROCS;i++) init_proc(i);
sei();
processus[0].fonction();
while(1)
{
}
return 0;
}
J'ai réussi à faire fonctionner l'ordonnanceur avec les deux tâches de clignotement de LED qui s'alternent entre elles.
Réalisation de la carte mère
Type de carte choisie
J'ai choisi de réaliser la carte mère du pico ordinateur sur laquelle on va venir brancher les cartes filles. Voici les choix que j'ai fait pour la conception de cette carte:
- Alimentation 5V provenant de l'USB (j'utilise un connecteur mini USB B).
- Programmation de la carte avec dfu-programmer grâce à un FTDI (FT232RL) mais connecteur AVR ISP inclus aussi.
- Régulateur de tension pour la puce mémoire et une adaptation des niveaux logiques pour cette dernière.
- ATmega328p traversant.
- Possibilité de brancher 5 cartes filles grâce à 5 connecteurs HE10.
Schematic de la carte mère
Tout d'abord, il a fallu réaliser le schématic de la carte sur KiCAD. Je me suis inspiré du Shield de test et ai inclus les nouveaux composants tels que le FTDI, le connecteur mini USB B ...
J'ai ajouté des condensateurs de découplages pour chaque composant important afin d'éliminer les parasites produits par les pistes. Lors de la réalisation du PCB, il faudra faire en sorte qu'ils soient physiquement proches pour être efficaces. Je me suis renseigné sur internet pour savoir comment connecter le FTDI et le mini USB B.
PCB final réalisé
J'ai réalisé ce PCB en prenant en compte les contraintes liées aux composants.
Soudage de la carte
Quelques semaines après avoir envoyé le .zip contenant les fichiers gerber de KiCAD, j'ai reçu mon PCB imprimé
Par la suite, j'ai commencé à souder les composants, cependant, j'ai rencontré plusieurs problèmes:
- les pistes D+ et D- du FTDI sont inversées
- Les connecteurs HE10 couvrent légèrement certains composants et la LED pour certains
- La carte n'est pas reconnue en périphérique USB si on fait un lsusb.
- J'ai dû refaire une autre carte mère avec un FTDI qui fonctionne car je ne suis pas parvenu à inverser D+ et D- sur la carte originale.
- Après avoir mis un bootloader sur l'ATMEGA328P, je suis parvenu à téléverser des programmes à travers le FTDI en utilisant l'utilitaire avrdude.
- Le programme fait clignoter la LED à la bonne vitesse, cela signifie donc que le cristal 8MHz a été soudé correctement et que la carte fonctionne sans programmeur.
- Par la suite, je vais souder la carte SD et les connecteurs HE10 manquants.
Carte finale soudée
Ci-dessous, la carte mère finale.
J'ai dû retirer le connecteur ICSP car il m'empêchait de souder le dernier connecteur HE10, ce n'est pas grave puisque le FTDI me permet de programmer directement avec avr-dude. J'ai rencontré beaucoup de problèmes en raison de mauvaises soudures et de faux contacts. Cependant, après plusieurs tests et corrections sur la carte, elle est finalement fonctionnelle.
Programmation de la carte mère
Le but de cette partie est de réaliser un micro système de fichier. Pour cela, je dois implémenter les fonctions d'accès à la mémoire de la carte SD ainsi que les primitives qui permettront de manipuler les fichiers.
Ecriture et lecture sur la carte SD
Maintenant que la carte est terminée, il reste la partie accès mémoire pour ensuite écrire les fichiers du Pico ordinateur. J'ai rencontré des problèmes lors de l'écriture et lecture de la carte SD mais après avoir soudé à nouveau l'adaptateur de niveau et vérifié les tensions et les masses, je suis parvenu à écrire "0xac", "0xbc" et "0xcc" sur un bloc de la carte SD et récupérer ces informations pour les afficher sur le port série. Pour la suite du projet, je vais réutiliser ces fonctions de base d'accès à la carte SD pour écrire et lire les fichiers ainsi que les manipuler.
Micro système de fichiers
Fonctions d'accès à la mémoire
J'ai réalisé deux fonctions principales qui seront utilisées par les primitives du système de fichiers : writeBlock et readBlock
La fonction readBlock était la plus simple à implémenter, parmi les fichiers fournis dans le dossier de test de la carte SD, le fichier Sd2Card.c possède une fonction appelée readData qui correspond exactement à ce dont j'ai besoin pour écrire les primitives du système de fichiers cette fonction prend en paramètre:
- Un SD_info* (structure renvoyée par la fonction d'initialisation de la carte SD)
- Un numéro de bloc à lire
- Un offset (décalage en bits dans un bloc)
- Un string (tableau de char) qui contiendra les données lues dans le bloc
- La taille des données à lire
void readBlock(SD_info *sd, unsigned int numBlock, int offset, char *storage, int size) {
readData(sd,numBlock,offset, size, (uint8_t*) storage);
}
La fonction writeBlock est similaire et prend exactement les mêmes arguments mais je ne suis pas parvenu à avoir un offset fonctionnel.
- Un SD_info* (structure renvoyée par la fonction d'initialisation de la carte SD) - Un numéro de bloc (celui à modifier)
- Un offset (décalage en bits dans le bloc)
- Un string (tableau de char) qui contiendra les données à écrire dans le bloc
- La taille des données à écrire
void writeBlock(SD_info *sd, unsigned int numBlock, int offset, char *source, int size) {
char buffer_write[BLOCK_SIZE];
readData(sd, numBlock, 0, size, (uint8_t *)buffer_write);
if(offset + size > 512 )
{
printf("data to write is too big\n");
return;
}
for (int i = size - 1; i >= 0; i--) {
source[i + offset] = source[i];
}
// Ajouter les zéros au début de la chaîne
for (int j = 0; j < offset; j++) {
source[j] = buffer_write[j];
}
writeBlockSD(sd,numBlock, (uint8_t *)source);
}
problèmes rencontrés: Pour la fonction writeBlock, il n'y avait pas d'offset dans la fonction d'écriture de base dans Sd2Card.c, j'ai tenté de reproduire un offset avec des décalages mais cela fonctionne pas en pratique, je reçois des erreurs mémoires qui réinitialisent la carte. J'ai donc dû adapter la façon dont je stocke mes données en mémoire.
Organisation de la mémoire
J'organise la mémoire de la façon suivante:
Les 1024 premiers blocs de la carte SD sont réservés à la description des 64 fichiers du répertoire (superbloc), chaque fichier a une description de 16 blocs (16 x 64 = 1024). La description d'un fichier est la liste des numéros de blocs qui sont associés à ses données.
Les 16 blocs suivants sont réservés à la carte de disponibilité des blocs, chaque bloc fait 512 octets donc on a 16 x 512 x 8 = 65536 bits pour montrer la disponibilité d'un bloc. Chaque bit représente la disponibilité d'un bloc de donnée en mémoire (0 disponible / 1 pas disponible).
Les blocs restants sont les blocs de données qui commencent à partir du bloc 1040 et qui serviront à contenir les données des 64 fichiers.
Cependant, je ne peut pas utiliser d'offset cela limite considérablement l'efficacité de la gestion mémoire. Par exemple pour écrire le nom d'un fichier dans ses 16 blocs de description, je suis obligé d'utiliser un bloc entier (16 caractères donc 16 octets max) car je ne peux écrire que bloc par bloc. La description du fichier ne peut donc que contenir 15 blocs, soit 15 numéros de blocs de données, c'est à dire 15 x 512 = 7680 octets en théorie bien moins que si l'offset était présent. Par manque de temps je me suis contenté d'écrire que sur un bloc de donnée par fichier. Ce qui veut dire que la seule limitation est la taille des fichiers.
Primitives du système de fichiers
Les primitives système permettent de manipuler les fichiers du répertoire.
Je me suis inspiré du code ici: https://wiki-se.plil.fr/mediawiki/index.php/SE4_2022/2023_EC1
Le but est d'adapter le code en prenant en compte le fait que je ne peut pas utiliser d'offset à cause de writeBlock, que nous écrivons dans une carte SD au lieu d'un fichier et que les données lues ne proviennent pas de l'entrée standard mais du port série.
Voici les fonction que j'ai implémenté:
La fonction LS (list): pour lister les fichiers actuels dans le répertoire principal.
void LS(SD_info *sd) {
char buffer[MAX_FILENAME_LENGTH];
int fileCount = 0;
printf("\n");
// // Parcourir les blocs multiples de 16 à partir de 0 jusqu'à MAX_BLOCKS_IN_SUPERBLOCK
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) {
readBlock(sd,blockNum, 0, buffer, MAX_FILENAME_LENGTH);
// Vérifier si le nom de fichier est vide
if (buffer[0] != 0) {
// Afficher le nom du fichier
printf("%s\n", buffer);
fileCount++;
}
}
if (fileCount == 0) {
printf("\nAucun fichier trouvé.\n");
}
}
La fonction TYPE (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 et les données à ajouter.
void TYPE(SD_info *sd, char *filename, char *data) {
size_t sizeFilename = strlen(filename);
char buffer[MAX_FILENAME_LENGTH];
int placeFound = -1;
int index_description_block = 1; // indice du bloc de descrption
int block_counter = 1; //compteur de blocs utilisés
int index_in_descrBlock = 0;
int offset;
int lock_block = 0;
int append_command = -1;
int blockNum_buffer = 0;
if (sizeFilename > MAX_FILENAME_LENGTH) {
printf("\nImpossible de créer le fichier, nom trop long\n");
return;
}
// Parcours des blocs réservés pour la description des fichiers (superbloc)
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK+16; blockNum += 16) {
readBlock(sd,blockNum, 0, buffer, MAX_FILENAME_LENGTH);
// Vérifier si le bloc est vide (pas de nom de fichier)
if (buffer[0] == 0 && lock_block == 0){
//printf("\nbloc potentiel trouvé\n");
blockNum_buffer = blockNum; //au cas où on trouve un bloc libre
lock_block = 1;//on conserve cette place au cas où il faut créer le fichier
}
if (strncmp(buffer,filename,strlen(filename))==0) //si même nom de fichier alors on concatenne les data
{
append_command = 1;
char append_description_buffer[CHUNK_SIZE]; // récupère le numéro du bloc dans la description
char append_buffer[CHUNK_SIZE]; //récupère les données finales à écrire dans le bloc de donnée
readBlock(sd, blockNum+1, 0, append_description_buffer, 2);
int dataBlockNum = reconstituteNumber(append_description_buffer);
readBlock(sd, dataBlockNum, 0, append_buffer, CHUNK_SIZE);
strcat(append_buffer, data); //former les données finales
writeBlock(sd, dataBlockNum, 0, append_buffer, CHUNK_SIZE); // écrire dans bloc de donnée, rien à fiare dans description
break;
}
if (blockNum == MAX_BLOCKS_IN_SUPERBLOCK) { //si le fichier n'existe pas on le crée
if (sizeFilename < MAX_FILENAME_LENGTH) {
sizeFilename += 1; // Ajouter '\0' s'il y a de la place
}
writeBlock(sd,blockNum_buffer, 0, filename, sizeFilename);
placeFound = blockNum_buffer;
//chercher où mettre les données du
int dataBlockNum = findAvailableBlock(sd); // Premier bloc de données à partir du bloc 1040
char dataBlockNumBuffer[CHUNK_SIZE];
readBlock(sd, dataBlockNum, 0, dataBlockNumBuffer, CHUNK_SIZE);
while (dataBlockNumBuffer[0] != 0)
{
dataBlockNum++;
readBlock(sd, dataBlockNum, 0, dataBlockNumBuffer, CHUNK_SIZE);
}
int blockSizeUsed = 0; // Compteur d'octets dans le bloc actuel
char chunkBuffer[CHUNK_SIZE];
size_t bytesRead;
bytesRead = strlen(data);
strcpy(chunkBuffer,data);
// Écrire le chunk dans le bloc de données
writeBlock(sd,dataBlockNum, blockSizeUsed, chunkBuffer, bytesRead);
setBlockAvailability(sd,dataBlockNum, 1);
// Écrire le numéro du bloc actuel dans la description du fichier
char blockNumberBuffer[64];
createNumberBuffer(dataBlockNum,blockNumberBuffer);
blockSizeUsed += bytesRead;
// Si le bloc actuel est plein, passer au bloc suivant OU si c'est le premier tour dans la boucle
// Ecriture du numéro de bloc utilisé dans les blocs de description
if (index_description_block == 0) {
offset = MAX_FILENAME_LENGTH;
} else {
offset = 0;
}
writeBlock(sd,placeFound + index_description_block, offset + index_in_descrBlock*2, blockNumberBuffer, 2);
index_in_descrBlock++;
dataBlockNum = findAvailableBlock(sd); // Passer au bloc suivant
blockSizeUsed = 0; // Réinitialiser la taille utilisée
// Passage au bloc de description suivant
if (block_counter == (BLOCK_SIZE/2-offset)) {
index_description_block++;
index_in_descrBlock=0;
}
// Compteur de nombre de blocs utilisés
block_counter++;
// Vérifie si on a atteint le nombre maximal de blocs par fichier
if (block_counter >= 2040) {
printf("\nLe fichier a atteint sa taille maximale\n");
return;
}
break; // Fichier créé, sortir de la boucle
}
}
if (placeFound != -1) {
printf("\nLe fichier \"%s\" a été créé avec succès.\n", filename);
} else {
if(append_command == 1) printf("\nLes données ont été concaténées dans le fichier %s\n",filename);
else printf("\nPlus de place dans le système de fichier pour créer ce fichier.\n");
}
}
La fonction CAT (read): pour afficher le contenu d'un fichier, les paramètres sont le nom du fichier à lire.
void CAT(SD_info *sd, char *filename) {
char filenameBuffer[MAX_FILENAME_LENGTH];
char byteBuffer[CHUNK_SIZE];
char dataBuffer[CHUNK_SIZE];
// Parcours des blocs réservés pour la description des fichiers (superbloc)
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) {
readBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH);
// Vérifier si le bloc contient le nom du fichier recherché
if (strncmp(filenameBuffer, filename, MAX_FILENAME_LENGTH) == 0) {
// Le nom du fichier a été trouvé
// Parcours les blocs de description
for (int descrBlockNum=1; descrBlockNum<MAX_BLOCKS_PER_FILE_DESCRIPTION; descrBlockNum++) {
// Lecture des octets deux par deux
readBlock(sd,descrBlockNum+blockNum, 0, byteBuffer, 2);
if (byteBuffer[0] == 0 && byteBuffer[1]== 0) return;
// Lire les numéros de blocs associés à ce fichier depuis les blocs de description
int dataBlockNum = reconstituteNumber(byteBuffer);
// Lire et afficher le contenu du bloc de données
readBlock(sd,dataBlockNum, 0, dataBuffer, CHUNK_SIZE);
printf("\n%s\n",dataBuffer);
//}
//}
}
return; // Fichier affiché, sortie de la fonction
}
}
// Si le fichier n'a pas été trouvé
printf("\nLe fichier \"%s\" n'a pas été trouvé.\n", filename);
}
La fonction MV (rename): pour renommer un fichier, les paramètres sont le nom du fichier à renommer et le nouveau nom.
void MV(SD_info *sd, char *old_filename, char *new_filename) {
size_t sizeNew_filename = strlen(new_filename);
char filenameBuffer[MAX_FILENAME_LENGTH];
int fileFound = 0;
// Parcourir les blocs réservés pour la description des fichiers (superbloc)
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 1) {
readBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH);
// Vérifier si le bloc contient le nom du fichier recherché
if (strncmp(filenameBuffer, old_filename, MAX_FILENAME_LENGTH) == 0) {
if (sizeNew_filename < MAX_FILENAME_LENGTH) {
sizeNew_filename+=1;
}
// Écrire le nom du fichier dans l'emplacement
writeBlock(sd,blockNum, 0, new_filename, sizeNew_filename);
fileFound=1;
break; // Nom modifié, sortir de la boucle
}
}
if (fileFound == 1) {
printf("\nLe nom du fichier \"%s\" a été renommé avec succès.\n", old_filename);
} else {
printf("\nLe fichier \"%s\" n'a pas été trouvé.\n", old_filename);
}
}
La fonction CP (copy): pour copier un fichier et mettre ses données dans un nouveau fichier, les paramètres sont le nom du fichier copié et le nom du nouveau fichier.
void CP(SD_info *sd, char *source_filename, char *destination_filename) {
char source_filenameBuffer[MAX_FILENAME_LENGTH];
char destination_filenameBuffer[MAX_FILENAME_LENGTH];
char descriptionBuffer[CHUNK_SIZE];
char numBuffer[64];
int destination_offset;
int source_offset;
int numDataBlock;
int newDataBlock;
// Recherche du fichier source
source_offset = -1;
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) {
readBlock(sd,blockNum, 0, source_filenameBuffer, MAX_FILENAME_LENGTH);
// Vérifier si le bloc contient le nom du fichier source
if (strncmp(source_filenameBuffer, source_filename, MAX_FILENAME_LENGTH) == 0) {
source_offset = blockNum;
break;
}
}
if (source_offset == -1) {
printf("\nLe fichier source \"%s\" n'a pas été trouvé.\n", source_filename);
return;
}
// Recherche d'un emplacement libre pour le fichier destination
destination_offset = -1;
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) {
readBlock(sd,blockNum, 0, destination_filenameBuffer, MAX_FILENAME_LENGTH);
// Vérifier si le bloc est vide (pas de nom de fichier)
if (destination_filenameBuffer[0] == 0) {
destination_offset = blockNum;
break;
}
}
if (destination_offset == -1) {
printf("\nPlus de place dans le système de fichier pour créer la copie de \"%s\".\n", source_filename);
return;
}
// Copie du nom de la copie dans le bloc de description
writeBlock(sd,destination_offset, 0, destination_filename, strlen(destination_filename));
// Lecture des numéros de bloc dans les blocs de description
readBlock(sd,source_offset+1, 0, numBuffer, 2);
numDataBlock = reconstituteNumber(numBuffer);
//si aucune donnée à copier
if (numDataBlock == 0) {
printf("\nLa copie de \"%s\" sous le nom \"%s\" a été créée avec succès.\n", source_filename, destination_filename);
return;
}
// On stocke le bloc de données associé au fichier source dans descriptionBuffer
readBlock(sd,numDataBlock, 0, descriptionBuffer, CHUNK_SIZE);
// Trouver un nouveau bloc de données disponible
newDataBlock = findAvailableBlock(sd);
char dataBlockNumBuffer[CHUNK_SIZE];
readBlock(sd, newDataBlock, 0, dataBlockNumBuffer, CHUNK_SIZE);
while (dataBlockNumBuffer[0] != 0)
{
newDataBlock++;
readBlock(sd, newDataBlock, 0, dataBlockNumBuffer, CHUNK_SIZE);
}
// Mise à jour de la carte de disponibilités
setBlockAvailability(sd,newDataBlock, 1);
// Ecriture du numéro de bloc dans la description du fichier
createNumberBuffer(newDataBlock,numBuffer);
writeBlock(sd,destination_offset+1, 0, numBuffer, 2);
// Ecriture du bloc de données dans le premier bloc disponible
writeBlock(sd,newDataBlock, 0, descriptionBuffer, CHUNK_SIZE);
printf("\nLa copie de \"%s\" sous le nom \"%s\" a été créée avec succès.\n", source_filename, destination_filename);
}
La fonction RM (remove): pour effacer un fichier et ses données, les paramètres sont le nom du fichier à supprimer.
void RM(SD_info *sd, char *filename) {
int fileFound = -1;
int offset;
char fileBuffer[CHUNK_SIZE];
// Parcourir les blocs réservés pour la description des fichiers (superbloc)
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 16) {
char filenameBuffer[MAX_FILENAME_LENGTH];
readBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH);
// Vérifier si le bloc contient le nom du fichier recherché
if (strncmp(filenameBuffer, filename, MAX_FILENAME_LENGTH) == 0) {
// Effacer le nom du fichier dans le superbloc
memset(filenameBuffer, 0, MAX_FILENAME_LENGTH);
writeBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH);
fileFound = 1;
offset = blockNum;
break;
}
}
// Fin de fonction si fichier inexistant
if (fileFound == -1) {
printf("\nLe fichier \"%s\" n'a pas été trouvé.\n", filename);
return;
}
for (int blockNum = offset+1; blockNum < offset + 16; blockNum++) {
int chunkSize = 0;
int chunkStart = 0;
readBlock(sd,blockNum, 0, fileBuffer, chunkSize);
int blockNumData = reconstituteNumber(fileBuffer);
if (blockNumData == 0) {
writeBlock(sd,blockNum, chunkStart, fileBuffer, chunkSize);
printf("\nLe fichier \"%s\" a été supprimé avec succès.\n", filename);
return; // Sortir des boucles
}
else setBlockAvailability(sd,blockNumData, 0); // Marquer le bloc comme disponible
writeBlock(sd,blockNum, chunkStart, fileBuffer, chunkSize);
//}
}
printf("\nLe fichier \"%s\" a été supprimé avec succès.\n", filename);
}
// Fonction qui modifie le nom du fichier
void MV(SD_info *sd, char *old_filename, char *new_filename) {
size_t sizeNew_filename = strlen(new_filename);
char filenameBuffer[MAX_FILENAME_LENGTH];
int fileFound = 0;
// Parcourir les blocs réservés pour la description des fichiers (superbloc)
for (int blockNum = 0; blockNum < MAX_BLOCKS_IN_SUPERBLOCK; blockNum += 1) {
readBlock(sd,blockNum, 0, filenameBuffer, MAX_FILENAME_LENGTH);
// Vérifier si le bloc contient le nom du fichier recherché
if (strncmp(filenameBuffer, old_filename, MAX_FILENAME_LENGTH) == 0) {
if (sizeNew_filename < MAX_FILENAME_LENGTH) {
sizeNew_filename+=1;
}
// Écrire le nom du fichier dans l'emplacement
writeBlock(sd,blockNum, 0, new_filename, sizeNew_filename);
fileFound=1;
break; // Nom modifié, sortir de la boucle
}
}
if (fileFound == 1) {
printf("\nLe nom du fichier \"%s\" a été renommé avec succès.\n", old_filename);
} else {
printf("\nLe fichier \"%s\" n'a pas été trouvé.\n", old_filename);
}
}
// Renvoie le premier bloc de donné disponible
int findAvailableBlock(SD_info *sd) {
char availabilityBuffer[BLOCK_SIZE];
int offset;
// Lire la carte de disponibilité (à partir du bloc 1)
for (int blockNum = FIRST_DISPONIBILITY_CARD_BLOCK; blockNum < FIRST_DATA_BLOCK; blockNum++) {
readBlock(sd,blockNum, 0, availabilityBuffer, BLOCK_SIZE);
if (blockNum==FIRST_DISPONIBILITY_CARD_BLOCK) {
offset=FIRST_DATA_BLOCK/8;
} else {
offset=0;
}
// Parcourir les octets de la carte de disponibilité
for (int byteIndex = offset; byteIndex < BLOCK_SIZE; byteIndex++) {
char byte = availabilityBuffer[byteIndex];
// Parcourir les bits de chaque octet
for (int bitIndex = 0; bitIndex < 8; bitIndex++) {
// Vérifier si le bit est à 0 (bloc disponible)
if ((byte & (1 << bitIndex)) == 0) {
// Calculer l'indice du bloc en fonction du bloc et du bit
int blockIndex = byteIndex * 8 + bitIndex;
return blockIndex;
}
}
}
}
// Aucun bloc disponible trouvé
return -1;
}
La fonction FORMAT : pour supprimer toutes les données de la carte SD.
Pour tout supprimer il existe une fonction dans Sd2Card.h appelée "erase", il suffit de l'appeler dès que l'utilisateur entre la commande FORMAT pour tout effacer.
Programme principal du système de fichier
Après avoir initialisé la communication, pour faire fonctionner le système de fichier, le programme attend en permanence que l'utilisateur entre quelque chose au clavier à travers le port série à l'aide d'un utilitaire comme minicom. Une fois qu'il a entré la commande souhaitée et qu'il appuie sur "entrer", je vais comparer les premiers caractères entrés avec les différentes primitives disponible. Si la commande ne correspond à aucune connue, je renvoie une erreur, sinon on exécute la commande. A l'aide de strncmp, strtok... je suis capable de déterminer les arguments et la fonction voulue.
int main(int argc, char *argv[]) {
char user_data;
char current_cmd[CMD_SIZE +1];
SD_info sd;
init_printf();
spi_init();
sd_init(&sd);
printf("Pico ordinateur OK\n\n");
current_cmd[0] = '\0'; // Initialise la chaîne vide
printf("PicoOrdi>");
while (1) {
user_data = get_serial();
if (user_data == '\b'){
current_cmd[strlen(current_cmd) - 1] = '\0'; // retirer le dernier charactère de la chaine actuelle
printf("%c", user_data);
}
else {
printf("%c", user_data);
strncat(current_cmd, &user_data, 1); // Ajoute le caractère à la fin de la chaîne
if (user_data == '\r') {
char delim = ' '; // espace délimite les arguments
current_cmd[strlen(current_cmd) - 1] = '\0'; // Retire le retour à la ligne
if (strncmp(current_cmd, "FORMAT", 6) == 0) {
erase(&sd, 0, 15000); //supprimer toutes les données
printf("\nToutes les données ont été supprimées\n");
}
else if (strncmp(current_cmd, "LS", 2) == 0) { // Compare les deux premiers caractères
LS(&sd); //lister les fichiers
} else if (strncmp(current_cmd, "RM", 2) == 0){
char filenameRm[MAX_FILENAME_LENGTH];
char * tokenRm = strtok(current_cmd, &delim);
tokenRm = strtok(NULL, &delim); //token prend la valeur du nom de fichier
strcpy(filenameRm, tokenRm);
RM(&sd, filenameRm); // efface le fichier
} else if (strncmp(current_cmd, "TYPE", 4) == 0){
char filenameType[MAX_FILENAME_LENGTH];
char dataType[CHUNK_SIZE];
char * tokenType = strtok(current_cmd, &delim);
tokenType = strtok(NULL, &delim);
strcpy(filenameType, tokenType);
delim = '/';
tokenType = strtok(NULL, &delim);
strcpy(dataType, tokenType);
TYPE(&sd, filenameType,dataType); //crée le fichier
} else if (strncmp(current_cmd, "MV", 2) == 0){
char filename_old[MAX_FILENAME_LENGTH];
char filename_new[MAX_FILENAME_LENGTH];
char * tokenMv = strtok(current_cmd, &delim);
tokenMv = strtok(NULL, &delim); //prend ancien nom
strcpy(filename_old, tokenMv);
tokenMv = strtok(NULL, &delim); //prend nouveau nom
strcpy(filename_new, tokenMv);
MV(&sd,filename_old, filename_new); // renommer le fichier
} else if (strncmp(current_cmd, "CAT", 3) == 0){
char filenameCat[MAX_FILENAME_LENGTH];
char * tokenCat = strtok(current_cmd, &delim);
tokenCat = strtok(NULL, &delim); //prend nouveau nom
strcpy(filenameCat, tokenCat);
CAT(&sd,filenameCat); //afficher le fichier
} else if (strncmp(current_cmd, "CP", 2) == 0){
char filename_origin[MAX_FILENAME_LENGTH];
char filename_copy[MAX_FILENAME_LENGTH];
char * tokenCp = strtok(current_cmd, &delim);
tokenCp = strtok(NULL, &delim); //prend ancien nom
strcpy(filename_origin, tokenCp);
tokenCp = strtok(NULL, &delim); //prend nouveau nom
strcpy(filename_copy, tokenCp);
CP(&sd,filename_origin, filename_copy); // copier le fichier
}
else
{
printf("\nLa commande entrée %s, n'a pas été reconnue\n", current_cmd);
}
printf("\nPicoOrdi>");
current_cmd[0] = '\0'; // Réinitialise la chaîne
}
}
}
}
Démonstration
Afin d’interagir avec mon pico ordinateur, j'utilise minicom :
Commande pour ouvrir minicom :
minicom -b 9600 -D /dev/ttyUSB0
Au démarrage de l'ordinateur, on a l'interface suivante:
TYPE permet de créer un fichier avec des données s'il n'existe pas, sinon il ajoute les données en fin de fichier s'il existe déjà, CAT permet d'afficher un fichier existant:
CP permet de copier les données d'un fichier et d'en créer une copie à un emplacement libre du superbloc:
LS permet de lister les fichiers du répertoire en parcourant les noms des fichiers du superbloc:
RM permet de supprimer un fichier, ce dernier est supprimé du superbloc et les données qui lui sont associées sont effacées:
MV permet de renommer un fichier, je ne fais que changer le nom dans le superbloc:
FORMAT permet de supprimer toutes les données de la carte SD:
Ainsi le système de fichier est bien fonctionnel, les primitives de bases fonctionnent, mais la taille des fichiers est limitée. Le programme principal se trouve dans un fichier appelé picofs.c et est disponible dans le gitlab du projet dans le dossier appelé "SD". Il suffit de faire un "make upload" pour téléverser le programme dans la carte et d'ouvrir minicom pour interagir avec le système de fichier.
Primitives du bus SPI
Afin de pouvoir communiquer avec les cartes filles, la carte mère doit être capable de communiquer via le bus SPI.
Bilan sur le projet
Travailler en monôme sur le projet du pico-ordinateur n'a pas été simple, mais cela m'a permis de mettre en pratique ce que j'ai appris et de réaliser un vrai PCB. Le module dédié à KiCAD et la réalisation de PCB a été très théorique l'année dernière, et on n'a pas pu réaliser de carte. Ce projet m'a permis de passer par toutes les étapes et d'aller plus loin puisque j'ai dû faire le design, la réalisation et la programmation.
J'ai perdu beaucoup de temps dans la réalisation de la carte, car j'ai fait des erreurs à la fois sur le schéma, mais aussi sur la soudure des composants par manque d'expérience. Néanmoins, c'était aussi l'une des parties les plus satisfaisantes, car j'ai pu apprendre de mes erreurs et faire une nouvelle carte mère fonctionnelle.
L'ordonnanceur fonctionne, mais je n'ai pas eu le temps de tester autre chose que les LED. La programmation de la carte mère a été un vrai défi, car il a fallu adapter un code qui n'est pas le mien et qui est très différent en termes d'accès à la mémoire, tout en prenant en compte les problèmes liés à la carte SD.
Par manque de temps, je n'ai pas pu faire la programmation du FPGA et je n'ai pas pu gérer la partie communication avec les cartes filles. Cependant, je suis satisfait du travail que j'ai réalisé, car la carte mère finale fonctionne correctement. De plus, j'ai réussi à implémenter toutes les primitives du système de fichiers malgré les limitations de la carte SD et les ai testées en communiquant avec la carte à travers le port série.
lien gitlab du projet
https://gitlab.univ-lille.fr/dylan.ling.etu/projet_pico_ordi_b6.git