Marcel
Marcel

Reputation: 157

How to best convert legacy polling embedded firmware architecture into an event driven one?

I've got a family of embedded products running a typical main-loop based firmware with 150+k lines of code. A load of complex timing critical features is realized by a combination of hardware interrupt handlers, timer polling and protothreads (think co-routines). Actually protothreads are polling well and "only" are syntactic sugar to mimic pseudo-parallel scheduling of multiple threads (endless loops). I add bugfixes and extensions to the firmware all the time. There are about 30k devices of about 7 slightly different hardware types and versions out in the field.

For a new product-family member I need to integrate an external FreeRTOS-based project on the new product only while all older products need to get further features and improvements.

In order to not have to port the whole complex legacy firmware to FreeRTOS with all the risk of breaking perfectly fine products I plan to let the old firmware run inside a FreeRTOS task. On the older products the firmware shall still not run without FreeRTOS. Inside the FreeRTOS task the legacy firmware would consume all available processor time because its underlying implementation scheme is polling based. Due to the fact that its using prothreads (timer and hardware polling based behind the scenes) and polls on a free-running processor counter register I hope that I can convert the polling into a event driven behavior.

Here are two examples:


// first example: do something every 100 ms
if (GET_TICK_COUNT() - start > MS(100))
{
     start = GET_TICK_COUNT();
     // do something every 100 ms
}

// second example: wait for hardware event
setup_hardware();
PT_WAIT_UNTIL(hardware_ready(), pt);

// hardware is ready, do something else

So I got the impression that I can convert these 2 programming patterns (e.g. through macro magic and FreeRTOS functionality) into an underlying event based scheme.

So now my question: has anybody done such a thing already? Are there patterns and or best practices to follow?

[Update]

Thanks for the detailed responses. Let me comment some details: My need is to combine a "multithreading-simulation based legacy firmware (using coo-routines implementation protothreads)" and a FreeRTOS based project being composed of a couple of interacting FreeRTOS tasks. The idea is to let the complete old firmware run in its own RTOS task besides the other new tasks. I'm aware of RTOS principles and patterns (pre-emption, ressource sharing, blocking operations, signals, semaphores, mutexes, mailboxes, task priorities and so forth). I've planed to base the interaction of old and new parts exactly on these mechanisms. What I'm asking for is 1) Ideas how to convert the legacy firmware (150k+ LOC) in a semi-automated way so that the busy-waiting / polling schemes I presented above are using either the new mechanisms when run inside the RTOS tasks or just work the old way when build and run as the current main-loop-kind of firmware. A complete rewrite / full port of the legacy code is no option. 2) More ideas how to teach the old firmware implementation that is used to have full CPU resources on its hands to behave nicely inside its new prison of a RTOS task and not just consume all CPU cycles available (when given the highest priority) or producing new large Real Time Latencies when run not at the highest RTOS priority.

I guess here is nobody who already has done such a very special tas. So I just have to do the hard work and solve all the described issues one after another.

Upvotes: 0

Views: 1029

Answers (2)

Clifford
Clifford

Reputation: 93566

In an RTOS you create and run tasks. If you are not running more than one task, then there is little advantage in using an RTOS.

I don't use FreeRTOS (but have done), but the following applies to any RTOS, and is pseudo-code rather then FreeRTOS API specific - many details such as task priorities and stack allocation are deliberately missing.

First in most simple RTOS, including FreeRTOS, main() is used for hardware initialisation, task creation, and scheduler start:

int main( void )
{
    // Necessary h/w & kernel initialisation
    initHardware() ;
    initKernel() ;

    // Create tasks
    createTask( task1 ) ;
    createTask( task2 ) ;

    // Start scheduling
    schedulerStart() ;

    // schedulerStart should not normally return
    return 0 ;
}

Now let us assume that your first example is implemented in task1. A typical RTOS will have both timer and delay functions. The simplest to use is a delay, and this is suitable when the periodic processing is guaranteed to take less than one OS tick period:

void task1()
{
    // do something every 100 ms
    for(;;)
    {
        delay( 100 ) ; // assuming 1ms tick period

        // something
        ...
    }
}

If the something takes more than 1ms in this case, it will not be executed every 100ms, but 100ms plus the something execution time, which may itself be variable or non-deterministic leading to undesirable timing jitter. In that case you should use a timer:

void task1()
{
    // do something every 100 ms
    TIMER timer = createTimer( 100 ) ; // assuming 1ms tick period
    for(;;)
    {
        timerWait() ;     

        // something
        ...
    }
}

That way something can take up to 100ms and will still be executed accurately and deterministically every 100ms.

Now to your second example; that is a little more complex. If nothing useful can happen until the hardware is initialised, then you may as well use your existing pattern in main() before starting the scheduler. However as a generalisation, waiting for something in a different context (task or interrupt) to occur is done using a synchronisation primitive such as a semaphore or task event flag (not all RTOS have task event flags). So in a simple case in main() you might create a semaphore:

createSemaphore( hardware_ready ) ;

Then in the context performing the process that must complete:

// Init hardware
...

// Tell waiting task hardware ready
semaphoreGive( hardware_ready ) ;

Then in some task that will wait for the hardware to be ready:

void task2()
{
    // wait for hardware ready
    semaphoreTake( hardware_ready ) ;

    // do something else
    for(;;)
    {
        // This loop must block is any lower-priority task
        // will run.  Equal priority tasks may run is round-robin
        // scheduling is implemented. 
        ...
    }
}

Upvotes: 3

Doug Currie
Doug Currie

Reputation: 41220

You are facing two big gotchas...

  1. Since the old code is implemented using protothreads (coroutines), there is never any asynchronous resource contention between them. If you split these into FreeRTOS tasks, there there will be preemptive scheduling task switches; these switches may occur at places where the protothreads were not expecting, leaving data or other resources in an inconsistent state.
  2. If you convert any of your protothreads' PT_WAIT calls into real waits in FreeRTOS, the call will really block. But the protothreads assume that other protothreads continue while they're blocked.

So, #1 implies you cannot just convert protothreads to tasks, and #2 implies you must convert protothreads to tasks (if you use FreeRTOS blocking primitives, like xEventGroupWaitBits().

The most straightforward approach will be to put all your protothreads in one task, and continue polling within that task.

Upvotes: 2

Related Questions