В предыдущей части урока мы познакомились с тем, что такое очередь, какие они бывают, какие у них особенности, а также попробовали в работе очередь, передав через неё пока однотипные данные из одной задачи в другую.
Теперь мы попробуем в очереди передать целую структуру.
Мы пока будем использовать и однотипный вид очереди и потоковый.
Поэтому добавим ещё одну глобальную переменную для очереди. Это будет несколько другой тип
osMessageQId pos_Queue;
osMailQId strout_Queue;
Данный тип, ровно как и функции, которые мы будем применять ниже, используются для работы с указателями в очередях.
Также добавим глобальную структуру для нашей очереди, в которой будет строка и количество тиков, которое мы также будем передавать из наших задач, чтобы ещё больше разгрузить задачу с выводом строки на дисплей
} struct_arg;
typedef struct struct_out_t {
uint32_t tick_count;
char str[60];
} struct_out;
Тогда и макрос тоже сделаем отдельный для такого типа очередей
#define QUEUE_SIZE (uint32_t) 1
#define MAIL_SIZE (uint32_t) 1
В main() создадим вторую очередь
pos_Queue = osMessageCreate(osMessageQ(pos_Queue), NULL);
osMailQDef(stroutqueue, MAIL_SIZE, struct_out);
strout_Queue = osMailCreate(osMailQ(stroutqueue), NULL);
В функции для трёх задач Task01 добавим переменную типа передаваемой в очереди структуры
arg = (struct_arg*) argument;
struct_out *qstruct;
Для очередей такого типа необходимо также выделить память. Сделаем это в бесконечном цикле в самом начале
for(;;)
{
qstruct = osMailAlloc(strout_Queue, osWaitForever);
Запишем количество системных квантов и имя функции в строку в структуру очереди, взяв имя из параметров
qstruct = osMailAlloc(strout_Queue, osWaitForever);
qstruct->tick_count = osKernelSysTick();
sprintf(qstruct->str, "%s %d", arg->str_name, osThreadGetPriority(NULL));
Отправим структуру в очередь
osMessagePut(pos_Queue, arg->y_pos, 100);
osMailPut(strout_Queue, qstruct);
В функции задачи-приёмника TaskStringOut добавим ещё одну переменную типа структуры состояния очереди, а также переменную типа передаваемой в очереди структуры
osEvent event, event1;
struct_out *qstruct;
В бесконечном цикле заберём данные из очереди, тем самым заодно получим все статусы
for(;;)
{
event1 = osMailGet(strout_Queue, osWaitForever);
Условие, находящееся в бесконечном цикле соберём ещё в тело другого условия
if (event1.status == osEventMail)
{
if (event.status == osEventMessage)
{
sprintf(str1,"task %lu", osKernelSysTick());
TFT_DisplayString(120, event.value.v, (uint8_t *)str1, LEFT_MODE);
}
}
Таким образом мы проверим состояние обоих очередей. Вообще-то, так делать нежелательно, но у нас код построен так. что это сработает. Потом мы всё подправим.
В теле условия самого нижнего уровня присвоим переменной типа структуры очереди указатель на элемент очереди
if (event.status == osEventMessage)
{
qstruct = event1.value.p;
И затем немного исправим вывод строки на дисплей, в котором помимо данных из первой очереди мы воспользуемся данными из второй — структурированной
sprintf(str1,"%s %lu", qstruct->str, qstruct->tick_count);
Соберём проект, прошьём контроллер и посмотрим результат
Всё прекрасно работает! Строки не путаются, приоритеты соблюдаются. Я специально сфотографировал дисплей в момент работы задач с максимальным приоритетов.
Только давайте теперь избавляться от вложенности условий наличия в очереди значения. Так как в одной оно может быть, в другой нет. Нам надо либо переделать алгоритм с сохранением предыдущего значения, а это очень ресурсозатратно. Поэтому, думаю, следует значение, которое мы забираем из обычной неструктурированной очереди, добавить в структурированную, а обычную удалить, смысла от неё уже нет, главное, что работать мы теперь умеем с очередями разного типа.
Удалим объявление очереди
osMessageQId pos_Queue;
А в структуру для потоковой очереди добавим теперь значение координаты по оси y
typedef struct struct_out_t {
uint32_t tick_count;
uint16_t y_pos;
Удалим макрос
#define QUEUE_SIZE (uint32_t) 1
В main() удалим создание очереди
osMessageQDef(pos_Queue, QUEUE_SIZE, uint16_t);
pos_Queue = osMessageCreate(osMessageQ(pos_Queue), NULL);
В функции задач Task01 отправим в структуру вертикальную координату
qstruct->tick_count = osKernelSysTick();
qstruct->y_pos = arg->y_pos;
Удалим строку, в которой мы отправляем данные в обычную очередь
osMessagePut(pos_Queue, arg->y_pos, 100);
Удалим из функции TaskStringOut одну переменную состояния очереди
osEvent event
, event1;
Также удалим строку, где выбираем данные из обычной очереди
event = osMessageGet(pos_Queue, 100);
Поправим переменную в следующей строке, убрав из неё единичку
event = osMailGet(strout_Queue, osWaitForever);
В условии также поправим
if (event.status == osEventMail)
Условие нижнего уровня удалим, а в теле условия также исправим переменную структуры и в функции вывода строки на дисплей мы также исправим вертикальную координату
if (event.status == osEventMail)
{
if (event.status == osEventMessage)
{
qstruct = event.value.p;
sprintf(str1,"%s %lu", qstruct->str, qstruct->tick_count);
TFT_DisplayString(120, qstruct->y_pos, (uint8_t *)str1, LEFT_MODE);
}
}
Соберём проект, прошьём контроллер, и посмотрим результат. У нас работает всё как и прежде не смотря на то, что мы всё объединили в одну очередь.
Итак, сегодня мы научились пользоваться очередями в операционной системе FreeRTOS, причём очереди мы умеем использовать как с элементами какого-то определённого типа переменной, так и со структурированными типами, что позволяет нам передавать из задачи в задачу данные одновременно нескольких типов, а также массивов.
Всем спасибо за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь 32F746G-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
Уважаемый автор, подскажите, не будет ли в этом месте:
for(;;)
{
qstruct = osMailAlloc(strout_Queue, osWaitForever);
утечки памяти. На каждой итерации цикла, в qstruct будет выделяться новый кусок памяти, а что его удаляет?
Может qstruct = osMailAlloc(strout_Queue, osWaitForever) должна быть до цикла?
Я думал об этом, но скорей всего не будет. У меня код работал почти сутки, не завис. В примере в репозитории было то же самое. Хотите, попробуйте до цикла, но заранее вижу, что работать не будет.
Да. Вы правы, функция osMailAlloc очищает и выделяет память одновременно.
Задался тем же вопросом. Но тогда зачем вот это:
/// Free a memory block from a mail.
/// \param[in] queue_id mail queue ID obtained with \ref osMailCreate.
/// \param[in] mail pointer to the memory block that was obtained with \ref osMailGet.
/// \return status code that indicates the execution status of the function.
/// \note MUST REMAIN UNCHANGED: \b osMailFree shall be consistent in every CMSIS-RTOS.
osStatus osMailFree (osMailQId queue_id, void *mail);
Тоже, сразу при просмотре кода, задался таким же вопросом. Недавно нашел неплохой код (проект) для отлова утечек и пр. во FreeRTOS, но т.к. только начал изучать материал по этой теме и еще не был готов его использовать, то потерял эту ссылку/материал. Может кто-то посоветует по этой теме что-то дельное? Спасибо. Если бы Владимир перешел бы снова к теме FreeRTOS, особливо к использованию STM32F746-Disco (ведь уже есть по ней наработки, да и плаха очень удалась у STM), то было очень-очень неплохо.
Как я понял, после того, как получили mail, память нужно освободить. Делается это функцией osMailFree (osMailQId queue_id, void *mail). Из того же примера, в функции, которая принимает mail, в конце обработки приёма mail используется функция osMailFree(mailId, pRMail).
В примере это не делается, поэтому, я так понял, что освобождать только при уничтожении задачи.
Здравствуйте, ошибочка у Вас закралась . Необходимо в функцию void TaskStringOut(void const * argument) в участке
if (event.status == osEventMail) дописать
osMailFree(strout_Queue, qstruct); /* free memory allocated for mail */
…без этого работать не будет…а так все норм.
Это не страшно, я знаю. Без этого работает.
Вообще-то надо, причём и в примере тоже ничего нет.
Без этого работать не будет, если мы будем использовать более древнюю схему распределения памяти, а при пятой это само происходит.
А какая версия памяти используется в CubeMX тут по умолчанию? Вы вроде говорили, в одном из уроков, про 4-ю? Так это? Спасибо.
Разобрался (нужно почаще в настройки смотреть).
У Вас в проекте 4-я схема распределения памяти используется.
Отсутствие утечки объясняется просто: в очереди всегда не более одного элемента.
Здравствуйте.
Скажите как передать данные из двух разных задач в третью?
Практически также как и из одной в другую.
Доброго времени суток.
А где же описано (вообще откуда растут функции) файла cmsis_os.c? В смысле что в мануале Mastering the FreeRTOS ™ Real Time Kernel. или The FreeRTOS™ Reference Manual вообще нет описания ни одной функции с префиксом os.
Это порт, там и так понятно, зайдите в функцию, а там уже будут вызовы функций FREERTOS, только они там уже не просто вызываются, а со всевозможными проверками. Я в качестве документации пользуюсь самой библиотекой.