На прошлом уроке мы уже работали с семафорами, но это были двоичные семафоры. Теперь на повестке дня семафоры счётные. Двоичные семафоры от счётных практически не отличаются ничем, только количество элементов в них всегда равно одному, а в счётных — более одного.
Счётные семафоры как правило требуются тогда, когда либо надо подсчитать несколько событий из различных задач или прерываний, либо когда двоичные не успевают срабатывать в том случае, когда прерывания от одного устройства происходят со слишком большой частотой и задача-обработчик, в которую отдаётся семафор, не успевает перейти в блокированное состояние по завершении обработки предыдущего прерывания, а сразу же займётся обслуживанием следующего.
Только это всё теоретическая информация. На самом деле в обычной жизни счётные семафоры применяются и в других случаях, не обязательно для синхронизации векторов прерываний с обслуживающими задачами. И сегодня мы это увидим на практике.
Для создания счётного семафора применяется вот такая функция
где
uxMaxCount – Максимальное значение счётчика, которое может быть достигнуто. Когда семафор достигает этого значения, его больше нельзя отдать.
uxInitialCount – Значение счётчика, присвоенное семафору при его создании.
С остальными функциями мы познакомимся при написании нашего проекта.
Схема наша со времён предыдущего урока не изменилась
Проект был сделан из проекта урока 7 с именем I2C_LCD2004 и получил имя CNT_SEM_LCD.
Откроем наш проект в Espressif IDE и в функции app_main файла main.c удалим объявление переменной и массива
uint16_t i=0;
char str01[10];
Вот это объявление также удалим
qLCDData xLCDData;
Удалим также и весь код после строки
LCD_ini();
до бесконечного цикла.
В бесконечном цикле оставим только задержку
while (1) {
vTaskDelay(100 / portTICK_PERIOD_MS);
}
Сегодня давайте объявим и создадим семафор сразу в функции, не будем использовать глобальный указатель
1 2 |
ESP_LOGI(TAG, "i2c_ini: %d", ret); xSemaphoreHandle s_sem_cnt = xSemaphoreCreateCounting(3, 0); |
Логика проекта будет следующая. Мы создали семафор с максимальным значением счёта 3, проинициализировали его нулём, то есть забирать нам оттуда пока нечего. Далее мы создадим 4 задачи, 3 из которых будут семафор отдавать, а одна — забирать.
Выше функции app_main добавим функции для наших задач — пока для трёх
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
//------------------------------------------------ static void task1(void* arg) { SemaphoreHandle_t sem_cnt = (SemaphoreHandle_t) arg; char str1[15] = {0}; qLCDData xLCDData; xLCDData.x_pos = 0; xLCDData.y_pos = 0; xLCDData.str = str1; for(int i=0;i<10;i++) { sprintf(str1,"%05d",i); xQueueSendToBack(lcd_string_queue, &xLCDData, 0); vTaskDelay(500 / portTICK_PERIOD_MS); } xSemaphoreGive(sem_cnt); vTaskDelete(NULL); } //------------------------------------------------ static void task2(void* arg) { SemaphoreHandle_t sem_cnt = (SemaphoreHandle_t) arg; char str1[15] = {0}; qLCDData xLCDData; xLCDData.x_pos = 0; xLCDData.y_pos = 1; xLCDData.str = str1; for(int i=0;i<20;i++) { sprintf(str1,"%05d",i); xQueueSendToBack(lcd_string_queue, &xLCDData, 0); vTaskDelay(500 / portTICK_PERIOD_MS); } xSemaphoreGive(sem_cnt); vTaskDelete(NULL); } //------------------------------------------------ static void task3(void* arg) { SemaphoreHandle_t sem_cnt = (SemaphoreHandle_t) arg; char str1[15] = {0}; qLCDData xLCDData; xLCDData.x_pos = 0; xLCDData.y_pos = 2; xLCDData.str = str1; for(int i=0;i<30;i++) { sprintf(str1,"%05d",i); xQueueSendToBack(lcd_string_queue, &xLCDData, 0); vTaskDelay(500 / portTICK_PERIOD_MS); } xSemaphoreGive(sem_cnt); vTaskDelete(NULL); } //------------------------------------------------ |
Код во всех задачах аналогичный. Мы забираем указатель на семафор из параметров, считаем с задержкой в полсекунды в первой задаче до 10, во второй — до 20, в третьей — до 30, отображая процесс счёта на дисплее в каждой задаче на определённой строке дисплея и по окончанию счёта отдаём семафор, в результате чего у нас как раз наш семафор и достигнет максимального значения. Функция xSemaphoreGive возвращает значение pdTRUE или pdFALSE, по которому можем узнать, смогли ли мы отдать семафор, но мы этого делать не будем, чтобы не усложнять наш код. У нас три задачи и максимальное значение счёта также три, поэтому ошибок у нас не будет.
Ниже добавим функцию для четвёртой задачи
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
//------------------------------------------------ static void task4(void* arg) { SemaphoreHandle_t sem_cnt = (SemaphoreHandle_t) arg; char str1[15] = {0}; int cnt=0; qLCDData xLCDData; xLCDData.x_pos = 0; xLCDData.y_pos = 3; xLCDData.str = str1; for (int i=0;i<3;++i) { xSemaphoreTake(sem_cnt, portMAX_DELAY); } for(;;) { sprintf(str1,"%05d",cnt); xQueueSendToBack(lcd_string_queue, &xLCDData, 0); vTaskDelay(500 / portTICK_PERIOD_MS); cnt++; } } //------------------------------------------------ |
Здесь мы также получаем указатель на семафор из параметров и в цикле пытаемся его забрать, так как цикл у нас имеет три итерации, то значит, что пока мы не заберём все три элемента семафора, то мы ниже цикла не провалимся и код наш не выполнится, то есть счётчик нашей задачи не начнёт считать. Таким образом, чтобы счётчик в четвёртой задаче запустился, все три остальные задачи должны отработать, то есть отдать нам семафор. Вот в этом и суть счётного семафора в проектах подобных нашему. Мы получим сигнал тогда, когда произойдут все три события.
Вернёмся в функцию app_main и создадим наши задачи
1 2 3 4 5 |
LCD_ini(); xTaskCreate(task1, "task1", 2048, s_sem_cnt, 5, NULL); xTaskCreate(task2, "task2", 2048, s_sem_cnt, 5, NULL); xTaskCreate(task3, "task3", 2048, s_sem_cnt, 5, NULL); xTaskCreate(task4, "task4", 2048, s_sem_cnt, 5, NULL); |
Соберём код, прошьём контроллер и посмотрим результат
Всё работает так, как и было задумано. Как только три задачи отработали (все три события произошли), код в четвёртой задаче продолжил выполняться.
Итак, на данном уроке мы познакомились со счётными семафорами, как ещё с одним видом инструмента для синхронизации задач и межпроцессным взаимодействием в операционных системах реального времени, со смыслом их использования, а также продолжили знакомство на практике. Возможно, мы продолжим в будущем знакомство с данными видом семафоров в плане их использования в прерываниях, хотя большой разницы в коде там не будет.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Переходник I2C to LCD можно приобрести здесь I2C to LCD1602 2004
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий