Interrupt Handlers and Tasks in REXIS

REXIS is both ImageCraft’s RTOS and a component of the JumpStart IoT Connectivity Suite. It’s easy to use and also supports multiple APIs for IPC (Inter-Process Communication). In this post, we aim to show how easy it is to write an interrupt handler to interact with a normal REXIS task.

Problem Description: in an embedded system, the most efficient method of detecting sensor and other data input is through peripheral interrupts. An interrupt handler, or Interrupt Service Routine (ISR), is called when the hardware device receives data (e.g.: a character arrives at a UART receive register). The ISR reads the data from the hardware device, and the data can then be processed by the firmware. Because interrupts preempt other code from running, to minimize performance impact on the rest of the system the ISR should do its job as quickly as possible and leave the more complex data processing to code that is not running in an interrupt context.

Why is this important? While the example in this post is trivial in nature (basically, it’s a terminal I/O echo program), it demonstrates one of the most important aspects of using an RTOS: use an ISR to handle a hardware event, and then pass the data to a normal task for data processing.

Creating a Task: This single line does the job of creating a task:

mbox1 = REXIS_MailboxCreate(0);

REXIS_TaskCreate((char *)"task 1", SimpleTask, 0, 0, (unsigned int)mbox1);

“mbox1” is a mailbox object used for IPC. This is the Doxygen description of the REXIS_TaskCreate function, which describes the arguments to the REXIS_TaskCreate function:

/** \brief Create a new task. A task can be created in the initial main program, or in another task.
*
\param name the name of the task. You can search for a task by its name. NOTE: REXIS does not check for the uniqueness of the name. If there are multiple tasks with the same name, the behavior is undefined.
\param func name/address of the task function. Any C or asm function can be used.
\param prio the task priority.
\param stack_size size of the stack for the task. NOTE: all REXIS allocated memory is from the memory pool specified in the REXIS_SysInit() call. If you allocate too much memory for a task, resource will be wasted.
\param arg0 initial argument to "func".
\return a "process ID" (or PID for short), which is an integer. Every task has a unique PID starting with "1". * */
int REXIS_TaskCreate(char *name, void (*func)(unsigned), unsigned prio, unsigned stack_size, unsigned arg0);

A REXIS task is just a regular C function, typically running an infinite loop. The function takes an initial argument which is supplied as one of the arguments in the REXIS_TaskCreate function call. This might be useful if you use the same C function for different tasks.

The actual task function is very simple:

void SimpleTask(unsigned arg0)
{
REXIS_MAILBOX *mbox = (REXIS_MAILBOX *)arg0;
  while (1) { unsigned char *pch; REXIS_MailboxFetch(mbox, (void **)&pch, 0); putchar(pch[0]); } }

The task function fetches a character from the mailbox and then outputs it using putchar (to the terminal screen). This process repeats forever. If there is no message in the mailbox, REXIS_MailboxFetch puts the task in a sleep state until a message arrives; at which point the task awakes. This means that the task does not use any CPU time when there is nothing for it to do.

In a non-RTOS scheme, a similar function will have to periodically poll to see if there is data available, using CPU time even when there is nothing for it to do.

Notice that with REXIS, the task function does not need to explicitly relinquish control to other tasks. REXIS uses priority-based preemptive scheduling and places few obstacles on task functions.

Interrupt Handler: this is the UART interrupt handler:

void UART2_RX_ISR(void)
    {
static unsigned char ch;
  ch = getchar(); REXIS_MailboxTryPostFromISR(mbox1, &ch); NVIC_ClearPendingIRQ(USART2_IRQn); }

The handler is called when there is data available on the hardware peripheral receive data register. When the data is available, the getchar function returns immediately with the data as read from the hardware. The ISR then calls REXIS_MailboxTryPostFromISR to post the received character to the mailbox, which will then be read by “SimpleTask” above. Finally, NVIC_ClearPendingIRQ function is a housekeeping function that resets the interrupt status so that the handler will be called again at the next data input.

Note that as a mailbox does not copy the content of the message from the sender to the receiver (from the ISR to SimpleTask in this example), the “message”, which is the variable “ch”and contains the data from the UART, is declared as a static variable so it can be referenced correctly when SimpleTask accesses the data. If the data is declared as “auto” (the default for variables declared inside a function), then when SimpleTask runs, “ch” might be corrupted.

To use REXIS, a few more API calls must be made, and relevant interrupt vector entries must be set up. This will be explained in a future blog post.

In Summary, it is very easy to use REXIS, and the resulting firmware runs very efficiently without wasting CPU cycles on polling for “something to do”.

When there is nothing to do, REXIS can put the CPU in a low power sleep state, minimizing the power usage.

Scroll to Top