Skip to content

Application structure

Antonio Giacomelli edited this page May 25, 2026 · 20 revisions

This page describes the general structure of an application using RK0.

Barrier

Suppose three tasks are cooperating - each one executes part of a job. A round means that every task has finished its cycle - and we need to ensure no tasks runs more than once per round. This is called mutual coincidence - as opposed to mutual exclusion in concurrent programming. A barrier is a mechanism to implement mutual coincidence.

Configuring the kernel

Knowing that we want to use the Application Logger Facility:

3 solution Tasks + 1 Logger Task = 4 tasks.

We set the kernel to have a 10ms system tick, 4 user tasks and lowest priority of these tasks is 4.

LoggerTask is configured as follows:

//@file app/inc/logger.h

#define CONF_LOGGER 1 /* Turn logger on/off */

#if (RK_CONF_MESG_QUEUE == OFF)
#error "Need RK_CONF_MESG_QUEUE enabled for logger facility"
#endif
#endif

#if (CONF_LOGGER == 1)
#define LOGLEN 64    /* Max length of a single log message */
#define LOGPOOLSIZ 16 /* Number of log buffers  */
#define LOG_STACKSIZE 256 /* Size of the stack */

Regarding kernel services, we need:

  • Sleep Queues
  • Mutexes
  • Message Queues (used by the logger facility)
  • (Memory Partitions for Dynamic allocation is always ON)
//@file core/inc/kconfig.h
#define RK_CONF_SLEEP_QUEUE                      (ON)

#define RK_CONF_MUTEX                            (ON)

#define RK_CONF_MESG_QUEUE                       (ON)

Application code

Depending on how you structure your application, this can vary a little. Here, the main() function is already within the application.c file. If not, and both compilation units need to be exposed to the same dependencies, you might want to append them in application.h.

The application.c in this repository contains a Barrier example using time-outs to demonstrate time guarantees. Furthermore, shared-state and message-passing versions are implemented to illustrate the duality.

//@file application.c

#include <kapi.h> /* Kernel API */
#include <logger.h> 
#include <bsp.h> /* platform bsp */
int main(void)
{
    /* keep interrupts disabled */
    kDisableIRQ();
 
    /* this might setup the lower layer, configure PLLs, etc. depends 
    on the platform.  */

    BSP_Init();
    
    /* < any other middleware initialisation might be placed here > */
    initOtherSutff();

 
     /* Configure and initialise armv6/7M core interrupts needed. 
        This a RK0 function that works for its target plaftorms,
        with or without CMSIS-Core HAL */
    
     kCoreInit();

     /* initialise internal kernel 
data structures and start the scheduler. 
Interrupts will be enabled on due time.  */
    
     kInit();
    
   /* we shall never return from kInit unless
      things go wrong */
    while(1)
    {  
        /* it gets here only if fault is not caught before */
        kErrHandler(RK_FAULT_APP_CRASH);
           
    }

    return (-1); /* keep it tight, int func must have a return */
 
}

/*** Synchronisation Barrier  ***/

typedef struct
{
    RK_MUTEX            lock; /* this lock keeps a single active task in the monitor */
    RK_SLEEP_QUEUE      cond; /* queue tasks sleep for required==count */
    UINT count;        /* number of tasks in the barrier */
    UINT required;     /* number of required tasks */
    UINT round;        /* increased every time all tasks synch     */
} Barrier_t;

VOID BarrierInit(Barrier_t *const barPtr, UINT requiredTasks)
{
    kMutexInit(&barPtr->lock, RK_INHERIT);
    kSleepQueueInit(&barPtr->cond);
    barPtr->count = 0;
    barPtr->round = 0;
    barPtr->required = requiredTasks;
}

VOID BarrierWait(Barrier_t *const barPtr)
{
    UINT myRound = 0;
    kMutexLock(&barPtr->lock, RK_WAIT_FOREVER);

    /* save round number */
    myRound = barPtr->round;
    /* increase count on this round */
    barPtr->count++;
    logPost("[BARRIER: %u/%u]: %s ENTERED ",  barPtr->count, barPtr->required, RK_RUNNING_NAME);

    if (barPtr->count == barPtr->required)
    {
         logPost("[BARRIER: %u/%u]: %s WAKING ALL TASKS",  barPtr->count, barPtr->required, RK_RUNNING_NAME);


        /* reset counter, inc round, broadcast to sleeping tasks */
        barPtr->round++;
        barPtr->count = 0;
        kCondVarBroadcast(&barPtr->cond);
    }
    else
    {
         logPost("[BARRIER: %u/%u]: %s BLOCKED ",  barPtr->count, barPtr->required, RK_RUNNING_NAME);
        /* a proper wake signal might happen after inc round */
        while ((UINT)(barPtr->round - myRound) == 0U)
        {
            /* helper: task sleeps and unlock the mutex (so another task can enter) 
               atomically */
            kCondVarWait(&barPtr->cond, &barPtr->lock, RK_WAIT_FOREVER);
        }
    }

    kMutexUnlock(&barPtr->lock);

}

/* Declare objects needed for each task: 
its Handle name, its entry function, stack buffer name 
and stack size */

#define STACKSIZE 256 /* 1024 Bytes for each stack */

RK_DECLARE_TASK(task1Handle, Task1, stack1, STACKSIZE)
RK_DECLARE_TASK(task2Handle, Task2, stack2, STACKSIZE)
RK_DECLARE_TASK(task3Handle, Task3, stack3, STACKSIZE)

#define LOG_PRIORITY 4 /* the logger task priority */ 

/* declare the monitor */
Barrier_t syncBarrier; 
#define REQUIRED_TASKS 3


/*** Initialise Application ***/
/* This function is mandatory  */
VOID kApplicationInit(VOID)
{

    RK_ERR err = kCreateTask(&task1Handle, Task1, RK_NO_ARGS, "Task1", stack1, STACKSIZE, 1, RK_PREEMPT);
    K_ASSERT(err==RK_ERR_SUCCESS);

    err = kCreateTask(&task2Handle, Task2, RK_NO_ARGS, "Task2", stack2, STACKSIZE, 2, RK_PREEMPT);
    K_ASSERT(err==RK_ERR_SUCCESS);

    err = kCreateTask(&task3Handle, Task3, RK_NO_ARGS, "Task3", stack3, STACKSIZE, 3, RK_PREEMPT);
    K_ASSERT(err==RK_ERR_SUCCESS);
 
    BarrierInit(&syncBarrier, REQUIRED_TASKS); /* initialise barrier (sleep queues and mutexes) */
   
    logInit(LOG_PRIORITY); /* initialise application logger */

}

/* Tasks definition */
VOID Task1(VOID* args)
{
    RK_UNUSEARGS
    while (1)
    {
        logPost("Task 1 dispatched. Working...");
        kBusyDelay(100); /* simulate work */
        BarrierWait(&syncBarrier);
        logPost("Task 1 left the barrier!");
        kSleep(1); 
    }
}

VOID Task2(VOID* args)
{
    RK_UNUSEARGS
    while (1)
    {
        logPost("Task 2 dispatched. Working...");
        kBusyDelay(200); /* simulate work */
        BarrierWait(&syncBarrier);
        logPost("Task 2 left the barrier!");
        kSleep(1);  
    }
}

VOID Task3(VOID* args)
{
    RK_UNUSEARGS
    while (1)
    {
        logPost("Task 3 dispatched. Working...");
        kBusyDelay(300); /* simulate work */
        BarrierWait(&syncBarrier);
        logPost("Task 3 left the barrier!");
        kSleep(1);
    }
}

Output

       0 ms :: Task 1 dispatched. Working... 
    1000 ms :: [BARRIER: 1/3]: Task1 ENTERED  
    1000 ms :: [BARRIER: 1/3]: Task1 BLOCKED  
    1000 ms :: Task 2 dispatched. Working... 
    3000 ms :: [BARRIER: 2/3]: Task2 ENTERED  
    3000 ms :: [BARRIER: 2/3]: Task2 BLOCKED  
    3000 ms :: Task 3 dispatched. Working... 
    6000 ms :: [BARRIER: 3/3]: Task3 ENTERED  
    6000 ms :: [BARRIER: 3/3]: Task3 WAKING ALL TASKS 
    6000 ms :: Task 3 left the barrier! 
    6000 ms :: Task 1 left the barrier! 
    6000 ms :: Task 2 left the barrier! 
    6010 ms :: Task 1 dispatched. Working... 
    7010 ms :: [BARRIER: 1/3]: Task1 ENTERED  
    7010 ms :: [BARRIER: 1/3]: Task1 BLOCKED  
    7010 ms :: Task 2 dispatched. Working... 
    9010 ms :: [BARRIER: 2/3]: Task2 ENTERED  
    9010 ms :: [BARRIER: 2/3]: Task2 BLOCKED  
    9010 ms :: Task 3 dispatched. Working... 
   12010 ms :: [BARRIER: 3/3]: Task3 ENTERED  
   12010 ms :: [BARRIER: 3/3]: Task3 WAKING ALL TASKS 

Clone this wiki locally