Skip to content
Rajiv Verma edited this page May 13, 2014 · 2 revisions

Application Support Framework

ASF stands for Application Support Framework. It is a framework for stitching together the various system components to the application. The main purpose is application portability across hardware platforms, CPU architectures and RTOSes.

It provides APIs, macros and utility functions for managing tasks, timers and message passing in the application. These methods have been created to abstract the underlying RTOS and allow porting of the existing application firmware to different platforms with minimal efforts.

The ASF encourages organization of application tasks as message handling loops as illustrated by the following pseudo code.

while(1)
{
    Message = WaitForAMessage();
    switch(Message->ID) {
        case MSG_ID_1: HandleMessageID_1( Message );
        case MSG_ID_2: HandleMessageID_2( Message );
	.
	.
    }
}

Creating and sending a message passing is as simple as:

  • (Compile time) Define the structure of the message
  • (Compile time) Define a message identifier and associate it with the message structure.
  • (Run time) Create a new message
  • (Run time) Populate the message structure
  • (Run time) Send the message to the target task/thread

Messages can be created/sent from both thread and ISR context. ASF internally takes care of allocating necessary buffers for a messages and freeing them after the messages has been handled. The following sections provide detailed description of the various APIs and macros that are part of the ASF.

Creating a Task in ASF

1. Define your task in the system by simply using the following ASF macro:

ASF_TASK_STATIC (TaskId, EntryFunction, TaskPriority, TaskStackSize, QueueSize);
What Description
TaskId User defined C-Style identifier for the thread. E.g. MY_THREAD_ID. This identifier will be used in the application to access the thread properties and its associated queue.
EntryFunction Task entry function name.
TaskPriority Task priority in the range of 1 through 255. Higher value implies higher priority.
TaskStackSize Task’s stack size in bytes.
QueueSize This denotes the maximum number of messages that are allowed to be queued for the task. An optimum value should be chosen for this parameter to minimize memory wastage.
Example ASF_TASK_STATIC ( COMM_TASK_ID, CommTaskEntry, 55, 1024, 16 )
File App_Tasks.h

2. In a C file the task entry function must be defined in one the following ways:

ASF_TASK MyTaskEntryFunction( void )
{
    ...
}

3. It is not needed to declare the entry function elsewhere. This is done internally by the ASF.

All application tasks must be defined in the file App_Tasks.h in the Project<platform>\Sources\App directory. Examples are also provided in this header file.

Creating a Message

Creating a message is a two part process.

1. In App_MsgDef.h file, define the message type using the following macro:

ASF_MSG_DEF ( MsgId, MsgStructure msgStructVar )
What Description
MsgId User defined C-Style identifier for the message. E.g. RTC_READ_REQ. This identifier will be used in the application to access the message and its associated contents.
MsgStructure Type-defined structure name of the message structure.
MsgStructVar A C-style variable of the type MsgStructure. The MsgStructure – msgStructVar pair is used to define a message union of all message types used in the application.
Example ASF_MSG_DEF ( RTC_READ_CNF, RtcDateTime rtcDateTime )
File App_MsgDef.h (included by ASF_MsgDef.h

2. In AppMsgStruct.h file, define the message structure as a typedef struct.

Example:

typedef struct RtcDateTimeTag
{
      TaskId  srcTask;
      uint8_t    date;
      uint8_t    month;
      uint16_t   year;
      uint8_t    hour;
      uint8_t    minutes;
      uint8_t    seconds;
}  RtcDateTime;

Note that it is a good practice to include the TaskId of the message originator in the message structure so that the recipient can identify and respond to the sender of the message because the same message type may be sent by more than one tasks. App_MsgDef.h & AppMsgStruct.h files can be found Projects\<platform>\Sources\App folder.

Receiving a Message

1. In the task function, define a local (pointer) variable of type MessageBuffer. This variable must be initialized to NULLP.

2. Wait on the task’s internal queue to receive a message by calling the ASFReceiveMessage() API. The following example illustrates this.

Example:

#includeCommon.hASF_TASK MyTask ( void )
{
    MessageBuffer *newMsg = NULLP;
    TaskId  sender;

    while (1)
    {
        ASFReceiveMessage(MY_TASK_ID, &newMsg);

        switch (newMsg->msgId)
        {
            case RTC_READ_REQ:
                /* Process the message */
                sender = newMsg->msg.rtcDateTime.srcTask;
                /* Access other members of rtcDateTime message
                   structure in similar way */
                break;

            default:
                /* Unhandled/ unexpected messages */
                break;
        }
    }
}

Notes on the example:

  • ASFReceiveMessage() should always be called using the task id of the calling task. It would not work for other task ids.
  • All the messages in the application are declared inside a union called Message. Member msg of newMsg above points to this union and can map on to the message structure corresponding to the message type.

Messages can only be received within the context of a task

Sending a Message

1. In the task function, define a local variable of type MessageBuffer. This variable must be initialized to NULLP.

2. Call ASFCreateMessage() with message type, size of the message structure (corresponding to the message type) and pointer to the MessageBuffer variable created above. This will allocate the required amount of memory and attach it to the MessageBuffer variable. All this is transparent to the user.

3. Populate the MessageBuffer variable with the parameters corresponding to the message type (structure).

4. Send the message using ASFSendMessage() API call to the desired destination task. The following example illustrates this.

Example:

#includeCommon.hASF_TASK MyTask ( void )
{
    MessageBuffer *newMsg = NULLP;
    MessageBuffer *sendMsg = NULLP;

    while (1)
    {
        ASFReceiveMessage(MY_TASK_ID, &newMsg);

        switch (newMsg->msgId)
        {
            case RTC_READ_REQ:
                /* Create a response to the request */
                ASFCreateMessage(RTC_READ_CNF,
                                 sizeof(RtcDateTime),
                                 &sendMsg);

                /* Populate the message just created */
                sendMsg->msg.rtcDateTime.date = ucDate;
                sendMsg->msg.rtcDateTime.month = ucMonth;
                sendMsg->msg.rtcDateTime.year = usYear;
                sendMsg->msg.rtcDateTime.hour = ucHour;
                sendMsg->msg.rtcDateTime.minutes = ucMin;
                sendMsg->msg.rtcDateTime.seconds = ucSec;

                /* Send the message to the requesting task */
                ASFSendMessage(TARGET_TASK_ID, &sendMsg, CTX_THREAD);
                break;

            default:
                /* Unhandled/ unexpected messages */
                break;
        }
    }
}

Notes on the example:

  • In this example it is assumed that ucDate, ucMonth, usYear, etc. are global/static variables which contain the current date and time values.
  • After a message is sent, the sendMsg variable maybe reused to send another message. However, it needs to be initialized to NULLP again.
  • Messages can be sent within the context of a task or ISR. The context is auto detected by the ASF.

Non-blocking Message Receive

If the caller does not want to block on a queue until a message is received, it can use the non-blocking version of the ASFReceiveMessage API:

1. As with the ASFReceiveMessage(), define a local (pointer) variable of type MessageBuffer. This variable must be initialized to NULLP.

2. Check on the task’s internal queue to try and receive a message by calling the ASFReceiveMessagePoll() API. This API call will return true if message is received and false otherwise. The following example illustrates this:

Example:

#includeCommon.hASF_TASK MyTask ( void )
{
    MessageBuffer *newMsg = NULLP;
    TaskId  sender;

    while (1)
    {
        if (ASFReceiveMessagePoll(MY_TASK_ID, &newMsg))
        {
            switch (newMsg->msgId)
            {
                case RTC_READ_REQ:
                    /* Access other members of rtcDateTime message
                       structure as in previous example */
                    break;

                default:
                    /* Unhandled/ unexpected messages */
                    break;
            }
        }
        ... //Do other stuff in the meantime
    }
}

Notes on the example:

  • ASFReceiveMessagePoll() must be used exactly like the ASFReceiveMessage() API call. The only difference is that this call will not block the calling task.

Creating and Using a Timer

1. Define a variable of type AsfTimer. This variable must be initialized to NULL_TIMER.

2. Call ASFTimerStart() with calling task id, tick count and user reference value as shown in the example. In the task message loop handle the MSG_TIMER_EXPIRY along with other application messages. The following example illustrates this.

Example:

#includeCommon.hAsfTimer myTimer;
ASF_TASK MyTask ( void )
{
    MessageBuffer *newMsg = NULLP;
    ASFTimerStart(MY_TASK_ID, MY_TIMER_REF, MSEC_TO_TICS(100), &myTimer );

    while (1)
    {
        ASFReceiveMessage(MY_TASK_ID, &newMsg);

        switch (newMsg->msgId)
        {
            case RTC_READ_REQ:
                /* Process the message */
                sender = newMsg->msg.rtcDateTime.srcTask;
                /* Access other members of rtcDateTime message
                   structure in similar way */
                break;

            case MSG_TIMER_EXPIRY:
                if (newMsg->msg.msgTimerExpiry.userValue == MY_TIMER_REF)
                {
                    LED_On( BlinkLED );
                    ... //other timer related stuff
                }
                // Call again to repeat timer
                ASFTimerStart(MY_TASK_ID, MY_TIMER_REF, MSEC_TO_TICS(100), &myTimer
                             );
                break;

            default:
                /* Unhandled/ unexpected messages */
                break;
        }
    }
}

Notes on the example:

  • MY_TIMER_REF is a user defined constant value (16 bit) that can uniquely distinguish between multiple timers started within a task.
  • All timers are one-shot timers i.e. they will not repeat after expiring. To repeat a timer simply call the ASFTimerStart() again from the MSG_TIMER_EXPIRY handler code (as shown).
  • It is possible to kill or stop a timer before it expires. To do this call ASFTimerKill( &myTimer ) from within the task. This call is not supported from ISR context.
  • In the current implementation Timers cannot be started or stopped from an ISR.

Utility ASF Functions and Macros

  • Dx_printf() – This helper function implements the standard printf functionality via the serial communication port. Note that ‘x’ in Dx_printf can be 0, 1 or 2 where 0 always prints while 1 & 2 depends on the DEBUG_LVL definition. The message is limited to characters defined by DPRINTF_BUFF_SIZE. DEBUG_OUTPUT must be enabled in the build for this to be included. If you want the message to always display use D0_printf().
  • ASF_assert( condition ) – This macro can be used to assert that a logical condition be true for the proper functioning of the application. It prints the assert “expression” and corresponding line number and file name where the expression failed. It also causes the system to freeze or reboot (if RESET_ON_ASSERT is defined). Asserts must not be used where an error condition calls for proper handling or is not considered fatal for the application’s normal behavior. E.g.: APP_assert( pMyPointer != NULL );
  • ASF_assert_var( condition, var1, var2, var3 ) – This macro is similar to the APP_assert macro except that it also allows user to display upto 3 variables (upto 32-bit) for debugging purposes. Similarly ASF_assert_msg( condition, message ) allows a string message to be printed along with the condition expression.
  • See also the definition of the assert macros in the file Common.h. Note that the assert macros can be disabled if DEBUG_BUILD is not defined in the build file. However, it is recommended to always have asserts enabled.

Clone this wiki locally