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 (version restructurée et en français)

1. Vue d’ensemble du système

Le firmware implémente un petit RTOS coopératif optimisé en mémoire pour ATmega (ex. ATmega328P). Principales caractéristiques :

  • Ordonnanceur round-robin simple.
  • Table des tâches statique (pas d’allocation dynamique).
  • Gestion des sections critiques imbriquées.
  • Timer système à 100 Hz pour la gestion des ticks et du sommeil.

2. Arborescence (extrait)

firmware/
 ├─ kernel/
 │  ├─ kernel.h
 │  ├─ kernel.c
 │  ├─ scheduler.h
 │  ├─ scheduler.c
 │  ├─ task.h
 │  └─ task.c
 ├─ config.h
 └─ main.c

3. Paramètres de configuration

#define MAX_TASKS       4      // nombre max de tâches
#define STACK_SIZE      96     // octets par pile de tâche
#define TICK_FREQUENCY  100    // Hz — fréquence du tick système
#define TASK_NAME_LEN   8

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;

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();
    }
}

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)
{
    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);
            return i;
        }
    }
    return -1;
}

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();
}

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

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();
}

9. Initialisation du système

  1. Mise à zéro des structures de données.
  2. Initialisation du timer à 100 Hz.
  3. Création de la tâche d’« idle ».
  4. Activation des interruptions globales.
  5. Lancement de la boucle principale qui exécute `scheduler_run()`.

10. Gestion d’erreurs

  • Vérification de la validité des pointeurs de fonctions.
  • Conversion des durées nulles (`sleep(0)`) en `sleep(1)`.
  • Gestion des appels imbriqués de sections critiques.
  • Fallback automatique sur la tâche idle si aucune tâche prête.

11. Estimations mémoire

  • Piles : 4 × 96 = 384 octets
  • TCB : 4 × 28 = 112 octets (approx.)
  • Variables globales : ~20 octets
  • **Total estimé :** ~516 octets

12. Intégration du build system

MCU = atmega328p
F_CPU = 16000000UL
CFLAGS = -mmcu=$(MCU) -DF_CPU=$(F_CPU) -Os -Wall -std=c99