Операционная система выходного дня

    Дисклеймер. Автор не является сторонником использования многозадачных операционных систем для микроконтроллеров.

    image

    Жизнь нещадно заставляет применять операционные системы (ОС) для микроконтроллеров. На рынке существует немерянное количество подобных систем. Разработчики операционных систем, соревнуясь друг с другом, пытаются максимально увеличить функциональность своих продуктов. Это зачастую приводит к увеличению «тяжеловесности» системы, а также значительно повышает «порог вхождения» для программиста, разрабатывающего программное обеспечение встраиваемых систем.

    Чтобы не мучатся с выбором ОС для своих проектов, а также не затуманить сознание изучением чужого продукта, а также освоить технику написания встраиваемых приложений под операционные системы, а также разобраться, что это вообще такое, я решил написать свою ОСьку. Свое не пахнет.

    Предлагаемая ОСька (именно ОСька, язык не поворачивается назвать ее ОС, а тем более ОСРВ) кооперативная со статическими задачами. Как было отмечено выше, я не являюсь сторонником использования ОС для микроконтроллеров, но еще больше я не являюсь сторонником использования вытесняющих операционных систем в микроконтроллерах. Вытесняющая многозадачность, по сравнению с кооперативной, это не только сложные процедуры переключения контекста, но и ресурсоемкая синхронизация потоков. Использование динамических задач также значительно утяжеляет операционную систему.

    Операционная система разрабатывалась под процессор семейства Cortex-M0. При небольших изменениях, касающихся правил сохранения-восстановления контекста, ее можно использовать для других типов процессоров.

    Исходный код


    Файл 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
    #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;
      }
    
    


    Файл 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
    


    Файл 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
    


    Константы компиляции ОСьки


    #define IntorOSMaxKolvoZadach (2) //максимальное количество задач (резервирование памяти)
    #define IntorOSRazmerSteka (1024) //размер стека под все задачи [байты] (резервирование памяти)

    По религиозным причинам я не могу использовать динамическое выделение памяти, поэтому объем требуемой памяти необходимо указывать на этапе компиляции.

    Сервисы ОСьки


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

    Инициализация задачи. Задача оформляется в виде функции, указатель на функцию передается процедуре инициализации. При инициализации необходимо указать размер стека, выделяемый задаче. Порядок инициализации задач определяет их идентификаторы. Задача, инициализируемая первой, имеет идентификатор 0. Если указать суммарный размер стека больше зарезервированного произойдет ошибка. При инициализации задачи настраивается указатель стека задачи, стек загружается контекстом задачи.

    void StartOS(unsigned long Num);

    Старт операционной системы. В качестве аргумента функции передается идентификатор задачи, с которой необходимо начать выполнение. При старте операционной системы настраивается системный таймер на квант одна миллисекунда. Со стека запускаемой задачи списывается контекст и вызывается задача.

    void Sleep(unsigned long ms);

    Планировщик. При вызове этой функции из задачи, управление передается операционной системе. Операционная система из списка выбирает задачу готовую для исполнения и передает ей управление. Аргумент функции – время в миллисекундах, через которое необходимо вернуть управление текущей задаче. При вызове функции с аргументом 0xFFFFFFFF возврат управления не произойдет никогда.

    Данную функцию невозможно написать на языке Си, так алгоритм ее работы полностью разрушает логику языка. В исходных кодах приведены тесты программ на языке ассемблера для систем программирования IAR и GCC. Для страждущих приведен код на языке Си. Но хотелось бы отметить, что правильно скомпилироваться он способен только при определенных «фазах луны». В моем случае это произошло только при использовании среднего уровня оптимизации, на низком и на высоком уровне код компилировался ошибочно.

    Файл 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);

    Завершение задачи. Как отмечалось выше, задачи являются статическими, выгрузка задачи невозможна. Если необходимо задачу завершить можно воспользоваться этой функцией. Задача при этом остается в списке, но управление ей не передается.

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

    Остановить или запустить задачу. Аргумент – идентификатор задачи. Эти функции позволяют реализовать диспетчер задач. Стоить отметить, что запустить можно только ранее остановленную задачу, время до запуска которой равно 0xFFFFFFFF.

    Использование ОСьки


    Для примера традиционный микроконтроллерный «хелворд» под разработанную операционную систему.

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

    В заключении хочется искренне надеяться, что эта, по приколу, разработанная ОСька будет интересна и полезна разработчикам программного обеспечения для встраиваемых систем.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 18

      +4
      В качестве тренировки написания своего это хорошо!
      Но еще раз убеждаюсь, что электронщик и программист в микроконтроллерах это должны быть два разных человека.
      Вот проще не бывает, порт для кооперативного super simple tasker. И проще и линтом проверен и кода меньше.
      github.com/QuantumLeaps/qpc/tree/master/ports/arm-cm/qv/iar

      www.state-machine.com/qpc/api.html#api_qv
        +2
        электронщик и программист в микроконтроллерах это должны быть два разных человека.

        Сильное заявление. И справедливое только для большого и сложного проекта, но 90% (цифра из головы) проектов не такие, поэтому лучше и денежней совмещать.
          +4
          Да уж, сказано сильно — только вот работодатели про это обычно не знают :-)…
          Помнится, ходил на собеседку в «Электроприбор», хорошую для Питера зарплату предлагали, вполне на уровне для «Инженер, регулировщик РЭА».
          В результате выяснилось, что они от работника хотят и программирование АРМ на С++, и для ПЛИС Ксайлинкс/Альтера на Верилог чтоб ошибки находил в коде…
          Ну чтобы минимум за пару квалифицированных работников работал за одну зарплату.
          Вот такие «скромные» хотелки у работодателей нередко встречаются.
            +1

            Мое мнение, что хороший продукт при этом получится может с вероятностью 50 на 50. Может получится, а может нет. Если делается поделка в двух экземплярах, то да так можно делать. Но если, делается электроника и софт для производства 10000 приборов в год. То боже упаси совмещать две эти роли. Один баг в софте или ошибка проектирования электроники и компания теряет 1 000 000 долларов при цене прибора 100 баксов. И кого вы думаете назначаи козлом отпущения?

          +1
          Не могу полностью согласится, я считаю, что тут дело в опыте и квалификации, и всех грести под одну гребенку я бы не стал. Если условный «Эмбеддер», к примеру, может понимать ассемблер для отлова багов, знать си для средних и мелких проектов, а также уметь в с++ для крупных (то бишь разрабатываемыми приличной командой), знает особенности архитектуры контроллера и именно встраиваемого программирования, понимает принципы RTOS(по необходимости), разбирается как в схемотехнике, так и в корректной трассировке плат, то почему бы и не совмещать эти роли. Тогда, это совсем не обязательно будет мелкая поделка. Очевидно, вышеописанный специалист всегда найдет себе работу с адекватными требованиями и соответствующей оплатой труда.
          +3
          Данную функцию невозможно написать на языке Си, так алгоритм ее работы полностью разрушает логику языка.

          Да ладно, на раз такая функция пишется на чистом С, без ассемблера и без фаз луны:

          void Sleep(unsigned long ms)
          {
              TaskList[TaskNum].TaskSleep = ms;
              if (setjmp(TaskList[TaskNum].context) == 0)
              {
                  while(1)
                  {
                      ....
                      if(TaskList[TaskNum].TaskSleep==0)
                          longjmp(TaskList[TaskNum].context, 1);
                      ...
              }
          }
            0
            Не хиляет…
            Как найти точку входа в задачу?
              0
              Как найти точку входа в задачу?

              Есть варианты. Один — руками заполнить jmp_buf в InitTask. Для этого надо знать его структуру. Другой — в InitTask сделать что-то такое:

              static void InitTask1(jmp_buf *buf, void (*TaskPointer)(void), unsigned long stack)
              {
                  set_stack_pointer(stack); // установить указатель стека созданной задачи
                  if (setjmp(TaskList[TaskNum].context) == 0) {
                      longjmp(*buf, 1);
                  } else {
                      TaskPointer();
                      KillTask();
                  }
              }
              
              void InitTask(void (*TaskPointer)(void), unsigned long stack)
              {
                  jmp_buf buf;
                  if (setjmp(buf) == 0) {
                      InitTask1(&buf, TaskPointer, stack);
                  }
              }


              Возможно лучше сделать это через третью промежуточную функцию на ассемблере, которая только и делает, что устанавливает указатель стека.
                0
                Возможно лучше сделать это через третью промежуточную функцию на ассемблере, которая только и делает, что устанавливает указатель стека.


                Что я и делаю без этих хлопот…
                  0
                  Что я и делаю без этих хлопот…

                  Я не сомневаюсь. Мой комментарий был о
                  Данную функцию невозможно написать на языке Си
                  .
            0
            лет 10 назад для AVR была похожая оська YAVRTOS. сейчас сайт ее мертв, но архивы все еще находятся гуглом. в свое время оказалась очень полезной для меня к изучению…
            +1
            Мне кажется, что для большинства задач, для решения которых, собственно, и стоит использовать ОСРВ, необходима вытесняющая многозадачность. Кооперативная ОСРВ со статическими задачами ничего особо не дает в сравнении даже с банальной машиной состояний. Наверное, поэтому большинство кооперативных ОСРВ и умерли давно и безвозвратно.
              0
              Мне ОС нужна для возможности использования чужих исходников, написанных в блокирующем стиле.
              0
              OSA для исходно пиков аналогичная.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое