Counting Semaphores will help you manage your events and resources in FreeRTOS. Read through this article to learn more about it.
Introduction
Managing events or resources can be a challenge when you are writing programs in a FreeRTOS environment. FreeRTOS is a multitasking operating system where each task operates on a never-ending loop. With this, you need some sort of synchronization mechanism to help tasks or events relay it’s intended operation to one another. One such synchronization mechanism is the binary semaphore discussed earlier.
However, binary semaphores are limited to counting only to a bit. There may be times you need to monitor the counting of an event. Such events like sequential fading of LEDs or access to a resource constrained four level UART FIFO buffer. The number of times you access these peripherals or resources matter. Here is where counting semaphores come into play.
What are Counting Semaphores
Counting semaphores, much like binary semaphores synchronize events. However, they have a count value that is significant when accessing a constrained resource or defining the behavior of a process. Below, you’ll get to know how to use a counting semaphore in FreeRTOS.
How to Use a Counting Semaphore
First, declare a counting semaphore type at the beginning of your program. Below the xCountingSemaphore token is declared as SemaphoreHandle_t.
SemaphoreHandle_t xCountingSemaphore;
Next, create the counting semaphore by using xSemaphoreCreateCounting() API. The parameters of this API includes the number of counts and the starting count of the counting semaphore token.
xCountingSemaphore = xSemaphoreCreateCounting(NUM_SEMAPHORE, 0);
After that, you can begin using the semaphore on your tasks or events. Use xSemaphoreGive() from a task or event to give the counting semaphore token to another task. The parameter of xSemaphoreGive() includes only the counting semaphore you declared earlier (xCountingSemaphore).Â
xSemaphoreGive(xCountingSemaphore);
When taking the semaphore through another task or event, use xSemaphoreTake(). The parameters of this API include the counting semaphore token and a blocking delay value while waiting for the token from xSemaphoreGive(). After the wait delay or taking the token, the next series of statements are executed. A return flag value of pdFalse is set by xSemaphoreTake() when the wait delay expires so that the user can apply an error handler routine. A wait delay of portMAX_DELAY signifies waiting forever for the token while blocking on the calling task.
xSemaphoreTake(xCountingSemaphore, portMAX_DELAY);
Example Applications
Buffering Events
Below is an example code that illustrates buffering of events through a counting queue token using a counting semaphore.Â
#include
#include
#include
#include
#include
#define LED_1 GPIO_NUM_4
#define LED_2 GPIO_NUM_5
#define LED_3 GPIO_NUM_6
#define LED_4 GPIO_NUM_7
#define NUM_SEMAPHORE 4
#define delay_time 500
void LED1_task(void *parameters);
void LED2_task(void *parameters);
SemaphoreHandle_t xCountingSemaphore1;
void app_main(void)
{
// LED Pin
gpio_set_direction(LED_1, GPIO_MODE_OUTPUT);
gpio_set_direction(LED_2, GPIO_MODE_OUTPUT);
gpio_set_direction(LED_3, GPIO_MODE_OUTPUT);
gpio_set_direction(LED_4, GPIO_MODE_OUTPUT);
xCountingSemaphore1 = xSemaphoreCreateCounting(NUM_SEMAPHORE, 0);
xTaskCreate(LED1_task, "LED1 task", 1024, NULL, 2, NULL);
xTaskCreate(LED2_task, "LED2 task", 1024, NULL, 2, NULL);
for(;;)
{
vTaskDelay(100);
}
}
void LED1_task(void *parameters)
{
BaseType_t i;
// simulate sending events (queue buffered) to LED2_task()
for(i=0;i
Here the counting semaphore is declared as xCountingSemaphore1;
SemaphoreHandle_t xCountingSemaphore1;
Two tasks are created, LED1_task() and LED2_task() that functions to blink LEDs.
void LED1_task(void *parameters);
void LED2_task(void *parameters);
The counting semaphore xCountingSemaphore1 is created below with a count of 4 in main.
xCountingSemaphore1 = xSemaphoreCreateCounting(NUM_SEMAPHORE, 0);
LED1_task() gives the semaphore token 4 times, thereafter runs its own task of blinking its LEDs. This procedure can be likened to sending events to another task. It doesn’t matter where or when it is sent as long as it is taken by the receiving task on the semaphore queue buffer. The events are safely lined up in this queue.
void LED1_task(void *parameters)
{
BaseType_t i;
// simulate sending events (queue buffered) to LED2_task()
for(i=0;i
LED2_task() takes the semaphore tokens one by one. As long as there are tokens on the semaphore queue, LED2_task() runs. If there are no more tokens then LED2_task() blocks itself. In this example, the event or token is given 4 times, hence, the task will run or blink it’s LEDs 4 times.
void LED2_task(void *parameters)
{
for(;;)
{
// receive the buffered events
xSemaphoreTake(xCountingSemaphore1, portMAX_DELAY);
// run own task through the semaphore
gpio_set_level(LED_2, 1);
vTaskDelay(delay_time/portTICK_PERIOD_MS);
gpio_set_level(LED_2, 0);
vTaskDelay(delay_time/portTICK_PERIOD_MS);
}
}
USing the LEDC Hardware-based LED Module for LED DIMMING Sequences
Previously, you’ve learned how to use the LEDC module of an ESP32 through Build a Simple LED/FAN Speed Controller for your ESP32 Device. However, you’ll be missing out on the advanced hardware features of this module if you don’t use some of its callback functions that can be handled by counting semaphores.
Â
Below is code that handles the LEDC callback inside an ISR. The specific callback triggers on an LEDC Fade End event. When an LEDC port ends its fade, a semaphore is given from the ISR to the main loop code.
#include
#include
#include
#include
#include
/*
* This callback function will be called when fade operation has ended
* Use callback only if you are aware it is being called inside an ISR
* Otherwise, you can use a semaphore to unblock tasks
*/
static IRAM_ATTR bool cb_ledc_fade_end_event(const ledc_cb_param_t *param, void *user_arg)
{
BaseType_t taskAwoken = pdFALSE;
if (param->event == LEDC_FADE_END_EVT) {
SemaphoreHandle_t counting_sem = (SemaphoreHandle_t) user_arg;
xSemaphoreGiveFromISR(counting_sem, &taskAwoken);
}
return (taskAwoken == pdTRUE);
}
void app_main(void)
{
ledc_timer_config_t ledc_timer1 = {
.duty_resolution = LEDC_TIMER_13_BIT, // resolution of PWM duty
.freq_hz = 4000, // frequency of PWM signal
.speed_mode = LEDC_LOW_SPEED_MODE, // timer mode
.timer_num = LEDC_TIMER_1, // timer index
.clk_cfg = LEDC_AUTO_CLK, // Auto select the source clock
};
// Set configuration of timer0 for high speed channels
ledc_timer_config(&ledc_timer1);
ledc_timer_config_t ledc_timer2 = {
.duty_resolution = LEDC_TIMER_13_BIT, // resolution of PWM duty
.freq_hz = 4000, // frequency of PWM signal
.speed_mode = LEDC_LOW_SPEED_MODE, // timer mode
.timer_num = LEDC_TIMER_2, // timer index
.clk_cfg = LEDC_AUTO_CLK, // Auto select the source clock
};
// Set configuration of timer0 for high speed channels
ledc_timer_config(&ledc_timer2);
ledc_channel_config_t ledc_channel1 = {
.channel = LEDC_CHANNEL_0,
.duty = 0,
.gpio_num = GPIO_NUM_4,
.speed_mode = LEDC_LOW_SPEED_MODE,
.hpoint = 0,
.timer_sel = LEDC_TIMER_1,
.flags.output_invert = 0
};
ledc_channel_config_t ledc_channel2 = {
.channel = LEDC_CHANNEL_1,
.duty = 0,
.gpio_num = GPIO_NUM_5,
.speed_mode = LEDC_LOW_SPEED_MODE,
.hpoint = 0,
.timer_sel = LEDC_TIMER_2,
.flags.output_invert = 0
};
SemaphoreHandle_t counting_sem = xSemaphoreCreateCounting(2, 0);
ledc_channel_config(&ledc_channel1);
ledc_channel_config(&ledc_channel2);
ledc_fade_func_install(0);
ledc_cbs_t callbacks = {
.fade_cb = cb_ledc_fade_end_event
};
ledc_cb_register(ledc_channel1.speed_mode, ledc_channel1.channel, &callbacks, (void *) counting_sem );
ledc_cb_register(ledc_channel2.speed_mode, ledc_channel2.channel, &callbacks, (void *) counting_sem );
for(;;)
{
// fade up
ledc_set_fade_with_time(ledc_channel1.speed_mode, ledc_channel1.channel, 4000, 5000 );
ledc_fade_start(ledc_channel1.speed_mode, ledc_channel1.channel, LEDC_FADE_NO_WAIT);
ledc_set_fade_with_time(ledc_channel2.speed_mode, ledc_channel2.channel, 4000, 3000 );
ledc_fade_start(ledc_channel2.speed_mode, ledc_channel2.channel, LEDC_FADE_NO_WAIT);
xSemaphoreTake(counting_sem, portMAX_DELAY);
xSemaphoreTake(counting_sem, portMAX_DELAY);
// fade down
ledc_set_fade_with_time(ledc_channel1.speed_mode, ledc_channel1.channel, 0, 5000 );
ledc_fade_start(ledc_channel1.speed_mode, ledc_channel1.channel, LEDC_FADE_NO_WAIT);
ledc_set_fade_with_time(ledc_channel2.speed_mode, ledc_channel2.channel, 0, 3000 );
ledc_fade_start(ledc_channel2.speed_mode, ledc_channel2.channel, LEDC_FADE_NO_WAIT);
xSemaphoreTake(counting_sem, portMAX_DELAY);
xSemaphoreTake(counting_sem, portMAX_DELAY);
}
}
There are 2 LEDC ports and configurations used. One for GPIO_NUM_4 and GPIO_NUM_5. Each port fades to his own value. Once one port’s fade ends, a semaphore is given from the callback inside the ISR. This means you’ll need two pairs of xSemaphoreGive() and xSemaphoreTake() to be able to finish the LED fade up sequence. Next, the same goes true for the LED fade down sequence. Because of the counting semaphores, there won’t be an overlap between an LED fade up and LED fade down event. Thus, synching the process accordingly.
SHOP THIS PROJECT
-
ESP32-CAM WiFi Bluethooth Development Board with OV2640 Camera Module
$31.95Original price was: $31.95.$29.95Current price is: $29.95. Add to cart