Создание приложений под 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/.
