|
| 1 | +== Создание приложений под FreeRTOS и RISC-V |
| 2 | + |
| 3 | +В последней главе этого курса мы рассмотрим процесс создания простого приложения FreeRTOS для процессора RISC-V, а затем скомпилируем и запустим наше приложение с помощью инструментария RISC-V. |
| 4 | +Мы закончим главу описанием различных сложных приложений, которые уже являются частью платформы FreeRTOS. |
| 5 | + |
| 6 | +К концу этой главы вы будете в силах: |
| 7 | + |
| 8 | +* Объяснять этапы создания и запуска приложения FreeRTOS. |
| 9 | +* Создавать простые приложения. |
| 10 | +* Компилировать и запускать приложения. |
| 11 | +* Понимать процесс создания более сложных приложений. |
| 12 | +
|
| 13 | +=== Этапы создания приложения FreeRTOS |
| 14 | + |
| 15 | +==== Инструкции |
| 16 | + |
| 17 | +Давайте рассмотрим шаги, необходимые для создания приложения FreeRTOS. |
| 18 | + |
| 19 | +===== Шаг 1: |
| 20 | + |
| 21 | +Начните со списка файлов и настроек, представленных в главе <<section-freertos>>. |
| 22 | + |
| 23 | +===== Шаг 2: |
| 24 | + |
| 25 | +Определите требования к приложению. |
| 26 | +После определения требований к приложению убедитесь, что следующие детали также определены: |
| 27 | + |
| 28 | +* необходимые задачи для приложения; |
| 29 | +* взаимодействие между задачами; |
| 30 | +* зависимости между задачами: |
| 31 | +** зависимости от данных; |
| 32 | +** зависимости управления; |
| 33 | +* требования к отклику в реальном времени для задач в приложении. |
| 34 | + |
| 35 | +После их определения разработчик приложения может планировать использование либо одного файла, либо нескольких файлов для определения и создания приложения. |
| 36 | + |
| 37 | +===== Шаг 3: |
| 38 | + |
| 39 | +Начните определять задачи в файлах по мере необходимости. |
| 40 | +Все задачи должны быть определены перед запуском планировщика FreeRTOS. |
| 41 | + |
| 42 | +.Пример определения задачи |
| 43 | +[source,c] |
| 44 | +---- |
| 45 | +xTaskCreate( |
| 46 | + /* Pointer to the function that implements the task. */ |
| 47 | + vTaskCode, |
| 48 | + /* Name of the task. */ |
| 49 | + "Demo task", |
| 50 | + /* The size of the stack that should be created for the task. This is defined in words, not bytes. */ |
| 51 | + STACK_SIZE, |
| 52 | + /* A reference to xParameters is used as the task parameter. This is cast to a void * to prevent compiler warnings. */ |
| 53 | + (void*) &xParameter, |
| 54 | + /* The priority to be assigned to the newly created task. */ |
| 55 | + TASK_PRIORITY, |
| 56 | + /* The handle to the created task will be placed into xHandle parameter as the output of the xTaskCreate function. */ |
| 57 | + &xHandle |
| 58 | +) |
| 59 | +---- |
| 60 | + |
| 61 | +Более подробную информацию о создании задач см. в https://www.freertos.org/Documentation/RTOS_book.html[справочном руководстве FreeRTOS], доступном на веб-сайте FreeRTOS. |
| 62 | + |
| 63 | +Добавьте дополнительные задачи, если это необходимо для приложения. |
| 64 | +Когда все задачи определены, можно запускать планировщик. |
| 65 | + |
| 66 | +===== Шаг 4: |
| 67 | + |
| 68 | +Скомпилируйте код с помощью установленного компилятора RISC-V и запустите приложение в QEMU. |
| 69 | +Позже вы сможете портировать его на выбранное вами оборудование. |
| 70 | + |
| 71 | +=== Создание простого приложения |
| 72 | + |
| 73 | +==== Описание примера приложения |
| 74 | + |
| 75 | +Ниже приводится описание упрощенного образца приложения. |
| 76 | +На основе этого описания мы создадим требования и построим приложение. |
| 77 | + |
| 78 | +Есть два входа, которые поступают в систему из двух внешних источников. |
| 79 | +Назовем эти входы `In1` и `In2`. |
| 80 | +Имеется один выход, `Out1`, размер которого составляет два бита. |
| 81 | + |
| 82 | +`In1` может менять свое состояние каждые 10 мс, а `In2` может менять свое состояние каждые 20 мс. |
| 83 | +Исходя из значений `In1` и `In2`, поведение `Out1` определено ниже. |
| 84 | + |
| 85 | +* Если `In1` *высокий*, бит `0` `Out1` *высокий*; если `In1` *низкий*, бит `0` `Out1` *низкий* |
| 86 | +* Если `In2` *высокий*, бит `1` `Out1` *высокий*; если `In2` *низкий*, бит `1` `Out1` *низкий* |
| 87 | + |
| 88 | +Для этого приложения необходимо три задачи: две для сбора входных данных и одна для управления выходом. |
| 89 | +Поэтому мы определим три задачи в главной функции. |
| 90 | + |
| 91 | +Ниже приводится одна из возможных реализаций двух задач для сбора входных данных: |
| 92 | + |
| 93 | +[source,c] |
| 94 | +---- |
| 95 | +static void prvQueueSendTask1( void *pvParameters ) |
| 96 | +{ |
| 97 | + TickType_t xNextWakeTime; |
| 98 | + const unsigned long ulValueToSend = 100UL; |
| 99 | + const char * const pcMessage1 = "Transfer1"; |
| 100 | + const char * const pcMessage2 = "Transfer2"; |
| 101 | +
|
| 102 | + /* Remove compiler warning about unused parameter. */ |
| 103 | + ( void ) pvParameters; |
| 104 | +
|
| 105 | + /* Initialize xNextWakeTime; this only needs to be done once. */ |
| 106 | + xNextWakeTime = xTaskGetTickCount(); |
| 107 | +
|
| 108 | + for( ;; ) |
| 109 | + { |
| 110 | +
|
| 111 | + char buf[40]; |
| 112 | +
|
| 113 | + sprintf( buf, "%d: %s: %s", xGetCoreID(), |
| 114 | + pcTaskGetName( xTaskGetCurrentTaskHandle() ), |
| 115 | + pcMessage1 ); |
| 116 | + vSendString( buf ); |
| 117 | +
|
| 118 | + /* Place this task into Blocked state until it is time to run again. */ |
| 119 | + vTaskDelayUntil( &xNextWakeTime, mainQUEUE_SEND_FREQUENCY_MS1 ); |
| 120 | +
|
| 121 | + /* Send input of 100UL to the queue, causing the queue to |
| 122 | + receive the task to unblock and toggle the LED. Since 0 is |
| 123 | + used as the block time, the sending operation will not block; |
| 124 | + it shouldn't need to block, as the queue should always be |
| 125 | + empty at this point in the code. */ |
| 126 | + xQueueSend( xQueue, &ulValueToSend, 0U ); |
| 127 | + } |
| 128 | +} |
| 129 | +
|
| 130 | +static void prvQueueSendTask2( void *pvParameters ) |
| 131 | +{ |
| 132 | +
|
| 133 | + TickType_t xNextWakeTime; |
| 134 | + const unsigned long ulValueToSend = 200UL; |
| 135 | + const char * const pcMessage1 = "Transfer1"; |
| 136 | + const char * const pcMessage2 = "Transfer2"; |
| 137 | +
|
| 138 | + /* Remove compiler warning about unused parameter. */ |
| 139 | + ( void ) pvParameters; |
| 140 | +
|
| 141 | + /* Initialize xNextWakeTime; this only needs to be done once. */ |
| 142 | + xNextWakeTime = xTaskGetTickCount(); |
| 143 | +
|
| 144 | + for( ;; ) |
| 145 | + { |
| 146 | +
|
| 147 | + char buf[40]; |
| 148 | +
|
| 149 | + sprintf( buf, "%d: %s: %s", xGetCoreID(), |
| 150 | + pcTaskGetName( xTaskGetCurrentTaskHandle() ), |
| 151 | + pcMessage2 ); |
| 152 | + vSendString( buf ); |
| 153 | +
|
| 154 | + /* Place this task into Blocked state until it is time to run again. */ |
| 155 | + vTaskDelayUntil( &xNextWakeTime,mainQUEUE_SEND_FREQUENCY_MS2 ); |
| 156 | +
|
| 157 | + /* Send input of 200UL to the queue, causing the queue to |
| 158 | + receive the task to unblock and toggle the LED. Since 0 is |
| 159 | + used as the block time, the sending operation will not block; |
| 160 | + it shouldn't need to block, as the queue should always be |
| 161 | + empty at this point in the code. */ |
| 162 | + xQueueSend( xQueue, &ulValueToSend, 0U ); |
| 163 | + } |
| 164 | +} |
| 165 | +---- |
| 166 | + |
| 167 | +Задача для управления выходом может быть смоделирована следующим образом: |
| 168 | + |
| 169 | +[source,c] |
| 170 | +---- |
| 171 | +static void prvQueueReceiveTask( void *pvParameters ) |
| 172 | +{ |
| 173 | +
|
| 174 | + unsigned long ulReceivedValue; |
| 175 | + const unsigned long ulExpectedValue1 = 100UL; |
| 176 | + const unsigned long ulExpectedValue2 = 200UL; |
| 177 | + const char * const pcMessage1 = "Blink1"; |
| 178 | + const char * const pcMessage2 = "Blink2"; |
| 179 | + const char * const pcFailMessage = "Unexpected value received\r\n"; |
| 180 | +
|
| 181 | + /* Remove compiler warning about unused parameter. */ |
| 182 | + ( void ) pvParameters; |
| 183 | +
|
| 184 | + for( ;; ) |
| 185 | + { |
| 186 | +
|
| 187 | + char buf[40]; |
| 188 | +
|
| 189 | + /* Wait until something arrives in the queue; this task will |
| 190 | + block indefinitely, provided that INCLUDE_vTaskSuspend is set |
| 191 | + to 1 in FreeRTOSConfig.h. */ |
| 192 | + xQueueReceive( xQueue, &ulReceivedValue, portMAX_DELAY ); |
| 193 | +
|
| 194 | + /* To get here, something must have been received from the queue – but is it the expected value? If it is, toggle the LED. */ |
| 195 | + if( ulReceivedValue == ulExpectedValue1 ) |
| 196 | + { |
| 197 | + sprintf( buf, "%d: %s: %s", xGetCoreID(), |
| 198 | + pcTaskGetName( xTaskGetCurrentTaskHandle() ), |
| 199 | + pcMessage1 ); |
| 200 | + vSendString( buf ); |
| 201 | +
|
| 202 | + ulReceivedValue = 0U; |
| 203 | + } |
| 204 | + else if( ulReceivedValue == ulExpectedValue2 ) |
| 205 | + { |
| 206 | + sprintf( buf, "%d: %s: %s", xGetCoreID(), |
| 207 | + pcTaskGetName( xTaskGetCurrentTaskHandle() ), |
| 208 | + pcMessage2 ); |
| 209 | + vSendString( buf ); |
| 210 | +
|
| 211 | + ulReceivedValue = 0U; |
| 212 | + } |
| 213 | + else |
| 214 | + { |
| 215 | + vSendString( pcFailMessage ); |
| 216 | + } |
| 217 | + } |
| 218 | +} |
| 219 | +---- |
| 220 | + |
| 221 | +Поскольку этот пример выполняется в режиме эмуляции, мы реализовали задачи ввода для совместного использования входных данных с задачей вывода через очередь. |
| 222 | +В реальной системе эти данные поступали бы через входные контакты. |
| 223 | +Аналогично, выходной сигнал в примере представлен в виде текстовых сообщений, тогда как в реальном приложении он будет иметь форму светящихся светодиодов. |
| 224 | + |
| 225 | +=== Компиляция и запуск приложения |
| 226 | + |
| 227 | +==== Как компилировать и запускать приложения |
| 228 | + |
| 229 | +Компиляция и запуск приложения могут быть выполнены с помощью скриптов или простых файлов `make`. |
| 230 | + |
| 231 | +[%unbreakable] |
| 232 | +-- |
| 233 | +Результат примера показан на изображении ниже: |
| 234 | + |
| 235 | +image:app_execution.png[Пример запуска приложения] |
| 236 | +-- |
| 237 | + |
| 238 | +Вывод для этого примера также можно увидеть в демонстрационном видео, представленном в главе <<section-porting>>. |
| 239 | + |
| 240 | +=== Создание более сложных приложений |
| 241 | + |
| 242 | +Общие шаги для создания более сложных приложений такие же, как и для создания простых приложений, а именно: |
| 243 | + |
| 244 | +[arabic] |
| 245 | +. Сбор требований к приложению. |
| 246 | +. Определение входов и выходов системы и их зависимостей. |
| 247 | +. Сбор всех требований к приложению, связанных со временем. |
| 248 | + |
| 249 | +После сбора вышеуказанной информации следующим шагом будет определение необходимых задач, очередей, семафоров и других соответствующих компонентов для приложения. |
| 250 | +Создайте приложение, используя эту информацию, а затем перейдите к фазам компиляции и запуска. |
| 251 | + |
| 252 | +Сложные демонстрационные приложения для FreeRTOS можно найти в следующем месте FreeRTOS на GitHub: `+FreeRTOS/FreeRTOS-Plus/Demo/+`. |
0 commit comments