Migrating TI-RTOS examples to use FreeRTOS

There are significant project setting modifications required to migrate from a TI-RTOS example to one that supports FreeRTOS, such as modifying the toolchain, adding include paths, and managing predefined symbols. For this reason, the recommended approach to port an example from TI-RTOS to FreeRTOS is to create a new projectspec file. A projectspec, or project specification file, is used to create a new project based on predefined settings. For more information on projectspec files, see the ProjectSpecs in CCS page. In this section, we will be using the multi_role example as a guide to convert the simple_central project to FreeRTOS.

Most embedded applications are built the same way: a main loop that receives messages from the iCall or from the application itself. This “loop” is implemented and located inside the multi_role example so, the developer can take the example from there. This means that the majority of the application code can be reused with slight modifications.

In this section, we will be using the multi_role FreeRTOS example as a guide to convert the simple_central project to FreeRTOS. The same can be used to convert any BLE application into FreeRTOS + GCC.

Important

If you are starting with an example that already enables FreeRTOS (i.e. if using multi_role imported from SimpleLink CC13xx/CC26xx SDK), skip the section below and jump to Modify the application code to support FreeRTOS.

Modify the Project Properties to support FreeRTOS

The goal of this section is to inherit the project configuration settings from an existing example project that is already setup for FreeRTOS.

  1. Setup the new project folders. Create a freertos folder inside the simple_central project directory located in: {SDK_INSTALL_DIR}\examples\rtos\CC26X2R1_LAUNCHXL\ble5stack\simple_central

  2. Create a new projectspec file. Copy the gcc folder from multi_role/freertos/gcc to simple_central/freertos/gcc and rename the projectspec from multi_role_CC26X2R1_LAUNCHXL_freertos_gcc.projectspec to simple_central_CC26X2R1_LAUNCHXL_freertos_gcc.projectspec.

  3. Ensure the linker file CC26X2R1_LAUNCHXL_FREERTOS.lds is copied inside simple_central/freertos/gcc if not already done.

  4. Update simple_central_CC26X2R1_LAUNCHXL_freertos_gcc.projectspec, find and replace all multi_role with simple_central.

  5. By comparing the projectspecs, there will be some files that will need to be added or removed.

    • simple_central_CC26X2R1_LAUNCHXL_freertos_gcc.projectspec new (FreeRTOS + gcc)

    • simple_central_CC26X2R1_LAUNCHXL_tirtos_ccs.projectspec legacy (TI-RTOS + CCS)

    The following diff below shows files that are used by multi_role for its peripheral service declarations. Since we are using the simple_central example, these are not needed and can be removed.

    Listing 34. Files removed from the new projectspec
    -<file path="${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti/ble5stack/profiles/dev_info/cc26xx/devinfoservice.c" openOnCreation="false" excludeFromBuild="false" action="link" targetDirectory="Profiles">
    -</file>
    -<file path="${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti/ble5stack/profiles/dev_info/devinfoservice.h" openOnCreation="false" excludeFromBuild="false" action="link" targetDirectory="Profiles">
    -</file>
    -<file path="${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti/ble5stack/inc/gatt_profile_uuid.h" openOnCreation="false" excludeFromBuild="false" action="link" targetDirectory="Profiles">
    -</file>
    -<file path="${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti/ble5stack/host/gatt_uuid.c" openOnCreation="false" excludeFromBuild="false" action="link" targetDirectory="Profiles">
    -</file>
    -<file path="${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti/ble5stack/inc/gatt_uuid.h" openOnCreation="false" excludeFromBuild="false" action="link" targetDirectory="Profiles">
    -</file>
    -<file path="${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti/ble5stack/host/gattservapp_util.c" openOnCreation="false" excludeFromBuild="false" action="link" targetDirectory="Profiles">
    -</file>
    -<file path="${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti/ble5stack/profiles/simple_profile/cc26xx/simple_gatt_profile.c" openOnCreation="false" excludeFromBuild="false" action="link" targetDirectory="Profiles">
    -</file>
    -<file path="${COM_TI_SIMPLELINK_CC13XX_CC26XX_SDK_INSTALL_DIR}/source/ti/ble5stack/profiles/simple_profile/simple_gatt_profile.h" openOnCreation="false" excludeFromBuild="false" action="link" targetDirectory="Profiles">
    -</file>
    

Modify the application code to support FreeRTOS

This section describes the changes to the application code that are necessary to support FreeRTOS. It is recommended to import the multi_role FreeRTOS project and search for the predefined symbol FREERTOS. This will reveal the modifications to the multi_role application that were done. The following steps are provided using the search mentioned in the previous section as a guide.

The diffs below provide an overview of all the differences and changes between the legacy (TI-RTOS + CCS) multi_role example and the new (FreeRTOS + gcc) multi_role example. These changes should be applied to the corresponding simple_central example.

  1. Replace all the TI-RTOS calls with DPL/FreeRTOS/POSIX calls in simple_central.c.

    Listing 35. multi_role FreeRTOS differences - INCLUDES
    +#ifdef FREERTOS
    +#include <FreeRTOS.h>
    +#include <task.h>
    +//#include <pthread.h>
    +#include <mqueue.h>
    +#else
    #include <string.h>
    #include <ti/sysbios/knl/Clock.h>
    #include <ti/sysbios/knl/Event.h>
    #include <ti/sysbios/knl/Queue.h>
    #include <ti/sysbios/knl/Task.h>
    +#endif
    
    #ifdef FREERTOS
    #include <stdarg.h>
    #endif
    
    #include <ti/display/Display.h>
    
    +#if (!(defined FREERTOS) && !(defined __TI_COMPILER_VERSION__)) && !(defined(__clang__))
    #include <intrinsics.h>
    #endif
    
    Listing 36. multi_role FreeRTOS differences - STACK SIZE
    #ifndef MR_TASK_STACK_SIZE
    +#ifdef FREERTOS
    +#define MR_TASK_STACK_SIZE                   2048
    +#else
    #define MR_TASK_STACK_SIZE                   1024
    +#endif
    
    Listing 37. multi_role FreeRTOS differences - EVENT QUEUE
    /*********************************************************************
    * GLOBAL VARIABLES
    */
    +#ifdef FREERTOS
    +mqd_t g_EventsQueueID;
    +#endif
    
    Listing 38. multi_role FreeRTOS differences - ICall_Sync
    /*********************************************************************
    * LOCAL VARIABLES
    */
    
    // Entity ID globally used to check for source and/or destination of messages
    static ICall_EntityID selfEntity;
    
    // Event globally used to post local events and pend on system and
    // local events.
    +#ifdef FREERTOS
    +ICall_SyncHandle syncEvent;
    +#else
    static ICall_SyncHandle syncEvent;
    +#endif
    
    Listing 39. multi_role FreeRTOS differences - TASK VARIABLES
    +#ifdef FREERTOS
    +/*Non blocking queue */
    +mqd_t g_POSIX_appMsgQueue;
    
    +#else
    // Queue object used for app messages
    static Queue_Struct appMsg;
    static Queue_Handle appMsgQueue;
    +#endif
    
    // Task configuration
    +#ifdef FREERTOS
    +typedef uint32_t * Task_Handle;
    +TaskHandle_t mrTask = NULL;
    +#else
    Task_Struct mrTask;
    +#endif
    
    +#ifndef FREERTOS
    #if defined __TI_COMPILER_VERSION__
    #pragma DATA_ALIGN(mrTaskStack, 8)
    +#else
    +#pragma data_alignment=8
    +#endif
    +#ifndef FREERTOS
    uint8_t mrTaskStack[MR_TASK_STACK_SIZE];
    +#endif
    +#endif
    
    Listing 40. multi_role FreeRTOS differences - LOCAL FUNCTIONS
    /*********************************************************************
    * LOCAL FUNCTIONS
    */
    static void multi_role_init(void);
    static void multi_role_scanInit(void);
    static void multi_role_advertInit(void);
    
    +#ifdef FREERTOS
    +static void multi_role_taskFxn(void *a0);
    +#else//TI_RTOS
    static void multi_role_taskFxn(UArg a0, UArg a1);
    +#endif
    
    //..
    //..
    //..
    
    +#ifdef FREERTOS
    +static void multi_role_clockHandler(void * arg);
    +#else
    static void multi_role_clockHandler(UArg arg);
    +#endif
    
    Listing 41. multi_role FreeRTOS differences - multi_role_createTask
    /*********************************************************************
    * @fn      multi_role_createTask
    *
    * @brief   Task creation function for multi_role.
    *
    * @param   None.
    *
    * @return  None.
    */
    
    
    void multi_role_createTask(void)
    {
    
    +#ifdef FREERTOS
    +    BaseType_t xReturned;
    +
    +    /* Create the task, storing the handle. */
    +    xReturned = xTaskCreate(
    +            multi_role_taskFxn,                     /* Function that implements the task. */
    +            "MULTI_ROLE_APP",                       /* Text name for the task. */
    +            MR_TASK_STACK_SIZE / sizeof(uint32_t),  /* Stack size in words, not bytes. */
    +            ( void * ) NULL,                        /* Parameter passed into the task. */
    +            MR_TASK_PRIORITY,                       /* Priority at which the task is created. */
    +            &mrTask );                              /* Used to pass out the created task's handle. */
    +
    +    if(xReturned == errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY)
    +    {
    +        /* Creation of FreeRTOS task failed */
    +        while(1);
    +    }
    +#else
          Task_Params taskParams;
    
          // Configure task
          Task_Params_init(&taskParams);
          taskParams.stack = mrTaskStack;
          taskParams.stackSize = MR_TASK_STACK_SIZE;
          taskParams.priority = MR_TASK_PRIORITY;
          Task_construct(&mrTask, multi_role_taskFxn, &taskParams, NULL);
    
    +#endif
    
    }
    
    Listing 42. multi_role FreeRTOS differences - multi_role_init()
    +#ifdef FREERTOS
    +  Util_constructQueue(&g_POSIX_appMsgQueue);
    +#else
       // Create an RTOS queue for message from profile to be sent to app.
       appMsgQueue = Util_constructQueue(&appMsg);
    +#endif
       // Create one-shot clock for internal periodic events.
    +#ifdef FREERTOS
    +  Util_constructClock(&clkPeriodic,(void*) multi_role_clockHandler,
    +                        MR_PERIODIC_EVT_PERIOD, 0, false,
    +                        (void*)&periodicUpdateData);
    +#else
    
       Util_constructClock(&clkPeriodic, multi_role_clockHandler,
                            MR_PERIODIC_EVT_PERIOD, 0, false,
                            (UArg)&periodicUpdateData);
    +#endif
    

    Note

    simple_central does not make use of a periodic timer like the one shown above. The above change should be performed to any clock constructed in the application using Util_constructClock() to avoid build issues.

    Listing 43. multi_role FreeRTOS differences - multi_role_taskFxn
    /*********************************************************************
    * @fn      multi_role_taskFxn
    *
    * @brief   Application task entry point for the multi_role.
    *
    * @param   a0, a1 - not used.
    *
    * @return  None.
    */
    +#ifdef FREERTOS
    +static void multi_role_taskFxn(void* a0)
    +#else
    static void multi_role_taskFxn(UArg a0, UArg a1)
    +#endif
    {
       // Initialize application
       multi_role_init();
    
       // Application main loop
       for (;;)
       {
          uint32_t events;
    
          // Waits for an event to be posted associated with the calling thread.
          // Note that an event associated with a thread is posted when a
          // message is queued to the message receive queue of the thread
    
    +#ifdef FREERTOS
    +    mq_receive(syncEvent, (char*)&events, sizeof(uint32_t), NULL);
    +#else
          events = Event_pend(syncEvent, Event_Id_NONE, MR_ALL_EVENTS,
                            ICALL_TIMEOUT_FOREVER); // event_31 + event_30
    +#endif
    
          if (events)
    
    //..
    //..
    //..
    
             // If RTOS queue is not empty, process app message.
          if (events & MR_QUEUE_EVT)
          {
    +#ifdef FREERTOS
    +
    +         mrEvt_t *pMsg;
    +         do {
    +             pMsg = (mrEvt_t *)Util_dequeueMsg(g_POSIX_appMsgQueue);
    +             if (NULL != pMsg)
    +             {
    +                 // Process message.
    +                 multi_role_processAppMsg(pMsg);
    +
    +                // Free the space from the message.
    +                ICall_free(pMsg);
    +             }
    +             else
    +             {
    +                 break;
    +             }
    +           }while(1);
    +#else
                while (!Queue_empty(appMsgQueue))
                   {
                      mrEvt_t *pMsg = (mrEvt_t *)Util_dequeueMsg(appMsgQueue);
                      if (pMsg)
                      {
                      // Process message.
                      multi_role_processAppMsg(pMsg);
    
                      // Free the space from the message.
                      ICall_free(pMsg);
                      }
                   }
    +#endif
          }
    
    Listing 44. multi_role FreeRTOS differences - multi_role_advertInit
       if (addrMode > ADDRMODE_RANDOM)
       {
          multi_role_updateRPA();
    +#ifdef FREERTOS
    +    // Create one-shot clock for RPA check event.
    +    Util_constructClock(&clkRpaRead, (void*)multi_role_clockHandler,
    +                        READ_RPA_PERIOD, 0, true,
    +                        (void*) &argRpaRead);
    +
    +#else
          // Create one-shot clock for RPA check event.
          Util_constructClock(&clkRpaRead, multi_role_clockHandler,
                            READ_RPA_PERIOD, 0, true,
                            (UArg) &argRpaRead);
    +#endif
       }
    }
    
    Listing 45. multi_role FreeRTOS differences - multi_role_enqueueMsg
          pMsg->pData = pData;
    
          // Enqueue the message.
    +#ifdef FREERTOS
    +    success = Util_enqueueMsg(g_POSIX_appMsgQueue, syncEvent, (uint8_t *)pMsg);
    +#else
          success = Util_enqueueMsg(appMsgQueue, syncEvent, (uint8_t *)pMsg);
    +#endif
          return (success) ? SUCCESS : FAILURE;
       }
    
    Listing 46. multi_role FreeRTOS differences - multi_role_clockHandler
    /*********************************************************************
    * @fn      multi_role_clockHandler
    *
    * @brief   Handler function for clock timeouts.
    *
    * @param   arg - event type
    *
    * @return  None.
    */
    +#ifdef FREERTOS
    +static void multi_role_clockHandler(void * arg)
    +#else
    static void multi_role_clockHandler(UArg arg)
    +#endif
    {
    
    Listing 47. multi_role FreeRTOS differences - multi_role_handleKeys
    /*********************************************************************
    * @fn      multi_role_handleKeys
    *
    * @brief   Handles all key events for this device.
    *
    * @param   keys - bit field for key events. Valid entries:
    *                 HAL_KEY_SW_2
    *                 HAL_KEY_SW_1
    *
    * @return  none
    */
    static void multi_role_handleKeys(uint8_t keys)
    {
    +#ifndef FREERTOS
          uint32_t rtnVal = 0;
    +#endif
          if (keys & KEY_LEFT)
       {
    +#ifndef FREERTOS
          // Check if the key is still pressed
          if (PIN_getInputValue(CONFIG_GPIO_BTN1) == 0)
          {
    +#endif
          tbm_buttonLeft();
    +#ifndef FREERTOS
          }
    +#endif
       }
       else if (keys & KEY_RIGHT)
       {
          // Check if the key is still pressed
    +#ifndef FREERTOS
          rtnVal = PIN_getInputValue(CONFIG_GPIO_BTN2);
          if (rtnVal == 0)
          {
    +#endif
          tbm_buttonRight();
    +#ifndef FREERTOS
          }
    +#endif
       }
    }
    
    Listing 48. multi_role FreeRTOS differences - multi_role_addConnInfo
                // Create a clock object and start
                connList[i].pUpdateClock
                = (Clock_Struct*) ICall_malloc(sizeof(Clock_Struct));
    
                if (connList[i].pUpdateClock)
                {
    +#ifdef FREERTOS
    +              Util_constructClock(connList[i].pUpdateClock,
    +                                              (void*)multi_role_clockHandler,
    +                                              SEND_PARAM_UPDATE_DELAY, 0, true,
    +                                              (void*) connList[i].pParamUpdateEventData);
    +
    +#else
                   Util_constructClock(connList[i].pUpdateClock,
                                        multi_role_clockHandler,
                                     SEND_PARAM_UPDATE_DELAY, 0, true,
                                     (UArg) connList[i].pParamUpdateEventData);
    +#endif
                }
                else
                {
    

    Note

    There are other clocks (i.e. pRssiClock) defined in the simple_central project. Make sure to apply the above change to those instances as shown in the code snippet below, take note of the new scClockEventData_t structure used to pass in arguments. This can be taken from multi_role.c file and renamed.

    The example below shows the syntax required when passing in parameters to the Util_constructClock() function using FreeRTOS. Simply put, logical statements made inside the function call will result in a build issue.

    Listing 49. simple_central::SimpleCentral_StartRssi() - Clock construction
          if (connList[connIndex].pRssiClock)
       {
    +#ifdef FREERTOS
    +    scClockEventData_t locData;
    +    locData.event = (connIndex << 8) | SC_EVT_READ_RSSI;
    +
    +    // Create one-shot clock for RPA check event.
    +    Util_constructClock(connList[connIndex].pRssiClock,
    +                        (void*)SimpleCentral_clockHandler,
    +                        DEFAULT_RSSI_PERIOD, 0, true,
    +                        (void*)&locData);
    +#else
          Util_constructClock(connList[connIndex].pRssiClock,
                            SimpleCentral_clockHandler,
                            DEFAULT_RSSI_PERIOD, 0, true,
                            (connIndex << 8) | SC_EVT_READ_RSSI);
    +#endif
       }
    

    While searching for the macro FREERTOS in the project, you will find changes to other files (i.e. util.c/h, icall_user_config.c, etc). These changes do not need to be applied to the new example because these files are inherited directly from the SDK.

  2. Modify main_freertos.c

    Listing 50. Modifying main.c - include
    #include "bcomdef.h"
    -#include "multi_role.h"
    +#include "simple_central.h"
    
    Listing 51. Modifying main.c - createTask
    /* Kick off application - Priority 1 */
    -multi_role_createTask();
    +SimpleCentral_createTask();
    
  3. Modify the .syscfg file using a text editor to change the target rtos to freertos.

    Listing 52. multi_role freertos differences - SysConfig
    */
    -// @cliArgs --board /ti/boards/CC26X2R1_LAUNCHXL --rtos tirtos
    +// @cliArgs --board /ti/boards/CC26X2R1_LAUNCHXL --rtos freertos
    
    /*
    *  simple_central.syscfg
    */
    

Caution

Workaround for Button Debouncing

There exists a known issue with FreeRTOS projects in the TI BLE5-Stack (see Release Notes for more information). This issue affects only the usage of the example application’s two button menu. To workaround this, manually add a delay inside board_key.c::Board_keyCallback, as opposed to starting a FreeRTOS timer. Below is an example of the modification:

Listing 53. Reworking the Pin debounce timer
//define this outside the function
+#define CPU_convertMsToDelayCycles(milliseconds) \
+   (((uint32_t)(milliseconds)) * (48000 / 3))

//Place the below changes at the end of Board_keyCallback
         keysPressed |= KEY_RIGHT;
}
#endif

+#ifdef FREERTOS
+/* 100 ms delay */
+CPUdelay(CPU_convertMsToDelayCycles(100));

+// Notify the application
+(*appKeyChangeHandler)(keysPressed);
+#else
Util_startClock(&keyChangeClock);
+#endif