SE4Binome2023-1
Ordonnanceur
Soudure et Test du SHIELD
Au cours des 2 premières séances nous avons soudé et testé notre Shield.
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;
}
|
Puis nous avons aussi soudé l'adaptateur HE10 pour l'utilisation de la 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
}
|
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;
}
}
}
|
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);
}
}
|
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.
Puis nous avons Routé la carte et envoyé à la Fabrication.
Carte non soudée | Carte soudée | Video Test |
---|---|---|
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);
}
|
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:
- Le code ESCAPE (0x1b)
- Le CURSOR_MODE (0x1c)
- 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