Running multiple FreeRTOS tasks on AVR

In the previous post, we just run a single task. Running RTOS with single task has no meaning at all. This can be quickly done with a conventional program. But what if we need to have more separate functions. To execute them at exact timing would require a separate timer or interrupt. But microcontroller cannot guarantee an interrupt for every task. 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 an endless loop. Kernel scheduler takes care of assuring each task gets its chunk of processing time. Additionally, it does bearing the priority systems – more critical tasks are executed before 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 the button state. Also, we are going to send some information to the LCD. As always let’s 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 the Drivers folder. We have one button connected to Atmega128 pin B5. It is connected to ground so to read it we need to set the port pin as input and enable internal pull-up resistor. For this we write simple vButtonInit() function:

void vButtonInit(void)
{
    // Set SWITCH_IP as input pin
    DDR_SWITCH_IP &= ~(1<<BIT_SWITCH_IP);
    // Enable pull-up on SWITCH_IP
    PORT_SWITCH_IP |= (1<<BIT_SWITCH_IP);
}

To check if the button is pressed we write another function. It returns pdTRUE if the 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;
        }
        else
        {
           return pdFALSE;
        }
}

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

We are going to use binary semaphore that will be set when the button is pressed. Semaphores must be accessible by other tasks in the application, so it needs to be a global variable. As our task functions are located in a separate 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:

xSemaphoreHandlexButtonSemaphore=NULL;

 

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

externxSemaphoreHandlexButtonSemaphore;

 

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

void vButtonCheckTask( void *pvParameters )
{
portTickType xLastWakeTime;
const portTickType xFrequency = 20;
xLastWakeTime=xTaskGetTickCount();
xSemaphoreTake(xButtonSemaphore, (portTickType)0);
vButtonInit();
	for (;;)
	{
	if (xButtonGetStatus()==pdTRUE)
		{
			xSemaphoreGive(xButtonSemaphore);
		}
	vTaskDelayUntil(&xLastWakeTime,xFrequency);
	}
}

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

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

vSemaphoreCreateBinary(xButtonSemaphore);

 

then if semaphore was successfully created, we could create a button check task.

	if(xButtonSemaphore!=NULL)
	{
		//successfully created
		xTaskCreate( vButtonCheckTask, ( signed char * ) "Button", configMINIMAL_STACK_SIZE, NULL, mainButton_TASK_PRIORITY, NULL );
	}

Driving LCD

Let’s connect the LCD screen to our Atmega128 board and create a simple task that outputs button state by using semaphores. Additionally, we are going to output the 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:

We 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

Let’s create an 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;
xLastWakeTime=xTaskGetTickCount();
unsigned portBASE_TYPE uxTasks;
LCDinit();
LCDclr();
LCDcursorOFF();
CopyStringtoLCD(welcomeln1, 0, 0);
CopyStringtoLCD(buttonln1, 0, 1);
CopyStringtoLCD(tasksln1, 7, 1);

	for (;;)
	{
		uxTasks=uxTaskGetNumberOfTasks();
		LCDGotoXY(13,1);
		//works only up to 9 tasks
		LCDsendChar(uxTasks+48);
		if (xButtonSemaphore != NULL)
		{
			LCDGotoXY(3,1);
			//poll
			if (xSemaphoreTake(xButtonSemaphore, (portTickType)0)==pdTRUE)
			{
				LCDsendChar('1');
			}
			else
			{
				LCDsendChar('0');
			}
		}
	vTaskDelayUntil(&xLastWakeTime,xFrequency);
	}
}

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 a static text to LCD as it won’t change in our case. Updating LCD isn’t critical, so we are going to call the 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 the value to 1 (otherwise it shows 0).

Finally, we need to create a 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 the program an see results:

when the button isn’t pressed

and when pressed

vLCDUpdateTask also reads the number of tasks created. We can see the 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 can 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 reset it.  As you may know, semaphores also are great at sharing resources, where tasks may access hardware resources only when the 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]

8 Comments:

  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..

    thanks,

  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 to hitesh Cancel reply