SE4Binome2023-1

De projets-se.plil.fr
Révision datée du 2 février 2024 à 08:54 par Rex (discussion | contributions) (→‎Conclusion)
(diff) ← Version précédente | Voir la version actuelle (diff) | Version suivante → (diff)
Aller à la navigation Aller à la recherche

Ordonnanceur

Soudure et Test du SHIELD

Au cours des 2 premières séances nous avons soudé et testé notre Shield.

Shield soudé et monté sur Arduino

Sur l'un des connecteurs, la led est abîmée dû à une soudure excessive. Sachant que les leds sont uniquement utilisées en tant qu'indicateurs pour savoir quelle carte fille à le contrôle et que nous n'en utiliserons pas plus de 3 pour tester nos programmes, cela ne va pas avoir d'influence négative. Le potentiomètre permet de régler le niveau de luminosité de l'écran et le connecteur HE10 permettra de communiquer avec l'écran. Les problèmes rencontrés sont les suivants : Au départ, la carte ne fonctionnait pas, le souci étant le quartz mal soudé, c'est à l'aide d'un pistolet à air chaud que celui-ci a été ressoudé. Un autre problème était le reset de la carte fille qui ne fonctionnait pas non plus, une ressoudure classique a permis de le faire fonctionner.

Code Image
#include <avr/io.h>

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

int main(){
  init_led();
  while(1)
    {
	PORTD=0x92;
	PORTC=0x09;
    }
  return 0;
}
Test des Leds du Shield

Puis nous avons aussi soudé l'adaptateur HE10 pour l'utilisation de la matrice de leds.

Adaptateur HE10 pour Matrice de leds

Programmation de l'Ordonnanceur

Nous commençons maintenant l'ordonnanceur, tout d'abord nous créons la structure de nos taches.

Code
struct TacheInfo{
	void (*depart)(void);
	uint16_t SPointeur;
	int etat;
};

Puis nous ajoutons les 2 premières taches demandées qui sont de faire clignoter 2 leds différentes à des timings différents.

Code
void TacheA(){
    while(1)
    {
        PORTC ^= 0x08;
	_delay_ms(300);
    }
}

void TacheB(){
    while(1)
    {
        PORTD ^= 0x80;
	_delay_ms(500);
    }
}

struct TacheInfo Taches[2]={
  {TacheA,0x0600,0},
  {TacheB,0x0700,0}
};

On s'occupe maintenant de l'ordonnanceur, ainsi que de l'ISR.

Code
void ordonnanceur ()
{	
	PORTD ^= 0x02;
	courant++;
	if (courant == NBR_TACHE) courant = 0;
}

ISR(TIMER1_COMPA_vect,ISR_NAKED)
{
	/* Sauvegarde du contexte de la tâche interrompue */
	SAVE_REGISTER();
	Taches[courant].SPointeur = SP;

	/* Appel à l'ordonnanceur */
	ordonnanceur();

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

Nous avons décidé de faire clignoter une LED (PORTD ^= 0x02) à chaque utilisation de la fonction ordonnanceur() dans l'ISR.

La variable courant indique la Tache qui est actuellement en cours.

A chaque changement de tache, l'ISR se déclenche enregistre la tache en cours (les registres ainsi que le Stack Pointer), appel l'ordonnanceur, puis change "charge" la tache suivante.

On n'oublie pas maintenant de générer l'interruption toute les 20ms, pour cela on choisit un prescaler de 1024 et un nombre de ticks de 312.

Code
#define PRESCALER 1024
#define NB_TICK  312
#define CTC1  WGM12

void init_timer(){
TCCR1A = 0;  // No output pin connected, no PWM mode enabled
TCCR1B = 1<<CTC1; // No input pin used, clear timer counter on compare match
#if (PRESCALER==1024)
 TCCR1B |= (1<<CS12 | 1<<CS10);
#endif
OCR1A = NB_TICK;
TCNT1 = 0;
TIMSK1 = (1<<OCIE1A); // No overflow mode enabled, no input interrupt, output compare interrupt
}

Ainsi que d'initialiser les taches pour assurer le bon fonctionnement de notre ordonnanceur.

Code
void init_taches(int pid)
{
	int save=SP;
	SP=Taches[pid].SPointeur;
	uint16_t adresse=(uint16_t)Taches[pid].depart;
	asm volatile ("push %0 \n\t" : : "r" (adresse & 0x00ff));
	asm volatile ("push %0 \n\t" : : "r" ((adresse & 0xff00) >> 8));
	SAVE_REGISTER();
	Taches[pid].SPointeur=SP;
	SP=save;
}

On peut maintenant tester notre ordonnanceur.

Code Video Test
#define NBR_TACHE 2
int main(void){
	init_ports();
	init_timer();
	init_taches(1);
	sei();
	SP = Taches[courant].SPointeur;
	Taches[courant].depart();
	return 0;
}

Les 2 leds clignotent bien avec le bon timing, et la led témoin de l'ordonnanceur clignotent aussi, ce qui indique que l'ISR est bien actif.

Nous pouvons donc maintenant passer à la gestion des "sleeps".

En effet, l'utilisation de _delay_ms dans chaque tache n'est pas la manière la plus optimale de procéder. Et même cela nous bloquera par la suite.

Nous ajoute donc à la structure de nos taches, une structure InfoEndormi, qui, comme son nom l'indique nous donne des informations si la tache est endormie. A savoir la raison mais aussi des données comme par exemple le temps que cette tache doit passer endormie.

Puis afin de remplacer _delay_ms, nous créons une fonction Attente qui endort une tache pour un temps donné.

Code Code
#define REVEILLE 0
#define ENDORMI 1
#define RAISON_DELAY 0

struct InfoEndormi{
    int raison;
    uint16_t donnee;
}sleep_t;


struct TacheInfo{
    void (*depart)(void); 
    uint16_t SPointeur;
    int etat;
    struct InfoEndormi endormi;
};
void Attente(uint16_t temps_ms){
    cli();
    Taches[Tache_courante].etat = ENDORMI;
    Taches[Tache_courante].endormi.raison = RAISON_DELAY;
    Taches[Tache_courante].endormi.donnee = temps_ms;
    TCNT1 = 0;
    sei();
}

Voilà maintenant nos taches, il est important de créer une TacheZombie qui comme son nom l'indique est toujours réveillée.

Code
void TacheZombie() //Tache toujours réveillé
{
	while(1)
	{
		_delay_ms(1.5*PERIODE_INTERUPTION); //Delay légèrement supèrieur à la periode pour éviter tout soucis
	}
}

void TacheA()
{
	while(1)
	{
		PORTC ^= 0x08;
		Attente(300);
    }
}

void TacheB()
{
	while(1)
	{
		PORTD ^= 0x80;
		Attente(500);
    }
}

Il est important de modifier notre ordonnanceur afin que celui-ci puisse recalculer le nouveau temps de sleep d'une tache et si ce temps devient inférieur ou ègal à 0 alors l'ordonnanceur réveille la tache.

Code
void ordonnanceur ()
{
	PORTD ^= 0x02;
	for(int i = 0; i < NBR_TACHE; i++)
	{
		if(Taches[i].etat == ENDORMI && Taches[Tache_courante].endormi.raison == RAISON_DELAY)
		{
			uint16_t diff_temps = PERIODE_INTERUPTION;
			if(TCNT1 != 0)
			{
				//Calcul précis de la différence de temps (pas obligatoirement 20 ms)
				diff_temps = ((TCNT1*PERIODE_INTERUPTION*10)/OCR1A)/10;
				TCNT1 = 0;
			}
			Taches[i].endormi.donnee  -= diff_temps;
            
			if(Taches[i].endormi.donnee <= 0)
			{
				Taches[i].etat = REVEILLE;
			}
		}
	}
    
	do
	{
		Tache_courante++;
		if (Tache_courante == NBR_TACHE) Tache_courante = 0;
    }
	while(Taches[Tache_courante].etat == ENDORMI);
}

Maintenant que cela est fait, nous pouvons ajouter 2 nouvelles taches qui sont la lecture et l'écriture sur le port série.

Pour cela, nous allons procéder avec des sémaphores. En effet, il ne peut y avoir que 1 seule tache en écriture ou en lecture, il faut donc que si plusieurs taches essaient de prendre la ressource, cette ressource soit donné au premier arrivé et que les autres taches en attendant patientent.

Code
void semaphore_e(int ACTION){
	if (ACTION == PRENDRE)
	{
		while(1)
		{
			cli();
			if (semaphore_ecriture > 0) //Si ressource dispo 
			{
				semaphore_ecriture = 0;
				sei();
				break;
        		} else
			{
				// La ressource est occupée, on endort la tache
				Taches[Tache_courante].etat = ENDORMI;
				Taches[Tache_courante].endormi.raison = RAISON_ECRITURE;
				TCNT1=0;
				TIMER1_COMPA_vect();
			}
			sei();
			while(semaphore_ecriture==0);
		}
			
	}
	else if (ACTION == LAISSER)
	{
		cli();
		semaphore_ecriture = 1;
		sei();
	}
}

On fait la même chose pour la lecture.

On n'oublie pas d'initialiser le port série avec les bons paramètres, créer les taches, puis on test.

Code Image
void init_serie(long int vitesse){
	UBRR0=FREQ_CPU/(((unsigned long int)vitesse)<<4)-1; 
        // configure la vitesse
	UCSR0B=(1<<TXEN0 | 1<<RXEN0);   // autorise l'envoi et la réception
	UCSR0C=(1<<UCSZ01 | 1<<UCSZ00); // 8 bits et 1 bit de stop
	UCSR0A &= ~(1 << U2X0);         // double vitesse désactivée
}
Test de la lecture et de l'écriture sur le port série.(On écrit 9 caractères puis le shield nous renvoie les mêmes 9)

On passe à la matrice de leds.

On souhaite pouvoir lire sur le port série et afficher ce qu'il s'y écrit. Nous procéderons uniquement avec les chiffres.

Code Image
void Tache_Matrice()
{
    int val = -1;
    DDRB |= (1<<2);
    PORTB |= (1<<2);
    _delay_ms(500);
    matrice_init(1);
    while(1)
    {
        char c = chaine[0];
        if(c == '0' && val != 0)
        {
            Afficher_0();
            val = 0;
        }
        if(c == '1' && val != 1)
        {
            Afficher_1();
            val = 1;
        }
        if(c == '2' && val != 2)
        {
            Afficher_2();
            val = 2;
        }
        if(c == '3' && val != 3)
        {
            Afficher_3();
            val = 3;
        }
        if(c == '4' && val != 4)
        {
            Afficher_4();
            val = 4;
        }
        if(c == '5' && val != 5)
        {
            Afficher_5();
            val = 5;
        }
        if(c == '6' && val != 6)
        {
            Afficher_6();
            val = 6;
        }
        if(c == '7' && val != 7)
        {
            Afficher_7();
            val = 7;
        }
        if(c == '8' && val != 8)
        {
            Afficher_8();
            val = 8;
        }
        if(c == '9' && val != 9)
        {
            Afficher_9();
            val = 9;
        }
    }
}
Test de la matrice de leds (On écrit un chiffre sur le port série puis la matrice affiche le même chiffre)

Pour finir, on fait la même chose pour l'afficheur 7 segments.

Code Image
void Tache_7Seg()
{
    DDRB |= (1<<2);       
    PORTB |= (1<<2);
    _delay_ms(500);
    seg_init();
    while(1)
    {
        char c = chaine[0];
        seg_affiche(c);
    }
}
Test de l'afficheur 7 segments. (On écrit un chiffre sur le port série puis l'afficheur affiche cette valeur.)

L'ordonnanceur est maintenant plus ou moins complet, toutes les taches fonctionnent individuellement.

Malheureusement nous ne pouvons pas essayer le tout ensemble car l'afficheur 7 segments et la matrice de leds communique tous les 2 via SPI. Il faudrait donc rajouter des sémaphores par exemple pour savoir, si la ressource est utilisée ou non. Par manque de temps, nous ne l'avons donc pas fait.

Carte électronique numérique

Conception et Création

Nous avons choisi la Carte Fille : Ecran LCD

Pendant la 3ème séance nous avons fini le schematic de la carte.

Schematic de la Carte Fille : Ecran LCD

Puis nous avons Routé la carte et envoyé à la Fabrication.

Routage de la Carte Fille : Ecran LCD
Carte non soudée Carte soudée Video Test
Carte non soudée
Carte soudée

Programmation de la carte LCD

Tout d'abord, nous avons voulu tester le bon fonctionnement de notre carte mais surtout du bon contrôle de l'écran HD44780 grâce à l'IDE Arduino.

Code Image
const int rs = 2, en = 18, d4 = 14, d5 = 15, d6 = 16, d7 = 17;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

void setup() {
  // set up the LCD's number of columns and rows:
  pinMode(9,OUTPUT);
  digitalWrite(9,LOW);
  lcd.begin(16, 2);
  // Print a message to the LCD.
  lcd.print("Je compte");
}

void loop() {
  // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  lcd.setCursor(0, 1);
  // print the number of seconds since reset:
  lcd.print(millis() / 1000);
}
Test LCD via IDE Arduino

Maintenant que cela fonctionne, nous pouvons commencer à écrire notre librairie pour faire fonctionner notre carte.

Librairie HD44780

Nous avons commencé par reprendre la librairie d'ancien élève, merci à eux pour leur travail.

Dans notre cas, pour l'utilisation la plus simple nous avons besoin de plusieurs fonctions. La première écrit un caractère sur l'écran, la deuxième copie une ligne et la dernière efface une ligne.

HD44780_CopyLine1in0 :

Permet de copier la seconde ligne sur la première. Pour ce faire, on se place au début de la première ligne, on écrit le contenu du tableau de caractère, et on le vide ensuite (EmptyLine1).

Code
void HD44780_CopyLine1in0(char line1[], int nbrows, int nbcols, int ind_line1) {
    for (int i = 0; i < ind_line1; i++) {
        int address_ligne0 = HD44780_XY2Adrr(nbrows, nbcols, 0, i);
        HD44780_WriteCommand(LCD_ADDRSET | address_ligne0);
        HD44780_WriteData(line1[i],0);
    }
    HD44780_EmptyLine1(line1,nbcols);
}


HD44780_EraseLine  :

Permet d'effacer une ligne. Pour ce faire on se place au début de la ligne voulue, et on écrit une suite de caractères vide.

Code
void HD44780_EraseLine(int nbrows, int nbcols, int line) {
    int address_line1;
    char espace = ' ';
    for (int i = 0; i < nbcols; i++) {
    	address_line1 = HD44780_XY2Adrr(nbrows, nbcols, line, i);
    	HD44780_WriteCommand(LCD_ADDRSET | address_line1);
	    HD44780_WriteData(espace,0);
    }
}


HD44780_WriteChar  :

Permet d'écrire un caractère sur l'écran. Chaque caractère écrit sur la seconde ligne est placé dans un tableau de caractère.

Lorsqu'on arrive au bout d'une ligne : si nous sommes à la première ligne (Ligne 0), on passe à la seconde (Ligne 1) et on se replace au début des colonnes. Si nous sommes à la seconde ligne, on efface les deux lignes et on copie la seconde ligne sur la première ligne de l'écran, et on se replace au début de la seconde.

Code
void HD44780_WriteChar(char car, char line1[], int* ind_line1, int nbrows, int nbcols, int* row, int* col) {
    if ((*col) == nbcols) {
        (*col) = 0;
        if ((*row) <= 1) (*row)++;
        if ((*row) > 1) {
            (*row) = 1;
            HD44780_EraseLine(nbrows, nbcols,0);
            HD44780_EraseLine(nbrows, nbcols,1);
            HD44780_CopyLine1in0(line1, nbrows, nbcols,*ind_line1);
        }
        (*ind_line1) = 0;
    }

    int address = HD44780_XY2Adrr(nbrows, nbcols, *row, *col);
    HD44780_WriteCommand(LCD_ADDRSET | address);
    HD44780_WriteData(car,1);
    (*col)++;
    if ((*row) == 1) 
    {
        line1[*ind_line1] = car;
        (*ind_line1)++;
    }
}
Video Test

Super, ça fonctionne !

Désormais, nous voulons que notre écran affiche ce qu'il reçoit via le bus SPI.

Communication SPI

Pour cela il nous faut initialiser le maitre (un arduino UNO) et l'esclave (notre carte LCD).

Puis, que le maître transmette des données et que l'esclave les reçoive.

Code Maitre Code Esclave
void SPI_init()
{
    DDRB |= (1 << SPI_MOSI) | (1 << SPI_SCK) | (1 << SPI_SS);
    DDRB &= ~(1 << SPI_MISO);
    SPCR |= (1 << MSTR);
    SPCR |= (1 << SPE) | (1<<SPR0) | (1<<SPR1);
}

void SPI_MasterTransmit(char cData)
{
SPDR = cData;
while(!(SPSR & (1<<SPIF)));
}
void SPI_init()
{
    DDRB |= (1 << SPI_MISO);
    DDRB &= ~((1 << SPI_MOSI) | (1 << SPI_SCK) | (1 << SPI_SS));
    SPCR |= (1 << SPE); 
}

char SPI_SlaveReceive(void)
{
    /* Attendre la fin de la réception */
    while (!(SPSR & (1 << SPIF)));
    char receivedData = SPDR;
    return receivedData;
}

Faisons un petit test en envoyant des 'A' suivis de 'B' et enfin un 'X' pour terminer.

Code Test du Maitre Video Test
int main()
{
    ...
    ...
    while (x<17)
    {
      SPI_MasterTransmit('A');
      _delay_ms(100);
      SPI_MasterTransmit('B');
      x++;
      PORTD ^= (1<<7);
      _delay_ms(100);
    }
    SPI_MasterTransmit('X');
    while(1)
    {
    }
}

Notre test fonctionne mais cela n'a pas été sans peine. Nous avons rencontré énormément de problème sur la communication SPI. La communication était clairement douteuse et aléatoire.

Problèmes rencontrés

Plusieurs pistes ont été considérées pour comprendre le problèmes qu'on avait avec le SPI, était-ce l'arduino UNO? Notre connecteur? Le shield? Notre carte? Au départ, par souci de praticité, nous voulions utiliser l'arduino UNO qui disposait de fonctionnalités de test simple avant de passer au shield, mais à chaque essai, le SPI ne fonctionnait pas correctement bien qu'on pouvait observer à l'oscilloscope que le message était bien envoyé à notre carte.

Finalement, nous sommes directement passé à la communication SPI avec notre shield, qui cette fois-ci, permettait d'avoir un résultat satisfaisant. Le résultat n'était pas parfait car on remarquait que la position des fils déterminait si la communication allait fonctionner, il fallait donc trouver la position qui permettait cette communication. Pour régler ce problème, nous avons déterminé la fréquence de travail du SPI utilisé par le shield et notre carte, pour vérifier qu'il n'y ait pas de problème de synchronisation ou que la fréquence utilisée soit bien adaptée. Pour ce faire nous avons observé à l'oscilloscope les différentes divisions de fréquences possibles en vérifiant en même temps que la communication SPI fonctionne bien. Les résultats sont surprenants, car peu importe la fréquence de travail, aucun problème n'a été détécté.

Communication SPI par interruption

Il nous faut désormais communiquer par SPI par interruption car cela offre certains avantages par rapport à la communication SPI standard sans interruption, en particulier en matière d'efficacité et de gestion du temps. En effet, l'utilisation d'interruptions permet au microcontrôleur de gérer d'autres tâches pendant les transferts SPI. Plutôt que d'attendre de manière synchrone la fin d'une transaction SPI, le microcontrôleur peut être informé via une interruption lorsque les données sont prêtes à être lues ou écrites. Cela permet un multitâche plus efficace. Ce qui va nous être très pratique sachant que le projet comporte plusieurs cartes filles.

Pour cela il nous faut coté maitre et esclave activé le mode SPI Interrupt lors de l'initialisation du SPI, on rajoute donc SPCR |= (1<<SPIE); lors à l'initialisation.

Ensuite on génère l'interruption côté esclave à la fin de l'initialisation et on configure notre ISR.

Le maitre à plusieurs types d'envoi possible :

0x00 : Demande le type de la carte.

0x01 : Active la communication avec la carte LCD.

0xff  : Met fin à la communication.

Voici comment l'ISR lui fonctionne, si je reçois 0x00 je renvoie on type, 0x01 je passe en mode EnReception.

En mode EnReception : lorsque je reçois un octet je l'ajoute à un buffer, lorsque je recois 0xff je désactive le mode en Reception.

Dans mon main, une fois que mon mode EnRecption est désactivé mais que mon indice du buffer et supérieur à 0 alors je peux traiter mes octets.

Code ISR Code Main
ISR(SPI_STC_vect) {
    uint8_t  data = SPDR;
    if(EnReception == 1)
        switch (data){
	   case 0xff:
		EnReception=0;
		break;
	   default :
		buffer[IndBuffer++]=data;
	}
    else
        switch (data){
            case 0x00: 
		sendType();
		break;
           case 0x01:
		EnReception=1;
		break;
	}
}
while (1) {
	if (EnReception == 0 && IndBuffer>0)
	{
		for(int i=0; i < IndBuffer; i++)
		{
			"Fonction de traitement"
		}
		IndBuffer=0;
	}
    }

Gestion des caractères spéciaux

Nous avons décidé de gérer 4 caractères spéciaux différents : \n , \t, \b, \r

Pour cela on crée la fonction HD44780_Traitement qui sera notre fonction principale.

Si on reçoit un caractère classique alors on l'affiche grâce à HD44780_WriteChar précédemment créé.

Sinon il sagit d'un caractère spécial et alors on le traite dans un switch case :

\n

Si on est sur la première ligne, on se place au début de la seconde.

Si on est sur la seconde ligne, on efface les deux lignes et on copie la seconde ligne sur la première ligne de l'écran, et on se replace au début de la seconde.

Code \n Video Test
        case '\n': //Retour à la ligne
            switch (*row)
            {
                case 0:
                    (*col) = 0;
                    (*row) = 1;
                    break;
                case 1:
                    *col = 0;
                    HD44780_EraseLine(nbrows, nbcols,0);
                    HD44780_EraseLine(nbrows, nbcols,1);
                    HD44780_CopyLine1in0(line1, nbrows, nbcols,*ind_line1); 
                    *ind_line1 = 0;
                    break;    
            }
            break;

\r

On se replace au début de la ligne sur laquelle on est actuellement en train d'écrire.

Code \r Video Test
case '\r': // Retour chariot
            *col = 0;
            (*ind_line1) = 0;
            HD44780_EmptyLine1(line1,nbcols);
            break;

\r

On efface le caractère précédent, ajuste la position du curseur, et passe à la ligne supérieure si nécessaire.

Code \b Video Test
case '\b': // Retour arrière
            switch (*row)
            {
                case 0:
                    if (*col > 0)
                    {
                        (*col)--;//Retourne sur le caractère précédent
                        char espace = ' ';
                        int address = HD44780_XY2Adrr(nbrows, nbcols, *row, *col);
                        HD44780_WriteCommand(LCD_ADDRSET | address);
                        HD44780_WriteData(espace, 1);//"L'efface"
                        _delay_ms(500);

                    }
                    break;
                case 1:
                    if (*col > 0)
                    {
                      (*col)--;
                      (*ind_line1) --;
                    }
                    else 
                    {
                      (*row) = 0;
                      (*col) = 15;
                    }
                    char espace = ' ';
                    int address = HD44780_XY2Adrr(nbrows, nbcols, *row, *col);
                    HD44780_WriteCommand(LCD_ADDRSET | address);
                    HD44780_WriteData(espace, 1);
                    break;
            }
            break;

\t

On écrit 4 espaces, à la suite de notre position actuelle.

Code \t Video Test
case '\t': // Tabulation de 4 caractères
            for (int i=0; i < 4; i++)
            {   
                char espace = ' ';
                HD44780_WriteChar(espace,line1,ind_line1,nbrows,nbcols,row,col);
            }
            break;

Gestion des codes VT100

Pour finir, nous allons gérer quelques codes VT100, les déplacements de curseurs.

Pour envoyer un code VT100 pour déplacer le curseur le maitre doit nous envoyer:

  1. Le code ESCAPE (0x1b)
  2. Le CURSOR_MODE (0x1c)
  3. Et enfin le mouvement du curseur souhaité CURSOR_UP (0x41), CURSOR_DOWN (0x42), CURSOR_RIGHT (0x43), CURSOR_LEFT (0x44).

On gère cela dans la fonction VT100_Traitement.

Code
void VT100_Traitement(char car, int nbrows, int nbcols, int* row, int* col, int* vt100_mode)
{
    if (*vt100_mode == 1) // CURSOR_MODE
    {
        int address;

        switch (car)
        {
            case CURSOR_UP:
                *row = (*row == 1) ? 0 : *row;
                break;

            case CURSOR_DOWN:
                *row = (*row == 0) ? 1 : *row;
                break;

            case CURSOR_RIGHT:
                *col = (*col < nbcols - 1) ? (*col + 1) : *col;
                break;

            case CURSOR_LEFT:
                *col = (*col > 0) ? (*col - 1) : *col;
                break;
        }

        address = HD44780_XY2Adrr(nbrows, nbcols, *row, *col);
        HD44780_WriteCommand(LCD_ADDRSET | address);
        HD44780_WriteCommand(LCD_ON | CURSOR_BLINK);
    }
}
Video Test

Gestion des primitives systèmes

Voici les primitives systèmes pour la carte-mère, elles permettent de communiquer les chaînes de caractères à envoyer en préparant une transmission SPI constituée de l'octet de commande 0x01 et des données correspondant aux caractères.

La fonction Reset_Display permet de reset l'affichage de l'écran et permet aussi à la carte-mère de le remarquer en lui attribuant un numéro de port.

La fonction Transmit_to_display est la fonction de transmission de données par SPI. Elle utilise les fonctions SPI créées précédemment pour envoyer les données à afficher.

Code
void Reset_display(volatile uint8_t *resPort, volatile uint8_t resSel) //Utile pour reset l'ecran
{
    *resPort &= ~(1<<resSel);
    _delay_ms(1);
    *resPort |= (1<<resSel);
}


void Transmit_To_display(uint8_t data, volatile uint8_t *ssPort, volatile uint8_t ss)
{
	selectSlaveSPI(ssPort,ss);
	_delay_ms(1);
	transferSPI(data);
	_delay_ms(1);
	unselectSlaveSPI(ssPort,ss);
	_delay_ms(1);
}

Conclusion

Pour conclure, faisons un bilan de ce qui a été produit:

  • Une carte fille fonctionnelle qui gère l'affichage d'un écran LCD
    • Communication SPI par interruption
    • Défilement automatique de l'écran
    • Gestion des caractères spéciaux et code VT100
  • Primitives systèmes pour la carte mère
  • Ordonnanceur avec un système de priorité à l'aide de sémaphores

Malheureusement, par manque de temps, nous n'avons pas pu faire communiquer les cartes filles ensemble en les connectant à la carte-mère. Anisi que finir à 100% l'ordonnanceur.

Nous restons cependant très satisfaits du résultat et des fonctionnalités de notre carte !

Références utiles

Lien du git :

https://archives.plil.fr/mchauvel/PICO_Taha_NEHARI_Martin_CHAUVELIERE.git