SE4Binome2023-10
Ordonnanceur / Système d'exploitation
Réalisation du Shield
Séance 13/09 : Parmi les activités disponibles, nous avons soudé le régulateur de tension, le lecteur SD, les résistances liées aux LEDs. Nous avons réalisé 2 câbles pour les cartes filles et un adaptateur.
Séance 19/09 : Durant cette séance nous avons soudé les autres résistances et les connecteurs. Et nous avons testé le bon fonctionnement de la carte Shield.
Voici la carte Shield soudé :
Nous avons soudé cette carte :
Pour le test nous avons utilisé le code qui est disponible sur le git. Voici ci-dessous une vidéo montrant le clignotement des LEDs.
Clignotement des LEDs avec ordonnanceur
Au cours de la fabrication de notre carte, nous avons commencé la réalisation de l'ordonnanceur.
Pour réaliser l'ordonnanceur, nous avons besoin d'une fonction de gestion d'interruption ISR qui sera déclenché par l'interruption.
ISR(TIMER1_COMPA_vect, ISR_NAKED) {
SAVE_REGISTERS();
taches[current].pointer_pile = SP;
// Appel à l'ordonnanceur
scheduler();
SP = taches[current].pointer_pile;
RESTORE_REGISTERS();
asm volatile ( "reti" );
}
Le principe c'est de créer deux tâches pour faire clignoter deux LEDs du bouclier avec des périodes différentes. La première tache allume et éteint une LED toutes les 200ms, la deuxième tache réalise la même opération sur une LED différente toutes les 1000ms.
// Tâche 1
void task_LED_1() {
DDRD |= (1 << LED4_PIN);
PORTD &= ~(1 << LED4_PIN);
while (1) {
PORTD ^= (1 << LED4_PIN);
_delay_ms(200); // Fréquence 200 ms
}
}
// Tâche 2
void task_LED_2() {
DDRC |= (1 << LED1_PIN);
PORTC &= ~(1 << LED1_PIN);
while (1) {
PORTC ^= (1 << LED1_PIN);
_delay_ms(1000); // Fréquence 1000 ms
}
}
Et voici la fonction permettant d'initialiser le contexte de chaque tâche :
void init_task(int id)
{
uint16_t save_sp = SP;
SP= taches[id].pointer_pile;
uint16_t adresse=(uint16_t)taches[id].task_func;
asm volatile("push %0" : : "r" (adresse & 0x00ff) );
asm volatile("push %0" : : "r" ((adresse & 0xff00)>>8) );
SAVE_REGISTERS();
taches[id].pointer_pile = SP;
SP = save_sp;
}
Afficheur 7 segments
Pour l'afficheur 7 segments, nous l'avons dans un premier temps fait sans ordonnancement pour connaitre sa fonctionnalité. Pour le tester on a juste affiché "1 2 3 4" sur l'afficheur comme indiqué sur l'image ci-dessous :
void afficheur(void)
{
spi_activer();
spi_exch(0x76);
spi_desactiver();
char seg[4] = "1234";
while(1){
spi_activer();
for(uint8_t i = 0; i < 4 ; i++)
{
spi_exch(seg[i]);
}
_delay_ms(1000);
spi_desactiver();
}
}
Pour l'ordonnancement, nous l'avons testé sur l'afficheur 7 segments avec lecture/écriture sur le port série :
// fonction pour afficheur 7 segments
void afficheur(void)
{
pause_smp(SEMAPHORE_SPI);
spi_activer(SS_3);
spi_exch(0x76);
spi_desactiver(SS_3);
while(1){
for(int i = 0; i < 4 ; i++)
{
spi_activer(SS_3);
spi_exch(caractere);
}
spi_desactiver(SS_3);
_wait_ms(5);
liber_smp(SEMAPHORE_SPI);
}
}
Les fonctions spi_activer
et spi_desactiver
sont utilisées pour activer et désactiver les différents esclaves sur le bus SPI en manipulant les broches SS correspondantes. Cela permet de sélectionner le périphérique avec lequel le microcontrôleur va communiquer via le bus SPI.
void spi_activer(int chip_s) {
if (chip_s == SS_2 || chip_s == SS_3) {
PORTD &= ~(1 << chip_s);
} else if (chip_s == SS_5) {
PORTB &= ~(1 << chip_s);
}
}
void spi_desactiver(int chip_s) {
if (chip_s == SS_2 || chip_s == SS_3) {
PORTD |= (1 << chip_s);
} else if (chip_s == SS_5) {
PORTB |= (1 << chip_s);
}
}
La matrice Led
Nous avons essayé l'affichage avec la matrice Led par le protocole SPI. On a inclut les représentations matricielles pour les chiffres de 0 à 9 et les lettres de l'alphabet de A à Z. Nous avons écrit une fonction pour l'envoi de données à la matrice LED via le protocole SPI. Elle active le périphérique SPI, envoie la ligne et la valeur via SPI, puis désactive le périphérique.
void matrice_Byte(uint8_t row, uint8_t value) {
// Activer le périphérique SPI correspondant à la matrice
spi_activer(SS_2);
// Envoyer les données à la matrice via SPI
spi_exch(row);
spi_exch(value);
// Désactiver le périphérique SPI
spi_desactiver(SS_2);
}
Pour l'affichage de caractère et le test :
//fonction pour la matrice
void display_on_matrix(void) {
char l = caractere;
int index = 0;
if (l >= '0' && l <= '9')
index = l - '0';
else if (l >= 'a' && l <= 'z')
index = 10 + (l - 'a');
else if (l >= 'A' && l <= 'Z')
index = 10 + (l - 'A');
while(1){
pause_smp(SEMAPHORE_SPI);
spi_activer(SS_2);
spi_exch(0x76);
_wait_ms(1);
spi_desactiver(SS_2);
if (index >= 0 && index <= 9) {
for (uint8_t i = 0; i < 8; i++) {
matrice_Byte(i + 1, numbers[index][i]);
}
} else if (index >= 10 && index <= 35) {
for (int i = 0; i < 8; i++) {
matrice_Byte(i + 1, letters[index - 10][i]);
}
}
spi_desactiver(SS_2);
_wait_ms(500);
liber_smp(SEMAPHORE_SPI);
}
}
Malheureusement, malgré nos efforts, la fonction display_on_matrix
n'a pas réussi à produire l'affichage voulu sur la matrice LED. Malgré la mise en œuvre du protocole SPI et des déterminations d'index pour les caractères, l'effet clignotant souhaité n'a pas été observé.Faute de temps, nous avons décidé de recentrer nos efforts sur la carte LCD.
Carte FPGA / VHDL
Controleur VGA
Un contrôleur VGA sert à générer des signaux de synchronisation horizontale (HS) et verticale (VS), ainsi que des signaux de couleur (r, g, b) pour un écran VGA. L’horloge d'entrée est à 100 MHz et génère une horloge de sortie à 65 MHz généré par le wizard (clk_wiz_0).
Le code ci-dessous permet de générer des signaux VGA pour afficher des images colorées sur un écran VGA et un Basys3
- Génération des signaux HS et VS pour indiquer les périodes de synchronisation horizontale et verticale
process1 : process(clock_65Mhz)
begin
if clock_65Mhz'event and clock_65Mhz = '1' then
if compteur_vert < 774 then
if compteur_hori < 1048 then
HS <= '1';
compteur_hori <= compteur_hori + 1;
elsif compteur_hori >= 1048 and compteur_hori <1183 then
HS <= '0';
compteur_hori <= compteur_hori + 1;
elsif compteur_hori >= 1183 and compteur_hori < 1343 then
HS <= '1';
compteur_hori <= compteur_hori + 1;
else
HS <= '1';
compteur_hori <= 0;
compteur_vert <= compteur_vert + 1;
end if;
end if;
if compteur_hori < 1048 then
if compteur_vert < 774 then
VS <= '1';
compteur_vert <= compteur_vert + 1;
elsif compteur_vert <= 774 and compteur_vert < 780 then
VS <= '0';
compteur_vert <= compteur_vert + 1;
elsif compteur_vert <=780 and compteur_vert < 805 then
VS <='1';
compteur_vert <= compteur_vert + 1;
else
VS<= '1';
compteur_vert <= 0;
compteur_hori <= compteur_hori + 1;
end if;
end if;
- Génération des Signaux de Couleur (r, g, b) en utilisant les boutons b1, b2 et b3
if b1 = '1' then
--couleur rouge
if compteur_hori > 300 and compteur_hori < 600 then
if compteur_vert > 300 and compteur_vert < 600 then
r <= "1111";
g <= "0000";
b <= "0000";
end if;
end if;
end if;
--couleur verte
if b2 = '1' then
if compteur_hori > 360 and compteur_hori < 1080 then
if compteur_vert > 201 and compteur_vert < 604 then
r <= "0000";
g <= "1111";
b <= "0000";
end if;
end if;
end if;
--couleur bleue
if b3 = '1' then
if compteur_hori > 360 and compteur_hori < 1080 then
if compteur_vert > 201 and compteur_vert < 604 then
r <= "0000";
g <= "0000";
b <= "1111";
end if;
end if;
end if;
end if;
Controleur bus SPI
Les dispositifs SPI communiquent en utilisant une architecture maître-esclave avec un seul maître.Un ou plusieurs dispositifs esclaves SPI sont pris en charge par la sélection d'esclaves individuels SS
Avant de démarrer une communication avec un esclave particulier, le maître active la ligne SS
associée à cet esclave en la faisant passer de l'état haut à l'état bas.
Une fois que l'esclave est sélectionné, le maître peut commencer à envoyer des données sur la ligne MOSI et recevoir des données sur la ligne MISO.
Nous devons illustrer une implémentation d'un émetteur SPI synchrone. Dans notre contexte, on gère la transmission séquentielle d'octets sur la ligne MOSI, tout en signalant la fin de chaque transmission par le signal complete
. Lorsqu'il est déclenché par un front montant d'horloge, le code gère différents états, tels que l'état IDLE
où le système est en attente, et l'état TRANSFER
où les données sont envoyées bit par bit.
Voici le code pour tester chaque état
- Etat IDLE
Dans l'état IDLE, le système est en attente d'une nouvelle transmission SPI, et la transition vers l'état de transfert est conditionnée par la détection d'un signal de démarrage. Lorsque ce signal est activé, l'état passe à TRANSFER, initialisant la communication en activant la sélection d'esclave et préparant la transmission des données.
when IDLE =>
if start = '1' then
-- Début d'une nouvelle transmission
state <= TRANSFER;
ss <= '0'; -- Activer la sélection d'esclave
bit_counter <= 7; -- Initialiser le compteur de bits
mosi <= data_buffer(7);
end if;
- Etat TRANSFER
L'état TRANSFER gère la transmission séquentielle des bits sur la ligne MOSI.
when TRANSFER =>
if bit_counter > 0 then
-- Transfert des bits
bit_counter <= bit_counter - 1;
mosi <= data_buffer(bit_counter);
else
-- Fin de la transmission
state <= DONE;
end if;
- Etat DONE
Dans l'état DONE, après la fin de la transmission SPI, le système désactive la sélection d'esclave et peut lire les données reçues sur la ligne MISO
when DONE =>
-- Désactiver la sélection d'esclave et générer une impulsion d'horloge
ss <= '1';
--sclk <= not sclk;
complete <= '1';
-- Lecture des données reçues sur la ligne MISO
data_in <= data_in (6 downto 0) & miso;
Carte électronique numérique
Type de carte choisie
La carte fille écran LCD.
Elle se compose :
- d'un ATMega328p
- d'un écran LCD à base de contrôleur HD44780
- d'un connecteur HE10
- d'un connecteur AVR ISP pour programmer le microcontrôleur
- d'un potentiomètre pour régler la luminosité des cristaux liquides
- d'un bouton switch pour le reset
Réalisation de la carte
Séance 22/09 : Durant cette séance nous avons commencé la schématique de notre carte.
Voici le schéma électronique de notre carte fille LCD :
Séance 03/10 : nous avons commencé le routage.
Séance 17/10 : routage terminé
Après le routage nous avons obtenu :
Séance 24/10 :
Le routage terminé, le prof a vérifié le routage final et nous avons envoyé la carte afin de l'imprimer.
Durant cette même séance nous avons commencer la programmation de l'ordonnancement.
Voici une vue 3D de ce qui va se trouver au dessus de notre carte. Notre écran se trouvera donc en dessous.
Présentation de la carte fille LCD
Séance 14/11 :
Nous avons reçu la carte, nous avons commencé la soudure des composants nécessaires pour un premier test de la carte.
- Le microprossesseur
- une led
- L'horloge
Pour vérifier le bon fonctionnement de la carte, nous avons mis une led afin de savoir la fonctionnalité de la carte.
Pour cela, on a testé avec le code exemple présent Arduino IDE.
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(7, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(7, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(7, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
Nous avons soudé le reste des composants ainsi que l'écran LCD.
- Le potentiomètre
- le bouton reset
- les connecteurs
- l'écran LCD
- Les capacités
Voici tous la présentation de tous les composants de notre carte fille :
Test de l'ecran avec Aduino IDE
Nous avons testé le lcd avec le code qui est présent sur Arduino IDE. Voici le code utilisé:
// include the library code:
#include <LiquidCrystal.h>
// initialize the library by associating any needed LCD interface pin
// with the arduino pin number it is connected to
const int rs = A0, en = A2, d4 = 4, d5 = 5, d6 = 9, d7 = 10;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
void setup() {
pinMode(A1,OUTPUT);
digitalWrite(A1,LOW);
delay(1000);
// set up the LCD's number of columns and rows:
lcd.begin(16, 2);
// Print a message to the LCD.
lcd.print("hello, world!");
pinMode(7,OUTPUT);
digitalWrite(7,HIGH);
}
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);
}
Programmation de la carte fille LCD
Les broches nécessaire à la programmation de l'écran LCD :
- RS (Register Select)
- EN (Enable)
- R/W (Read/Write)
- DB4 à DB7
Ecriture sur la carte LCD
La fonction lcdWrite4bits
est conçue pour écrire quatre bits de données sur l’écran LCD en utilisant une interface 4 bits.
void lcdWrite4bits(uint8_t data, uint8_t rs){
if(rs)
LCD_RS_ON();
else
LCD_RS_OFF();
LCD_RW_OFF();
LCD_EN_ON();
_delay_us(1);
if(data & 1)
LCD_DB4_ON();
else
LCD_DB4_OFF();
if(data & 2)
LCD_DB5_ON();
else
LCD_DB5_OFF();
if(data & 4)
LCD_DB6_ON();
else
LCD_DB6_OFF();
if(data & 8)
LCD_DB7_ON();
else
LCD_DB7_OFF();
LCD_EN_OFF();
_delay_us(1);
}
- rs
indique si les données à écrire sont des données :
- d'instruction « 0 »
- des données à afficher « 1 »
- Les blocs de if
testent chaque bit dans data
. Ils activent ou désactivent les lignes de données (DB4 à DB7).
Gestion des caractères de contrôle et initialisation LCD
Pour initialiser l'écran LCD, nous avons écris la fonction suivante
void lcdInit(){
LCD_RS_INIT();
LCD_EN_INIT();
LCD_RW_INIT();
LCD_DB4_INIT();
LCD_DB5_INIT();
LCD_DB6_INIT();
LCD_DB7_INIT();
_delay_ms(1);
lcdWrite8bits(0x28,0); //4bits, 2 lignes
lcdWrite8bits(0x08,0); //LCD off
lcdWrite8bits(0x01,0); //Clear LCD
_delay_ms(2);
lcdWrite8bits(0x06,0); //incrementation
lcdWrite8bits(0x0c,0);
}
lcdWrite8bits(0x0c, 0)
: Allume l'écran LCD sans afficher le curseur.
La fonction lcdWrite8bits est conçue pour écrire une valeur de 8 bits de data sur l'écran LCD.
Pour la gestion du retour à la ligne et le retour chariot il faut vérifier si le caractère data est égal à un retour à la ligne \n
ou un retour chariot \r
.
if ((data == '\n') || (data == '\r')) {
pos.current_x = 0;
pos.current_y = (pos.current_y + 1) % NB_LIGNE;
lcdMoveCursor(pos.current_y, 0);
return;
}
La mise à jour du curseur dépend de la valeur de data et si elle vaut l'un des caractère de contrôle, le curseur se mettra à jour sur la ligne suivante grâce à la fonction lcdMoveCursor(pos.current_y, 0)
Pour le retour à la ligne automatique, faut vérifier la position de x, pour cela on a ajouté la condition suivante :
if (pos.current_x == NB_COL) {
pos.current_x = 0;
pos.current_y = (pos.current_y + 1) % NB_LIGNE;
lcdMoveCursor(pos.current_y, 0);}
Pour déplacer le curseur sur l'écran LCD :
void lcdMoveCursor(uint8_t row, uint8_t col) {
if (row == 0) {
// Pour la première ligne
lcdWrite8bits(0x80 + col, 0); // Adresse de début pour la première ligne + colonne
} else if (row == 1) {
// Pour la deuxième ligne
lcdWrite8bits(0xC0 + col, 0); // Adresse de début pour la deuxième ligne + colonne
}
}
Pour effacer le contenu affiché sur un écran LCD. on a ajouté la fonction lcdClear qui envoie une commande à l'écran LCD pour effacer son contenu, puis introduit un délai de 2 millisecondes pour permettre à l'écran de s'effacer correctement avant de poursuivre l'exécution du programme.
void lcdClear() {
lcdWrite8bits(0x01, 0);
_delay_ms(2);
}
Affichage des caractères
Pour afficher chaque caractère on a écris la fonction lcdPutc
void lcdPutc(char c){
busy = 1;
lcdWrite8bits(c,1);
busy = 0;
}
busy
à 1 est utilisé pour indiquer l'état de l'écran LCD s'il est en train de traiter une opération.
Voici la vidéo montrant le bon fonctionnement de notre carte :
le code source complet est dans le git.
LCD avec SPI
Lors de la programmation de la carte fille, nous avons constaté qu'il y'a une erreur sur la carte: le pin CS de l'Atmega est liée au PD6 alors qu'il doit être connecté au PB2. Donc on a du interchanger CS et B7 sinon la communication SPI ne sera pas fonctionnelle.
Nous avons donc procéder avec monsieur Redon la modification des pins.
Nous avons couper la piste du pin 14 et le relier avec le "via" connecté à CS avec un fil. Puis relier le pin 10 avec B7 de l'écran LCD.
Malheureusement on a pris beaucoup de temps pour constater l'erreur, donc on a perdu du temps en pensant que l'erreur était dans le code.
Programmation du microcontrôleur de la carte LCD
Pour traiter les commandes reçues via l'interface SPI, on a ajouté la fonction commandes() :
void commandes(void)
{
char cmd = SPDR; //spi_wait_cmd();
char data = spi_wait_cmd();
char tmp;
if(cmd == 0x00)
{
lcdPutc('o'); //test
lcdPutc('k');
if(data == INFO)
{
tmp = data;
if(tmp != 0xff)
{
lcdPutc(data);
}
else if(tmp==0x00)
{
SPDR = 0xA2; //Pour Affichage, Envoi de la donnée 0xA2 via SPI si la donnée reçue est égale à 0x00
}
}
}
if(cmd == 0xff)
{
if(data == 0xff)
{
SPDR = 0x55; // Envoi de la donnée 0x55 via SPI si la commande est 0xff et la donnée reçue est 0xff
}
}
}
Voici le main pour tester la communication entre le maitre (arduino) et notre écran LCD :
int main() {
spi_init();
lcdInit();
commandes();
sei();
while (1) {
}
return 0;
}
Pour gérer la réception des données via le port SPI et pour configurer l'écran comme Esclave :
void spi_init(void) {
// Configuration des broches
SPI_DDR = (1 << SPI_MISO); // MISO en sortie
SS_DDR &= ~((1 << SPI_SCK) | (1 << SPI_SS));
SS_DDR &= ~(1 << SPI_MOSI);
// Configuration du registre de contrôle SPI
SPCR = (1 << SPE);
}
uint8_t spi_wait_cmd(void) // Communication sur le bus SPI
{
while(!(SPSR & (1<<SPIF)));
return SPDR;
}
Programmation du maitre
Pour la configuration de la carte maitre, on a repris le code spi qu'on a utilisé pour l'ordonnancement avec quelque modification pour utiliser un seul connecteur HE10:
void spi_init(void)
{
// Configurer la broche de sortie pour MOSI
SPI_DDR |= (1 << SPI_MOSI);
// Configurer la broche de sortie pour SCK
SPI_DDR |= (1 << SPI_SCK);
// Configurer la broche d'entrée pour MISO
SPI_DDR &= ~(1 << SPI_MISO);
DDRD |= (1<<SS_3);
PORTD |= (1<<SS_3);
SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0);
}
uint8_t spi_exch(uint8_t output) // Communication sur le bus SPI
{
SPDR = output;
while(!(SPSR & (1<<SPIF)));
return SPDR;
}
Voici le main qu'on a fait pour tester la communication SPI entre le maitre et esclave:
int main() {
spi_init();
spi_desactiver(SS_3);
DDRD |= (1<<4);
while(1){
spi_activer(SS_3);
spi_exch(0);
spi_exch(1);//clavier
spi_exch(0xff);
spi_desactiver(SS_3);
}
return 0;
}
Voici le résultat du test qui permet de tester la communication sur le port SPI:
la primitive système :
Cette fonction permet une transmission SPI constituée de l'octet de commande 0x01, et de communiquer les chaînes de caractères à envoyer.
void envoyer_commande_ecran(uint8_t ligne_selection, const char* caracteres) {
// Activer la ligne de sélection SPI (SS) correspondante à l'écran
spi_activer(ligne_selection);
// Envoyer l'octet de commande
spi_exch(0x01);
// Envoyer les octets correspondant aux caractères
size_t longueur_chaine = strlen(caracteres);
for (size_t i = 0; i < longueur_chaine; ++i) {
spi_exch(caracteres[i]);
}
// Désactiver la ligne de sélection SPI (SS)
spi_desactiver(ligne_selection);
}
Conclusion
Durant le projet, comme nous l’avons déjà cité plus haut, nous avons ressoudé la carte LCD à cause d’un problème d’inter-changement de pins. Et comme c’était sensible, la soudure s’est enlevée pendant les vacances et nous n’avons pas pu travailler correctement avec la carte car nous avons perdu beaucoup de temps pour pouvoir la ressouder.
Pour la conception de la carte électronique, de la conception du schématique et le routage, le temps était bien géré jusqu'à la soudure des composants.
Pour la programmation :
- Pour la partie ordonnanceur nous avons réussi à faire ordonnancer la tâche d'écriture sur l'afficheur 7 segments et la lecture avec le port série. En revanche nous n’avons pas eu le temps de programmer la matrice led correctement malgré notre tentative parce que nous avons préféré nous concentrer sur la programmation de notre carte fille LCD Cette partie était une partie compliquée à programmer mais nous en avons beaucoup appris.
- Pour la carte fille LCD : c’est une partie que nous avons bien découverte et nous avons bien aimé la programmation pour afficher des caractères sur l’écran. Cette partie nous a aidé à renforcer nos connaissances sur la programmation et le fonctionnement d’une carte électronique ainsi que la connaissance en communication SPI. Nous avons réussi à avoir un affichage sur l’écran par simple communication avec arduinoIDE ainsi qu’une communication SPI avec l’Arduino(faisant référence à la carte mère) cependant l’envoi de données n’a pas pu être fonctionnelle.
GIT
Voici le lien du git : https://archives.plil.fr/handrian/projet_pico.git