freertos

FreeRTOS Firmware guide for ARM – LPC1768

Introduction

OpenLab ARM utilizes the LPC1768 Cortex M3 microcontroller which is suited for Embedded real-time application. These kinds of application typically consists of both hard and soft real-time application. FreeRTOS is a real-time kernel or real-time scheduler that can be used to meet the hard real-time requirements. It allows all applications to be organized as a collection of independent threads of execution. As the LPC1768 has only one core, in reality only a single thread can be executing at any one time. The kernel decides which thread should be executed by examining the priority assigned to each thread by the application designer. In the simplest case, the application designer could assign higher priorities to threads that implement hard real-time requirements, and lower priorities to threads that implement soft real-time requirements. This would ensure that hard real-time threads are always executed ahead of soft real-time threads.

The LPC1768 Port of FreeRTOS

The LPC1768 port includes all the standard FreeRTOS features:

  • Pre-emptive or co-operative operation
  • Very flexible task priority assignment
  • Queues
  • Binary semaphores
  • Counting semaphores
  • Recursive semaphores
  • Mutexes
  • Tick hook functions
  • Idle hook functions
  • Stack overflow checking
  • Trace hook macros
  • Optional commercial licensing and support

FreeRTOS also manages interrupt nesting, and allows interrupts above a user-definable priority level to remain unaffected by the activity of the kernel. Using FreeRTOS will not introduce any additional timing jitter or latency for these interrupts.

Configurations required for new FreeRTOS project in Keil

After creating a project there are different files that are required to be located and copied to the project folder.
some of the files are given below:

FreeRTOS files to be located for a new project

  • Downloads                               |-> http://www.freertos.org/a00104.html
    • Download the zip file and extract it to a suitable location for further access.
    • We will be using FreeRTOS v9.0.0 in this tutorial.
  • Port file Keil                           |-> 0.0\FreeRTOS\Source\portable\RVDS\ARM_CM3
  • heap_1.c                                  |->FreeRTOSv9.0.0\FreeRTOS\Source\portable\MemMang

-> FreeRTOSv9.0.0\FreeRTOS\Source\include

  • croutine.h
  • deprecated_definitions.h
  • event_groups.h
  • FreeRTOS.h
  • list.h
  • mpu_wrappers.h
  • portable.h
  • projdefs.h
  • queue.h
  • semphr.h
  • StackMacros.h
  • task.h
  • timers.h

-> FreeRTOSv9.0.0\FreeRTOS\Source

  • list.c
  • queue.c
  • tasks.c

After locating the above files copy them to the project folder and add the c files to the project from the Keil Ide.

After all those procedures its time to configure the project, this configuration is done within the startup_lpc17xx.c file and FreeRTOSConfig.h. startup_lpc17xx.c file will be added to the project when we create the project, FreeRTOSConfig.h file is needed to created by our own or edit any existing FreeRTOSConfig.h from different available demo project in FreeRTOS folder (FreeRTOSv9.0.0\FreeRTOS\Demo).

1.Configuring the startup_lpc17xx.c for FreeRTOS

While using FreeRTOS there are some FreeRTOS specific files that are required to be added to the startup_lpc17xx.c to make it work.

FreeRTOS specific handlers:

  • EXTERN vPortSVCHandler
  • EXTERN xPortPendSVHandler
  • EXTERN xPortSysTickHandler

Add the above lines to the startup_lpc17xx.c file before Vector Table Mapped to Address 0 at Reset.

After that its is required to change some native key words of startup_lpc17xx.c with that of FreeRTOS specific ones such as SVC_Handler, PendSV_Handler, SysTick_Handler from Vector Table Mapped to Address 0 at Reset.

Changes to be made:

  • SVC_Handler        –    vPortSVCHandler
  • PendSV_Handler     –    xPortPendSVHandler
  • SysTick_Handler    –    xPortSysTickHandler

2. Configuring the FreeRTOSConfig.h for FreeRTOS

While creating FreeRTOSConfig.h files there are various conditions that are needed to be considered and those conditions depend on the user-specific application. Regarding this, there are clean guides available from FreeRTOS in a pdf book form that is available from their official website.

Here we will be discussing on how to modify an existing FreeRTOSConfig.h file from a demo project in FreeRTOS (for eg: FreeRTOSv9.0.0\FreeRTOS\Demo\CORTEX_LPC1768_GCC_RedSuite\src). It will be configured for basic applications such as LED blinking. For high-end applications, users can refer the manual form FreeRTOS to configure FreeRTOSConfig.h file.

Basic Configurations

  1. Remove or comment the line starting with #error, this error message will notify that a batch file (for adding project files to project folder) is required to be executed before starting a new project. Since we are moving the required files to the main project folder user won’t be required to be notified with that specific error.
  2. Set configUSE_TICK_HOOK vales as 0 since we are not using tick hook function in this project.
  3. Change the configCPU_CLOCK_HZ to the required frequency.
  4. Set the configCHECK_FOR_STACK_OVERFLOW value as 0 since this is a simple LED toggling application it does not require to check the stack overflow. Else it is required to provide stack overflow hook function.
  5. Set configGENERATE_RUN_TIME_STATS value to 0 again because this application does not require any statistics gathering functionality, it might be required in any high-end applications.
  6. Then in the last section remove or comment the entire section for setting up the timer for the run time stats, since we are not using it. This also might be used for high-end applications to provide information on the amount of processing time each task has received.

It depends on the application we develop on how to configure all these functions in the project, a correct configuration only allows a user to run the application successfully, A complete description on this function and its configurations are available in the user manual for FreeRTOS.

Firmware

The definitions that are used in this program are as follows:

#include <lpc17xx.h>
#include "FreeRTOS.h"
#include "task.h"

#define LED1                16          /* LED for Task-1 */
#define LED2                17          /* LED for Task-2 */
#define LED3                18          /* LED for Task-2 */
#define IDEAL_LED           19          /* LED for Ideal-Task */

#define GPIO_PIN_DIR    LPC_GPIO0->FIODIR
#define GPIO_PIN_SET    LPC_GPIO0->FIOSET
#define GPIO_PIN_CLR    LPC_GPIO0->FIOSET

An example of the required function declaration for all the tasks looks like below code section:

/* Local Functions for Task declaration */
void vTask1(void *pvParameters);
void vTask2(void *pvParameters);
void vTask3(void *pvParameters);
void vIdealTask(void *pvParameters);
  • In the main program, the task or thread that is required is defined as different functions. So as in the basic programming, these functions are required to be declared before its definition. Here we are defining four different functions in this example. These four functions indicate four different tasks.
  • These four Tasks have four different priority, higher priority task will enter the running state first followed by lower priority and the ideal task will execute.
  • vTask1 is the function name we assign in the program for Task1.
  • vTask2 is the function name we assign in the program for Task2.
  • vTask3  is the function name we assign in the program for Task3.
  • vIdealTask is the function name we assign in the program for IdealTask.
    • Here an Ideal task is used to keep running the program when there is no task under schedule.
  • pvParameters in the argument section is just a value that will be passed into the created task as the task’s parameter.

An example of function definition for the required task looks like below code section:

void vTask1(void *pvParameters)
{
    while(1)
    {
        GPIO_PIN_SET |= (1<LED1);                /* LED for Task-1 with priority 1 */
        vTaskDelay(500);
        GPIO_PIN_CLR |= (1<LED1);
        vTaskDelay(500);
    }
}

The required task is always an infinite loop to maintain its functionality throughout the execution. Even though the tasks are in the infinite loop it does not affect the task switching, it is taken control by the scheduler.

An example of the main function looks like below code section:

int main(void)
{
    SystemInit();
    
    /* Setting Port0 Pins 16, 17, 18 & 19 as output for Task-1, Task-2, Task-3 & Ideal-Task. */
    GPIO_PIN_DIR |= (1<<LED1)|(1<<LED2)|(1<<LED3)|(1<<IDEAL_LED);
    
    /* Creating four tasks with priorities Ideal priority, 1, 2 and 3. */
    xTaskCreate( vIdealTask,                /* Pointer to the function that implements the task. */
                 "IdealTask",               /* Text name for the task. This is to facilitate debugging only. */
                 configMINIMAL_STACK_SIZE,  /* The size of the stack that should be created for the task. 
                                             * This is defined in words, not bytes. */
                 NULL,                      /* A reference to xParameters is used as the task parameter. 
                                             * This is cast to a void * to prevent compiler warnings.
                                             * This example does not use the task parameter. */
                 tskIDLE_PRIORITY,          /* The priority to assign to the newly created task. 
                                             * This task has Ideal Priority. */
                 NULL);                     /* The handle to the task being created will be placed in xHandle. 
                                             * This example does not use the task handle. */
    xTaskCreate( vTask1, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, NULL );
    xTaskCreate( vTask2, "Task2", configMINIMAL_STACK_SIZE, NULL, 2, NULL );
    xTaskCreate( vTask3, "Task3", configMINIMAL_STACK_SIZE, NULL, 3, NULL );
    
    /* Start the scheduler so the tasks start executing. */
    vTaskStartScheduler();    
    
    /* If all is well then main() will never reach here as the scheduler will
    now be running the tasks. If main() does reach here then it is likely that 
    the idle task could not be created inside vTaskStartScheduler() or there was 
    insufficient heap memory available for the idle task to be created. */
    while(1);
}
  • The main function is the code section where we create the task with different priorities and schedule it for the execution.
  • We use the functions xTaskCreate() and vTaskStartScheduler() in this example for creating and scheduling our different tasks.
  • xTaskCreate() is used to creates a new instance of a task. The basic syntax and parameter of xTaskCreate() are as shown below.
xTaskCreate( TaskFunction_t pvTaskCode,
             const char * const pcName,
             unsigned short usStackDepth,
             void *pvParameters,
             UBaseType_t uxPriority,
             TaskHandle_t *pxCreatedTask );

Arguments and its Description for xTaskCreate()

[fusion_table fusion_table_type=”1″ hide_on_mobile=”small-visibility,medium-visibility,large-visibility” class=”” id=”” animation_type=”” animation_direction=”left” animation_speed=”0.3″ animation_offset=””]

Arguments Description
pvTaskCode The pvTaskCode parameter is simply a pointer to the function (in effect, just the function name) that implements the task
pcName A descriptive name for the task. This is mainly used to facilitate debugging, but can also be used in a call to xTaskGetHandle() to obtain a task handle.
usStackDepth Each task has its own unique stack that is allocated by the kernel to the task when the task is created. The usStackDepth value tells the kernel how large to make the stack.
pvParameters Task functions accept a parameter of type ‘pointer to void’ ( void* ). The value assigned to pvParameters will be the value passed into the task.
uxPriority Defines the priority at which the task will execute. Priorities can be assigned from 0, which is the lowest priority, to (configMAX_PRIORITIES – 1), which is the highest priority.
pxCreatedTask pxCreatedTask can be used to pass out a handle to the task being created. This handle can then be used to reference the task in API calls that, for example, change the task priority or delete the task.If your application has no use for the task handle, then pxCreatedTask can be set to NULL.

[/fusion_table]

  • Each task requires RAM that is used to hold the task state (the task control block, or TCB), and used by the task as its stack. If a task is created using xTaskCreate() then the required RAM is automatically allocated from the FreeRTOS heap. If a task is created using xTaskCreateStatic() then the RAM is provided by the application writer, which results in two additional function parameters, but allows the RAM to be statically allocated at compile time.
  • Newly created tasks are initially placed in the Ready state, but will immediately become the Running state task if there are no higher priority tasks that are able to run.
  • Tasks can be created both before and after the scheduler has been started.
  • After creating tasks we need to schedule all the tasks to keep it in a running state. For this we use the function vTaskStartScheduler(), Starting the scheduler causes the highest priority task that was created while the scheduler was in the Initialization state to enter the Running state.
  • Typically, before the scheduler has been started, main() (or a function called by main()) will be executing. After the scheduler has been started, only tasks and interrupts will ever execute.
  • Once the scheduler is started the main function will not reach the infinite while(1); loop, if any case it reaches there, then it is likely that the idle task could not be created inside vTaskStartScheduler() or there was insufficient heap memory available for the idle task to be created.