Running multiple FreeRTOS tasks on AVR

In previous post we just run a single task. Running RTOS with single task has no meaning at all. This can be easily done with conventional program. But what if we need to have more separate functions. To execute them at exact timing would require separate timer or interrupt. But microcontroller cannot guarantee an interrupt for every tasks. This way it is hard to make code modular and testing can be painful. Using RTOS solves this kind of problem. It allows programming each task as endless loop. Kernel scheduler takes care of assuring each task gets it’s chunk of processing time. Additionally it does bearing the priority systems – more important tasks are executed prior to less important ones.

Let us go further with our example code and add more tasks to our FreeRTOS engine. We already have LED flashing task that toggles LED every second. Additionally we are going to create another task that checks button state. Also we are going to send some information to LCD. As always lets take care of drivers for all of them.

Writing driver to read button state

First of all create button.c and button.h files and place them in Drivers folder. We have one button connected to Atmega128 pin B5. It is connected to ground so to read it we need to set port pin as input and enable internal pullup resistor. For this we write simple vButtonInit() function:

void vButtonInit(void)
    // Set SWITCH_IP as input pin
    // Enable pull-up on SWITCH_IP

To check if button is pressed we write another function. It returns pdTRUE if button was pressed and pdFALSE if not:

char xButtonGetStatus(void)
	        // See if switch is pressed
        if((PIN_SWITCH_IP&(1<<BIT_SWITCH_IP)) == 0)
           return pdTRUE;
           return pdFALSE;

Having driver ready we can proceed to writing task function. But question is, what to do if button was pressed? We want our tasks to be as independent as possible. FreeRTOS has special means for this – semaphores. Semaphores simply is a queue with length 1 with data size 0 it simply stores binary data (like flag). IT is handy for task synchronization.

We are going to use binary semaphore that will be set when button is pressed. Semaphores must be accessible by other tasks in application so it needs to be a global variable. As our task functions are located in other source file we need to make it external. To be able to use semaphores we have to include semphr.h file in our project. Then we create semaphore handler:


I have also created additional freertosm128.h file where I declared xButtonSepmaphore variable as external:


Now we can create our button check task in mytasks.c:

void vButtonCheckTask( void *pvParameters )
portTickType xLastWakeTime;
const portTickType xFrequency = 20;
xSemaphoreTake(xButtonSemaphore, (portTickType)0);
	for (;;)
	if (xButtonGetStatus()==pdTRUE)

We are going to check button state every 20ms, so we use vTaskDelayUntil() function with frequency=20. As you can see if task detect button press it simply sets semaphore with xSemaphoreGive() function.

Now we can create our task in main program. First of all we need to create semaphore before use with function:


then if semaphore was succesfully created we can create button check task.

		//successfully created
		xTaskCreate( vButtonCheckTask, ( signed char * ) "Button", configMINIMAL_STACK_SIZE, NULL, mainButton_TASK_PRIORITY, NULL );

Driving LCD

Lets connect LCD screen to our Atmega128 baord and create simple task that outputs button state by using semaphores. Additionally we are going to output number of tasks running.

We are going to use our LCD library to drive LCD in 4-bit mode. LCD is connected to AVR PORT E as follows:

Wee need to make correct pin assignments in lcd.lib.h file:

#defineLCD_RS2//define MCU pin connected to LCD RS

#defineLCD_RW2//define MCU pin connected to LCD R/W

#defineLCD_E3//define MCU pin connected to LCD E

#defineLCD_D00//define MCU pin connected to LCD D0

#defineLCD_D11//define MCU pin connected to LCD D1

#defineLCD_D22//define MCU pin connected to LCD D1

#defineLCD_D33//define MCU pin connected to LCD D2

#defineLCD_D44//define MCU pin connected to LCD D3

#defineLCD_D55//define MCU pin connected to LCD D4

#defineLCD_D66//define MCU pin connected to LCD D5

#defineLCD_D77//define MCU pin connected to LCD D6

#defineLDPPORTE//define MCU port connected to LCD data pins

#defineLCPPORTE//define MCU port connected to LCD control pins

#defineLDDRDDRE//define MCU direction register for port connected to LCD data pins

#defineLCDRDDRE//define MCU direction register for port connected to LCD control pins

Lets create LCD update task.

void vLCDUpdateTask( void *pvParameters )
static const uint8_t welcomeln1[] PROGMEM="FreeRTOS DEMO";
static const uint8_t buttonln1[] PROGMEM="BT:";
static const uint8_t tasksln1[] PROGMEM="TSKS:";
portTickType xLastWakeTime;
const portTickType xFrequency = 500;
unsigned portBASE_TYPE uxTasks;
CopyStringtoLCD(welcomeln1, 0, 0);
CopyStringtoLCD(buttonln1, 0, 1);
CopyStringtoLCD(tasksln1, 7, 1);

	for (;;)
		//works only up to 9 tasks
		if (xButtonSemaphore != NULL)
			if (xSemaphoreTake(xButtonSemaphore, (portTickType)0)==pdTRUE)

To save RAM space all static LCD strings are stored in FLASH memory. Before tasks endless loop we need to initialize LCD screen also we send static text to LCD as there wont change in our case. Updating LCD isn’t critical so we are going to call task every 500ms. After initial settings are done, then we enter endless for(;;) loop where first of all we update the number of tasks and send it to LCD which is labeled as “TSKS:”. Secondly we are pooling for xButtonSemaphore. If vButtonCheckTask reads button press and sets semaphore vLCDUpdateTask takes it and updates LCD “BT:” status by setting value to 1 (otherwise it shows 0).

Finally we need to create vLCDUpdateTask task so scheduler could add it to its management queue:

xTaskCreate( vLCDUpdateTask, ( signed char * ) "LCD", configMINIMAL_STACK_SIZE, NULL, mainLCD_TASK_PRIORITY, NULL );

As LCD task isn’t critical it is set to idle priority. Now we can run program an see results:

when button isn’t pressed

and when pressed

vLCDUpdateTask also reads the number of tasks created. We can see number “4”. which means that we are running four tasks. Three of them are our tasks (vLEdFlashTask, vButtonUpdateTask, vLCDUpdateTask) and idle task created when vTaskStartScheduler() is called.

So far we are able to run several independent tasks and have simple communications with semaphores. In this example we are sending button status one way. LCD task takes semaphore which is equal to resetting it.  As you may know semaphores also are great at sharing resources, where tasks may access hardware resources only when semaphore is released. Also there are more ways to communicate between tasks like queues and mutexes. These will be covered in future topics. [download M128RTOS]


  1. Comments and corrections are welcome!

    • Using Atmel Studio 7, I had to change “SIG_OUTPUT_COMPARE1A” in port.c to “TIMER1_COMPA_vect”. After that, it’s worked great. Thanks.

  2. Pingback: Willing To learn RTOS through Online.

  3. nice tut but which avr is used

  4. It’s classic ATmega128. See previous post for more details on board used.

  5. hi,

    i want to know maximum number of task could run and stack size for atmega128..


  6. very nice tutorial to start with RTOS…well explained…

  7. Hello, First thanks for such a good tutorial and my question is that how to write ISR in free rtos ,means how we can use interrupts(like external,Uart tx rx interrupts) in free rtos?

Leave a Reply