A real world migration from TI-RTOS to the POSIX API of TI SimpleLink.

Texas Instruments migrated from the proprietary TI-RTOS to the open POSIX API.

I'm migrating a project that extensively used the TI paradigms to that POSIX API. You're my witness.

The blog assumes that you are a TI-RTOS user and want to adapt to the new ways of working.

 

POSIX is an open API that defines how threads can be used. It also deals with common multi-thread topics like sharing (or reserving) resources, scheduling, priorities, and what have you.

I'm using the POSIX API to program the firmware of a Programmable Electronic Load. In fact, I'm porting the firmware from TI-RTOS to POSIX. You can follow along.

 

Side note: TI's move to SimpleLink from TI-RTOS

If you've never used TI-RTOS and its APIs, this blog is not for you. Just dive into SimpleLink and learn it.

If you've been using TI-RTOS, you can learn how the concepts of its API are handled now in SimpleLink.

 

When TI switched to SimpleLink, their RTOS approach underwent a significant overhaul.

Before SimpleLink, the RTOS was TI-RTOS centric. All examples used that APIs, it was an integral part of CCS and there was a GUI to configure tasks, semaphores, mailboxes and events.

In the new approach, SimpleLink uses the POSIX API. Under the hood, TI-RTOS or FreeRTOS can do the work. Your code doesn't need to know.

You win some, you loose some. For POSIX savvy people, this is a win. No need to learn a new RTOS. And POSIX is a standard. Adapting to standards is great.

I do have some gripes with it: the integration with CCS, where a separate project is created for the RTOS, feels hacky. And we lost the great GUI where you can declare tasks, mailboxes, semaphores, events.

Both things could be addressed if TI would put some effort into this. But I'm over that. I'm now porting the Programmable Electronic Load that we're designing here to SimpleLink .

 

disclaimer: I'm human. Any change to a trusted and known approach results in resistance at first. The main reason why I waited to adapt the DC Load is because the pre-production LaunchPad that I used for the DC Load didn't support the latest SimpleLink releases. Not the things above. I tried to switch before but ran into (documented!) incompatibility issues with that pre-production LaunchPad.

 

Exercice 1: Threads

 

A TI-RTOS task turns into a POSIX thread. In the original firmware for the electronic load, I used the RTOS GUI to configure them.

In the POSIX paradigm, you declare and create them in code.

 

The heartbeat thread described here is a low priority job that will blink a LED on the LaunchPad. It helps to visually check the health of the RTOS while developing.

Any activity that impacts the bandwidth to run jobs regularly is easy spotted because the LED's rhythm gets disrupted. That's something that can be spotted by just looking at the board.

 

At the startup of the firmware, before the RTOS is started, The tasks are created.

note: BIOS_start() is TI-RTOS specific.

 

    /* Set priority and stack size attributes */
    pthread_attr_init(&attrs);
    priParam.sched_priority = 1;


    detachState = PTHREAD_CREATE_DETACHED;
    retc = pthread_attr_setdetachstate(&attrs, detachState);
    if (retc != 0) {
        /* pthread_attr_setdetachstate() failed */
        while (1);
    }


    pthread_attr_setschedparam(&attrs, &priParam);


    retc |= pthread_attr_setstacksize(&attrs, THREADSTACKSIZE);
    if (retc != 0) {
        /* pthread_attr_setstacksize() failed */
        while (1);
    }

    retc = pthread_create(&thread, &attrs, threadHeartBeat, NULL);
    if (retc != 0) {
        /* pthread_create() failed */
        while (1);
    }
// initiate shared hardware modules only. The ones dedicated to a task are initiated in that task
    GPIO_init();




    BIOS_start();

 

The thread has the same look and feel as the TI-RTOS task. You initialise the work to be done, then loop and sleep.

I still have to turn that sleep time into something that is centrally gouverned (together with priority), so that I don't loose view on what's running when.

I could pass it in arg0, but for the moment I'm planning to use just a header file where each thread's signature, sleep time and priority is defined (discuss if you think this can be done better).

 

 

/*
 *  ======== threadHeartBeat ========
 */
void *threadHeartBeat(void *arg0)
{
    /* 1 second delay */
    uint32_t time = 1;


    /* Call driver init functions */


    /* Configure the LED pin */
    GPIO_setConfig(Board_GPIO_LED0, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);


    /* Turn on user LED */
    GPIO_write(Board_GPIO_LED0, Board_GPIO_LED_ON);


    while (1) {
        sleep(time);
        GPIO_toggle(Board_GPIO_LED0);
    }
}

 

This is an easy migration. I didn't expect complexities here because I've done this before in both FreeRTOS and TI-RTOS, both without the POSIX API.

Also, it's the main chunk of the SimpleLink RTOS "empty" example.

 

Exercice 2: UART , Triggers and Semaphores

 

I'm starting with the UART interface because it uses some interesting RTOS structures: interrupt driven IO and semaphores.

 

 

The task to read the UART is interrupt driven. Our task always sleeps and only wakes up when a character arrives at the USB Rx line.

We use a binary semaphore for that - one that always blocks unless a process (in our case, serial in interrupt) posts to that semaphore.

 

sem_t SEM_uart_rx; // this binary semaphore handles uart receiving interrupts

 

The semaphore gets initialised at the start of the UART task:

 

    iError = sem_init(&SEM_uart_rx, 0, 0);

 

Then, in that task's while loop, the process blocks on that binary semaphore and goes to sleep. Check the sem_wait() call.

 

   /* Loop forever echoing */
    while (1) {
        UART_read(uart, &input, 1);
        iError = sem_wait(&SEM_uart_rx); // when a character is received via UART, the interrupt handler will release the binary semaphore
        while (iError) {
            // POSIX reported error with semaphore. Can't recover.
        }
        // in my case: I get an interrupt for a single character, no need to loop.
        scpi_instrument_input((const char *)&input, 1);
    }

 

The task will sleep until someone wakes up the semaphore. The UART data receive interrupt will do that:

 

void UART00_IRQHandler(UART_Handle handle, void *buffer, size_t num)
{
    sem_post(&SEM_uart_rx);
}

 

This task does not have a sleep() call, because it isn't scheduled. The semaphore construct makes it reactive (and very cheap on resources while waiting).

The whole construct (including the registration of the read interrupt handler) looks like this:

 

void *threadUART(void *arg0)
{
    char        input;
    UART_Params uartParams;
    int iError;


    /* Call driver init functions */
//    GPIO_init();
    UART_init();


    /* Configure the LED pin */
    GPIO_setConfig(Board_GPIO_LED0, GPIO_CFG_OUT_STD | GPIO_CFG_OUT_LOW);


    /* Create a UART with data processing off. */
    UART_Params_init(&uartParams);
    uartParams.writeDataMode = UART_DATA_BINARY;
    uartParams.readDataMode = UART_DATA_BINARY;
    uartParams.readReturnMode = UART_RETURN_FULL;
    uartParams.readEcho = UART_ECHO_OFF;
    uartParams.baudRate = 9600;
    uartParams.readMode = UART_MODE_CALLBACK; // the uart uses a read interrupt
    uartParams.readCallback = &UART00_IRQHandler; // function called when the uart interrupt fires

    uart = UART_open(Board_UART0, &uartParams);

    if (uart == NULL) {
        /* UART_open() failed */
        while (1);
    }


    iError = sem_init(&SEM_uart_rx, 0, 0);

    /* Loop forever echoing */
    while (1) {
        UART_read(uart, &input, 1);
        iError = sem_wait(&SEM_uart_rx); // when a character is received via UART, the interrupt handler will release the binary semaphore
        while (iError) {
            // POSIX reported error with semaphore. Can't recover.
        }
        // in my case: I get an interrupt for a single character, no need to loop.
        scpi_instrument_input((const char *)&input, 1);
    }
}

 

Writing to UART hasn't changed from the original TI-RTOS implementation, because that's not using interrupts and only has dependencies on the TI Driver lib.

 

The task is created exactly the same as the heartbeat one:

 

    retc = pthread_create(&thread, &attrs, threadUART, NULL);
    if (retc != 0) {
        /* pthread_create() failed */
        while (1);
    }

 

I'll have to port all of the electronic load tasks. Most of that exercise will be a repeat from what you see in this post.

The exception is those locations where I use a mailbox to send info between tasks (and wait on traffic on the mailboxes).

I'll have to do some investigation first before I can do that. But if it's as easy as porting the threads and semaphores, I have good hope.

 

Related Blog
Switch from TI-RTOS to SimpleLink POSIX: Threads and Semaphores
Switch from TI-RTOS to SimpleLink POSIX: EEPROM API

Switch from TI-RTOS to SimpleLink POSIX: From MailBox to Message Queue

Switch from TI-RTOS to SimpleLink POSIX: LCD Display Driver
Switch from TI-RTOS to SimpleLink POSIX: Sleep when Idle