FreeRTOS is one of the most widely used open-source real-time operating systems (RTOS). In this article, we will look at a very simple FreeRTOS program: a push-button will enable/disable the blinking of an LED. The program is developed and tested using the LPCXpresso54102 board with NXP’s LPC54102 microcontroller. The program can be easily adapted for other MCUs.

An obvious question is “Do we need an RTOS just to blink a LED?”. The short answer is no, but the idea behind this simple program is to introduce important FreeRTOS mechanisms, that can be applied in much larger and complex applications. In the example below, we will see how to use periodic tasks and exchange information between tasks using event groups.

The Example Program

The program will use two FreeRTOS tasks. One for reading the state of the push-button and one for operating the blinking of the LED. In the event of a button press, the information will be passed using an event group.

The code for the board and peripheral initialization is removed for simplicity. The full code of the program and the FreeRTOS configuration can be found in the following GitHub repository.

/* FreeRTOS kernel includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "timers.h"
#include "event_groups.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/
#define led_toggle_task_PRIO (configMAX_PRIORITIES - 1)
#define btn_press_task_PRIO (configMAX_PRIORITIES - 1)

#define BTN_PRESSED (0)
#define BTN_NOT_PRESSED (1)

#define READ_BTN_PIN_LEVEL() GPIO_PinRead(GPIO, BOARD_SW1_GPIO_PORT, BOARD_SW1_GPIO_PIN)
#define LED_OFF()            GPIO_PinWrite(GPIO, BOARD_LED_BLUE_GPIO_PORT, BOARD_LED_BLUE_GPIO_PIN, 1)
#define LED_TOGGLE()         GPIO_PortToggle(GPIO, BOARD_LED_BLUE_GPIO_PORT, 1u << BOARD_LED_BLUE_GPIO_PIN);
//Button pressed event bit(flag)
#define BTN_PRESSED_Pos                         0U
#define BTN_PRESSED_Msk                         (1UL << BTN_PRESSED_Pos)
/*******************************************************************************
 * Prototypes
 ******************************************************************************/
static void led_toggle_task( void * pvParameters );
static void btn_read_task(void *pvParameters);
/* Declare a variable to hold the created event group. */
static        EventGroupHandle_t xFlagsEventGroup;
/*******************************************************************************
 * Code
 ******************************************************************************/
int main(void)
{
	
	/* create the event group. */
	xFlagsEventGroup = xEventGroupCreate();

	xTaskCreate(led_toggle_task, "LED_toggle_task", configMINIMAL_STACK_SIZE + 10, NULL, led_toggle_task_PRIO, NULL);
	xTaskCreate(btn_read_task, "button_read_task", configMINIMAL_STACK_SIZE + 10, NULL, btn_press_task_PRIO, NULL);

	vTaskStartScheduler();
	for (;;)
		;
}

Reading the State of the Button

We use a push-button connected to an input pin of the MCU as shown below in Fig.1. When the button is pressed the level on the pin will be logic 0, otherwise, it will be kept logic 1 by a pull-up resistor.

Fig.1 Push-button schematic

A very important thing to consider when using buttons is that they generate electrical noise when pressed. This noise is short-lived but can cause toggling of the logic level on the MCU input and false readings in the code. For this reason, it is necessary to use a de-bounce filter. Many MCUs have such filters integrated into the GPIO pins. A software de-bounce filter can also be implemented (Note: The MCU we use for this example has a hardware de-bounce filter).

There are generally two ways of reading the state of a pin in an embedded system:

  • Polling method – the state of the pin is checked (polled) periodically (e.g every 10ms).
  • Interrupt-based method – an interrupt is generated If the logic level on the input pin changes.

The interrupt-based method will give us the fastest response time, but we have to consider the context in which the button will be used. Do we really need the fastest response time? Do we have only one button in our system or there are more (e.g keyboard). Will the fastest response time have an impact on the functionality or we can use slower response time? In our case, the button is used as a human interface device, and based on empirical evidence, if a button press event is processed within 50ms, we as humans do not perceive a lag. This makes the polling method more practical, as it will do the job with less resource usage (no need for external interrupts).

For the polling method, we need the button state to be checked periodically. In FreeRTOS, for a periodic task with a constant execution frequency, we can use vTaskDelayUntil() function. It delays the task until a specified time (in a number of ticks).

/*!
 * @brief Task responsible for reading a button state (0 or 1).
 * 		  The task sets an event flag in case the button is pressed.
 */
static void btn_read_task(void *pvParameters)
{
	TickType_t last_wake_time;
	uint8_t curr_state,prev_state = BTN_NOT_PRESSED; //State for the button
	//Set toggle interval to 20ms
	const TickType_t sample_interval = 20/portTICK_PERIOD_MS;

	// Initialize the last_wake_time variable with the current time
	last_wake_time = xTaskGetTickCount();

	for( ;; )
	{
		// Wait for the next cycle.
		vTaskDelayUntil( &last_wake_time, sample_interval );
		// Get the level on button pin
		curr_state = READ_BTN_PIN_LEVEL();
		if ((curr_state == BTN_PRESSED) && (prev_state == BTN_NOT_PRESSED)) {
			xEventGroupSetBits(xFlagsEventGroup, BTN_PRESSED_Msk);
		}
		prev_state = curr_state;
	}
}

Blinking the LED

The LED in the hardware setup we use is connected using the schematic shown in Fig.2 (the Blue LED). In order to control it, we need the pin of the GPIO peripheral to be configured as an output. Setting it to logic 1 will turn the LED off and setting it to logic 0 will turn the LED on. In order to make the LED blink, we need to periodically toggle the value of the output. .Just as with the button read task, we again use vTaskDelayUntil() function for achieving periodic execution.

Fig.2 LED schematic

Additionally, we want to be able to enable/disable the blinking of the LED using the push button. To operate the LED we need two states: LED blinking state and LED turned off-state. The information on whether the button has been pressed is passed using a bit from the event group xFlagsEventGroup.

/*!
 * @brief Task responsible for toggling the LED and switching it OFF.
 */
static void led_toggle_task( void * pvParameters )
{
	TickType_t last_wake_time;
	//Set toggle interval to 200ms
	const TickType_t toggle_interval = 200/portTICK_PERIOD_MS;
	EventBits_t event_bits;
	uint8_t toggle_en = 0;

	// Initialize the last_wake_time variable with the current time
	last_wake_time = xTaskGetTickCount();

	for( ;; )
	{
		// Wait for the next cycle.
		vTaskDelayUntil( &last_wake_time, toggle_interval );
		//Get the button pressed event bit and clear it.
		event_bits = xEventGroupClearBits( xFlagsEventGroup,BTN_PRESSED_Msk);
		if (event_bits & BTN_PRESSED_Msk) {
			toggle_en = ~toggle_en;
			/* if toggling was enabled then we turn off the LED
			   if toggling was disabled, then the led was turned off anyway */
			LED_OFF();
		}
		if (toggle_en) {
			LED_TOGGLE();
		}
	}
}

Conclusion

We implemented a simple FreeRTOS-based program for LED blinking with push-button enable/disable. An important design approach that should be noted is that we tried to make the two tasks as independent and decoupled from one another as possible. The button read task btn_read_task() just registers a button press event, it does not have awareness of the LED functionality. The same is true for the LED task led_toggle_task(), it reacts to an event bit, not concerning itself with who has set the event. Adopting this approach when possible can make our programs more flexible.

Was this article helpful?