В предыдущей части урока мы познакомились с семафорами, создали и настроили проект для работы с ними.
Подключим библиотеки в main.c
/* USER CODE BEGIN Includes */
#include "stdint.h"
#include "string.h"
#include "ltdc.h"
#include "MT48LC4M32B2.h"
#include "fonts.h"
/* USER CODE END Includes */
Подключим память для видеобуфера
/* Private variables ---------------------------------------------------------*/
#define LCD_FRAME_BUFFER SDRAM_DEVICE_ADDR
/* USER CODE END PV */
Инициализируем память, видеобуфер и окрасим экран в чёрный цвет в main()
/* USER CODE BEGIN 2 */
MT48LC4M32B2_init(&hsdram1);
HAL_LTDC_SetAddress(&hltdc,LCD_FRAME_BUFFER,0);
TFT_FillScreen(LCD_COLOR_BLACK);
/* USER CODE END 2 */
Оформим наш экран шапкой, наименованием колонок и строк
TFT_FillScreen(LCD_COLOR_BLACK);
TFT_SetFont(&Font24);
TFT_SetTextColor(LCD_COLOR_LIGHTGREEN);
TFT_DisplayString(0, 10, (uint8_t *)"Binary Semaphores", CENTER_MODE);
TFT_SetTextColor(LCD_COLOR_DARKGREEN);
TFT_DisplayString(110, 70, (uint8_t *)"work", LEFT_MODE);
TFT_SetTextColor(LCD_COLOR_DARKRED);
TFT_DisplayString(200, 70, (uint8_t *)"wait", LEFT_MODE);
TFT_SetTextColor(LCD_COLOR_MAGENTA);
TFT_DisplayString(14, 100, (uint8_t *)"Task1:", LEFT_MODE);
TFT_SetTextColor(LCD_COLOR_CYAN);
TFT_DisplayString(14, 160, (uint8_t *)"Task2:", LEFT_MODE);
Вот так вот должно красиво получиться
Какие и где будут бежать циферки, мы разберёмся позже.
Для начала давайте добавим обычный счётчик секунд немного ниже нашей таблицы.
Для этого сначала добавим несколько глобальных переменных и строковый массив
#define LCD_FRAME_BUFFER SDRAM_DEVICE_ADDR
volatile uint32_t TIM1_Count=0, TIM1_Count_Sec=0;
char str1[20];
volatile uint8_t tasks_started=0;
Мы добавили счётчик тиков таймера 1, который мы выбрали в качестве базового для ОС, счётчик секунд, а также счетчик количества запущенных задач.
В каждой задаче проинкрементируем данный счётчик
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN 5 */
tasks_started++;
void StartTask02(void const * argument)
{
/* USER CODE BEGIN StartTask02 */
tasks_started++;
В процедуре обработчика таймера, которая уже добавлена внизу страницы, вставим следующий код
/* USER CODE BEGIN Callback 1 */
if(tasks_started>=2)
{
TIM1_Count++;
if(TIM1_Count%1000==0)
{
TIM1_Count_Sec++;
TFT_SetTextColor(LCD_COLOR_BLUE);
sprintf(str1,"%lu ",TIM1_Count_Sec);
TFT_DisplayString(167, 190, (uint8_t *)str1, LEFT_MODE);
}
if(TIM1_Count>=10000000) TIM1_Count=0;
}
/* USER CODE END Callback 1 */
В определённое место экрана мы будем выводить секунды, так как таймер работает с периодом в 1 милисекунду. Мы делим по модулю на 1000 и если ноль, то попадаем в тело условия. А чтобы не было проблем с последними тиками, то ограничим счёт до числа, делящегося на 1000. Также начинать счёт наш счётчик будет только в том случае, когда обе задачи уже будут запущены (tasks_started>=2).
Соберём код, прошьём контроллер и посмотрим, как считаются секунды
Добавим глобальные переменные для счётчиков каждой задачи
volatile uint8_t tasks_started=0;
uint32_t ncount1=0, ncount2=0;
Добавим функцию для процесса, который будет вызываться из обеих задач, над функциями самих задач
/* USER CODE BEGIN 4 */
void PrintCounter(uint8_t ID_Task)
{
uint8_t i=0;
i++;
for(i=1;i<=50;i++)
{
if(ID_Task==1)
{
TFT_SetTextColor(LCD_COLOR_MAGENTA);
sprintf(str1,"%d ",i);
TFT_DisplayString(130, 100, (uint8_t *)str1, LEFT_MODE);
}
else if
(ID_Task==2)
{
TFT_SetTextColor(LCD_COLOR_CYAN);
sprintf(str1,"%d ",i);
TFT_DisplayString(130, 160, (uint8_t *)str1, LEFT_MODE);
}
osDelay(500);
}
if(ID_Task==1)
{
TFT_SetTextColor(LCD_COLOR_MAGENTA);
TFT_DisplayString(130, 100, (uint8_t *)" ", LEFT_MODE);
ncount1=0;
}
else if
(ID_Task==2)
{
TFT_SetTextColor(LCD_COLOR_CYAN);
TFT_DisplayString(130, 160, (uint8_t *)" ", LEFT_MODE);
ncount2=0;
}
}
/* USER CODE END 4 */
В данной функции мы считаем от 1 до 50 с интервалом счёта в полсекунды. В качестве параметра передаём идентификатор задачи, который мы сами присвоим, вызывая процесс из задач. В соответствии с данным идентификатором мы будем отображать цифру со счётом в строку, соответствующую задаче. После того, как счётчик досчитает, мы сбрасываем счётчики задач, которые пока не будут иметь смысла, весь смысл будет позже.
Далее переходим в первую задачу и вызовем оттуда данную функцию
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN 5 */
tasks_started++;
/* Infinite loop */
for(;;)
{
PrintCounter(1);
osDelay(1);
Также вызовем её и в другой задаче
void StartTask02(void const * argument)
{
/* USER CODE BEGIN StartTask02 */
tasks_started++;
/* Infinite loop */
for(;;)
{
PrintCounter(2);
osDelay(1);
Соберём код, прошьём контроллер и получим вот такой результат
Мы видим, что всё прекрасно работает, многозадачность поддерживается, то есть обе задачи (или процесса) в ней одновременно присутствуют, поэтому счётчики считают одновременно. Но давайте себе представим, что код, находящийся в нашей функции, представляет собой критическую секцию, которая должна выполняться в одно время только одним процессом. Для этого и существуют семафоры. Поэтому, код, находящийся в ней (тело функции), мы заключим ещё в некоторые условия
void PrintCounter(uint8_t ID_Task)
{
uint8_t i=0;
if (myBinarySem01Handle != NULL)
{
if(osSemaphoreWait(myBinarySem01Handle , 100) == osOK)
{
i++;
. . . . . . . . . . . . . . .
ncount2=0;
}
osSemaphoreRelease(myBinarySem01Handle);
}
}
}
Сейчас я расскажу, что тут происходит. Критическую секцию мы включили в условие, в котором мы сначала проверяем, что у нас вообще существует семафор а затем применяем функцию osSemaphoreWait, которая блокирует критическую секцию, то есть эта функция и убавляет счётчик семафора на 1, а так как он у нас уже в 1, так как он бинарный, то теперь его значение будет 0, и семафор до тех пор, пока процесс, захвативший его первым не покинет критическую секцию, вернее, пока он не вызовет определённую функцию, никого больше туда не пустит. Первым параметром функции будет идентификатор семафора, а вторым — таймаут в милисекундах, в течении которого процесс, который будет пытаться войти в заблокированную критическую секцию, будет ждать. Если он в течении данного времени, в нашем случае 100 милисекунд, не дождётся освобождения критической секции, то он продолжит дальше выполнять свой код, не входя в секцию. После выполнения кода критической функции мы вызываем функцию osSemaphoreRelease, которая и увеличит счётчик семафора на 1, тем самым вернёт ему значение 1 и вход в критическую секцию будет свободен. Чтобы лучше проследить за процессом, добавим ещё некоторый код в наши задачи, недаром же у нас есть ещё один столбец в таблице
void StartDefaultTask(void const * argument)
{
/* USER CODE BEGIN 5 */
tasks_started++;
/* Infinite loop */
for(;;)
{
ncount1++;
TFT_SetTextColor(LCD_COLOR_MAGENTA);
sprintf(str1,"%lu ",ncount1);
TFT_DisplayString(220, 100, (uint8_t *)str1, LEFT_MODE);
PrintCounter(1);
void StartTask02(void const * argument)
{
/* USER CODE BEGIN StartTask02 */
tasks_started++;
/* Infinite loop */
for(;;)
{
ncount2++;
TFT_SetTextColor(LCD_COLOR_CYAN);
sprintf(str1,"%lu ",ncount2);
TFT_DisplayString(220, 160, (uint8_t *)str1, LEFT_MODE);
PrintCounter(2);
Мы увеличиваем значение счётчика, соответствующего задаче, отображаем его в соответствующей задаче строке в колонке «wait», а затем вызываем процесс.
Соберём код, прошьём контроллер и посмотрим результат
Как мы видим, счётчик считает только одного процесса, а другой процесс его терпеливо дожидается, стучась каждые 100 милисекунд в критическую секцию, что подтверждает счётчик в колонке «wait». Ну вернее, если быть точными, то 101 милисекунду, так как у нас ещё в бесконечном цикле есть задержка на такую величину. Ну и сам код тоже какое-то время выполняется. Поэтому и счётчик ожидания у нас, видимо досчитывает не до 249, а до 248.
Но дело не в этом, а в том что цель, которую мы поставили в начале занятия, мы с успехом достигли. Мы научились в операционной системе реального времени FreeRTOS добавлять определённый код, выполнение которого нежелательно одновременно несколькими процессами. И данную задачу мы выполнили, так как мы наглядно увидели, что когда один процесс выполняет данный код, другой процесс туда не попадает. А достигли мы этого благодаря применению бинарного семафора. Надеюсь, что то, что я здесь рассказал, вы поняли. Я старался рассказать подробно, но без лишней воды.
Всем спасибо за внимание!
Предыдущая часть Программирование МК STM32 Следующий урок
Отладочную плату можно приобрести здесь 32F746G-DISCOVERY
Смотреть ВИДЕОУРОК (нажмите на картинку)
Уважаемый Владимир,
В коде функции:
void PrintCounter(uint8_t ID_Task)
{
uint8_t i=0;
i++;
for(i=1;i<=50;i++)
не понятно для чего i=0 и i++ если далее следует цикл с присвоением i.
Есть еще 1 момент, у меня myTask02 с стексайзом 128 завис. И я долго не мог понять почему оба счетчика в процессах = 1, пришлось увеличивать стексайз для myTask02 до 1280. Может у меня в чем-то другом проблема?
Спасибо за столь бурный интерес к теме!
Значит не зря я её поднял.
Очень странно, что повисает. У меня ничего не повисало. Тут стека много не надо.
А переменные я привык уже инициализировать нулём, тут ничего страшного нет. Всего лишь одна элементарная операция тратится. А вот i++ — косяк, ну пусть остаётся. Это у меня такой призрак остался от разных проб с функцией. Так что можете убрать.
Спасибо вам. Очень интересная тема. Очень хочется продолжения по Rtos. Все-таки стм32 не ардуино и Ртос для него просто необходимость!
Может нехватать памяти стеку при использовании va_start и тд?
И Вам спасибо за интерес!
Трудно сказать. А плата у Вас такая же? F746-Disco?
Да.
Но я использую связку arm-none-eabi-gcc и makefile который генерирует Куб. Кстати, последняя версия Куба 4.23 поломала создание мейкфайла, почему-то дублирует в секции C_SOURCES файлы из папки Scr. Приходится в ручную удалять дубликаты.
Посмотрел мануал на РТОС, что вы дали, там так и написано: использование printf может вызвать переполнения стека.
Добрый вечер Владимир!
Давно смотрю ваши курсы — огромное спасибо за столь тяжелый труд и толковое изложение материала!
Возник вопрос — в последних фильмах не четко программы вижу с экрана — размыто и нет контрасности!
Смотрю через KMPlayer! Может Вы изменили формат или кодек, подскажите что может быть?
Точнее чем лучше смотреть,чтобы вернуть четкость?
Ответил на видеохостинге.
Спасибо! Разобрался!