Создание приложений на базе RISC-V и FreeRTOS

Создание приложений под FreeRTOS и RISC?V

Показывать лекцию целиком

В последней главе этого курса мы рассмотрим процесс создания простого приложения FreeRTOS для процессора RISC?V, а затем скомпилируем и запустим наше приложение с помощью тулчейна RISC?V.

Мы закончим главу описанием различных сложных приложений, которые уже являются частью платформы FreeRTOS.

К концу этой главы вы будете в силах:

  • Объяснять этапы создания и запуска приложения FreeRTOS.
  • Создавать простые приложения.
  • Компилировать и запускать приложения.
  • Понимать процесс создания более сложных приложений.
  • Презентация по лекции

    Этапы создания приложения FreeRTOS

    Инструкции

    Давайте рассмотрим шаги, необходимые для создания приложения FreeRTOS.

    Шаг 1:

    Начните со списка файлов и настроек, представленных в главе FreeRTOS.

    Шаг 2:

    Определите требования к приложению. После определения требований к приложению убедитесь, что следующие детали также определены:

  • необходимые задачи для приложения;
  • взаимодействие между задачами;
  • зависимости между задачами:

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

    Шаг 3:

    Начните определять задачи в файлах по мере необходимости. Все задачи должны быть определены перед запуском планировщика FreeRTOS.

    Пример определения задачи

    xTaskCreate(
         /* Pointer to the function that implements the task. */
         vTaskCode,
         /* Name of the task. */
         "Demo task",
         /* The size of the stack that should be created for the task. This is defined in words, not bytes. */
         STACK_SIZE,
         /* A reference to xParameters is used as the task parameter. This is cast to a void * to prevent compiler warnings. */
         (void*) xParameter,
         /* The priority to be assigned to the newly created task. */
         TASK_PRIORITY,
         /* The handle to the created task will be placed into xHandle parameter as the output of the xTaskCreate function. */
         xHandle
    )
    

    Более подробную информацию о создании задач см. в справочном руководстве FreeRTOS, доступном на веб-сайте FreeRTOS.

    Добавьте дополнительные задачи, если это необходимо для приложения. Когда все задачи определены, можно запускать планировщик.

    Шаг 4:

    Скомпилируйте код с помощью установленного компилятора RISC?V и запустите приложение в QEMU. Позже вы сможете портировать его на выбранное вами оборудование.

    Создание простого приложения

    Описание примера приложения

    Ниже приводится описание упрощенного образца приложения. На основе этого описания мы создадим требования и построим приложение.

    Есть два входа, которые поступают в систему из двух внешних источников. Назовем эти входы In1 и In2. Имеется один выход, Out1, размер которого составляет два бита.

    In1 может менять свое состояние каждые 10 мс, а In2 может менять свое состояние каждые 20 мс. Исходя из значений In1 и In2, поведение Out1 определено ниже.

  • Если In1 высокий, бит 0 Out1 высокий; если In1 низкий, бит 0 Out1 низкий
  • Если In2 высокий, бит 1 Out1 высокий; если In2 низкий, бит 1 Out1 низкий
  • Для этого приложения необходимо три задачи: две для сбора входных данных и одна для управления выходом. Поэтому мы определим три задачи в главной функции.

    Ниже приводится одна из возможных реализаций двух задач для сбора входных данных:

    static void prvQueueSendTask1( void *pvParameters )
    {
         TickType_t xNextWakeTime;
         const unsigned long ulValueToSend = 100UL;
         const char * const pcMessage1 = "Transfer1";
         const char * const pcMessage2 = "Transfer2";
    
         /* Remove compiler warning about unused parameter. */
         ( void ) pvParameters;
    
         /* Initialize xNextWakeTime; this only needs to be done once. */
         xNextWakeTime = xTaskGetTickCount();
    
         for( ;; )
         {
    
               char buf[40];
    
               sprintf( buf, "%d: %s: %s", xGetCoreID(),
                          pcTaskGetName( xTaskGetCurrentTaskHandle() ),
                          pcMessage1 );
               vSendString( buf );
    
               /* Place this task into Blocked state until it is time to run again. */
               vTaskDelayUntil( xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS1 );
    
               /* Send input of 100UL to the queue, causing the queue to
               receive the task to unblock and toggle the LED. Since 0 is
               used as the block time, the sending operation will not block;
               it shouldn't need to block, as the queue should always be
               empty at this point in the code. */
               xQueueSend( xQueue, ulValueToSend, 0U );
         }
    }
    
    static void prvQueueSendTask2( void *pvParameters )
    {
    
         TickType_t xNextWakeTime;
         const unsigned long ulValueToSend = 200UL;
         const char * const pcMessage1 = "Transfer1";
         const char * const pcMessage2 = "Transfer2";
    
         /* Remove compiler warning about unused parameter. */
         ( void ) pvParameters;
    
         /* Initialize xNextWakeTime; this only needs to be done once. */
         xNextWakeTime = xTaskGetTickCount();
    
         for( ;; )
         {
    
               char buf[40];
    
               sprintf( buf, "%d: %s: %s", xGetCoreID(),
                          pcTaskGetName( xTaskGetCurrentTaskHandle() ),
                          pcMessage2 );
               vSendString( buf );
    
               /* Place this task into Blocked state until it is time to run again. */
               vTaskDelayUntil( xNextWakeTime,mainQUEUE_SEND_FREQUENCY_MS2 );
    
               /* Send input of 200UL to the queue, causing the queue to
               receive the task to unblock and toggle the LED. Since 0 is
               used as the block time, the sending operation will not block;
               it shouldn't need to block, as the queue should always be
               empty at this point in the code. */
               xQueueSend( xQueue, ulValueToSend, 0U );
         }
    }
    

    Задача для управления выходом может быть смоделирована следующим образом:

    static void prvQueueReceiveTask( void *pvParameters )
    {
    
         unsigned long ulReceivedValue;
         const unsigned long ulExpectedValue1 = 100UL;
         const unsigned long ulExpectedValue2 = 200UL;
         const char * const pcMessage1 = "Blink1";
         const char * const pcMessage2 = "Blink2";
         const char * const pcFailMessage = "Unexpected value received\r\n";
    
         /* Remove compiler warning about unused parameter. */
         ( void ) pvParameters;
    
         for( ;; )
         {
    
               char buf[40];
    
               /* Wait until something arrives in the queue; this task will
               block indefinitely, provided that INCLUDE_vTaskSuspend is set
               to 1 in FreeRTOSConfig.h. */
               xQueueReceive( xQueue, ulReceivedValue, portMAX_DELAY );
    
               /* To get here, something must have been received from the queue - but is it the expected value? If it is, toggle the LED. */
               if( ulReceivedValue == ulExpectedValue1 )
               {
                    sprintf( buf, "%d: %s: %s", xGetCoreID(),
                               pcTaskGetName( xTaskGetCurrentTaskHandle() ),
                               pcMessage1 );
                    vSendString( buf );
    
                    ulReceivedValue = 0U;
               }
               else if( ulReceivedValue == ulExpectedValue2 )
               {
                     sprintf( buf, "%d: %s: %s", xGetCoreID(),
                                pcTaskGetName( xTaskGetCurrentTaskHandle() ),
                                pcMessage2 );
                     vSendString( buf );
    
                     ulReceivedValue = 0U;
               }
               else
               {
                     vSendString( pcFailMessage );
               }
         }
    }
    

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

    Компиляция и запуск приложения

    Как компилировать и запускать приложения

    Компиляция и запуск приложения могут быть выполнены с помощью скриптов или простых файлов make.

    Результат примера показан на изображении ниже:

    Вывод для этого примера также можно увидеть в демонстрационном видео, представленном в лекции Портирование приложений FreeRTOS на процессоры RISC?V.

    Создание более сложных приложений

    Общие шаги для создания более сложных приложений такие же, как и для создания простых приложений, а именно:

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

    Сложные демонстрационные приложения для FreeRTOS можно найти в следующем месте FreeRTOS на GitHub: FreeRTOS/FreeRTOS-Plus/Demo/.

    Вернуться к учебному плану