« SE4Binome2025-1 » : différence entre les versions

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
Ligne 36 : Ligne 36 :
-d'un état : 0 pour en cours et 1 pour terminé ou vide
-d'un état : 0 pour en cours et 1 pour terminé ou vide


Ces deux derniers points nous servirons plus tard.<syntaxhighlight lang="c">
Ces deux derniers points nous servirons plus tard.<syntaxhighlight lang="c" line="1">
typedef struct {
typedef struct {
uint16_t stackPointer;
uint16_t stackPointer;
Ligne 44 : Ligne 44 :
} process;
} process;


</syntaxhighlight>On initialise donc notre tableau de processus de la façon suivante : <syntaxhighlight lang="c">
</syntaxhighlight>On initialise donc notre tableau de processus de la façon suivante : <syntaxhighlight lang="c" line="1">
process Tasks_list[NBTASKS] = {
process Tasks_list[NBTASKS] = {
                                 {0, led1_blink, 0,0},
                                 {0, led1_blink, 0,0},
Ligne 51 : Ligne 51 :




</syntaxhighlight>Concernant les fonction liés leds, on utilise une simple boucle while contenant un changement d'état <syntaxhighlight lang="c">
</syntaxhighlight>Concernant les fonction liés leds, on utilise une simple boucle while contenant un changement d'état <syntaxhighlight lang="c" line="1">
void led_init() {
void led_init() {
...
...
Ligne 65 : Ligne 65 :
</syntaxhighlight>La fonction init_stackPointer_tasks() permet de diviser la pile "réel" en plusieurs piles pour chaque processus.  Ces sous piles commence à la position FIRST_STACK_POSITION, afin de ne pas réécrire par dessus des données. Toutes les sous piles diposent d'une même taille de pile STACK_LENGTH.
</syntaxhighlight>La fonction init_stackPointer_tasks() permet de diviser la pile "réel" en plusieurs piles pour chaque processus.  Ces sous piles commence à la position FIRST_STACK_POSITION, afin de ne pas réécrire par dessus des données. Toutes les sous piles diposent d'une même taille de pile STACK_LENGTH.


La fonction init_piles() initialise correctement la pile avec le tableau de processus si le processus est actif.<syntaxhighlight lang="c">
La fonction init_piles() initialise correctement la pile avec le tableau de processus si le processus est actif.<syntaxhighlight lang="c" line="1">
void init_stackPointer_tasks(int Cprocess) { //initialise le pointer de pile
void init_stackPointer_tasks(int Cprocess) { //initialise le pointer de pile
if(Tasks_list[Cprocess].state == 0) {
if(Tasks_list[Cprocess].state == 0) {
Ligne 87 : Ligne 87 :




</syntaxhighlight>le scheduler permet de passer à la tâche suivante<syntaxhighlight lang="c">
</syntaxhighlight>le scheduler permet de passer à la tâche suivante<syntaxhighlight lang="c" line="1">
void scheduler() {
void scheduler() {
Current_task++;
Current_task++;
Ligne 96 : Ligne 96 :
</syntaxhighlight>l'atmega328p ne dispose pas de de fonction en assembleur permettant de push ou de pop tout les registres. on doit donc le faire un par un.
</syntaxhighlight>l'atmega328p ne dispose pas de de fonction en assembleur permettant de push ou de pop tout les registres. on doit donc le faire un par un.


On défini alors les macros suivantes :  <syntaxhighlight lang="c">
On défini alors les macros suivantes :  <syntaxhighlight lang="c" line="1">
#define SAVE_REGISTERS() \
#define SAVE_REGISTERS() \
asm volatile ( \
asm volatile ( \
Ligne 110 : Ligne 110 :
)
)


</syntaxhighlight>L'ISR gère les interruptions, en commençant par sauvegarder l'état de la pile ainsi que le stack pointeur propre au processus qui s'interrompt. L'appel au schedruler permet de changer de processus pour cela il faut restorer le stack pointer propre au nouveau processus actif et de la pile liée à ce processus. La dernière ligne arrête l'ISR.<syntaxhighlight lang="c">
</syntaxhighlight>L'ISR gère les interruptions, en commençant par sauvegarder l'état de la pile ainsi que le stack pointeur propre au processus qui s'interrompt. L'appel au schedruler permet de changer de processus pour cela il faut restorer le stack pointer propre au nouveau processus actif et de la pile liée à ce processus. La dernière ligne arrête l'ISR.<syntaxhighlight lang="c" line="1">
ISR(TIMER1_COMPA_vect, ISR_NAKED) {
ISR(TIMER1_COMPA_vect, ISR_NAKED) {
SAVE_REGISTERS();
SAVE_REGISTERS();
Ligne 125 : Ligne 125 :
====Ajout d'un système d'endormissement des processus====
====Ajout d'un système d'endormissement des processus====


La fonction wait va permettre d'endormir un processus, elle va donné un temps de sommeil au processus puis lancer une interruption (car on veut changer de processus directement après que le précédent ait été endormi).<syntaxhighlight lang="c">
La fonction wait va permettre d'endormir un processus, elle va donné un temps de sommeil au processus puis lancer une interruption (car on veut changer de processus directement après que le précédent ait été endormi).<syntaxhighlight lang="c" line="1">
void wait(int time) { // met un process en veille pour une durée donné
void wait(int time) { // met un process en veille pour une durée donné
// valeur max de time 32000 car int sur atmega328p code sur 16 bits donc 32000 max
// valeur max de time 32000 car int sur atmega328p code sur 16 bits donc 32000 max
Ligne 134 : Ligne 134 :
TIMER1_COMPA_vect();
TIMER1_COMPA_vect();


</syntaxhighlight>Pour verifier si un processus a besoin d'être lancé, on ajoute une boucle while dans le scheduler afin de vérifier si il y a un temps de sommeil. Le scheduler va aussi servir à diminuer le temps de sommeil de tout les processus (avec un minimum de 0).<syntaxhighlight lang="c">
</syntaxhighlight>Pour verifier si un processus a besoin d'être lancé, on ajoute une boucle while dans le scheduler afin de vérifier si il y a un temps de sommeil. Le scheduler va aussi servir à diminuer le temps de sommeil de tout les processus (avec un minimum de 0).<syntaxhighlight lang="c" line="1">
void scheduler() {
void scheduler() {
for (int i=0 ; i<NBTASKS ; i++) {
for (int i=0 ; i<NBTASKS ; i++) {
Ligne 156 : Ligne 156 :
====Tableau dynamique des tâches====
====Tableau dynamique des tâches====


En premier lieu on va initialiser les état de tout les cases du tableau de processus. On choisit 1 la case est vide, 0 sinon.<syntaxhighlight lang="c">
En premier lieu on va initialiser les état de tout les cases du tableau de processus. On choisit 1 la case est vide, 0 sinon.<syntaxhighlight lang="c" line="1">
void init_state() { //initialise state
void init_state() { //initialise state
for (int i= 0; i<NBTASKS; i++) {
for (int i= 0; i<NBTASKS; i++) {
Ligne 175 : Ligne 175 :
-add ajoute un processus sur la première case vide du tableau, initialise son pointeur de position et sa pile.
-add ajoute un processus sur la première case vide du tableau, initialise son pointeur de position et sa pile.


Pour finish et kill, on met le temps de sommeil à 0 au cas où on arrête un processus endormi. C'est une mesure de sécurité pour finish, mais une obligation pour kill.<syntaxhighlight lang="c">
Pour finish et kill, on met le temps de sommeil à 0 au cas où on arrête un processus endormi. C'est une mesure de sécurité pour finish, mais une obligation pour kill.<syntaxhighlight lang="c" line="1">
void finish() { //supprime un process lorsque celui-ci est terminé
void finish() { //supprime un process lorsque celui-ci est terminé
cli();
cli();
Ligne 225 : Ligne 225 :
on obtient le résultat suivant :[[Fichier:Demo fonction add et finish.mp4|centré|vignette]]
on obtient le résultat suivant :[[Fichier:Demo fonction add et finish.mp4|centré|vignette]]
====lecture et écriture sur le port série====
====lecture et écriture sur le port série====
Afin de lire et écrire sur le port série, on utilise les fonctions USART_Init, USART_Transmit et USART_Receive  vu en cours de microP l'année précédente. Le but sera donc d'écrire le message qui vient d'être reçu, on utilise donc le processus suivant :<syntaxhighlight lang="c">
Afin de lire et écrire sur le port série, on utilise les fonctions USART_Init, USART_Transmit et USART_Receive  vu en cours de microP l'année précédente. Le but sera donc d'écrire le message qui vient d'être reçu, on utilise donc le processus suivant :<syntaxhighlight lang="c" line="1">
void Serial_Message(){
void Serial_Message(){
     unsigned char data;
     unsigned char data;
Ligne 239 : Ligne 239 :


====afficheur 7 segments====
====afficheur 7 segments====
=====test=====
<syntaxhighlight lang="c" line="1">
[[Fichier:Video7segvideo.mp4|vignette|centré]]
void seven_seg() {
while (1) {
        spi_activer((uint16_t)&PORTB, CS4);
spi_echange(0x7E);
spi_echange(0b1101111);
spi_desactiver((uint16_t)&PORTB, CS4);
 
wait(500);
 
spi_activer((uint16_t)&PORTB, CS4);
spi_echange(0x76);
spi_desactiver((uint16_t)&PORTB, CS4);
 
wait(500);
    }
}
</syntaxhighlight>Le processus seven_seg est ajouté au tableau des processus. Avant d'échanger en spi il faut activer la communication, et la désactiver après l'échange. La ligne 4 permet de sélectionner le 7 seg sur lequel on souhaite modifier l'affichage. La ligne 5 permet d'afficher le chiffre 9. Enfin la ligne 11 permet de clear le display. Voir tableau ci-dessous.
[[Fichier:Tab 7 seg sparkfun.png|centré|vignette]]
<syntaxhighlight lang="c" line="1">
void spi_init(void) {                                // Initialisation du bus SPI
SPI_DDR |= (1<<SPI_MOSI)|(1<<SPI_SCK);  // Définition des sorties
SPI_DDR &= ~(1<<SPI_MISO);                          // Définition de l'entrée                         
SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);                // Activation SPI (SPE) en état maître (MSTR)
                                                    // horloge F_CPU/64 (SPR1=1,SPR0=0)
SPI_DDR |= (1<<CS1) | (1<<CS4);;
DDRD |= (1<<CS5) | (1<<CS6);
DDRC |= (1<<CS2) | (1<<CS3);
}
 
 
void spi_activer(uint16_t port, uint8_t cs) { // Activer le périphérique
volatile uint8_t *reg = (volatile uint8_t*)port;
*reg &= ~(1<<cs);                            // Ligne SS à l'état bas
}
 
 
void spi_desactiver(uint16_t port, uint8_t cs) {                          // Désactiver le périphérique
volatile uint8_t *reg = (volatile uint8_t*)port;
*reg |= (1<<cs);                            // Ligne SS à l'état haut
}
 
 
uint8_t spi_echange(uint8_t envoi) {                  // Communication sur le bus SPI
SPDR = envoi;                                        // Octet a envoyer
while(!(SPSR & (1<<SPIF)));                          // Attente fin envoi (drapeau SPIF du statut)
return SPDR;                                        // Octet reçu
}
 
</syntaxhighlight>Afin de rendre nos fonctions les plus réutilisables possibles nous avons passer en paramètre le port ainsi que le CS qui correspond au slave select. Le type réel du port est volatile uint8* cependant le compilateur n'accepte que le uint16* ce qui explique le cast un peu étrange.
 
=====Implémentation=====
[[Fichier:Video7segvideo.mp4|vignette|centré]]L'implémentation correspond à nos attentes, seul un 7 seg affiche le chiffre 9.


=Carte Réseau=
=Carte Réseau=

Version du 24 novembre 2025 à 10:15

Shield Arduino

Hardware

Afin de tester notre futur carte réseau, nous avons fait un shield d'arduino qui prendra le rôle de la carte mère durant.

Schématique

Nous nous sommes évidemment aider de la datasheet de l'atmega328p ainsi que celle de l'arduino uno afin de sélectionner les bon pins.

Datasheet ATmega328p
Datasheet Arduino Uno R3

Plus précisément nous nous inspirons très fortement du schéma page 10 de la datasheet de l'arduino :

Schéma pinouts

Nous sommes donc parti de l'exemple fourni par Mr Redon. Mais nous avons utilisé une template disponible dans kicad permettant de créer des shields d'arduino Uno R3.

Schématique shield arduino

Routage

Au niveau du routage, nous avons tenté de faire simple (sans trop abuser sur les vias. Nous avons positionner le lecteur de carte sd au bord de la carte afin de ne pas avoir de problèmes avec d'autres composants lorsque nous voudrons mettre ou enlever la carte sd.

Routage shield arduino

Soudage

La schématique et le routage terminés, nous avons soudé les différents pins permettant de souder la carte shield à l'arduino, le reste n'étant pas forcément utile pour le moment (il est important de noter que notre shield est arrivé avec des composants pré-soudés).

Shield soudage de base .jpg

Tests

Il faudra ensuite faire des test d'allumage des leds, afin de vérifier le bon fonctionnement du shield

Les leds fonctionnant à merveille, Il est temps de passer de passer à l'ordonnanceur.

Software

Ordonnanceur

[à completer et commenter , seulement des codes pour le moment !!!!]

Ordonnanceur basique

La première étape consiste à définir la structure d'un process, il sera composé :

-d'un pointeur de pile

-de l'adresse de la fonction

-d'un temps de sommeil

-d'un état : 0 pour en cours et 1 pour terminé ou vide

Ces deux derniers points nous servirons plus tard.

typedef struct {
	uint16_t stackPointer;
	void (*functionAddress)(void);
	int sleep_time;
	int state;
} process;

On initialise donc notre tableau de processus de la façon suivante :

process Tasks_list[NBTASKS] = {
                                {0, led1_blink, 0,0},
				...
			       };

Concernant les fonction liés leds, on utilise une simple boucle while contenant un changement d'état

void led_init() {
	...
}
void led1_blink() {
	while(1) {
		PORTC ^= (1<<PLED1);
		_delay_ms(100);
        }
}
void led2_blink() {
...

La fonction init_stackPointer_tasks() permet de diviser la pile "réel" en plusieurs piles pour chaque processus. Ces sous piles commence à la position FIRST_STACK_POSITION, afin de ne pas réécrire par dessus des données. Toutes les sous piles diposent d'une même taille de pile STACK_LENGTH. La fonction init_piles() initialise correctement la pile avec le tableau de processus si le processus est actif.

void init_stackPointer_tasks(int Cprocess) { //initialise le pointer de pile
	if(Tasks_list[Cprocess].state == 0) {
		Tasks_list[Cprocess].stackPointer = FIRST_STACK_POSITION - (Cprocess * STACK_LENGTH);
	}
}


void init_piles(int Cprocess){ //initilalise la pile
	if(Tasks_list[Cprocess].state == 0) {
		int save = SP;
		SP = Tasks_list[Cprocess].stackPointer;
		uint16_t address = (uint16_t)Tasks_list[Cprocess].functionAddress;
		asm volatile("push %0" : : "r" (address & 0x00ff) );
		asm volatile("push %0" : : "r" ((address & 0xff00)>>8) );
		SAVE_REGISTERS();
		Tasks_list[Cprocess].stackPointer = SP;
		SP = save;
	}
}

le scheduler permet de passer à la tâche suivante

void scheduler() {
	Current_task++;
	if (Current_task >= NBTASKS) {
		Current_task = 0; 
}

l'atmega328p ne dispose pas de de fonction en assembleur permettant de push ou de pop tout les registres. on doit donc le faire un par un. On défini alors les macros suivantes :

#define SAVE_REGISTERS() \
	asm volatile ( \
		"push r0 	 \n\t" \
		...\
)


#define RESTORE_REGISTERS() \
	asm volatile ( \
		"pop r31 	  \n\t" \
		...\
)

L'ISR gère les interruptions, en commençant par sauvegarder l'état de la pile ainsi que le stack pointeur propre au processus qui s'interrompt. L'appel au schedruler permet de changer de processus pour cela il faut restorer le stack pointer propre au nouveau processus actif et de la pile liée à ce processus. La dernière ligne arrête l'ISR.

ISR(TIMER1_COMPA_vect, ISR_NAKED) {
	SAVE_REGISTERS();
	Tasks_list[Current_task].stackPointer = SP;
	scheduler();
	SP = Tasks_list[Current_task].stackPointer;
	RESTORE_REGISTERS();
	asm volatile("reti");
}
test

Les processus de clignotement de leds ont tous un délai d'attente différent, l'interruption se produit toutes les millisecondes. On obtient ceci :

Il est intéressant de remarqué que la vitesse à laquelle les leds changent d'état ne correspond pas au délai qui leur à été donné. Cela parait logique puisque l'interruption met la fonction _delay_ms en pause, ainsi la vitesse se voit divisé par le nombre de processus actif.

Ajout d'un système d'endormissement des processus

La fonction wait va permettre d'endormir un processus, elle va donné un temps de sommeil au processus puis lancer une interruption (car on veut changer de processus directement après que le précédent ait été endormi).

void wait(int time) { // met un process en veille pour une durée donné
	// valeur max de time 32000 car int sur atmega328p code sur 16 bits donc 32000 max
	cli();
	Tasks_list[Current_task].sleep_time = time;
	TCNT1=0;
	sei();
	TIMER1_COMPA_vect();

Pour verifier si un processus a besoin d'être lancé, on ajoute une boucle while dans le scheduler afin de vérifier si il y a un temps de sommeil. Le scheduler va aussi servir à diminuer le temps de sommeil de tout les processus (avec un minimum de 0).

void scheduler() {
	for (int i=0 ; i<NBTASKS ; i++) {
		if (Tasks_list[i].sleep_time - SLEEP_DEC < 0) {
			Tasks_list[i].sleep_time = 0;
		} else {
			Tasks_list[i].sleep_time -= SLEEP_DEC;
		}
	}


	do {
		Current_task++;
		if (Current_task >= NBTASKS) {
			Current_task = 0;
		}
	} while (Tasks_list[Current_task].sleep_time > 0);
}

Tableau dynamique des tâches

En premier lieu on va initialiser les état de tout les cases du tableau de processus. On choisit 1 la case est vide, 0 sinon.

void init_state() { //initialise state
	for (int i= 0; i<NBTASKS; i++) {
		if (Tasks_list[i].functionAddress == NULL) {
			Tasks_list[i].state = 1;
		}
		else {
			Tasks_list[i].state = 0;
		}
	}
}

Nous avons programmé deux fonction d'arrêt et une fonction d'ajout de processus :

-finish stop le processus en cours et le supprime de tableau, puis lance une interruption

-kill stop un processus donné et le supprime du tableau

-add ajoute un processus sur la première case vide du tableau, initialise son pointeur de position et sa pile.

Pour finish et kill, on met le temps de sommeil à 0 au cas où on arrête un processus endormi. C'est une mesure de sécurité pour finish, mais une obligation pour kill.

void finish() { //supprime un process lorsque celui-ci est terminé
	cli();
		Tasks_list[Current_task].state =1;
		Tasks_list[Current_task].sleep_time = 0;
		Tasks_list[Current_task].functionAddress = NULL;
	TCNT1=0; 
	sei();
	TIMER1_COMPA_vect();
}

void kill(void (*function)(void)) { //tue le process passé en paramètre
	cli();
	for(int i = 0; i<NBTASKS; i++) {
		if(Tasks_list[i].functionAddress == function) {
			Tasks_list[i].state =1;
			Tasks_list[i].sleep_time = 0;
			Tasks_list[i].functionAddress = NULL;
		}
	} 
	sei();
}


void add(void (*newFunction)(void)) { //ajoute un process
	cli();
	int i =0;
	while(Tasks_list[i].state != 1) {
		i++;
	}
	Tasks_list[i].functionAddress = newFunction;
	Tasks_list[i].state = 0;
	init_stackPointer_tasks(i);
	init_piles(i);

	sei();
	
}

Il suffit maintenant de modifier le scheduler afin de ne pas prendre les processus terminé, puis utiliser add, finish et kill dans les fonctions choisis.

test

Pour tester les fonction add et finish, on fait le test suivant :

-on créer un process qui ajoute le process led2_blink (avec add) au bout d'un certain temps

-le process led2_blink s'arrête (avec finish) au bout de 20 changement d'état (c'est à dire 10 clignotement).


on obtient le résultat suivant :

lecture et écriture sur le port série

Afin de lire et écrire sur le port série, on utilise les fonctions USART_Init, USART_Transmit et USART_Receive vu en cours de microP l'année précédente. Le but sera donc d'écrire le message qui vient d'être reçu, on utilise donc le processus suivant :

void Serial_Message(){
    unsigned char data;
    while(1){
         data = USART_Receive();
        USART_Transmit(data);
    }
}
test

Pour tester notre processus de communication, on utilise minicom.

On notera que si la période entre les interruptions est trop élevé, certains caractères ne seront pas lus.

afficheur 7 segments

void seven_seg() {
	while (1) {
        spi_activer((uint16_t)&PORTB, CS4);
		spi_echange(0x7E);
		spi_echange(0b1101111);
		spi_desactiver((uint16_t)&PORTB, CS4);

		wait(500);

		spi_activer((uint16_t)&PORTB, CS4);
		spi_echange(0x76);
		spi_desactiver((uint16_t)&PORTB, CS4);

		wait(500);
    	}
}

Le processus seven_seg est ajouté au tableau des processus. Avant d'échanger en spi il faut activer la communication, et la désactiver après l'échange. La ligne 4 permet de sélectionner le 7 seg sur lequel on souhaite modifier l'affichage. La ligne 5 permet d'afficher le chiffre 9. Enfin la ligne 11 permet de clear le display. Voir tableau ci-dessous.

Tab 7 seg sparkfun.png
void spi_init(void) {                                 // Initialisation du bus SPI
	SPI_DDR |= (1<<SPI_MOSI)|(1<<SPI_SCK);   // Définition des sorties
	SPI_DDR &= ~(1<<SPI_MISO);                           // Définition de l'entrée                          
	SPCR = (1<<SPE)|(1<<MSTR)|(1<<SPR1);                 // Activation SPI (SPE) en état maître (MSTR)
        	                                             // horloge F_CPU/64 (SPR1=1,SPR0=0)
	SPI_DDR |= (1<<CS1) | (1<<CS4);;
	DDRD |= (1<<CS5) | (1<<CS6);
	DDRC |= (1<<CS2) | (1<<CS3);
}


void spi_activer(uint16_t port, uint8_t cs) { 	// Activer le périphérique
	volatile uint8_t *reg = (volatile uint8_t*)port;
	*reg &= ~(1<<cs);                            // Ligne SS à l'état bas
}


void spi_desactiver(uint16_t port, uint8_t cs) {                           // Désactiver le périphérique
	volatile uint8_t *reg = (volatile uint8_t*)port;
	*reg |= (1<<cs);                             // Ligne SS à l'état haut
}


uint8_t spi_echange(uint8_t envoi) {                  // Communication sur le bus SPI
	SPDR = envoi;                                        // Octet a envoyer
	while(!(SPSR & (1<<SPIF)));                          // Attente fin envoi (drapeau SPIF du statut)
	return SPDR;                                         // Octet reçu
}

Afin de rendre nos fonctions les plus réutilisables possibles nous avons passer en paramètre le port ainsi que le CS qui correspond au slave select. Le type réel du port est volatile uint8* cependant le compilateur n'accepte que le uint16* ce qui explique le cast un peu étrange.

Implémentation

L'implémentation correspond à nos attentes, seul un 7 seg affiche le chiffre 9.

Carte Réseau

Lors de la répartition des tâches, nous avons opté pour la carte réseau.

Description

Le sujet nous permettait de choisir parmi plusieurs microcontrôleurs. Nous avons retenu l’ATmega32U4, qui nous semble représenter un juste milieu entre l’ATmega16U2 et l’AT90USB.

Hardware

Notre carte intègre des LED permettant d’afficher différents états de communication (que nous définirons ultérieurement).

À la demande de M. Redon, nous avons ajouté un MAX232 ainsi que des connecteurs DB9 et DB25. Nous espérons que ces éléments pourront être utiles.

Schématque

Schématique CReseau.png

Routage

Routage Creseau.png