Introduction
Cela fait un certain temps que ce blog se focalise exclusivement sur Linux, aussi est-il temps de revenir sur l'autre type d'OS que l'on rencontre dans le monde de l'embarqué : les OS temps réel. Parmi ceux-ci, nous allons aujourd'hui étudier ERIKA Enterprise, un noyau GPLv2 avec exception de linkage et RT-Druid, l'environnement Eclipse qui l'accompagne.
ERIKA Enterprise est un OS intéressant car c'est sans doute le seul OS Open Source certifié OSEK. Il est donc bien placé pour être utilisé dans le monde de l'automobile.
Il existe d'autres solutions telle que FreeOSEK ou encore Trampoline, mais celles-ci ne sont pas certifiées. Nous utiliserons pour cet article la carte STM32F4 Discovery.
OSEK/VDX
Destiné à fournir un standard dans l'automobile et permettre une meilleure portabilité, l'architecture proposée par cette norme s'articule sur trois couches, la communication (COM), gestion de réseau (NM) et le système d'exploitation (RTOS). Seul ce dernier point sera traité dans cet article.
OIL
ERIKA Enterprise est un OS à compilation statique, c'est à dire que tout l'applicatif est compilé avec le noyau. Contrairement à un OS classique, on ne peut pas lancer de programme. Le noyau est plus proche d'une bibliothèque de service que d'un système d'exploitation tel que nous les connaissons.
Pour ERIKA Enterprise, l'ensemble des services du système d'exploitation est défini statiquement à la compilation dans le fichier OIL (OSEK Implementation Language). OIL décrit les timers, tâches à effectuer et alarmes. Ce fichier définit également quel processeur doit exécuter quel processus.
Installation
Cet article va décrire comment utiliser ERIKA Enterprise pour générer un chenillard sur les GPIO d'une carte STM32 Discovery. Nous allons mettre en place un timer qui déclenchera une tâche. Cette tâche allumera et éteindra des GPIO auxquels nous aurons connecté des LED.
Nous utiliserons un environnement de développement Linux et une cible STM32 Discovery (ARM Cortex M4).
Sont nécessaires en plus :
- Un câble µUSB/USB
- Une chaîne de compilation ARM
- RT Druid Erika Enterprise
- Une version d'Eclipse avec les plugins
- L'outil ST-FLASH
Installation de la chaîne de compilation
Allez sur :
https://launchpad.net/gcc-arm-embedded
Sur la droite, différentes versions sont proposées, sélectionnez :
gcc-arm-none-eabi-4_9-2015q2-20150609-linux.tar.bz2
Décompressez-le dans un répertoire de votre espace de travail.
Installation de l'IDE et de RT-DRUID
Allez sur :
http://erika.tuxfamily.org/drupal/download.html
Sélectionnez la version qui vous correspond :
ERIKA Enterprise and RT-Druid 2.5.0, Linux 32bit version
ERIKA Enterprise and RT-Druid 2.5.0, Linux 64bit version
Décompressez-le dans un répertoire de votre espace de travail.
Vous pouvez utiliser votre version d'Eclipse mais vous devrez installer vous-même les plugins.
Sachez que d'autres outils sont disponibles, notamment pour la génération de code, pour le débogage ou pour l'utilisation sans Eclipse, mais ils ne seront pas abordés ici.
Installation de ST-FLASH
Il s'agit ici d'un reverse de la version officiel pour Windows.
Allez sur :
http://startingelectronics.org/tutorials/STM32-microcontrollers/programming-STM32-flash-in-Linux/
Et suivez les instructions.
Bien ! Vous êtes parés pour commencer à paramétrer.
Première application
Paramétrage d'Eclipse
Lancer Eclipse et sélectionner dans la barre outil windows/preference
puis RT-DRUID/ oil/Cortex
Dans la fenêtre, modifiez le GNU compiler path avec votre chemin:
/home/mon_nom/mes_chaines_de_compilation/gcc-arm-none-eabi-4_9-2015q (attention au copier coller, la version peut avoir changé)
Nous allons générer un premier exemple et vérifier que l'ensemble fonctionne correctement.
Sous Eclipse :
Dans File/ New/RT-Druid Oil and C/C++ project
Quand la nouvelle fenêtre s'ouvre,
Nommer votre projet et laissez Empty Project ainsi que –Other Toolchain--
Cliquez sur « Next »
Cochez « Create a project using... »
Sélectionnez Cortex-MX/STM32F4 Discovery Board/Demo GPIO
Le code
L'étape précédente a généré deux fichier :
- conf.oil qui contient l'architecture de l'application que nous voulons construire
- makefile qui contient les options de compilations générées à partir du conf.oil
conf.oil
Comme expliqué plus haut, il s'agit de la description statique des différents objets de l'OS.
CPU : Dans les OS temps réel, on définit les tâches et interruptions que doivent traiter chaque CPU de façon statique. Le fichier OIL contient un bloc CPU par cœur sur la cible et ce bloc contient une déclaration pour chaque objet actif (interrupt, tâche, tâche noyau) que ce cœur doit exécuter.
Dans le cas de notre carte, le STM32 est mono-cœur. Nous n'avons donc qu'un bloc CPU qui contient toutes nos déclarations.
CPU mySystem {
OS : Pour l'instant, on ne peut déclarer et paramétrer qu'un seul OS. Les paramètres relatifs à l'OS sont plutôt transparents. Pour information : nous utilisons ici un micro-contrôleur STM32F407 qui utilise un Cortex M4. La carte d'essai est une Discovery. CMSIS (Cortex Micro Controler Software Interface Standard) et un HAL (Hardware Abstraction Layer) en plus du débogueur qui nous permettra d'utiliser des interfaces sans (trop) nous soucier du matériel.Vous retrouverez donc ces différents mots clefs dans le paramétrage.
OS myOs { EE_OPT = "DEBUG"; //EE_OPT = "VERBOSE"; //EE_OPT = "__KEIL_4_54_OLDER__"; //EE_OPT = "__KEIL_USE_AXF_EXT__"; //LDFLAGS ="; CPU_DATA = CORTEX_MX { MODEL = M4; APP_SRC = "code.c"; //COMPILER_TYPE = KEIL; COMPILER_TYPE = GNU; MULTI_STACK = FALSE; }; EE_OPT = "__USE_SYSTICK__"; MCU_DATA = STM32 { MODEL = STM32F4xx; }; EE_OPT = "__ADD_LIBS__"; LIB = ENABLE { NAME = "ST_CMSIS"; }; LIB = ENABLE { NAME = "STM32F4XX_SPD"; }; LIB = ENABLE { NAME = "STM32F4_DISCOVERY"; }; STATUS = EXTENDED; STARTUPHOOK = FALSE; ERRORHOOK = FALSE; SHUTDOWNHOOK = FALSE; PRETASKHOOK = FALSE; POSTTASKHOOK = FALSE; USEGETSERVICEID = FALSE; USEPARAMETERACCESS = FALSE; USERESSCHEDULER = FALSE; KERNEL_TYPE = FP; };
COUNTER : Pour implémenter, les alarmes l'OS a besoin d'un ou plusieurs compteurs d'évènement.
Ici, nous utilisons un seul compteur « myCounter ».
COUNTER myCounter;
C'est à l'applicatif d'incrémenter le compteur avec l'API CounterTick.
L'incrémentation se fait généralement de façon périodique afin de créer une base de temps, le tick de l'OS.
L'incrémentation peut néanmoins être associée à n'importe quelle source d'évènement comme par exemple la détection d'une dent sur un capteur de rotation afin de créer une base angulaire très utile dans les applications de contrôle moteur par exemple.
Dans notre cas le compteur sera incrémenté toutes les 1ms grâce à une interruption périodique.
ISR systick_handler { CATEGORY = 2; ENTRY = "SYSTICK"; PRIORITY = 1; };
« systick_handler » est le symbol C de la routine d'interruption qui sera associé par l'OS au vecteur d'interruption « SYSTICK ».
Le mot clé « SYSTICK » est propre à la cible et désigne le vecteur d'interruption d'un timer.
Attention, la configuration de la source d'interruption n'est pas de la responsabilité de l'OS mais bien de l'application.
L'OS est uniquement responsable de construire la table de branchement des vecteurs d'interruption.
Dans le cas de notre application, le timer est configuré pour déclencher l'interruption toutes les 1ms.
/*Initialize systick */ EE_systick_set_period(MILLISECONDS_TO_TICKS(1, SystemCoreClock)); EE_systick_enable_int(); EE_systick_start();
Bien entendu l'implémentation de la routine d'interruption reste de la responsabilité de l'application.
Ici l'API CounterTick pour le compteur « myCounter » est appelée, permettant ainsi de mettre à jour ce compteur et toutes les alarmes qui s'y rattachent.
ISR2(systick_handler) { /* count the interrupts, waking up expired alarms */ CounterTick(myCounter); }
Attention: Il existe 2 catégories d'interruptions:
- CATEGORY = 1. La routine d'interruption n'est pas autorisée à faire des appels aux services de l'OS.
- CATEGORY = 2. La routine d'interruption peut faire appel à des services de l'OS comme ActivateTask, SetEvent, Get/ReleaseResource... et est donc capable d'interagir avec les autres interruption de catégorie 2 et les tâches.
ALARM: Nous associons à notre compteur un objet Alarm. Cet objet « sonne » lorsque le compteur associé a atteint sa limite. La limite exacte sera définie dans la partie C de notre applicatif.
Nous précisons le nom de la tâche à déclencher : « TaskIOToggle »
ALARM AlarmToggle { COUNTER = myCounter; ACTION = ACTIVATETASK { TASK = TaskIOToggle; }; }
TASK : Nous devons enfin déclarer la tâche elle-même. Ces différents paramètres définissent comment l'OS l'ordonnancera, si elle est préemptible (ici SCHEDULE = FULL indique qu'elle l'est) etc.
TASK TaskIOToggle { PRIORITY = 0x01; /* High priority */ AUTOSTART = FALSE; STACK = SHARED; ACTIVATION = 1; /* only one pending activation */ SCHEDULE = FULL; };
Code.c
Bien, passons au code!
L'exemple ci-dessous est fourni directement par RT-Druid.
Si vous partez de zéro, le fichier C n'est pas généré automatiquement, donc pas de squelette de l’ensemble.
Notre fichier C doit contenir les éléments suivants :
- L'inclusion des bibliothèques
- Le code à exécuter lorsque l'interruption systick_handler se déclenche
- Le code à exécuter pour la tâche TaskIOToggle
- Le code principal, exécuté par le noyau avant toute autre tâche, c'est à dire une fonction main
Tout d'abord, nous allons inclure les fichiers relatifs au RTOS (ee.h pour Erika Enterprise) et les informations relatives à la carte de développement. Néanmoins, soyez prudent ! Malgré l'écriture stm32f4xx qui laisse supposer que cette bibliothèque est faite pour tout type de stm32f4, elle est surtout faite pour la version stm32f407. Il en est de même pour la carte Discovery où il y a plusieurs versions matérielles, notamment au niveau de l’accéléromètre.
#include "ee.h" #include "stm32f4xx.h"
L'ISR2 a été déclaré dans le conf.oil. La ligne « CounterTick(myCounter); » indique que l'on incrémente le compteur « mycounter ».
ISR2(systick_handler) { /* count the interrupts, waking up expired alarms */ CounterTick(myCounter); }
La tâche, la partie applicative du programme, est chargée d'implémenter le chenillard qui est l'équivalent d'un « Hello World » en programmation GPIO. Un chenillard allume et éteind simplement des diodes. GPIO_SetBits est une API qui permet d'allumer des leds et GPIO_ResetBits permet de les éteindre.
#define MAX_ROUND 5; TASK(TaskIOToggle) { static EE_UINT8 act = 0; switch (act) { case 0: GPIO_SetBits(GPIOD, GPIO_Pin_12); act = (act + 1) % MAX_ROUND; break; case 1: GPIO_SetBits(GPIOD, GPIO_Pin_13); act = (act + 1) % MAX_ROUND; break; case 2: GPIO_SetBits(GPIOD, GPIO_Pin_14); act = (act + 1) % MAX_ROUND; break; case 5: GPIO_SetBits(GPIOD, GPIO_Pin_15); act = (act + 1) % MAX_ROUND; break; case 4: GPIO_ResetBits(GPIOD, GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15); act = (act + 1) % MAX_ROUND; break; } }
Enfin, la fonction "main", un peu plus complexe, est chargée de l'initialisation de l'ensemble du système. Nous n'en détaillerons que les deux points les plus intéressants.
1) La déclaration des GPIOs.
La première ligne est indispensable pour activer les broches. C'est le fait d'activer l'horloge qui permettra de mettre à jour leur état.
La suivante indique les GPIO ou broches commandées par cette structure.
/* GPIOD Periph clock enable */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); /* Configure PD12, PD13, PD14 and PD15 in output pushpull mode */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 |GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOD, &GPIO_InitStructure);
2) Le démarrage de l'alarme
L'API SetRelAlarm permet de démarrer une alarme. Ici l'alarme « AlarmToggle » avec une période de 200 ticks (soit 200ms) et un offset de 10 ticks (soit 10ms).
SetRelAlarm(AlarmToggle, 10, 200);
« AlarmToggle » étant configurée pour activer la tâche « TaskIOToggle ». Cette dernière sera ainsi activée une première fois après 10ms puis de façon périodique toute les 200ms.
3) La fonction
La fonction commence par une phase d'initialisation et se termine par une boucle infinie qui sera interrompue dès lors qu'une interruption sera activée.
int main(void) { /* Preconfiguration before using DAC----------------------------------------*/ GPIO_InitTypeDef GPIO_InitStructure; /* * Setup the microcontroller system. * Initialize the Embedded Flash Interface, the PLL and update the * SystemFrequency variable. * For default settings look at: * pkg/mcu/st_stm32_stm32f4xx/src/system_stm32f4xx.c */ SystemInit(); /*Initialize Erika related stuffs*/ EE_system_init(); /*Initialize systick */ EE_systick_set_period(MILLISECONDS_TO_TICKS(1, SystemCoreClock)); EE_systick_enable_int(); EE_systick_start(); /* GPIOD Periph clock enable */ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE); /* Configure PD12, PD13, PD14 and PD15 in output pushpull mode */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13 | GPIO_Pin_14 |GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOD, &GPIO_InitStructure); /* Program cyclic alarms which will fire after an initial offset, * and after that periodically * */ SetRelAlarm(AlarmToggle, 10, 200); /* Forever loop: background activities (if any) should go here */ for (;;); }
Pour terminer ce test, vous pouvez compiler et flasher le code.
Cliquez sur « Build » dans la barre des raccourcis.
S'il y a pas d'erreur :
$cd ~/workspace/votre_projet/Debug $st-flash write c_mX.bin 0x8000000
Pour résumer :
.OIL |
Le programme |
Compilation |
Fichier à flasher |
|||
1) Configuration de : Environnement Bibliothèque Option de compil. |
=> |
Makefile Généré a partir de la partir du .oil. Généré par le plugins d'EE. Pointe sur d'autre makefile spécifique à la cible. |
Les différentes bibliothèques seront appelées au travers des inclusions. Il est possible d'ajouter ces propres bibliothèques (complexes). |
Une fois compilé, grâce au règle de compilation dans les makefile, vous pourrez flasher le .bin. Il s'agit du fichier binaire exécutable par votre cible. |
||
2) Déclaration des éléments Taches Interruption Alarme Compteur ... |
=> |
Code.c Initialise le système Relie les éléments Réalise les fonctions Inclue des .h |
Les bibliothèques comprennent aussi des makefiles qui permettent d'assembler les fichiers dans ces répertoires. |
Si vous souhaitez continuer, je vous propose deux exercices très différents.
1) L'étape suivante est de créer une tache supplémentaire qui gère les mêmes GPIOs. L'idée serait aussi d'ajouter une « RESSOURCE » pour coordonner l’accès à cette ressource.
2) Si vous avez la dernière version de la carte Discovery, le pilote de l’accéléromètre n'est pas le bon. Les deux versions sont LIS302DL (ancien) et LIS3DSH (nouveau). Nous vous proposons de l'ajouter au projet. Il va donc falloir modifier les makefiles, le pilote en lui même et ajouter des options dans le conf.oil pour sélectionner l'un ou l'autre.
Liens externes
pour en savoir plus sur OSEK:
https://fr.wikipedia.org/wiki/OSEK/VDX
L'OS trampoline
http://trampoline.rts-software.org/spip.php?article2
La spécification complète des fichiers OIL
http://portal.osek-vdx.org/files/pdf/specs/oil25.pdf
Enfin, un article de ce blog pour apprendre à connecter des diodes sur des GPIO
http://www.linuxembedded.fr/2014/07/electronique-simple-pour-gpio/