This describes the real-time libraries that come with the Keil MCB1700 evaluation board, specifically discussing the Keil RL-RTX (Real-Time eXecutive) operating system. Keil provides the RL-ARM real-time library; a collection of routines, data-structures, and tools designed to operate in a real-time environment. The available libraries include:
RL-RTX | A real-time operating system. |
RL-FlashFS | A file system for dealing with standard Flash, RAM, and memory card devices. |
RL-TCPnet | A library allowing the microcontroller to communicate with the Internet through TCP/IP protocols. |
RL-CAN | A library for interfacing with a Controller Area Network (CAN). |
RL-USB | A library for USB devices and USB hosting. |
You can read more about these libraries in RL-ARM User's Guide. We will focus on the real-time operating system.
The purpose of an operating system is to contain the core functionality to manage and allocate the resources of the system. A real-time operating system is one that performs this within specified time constraints. In most cases, developing the tasks within the framework of a real-time operating system will simplify numerous issues including those dealing with scheduling (processor managment), memory, resources and communications (resource management).
To examine the Real-time Library Real-time Executive, We will look at two header files:
The interface for communicating with the core RTOS functionality (the RTX kernel) is defined in the RTL.h header file. This file may, at first, seem daunting—you are no doubt asking yourself what everything means. However, a closer inspection reveals some organization in the file.
The first block of code are macros (using #define) and type definitions (using typedef).
The division of the balance of the code, however, leaves something to be desired. The only means of differentiating between major headers and minor headers is that the major headers are centered:
The first section, that on the RTX Kernel API, begins with a number of type definitions and macros specific to the kernel API. We will look at this section, as this is relevant to the material in this course. The RTX Kernel API is followed by two subsections:
You will note that surrounding these two is the pre-processor directives
#if !(__TARGET_ARCH_6S_M || __TARGET_ARCH_7_M || __TARGET_ARCH_7E_M) /*---------------------------------------------------------------------------- * Functions ARM *---------------------------------------------------------------------------*/ ... #else /*---------------------------------------------------------------------------- * Functions Cortex-M *---------------------------------------------------------------------------*/ ... #endif
As the architecture we're working on the Cortex-M3, we need only concern ourselves with the second half.
The functions are divided according to the tasks they perform:
We will look in depth at the first of these, task management.
Inspecting the header file, we see a number of functions related to task management (all starting with rt_tsk_).
/* Task Management */ #define os_sys_init( tsk ) os_sys_init0(tsk, 0, NULL ) #define os_sys_init_prio( tsk, prio ) os_sys_init0(tsk, prio, NULL ) #define os_sys_init_user( tsk, prio, stk, size ) \ os_sys_init0(tsk, prio|(size << 8), stk ) #define os_tsk_create( tsk, prio ) os_tsk_create0(tsk, prio, NULL, NULL ) #define os_tsk_create_user( tsk, prio, stk, size ) \ os_tsk_create0( tsk, prio|(size << 8), stk, NULL ) #define os_tsk_create_ex( tsk, prio, argv ) os_tsk_create_ex0(tsk, prio, NULL, argv ) #define os_tsk_create_user_ex( tsk, prio, stk, size, argv ) \ os_tsk_create_ex0( tsk, prio|(size << 8), stk, argv ) #define os_tsk_delete_self() os_tsk_delete( 0 ) #define os_tsk_prio_self( prio ) os_tsk_prio( 0, prio ) #define isr_tsk_get() os_tsk_self() extern void os_sys_init0( void (*task)(void), U32 prio_stksz, void *stk ); extern OS_TID os_tsk_create0( void (*task)(void), U32 prio_stksz, void *stk, void *argv ); extern OS_TID os_tsk_create_ex0( void (*task)(void *), U32 prio_stksz, void *stk, void *argv ); extern OS_TID os_tsk_self( void ); extern void os_tsk_pass( void ); extern OS_RESULT os_tsk_prio( OS_TID task_id, U8 new_prio ); extern OS_RESULT os_tsk_delete( OS_TID task_id );
You can read more about these at the Keil Real-time Library Reference.
The first step in reading any header file is to determine the lingo:
Looking back up in the file, OS_TID and OS_RESULT are defined to be unsigned 32-bit integer ( U32). The argument void (*task)( void ) may seem odd: to understand such parameters, you should visit cdecl.org which allows you type in a C or C++ declaration and it will give you an English language explanation; in this case, "declare task as pointer to function (void) returning void".
What follows are a number of macros that simplify tasks:
#define os_sys_init( tsk ) os_set_env(); \ _os_sys_init( (U32)rt_sys_init, tsk, 0, NULL ) #define os_sys_init_user( tsk, prio, stk, size ) \ os_set_env(); \ _os_sys_init( (U32)rt_sys_init, tsk, prio|(size << 8), stk ) #define os_sys_init_prio( tsk, prio ) \ os_set_env(); \ _os_sys_init( (U32)rt_sys_init, tsk, prio, NULL ) #define os_tsk_create( tsk, prio ) _os_tsk_create( (U32)rt_tsk_create, tsk, prio, NULL, NULL ) #define os_tsk_create_user( tsk, prio, stk, size ) \ _os_tsk_create( (U32)rt_tsk_create, tsk, prio|(size << 8), stk, NULL ) #define os_tsk_create_ex( tsk, prio, argv ) \ _os_tsk_create_ex( (U32)rt_tsk_create, tsk, prio, NULL, argv ) #define os_tsk_create_user_ex( tsk, prio, stk, size, argv ) \ _os_tsk_create_ex( (U32)rt_tsk_create, tsk, prio|(size << 8 ), stk, argv ) #define os_tsk_self() _os_tsk_self( (U32)rt_tsk_self ) #define os_tsk_pass() _os_tsk_pass( (U32)rt_tsk_pass ) #define os_tsk_prio( task_id, new_prio ) \ _os_tsk_prio( (U32)rt_tsk_prio, task_id, new_prio ) #define os_tsk_prio_self( prio ) _os_tsk_prio( (U32)rt_tsk_prio, 0, prio ) #define os_tsk_delete( task_id ) _os_tsk_delete( (U32)rt_tsk_delete, task_id ) #define os_tsk_delete_self() _os_tsk_delete( (U32)rt_tsk_delete, 0 ) #define isr_tsk_get() rt_tsk_self()
and this is followed by additional functions:
void _os_sys_init ( U32 p, void (*task)( void ), U32 prio_stksz, void *stk ) __SVC_0; OS_TID _os_tsk_create ( U32 p, void (*task)( void ), U32 prio_stksz, void *stk, void *argv ) __SVC_0; OS_TID _os_tsk_create_ex ( U32 p, void (*task)( void * ), U32 prio_stksz, void *stk, void *argv ) __SVC_0; OS_TID _os_tsk_self ( U32 p ) __SVC_0; void _os_tsk_pass ( U32 p ) __SVC_0; OS_RESULT _os_tsk_prio ( U32 p, OS_TID task_id, U8 new_prio ) __SVC_0; OS_RESULT _os_tsk_delete ( U32 p, OS_TID task_id ) __SVC_0;
First, the __SVC_0 at the end of each line appears confusing—this is not standard C. If you look above, it doesn't help, as we see that it appears to be a macro:
#define __SVC_0 __svc_indirect(0)
It becomes even more confusing until you find a file armcc_intr.h. This file is one that is included automatically with all compilations—it is not necessary to include this file explicitly. The name comes from C-compiler intrinsic functions, that is, a function made available for a programming language where the implementation is handled by the compiler.
In this file, we see that __svc_indirect(x) is a macro:
#define __svc_indirect(x)
That is, it is a null.
In the declaration of functions, it is not necessary to give parameters names; it is only necessary to give the type. However, giving parameter names is often useful in describing the purpose of the parameter. Thus, the type of the parameter informs the compiler while the name of the parameter informs the programmer.
The real-time executive configuration file for the Cortex-M series of processors is given in the file RTX_Conf_CM.c. This begins with a number of user-configurable components. You will notice that the comments are marked up: this allows the user to modify these values using the configuration wizard (we will look at this later). The user-configurable parameters are:
You will note that the comments preceding each of these configurable declarations has included in it a number of markups: <h>, <e>, <i>, and <o>. These are markups that the μVision4 IDE will use to generate a Configuration Wizard associated with the configurable parameters. This wizard is shown in Figure 1.
Figure 1. The configuration wizard for the file RTX_Conf_CM.c, accessible by selecting the Configuration Wizard tab
underneath the file content.
Question: Immediately following the part of the file for RTX user configuration, there is the symbol OS_TRV defined to be a much more complex expression. What does this evaluate to?
The description of this value is that it specifies the timer reload value for the peripheral timer. Peripheral timer counts up to a reload value, then overflows to 0, and then generates a tick interrupt. The reload value should be calculated to generate the desired interval length (for example 10 ms). Based on this definition, what is the purpose of the -1 in the macro?
You can read more about configuration macros in the RL-ARM User's Guide at the Keil web site.
Following this section, five functions are defined:
__task void os_idle_demon ( void ); int os_tick_init ( void ); void os_tick_irqack ( void ); void os_tmr_call ( U16 info ); void os_error ( U32 err_code );
You will see that at the bottom of the RTX_Conf_CM.c file is the inclusion of another source file, RTX_lib.c, which we will discuss next.
The RTX_lib.c file is broken into a number of sections:
The macros are, for the most part, alternate variations of function calls—perhaps to maintain some form of backward compatibility or perhaps to adhere to a specific interface.
Under the global variables, we see a macro just seen above:
#if (__CM__) U32 const os_trv = OS_TRV; #endifQuestion: why define a macro OS_TRV in the previous file and then define a constant here?