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

De projets-se.plil.fr
Aller à la navigation Aller à la recherche
 
(26 versions intermédiaires par 2 utilisateurs non affichées)
Ligne 1 : Ligne 1 :
'''<big>Carte Son</big>''' pour un pico ordinateur
'''<big>Carte Son</big>''' pour un pico ordinateur : lien git [https://gitea.plil.fr/vsouopme/SE4-PICO-B4.git SE4-PICO-B4.git]




== <big>Présentation projet</big>  ==
Dans ce projet, il est question pour nous de réaliser un pico ordinateur composé de différentes cartes fille. Notre groupe avait donc la responsabilité de réaliser la carte son du pico ordinateur.
<p style="clear: both;" />


=== <big>Code source</big> ===
<big>'''Présentation projet'''</big>
Pour suivre l'avancée du travail, les fichiers seront déposés sur le repo suivant : [https://gitea.plil.fr/vsouopme/SE4-PICO-B4.git SE4-PICO-B4.]
<p style="clear: both;" />


Dans le cadre du cursus Systèmes Embarqués (SE4), ce projet consiste à concevoir un système capable de générer des signaux sonores de manière autonome. L'objectif est de transformer une valeur numérique calculée par un programme en une tension électrique réelle pour faire vibrer un haut-parleur.


Le système repose sur deux éléments principaux :


== CARTE SON ==
La carte Shield Arduino(PICO) : Équipée d'un microcontrôleur ATmega32U4, elle assure l'intelligence du système et l'ordonnancement des tâches.
La première étape est de réaliser le schema et le routage de notre carte.  
La carte fille son : Elle contient le dispositif de conversion numérique-analogique (DAC) et l'étage d'amplification.


Pour notre carte, nous avons pris comme composants :


* Un microphone
== '''Shield''' ==
* Un microprocesseur
* Un haut parleur
* Un USB données et alimentation
* AVR ISP


== Shield ==
=== <small>Réalisation du shield arduino</small> ===
Nous avons réalisé un bouclier pour Arduino Uno afin d'implémenter un système d'ordonnancement, ce qui nous permettra de simuler le fonctionnement d'une carte mère.


==== Réalisation ====
==== <small>Schématique - Routage et vue 3D</small> ====
{| class="wikitable mw-collapsible"
{| class="wikitable mw-collapsible"
|+
|+
![[Fichier:PicoShield - schema vue3D SE4-2025 G4.png|alt=PicoShield - schema électrique SE4-2025|centré|vignette|PicoShield - schema vue3D SE4-2025 G4]]
![[Fichier:PicoShield - schema vue3D SE4-2025 G4.pdf.pdf|alt=PicoShield - schema électrique SE4-2025|centré|vignette|PicoShield - schema vue3D SE4-2025 G4]]
![[Fichier:PicoShield pcb schema routage se4 pico 4 2025.png|vignette|PicoShield - schema routage SE4-2025 G4]]
![[Fichier:PicoShield pcb schema routage se4 pico 4 2025.png|vignette|PicoShield - schema routage SE4-2025 G4]]
|-
|-
Ligne 37 : Ligne 30 :




== '''Carte Son''' ==


==== Réalisation ====
 
 
== '''Ordonnanceur / Système d'exploitation''' ==
Le code implémente un '''mini ordonnanceur préemptif''' sur Arduino, qui alterne deux tâches toutes les 20 ms grâce à un timer et à son ISR.
 
Chaque tâche a sa propre pile et son contexte CPU.
 
=== Code de l'ordonnanceur ===
Un ordonnanceur sert à gérer l'exécution des tâches dans un système temps réel, en assurant qu'elles s'exécutent dans un ordre optimal et respectent les délais.
 
 
 
On sauvegarde les registres et le registre d'etat SREG, pour ensuite les restaurer dans l'ordre inverse.
 
le programme définit deux macros assembleur :
 
* SAVE_REGISTERS (): sauvegarde l’ensemble des registres généraux ainsi que le registre d’état <code>SREG</code>,
* RESTORE_REGISTERS () : restaure ces mêmes registres dans l’ordre inverse.
 
Cette sauvegarde manuelle est rendue nécessaire par l’utilisation d’une interruption déclarée avec l’attribut <code>ISR_NAKED</code>,<syntaxhighlight lang="c">
#define SAVE_REGISTERS()  \
        asm volatile (  \
        "push r0 \n\t" \
        "in r0,__SREG__ \n\t push r0 \n\t" \
        "push r1 \n\t push r2 \n\t push r3 \n\t push r4 \n\t push r5 \n\t push r6 \n\t push r7 \n\t push r8 \n\t push r9                        \n\t" \
        "clr  r1 \n\t" \
        "push r10 \n\t push r11 \n\t push r12 \n\t push r13 \n\t push r14 \n\t push r15 \n\t push r16 \n\t push r17 \n\t push r18 \n\t push r19 \n\t" \
        "push r20 \n\t push r21 \n\t push r22 \n\t push r23 \n\t push r24 \n\t push r25 \n\t push r26 \n\t push r27 \n\t push r28 \n\t push r29 \n\t" \
        "push r30 \n\t push r31 \n\t" \
        );
 
#define RESTORE_REGISTERS() \
        asm volatile ( \
        "pop r31 \n\t pop r30 \n\t" \
        "pop r29 \n\t pop r28 \n\t pop r27 \n\t pop r26 \n\t pop r25 \n\t pop r24 \n\t pop r23 \n\t pop r22 \n\t pop r21 \n\t pop r20 \n\t" \
        "pop r19 \n\t pop r18 \n\t pop r17 \n\t pop r16 \n\t pop r15 \n\t pop r14 \n\t pop r13 \n\t pop r12 \n\t pop r11 \n\t pop r10 \n\t" \
        "pop r9 \n\t pop r8 \n\t pop r7 \n\t pop r6 \n\t pop r5 \n\t pop r4 \n\t pop r3 \n\t pop r2 \n\t pop r1                      \n\t" \
        "pop r0 \n\t out __SREG__,r0 \n\t" \
        "pop r0 \n\t" \
        );
</syntaxhighlight>ici on definit l'etat et la structure des  taches
 
Les tâches peuvent se trouver dans deux états distincts :
 
* '''Ready''' : la tâche est prête à être exécutée
* '''Sleep''' : la tâche est temporairement suspendue
 
Chaque tâche est représentée par une structure '''Process''' contenant les informations nécessaires à sa gestion :
 
* un pointeur vers la fonction à exécuter,
* l’adresse du pointeur de pile associé à la tâche,
* l’état courant de la tâche,
* le temps de sommeil restant avant son réveil.
 
Cette structure constitue le bloc fondamental de l’ordonnanceur.<syntaxhighlight lang="c">
typedef enum{
        Ready,
        Sleep,
}task_state;
 
#if 0
struct task {
        uint16_t function;
        uint16_t stack;
};
#endif
 
typedef struct  {
        void(*function)(void);
        uint16_t stack;
        volatile task_state state;
        uint16_t sleepTime;
}process;
</syntaxhighlight>Tableau des taches, la tache idle qui s'execute si aucune tache n'est prete<syntaxhighlight lang="c">
void idle (void);
void task1 (void);
void task2 (void);
 
process Tasks [MAX_TASKS] = {
        {idle, STACKTOP-STACKSIZE,Ready,0},
        {task1, STACKTOP-2*STACKSIZE,Ready,0},
        {task2, STACKTOP-3*STACKSIZE,Ready,0},
        {0,0},
        {0,0},
        {0,0},
        {0,0},
        {0,0}
};
 
</syntaxhighlight>L'interruption déclenche une fonction de gestion d'interruption (ISR pour Interrupt Service Routine).
 
Cette interruption est déclenchée par le '''Timer1''' à intervalle régulier.
 
Elle sert à :
 
* Sauvegarder le contexte de la tâche courante
* Choisir la tâche suivante ('''scheduler''')
* Restaurer le contexte de la nouvelle tâche
<syntaxhighlight lang="c">
 
ISR(TIMER1_COMPA_vect,ISR_NAKED){    // Procédure d'interruption
SAVE_REGISTERS();
Tasks[current].stack=SP;
LED_PORT ^= (1<<LED1_BIT);
scheduler();
SP=Tasks[current].stack;
RESTORE_REGISTERS();
asm volatile ( "reti" );
}
 
 
</syntaxhighlight>
 
=== Fonction `wait()` ===
Comme première amélioration de votre ordonnanceur, il nous a été demandé de mettre en place un état endormi pour nos processus. Pour cela nous avons créer la procédure wait() qui a pour role :
 
* De mettre une tache en pause pendant une duree donnee (avec Delay), tout ca sans bloquer les autres taches
<syntaxhighlight lang="c">
void wait(uint16_t delay) {
    Tasks[current].sleepTime = delay;
    Tasks[current].state = Sleep;
    TCNT1=nb_ticks-1;
    while(Tasks[current].state == Sleep);
}
 
</syntaxhighlight>Par la suite, on a ajouter  les fonctions
 
* add_task :  Fonction qui sert a ajouter une nouvelle tache au système
* remove_task : Fonction pour supprimer (desactiver) une tâche existante
 
Ensuite dans la fonction principale, on
 
* Configure les Leds LED1 et LED2 comme sortie
 
* Initialise les  tâches       
 
* Initialise le minuteur (timer)
* Charge la pile de la  tâche courante
* Restaure les registres
* Lance la  tâche avec '''reti'''
<syntaxhighlight lang="c">
//  Fonction pour ajouter une tâche
 
 
void add_task(void (*task_function)(void)) {
    for (int n = 0; n < MAX_TASKS; n++) {
        if (Tasks[n].function == 0) {
            Tasks[n].function = task_function;
            Tasks[n].stack = (uint16_t)(STACKTOP - n * STACKSIZE);
            Tasks[n].state = Ready;
            sei();
        }
    }
}
 
// Fonction pour supprimer une tâche
void remove_task(int n) {
    if (n >= 0 && n < MAX_TASKS) {
        Tasks[n].function = 0;  // Supprime la tâche en réinitialisant la fonction
        Tasks[n].state = Sleep; // Met la tâche en état de sommeil
    }
}
 
 
 
int main(void)
{
LED_DIR |= (1<<LED1_BIT)|(1<<LED2_BIT);
LED_PORT &=  ~((1<<LED1_BIT)|(1<<LED2_BIT));
 
for(int i=0; i<MAX_TASKS; i++){
        if (Tasks[i].function!=0) init_stack(i);
      // Tasks[i].stack = (uint16_t)(STACKTOP - (i + 1) * STACKSIZE);
}
init_minuteur(256,PERIODE);
SP = Tasks[current].stack;
RESTORE_REGISTERS();
//sei();
asm volatile ( "reti" );
while(1);
 
return 0;
}
 
</syntaxhighlight>[[Fichier:Clignotement de la led.mov|vignette|gauche]]
 
 
 
 
 
 
 
 
 
 
=='''Carte Son'''==
 
=== <small>Description</small> ===
Notre carte fille son sera alimentée par la carte mère.
 
Elle comporte une ATmega328p comme coeur et sa spécificité est un convertisseur numérique vers analogique dit R-2R.
 
Pour fonctionner, elle a besoin :
 
* d'un microphone
* d'un haut-parleur pour jouer le son,
* d'un connecteur USB permettant la configuration de l'appareil via une connexion a un ordinateur .
 
=== <small>Schématique - routage et vue 3D</small> ===
{| class="wikitable mw-collapsible"
{| class="wikitable mw-collapsible"
|+
|+
Ligne 48 : Ligne 248 :
|[[Fichier:SoundCard pcb verso se4 pico 4 2025.png|alt=Carte son - schema vue3D verso SE4-2025 G4|vignette|Carte son - schema vue3D verso SE4-2025 G4]]
|[[Fichier:SoundCard pcb verso se4 pico 4 2025.png|alt=Carte son - schema vue3D verso SE4-2025 G4|vignette|Carte son - schema vue3D verso SE4-2025 G4]]
|}
|}
<big>'''Carte empruntée'''</big>
Suite à des défauts de conception sur notre propre PCB, nous utilisons la carte du [https://wiki-se.plil.fr/mediawiki/index.php/I2L_2023_Groupe2 Groupe 2 (I2L 2023/24)]'''.
* '''Adaptation''' : Nous avons dû identifier que cette carte utilise également le **PORTD** pour son réseau R-2R.
* '''Contrainte :''' Cette carte n'était pas prévue pour notre châssis PICO original, ce qui a nécessité une vérification des broches pour éviter tout court-circuit.
'''Architecture logicielle'''
Le code est divisé en 2 modules pour plus de clarté :
* <code>main.c</code> : Point d'entrée, initialise le système et lance les tâches.
* <code>scheduler.c</code> : L'ordonnanceur qui gère le multitâche "souple".
* <code>audio_card.c</code> : Le pilote bas-niveau qui communique avec le matériel.
'''Pilote audio'''
Le pilote utilise le **Timer 2** du microcontrôleur. Ce timer est réglé à '''10 kHz'''. À chaque "tic", il déclenche une fonction qui fait une seule chose : <code>PORTD = échantillon</code>. C'est l'écriture directe, la méthode la plus rapide possible sur AVR.
<blockquote></blockquote>

Version actuelle datée du 15 janvier 2026 à 10:40

Carte Son pour un pico ordinateur : lien git SE4-PICO-B4.git


Présentation projet

Dans le cadre du cursus Systèmes Embarqués (SE4), ce projet consiste à concevoir un système capable de générer des signaux sonores de manière autonome. L'objectif est de transformer une valeur numérique calculée par un programme en une tension électrique réelle pour faire vibrer un haut-parleur.

Le système repose sur deux éléments principaux :

La carte Shield Arduino(PICO) : Équipée d'un microcontrôleur ATmega32U4, elle assure l'intelligence du système et l'ordonnancement des tâches. La carte fille son : Elle contient le dispositif de conversion numérique-analogique (DAC) et l'étage d'amplification.


Shield

Réalisation du shield arduino

Nous avons réalisé un bouclier pour Arduino Uno afin d'implémenter un système d'ordonnancement, ce qui nous permettra de simuler le fonctionnement d'une carte mère.

Schématique - Routage et vue 3D

PicoShield - schema électrique SE4-2025
PicoShield - schema vue3D SE4-2025 G4
PicoShield - schema routage SE4-2025 G4
PicoShield - schema vue3D SE4-2025 G4
PicoShield - schema vue3D SE4-2025 G4




Ordonnanceur / Système d'exploitation

Le code implémente un mini ordonnanceur préemptif sur Arduino, qui alterne deux tâches toutes les 20 ms grâce à un timer et à son ISR.

Chaque tâche a sa propre pile et son contexte CPU.

Code de l'ordonnanceur

Un ordonnanceur sert à gérer l'exécution des tâches dans un système temps réel, en assurant qu'elles s'exécutent dans un ordre optimal et respectent les délais.


On sauvegarde les registres et le registre d'etat SREG, pour ensuite les restaurer dans l'ordre inverse.

le programme définit deux macros assembleur :

  • SAVE_REGISTERS (): sauvegarde l’ensemble des registres généraux ainsi que le registre d’état SREG,
  • RESTORE_REGISTERS () : restaure ces mêmes registres dans l’ordre inverse.

Cette sauvegarde manuelle est rendue nécessaire par l’utilisation d’une interruption déclarée avec l’attribut ISR_NAKED,

#define SAVE_REGISTERS()  \
        asm volatile (  \
         "push r0 \n\t" \
         "in r0,__SREG__ \n\t push r0 \n\t" \
         "push r1 \n\t push r2 \n\t push r3 \n\t push r4 \n\t push r5 \n\t push r6 \n\t push r7 \n\t push r8 \n\t push r9                        \n\t" \
         "clr  r1 \n\t" \
         "push r10 \n\t push r11 \n\t push r12 \n\t push r13 \n\t push r14 \n\t push r15 \n\t push r16 \n\t push r17 \n\t push r18 \n\t push r19 \n\t" \
         "push r20 \n\t push r21 \n\t push r22 \n\t push r23 \n\t push r24 \n\t push r25 \n\t push r26 \n\t push r27 \n\t push r28 \n\t push r29 \n\t" \
         "push r30 \n\t push r31 \n\t" \
        );

#define RESTORE_REGISTERS() \
        asm volatile ( \
         "pop r31 \n\t pop r30 \n\t" \
         "pop r29 \n\t pop r28 \n\t pop r27 \n\t pop r26 \n\t pop r25 \n\t pop r24 \n\t pop r23 \n\t pop r22 \n\t pop r21 \n\t pop r20 \n\t" \
         "pop r19 \n\t pop r18 \n\t pop r17 \n\t pop r16 \n\t pop r15 \n\t pop r14 \n\t pop r13 \n\t pop r12 \n\t pop r11 \n\t pop r10 \n\t" \
         "pop r9 \n\t pop r8 \n\t pop r7 \n\t pop r6 \n\t pop r5 \n\t pop r4 \n\t pop r3 \n\t pop r2 \n\t pop r1                       \n\t" \
         "pop r0 \n\t out __SREG__,r0 \n\t" \
         "pop r0 \n\t" \
        );

ici on definit l'etat et la structure des taches

Les tâches peuvent se trouver dans deux états distincts :

  • Ready : la tâche est prête à être exécutée
  • Sleep : la tâche est temporairement suspendue

Chaque tâche est représentée par une structure Process contenant les informations nécessaires à sa gestion :

  • un pointeur vers la fonction à exécuter,
  • l’adresse du pointeur de pile associé à la tâche,
  • l’état courant de la tâche,
  • le temps de sommeil restant avant son réveil.

Cette structure constitue le bloc fondamental de l’ordonnanceur.

typedef enum{
        Ready,
        Sleep,
}task_state;

#if 0
struct task {
        uint16_t function;
        uint16_t stack;
};
#endif

typedef struct  {
        void(*function)(void);
        uint16_t stack;
        volatile task_state state;
        uint16_t sleepTime;
}process;

Tableau des taches, la tache idle qui s'execute si aucune tache n'est prete

void idle (void);
void task1 (void);
void task2 (void);

process Tasks [MAX_TASKS] = {
        {idle, STACKTOP-STACKSIZE,Ready,0},
        {task1, STACKTOP-2*STACKSIZE,Ready,0},
        {task2, STACKTOP-3*STACKSIZE,Ready,0},
        {0,0},
        {0,0},
        {0,0},
        {0,0},
        {0,0}
};

L'interruption déclenche une fonction de gestion d'interruption (ISR pour Interrupt Service Routine).

Cette interruption est déclenchée par le Timer1 à intervalle régulier.

Elle sert à :

  • Sauvegarder le contexte de la tâche courante
  • Choisir la tâche suivante (scheduler)
  • Restaurer le contexte de la nouvelle tâche
ISR(TIMER1_COMPA_vect,ISR_NAKED){    // Procédure d'interruption
SAVE_REGISTERS();
Tasks[current].stack=SP;
LED_PORT ^= (1<<LED1_BIT);
scheduler();
SP=Tasks[current].stack;
RESTORE_REGISTERS();
asm volatile ( "reti" );
}

Fonction `wait()`

Comme première amélioration de votre ordonnanceur, il nous a été demandé de mettre en place un état endormi pour nos processus. Pour cela nous avons créer la procédure wait() qui a pour role :

  • De mettre une tache en pause pendant une duree donnee (avec Delay), tout ca sans bloquer les autres taches
void wait(uint16_t delay) {
    Tasks[current].sleepTime = delay;
    Tasks[current].state = Sleep;
    TCNT1=nb_ticks-1;
    while(Tasks[current].state == Sleep);
}

Par la suite, on a ajouter les fonctions

  • add_task : Fonction qui sert a ajouter une nouvelle tache au système
  • remove_task : Fonction pour supprimer (desactiver) une tâche existante

Ensuite dans la fonction principale, on

  • Configure les Leds LED1 et LED2 comme sortie
  • Initialise les tâches
  • Initialise le minuteur (timer)
  • Charge la pile de la tâche courante
  • Restaure les registres
  • Lance la tâche avec reti
//  Fonction pour ajouter une tâche


void add_task(void (*task_function)(void)) {
    for (int n = 0; n < MAX_TASKS; n++) {
        if (Tasks[n].function == 0) {
            Tasks[n].function = task_function;
            Tasks[n].stack = (uint16_t)(STACKTOP - n * STACKSIZE);
            Tasks[n].state = Ready;
            sei();
        }
    }
}

// Fonction pour supprimer une tâche
void remove_task(int n) {
    if (n >= 0 && n < MAX_TASKS) {
        Tasks[n].function = 0;  // Supprime la tâche en réinitialisant la fonction
        Tasks[n].state = Sleep; // Met la tâche en état de sommeil
    }
}



int main(void)
{
LED_DIR |= (1<<LED1_BIT)|(1<<LED2_BIT);
LED_PORT &=  ~((1<<LED1_BIT)|(1<<LED2_BIT));

for(int i=0; i<MAX_TASKS; i++){
        if (Tasks[i].function!=0) init_stack(i);
       // Tasks[i].stack = (uint16_t)(STACKTOP - (i + 1) * STACKSIZE);
}
init_minuteur(256,PERIODE);
SP = Tasks[current].stack;
RESTORE_REGISTERS();
//sei();
asm volatile ( "reti" );
while(1);

return 0;
}






Carte Son

Description

Notre carte fille son sera alimentée par la carte mère.

Elle comporte une ATmega328p comme coeur et sa spécificité est un convertisseur numérique vers analogique dit R-2R.

Pour fonctionner, elle a besoin :

  • d'un microphone
  • d'un haut-parleur pour jouer le son,
  • d'un connecteur USB permettant la configuration de l'appareil via une connexion a un ordinateur .

Schématique - routage et vue 3D

PicoShield - schema électrique SE4-2025
Carte son - schema electrique SE4-2025 G4
Carte son - schema routage SE4-2025 G4
Carte son - schema routage SE4-2025 G4
Carte son - schema vue3D recto SE4-2025 G4
Carte son - schema vue3D recto SE4-2025 G4
Carte son - schema vue3D verso SE4-2025 G4
Carte son - schema vue3D verso SE4-2025 G4


Carte empruntée

Suite à des défauts de conception sur notre propre PCB, nous utilisons la carte du Groupe 2 (I2L 2023/24).

  • Adaptation : Nous avons dû identifier que cette carte utilise également le **PORTD** pour son réseau R-2R.
  • Contrainte : Cette carte n'était pas prévue pour notre châssis PICO original, ce qui a nécessité une vérification des broches pour éviter tout court-circuit.

Architecture logicielle

Le code est divisé en 2 modules pour plus de clarté :

  • main.c : Point d'entrée, initialise le système et lance les tâches.
  • scheduler.c : L'ordonnanceur qui gère le multitâche "souple".
  • audio_card.c : Le pilote bas-niveau qui communique avec le matériel.

Pilote audio

Le pilote utilise le **Timer 2** du microcontrôleur. Ce timer est réglé à 10 kHz. À chaque "tic", il déclenche une fonction qui fait une seule chose : PORTD = échantillon. C'est l'écriture directe, la méthode la plus rapide possible sur AVR.