« SE4Binome2023-1 » : différence entre les versions
Ligne 418 : | Ligne 418 : | ||
|} | |} | ||
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). | 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). | |||
{| class="wikitable" style="width:100%;" | {| class="wikitable" style="width:100%;" | ||
! scope="col" | Code | ! scope="col" | Code |
Version du 16 janvier 2024 à 15:46
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 vas 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 comuniquer avec l'écran. Les problèmes rencontrés sont les suivants : Au départ, la carte ne fonctionnait pas, le soucis étant le quartz mal soudé, c'est à l'aide d'un pistolet à air chaud que celui-ci à é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éeons 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 timing 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écider de faire clignoter une LED (PORTD ^= 0x02) à chaques utilisations de la fonction ordonnanceur() dans l'ISR.
La variable courant indique la Tache qui est actuellement en cours.
A chaque chaques changements 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 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 | Vidéo |
---|---|
#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 maitenant 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 meme 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 donnees comme par exemple le temps que cette tache doit passer endormie.
Puis afin de remplacer _delay_ms, nous créeons une fonction Attente qui endors 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(); } |
Voila maitenant nos taches, il est important de créer une TacheZombie qui comme son nom l'indique est toujours révéillé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 maitenant 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 ègale à 0 alors l'ordonnacnceur 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'ecriture 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 donner à la première 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 meme chose pour la lecture.
On 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 } |
Carte FPGA
Carte électronique numérique
Conception et Création
Nous avons choisi la Carte Fille : Ecran LCD
Pendant la 3ème séance nous avons finis le Schématic de la carte
Puis nous avons Routé la carte et envoyé à la Fabrication
Carte non soudée | Carte soudé | Video Test |
---|---|---|
Programmation de la carte LCD
Tout d'abord, nous avons voulu tester le bon fonctionnement de notre carte mais surtout du bon controle de l'ecran 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.
Nous avons commencer 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.
Librairie HD44780
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ère 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
Super, ca fonctionne !
Désormais, nous voulons que notre écran affiche ce qu'il recoit 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 maitre transmette des données et que l'esclave les recoivent.
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 |
---|---|
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.
Plusieurs pistes ont été considérées pour comprendre le problème qu'on avait avec le SPI, était-ce l'arduino UNO? Notre connecteur? Le shield? Notre carte? Au départ, par soucis de praticité, nous voulions utiliser l'arduino UNO qui disposait de fonctionnalité de test simple avant de passer au shield, mais à chaque essai, le SPI ne fonctionait 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èmes de synchronisation ou que cette fréquence est 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 surprenant, car peu importe la fréquence de travail, aucun problème n'a été détécté.
Références utiles
Lien du git :
https://archives.plil.fr/mchauvel/PICO_Taha_NEHARI_Martin_CHAUVELIERE.git
https://github.com/Matiasus/HD44780
NHD-C12832A1Z-FSW-FBW-3V3-ND
https://michlstechblog.info/blog/raspberry-pi-connecting-a-hd44780-display-with-i2c-bus/
ReX : Merci d'utiliser le squelette de page présenté en séance.