STM Урок 108. FreeRTOS. Параметры
Продолжаем работу с операционной системой реального времени FreeRTOS.
Как я и обещал, поведаю я вам сегодня о том, каким образом можно создать несколько задач с уникальным кодом, используя для них для всех всего одну функцию реализации.
Оказывается, есть такой механизм — это механизм передачи параметров в функцию, которые мы туда передаём в момент создания задачи.
До сих пор мы входной параметр функции создания задачи, предназначенный для передачи параметров назначали как NULL. То есть мы вообще не передавали никаких параметров в задачи.
Можно передать один, два, одним словом сколько угодно параметров. Если нам нужно передать только один параметр, то мы создаём переменную любого типа, присваиваем её значение и передаём разименованный указатель типа void на эту переменную в качестве параметра. А если мы хотим передать несколько параметров, пусть даже различного типа, то тогда мы создаём структуру с разными переменными, также создаём переменную типа данной структуры, затем каждой переменной, которая станет уже полем структуры, присваиваем какие-либо значения и передаём уже указатель на данную структуру.
Так как работать мы будем с той же платой — STM32F746G-DISCOVERY, то для того, чтобы поработать с параметрами, мы создадим проект из проекта прошлого занятия CREATE_TASKS с именем PARAM01, откроем его в проектогенераторе Cube MX, и, вообще ничего не трогая сгенерируем проект для SystemWorkbench, откроем его там, настроим оптимизацию 1 и уберём отладочную конфигурацию, если та будет иметь место в проекте.
Попробуем собрать и прошить данный проект, если всё нормально, то двигаемся дальше.
Для параметров в файле main.c создадим глобальную структуру и три переменных её типа, так как у нас будет три задачи
osThreadId Task01Handle,Task02Handle,Task03Handle;
typedef struct struct_arg_t {
char str_name[10];
uint16_t y_pos;
uint32_t delay_per;
} struct_arg;
struct_arg arg01, arg02, arg03;
Первый параметр — это строка с именем задачи, мы её будем использовать, чтобы написать имя задачи на экране, второй параметр будет передавать позицию по вертикали, а третий — период задержки в милисекундах.
Из функции задачи по умолчанию StartDefaultTask убираем инициализацию SDRAM, LTDC и вывод шапки, в бесконечном цикле оставляем только небольшую задержку, а до бесконечного цикла выведем в USART список существующих задач с их некоторыми свойствами
/* USER CODE BEGIN 5 */
osThreadList((unsigned char *)str_buf);
HAL_UART_Transmit(&huart1,(uint8_t*)str_buf,strlen(str_buf),0x1000);
HAL_UART_Transmit(&huart1,(uint8_t*)"\r\n",2,0x1000);
/* Infinite loop */
for(;;)
{
osDelay(1);
}
А инициализацию нашей памяти и дисплея, а также вывод шапки на дисплей, мы добавим опять в функцию main()
/* USER CODE BEGIN 2 */
MT48LC4M32B2_init(&hsdram1);
HAL_LTDC_SetAddress(&hltdc,LCD_FRAME_BUFFER,0);
TFT_FillScreen(LCD_COLOR_BLACK);
TFT_SetFont(&Font24);
TFT_SetTextColor(LCD_COLOR_LIGHTGREEN);
TFT_DisplayString(0, 10, (uint8_t *)"Create tasks", CENTER_MODE);
TFT_SetTextColor(LCD_COLOR_MAGENTA);
TFT_DisplayString(14, 60, (uint8_t *)"Task1:", LEFT_MODE);
TFT_DisplayString(14, 110, (uint8_t *)"Task2:", LEFT_MODE);
TFT_DisplayString(14, 160, (uint8_t *)"Task3:", LEFT_MODE);
/* USER CODE END 2 */
Если мы теперь соберём и прошьём проект, то мы в терминальной программе теперь не увидим вновь созданных задач, так как мы удалили код их создания. Мы увидим лишь задачи, добавленные в Cube MX и задачу «Бездействие»

Из функций трёх задач, которые мы добавляли на прошлом уроке, оставим функцию только одной задачи Task01, а остальные удалим вместе с телами, а также удалим их прототипы. А вот хендлы оставим для всех трёх задач, так как функция у нас будет только одна, а задач будет три.
В функции main() после создания уже существующих задач, но до вызова планировщика, создадим пока одну задачу и пока без параметров
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
osThreadDef(tsk01, Task01, osPriorityIdle, 0, 128);
Task01Handle = osThreadCreate(osThread(tsk01), NULL);
/* USER CODE END RTOS_THREADS *
В функции, предназначенной для всех наших будущих трёх задач, Task01 мы немного изменим код, добавив туда бесконечный цикл с задержкой, в котором будем выводить количество прошедших системных квантов. Соответственно, уничтожение задачи мы также уберём. Мы это уже делать умеем и цель урока у нас теперь другая
void Task01(void const * argument)
{
TFT_SetTextColor(LCD_COLOR_BLUE);
for(;;)
{
sprintf(str1,"%lu ", osKernelSysTick());
TFT_DisplayString(280, 60, (uint8_t *)str1, RIGHT_MODE);
osDelay(1000);
}
}
Можно на данном этапе собрать код и прошить контроллер, в верхней строке у нас будут считаться системные кванты, как обычно

Пока у нас всё работает безо всяких параметров.
Вернёмся в функцию main() и проинициализируем наши параметры до создания нашей пока одной задачи
/* add threads, … */
strcpy(arg01.str_name,"task1");
strcpy(arg02.str_name,"task2");
strcpy(arg03.str_name,"task3");
arg01.y_pos = 60;
arg02.y_pos = 110;
arg03.y_pos = 160;
arg01.delay_per = 1000;
arg02.delay_per = 677;
arg03.delay_per = 439;
osThreadDef(tsk01, Task01, osPriorityIdle, 0, 128);
Мы здесь занесли значения во все поля наших структур — имена задач, позиции по вертикали, и значение задержки в милисекундах.
Теперь давайте при создании нашей первой задачи передадим в неё указатель на структуру с параметрами
Task01Handle = osThreadCreate(osThread(tsk01), (void*)&arg01);
Теперь у нас в задачу передан указатель на три параметра.
Немного изменим код функции задачи, чтобы наши параметры там отслеживались и использовались для того, чтобы код для каждого экземпляра задачи был уникален. Для этого мы сначала создадим локальный указатель типа нашей глобальной структуры и привяжем его к переданному указателю типа void
void Task01(void const * argument)
{
volatile struct_arg *arg;
arg = (struct_arg*) argument;
TFT_SetTextColor(LCD_COLOR_BLUE);
Код в бесконечном цикле данной функции мы также немного изменим, применяя наши параметры, обращаясь к полям структуры
for(;;)
{
sprintf(str1,"%lu ", osKernelSysTick());
TFT_DisplayString(280, arg->y_pos, (uint8_t *)str1, RIGHT_MODE);
sprintf(str1," %s ", arg->str_name);
TFT_DisplayString(275, arg->y_pos, (uint8_t *)str1, LEFT_MODE);
osDelay(arg->delay_per);
}
Первый параметр мы использовали для позиции по вертикали, второй — для вывода на дисплей имени задачи, а третий — для значения задержки.
Соберём код, прошьём контроллер и проверим работоспособность наших параметров

Отлично! Параметры передались, всё спозиционировалось, задержка срабатывает через заданное время и строка выводится.
Давайте аналогичным образом в функции main() создадим остальные две задачи и передадим им указатели на параметры, предназначенные для них
Task01Handle = osThreadCreate(osThread(tsk01), (void*)&arg01);
osThreadDef(tsk02, Task01, osPriorityIdle, 0, 128);
Task02Handle = osThreadCreate(osThread(tsk02), (void*)&arg02);
osThreadDef(tsk03, Task01, osPriorityIdle, 0, 128);
Task03Handle = osThreadCreate(osThread(tsk03), (void*)&arg03);
/* USER CODE END RTOS_THREADS */
Соберём код, прошьём контроллер и посмотрим результат на нашем дисплее

Всё прекрасно работает! Параметры в задачи передаются, тем самым обеспечивая задаче уникальный код. То есть у нас данные выводятся в разную позицию, строка также разная и задержка тоже. В первой строке цифры обновляются с одной частотой, во второй — быстрее, а в третьей ещё быстрее.
При старте нашей программы в терминальной программе мы также видим, что все наши задачи в списке присутствуют

Итак, в данном занятии мы научились передавать в создаваемые задачи уникальные параметры, тем самым позволяя использовать одну функцию для работы нескольких задач.
Всем спасибо за внимание!
Отладочную плату можно приобрести здесь STM32F746G-DISCOVERY
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)



Добрый день.
Пробую использовать FreeRTOS в STM32F103.
Настраивал и создавал задачи в CubeMX.
После сборки проекта в Keil выдало ошибку:
«no source»: Error: #5: cannot open source input file «../Middlewares/Third_Party/FreeRTOS/Source/stream_buffer.c»: No such file or directory
Подскажите с чем может быть связана ошибка? Что не активировал или не верно установил?
Спасибо.