Contexte et configuration

L’article compare deux implémentations sur un STM32F446ZET6 cadencé à 180 MHz. Le microcontrôleur est piloté par le HAL Rust et le SDK C de ST. Les mesures sont prises avec un oscilloscope Rigol DS1054Z. Les deux programmes exécutent trois tâches identiques : clignotement d’une LED (200 ms / 100 ms), lecture d’un bouton via interruption GPIO et transmission d’un message sur le port série. Les critères évalués sont la latence d’interruption, la taille du binaire, la consommation RAM et la facilité de développement.

Mécanisme d’exécution async vs threads

En Rust, la fonction async fn est transformée en une machine d’état implémentant le trait

pub trait Future { type Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll; }
. Chaque appel à poll avance la machine jusqu’à un état Pending ou Ready. Les wakers réveillent la tâche lorsqu’un événement (par ex. une interruption EXTI) se produit. Embassy impose une allocation statique : toutes les tâches sont connues à la compilation, aucune heap n’est utilisée, et le compilateur nightly est requis pour le feature type_alias_impl_trait. L’exécution est coopérative ; le changement de tâche n’intervient que lorsqu’une tâche await un futur.

FreeRTOS, à l’inverse, crée des threads préemptifs. Chaque thread possède son propre contexte CPU (registres, pile) sauvegardé lors d’un changement de contexte. Le noyau planifie les threads selon leurs priorités, ce qui garantit une réponse temporelle prévisible mais impose un coût de commutation plus élevé et nécessite une allocation de pile pour chaque thread.

Mesures de performance

La latence d’interruption du bouton est mesurée en mettant le pin de mesure haut à l’entrée de l’ISR et bas à la sortie. Sur l’implémentation Embassy, la latence moyenne observée est d’environ 3,2 µs, alors que FreeRTOS atteint 5,7 µs, reflétant le temps supplémentaire de sauvegarde/restauration du contexte. Le temps d’attente du thread (ou de la tâche) entre deux appels await ou vTaskDelay montre une différence similaire : 2,9 µs pour Embassy contre 4,4 µs pour FreeRTOS.

En termes de taille binaire, le firmware Rust compile à 78 KB, alors que le binaire C/FreeRTOS occupe 92 KB, principalement à cause du runtime C et des tables de vecteurs du noyau. La RAM utilisée est de 6,4 KB pour Embassy (tâches statiques, heap nul) contre 8,1 KB pour FreeRTOS (piles de threads et structures du scheduler).

Analyse des contraintes et de la programmation

Embassy élimine le besoin d’un allocateur, ce qui simplifie la validation de la sécurité mémoire mais impose une contrainte de conception : chaque tâche doit être déclarée à la compilation, limitant la flexibilité dynamique. Le modèle coopératif évite les inversions de priorité, mais requiert que chaque point d’attente soit correctement placé pour garantir la réactivité.

FreeRTOS offre une préemption native, permettant à des tâches critiques de prendre le contrôle immédiatement, mais le coût de commutation et la nécessité de gérer les piles augmentent la surface d’erreur. Le développement en C reste plus familier pour les ingénieurs embarqués, alors que Rust impose l’apprentissage du système de futures et du modèle d’emprunt, ce qui peut rallonger le temps de mise en œuvre initial.