SE4Binome2025-2

De projets-se.plil.fr
Aller à la navigation Aller à la recherche

Objectif

L'objectif du projet est de concevoir un pico-ordinateur complet, intégrant :

  • Une carte mère basée sue le microcontrôleur AT90USB1286

Une partie logicielle permettant l'éxecution de de commandes telles que ls, cp ou mv

Shield Arduino

Une première étape du projet a consisté à développer un shield pour Aduino uno, servant de plateforme de test et de développement pour les cartes filles SPI.

Fonctionalités:

  • Connexion de 5 périphériques SPI via des cartes filles.
  • Gestion des signaux Reset et Interruption.
  • Ajout d'une mémoire externe carte micro-SD via un connecteur Molex 10431.
  • Adaptation des niveaux logiques (5V a 3,3V) grâce à la puce 74LV125.

Ce shield joue le rôle de plateforme de développement temporaire, en attendant la carte mère du pico-ordinateur.

Schématique et routage

Schema shield arduino.jpg

Objectif

Schema shield arduino


Routage shield arduino


Carte mère

Schématique

Schéma carte mère du pico ordinateur



Firmware — RTOS

1. Vue d'ensemble du système

Le firmware implémente un système d'exploitation temps réel (RTOS) coopératif optimisé pour les microcontrôleurs AVR (ATmega328P/AT90USB1286).

Architecture coopérative vs préemptive :

  • Coopératif : Les tâches cèdent volontairement le contrôle (via task_yield(), task_sleep())
  • Avantage : Plus simple, moins de surcharge mémoire, pas besoin de sauvegarde de contexte
  • Inconvénient : Une tâche malveillante peut bloquer le système

Optimisations mémoire :

  • Allocation statique uniquement (pas de malloc/free)
  • Taille fixe des structures pour prédictibilité
  • Piles partagées entre noyau et applications

2. Arborescence (extrait)

firmware/
 ├─ kernel/                 # Cœur du système d'exploitation
 │  ├─ kernel.h            # API publique du noyau
 │  ├─ kernel.c            # Gestion système (ticks, délais)
 │  ├─ scheduler.h         # Déclarations de l'ordonnanceur  
 │  ├─ scheduler.c         # Implémentation round-robin
 │  ├─ task.h              # Structures de contrôle des tâches
 │  └─ task.c              # Création et gestion des tâches
 ├─ config.h               # Configuration matérielle (mémoire, timers)
 └─ main.c                 # Tâches applicatives (horloge binaire)

Séparation des responsabilités :

  • kernel/ : Fonctionnalités système bas niveau
  • config.h : Adaptation au matériel spécifique
  • main.c : Logique métier de l'application

3. Paramètres de configuration

#define MAX_TASKS       4      // Compromis fonctionnalité/mémoire
#define STACK_SIZE      96     // Suffisant pour appels de fonctions + variables locales
#define TICK_FREQUENCY  100    // 100Hz = résolution 10ms (équilibre précision/charge CPU)
#define TASK_NAME_LEN   8      // Noms courts pour économiser la RAM

Justification des valeurs :

  • MAX_TASKS=4 : Permet tâche système + 3 tâches applicatives dans 2KB RAM
  • STACK_SIZE=96 : Empilement typique des appels AVR + marge de sécurité
  • TICK_FREQUENCY=100 : Temps de réponse <10ms sans surcharger le CPU

4. Structure de contrôle de tâche (TCB)

typedef enum {
    TASK_INVALID = 0,
    TASK_READY,
    TASK_RUNNING,
    TASK_SLEEPING
} task_state_t;

typedef struct {
    void (*function)(void *);
    void *arg;
    uint8_t *stack_base;
    task_state_t state;
    uint16_t sleep_ticks;
    uint8_t priority;
    char name[TASK_NAME_LEN];
} task_t;

Cycle de vie d'une tâche :

CRÉATION → [READY] ↔ [RUNNING] → [SLEEPING] → [READY]
                    ↓
                 [COMPLETED] (si task_exit() appelée)

5. Gestion des sections critiques (imbriquées)

#include <avr/interrupt.h>
static volatile uint8_t critical_nesting = 0;

static inline void enter_critical(void) {
    cli();
    critical_nesting++;
}

static inline void leave_critical(void) {
    if (critical_nesting > 0) {
        critical_nesting--;
        if (critical_nesting == 0) sei();
    }
}

Protection des ressources partagées :

  • Empêche les accès concurrents aux structures partagées (task_table, variables globales)
  • Imbrication : Appels imbriqués autorisés
  • Performance : Sections critiques très courtes (<1ms)

6. Création de tâche

int8_t task_create(const char *name, void (*function)(void *), void *arg,
                   uint8_t priority, uint8_t *stack_buffer)
{
    enter_critical();
    
    for (uint8_t i = 0; i < MAX_TASKS; i++) {
        if (task_table[i].function == NULL) {
            task_table[i].function = function;
            task_table[i].arg = arg;
            task_table[i].stack_base = stack_buffer;
            task_table[i].state = TASK_READY;
            task_table[i].priority = priority;
            strncpy(task_table[i].name, name, TASK_NAME_LEN);
            
            leave_critical();
            return i;
        }
    }
    
    leave_critical();
    return -1;
}

Contraintes :

  • Appelée avant scheduler_start()
  • Piles statiques uniquement
  • Noms tronqués à TASK_NAME_LEN

7. Ordonnanceur (round-robin)

static uint8_t current_task_id = 0;

static inline uint8_t is_task_valid(uint8_t id) {
    return (id < MAX_TASKS) &&
           (task_table[id].function != NULL) &&
           (task_table[id].state == TASK_READY);
}

static uint8_t get_next_task(void) {
    uint8_t next = (current_task_id + 1) % MAX_TASKS;
    for (uint8_t i = 0; i < MAX_TASKS; i++) {
        if (is_task_valid(next)) return next;
        next = (next + 1) % MAX_TASKS;
    }
    return current_task_id;
}

void scheduler_run(void) {
    enter_critical();
    
    if (is_task_valid(current_task_id)) {
        task_table[current_task_id].state = TASK_RUNNING;
        leave_critical();
        
        task_table[current_task_id].function(task_table[current_task_id].arg);
        
        enter_critical();
        if (task_table[current_task_id].state == TASK_RUNNING)
            task_table[current_task_id].state = TASK_READY;
    }
    
    current_task_id = get_next_task();
    leave_critical();
}

Algorithme round-robin :

  • Parcours circulaire équitable
  • Recherche O(n)
  • Aucune sauvegarde de contexte (coopératif)

8. Mécanismes de sommeil et tick système

// Configuration du timer hardware pour 100Hz
void timer1_init_100hz(void) {
    TCCR1A = 0;
    TCCR1B = (1 << WGM12) | (1 << CS11) | (1 << CS10);
    OCR1A = 2499;
    TIMSK1 |= (1 << OCIE1A);
}

ISR(TIMER1_COMPA_vect) {
    extern volatile uint32_t system_ticks;
    system_ticks++;
    
    for (uint8_t i = 0; i < MAX_TASKS; i++) {
        if (task_table[i].state == TASK_SLEEPING && task_table[i].sleep_ticks > 0) {
            task_table[i].sleep_ticks--;
            if (task_table[i].sleep_ticks == 0)
                task_table[i].state = TASK_READY;
        }
    }
}

void task_sleep(uint16_t ticks) {
    if (ticks == 0) ticks = 1;
    
    enter_critical();
    task_table[current_task_id].sleep_ticks = ticks;
    task_table[current_task_id].state = TASK_SLEEPING;
    leave_critical();
    
    scheduler_run();
}

Gestion du temps :

  • Timer hardware 100Hz → tick 10ms
  • Pas de busy-waiting
  • Réveil automatique via ISR

9. Initialisation du système

void kernel_init(void) {
    memset(task_table, 0, sizeof(task_table));
    current_task_id = 0;
    task_count = 0;
    system_ticks = 0;
    
    timer1_init_100hz();
    uart_init();
    io_init();
    
    task_create("idle", idle_task, NULL, PRIORITY_IDLE, idle_stack);
    sei();
}

Séquence de boot :

  1. Réinitialisation
  2. Configuration hardware
  3. Création tâche idle
  4. Activation interruptions

10. Gestion d'erreurs

Stratégies de robustesse :

  • Validation des pointeurs
  • Sleep minimal
  • Tâche idle toujours active
if (function == NULL || stack_buffer == NULL) {
    return -1;
}

11. Estimations mémoire

Piles tâches      : 4 × 96 octets  = 384 octets
Structures TCB    : 4 × 28 octets  = 112 octets  
Variables globales: ~20 octets
TOTAL estimé      : 516 / 2048 (25%)

12. Intégration du build system

MCU = atmega328p
F_CPU = 16000000UL

CFLAGS = -mmcu=$(MCU) \
         -DF_CPU=$(F_CPU) \
         -Os -Wall -std=c99

all: firmware.hex
flash: firmware.hex
clean:

13. Comportements observables

kernel_init();
task_create("task1", ...);
task_create("task2", ...);
kernel_start();
void ma_tache(void* arg) {
    while (1) {
        faire_travail();
        task_sleep(100);
    }
}

14. Extensions possibles

  • Ordonnancement préemptif
  • Sémaphores / mutex
  • Files de messages
  • Gestion d’énergie

Ce RTOS démontre :

  • Les principes fondamentaux des systèmes d’exploitation embarqués
  • L’optimisation pour contraintes sévères (2KB RAM)
  • La coopération entre tâches sans protection matérielle
  • La gestion du temps réel à 10ms