Осрв quarkts. attiny817. будем поморгать.

Тема в разделе "Микроконтроллеры AVR", создана пользователем parovoZZ, 4 сен 2020.

  1. parovoZZ

    parovoZZ Гуру

    Случайно наткнулся на библиотеку, реализующую кооперативную операционную систему. Называется она quarkTS. Ссылки давать не буду, ибо очень просто ищется на GitHub. Помимо классических задач, библиотека может реализовывать задачи в виде конечного автомата. Также есть различная функциональность в виде таймеров, очереди сообщений, ко-рутин для временного и/или условного переключения контекста, утилит для работы с командной строкой, памятью. И всё это может уместиться в небольшом размере флэш памяти 8-ми, 16-ти, 32-х битных МК. Список протестированных платформ есть в мануале, но фактически из-за отсутствия аппаратной привязки библиотеки к какой-либо платформе, её можно запустить на любом МК.
     
    b707, AlexU и ИгорьК нравится это.
  2. parovoZZ

    parovoZZ Гуру

    Чтобы далеко не ходить, давайте решим такую задачу прямо из мануала: нажатие на кнопку зажигает светодиод, повторное нажатие на кнопку заставляет его моргать. Если ещё раз нажать, то светодиод гаснет. Мало того, через определённое время бездействия светодиод также должен гаснуть автоматически.
    У меня под рукой есть плата Explained Mini на Attiny817. На ней как раз расположены один светодиод и одна кнопка. К портам МК они подключены вот так:
    Код (C++):

     /*

                   |
                PC0| - LED
                   |
                PC5| - Button  ---/ ---
                   |                   |
                                      ---
    */

     
    Я начну с написания макроопределений для светодиода:
    Код (C++):
    #define   LED_TOGGLE()       PORTC.OUTTGL = PIN0_bm
    #define   LED_ON()           PORTC.OUTSET = PIN0_bm
    #define   LED_OFF()          PORTC.OUTCLR = PIN0_bm
    В новых AVR и Attiny есть специальные регистры, которые непосредственно управляют пинами без выполнения цепочки операций "чтение-модификация-запись". Ими я и воспользовался выше.
    Кнопку я заведу в обработчик прерывания:
    Код (C++):
    ISR (PORTC_PORT_vect)
    {
         BUTTON_PRESS = 1;

         PORTC.INTFLAGS = 0xFF;
    }
    Дребезг кнопки меня здесь никак не волнует, так как флаг
    Код (C++):
    volatile uint8_t   BUTTON_PRESS = 0;
    я буду опускать в теле обработки задачи. Прерывание не сбрасывает автоматически флаги, поэтому делаем это вручную.
    Рассматриваемая ОСРВ не имеет абсолютно никакой аппаратной привязки за исключением одного: системног тика часов. Для его передачи в ОСРВ предусмотрено два варианта: с помощью HAL функции целевой платформы или вызова функции
    Код (C++):
    qClock_SysTick();
    Я воспользуюсь вторым вариантом, т..к у меня нет системных часов:
    Код (C++):
    ISR (RTC_CNT_vect)
    {
         RTC.INTFLAGS = 1;

         qClock_SysTick();
    }
    Это прерывание RTC таймера по достижению им определённого значения. Данное прерывание не сбрасывает флаг, поэтому его сбрасываем вручную. Для инициализации RTC таймера воспользуюсь такой функцией:
    Код (C++):
    void RTC_Init (void)
    {
       
         RTC.CLKSEL = RTC_CLKSEL_INT32K_gc;                 // Подключаем источник тактирования 32 кГц

         RTC.PER = 32;                                     // В регистр переполнения 32, чтобы получить прерывание раз в 1 мс

         RTC.INTCTRL = RTC_OVF_bm;
       
         RTC.CTRLA = RTC_RUNSTDBY_bm | RTC_PRESCALER_DIV1_gc | RTC_RTCEN_bm;


    }
    RTC таймер настраиваю так, чтобы получить системный тик с периодом 1 мс. Что делают биты регистров не сложно догадаться по их названию. Тактируется таймер от внутреннего генератора 32 кГц. Мне для поморгать какая-то точность не нужна: как моргает, так и моргает.
    Ну и в завершении подготовительных процедур запишем инициализацию портов:
    Код (C++):
    void GPIO_Init (void)
    {
         PORTC.DIRSET = PIN0_bm;

         PORTC.PIN5CTRL = PORT_ISC_FALLING_gc;

    }
    Подтяжку к шине питания я не включаю - она есть на плате в виде резистора.
     
    AlexU и ИгорьК нравится это.
  3. parovoZZ

    parovoZZ Гуру

    Для решения нашей задачи так и напрашивается конечный автомат. В качестве состояний автомата возьмём состояния светодиода. Тогда граф состояний и переходов будет выглядеть так (картинка из мануала):
    FSM.png
    Здесь:
    S1 - светодиод выключен
    S2 - светодиод включен
    S3 - светодиод мигает
    t1 - нажатие кнопки оператором
    t2 - завершение таймаута

    Прежде, чем начать работу с конечным автоматом, необходимо произвести инициализацию ОСРВ. Делается это с помощью следующей функции:
    Код (C++):
    void qOS_Setup( const qGetTickFcn_t TickProvider, const qTimingBase_t BaseTimming, qTaskFcn_t IdleCallback )
    Первый аргумент - это функция системного тика, второй - это уведомление ОСРВ о периоде системного тика для правильного расчёта временных отсчётов. Ну и последний аргумент - это обратный вызов функции для обработки режима ожидания. Вызывается она тогда, когда в операционной системе отсутствуют задачи для текущего выполнения.
    Время в функции инициализации задаётся в формате float. Для систем, не поддерживающих данный формат аппаратно, работа с float весьма накладна. Поэтому ОСРВ QuarkTS поддерживает работу с целочисленными значениями времени. Для этого в файле qconfig.h необходимо включить опцию:
    Код (C++):
    #define Q_SETUP_TIME_CANONICAL      ( 1 )
    В таком случае системный тик жёстко задан и равен 1 мс, а все временные отсчёты также указываются в мс. Тогда наша функция сокращается до вида:
    Код (C++):
    void qOS_Setup( const qGetTickFcn_t TickProvider, qTaskFcn_t IdleCallback )
    В режиме простоя операционной системы я буду спать. Применённый мною таймер RTC умеет тикать в режимах сна вплоть до StandBy, поэтому разрешаем его:
    Код (C++):
    SLPCTRL.CTRLA = SLEEP_MODE_STANDBY | SLPCTRL_SEN_bm;
    В режиме простоя уходим спать:
    Код (C++):
    void IdleTask_Callback (qEvent_t e)
    {
         sleep_cpu();
    }
    Теперь запишем функцию инициализации операционной системы:
    Код (C++):
    qOS_Setup(NULL, IdleTask_Callback);
     
    AlexU и ИгорьК нравится это.
  4. parovoZZ

    parovoZZ Гуру

    Опишем функции состояний нашего автомата. Состояние, когда светодиод выключен:
    Код (C++):
    qSM_Status_t State_LED_Off (qSM_Handler_t m)
    {
        switch (m->Signal)
        {
            case QSM_SIGNAL_ENTRY:
                 
                   LED_OFF ();
               
                break;

            default:
                if (BUTTON_PRESS)
                {
                    m->NextState = State_LED_On;

                        BUTTON_PRESS = 0;
                }
                break;
        }

        return qSM_EXIT_SUCCESS;
    }
    Из всех полей структуры qSM_Handler_t нам сейчас интересны два: Signal и NextState. Поле Signal содержит признак выполнения функции (задачи): QSM_SIGNAL_ENTRY - этот признак указывает на то, что задача выполняется первый раз после смены состояния автомата. Для уведомления конечного автомата о том, что необходимо сменить его состояние, существует поле NextState.
    При нажатии на кнопку мы просим наш автомат включить светодиод:
    Код (C++):
    qSM_Status_t State_LED_On (qSM_Handler_t m)
    {
        static qSTimer_t    timeout;
        switch (m->Signal)
        {
            case QSM_SIGNAL_ENTRY:
                qSTimer_Set (&timeout, 5000);

                   LED_ON ();
               
                break;

            default:
                if (qSTimer_Expired(&timeout))
                {
                    m->NextState = State_LED_Off;
                }

                if (BUTTON_PRESS)
                {
                    m->NextState = State_LED_Blink;

                        BUTTON_PRESS = 0;
                }
           
        }

        return qSM_EXIT_SUCCESS;
    }
    Т.к. QSM_SIGNAL_ENTRY вызывается один раз после изменения состояния автомата, то здесь мы инициализируем таймер, по сигналу которого мы выйдем из данного состояния. Отведённая пауза - 5000 мс или 5 с. Вышло время или нет мы проверяем с помощью функции qSTimer_Expired. Если до истечения этого времени оператор нажмёт кнопку, то светодиод начнёт моргать:
    Код (C++):
    qSM_Status_t State_LED_Blink (qSM_Handler_t m)
    {
        static qSTimer_t timeout, blinktime;

        switch (m->Signal)
        {
            case QSM_SIGNAL_ENTRY:
                qSTimer_Set(&timeout, 10000);
                break;

            default:
                if (qSTimer_Expired(&timeout) || (BUTTON_PRESS))
                {
                    m->NextState = State_LED_Off;

                        BUTTON_PRESS = 0;
                }

                if (qSTimer_FreeRun(&blinktime, 100))
                {
                       LED_TOGGLE();
                }
                break;
        }

        return qSM_EXIT_SUCCESS;
    }
    Здесь с помощью функции qSTimer_FreeRun() мы организуем мигание светодиода с частотой 5 Гц. Функция qSTimer_FreeRun проверяет, вышло ли указанное в её аргументе время. Если вышло, она возвращает истину и перезапускает указанный таймер на указанный период.
    Все наши состояния мы описали, теперь необходимо в ОСРВ добавить задачу для выполнения. Но прежде инициализируем необходимые объекты:
    Код (C++):
    qTask_t    LED_Task;
    qSM_t    LED_FSM;
    LED_FSM - это переменная, которая описывает конечный автомат, LED_Task описывает нашу задачу.
    В ОСРВ задача с функциями конечного автомата добавляется так:
    Код (C++):
    qOS_Add_StateMachineTask(&LED_Task, qHigh_Priority, 20, &LED_FSM, State_LED_Off, NULL, NULL, NULL, NULL, qEnabled, NULL);
    qHigh_Priority - это приоритет нашей задачи. Она у нас всего одна (не считая простаивающей задачи), поэтому приоритет здесь не важен;
    20 - это период вызова нашей задачи. Напомню, что все временные интервалы у нас указываются в мс. Период 20 мс выбран с целью устранения дребезга контактов кнопки;
    State_LED_Off - это состояние, в которое должен перейти наш автомат сразу после инициализации;
    qEnabled - разрешение выполнения нашей задачи.
    Осталось разрешить прерывания и запустить выполнение операционной системы:
    Код (C++):
         sei();

         qOS_Run();
    Цикл
    Код (C++):
         /*
        while (1)
        {
        }*/
    при нормальном выполнении операционной системы здесь не нужен.

    Enjoy!
     
    AlexU и ИгорьК нравится это.
  5. Un_ka

    Un_ka Гуру

    Мне так лень резисторы паять, я подтяжку обычно включаю.
    Очень интересно. Попробуйте на Хабре публиковаться.
     
  6. AlexU

    AlexU Гуру

    Интересная штука, но разработчики говорят, что это не ОСРВ.
    Паровозз, уже не первый раз пытаешься за ОСРВ выдать то, что к ОСРВ ни какого отношения не имеет. Если уже так хочется "потыкать палочкой" в ОСРВ, то возьми туже FreeRTOS (в описании к quarkTS на Github есть ссылочка в последнем абзаце).
    Тебя ждёт очень занятный квест -- запуск FreeRTOS на ATtiny817. Хотя этот квест может оказаться не проходимым. Но не отчаивайся характеристик Arduino UNO 3-ей ревизии хватает для запуска FreeRTOS (по крайней мере так пишут).

    А что касается данной ОС, то кооперативность -- это скорее недостаток. Могли бы вытесняющую многозадачность завести....
    Тем более что:
     
  7. parovoZZ

    parovoZZ Гуру

    следующее
    Я уже взял uOS. После неё FreeRTOS мне не интересна. Как и не интересны старые МК.
    Очень популярна ещё RIOT OS. Но список поддерживаемых платформ скуден.
     
  8. b707

    b707 Гуру

    интересненько, спасибо...