SE4Binome2025-2
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
Objectif
Carte mère
Schématique
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 niveauconfig.h: Adaptation au matériel spécifiquemain.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 :
- Réinitialisation
- Configuration hardware
- Création tâche idle
- 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