Wochenendbetriebssystem

Haftungsausschluss. Der Autor befürwortet nicht die Verwendung von Multitasking-Betriebssystemen für Mikrocontroller.

Bild

Das Leben erzwingt gnadenlos die Verwendung von Betriebssystemen (OS) für Mikrocontroller. Es gibt eine immense Anzahl solcher Systeme auf dem Markt. Betriebssystementwickler, die miteinander konkurrieren, versuchen, die Funktionalität ihrer Produkte zu maximieren. Dies führt häufig zu einer Erhöhung der „Schwere“ des Systems und erhöht auch die „Eintrittsschwelle“ für einen Programmierer, der Software für eingebettete Systeme entwickelt, erheblich.

Um nicht mit der Wahl des Betriebssystems für unsere Projekte gequält zu werden, den Geist nicht durch das Studium des Produkts eines anderen zu trüben, die Technik des Schreibens eingebetteter Anwendungen für Betriebssysteme zu beherrschen und herauszufinden, worum es geht, habe ich beschlossen, mein Betriebssystem zu schreiben. Es riecht nicht.

Das vorgeschlagene Betriebssystem (Betriebssystem, die Sprache wagt es nicht, ihr Betriebssystem und insbesondere OSRV aufzurufen) kooperiert mit statischen Aufgaben. Wie oben erwähnt, bin ich kein Befürworter der Verwendung des Betriebssystems für Mikrocontroller, aber noch mehr bin ich kein Befürworter der Verwendung von präventiven Betriebssystemen in Mikrocontrollern. Präventives Multitasking ist im Vergleich zu kooperativ nicht nur eine komplexe Kontextumschaltung, sondern auch eine ressourcenintensive Thread-Synchronisation. Die Verwendung dynamischer Aufgaben verkompliziert auch das Betriebssystem erheblich.

Das Betriebssystem wurde für den Prozessor der Cortex-M0-Familie entwickelt. Mit geringfügigen Änderungen an den Regeln zum Speichern und Wiederherstellen des Kontexts kann es für andere Prozessortypen verwendet werden.

Quellcode


Datei 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 


IntorOS.c-Datei
 #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; } 


Datei 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 


Datei 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 


OS-Kompilierungskonstanten


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

Aus religiösen Gründen kann ich die dynamische Speicherzuweisung nicht verwenden, daher muss die erforderliche Speichermenge bei der Kompilierung angegeben werden.

OS OS-Dienste


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

Aufgabeninitialisierung. Die Aufgabe wird in Form einer Funktion ausgeführt, ein Zeiger auf eine Funktion wird an die Initialisierungsprozedur übergeben. Während der Initialisierung müssen Sie die der Aufgabe zugewiesene Stapelgröße angeben. Die Reihenfolge, in der Aufgaben initialisiert werden, bestimmt deren Bezeichner. Die zuerst initialisierte Aufgabe hat den Bezeichner 0. Wenn Sie die Gesamtgröße des Stapels angeben, die größer als die reservierte ist, tritt ein Fehler auf. Wenn die Aufgabe initialisiert wird, wird der Zeiger auf den Aufgabenstapel gesetzt, der Stapel wird vom Aufgabenkontext geladen.

 void StartOS(unsigned long Num); 

Der Start des Betriebssystems. Als Argument für die Funktion wird die Kennung der Aufgabe übergeben, mit der die Ausführung gestartet werden soll. Wenn das Betriebssystem gestartet wird, wird der System-Timer auf ein Quantum von einer Millisekunde eingestellt. Der Kontext wird vom Stapel der gestarteten Aufgabe abgeschrieben und die Aufgabe aufgerufen.

 void Sleep(unsigned long ms); 

Planer Wenn diese Funktion von einer Task aufgerufen wird, wird die Steuerung an das Betriebssystem übertragen. Das Betriebssystem wählt eine zur Ausführung bereitstehende Aufgabe aus der Liste aus und überträgt die Kontrolle an sie. Das Argument der Funktion ist die Zeit in Millisekunden, nach der die Steuerung auf die aktuelle Aufgabe zurückgesetzt werden muss. Wenn eine Funktion mit dem Argument 0xFFFFFFFF aufgerufen wird, wird die Steuerung niemals zurückgegeben.

Es ist unmöglich, diese Funktion in C zu schreiben, daher zerstört der Algorithmus ihrer Operation die Logik der Sprache vollständig. Der Quellcode enthält Tests von Assemblersprachenprogrammen für die Programmiersysteme IAR und GCC. Für Betroffene ist der Code in C angegeben. Ich möchte jedoch darauf hinweisen, dass er nur mit bestimmten "Mondphasen" korrekt kompilieren kann. In meinem Fall geschah dies nur, wenn die mittlere Optimierungsebene verwendet wurde. Auf niedriger und hoher Ebene wurde der Code fehlerhaft kompiliert.

Datei 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); 

Abschluss der Aufgabe. Wie oben erwähnt, sind Aufgaben statisch, das Entladen von Aufgaben ist nicht möglich. Wenn Sie die Aufgabe erledigen müssen, können Sie diese Funktion verwenden. Die Aufgabe bleibt in der Liste, aber die Kontrolle wird nicht auf sie übertragen.

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

Stoppen oder starten Sie eine Aufgabe. Argument ist die Kennung der Aufgabe. Mit diesen Funktionen können Sie den Task-Manager implementieren. Es ist zu beachten, dass Sie nur eine zuvor gestoppte Aufgabe starten können. Die Zeit bis zu ihrem Start beträgt 0xFFFFFFFF.

Verwenden des Betriebssystems


Zum Beispiel ein traditionelles Mikrocontroller- "Helword" für ein entwickeltes Betriebssystem.

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

Abschließend möchte ich aufrichtig hoffen, dass dieses zum Spaß entwickelte Betriebssystem für Entwickler von Software für eingebettete Systeme interessant und nützlich sein wird.

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


All Articles