SE4Binome2023-2
Introduction
Dans ce wiki, vous retrouverez notre travail sur le projet PICO-ordinateur. Une grosse partie portera sur la programmation de notre carte puis une autre sur la partie électronique/réalisation de la PCB.
Ordonnanceur/ Système d'exploitation
Ordonnanceur
Durant les dernières séances, nous avons avancé sur la réalisation de l'ordonnanceur. En effet nous avons actuellement un version d'ordonnanceur qui fait marcher trois tâche de la carte simultanément. Nous avons adapté le delay des tâches pour avoir une alternance de 0.9s à l'état haut et 0.9s à l'état bas, et ce grâce à un Delay de 0.3s dans chaque tâche (0.3*3=0.9).
Lors de la Réalisation nous avons eu quelques problème, notamment dû a l'initialisation des adresses des tâches qui se faisait après l'initialisation des tâches elles même, rendant le code aléatoire.
Nous avons ensuite réaliser la fonction sleep_ms qui permet de changer l'état des tâches afin d'endormir les processus à l'appel de la fonction et de les réveiller dans l'ISR. Cette fonction sert notamment à mieux gérer le delay car précédemment nous devions définir un delay de 0.3 secondes sur trois tâches pour avoir ~1 secondes à l'état haut et ~1 secondes à l'état bas. Désormais nous pouvons mettre 1 secondes de delay et cela ne se cumulera plus entre les tâches.
void sleep_ms(uint16_t nb_ms){
cli();
Tache[id_tache].delay = nb_ms;
Tache[id_tache].etat = ENDORMI;
Tache[id_tache].raison = DELAY;
TCNT1 = 0;
sei();
TIMER1_COMPA_vect();
}
//Nous avons également dû changer la fonction ordonnanceur pour permettre aux tâches de s'eveiller
void ordonnanceur(){
for(int i=0; i<NOMBRE_TACHE; i++){
if(Tache[i].etat == ENDORMI && Tache[i].raison == DELAY){
uint16_t ms=INTERRUPT_MS;
if(TCNT1 != 0){
ms=((10*TCNT1*INTERRUPT_MS)/OCR1A)/10;
TCNT1 = 0;
}
Tache[i].delay-=ms;
if(Tache[i].delay <= 0){
Tache[i].etat = EVEILLER;
}
}
}
do{
id_tache ++;
if (id_tache >= NOMBRE_TACHE){
id_tache = 0;
}
}while(Tache[id_tache].etat==ENDORMI);
}
Nous avons rencontré quelques problèmes lors de la réalisation. Le problème principal étant la désynchronisation des leds après quelques battement, problème résolu en changeant les pointeurs de pile du setup.
il faudra ensuite réaliser un sémaphore permettant d'allouer et de désallouer la ressource pour que les cartes filles ne parle pas en même temps.
Nous avons réussi à ordonnancer l'écriture sur le port série et une led qui clignotte. Nous avons utilisé les sémaphores pour nous assurer que la resource est disponible.
La prochaine étape est d'ordonnancer l'UART et le SPI
Soudure et test du SHIELD Arduino Uno
Lors de la première séance, nous avons soudé tous les composants du shield. Sur l'un des connecteurs, il manque une led (led abimée lors de la soudure). Ce n'est pas grave car les leds sont uniquement des indicateurs pour savoir quelle carte a son ship select d'activé. Nous avons aussi réalisé un programme grâce à l'IDE arduino pour tester de façon rapide les différentes leds du shield.
void setup(){
pinMode(1, OUTPUT);
pinMode(4, OUTPUT);
pinMode(7, OUTPUT);
pinMode(A3, OUTPUT);
pinMode(A0, OUTPUT); // Led non soudée
}
void loop() {
digitalWrite(1, HIGH);
digitalWrite(4, HIGH);
digitalWrite(7, HIGH);
digitalWrite(A0, HIGH);
digitalWrite(A3, HIGH);
delay(1000);
digitalWrite(1, LOW);
digitalWrite(4, LOW);
digitalWrite(7, LOW);
digitalWrite(A0, LOW);
digitalWrite(A3, LOW);
}
Pour finir, nous avons testé le lecteur micro-SD grâce au programme Fichier:TestSD avrgcc.zip et nous l'avons exectué grâce à la commande make upload
.
Système d'exploitation
Afin de simplifier le processus d'activation/désactivation des divers esclaves, nous avons conçu une structure nommée cs_t, constituée d'un PORT et d'un PIN. En créant un tableau de cette structure, nous sommes en mesure d'associer facilement un CS à son PORT et son PIN. Cette approche élimine la nécessité de vérifier constamment sur quel PIN et quel PORT l'esclave est connecté, ce qui non seulement optimise le temps, mais renforce également la fiabilité du programme, étant donné que le tableau est initialisé une fois, reste inchangé, et demeure correct.
Système de fichiers
Avant de réfléchir à notre système de fichier, nous avons du tester notre mémoire. Pour cela nous avons pris le code disponible sur le site wiki_peip avec le projet clé USB. Nous avons pris plusieurs heures pour comprendre comment elle fonctionnait. Pour voir si l'écriture de la mémoire et la lecture étaient correct nous avons réalisé un test qui affiche '1' via minicom si le test s'est bien déroulé.
Voici notre code :
int main() {
spi_init();
serialInit();
unsigned char writeData[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE};
// Adresse à laquelle écrire et lire
int address = 1;
// Écriture des données dans la mémoire
AT45DB641E_write_buffer(CS[1],writeData,sizeof(writeData),SEQ_START | SEQ_STOP); // Ecriture dans le buffer
AT45DB641E_write_page(CS[1],address); //Envoi des données du buffer à l'adresse choisit
// Lecture des données depuis la même adresse
unsigned char readData[sizeof(writeData)];
AT45DB641E_read_page(CS[1],address, readData, sizeof(readData), SEQ_START | SEQ_STOP);
// Vérification des données lues avec celles écrites
int i;
while (1) {
for (i = 0; i < sizeof(writeData); i++) {
if (readData[i] == writeData[i]) {
serialWrite('1');
} else {
serialWrite('0');
}
_delay_ms(1000);
}
}
return 0;
}
Comme indiqué sur le sujet, nous devons séparer nos 8Mo de stockage en un SU Block ainsi que 64 fichiers différents. Nous avons pris la liberté de modifier le cahier des charges en modifiant la taille des fichiers à 256 blocks ainsi qu'inclure une structure info_fichier_t qui renseigne le nom du fichier et son adresse de début dans le SU Block.
Voici un plan détaillé de notre architecture :
Pour des raisons de temps, nous avons décidé d'utiliser un projet de système de fichier déjà existant. Nous avons donc pris le projet de Amine Sellali qui avait du en réaliser un pour son EC l'année dernière. Cependant le projet à dû être grandement modifié car les accés mémoire n'étaient absolument pas le même par rapport à notre projet. Nous avons alors modifié toutes les fonctions pour qu'elles intéragissent avec le port série via minicom et quelles répondent à notre structures définie au dessus.
Nous avons eu du fil à retordre sur pas mal de fonctions. Tout d'abords car nous nous somme basé sur un cycle en V pour la réalisation du système de fichier (on code et à la fin on corrige). Evidemment c'est à ne pas faire et nous avions eu pléthore erreurs lors de la première compilation.
Les différentes fonctions
mem_init
Afin de travailler sur une mémoire "vierge", nous devons supprimer les données du SU bloc. Pour cela nous avons créé une fonction pour l'initialisée :
void mem_init()
{
AT45DB641E_page_size(CS[1], 256);//Appliquer 256 octets par pages.
char empty_SU_bloc[BYTES_PER_BLOCK];
for (int i = 0; i < BYTES_PER_BLOCK; i++)
empty_SU_bloc[i] = 0x00;//Création d'un bloc avec que des 0
for (int i = 0; i < MAP_SIZE; i++) { //Charger la map
writeBlock(CS[1], i, empty_SU_bloc, sizeof(empty_SU_bloc));
}
for (int i = MAP_SIZE; i < OFFSET; i++) { // Charger la liste de nom
writeBlock(CS[1], i, empty_SU_bloc, sizeof(empty_SU_bloc));
}
}
LS
Nous avons réalisé différentes fonctions pour le système de fichiers. Tout d'abords, nous avons crée la fonction LS qui permet d'afficher les différents fichiers qui sont dans la mémoire.
void LS()
{ // V1 fonctionnelle
char data_block[BLOCK_SIZE];
int j = 0;
char position[5];
for (int i = 0; i < 4; i++) { // Parcours des 4 blocs du SU pour les noms de fichiers
readBlock(CS[1], ADD_NAME + i, data_block, sizeof(data_block));
char name[MAX_FILENAME_LENGTH + 1];
for (j = 0; j < 16; j++) { // Manipulation des données sur un bloc
int debut_nom = j * MAX_FILENAME_LENGTH;
strncpy((char*)name, data_block + debut_nom, MAX_FILENAME_LENGTH); // Récupération du nom du fichier
if (!(verifierZeros((char*)name))) { // Si le fichier existe alors l'imprimer
name[16] = '\0';
serialWrite(name);
serialWrite("\t\tposition ");
sprintf(position, "%d", j + i*16 + 1);
serialWrite(position);
serialWrite(" sur 64 \r\n");
}
}
}
}
Pour énumerer les fichiers dans la mémoire, nous parcours les blocs où il y a la liste des noms de fichiers (blocs 16 à 19). Etant donnée que la mémoire est initialisée avec uniquement des 0x00 pour le SU bloc, il suffit de parcourir la liste de nom et d'imprimer les caractères sauf les 0x00. On peut aussi imprimer la position dans la liste. Cette fonction marche parfaitement et nous l'avons testé en chargant manuellement des fichiers dans le liste de nom.
En mettant dans la mémoire 4 fichiers "pifou.txt", on obtient ce résultat :
TYPE
void TYPE(char* name, char* data, int len)
{
char nom_fichier[MAX_FILENAME_LENGTH];
// Etend le nom du fichier name avec des 0x00 et ce jusqu'à ce qu'il ai une longueur = MAX_FILENAME_LENGTH
strcpy(nom_fichier,extension_0x00(name, MAX_FILENAME_LENGTH));
// Renvoie l'adresse de l'emplacement du fichier s'il existe
int addr_fichier = find_file(nom_fichier);
int cmpt = 0;
if (addr_fichier < 0) { // Fichier non existant : fichier à creer
int addr_libre = find_file(nom_vide); // Trouve un emplacement vide
if (addr_libre < 0) {
serialWrite("Pas de place libre pour la création d'un fichier\r\n");
} else {
RENAME(nom_vide, nom_fichier); // Remplace le nom vide au bon emplacement par le nom du fichier
int nb_block = (len / BLOCK_SIZE) + 1;
if (nb_block > BLOCKS_PER_FILE) {
serialWrite("Longueur du fichier trop importante\r\n");
}
for (int i = addr_libre * BLOCKS_PER_FILE; i < addr_libre * BLOCKS_PER_FILE + nb_block; i++) {
char data_block[BLOCK_SIZE];
for (int j = 0; j < BLOCK_SIZE; j++) {
data_block[j] = data[cmpt * BLOCK_SIZE + j];
}
writeBlock(CS[1], i, data_block, sizeof(data_block));
cmpt++;
}
}
}
}
TYPE remplace la fonction standard touch et écris ensuite dans le fichier créer, elle permet alors de créer un fichier et de l'allouer dans un espace disponible de la mémoire. Puis elle nomme ce fichier en fonction du paramètre name et enfin écrit le contenu de data dans la mémoire. Cet espace mémoire devient alors inutilisable lors de la prochaine utilisation de TYPE et ce jusqu'à l'utilisation de REMOVE sur ce même fichier. Pour l'instant la fonction ne marche pas car elle utilise la fonction RENAME qui elle même ne fonctionne pas. Nous ne pouvons donc pas savoir si cette fonction comporte des bugs.
RENAME
int RENAME(char* nom_fichier_initial_1, char* nom_fichier_final_1)
{ // V1
char data_block[BLOCK_SIZE];
char nom_fichier_init[MAX_FILENAME_LENGTH];
char nom_fichier_final[MAX_FILENAME_LENGTH];
if (sizeof(nom_fichier_initial_1) != MAX_FILENAME_LENGTH) { //Mettre des 0x00 si la longueur n'est pas de MAX_FILENAME_LENGTH
strcpy(nom_fichier_init, extension_0x00(nom_fichier_initial_1,MAX_FILENAME_LENGTH));
}
else
strcpy(nom_fichier_init,nom_fichier_initial_1);
if (sizeof(nom_fichier_final_1) != MAX_FILENAME_LENGTH){ //Mettre des 0x00 si la longueur n'est pas de MAX_FILENAME_LENGTH
strcpy(nom_fichier_final, extension_0x00(nom_fichier_final_1, MAX_FILENAME_LENGTH));
}
else
strcpy(nom_fichier_final,nom_fichier_final_1);
int add_fichier = find_file(nom_fichier_init);
if (add_fichier != -1) {
int add_block = add_fichier / 16; // Adresse de block ou le nom est stocké
int num_block = add_block * 16;
int num_fichier_block = add_fichier - num_block; // Numéro de fichier dans le block de nom
readBlock(CS[1], add_block, data_block, sizeof(data_block)); // Lecture du block ou le nom du fichier est stocké
for (int i = 0; i < MAX_FILENAME_LENGTH; i++) {
data_block[num_fichier_block + i] = nom_fichier_final[i]; // Changement du nom.
}
writeBlock(CS[1], add_block, data_block, sizeof(data_block)); // Réecriture du nouveau block.
serialWrite("\r\nRENAME DONE\r\n");
return 1;
} else {
serialWrite("\r\nFichier introuvable, milles excuces\r\n"); // Pas de fichier trouvé.
return -1;
}
}
La fonction RENAME est essentielle pour le fonctionnement de notre système de fichier. Elle permet de changer le nom d'un fichier. On cherche dans un premier temps le nom du fichier initial dans la liste de noms. Si on le trouve, on change le nom initial par le final. Cette fonction n'est pas fonctionnelle malheureusement. En réalité, peut importe la chaine de caractère entrée en paramètre, nous obtenons sizeof(nom_fichier_initial_1) = 8. Cela pose problème et nous ne savons pas comment résoudre ce problème.
REMOVE
int REMOVE(char* nom) // Manque map
{
char data_block[BLOCK_SIZE];
char nom_fichier[MAX_FILENAME_LENGTH];
strcpy(nom_fichier,extension_0x00(nom, MAX_FILENAME_LENGTH));
int j = 0;
int add = find_file(nom);
for (int i = 0; i < 4; i++) { // Parcours des 4 blocs du SU pour les noms de fichiers
readBlock(CS[1], ADD_NAME + i, data_block, sizeof(data_block));
char name[MAX_FILENAME_LENGTH];
for (j = 0; j < 16; j++) { // Manipulation des données sur un bloc
int debut_nom = j * MAX_FILENAME_LENGTH;
strncpy(name, data_block + debut_nom, MAX_FILENAME_LENGTH);
if (strcmp(nom_fichier, name) == 0) {
strncpy(data_block + debut_nom,nom_vide, MAX_FILENAME_LENGTH); // On remplace le nom du fichier par des OxOO
writeBlock(CS[1],ADD_NAME + i, data_block, sizeof(data_block));
for (int j = add * BLOCKS_PER_FILE ; j < (add+1) * BLOCK_PER_FILE ; j++) //Suppresion des données de la mémoire
writeBlock(CS[1],j,bloc_vide,sizeof(bloc_vide));
// Modification de la carte bit à bit (a faire)
return 0;
}
j++;
}
}
serialWrite("Fichier introuvable, milles excuces\r\n"); // Pas de fichier trouvé
return -1;
}
Le principe de cette fonction et de chercher un fichier si il existe remplacer son nom avec des 0x00 et mettre des 0x00 dans les données du fichier pour etre sûr qu'il ne reste plus de données.
CAT
int CAT(char* nom)
{ // V1
serialInit();
char data_block[BLOCK_SIZE];
char nom_fichier[MAX_FILENAME_LENGTH];
strcpy(nom_fichier, extension_0x00(nom, MAX_FILENAME_LENGTH));
int add_fichier = find_file(nom_fichier);
if (add_fichier > -1) {
for (int i = 0; i < BLOCKS_PER_FILE; i++) {
readBlock(CS[1], OFFSET + (BYTES_PER_BLOCK * add_fichier) + i, data_block, sizeof(data_block));
serialWrite(data_block);
}
return 1;
} else {
serialWrite("Fichier introuvable, milles excuces\r\n"); // Pas de fichier trouvé
return -1;
}
}
Cette fonction accéde aux données du fichier et affiche des blocs un par un.
COPY
int COPY(char* nom_fichier_source_1, char* nom_fichier_destination_1)
{ // V1
serialInit();
char nom_fichier_source[MAX_FILENAME_LENGTH];
char nom_fichier_destination[MAX_FILENAME_LENGTH];
strcpy(nom_fichier_source, extension_0x00(nom_fichier_source_1, MAX_FILENAME_LENGTH));
strcpy(nom_fichier_destination, extension_0x00(nom_fichier_destination_1, MAX_FILENAME_LENGTH));
serialInit();
char data_block[BLOCK_SIZE];
int add_fichier = find_file(nom_fichier_source);
if (add_fichier > -1) {
int add_block_dest = find_file(nom_vide); // Chercher un emplacement libre dans la liste de nom
if (add_block_dest > -1) {
RENAME(nom_vide, nom_fichier_destination); // Ecrire le nom de la copie du fichier
for (int i = 0; i < BLOCKS_PER_FILE; i++) { // Ecrire les données dans les blocks du fichier
readBlock(CS[1], add_fichier + OFFSET + i, data_block, sizeof(data_block));
writeBlock(CS[1], add_block_dest + OFFSET + i, data_block, sizeof(data_block));
}
char map[BLOCK_SIZE];
readBlock(CS[1], add_block_dest, map, sizeof(map));
for (int i = 0; i < BLOCKS_PER_FILE; i++) { // Actualiser la map
}
serialWrite("Copie effectuée \r\n");
return 1;
}
} else {
serialWrite("Fichier introuvable, milles excuces\r\n"); // Pas de fichier trouvé.
return -1;
}
}
Cette fonction copie les données d'un fichier initiale en modifiant son nom.
QUOTADISK
void QUOTADISK() // V1
{
serialInit();
int cmpt = 0;
char map[BLOCK_SIZE];
for (int i = 0; i < MAP_SIZE; i++) {
readBlock(CS[1], i, map, sizeof(map));
for (int j = 0; j < BLOCK_SIZE; j++) {
long int count = 0;
for (int z = 0; z < 8; z++) {
if ((map[j] & (1 << z)) != 0) {
count++;
}
}
cmpt = cmpt + count;
}
}
int persent = (int)((float)cmpt / 32768.0);
char result[5];
sprintf(result, "%d", persent);
serialWrite("Voici le QUOTADISK : ");
serialWrite(result);
serialWrite("% de votre disque utilisé\r\n");
}
}
C'est une fonction bonus qui nous tient à coeur car elle n'est plus disponible sur les machines de l'école. Cette fonction lit la map et incrémente un compteur à chaque 1 pour chaque mot en hexadecimal. Puis on fait le poursantage et on l'affiche.
Carte FPGA/VHDL
Carte électronique numérique
SPI
Prérequis SPI
Pour faire fonctionner les différentes cartes sparkfun, nous devons tout d'abords comprendre le fonctionnement du SPI. Les cartes ne possèdent pas de MISO car elles ne font que recevoir des données du maître. Pour initialiser le SPI sur un Atmega328p, il faut initialiser les différents registres du SPI.
void spi_init()
{
// Initialisation des ports en mode SPI (sorties et entrées)
DDRB |= (1 << PB2);
DDRB |= (1 << MOSI);
DDRB &= ~(1 << MISO);
DDRB |= (1 << SCK);
// Initialisation des différents CS(1-6)
DDRB |= (1 << CS1);
DDRC |= (1 << CS2);
DDRC |= (1 << CS3);
DDRD |= (1 << CS4);
DDRD |= (1 << CS5);
DDRB |= (1 << CS6);
// Désactiver les différents esclaves
PORTB |= (1 << CS1);
PORTC |= (1 << CS2);
PORTC |= (1 << CS3);
PORTD |= (1 << CS4);
PORTD |= (1 << CS5);
PORTB |= (1 << CS6);
//Configuration des paramètres du mode de com SPI
SPCR = (1 << SPE)|(1 << MSTR)|(1<<SPR1);
}
Ensuite, le SPI discutera avec les différents esclaves en mettant les différents CS au niveau logique bas. C'est uniquement à ce moment que l'esclave se réveillera et lira les données sur le MOSI. Une fois l'esclave réveillé, nous devons envoyer des données sur le MOSI. Pour cela, nous allons devoir créer une fonction qui placera les données sur le MOSI.
// Envoie un octet sur le bus SPI
void SPI_transmit(uint8_t data)
{
// Début de la transmission
SPDR = data;
// Attendre que la transmission ne finisse
while (!(SPSR & (1 << SPIF)))
;
}
Une fois le code primitif du SPI effectué, nous devons comprendre comment les différentes cartes fonctionnent.
Afficheur 7 segments
Toutes les informations pour comprendre comment l'écran fonctionne sont disponibles sur ce site : [1]
Pour faire fonctionner l'écran, nous devons lui envoyer différentes commandes qui permetterons différentes actions. Il y a nottement des commandes pour selectionner la luminosité ou encore éteindre l'écran.
Si on veut mettre un certain nombre sur l'écran, nous pouvons découper avec nombre avec ses milliers,centaines,dixaines et unités. Puis en selectionnant le 7 segments correctement, nous pouvons envoyer la donnée.
- Pour selectionner le bon 7 segments, nous devons agir sur le "Digit X control".
- Pour agir sur les points du 7 segments, nous devons agir sur le "Decimal control" puis envoyer une donnée pour choisir quel(s) point(s) allumer
Pour afficher le chiffre 9 et un point, nous aurions un code comme celui ci :
Code | Résultat |
---|---|
int main(){
spi_init();
while(1){
set_activer(&PORTC,CS2);
SPI_trans(0x7E); // Segment des unités
SPI_trans(0b1101111); // Envoyer la donnée '9'
SPI_trans(0x77); // Contrôle des points
SPI_trans(0b0010000); // Envoyer le(s) point(s) à allumer ':'
set_desactiver(&PORTC, CS2);
_delay_ms(500);
nettoyerEcran(&PORTC, CS2); // Permet de faire clognoter
_delay_ms(500);
}
return 0;
}
|
On peut facilement afficher n'importe quel nombre avec une fonction dédiée et faire un compteur par exemple (cf. fonction afficher_nombre).
Matrice de Leds
Toutes les informations sur le fonctionnement de la matrice de leds se trouvent sur cette page : [2]
Il est important de noter que la vitesse maximale de SCK est de 125KHz. Il faut donc régler les paramètres du SPI.
Pour cela, on va devoir modifier notre fonction spi_init(). On doit diviser la fréquence par 128 ( 16 MHz / 125 KHz ).
void spi_init()
{
// Initialisation des ports en mode SPI (sorties et entrées)
DDRB |= (1 << PB2);
DDRB |= (1 << MOSI);
DDRB &= ~(1 << MISO);
DDRB |= (1 << SCK);
// Initialisation des différents CS(1-6)
DDRD |= (1 << CS1);
DDRC |= (1 << CS2);
DDRC |= (1 << CS3);
DDRD |= (1 << CS4);
DDRD |= (1 << CS5);
DDRB |= (1 << CS6);
// Desactiver les différents esclaves
PORTD |= (1 << CS1);
PORTC |= (1 << CS2);
PORTC |= (1 << CS3);
PORTD |= (1 << CS4);
PORTD |= (1 << CS5);
PORTB |= (1 << CS6);
// Configuration des paramètres du mode de com SPI avec une fréquence de 125KHz
SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1) | (1 <<SPR0);
}
Nous avons mis SPR0 et SPR1 à 1 comme expliqué dans la datasheet de l'AVR.
Pour faire fonctionner la matrice de leds, il faut comprendre son fonctionnement. Le principe est très simple. Pour alumer toutes leds leds d'une certaine couleur, il suffira d'envoyer un certain code 64 fois pour que toutes les leds s'allument de cette couleurs. Les leds sont rangés par lignes puis collones.
Pour que le code fonctionne, il faut envoyer le code 0x26 pour réinitialiser l'indice de l'image.
On peut regarder les différents codes couleurs et effectuer un code pour afficher du orange par exemple :
Pour le orange, il faut donc envoyer le code 0xFC.
Code | Résultat |
---|---|
int main(){
set_activer(&PORTC,CS2); //Esclave à l'écoute
SPI_trans(0x26); // Commande pour réinitialiser l'incide d'image
for (int i = 0; i < 64 ; i++){
SPI_trans(0x1C); // Transferer 64 octets pour mettre les leds en Vert
}
set_desactiver(&PORTC,CS2); //Esclave s'endort
return 0;
}
|
UART
Pour pouvoir utiliser la liaison UART grâce au FTDI via l'USB, nous devons initialiser la liaison :
#define BaudRate 9600
#define MYUBRR (F_CPU / 16 / BaudRate) - 1
void serialInit(void)
{
// Serial Initialization
/*Set baud rate 9600 */
UBRR0H = (unsigned char)((MYUBRR) >> 8);
UBRR0L = (unsigned char)MYUBRR;
/* Enable receiver and transmitter */
UCSR0B = (1 << RXEN0) | (1 << TXEN0);
/* Frame format: 8data, No parity, 1stop bit */
UCSR0C = (3 << UCSZ00);
}
Pour envoyer des données via UART, nous devons définir les fonctions suivantes :
unsigned char serialCheckTxReady(void)
{
return (UCSR0A & _BV(UDRE0)); // nonzero if transmit register is ready to receive new data.
}
void serialsend(unsigned char DataOut)//Envoi d'un caractère
{
while (serialCheckTxReady() == 0) // while NOT ready to transmit
{
;
;
}
UDR0 = DataOut;
}
void serialWrite(char* Data)//Envoi une chaine de caractère
{
for (int i = 0; i < strlen(Data); i++) {
serialsend((unsigned char)Data[i]);
}
}
Pour visualiser les données envoyées sur UART, nous utilisons minicom avec la commande suivante : minicom -D /dev/ttyUSB0 -b 9600
De plus grace au FTDI, nous pouvons visualiser l'activation de RX et TX grâce aux leds de la carte.
Type de carte choisie
Nous avons choisi la MOTHERBOARD
Design choisi
La MB est programmable via USB grace au FT232RL qui est un convertisseur USB série mais elle peut être programmée directement grâce à l'AVR ISP. La carte peut être alimentée en 5V grâce au port USB mais aussi en 12V avec le port Jack. Le 12V est converti en 5V pour les différents périphériques. On a pensé à mettre un connecteur pour un ventilateur (pourquoi pas) sur le 12V. Il y a un comparateur qui connecté avec un mosfet permet de choisir entre le une alimentation via l'USB ou directement par le port Jack. Il y a 2 differents convertisseurs : 12V-5V et 5V-3,3V. Le 3,3V est utilisé par la mémoire flash qui communique avec l'Atmega328p via le buffer.
Durant la deuxième séance nous avons avancé sur la réalisation du Schématique de la carte mère. Nous avons également soudée la carte qui permet de passer du connecteur HE10 à un afficheur 7 segment.
Lors de la troisième séance nous avons commencer le routage de la Carte mère.
Lors de la 6ième scéance, la carte mère nous avons finis le routage de la carte mère. Il y a eu des modifications et des ajouts pour le bon fonctionnement de la carte. La carte est prête à la fabrication.
Lors de la séance du 7 Novembre 2023, nous avons commencé la soudure de la carte. Les composants indispensables au premiers tests ont été soudés à savoir l'AVR, le cristal, des capacités, des résistances ainsi qu'une led.
Lors de la séance du 14 Novembre 2023, nous avons testé l'atmega328p grâce à un Arduino as ISP programmer.
Après le teste de programmation, nous avons continué de souder la carte à savoir le bridge USB - SERIE et des leds de test sur le RX, TX et l'alimentation 5V.
La carte est détectée par l'ordinateur avec la commande lsusb
. De plus toutes les leds s'allument ce qui est bon signe.
Par la suite il a fallu mettre un bootloader dans l'atmega328p pour la programmation via USB. Nous avons procédé au téléversement grâce à un arduino directement avec l'IDE. Par la suite nous avons essayé de mettre nous programme directement par USB. Malheureusement, nous avions une erreur stipulant avrdude: stk500_recv(): programmer is not responding
. Cette erreur était due à une capacité manquante sur entre les broches DTR et RESET. Une fois soudée, nous avons réessayé de programmer l'AVR par USB. Et maintenant, ÇA FONCTIONNE !!!
Il ne manque plus que de souder les autres leds, résistances et IC pour pouvoir programmer notre carte mère comme nous le faisons actuellement avec l'arduino et le shield.
Lors de la séance du 20 Novembre 2023, nous avons finis les soudures de la carte mère (en réalité il manque la partie 12V mais elle n'est pas indispensable). Nous allons pouvoir faire les tests de l'ordonnanceur sur la carte mère au lieux de l'arduino.
Nous avons du faire des modifications sur la carte mère pour qu'elle puisse fonctionner avec les autres cartes en SPI (plus de détails dans la section Améliorations / Problèmes rencontrés). Une fois les modifications effectuées, la carte fonctionne parfaitement (pas de test de la mémoire réalisé).
Voici un aperçue de la carte mère une fois modifiée avec tout de soudé :
Vue de face | Vue de dos |
---|---|
Nous avons également soudé toute la partie 12V (comparateur + mosfet p et autres composants passifs). La régulation se fait parfaitement et dès que nous appliquons du 12V, le 5V de l'usb est déconnecté. Pour le test de cette partie, nous avions peur de cramer notre carte. Nous avons donc fait un test avec une alimentation de laboratoire avec une limitation en courant.
Axe d'amélioration / Problèmes rencontrés
Bien que notre carte mère fonctionne de manière optimale, des améliorations sont possibles, notamment en intégrant une capacité de 100nF entre le DTR du FTDI et le reset de l'AVR. Cette modification aurait éliminé la nécessité de souder manuellement la capacité, assurant ainsi un résultat plus propre. De plus, la perforation de la carte pour l'ajout de patins clipables aurait constitué une autre amélioration pratique.
En revanche, nous avons découvert que nous avions inversé le MISO et le MOSI sur les connecteurs HE10. Cela nous a profondément freiné pour tester les périphériques en SPI. Une modification manuelle a été effectué en coupant et inversant les deux pistes en sortie de l'AVR ISP. Toutes les modifications indispensables sont disponibles sur le git.
Concernant les problèmes rencontrés. Nous avons eu pas mal de problème à différents niveau mais nous avons toujours essayé de trouver une solution et d'approfondir nos connaissances.
Il nous est arrivé de ne pas pouvoir téléverser de code sur la carte. En réalité, la carte mère n'était pas détectée. Le problème était qu'une trop grosse capacité de découplage (47µF à la place de 10µF) était installée. Quand nous débranchions et rebranchions rapidement, la capacité n'était pas totalement déchargée et donc avait une influence lors du démarrage (capacité débranchée, fonctionne bien sans). Aussi, quand la matrice de led est connectée à la carte, dès que nous branchons la CM à un SUB, aucun périphèrique n'est détecté. Nous pensons que la matrice led demande suffisement de puissance au démarrage pour que le FTDI ne démarre pas correctement. Nous ne rencontrons pas ce problème quand nous branchons uniquement le 7 segments.
Ce que nous avons pensé de ce projet
Simon : J'ai aimé travailler sur ce projet. Les objectifs été atteignables et le sujet interréssant. Je pense qu'avec plus d'heure et si nous avions passé moins de temps sur l'ordonnanceur, nous aurions pu avoir un système de fichier fonctionnel. Comme nous avons de nombreux bug, il est difficile de savoir si nos algorithmes sont corrects. C'est un peu frustrant quand on approche du but !
Amaury : Ce projet fut fort enrichissant et nous a permis de voir ce qu'était le travail en sous équipes. Les tâches étaient correctement réparties, mais un manque de visibilité sur la tâche finale (1er volet de ce projet) nous as certainement posé problème et nous a fait perdre pas mal de temps. Mise à part cela, je trouve dommage que nous n'ayons pas eu le temps de tester le PICO ordinateur en entier mais compte tenu du temps que nous avions, je suis tout de même satisfait du travail accomplie.
Annexes
Lien Git: [3]