Système d'exploitation Week-end

Clause de non-responsabilité. L'auteur ne préconise pas l'utilisation de systèmes d'exploitation multitâche pour les microcontrôleurs.

image

La vie force sans pitié l'utilisation de systèmes d'exploitation (OS) pour les microcontrôleurs. Il existe un nombre immense de tels systèmes sur le marché. Les développeurs de systèmes d'exploitation, en concurrence les uns avec les autres, tentent de maximiser la fonctionnalité de leurs produits. Cela conduit souvent à une augmentation de la «lourdeur» du système, et augmente également de manière significative le «seuil d'entrée» pour un programmeur qui développe des logiciels pour les systèmes embarqués.

Afin de ne pas être tourmenté par le choix du système d'exploitation pour nos projets, ainsi que de ne pas embrouiller l'esprit avec l'étude du produit de quelqu'un d'autre, ainsi que de maîtriser la technique d'écriture d'applications embarquées pour les systèmes d'exploitation, ainsi que de comprendre de quoi il s'agit, j'ai décidé d'écrire mon système d'exploitation. Ce n'est pas l'odeur.

L'OS proposé (OS, le langage n'ose pas appeler son OS, et surtout OSRV) coopératif avec des tâches statiques. Comme indiqué ci-dessus, je ne suis pas partisan de l'utilisation du système d'exploitation pour les microcontrôleurs, mais plus encore, je ne suis pas partisan de l'utilisation de systèmes d'exploitation préemptifs dans les microcontrôleurs. Le multitâche préemptif, comparé à coopératif, n'est pas seulement des procédures de changement de contexte complexes, mais aussi une synchronisation des threads gourmande en ressources. L'utilisation de tâches dynamiques complique également considérablement le système d'exploitation.

Le système d'exploitation a été développé pour le processeur de la famille Cortex-M0. Avec des modifications mineures aux règles d'enregistrement et de restauration du contexte, il peut être utilisé pour d'autres types de processeurs.

Code source


Fichier IntorOS.h
#ifndef __INTOROS_H #define __INTOROS_H //  IntorOS // #define IntorOSMaxKolvoZadach (2) //   ( ) #define IntorOSRazmerSteka (1024) //     [] ( ) #define IntorOSError (0) //  0- !0-   // //  // TaskPointer -   ,   // Stek -      void InitTask(void (*TaskPointer)(void), unsigned long Stek); //   //    void StartOS(unsigned long Num); //   ,   // ms -    void Sleep(unsigned long ms); //  static inline void EndTask(void){while(1)Sleep(0xFFFFFFFF);} //  // -   void StopTask(unsigned long Num); //    // -   void StartTask(unsigned long Num); #endif 


Fichier IntorOS.c
 #define _INTOROS_C #include "stm32l0xx.h" #include "IntorOS.h" //    typedef struct { unsigned long TaskSleep;//      unsigned long* SP; //   } Task_t; unsigned long KolvoTask;//  unsigned long KolvoTaskStek;//  unsigned long TaskNum;//    Task_t TaskList[IntorOSMaxKolvoZadach];//  unsigned long TaskStek[IntorOSRazmerSteka/4];//     //  // TaskPointer -   ,   // Stek -    void InitTask(void (*TaskPointer)(void), unsigned long Stek) { //   TaskList[KolvoTask].TaskSleep=0;//        TaskList[KolvoTask].SP=&(TaskStek[IntorOSRazmerSteka/4-1-KolvoTaskStek]);//   //   //       ( LR) TaskList[KolvoTask].SP--; (*(TaskList[KolvoTask].SP))=(unsigned long)(TaskPointer); TaskList[KolvoTask].SP--;//   R4 TaskList[KolvoTask].SP--;//   R5 TaskList[KolvoTask].SP--;//   R6 TaskList[KolvoTask].SP--;//   R7 TaskList[KolvoTask].SP--;//   R8 TaskList[KolvoTask].SP--;//   R9 TaskList[KolvoTask].SP--;//   R10 TaskList[KolvoTask].SP--;//   R11 TaskList[KolvoTask].SP--;//   R12 KolvoTask++;//   (  ) KolvoTaskStek=KolvoTaskStek+Stek/4;//   //   if(KolvoTaskStek>(IntorOSRazmerSteka/4)) #if IntorOSError==0 while(1);//      -  #else NVIC_SystemReset();//      -   #endif return; } //   //    void StartOS(unsigned long Num) { SysTick_Config(SystemCoreClock/1000);//      1 TaskNum=Num;//   //    TaskList[TaskNum].SP++;//   R12 TaskList[TaskNum].SP++;//   R11 TaskList[TaskNum].SP++;//   R10 TaskList[TaskNum].SP++;//   R9 TaskList[TaskNum].SP++;//   R8 TaskList[TaskNum].SP++;//   R7 TaskList[TaskNum].SP++;//   R6 TaskList[TaskNum].SP++;//   R5 TaskList[TaskNum].SP++;//   R4 TaskList[TaskNum].SP++;//   LR __set_SP((unsigned long)TaskList[TaskNum].SP);//     (*((void (*)(void))(*(TaskList[TaskNum].SP-1))))();//    //    #if IntorOSError==0 while(1);// #else NVIC_SystemReset();//  #endif } //  // -   void StopTask(unsigned long Num) { TaskList[Num].TaskSleep=0xFFFFFFFF; return; } //    // -   void StartTask(unsigned long Num) { if((~(TaskList[Num].TaskSleep))==0) {//   ,  TaskList[Num].TaskSleep=0x00000000; } return; } //   void SysTick_Handler(void); void SysTick_Handler(void) { TimingDelay++;//    for(int i=0;i<KolvoTask;i++) {//  if(((TaskList[i].TaskSleep)!=0) && ((~(TaskList[i].TaskSleep))!=0)) {//     0   0xFFFFFFFF (TaskList[i].TaskSleep)--;//    } } return; } 


Fichier IntorOSSleepIAR.s
 #define SHT_PROGBITS 0x1 EXTERN KolvoTask EXTERN TaskList EXTERN TaskNum PUBLIC Sleep SECTION `.text`:CODE:NOROOT(2) THUMB // 8 //    // 9 //    // 10 void Sleep(unsigned long ms) Sleep: // 11 { // 12 //  // 13 __asm("PUSH {R4-R7,LR}"); PUSH {R4-R7,LR} // 14 __asm("MOV R4,R8"); MOV R4,R8 // 15 __asm("MOV R5,R9"); MOV R5,R9 // 16 __asm("MOV R6,R10"); MOV R6,R10 // 17 __asm("MOV R7,R11"); MOV R7,R11 // 18 __asm("PUSH {R4-R7}"); PUSH {R4-R7} // 19 __asm("MOV R4,R12"); MOV R4,R12 // 20 __asm("PUSH {R4}"); PUSH {R4} // 21 TaskList[TaskNum].TaskSleep=ms;//       LDR R1,Sleep_0 LDR R2,Sleep_0+0x4 LDR R3,[R1, #+0] LSLS R3,R3,#+3 STR R0,[R2, R3] // 22 TaskList[TaskNum].SP =__get_SP();// SP MOV R0,SP LDR R3,[R1, #+0] LSLS R3,R3,#+3 ADDS R3,R2,R3 STR R0,[R3, #+4] // 23 //    // 24 while(1) // 25 { // 26 TaskNum++;if(TaskNum==KolvoTask)TaskNum=0;//    Sleep_1: LDR R0,[R1, #+0] ADDS R0,R0,#+1 LDR R3,Sleep_0+0x8 LDR R3,[R3, #+0] CMP R0,R3 BNE Sleep_2 MOVS R0,#+0 Sleep_2: STR R0,[R1, #+0] LSLS R0,R0,#+3 ADDS R0,R2,R0 LDR R3,[R0, #+0] CMP R3,#+0 BNE Sleep_1 // 27 //     // 28 if(TaskList[TaskNum].TaskSleep==0) // 29 {//    // 30 //  // 31 __set_SP(TaskList[TaskNum].SP);// SP LDR R0,[R0, #+4] MOV SP,R0 // 32 __asm("POP {R4}"); POP {R4} // 33 __asm("MOV R12,R4"); MOV R12,R4 // 34 __asm("POP {R4-R7}"); POP {R4-R7} // 35 __asm("MOV R11,R7"); MOV R11,R7 // 36 __asm("MOV R10,R6"); MOV R10,R6 // 37 __asm("MOV R9,R5"); MOV R9,R5 // 38 __asm("MOV R8,R4"); MOV R8,R4 // 39 __asm("POP {R4-R7,PC}"); POP {R4-R7,PC} // 40 // 41 //The End // 42 return; NOP // 43 } // 44 } // 45 } DATA Sleep_0: // extern unsigned long TaskNum;//   DC32 TaskNum // extern Task_t TaskList[IntorOSMaxKolvoZadach];//  DC32 TaskList // extern unsigned long KolvoTask;//  DC32 KolvoTask SECTION `.iar_vfe_header`:DATA:NOALLOC:NOROOT(2) SECTION_TYPE SHT_PROGBITS, 0 DATA DC32 0 SECTION __DLIB_PERTHREAD:DATA:REORDER:NOROOT(0) SECTION_TYPE SHT_PROGBITS, 0 SECTION __DLIB_PERTHREAD_init:DATA:REORDER:NOROOT(0) SECTION_TYPE SHT_PROGBITS, 0 END 


Fichier IntorOSSleepGCC.s
 .cpu cortex-m0 .text .cfi_sections .debug_frame .section .text.Sleep,"ax",%progbits .align 1 .global Sleep .syntax unified .thumb .thumb_func .type Sleep, %function .extern KolvoTask .extern TaskList .extern TaskNum .cfi_startproc // 8 //    // 9 //    // 10 void Sleep(unsigned long ms) Sleep: // 11 { // 12 //  // 13 __asm("PUSH {R4-R7,LR}"); PUSH {R4-R7,LR} // 14 __asm("MOV R4,R8"); MOV R4,R8 // 15 __asm("MOV R5,R9"); MOV R5,R9 // 16 __asm("MOV R6,R10"); MOV R6,R10 // 17 __asm("MOV R7,R11"); MOV R7,R11 // 18 __asm("PUSH {R4-R7}"); PUSH {R4-R7} // 19 __asm("MOV R4,R12"); MOV R4,R12 // 20 __asm("PUSH {R4}"); PUSH {R4} // 21 TaskList[TaskNum].TaskSleep=ms;//       LDR R1,Sleep_0 LDR R2,Sleep_0+0x4 LDR R3,[R1, #+0] LSLS R3,R3,#+3 STR R0,[R2, R3] // 22 TaskList[TaskNum].SP =__get_SP();// SP MOV R0,SP LDR R3,[R1, #+0] LSLS R3,R3,#+3 ADDS R3,R2,R3 STR R0,[R3, #+4] // 23 //    // 24 while(1) // 25 { // 26 TaskNum++;if(TaskNum==KolvoTask)TaskNum=0;//    Sleep_1: LDR R0,[R1, #+0] ADDS R0,R0,#+1 LDR R3,Sleep_0+0x8 LDR R3,[R3, #+0] CMP R0,R3 BNE Sleep_2 MOVS R0,#+0 Sleep_2: STR R0,[R1, #+0] LSLS R0,R0,#+3 ADDS R0,R2,R0 LDR R3,[R0, #+0] CMP R3,#+0 BNE Sleep_1 // 27 //     // 28 if(TaskList[TaskNum].TaskSleep==0) // 29 {//    // 30 //  // 31 __set_SP(TaskList[TaskNum].SP);// SP LDR R0,[R0, #+4] MOV SP,R0 // 32 __asm("POP {R4}"); POP {R4} // 33 __asm("MOV R12,R4"); MOV R12,R4 // 34 __asm("POP {R4-R7}"); POP {R4-R7} // 35 __asm("MOV R11,R7"); MOV R11,R7 // 36 __asm("MOV R10,R6"); MOV R10,R6 // 37 __asm("MOV R9,R5"); MOV R9,R5 // 38 __asm("MOV R8,R4"); MOV R8,R4 // 39 __asm("POP {R4-R7,PC}"); POP {R4-R7,PC} // 40 // 41 //The End // 42 return; NOP // 43 } // 44 } // 45 } .align 2 Sleep_0: // extern unsigned long TaskNum;//   .word TaskNum // extern Task_t TaskList[IntorOSMaxKolvoZadach];//  .word TaskList // extern unsigned long KolvoTask;//  .word KolvoTask .cfi_endproc 


Constantes de compilation du système d'exploitation


 #define IntorOSMaxKolvoZadach (2) //   ( ) #define IntorOSRazmerSteka (1024) //     [] ( ) 

Pour des raisons religieuses, je ne peux pas utiliser l'allocation dynamique de mémoire, donc la quantité de mémoire requise doit être spécifiée au stade de la compilation.

Services OS OS


 void InitTask(void (*TaskPointer)(void), unsigned long Stek); 

Initialisation de la tâche. La tâche est exécutée sous la forme d'une fonction, un pointeur vers une fonction est passé à la procédure d'initialisation. Lors de l'initialisation, vous devez spécifier la taille de pile allouée à la tâche. L'ordre dans lequel les tâches sont initialisées détermine leurs identifiants. La tâche qui est initialisée en premier a l'identifiant 0. Si vous spécifiez la taille totale de la pile supérieure à celle réservée, une erreur se produira. Lorsque la tâche est initialisée, le pointeur vers la pile de tâches est défini, la pile est chargée par le contexte de tâche.

 void StartOS(unsigned long Num); 

Le début du système d'exploitation. En tant qu'argument de la fonction, l'identifiant de la tâche avec laquelle démarrer l'exécution est transmis. Lorsque le système d'exploitation démarre, le minuteur du système est réglé sur un quantum d'une milliseconde. Le contexte est supprimé de la pile de la tâche lancée et la tâche est appelée.

 void Sleep(unsigned long ms); 

Planificateur Lorsque cette fonction est appelée à partir d'une tâche, le contrôle est transféré au système d'exploitation. Le système d'exploitation sélectionne une tâche prête à être exécutée dans la liste et lui transfère le contrôle. L'argument de la fonction est le temps en millisecondes après lequel il est nécessaire de retourner le contrôle à la tâche en cours. Lorsqu'une fonction est appelée avec l'argument 0xFFFFFFFF, le contrôle ne reviendra jamais.

Il est impossible d'écrire cette fonction en C, donc l'algorithme de son fonctionnement détruit complètement la logique du langage. Le code source contient des tests de programmes en langage assembleur pour les systèmes de programmation IAR et GCC. Pour les malades, le code en C est donné. Mais je voudrais noter qu'il n'est capable de compiler correctement qu'avec certaines "phases de la lune". Dans mon cas, cela ne s'est produit qu'en utilisant le niveau moyen d'optimisation, à bas et haut niveau, le code a été compilé par erreur.

Fichier Sleep.c
 extern Task_t TaskList[IntorOSMaxKolvoZadach];//  extern unsigned long TaskNum;//   extern unsigned long KolvoTask;//  //    //    #pragma optimize=medium void Sleep(unsigned long ms) { //  __asm("PUSH {R4-R7,LR}"); __asm("MOV R4,R8"); __asm("MOV R5,R9"); __asm("MOV R6,R10"); __asm("MOV R7,R11"); __asm("PUSH {R4-R7}"); __asm("MOV R4,R12"); __asm("PUSH {R4}"); TaskList[TaskNum].TaskSleep=ms;//       TaskList[TaskNum].SP =(unsigned long*)__get_SP();// SP //    while(1) { TaskNum++;if(TaskNum==KolvoTask)TaskNum=0;//    //     if(TaskList[TaskNum].TaskSleep==0) {//    //  __set_SP((unsigned long)TaskList[TaskNum].SP);// SP __asm("POP {R4}"); __asm("MOV R12,R4"); __asm("POP {R4-R7}"); __asm("MOV R11,R7"); __asm("MOV R10,R6"); __asm("MOV R9,R5"); __asm("MOV R8,R4"); __asm("POP {R4-R7,PC}"); //return } } } 


 void EndTask(void); 

Achèvement de la tâche. Comme indiqué ci-dessus, les tâches sont statiques, le déchargement des tâches est impossible. Si vous devez terminer la tâche, vous pouvez utiliser cette fonction. La tâche reste sur la liste, mais le contrôle ne lui est pas transféré.

 void StopTask(unsigned long Num); void StartTask(unsigned long Num); 

Arrêtez ou démarrez une tâche. L'argument est l'identifiant de la tâche. Ces fonctions vous permettent d'implémenter le gestionnaire de tâches. Il convient de noter que vous ne pouvez démarrer qu'une tâche précédemment arrêtée, l'heure jusqu'à laquelle elle démarre est 0xFFFFFFFF.

Utilisation du système d'exploitation


Par exemple, un microcontrôleur traditionnel "helword" pour un système d'exploitation développé.

 #include "stm32l0xx.h" #include "stm32l0xx_ll_gpio.h" #include "IntorOS.h" // 0 void Task0(void) { LL_GPIO_InitTypeDef GPIO_InitStruct; LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB); GPIO_InitStruct.Pin = LL_GPIO_PIN_0; GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT; GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; LL_GPIO_Init(GPIOB, &GPIO_InitStruct); while(1) { GPIOB->BRR=LL_GPIO_PIN_0; Sleep(1000); GPIOB->BSRR=LL_GPIO_PIN_0; Sleep(1000); } } // 1 void Task1(void) { LL_GPIO_InitTypeDef GPIO_InitStruct; LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB); GPIO_InitStruct.Pin = LL_GPIO_PIN_1; GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT; GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL; GPIO_InitStruct.Pull = LL_GPIO_PULL_NO; LL_GPIO_Init(GPIOB, &GPIO_InitStruct); while(1) { GPIOB->BRR=LL_GPIO_PIN_1; Sleep(500); GPIOB->BSRR=LL_GPIO_PIN_1; Sleep(500); } } void main(void) { // MCU Configuration SystemClock_Config(); //  InitTask(Task0, 512); InitTask(Task1, 256); //  StartOS(0); } 

En conclusion, je voudrais sincèrement espérer que ce système d'exploitation développé pour le plaisir sera intéressant et utile aux développeurs de logiciels pour systèmes embarqués.

Source: https://habr.com/ru/post/fr447302/


All Articles