Using Message Queues

The table below shows a summary of message-queue services available from µC/OS-III. Refer to µC-OS-III API Reference for a full description on their use.

Message Queue API Summary
Function NameOperation
OSQCreate()Create a message queue.
OSQDel()Delete a message queue.
OSQFlush()Empty the message queue.
OSQPend()Wait for a message.
OSQPendAbort()Abort waiting for a message.
OSQPost()Send a message through a message queue.

The table below is a summary of task message queue services available from µC/OS-III. Refer to µC-OS-III API Reference , for a full description of their use.

Task Message Queue API Summary
Function NameOperation
OSTaskQPend()Wait for a message.
OSTaskQPendAbort()Abort the wait for a message.
OSTaskQPost()Send a message to a task.
OSTaskQFlush()Empty the message queue.

The figure below shows an example of using a message queue when determining the speed of a rotating wheel.

Measuring RPM

(1) The goal is to measure the RPM of a rotating wheel.

(2) A sensor is used to detect the passage of a hole in the wheel. In fact, to receive additional resolution, the wheel could contain multiple holes that are equally spaced.

(3) A 32-bit input capture register is used to capture the value of a free-running counter when the hole is detected.

(4) An interrupt is generated when the hole is detected. The ISR reads the current count of the input capture register and subtracts the value of the previous capture to determine the time it took for one rotation (assuming only a single hole).

Delta Counts = Current Counts – Previous Counts;
Previous Counts = Current Counts;

(5-6)  The delta counts are sent to a message queue. Since a message is actually a pointer, if the pointer is 32-bits wide on the processor in use, you can simply cast the 32-bit delta counts to a pointer and send this through the message queue. A safer and more portable approach is to dynamically allocate storage to hold the delta counts using a memory block from µC/OS-III’s memory management services (see  Memory Management API Changes ) and send the address of the allocated memory block. The counts read are then saved in ‘Previous Counts’ to be used on the next interrupt.

(7) When the message is sent, the RPM measurement task wakes up and computes the RPM as follows:

RPM = 60 * Reference Frequency / Delta Counts;

The user may specify a timeout on the pend call and the task will wake up if a message is not sent within the timeout period. This allows the user to easily detect that the wheel is not rotating and therefore, the RPM is 0.

(8) Along with computing RPM, the task can also compute average RPM, maximum RPM, and whether the speed is above or below thresholds, etc.

A few interesting things are worth noting about the above example. First, the ISR is very short; it reads the input capture and post the delta counts to the task so it can computer the time-consuming math. Second, with the timeout on the pend, it is easy to detect that the wheel is stopped. Finally, the task can perform additional calculations and can further detect such errors as the wheel spinning too fast or too slow. In fact, the task can notify other tasks about these errors, if needed.

The listing below shows how to implement the RPM measurement example using µC/OS-III’s message queue services. Some of the code is pseudo-code, while the calls to µC/OS-III services are actual calls with their appropriate arguments.

Pseudo-code of RPM Measurement
OS_Q        RPM_Q;                                                   (1)
CPU_INT32U  DeltaCounts;
CPU_INT32U  CurrentCounts;
CPU_INT32U  PreviousCounts;
 
 
void main (void)
{
    OS_ERR  err ;
    :
    OSInit(&err) ;                                                  (2)
    :
    OSQCreate((OS_Q     *)&RPM_Q,
              (CPU_CHAR *)"My Queue",
              (OS_MSG_QTY)10,
              (OS_ERR   *)&err);
    :
    OSStart(&err);
}
 
 
void RPM_ISR (void)                                                 (3)
{
    OS_ERR  err;
 
 
    Clear the interrupt from the sensor;
    CurrentCounts  = Read the input capture;
    DeltaCounts    = CurrentCounts - PreviousCounts;
    PreviousCounts = CurrentCounts;
    OSQPost((OS_Q      *)&RPM_Q,                                    (4)
            (void      *)DeltaCounts,
            (OS_MSG_SIZE)sizeof(void *),
            (OS_OPT     )OS_OPT_POST_FIFO,
            (OS_ERR    *)&err);        
}

void RPM_Task (void *p_arg)                                         
{
    CPU_INT32U   delta;
    OS_ERR       err;
    OS_MSG_SIZE  size;
    CPU_TS       ts;
 
 
    DeltaCounts    = 0;
    PreviousCounts = 0;
    CurrentCounts  = 0;   
    while (DEF_ON) {
        delta = (CPU_INT32U)OSQPend((OS_Q        *)&RPM_Q,          (5)
                                    (OS_TICK      )OS_CFG_TICK_RATE_HZ * 10, 
                                    (OS_OPT       )OS_OPT_PEND_BLOCKING,
                                    (OS_MSG_SIZE *)&size,
                                    (CPU_TS      *)&ts,                 
                                    (OS_ERR      *)&err);               
        if (err == OS_ERR_TIMEOUT) {                                (6)
            RPM = 0;
        } else {
            if (delta > 0u) {
                RPM = 60 * Reference Frequency / delta;             (7)
            }
        }
        Compute average RPM;                                        (8)                         
        Detect maximum RPM;
        Check for overspeed;
        Check for underspeed;
        :                               
        :
    }
}

(1) Variables are declared. Notice that it is necessary to allocate storage for the message queue itself.

(2) You need to call OSInit() and create the message queue before it is used. The best place to do this is in startup code.

(3) The RPM ISR clears the sensor interrupt and reads the value of the 32-bit input capture. Note that it is possible to read RPM if there is only a 16-bit input capture. The problem with a 16-bit input capture is that it is easy for it to overflow, especially at low RPMs.

The RPM ISR also computes delta counts directly in the ISR. It is just as easy to post the current counts and let the task compute the delta. However, the subtraction is a fast operation and does not significantly increase ISR processing time.

(4) The code then sends the delta counts to the RPM task, which is responsible for computing the RPM and perform additional computations. Note that the message gets lost if the queue is full when the user attempts to post. This happens if data is generated faster than it is processed. Unfortunately, it is not possible to implement flow control in the example because we are dealing with an ISR.

(5) The RPM task starts by waiting for a message from the RPM ISR by pending on the message queue. The third argument specifies the timeout. In this case, ten seconds worth of timeout is specified. However, the value chosen depends on the requirements of an application.

Also notice that the ts variable contains the timestamp of when the post was completed. You can determine the time it took for the task to respond to the message received by calling OS_TS_GET(), and subtract the value of ts:

response_time = OS_TS_GET() – ts;  

(6) If a timeout occurs, you can assume the wheel is no longer spinning.

(7) The RPM is computed from the delta counts received, and from the reference frequency of the free-running counter.

(8) Additional computations are performed as needed. In fact, messages can be sent to different tasks in case error conditions are detected. The messages would be processed by the other tasks. For example, if the wheel spins too fast, another task can initiate a shutdown on the device that is controlling the wheel speed.

In the lising below , OSQPost() and OSQPend() are replaced with OSTaskQPost() and OSTaskQPend() for the RPM measurement example. Notice that the code is slightly simpler to use and does not require creating a separate message queue object. However, when creating the RPM task, it is important to specify the size of the message queue used by the task and compile the application code with OS_CFG_TASK_Q_EN set to DEF_ENABLED. The differences between using message queues and the task’s message queue will be explained.

Pseudo-code of RPM Measurement
OS_TCB RPM_TCB; (1)
OS_STK      RPM_Stk[1000];                           
CPU_INT32U  DeltaCounts ;
CPU_INT32U  CurrentCounts ;
CPU_INT32U  PreviousCounts ;
 
 
void main (void)
{
    OS_ERR  err ;
    :
    OSInit(&err) ;                                 
    :
    void  OSTaskCreate ((OS_TCB       *)&RPM_TCB,                   (2) 
                        (CPU_CHAR     *)"RPM Task",
                        (OS_TASK_PTR   )RPM_Task,
                        (void         *)0,
                        (OS_PRIO       )10,
                        (CPU_STK      *)&RPM_Stk[0],
                        (CPU_STK_SIZE  )100,
                        (CPU_STK_SIZE  )1000,
                        (OS_MSG_QTY    )10,
                        (OS_TICK       )0,
                        (void         *)0,
                        (OS_OPT        )(OS_OPT_TASK_STK_CHK + OS_OPT_TASK_STK_CLR),
                        (OS_ERR       *)&err);
    :
    OSStart(&err);
}

void RPM_ISR (void)
{
    OS_ERR  err;
 
    Clear the interrupting from the sensor;
    CurrentCounts  = Read the input capture;
    DeltaCounts    = CurrentCounts - PreviousCounts;
    PreviousCounts = CurrentCounts;
    OSTaskQPost((OS_TCB    *)&RPM_TCB,                                           (3)
                (void      *)DeltaCounts,
                (OS_MSG_SIZE)sizeof(DeltaCounts),
                (OS_OPT     )OS_OPT_POST_FIFO,
                (OS_ERR    *)&err);        
}
 
void RPM_Task (void *p_arg)
{
    CPU_INT32U   delta;
    OS_ERR       err;
    OS_MSG_SIZE  size;
    CPU_TS       ts;
 
 
    DeltaCounts    = 0;
    PreviousCounts = 0;
    CurrentCounts  = 0;   
    while (DEF_ON) {
        delta = (CPU_INT32U)OSTaskQPend((OS_TICK       )OS_CFG_TICK_RATE * 10,   (4)  
                                        (OS_OPT       )OS_OPT_PEND_BLOCKING,
                                        (OS_MSG_SIZE *)&size,
                                        (CPU_TS      *)&ts,                 
                                        (OS_ERR      *)&err);               
        if (err == OS_ERR_TIMEOUT) {
            RPM = 0;
        } else {
            if (delta > 0u) {
                RPM = 60 * ReferenceFrequency / delta;
            }
        }
        Compute average RPM;
        Detect maximum RPM;
        Check for overspeed;
        Check for underspeed;
        :  
        :
    }
}

(1)  Instead of declaring a message queue, it is important to know the  OS_TCB  of the task that will be receiving messages.

(2) The RPM task is created and a queue size of 10 entries is specified. Of course, hard-coded values should not be specified in a real application, but instead, you should use #defines. Fixed numbers are used here for sake of illustration.

(3) Instead of posting to a message queue, the ISR posts the message directly to the task, specifying the address of the OS_TCB of the task. This is known since the OS_TCB is allocated when creating the task.

(4) The RPM task starts by waiting for a message from the RPM ISR by calling OSTaskQPend(). This is an inherent call so it is not necessary to specify the address of the OS_TCB to pend on as the current task is assumed. The second argument specifies the timeout. Here, ten seconds worth of timeout is specified, which corresponds to 6 RPM.