Rtos. оставить delay() нельзя удалить

Тема в разделе "Arduino & Shields", создана пользователем parovoZZ, 3 май 2020.

Метки:
  1. parovoZZ

    parovoZZ Гуру

    Недавно писал про свою новую ля-лярдуину. Давайте помигаем светодиодами. А чтобы это сделать, необходимо скачать родную IDE - Simplicity Studio на основе народной Eclipse. Необходимо поставить SDK Gecko, а также файлы поддержки того МК (или Starter kit-а), на котором будет создан проект. У меня это SLSTK3402A на основе EFM32PG12. Ядро МК Cotex-M4, ОЗУ 256кБ, flash 1024кБ. Внутренний RC осциллятор тактирует до 38МГц, внешний до 40МГц.
    Вместе с SDK идет библиотека emlib, которая облегчает работу с периферией. Блоки периферии собраны в структуры, а поля структуры - это регистры блоков.
    Первое - инициализируем системные часы, а также dcdc регулятор, который оптимизирует энергопотребление:
    Код (C++):
        EMU_DCDCInit_TypeDef    dcdcInit = EMU_DCDCINIT_DEFAULT;
        CMU_HFXOInit_TypeDef    hfxoInit = CMU_HFXOINIT_DEFAULT;

        CHIP_Init();

        EMU_DCDCInit(&dcdcInit);
        CMU_HFXOInit(&hfxoInit);
    CHIP_Init(); - функция, устраняющая найденные ошибки (errata). Она из набора SDK.

    Далее включаем тактирование. Нам необходимо включить системный такт и блок ввода-вывода:
    Код (C++):
        CMU_ClockEnable(cmuClock_HF, cmuSelect_HFXO);

        CMU_ClockEnable(cmuClock_GPIO, true);
    Теперь, когда GPIO тактируется, мы можем с ним поиграться. Светодиоды - а их на плате два - подключены к пинам 4 и 5 порта F. Следующей функцией мы переводим эти пины на выход в состоянии "1":
    Код (C++):
        GPIO_PinModeSet(gpioPortF, 4, gpioModePushPull, 1);
        GPIO_PinModeSet(gpioPortF, 5, gpioModePushPull, 1);
    Для реализации функции Delay(), нам необходимо настроить системный таймер на прерывание каждые 1мс:
    Код (C++):
        if (SysTick_Config(CMU_ClockFreqGet(cmuClock_CORE) / 1000))
        {
            while (1);
        }
    Если что-то пошло не так, то дальнейшее выполнение программы бессмысленно, поэтому тупо повисаем.
    Ну а дальше мигаем светодиодами в бесконечном цикле:
    Код (C++):
        {

            GPIO_PinOutToggle(gpioPortF, 4);
            Delay(100);

            GPIO_PinOutToggle(gpioPortF, 5);
            Delay(1000);
      }
    Delay() самописная:
    Код (C++):
    void Delay (uint32_t dlyTicks)
    {
        uint32_t    curTicks;

        curTicks = msTicks;

        while ((msTicks - curTicks) < dlyTicks);
    }
    msTicks объявлена глобально:
    Код (C++):
    volatile uint32_t     msTicks;
    и наращиваем её прерывании:
    Код (C++):
    SysTick_Handler
    не забыть подключить заголовки:
    Код (C++):
    #include "em_chip.h"
    #include "em_gpio.h"
    #include "em_cmu.h"
    #include "em_emu.h"
    Это очень тупой подход, т.к. всё основное время процессор будет находится в функции Delay(). Но есть приложения, где именно такие ожидания являются нормой или даже необходимой мерой. Это операционная система. В нашем же случае это не просто операционная система, а операционная система реального времени или ОСРВ (в англоязычной литературе RTOS). Зачем оно? МК с таким ядром на борту будут нагружать многочисленными задачами, среди которых это и взаимодействие с пользователем, и общение по сетям, вывод на внешние устройства и прочее. Если программировать линейным кодом, то будет сложно сделать так, чтобы при обработке и малого и большого количества информации, все остальные задачи также успевали обрабатывать поступающую информацию, при этом не теряя её и не искажая.
     
  2. sslobodyan

    sslobodyan Гик

    Сорри, что-то я не увидел здесь RTOS. Вы уверены, что она присутствует? Что вот это мигание определено как задача (поток)? Терзают смутные сомнения...
     
  3. parovoZZ

    parovoZZ Гуру

    Итак, давайте писать код, для которого функция ожидания жизненно необходима. Т.к. у меня SiLabs, то ОС будет Micrium. Это вытесняющая ОС. Т.е. в ней присутствует планировщик, который распределяет процессорное время между задачами в соответствии с их приоритетами. Для чего в таких задачах необходимы функции ожидания? Допустим, у нас две задачи с разными приоритетами и обе задачи полностью используют отведённое им время. В этом случае задача с низким приоритетом никогда не исполнится, т.к. всё время будет исполняться задача с высоким приоритетом. В такой ситуации есть два пути решения: давать задачам небольшую паузу, чтобы планировщик смог вставить в очередь все задачи; второй путь - использование карусели (round-robin). У нас задача совсем простая - помигать светодиодами. Т.к. период мигания у нас очень длинный по сравнению с тем, как планировщик переключает задачи, то будем использовать первый путь.
    Напишем функции наших задач. Одна задача будет переключать один светодиод, другая - второй.
    Код первой задачи:
    Код (C++):
    void LED0Tgl_TaskCode (void *p_arg)
    {
        RTOS_ERR    err;

        const OS_TICK    delay = 501;

        while (DEF_ON)
        {
            GPIO_PinOutToggle(gpioPortF, LED0_PIN);

            OSTimeDly(delay, OS_OPT_TIME_DLY, &err);

            if (RTOS_ERR_CODE_GET(err) != RTOS_ERR_NONE)
            {
                ///
            }
        }
    }
    OSTimeDly() - это наш delay(), который сообщает планировщику и приостановке исполнения задачи на время delay.
    Вторая задача аналогична первой
    Код (C++):
    void LED1Tgl_TaskCode (void *p_arg)
    {
        RTOS_ERR    err;

        const OS_TICK    delay = 500;

        while (DEF_ON)
        {
            GPIO_PinOutToggle(gpioPortF, LED1_PIN);

            OSTimeDly(delay, OS_OPT_TIME_DLY, &err);

            if (RTOS_ERR_CODE_GET(err) != RTOS_ERR_NONE)
                    {
                        ///
                    }
        }
    }
    Теперь инициализируем наши GPIO ножки
    Код (C++):
    void InitGPIO (void)
    {
        CMU_ClockEnable(cmuClock_GPIO, true);

        GPIO_PinModeSet(gpioPortF, 4, gpioModePushPull, 1);
        GPIO_PinModeSet(gpioPortF, 5, gpioModePushPull, 1);
    }
    Не забываем, что в ARM архитектуре вся периферия сразу после подачи питания не тактируется. Поэтому необходимо выбрать источник тактирования и разрешить тактирование.
    Для того. чтобы планировщик в ОС исправно работал. ему необходим источник системных квантов. В micrium последней редакции для этого используется SleepTimer. В качестве SleepTimer на моём чипе используется таймер реального времени RTCC. А вот чтобы не писать длинную простыню инициализации этого таймера, в состав Micrium включена вспомогательная функция BSP_SystemInit() - она инициализирует не только таймер RTCC, но и главный системный тактовый сигнал. После этого необходимо инициализировать саму ОС, создать задачи и запустить их.
    Код (C++):
    int main (void)
    {
        RTOS_ERR    err;

        BSP_SystemInit();

        OSInit(&err);
        APP_RTOS_ASSERT_DBG((RTOS_ERR_CODE_GET(err) == RTOS_ERR_NONE), 1);

        InitGPIO();

        CPU_Init();

        OSTaskCreate(&LED0TGL_TASK_TCB,
                    "Toggle LED0",
                    LED0Tgl_TaskCode,
                    (void*)0,
                    LEDTGL_TASK_PRIORITY,
                    &LED0TGL_TASK_STK[0],
                    (LEDTGL_TASK_STK_SIZE/10),
                    LEDTGL_TASK_STK_SIZE,
                    0,
                    0,
                    (void*)0,
                    OS_OPT_TASK_STK_CHK+OS_OPT_TASK_STK_CLR,
                    &err);

        APP_RTOS_ASSERT_DBG((RTOS_ERR_CODE_GET(err) == RTOS_ERR_NONE), 1);

        OSTaskCreate(&LED1TGL_TASK_TCB,
                    "Toggle LED1",
                    LED1Tgl_TaskCode,
                    (void*)0,
                    LEDTGL_TASK_PRIORITY,
                    &LED1TGL_TASK_STK[0],
                    LEDTGL_TASK_STK_SIZE/10,
                    LEDTGL_TASK_STK_SIZE,
                    0,
                    0,
                    (void*)0,
                    OS_OPT_TASK_STK_CHK+OS_OPT_TASK_STK_CLR,
                    &err);

        OSStart(&err);
        APP_RTOS_ASSERT_DBG((RTOS_ERR_CODE_GET(err) == RTOS_ERR_NONE), 1);
    }
     
    Последнее редактирование: 14 май 2020
  4. parovoZZ

    parovoZZ Гуру

    Посмотрим на энергопотребление.
    RTOS_no_EM2.png
    Видим, что в простое наш код потребляет аж 1.3 mA! По сути, это ничем не отличается от простого линейного программирования. которое было в примере выше.
    Чтобы снизить энергопотребление, необходимо МК перевести в один из режимов энергосбережения. Из коробки Micrium поддерживает все режимы вплоть до EM2. Вот его и будем использовать как самый энергоэффективный. Для того. чтобы в него попасть. необходимо поймать момент простоя системы. Для этого предусмотрен хук void OSIdleEnterHook().
    Отлавливаем его и пишем код:
    Код (C++):
    void OSIdleEnterHook (void)
    {
        EMU_EnterEM2(true);
    }
    Смотрим теперь:
    RTOS_with_EM2.png
    1 мкА в простое - это уже уровень PicoPower AVR, MSP430 с включенным вачдогом.
     
  5. SergeiL

    SergeiL Оракул Модератор

    Так блин за такое убивают:
    upload_2020-5-12_22-37-32.png
     
  6. Daniil

    Daniil Гуру

    если правильно понимаю, тут не имеет значения. Сравнивается код тупой программы мигания диодами с кодом программы с ртос, где задержка реализуется временем процесса. И то, и то имеет место жить, но во 2-м случае код получается лаконичнее. Только паровоз не удержался и переключил своё внимание на потребление и получилось не сравнение задержек в ртос и без ртос, а сравнение потреблений)
    но уверен, статья ещё не знакончена (выводов нет).
    Имхо, если я правильно понял значение OSTimeDly.
     
  7. parovoZZ

    parovoZZ Гуру

    Возникает вполне резонный вопрос: раз у нас ОС, то как посмотреть на параметры наших задач в процессе исполнения? Ведь в любой ОС есть такой инспектор: "диспетчер задач", "top/htop". Ну так и для нашей RTOS такой инструмент найдётся. Один из них - uc/Probe. Он платный. Но любезно работает бесплатно аж целую минуту. Загружаем, кидаем интерактивный элемент Micrium OS Kernel на поле рабочей области и жмём кнопку RUN. И получаем данные для изучения:
    uc_probe.png Первые две задачи - это наши. Видим, что требуемый и используемый размер стек составляет 80 байт на задачу. Разумеется, я уже запускал этот инструмент, "подсмотрел" и вписал в код необходимый размер стека. Ниже идет задача системного тика - по этому тику планировщик переключает контексты (задачи). Размер стека для этой задачи жестко определён по умолчанию. Но его можно изменить. Правда для этого необходимо написать побольше кода, в котором необходимо переопределить абсолютно все параметры.
    Ну и последняя задача служебная - она обеспечивает вывод данных наружу для обеспечения работы нашего диспетчера задач. В Release её можно убрать, чтобы не отнимала время.
    Среди прочих параметров видим загрузку процессора - 0.31%!
     
  8. AlexU

    AlexU Гуру

    При кооперативной многозадачности планировщик максимум что может сделать, так это определить порядок выполнения задач, а за процессорным временем следить не сможет. Т.к. при такой многозадачности любая задача возьмёт столько процессорного времени, сколько пожелает.
    Но судя по наличию Kernel's Timer Task похоже, что Паровозз чего-то напутал...
     
    parovoZZ нравится это.
  9. parovoZZ

    parovoZZ Гуру

    Спасибо, подправил)