Skip to the content of the web site.

Laboratory 2

Based in part on the ECE 254 laboratory manual by Yiqing Huang.

This laboratory is an introduction to the Keil MCB1700 evaluation board, the LPC1768 microprocessor, and 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-RTXA real-time operating system.
RL-FlashFSA file system for dealing with standard Flash, RAM, and memory card devices.
RL-TCPnetA library allowing the microcontroller to communicate with the Internet through TCP/IP protocols.
RL-CANA library for interfacing with a Controller Area Network (CAN).
RL-USBA library for USB devices and USB hosting.

You can read more about these libraries in RL-ARM User's Guide.

RTX

There is no requirement to use an operating system on a microcontroller. It is entirely possible to author an embedded application that is entirely self contained; for example, by simply performing the required tasks in an infinite loop. In most cases, however, developing the tasks within the framework of a real-time operating system will simplify numerous issues including those dealing with scheduling, resources allocation, and communications.

We will look specifically at the RTX real-time operating system (RTOS) made available in the RL-RTX library. RTX stands for Real-time Executive. We will proceed in a number of steps:

  1. Look at an overview of the operating system.
  2. Develop a simple stand-alone Hello world application in the μVision4 (micro-Vision version 4) integrated development environment (IDE).
  3. Create an application that runs within the RL-RTX framework.
  4. Discuss the on-line documentation available.
  5. Build the RL-RTX kernel from source files.
  6. Create a multi-project workspace.
  7. Build the application using our version of the RTX.
  8. We will extend the kernel by defining an additional system call that will be accessed through a software interrupt (SVC).

The abbreviation SVC stands for Supervisor Call; that is, a call to a kernel function that will execute in supervisor mode.

1. The RL-RTX

To examine the Real-time Library Real-time Executive, We will look at two header files:

  • RTL.h
  • RTX_Conf_CM.c

1.1 RTL.h

The interface for communicating with 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:

  • RTX Kernel API
  • Flash File System API
  • TCPnet API

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:

  • Functions ARM
  • Functions Cortex-M

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:

  • Task management
  • Event flag management
  • Semaphore management
  • Mailbox management
  • Mutex management
  • Time management
  • User timer management
  • System functions
  • Fixed memory block management functions

We will look in depth at the first of these, task management.

1.1.1 Task Management

/* Task Management */
void       os_set_env    ( void );
void       rt_sys_init   ( void (*task)( void ), U8 priority, void *stk );
void       rt_tsk_pass   ( void );
OS_TID     rt_tsk_self   ( void );
OS_RESULT  rt_tsk_prio   ( OS_TID task_id, U8 new_prio );
OS_TID     rt_tsk_create ( void (*task)( void ), U8 priority, void *stk, void *argv );
OS_RESULT  rt_tsk_delete ( OS_TID task_id );

The first step in reading any header file is to determine the lingo:

os
operating system
rt
real time
tsk
task
env
environment
prio
priority
stk
stack
argv
argument vector

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.

1.2 RTX_Conf_CM.c

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:

  • Task Configuration
    • The maximum number of concurrently running tasks: from 0 to 250 with a default of 6.
    • The number of tasks that where the memory for the stack is provided by the user: from 0 to 250 with a default of 0.
    • The memory allocated by the system for the stack size of running tasks: from 20 to 4096 with a default of 512.
    • The system can also be required to check whether or not the stack overflows (that is, as a result of function calls, more memory is placed on the stack than is allocated by the system). By default, this is true, but this also reduces the performance of the kernel.
    • The system can be set up so that all tasks run in privileged (or supervisor) mode. By default, this is false.
  • Tick Timer Configuration
    • The system can be set up to us the core SysTick timer or a peripheral timer. By default, it is set to use core SysTick.
    • The frequency of the timer in Hz: 1 Hz to 1 GHz with a default of 6 MHz.
    • The timer tick value in microseconds: from 1 us to 1 s with a default of 10 ms.
  • System Configuration
    • Is round-robin task switching used, by default, true.
    • How many ticks a task executes before a task is switched out using the round-robin scheduling: from 1 to 1000 with a default of 5.
    • The maximum number of user timers that will run simultaneously with the clock: from 0 to 250 with a default of 0.
    • The size of the queue for the storage of requests by interrupt service routines (ISRs) when called by an interrupt handler: one of 4, 8, 12, 16, 24, 32, 48, 64, or 96 with a default of 16.

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.

1.2 RTX_lib.c

The RTX_lib.c file is broken into a number of sections:

  1. Definitions (macro declarations),
  2. Global variables,
  3. Tick Timer configuration (not for Cortex-M),
  4. RT Agent interface (again, not for Cortex-M),
  5. RTX Optimizations, and
  6. Standard library multithreading interface.

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; #endif

Question: why define a macro OS_TRV in the previous file and then define a constant here?

2. Hello world

We will now create an application that can be downloaded onto the Keil evaluation board that does not execute using an operating system. This application will run Hello world to the COM0 port which will be connected to the host computer.

Create a new project in μVision4 through Project→New μVision Project.... This will open the Create New Project dialog. The IDE will not accept any file or directory names with spaces, so it would be appropriate to choose a directory such as C:\Keil\mte241\Hello_world for this first application. The name of the application can be anything you choose, so Hello_world.uvproj is as appropriate as any.

The next dialog Select Device for Target 'Target 1'... requires you to select from the database NXP (founded by Philips)→LPC1768 as select OK. The next dialog box will as Copy 'startup_LPC17xx.s' to Project Folder and Add File to Project? Select Yes.

In the μVision4 IDE, expand the tree in the Project Window and rename Target 1 to something like LPC1768 and Source Group 1 to Startup Code, as shown in Figure 2.


Figure 2. Renaming the project target and group.

We will require three additional files, one header file and two source files:

uart_polling.h system_LPC17xx.c uart_polling.c

These can be downloaded from the Source Files link in the left-hand menu. The purpose of these files is:

system_LPC17xx.c
The CMSIS Cortex-M3 Device Peripheral Access Layer Source File for the NXP LPC17xx Device Series.
uart_polling.h
A header file describing the interface for polling the UART.
uart_polling.c
The source file of the UART interface.

Currently, your directory should appear as shown in Figure 3.


Figure 3. Source and project files.

While not required, it would be better if you moved the header and source files to a sub-directory src. In the Project window, right click on LPC1768 and add two more groups using New group... renaming them System Code and Source Code. Add the two C files above to the System Code group by right clicking on the name System Code and selecting Add Existing Files to Group 'System Code'.... Your Project window should now look like Figure 4.


Figure 4. Required course-provided source files.

Finally, you will create a file main.c in the Source Code group. Right click on the name Source Code and select Add New Item to Group 'Source Code'.... This will bring up the Add New Item to Group 'Source Code' dialog box. Select the C File icon, change the name to main.c and update the location to be the src sub-directory, as shown in Figure 5.


Figure 5. Adding a new source file to the group Source Code.

Now include the code

#include <LPC17xx.h>
#include "uart_polling.h"

int main() {
	SystemInit();

	uart_init( 0 );
	uart_put_string( 0, "Hello world!\n\r" );

	while ( 1 ) {
		// An embedded system does not terminate...
	}
}

Note: μVision4 requires a hard return after the last line in the document.

2.1 Building the project

At this point, we can build the project and download it to the Keil evaluation board. You have three options:

  • F7
  • Select Project→Build Target
  • Select the icon

The output of the compiler will appear in the Build Output. If you are successful, the output will be something like:

Build target 'LPC1768'
compiling uart_polling.c...
linking...
Program Size: Code=1180 RO-data=236 RW-data=4 ZI-data=612  
".\Hello_world.axf" - 0 Errors, 0 Warning(s).

If this is not the case, it will list the errors. Each error line will begin with the file where the error apparently occurs, for example

src\main.c(8): error:  ...

This example indicates the error is on line 8 and double clicking on the error message will bring you to line 8 in main.c.

2.2 Simulating

To simulate the execution of this program on the Keil evaluation board, we will use the debugger. To start or stop the debugger, you have three options:

  • Ctrl-F5
  • Select Debug→Start/Stop Debug Session
  • Select the icon

When you do so, a dialog box may appear with the text EVALUATION MODE Running with Code Size Limit: 32K. This indicates that you are using a version of μVision4 which does not have a full licence; consequently, there are some restrictions including the restriction that the code cannot be larger than 32 KiB—something that should not be an issue for MTE 241. Just select OK.

Having started the debug mode, we need to view the output of the UART. To do so, you have two options:

  • Select View→Serial Windows→UART #1
  • Select the icon

Note the ambiguity: COM0 is listened to by UART #1. You will note that UART #1 is one of the panels of the lower right window in μVision4.

To run the code, you have three options:

  • F5
  • Select Debug→Run
  • Select the icon

Having done so, you should see

Hello world!

appear in the UART #1 panel.

Note: because there is an infinite loop coded into the Hello world program, you can terminate the execution through one of two options:

  • Select Debug→Stop
  • Select the icon

Before you run it again, you should reset the microcontroller through one of two options:

  • Select Debug→Reset
  • Select the icon style="vertical-align:middle"

Later, we will discuss some of the other features of the debugger.

2.3 Downloading the code to flash memory

In order to download the compiled code to the flash memory of the microcontroller, you have two options:

  • Select Flash→Download
  • Select the icon

In the Build Output window, the following text will appear:

Load "C:\\Keil\\dwharder\\Hello_world.axf" 
Erase Done.
Programming Done.
Verify OK.

At this point, we must set up a terminal to listen to the serial port. One excellent and freely available terminal emulator is PuTTY. If it is not available on your computer, you can download the executable at Simon Tatham's web page. No installation is necessary: it is a self-contained executable which you can execute from where you have saved it. The icon for PuTTY is shown in Figure 6.


Figure 6. The PuTTY icon.

Having launched PuTTY, you are presented with the PuTTY Configuration dialog. Under session, select a Connection type of Serial and set the speed to 115200. The changes are shown in Figure 7.


Figure 7. The changes to PuTTY required to listen to the COM0 port.

Having opened up the terminal, you can now hit the RESET button on the Keil evaluation board shown in Figure 8.


Figure 8. The reset button on the Keil evaluation board.

This starts the execution of the program and the result is sent to the terminal, shown in Figure 9.


Figure 9. The output on the terminal.

3. Writing an application within the RTX framework

The previous Hello world application was a stand-alone program running on the Keil evaluation board without any underlying operating system. While such applications are possible, there are many reasons to build an application on top of an existing operating system: the scheduling of tasks is performed by the operating system based on priorities set by the programmer, and development and maintenance time and costs are reduced.

Create a new directory such as C:\Keil\mte241\RTX_hello_world and create a new project in μVision4 and create a src subdirectory. (Remember that you cannot have spaces in the path name.) In μVision4, change the target name Target 1 to something useful like LPC1768.

Having created the project, we must now indicate that we wish to use the RL-RTX for this project. There are three means to open the project target options:

  • Alt-F7
  • Select Project→Options for Target 'LPC1768'...
  • Select the icon

This will open the Options for Target 'LPC1768' dialog and it should be set to the panel corresponding to the Target tab. On this panel, you will note that currently, the operating system is identified as None. From this drop-down menu, select RTX Kernel, as shown in Figure 10.


Figure 10. Selecting the RTX Kernel as the operating system for this project.

This will signal to the compiler that the linker will include not only the object files for this project, but also the pre-compiled RL-RTX library.

Any application can now access the kernel by including the header file with the Application Programming Interface (API) for the RL-RTX, namely the RTL.h header file. Thus, your application will require the include directive

#include <RTL.h>

The location of this header file ARM\RV31\INC\ and the corresponding pre-compiled library is located in ARM\RV31\LIB\.

To continue with our project, we may need to configure the the RL-RTX. To do so, we need to modify the configuration file, RTX_Conf_CM.c. This file is found in the ARM\RL\RTX\Config directory. Include this file under a group named System Code and double click to open it in the editor, as shown in Figure 11. You will note that this file has been marked up for the configuration wizard.


Figure 11. The RTX_Conf_CM.c configuration file.

Select the Configuration Wizard tab below the editor and select the Expand All button. The Task stack size [bytes] should be set to 512 or 1024 bytes. For this project, 512 should be sufficient. We will also want the operating system to check for stack overflow: if we use more than 512 bytes on the stack, an error condition will be raised. The timer clock value should be set to 100 MHz or 100000000.

Note: you might notice the awkward interface where you can click on and change a number, but no visual cue is given that numbers or strings in the Value column can be set.

You should ensure that the values in the configuration wizard appear similar to that shown in Figure 12. You need only concern yourself with the entries discussed above.


Figure 12. Changing values in the configuration wizard for RTX_Conf_CM.c.

Like the previous Hello world project, we will need additional files:

system_LPC17xx.c uart_polling.h uart_polling.c Retarget.c

The first three can be copied from your previous project, and Retarget.c can be downloaded from the left-hand Source Files menu item. This file implements the low-level I/O functions that the C library functions such as printf will use to display characters to the output. Now, create a main.c file and ensure it is also placed into the src directory (when you Add New Item to Group 'Source Code', you need to append \src to the location). If you are not familiar with printf, see the relevant section under the Introduction to C.

Your Project directory should now look like Figure 13.


Figure 13. The updated Project window for the RTX hello world application.

You can now include the code for the new main.c:

/**
 * @file: main.c
 * @brief: Two simple tasks running pseduo-parallelly
 */

#include <LPC17xx.h>
#include <RTL.h>
#include <stdio.h>

#include "uart_polling.h"

__task void task1( void ) {
	unsigned int i = 0;

	for( ; ; i++ ) {
		printf( "Task 1: %d\n", i );
		os_dly_wait( 100 );
	}
}

__task void task2( void ) {
	while( 1 ) {
		printf( "Task 2: Hello world!\n" );
		os_dly_wait( 300 );
	}
}

__task void init( void ) {
	os_tsk_create( task1, 1 );   // task1 at priority 1 (higher)
	os_tsk_create( task2, 2 );   // task2 at priority 2
	os_tsk_delete_self();        // must delete itself before exiting
}

int main ( void ) {
	SystemInit();
	uart_init( 0 );
	os_sys_init( init );

	return 0;
}

As with the previous project, you can now build and download the code to the Keil evaluation board. You may want to reset your PuTTY terminal by right clicking on the title bar of the window, as shown in Figure 14. You can, of course, note other options that are available in PuTTY.


Figure 14. Resetting the PuTTY terminal.

Now, when you hit the RESET button on the Keil evaluation board, you should see output appearing as shown in Figure 15.


Figure 15. The terminal output of the Hello world! application.

Like any embedded application, this application is in an infinite loop.

4. Understanding what you've done

There are a number of functions used in our revised Hello world application. We can look up these functions on the web, but μVision4 also has a built-in manual. You can access the manual through two options:

  • Select Help→Open Books Window
  • At the bottom of the Program Window, there is a Books tab which you can click. If this tab is missing (the Books Window was closed), you can always re-open it with the menu selection above.

In this tab, you will be presented with a number of options, as shown in Figure 16.


Figure 16. Available references.

Many of these links will, unless there is a bug, open a browser and take you to the Keil web site; however, the most relevant, the Complete User's Guide Selection opens the ARM Development Tools window. The sections recommended by the laboratory instructor are highlighted: Writing Files, Debugging, and Function Reference. These sections are highlighted in Figure 17.


Figure 17. The Complete User's Guide Selection with recommended reading highlighted.

5. Building the RL-RTX kernel from source files

In the previous example, by specifying the operating system RTX Kernel in the Target Options..., you pass a directive onto the compiler to link in the pre-compiled RL-RTX library. It is possible, however, to compile the kernel from source code yourself. We will do so by copying the source files for the Cortex-M series of processors. These are located in the directory ARM\RL\RTX with further Cortex-M series specific source files in ARM\RL\RTX\SRC\CM. Within the top directory, we will require the Cortex-M series specific project files, shown in Figure 18, and the source files together with the Cortex-M3 specific Hardware Abstraction Layer source file in the sub-directory, shown in Figure 19.


Figure 18. The relevant files in ARM\RL\RTX.


Figure 18. The relevant files in ARM\RL\RTX\SRC\CM.

Create another directory such as C:\Keil\mte241\MyRTX (think MySQL, for example) for this third application. Within that directory, you will have to mirror the directory with a subdirectory such as C:\Keil\mte241\MyRTX\SRC and C:\Keil\mte241\MyRTX\SRC\CM. Copy the relevant files over and open the project in μVision4.

With the project open, you will have to remove the Hardware Abstraction Layer source files for the Cortex-M1 and Cortex-M4 processors, as shown in Figure 19.


Figure 19. Removing HAL_CM1.c and HAL_CM4.c.

From the Select Target drop-down box, you can select the target CM3_LE. We can also modify the components through the Components, Environment and Books dialog which can be accessed through one of the two options:

  • Select Project→Manage→Components, Environment, Books...
  • Select the icon

From this dialog box, you can delete the Project Targets not relevant to this application, as shown in Figure 20.


Figure 20. Deleting project targets through the Components, Environment and Books dialog.

We are now able to build the target files. After compilation, the output in the Build Output window should be

Build target 'CM3_LE'
compiling rt_Task.c...
compiling rt_System.c...
compiling rt_Event.c...
compiling rt_List.c...
compiling rt_Mailbox.c...
compiling rt_Semaphore.c...
compiling rt_Time.c...
compiling rt_Timer.c...
compiling rt_Mutex.c...
compiling rt_Robin.c...
compiling rt_MemBox.c...
assembling SVC_Table.s...
compiling HAL_CM3.c...
creating Library...
After Build - User command #1: cmd.exe /C copy ArInp.Scr CM3_LE\BuildLib.scr
        1 file(s) copied.
".\CM3_LE\RTX_CM3.lib" - 0 Errors, 0 Warning(s).

You will note the existence of the library file CM3_LE\RTX_CM3.lib. This contains your rebuilt RL-RTX library. In subsequent laboratories, you will be required to add further functionality to the kernel and, consequently, you will be modifying and adding to these source files.

Note: The "LE" stands for Little Endian, indicating that integers and other built-in datatypes are stored with the least-significant byte first. For example, the 32-bit number 1234567890 is represented in hex by 0x499602D2 (the 0x indicates that the following number is a hexadecimal number). Originally, and in big-endian processors today, such an integer would be stored as four bytes in the order 0x49, 0x96, 0x02, and 0xD2. However, for example, when adding two integers you need access to the least significant bytes first; consequently, it is more efficient to store the number in the reverse order with the least-significant byte first: 0xD2, 0x02, 0x96, and 0x49. Such storage is called Little Endian, for least-significant byte first.

Modifying the kernel

With the current setup, it is possible to modify the existing kernel functions; however, we will also be adding further functionality to the kernel, as well. Consequently, we must modify the interface, as well. To do this, we will copy the RTL.h file into our project, as well. The default location of the library is ARM\RV31\INC\RTL.h, so an appropriate location would parallel to the existing source directory: C:\Keil\mte241\MyRTX\INC; copy the file to this directory. Any project application using this soon-to-be-modified header file will have to use the include directive

#include "C:/Keil/mte241/MyRTX/INC/RTL.h"

You will note that we are using forward slash character instead of the Windows backslash character for separating directories. The preprocessor will continue to parse the directory structure correctly to find the file. You will recall that using angled brackets with an include directive causes the preprocessor to look at the header files installed with the compiler, while using double quotes will allow you to specify the location using either relative (for example, "../../MyRTX/INC/RTL.h") or absolute paths, as shown above.

Creating multi-project workspaces

For the remaining laboratories, you will be both making modifications to the kernel and creating applications that use the kernel; both testing your modifications to the kernel and testing your applications. Consequently, we will be using your kernel project in a number of other projects throughout the course. If we were to create individual projects, this would require you to either create a number of individual projects within one directory (confusing) or copy all the kernel code to a new project directory with each laboratory (error prone with unnecessary duplication). As an alternative, the μVision4 IDE allows you to create a workspace that spans multiple environments.

To create a joint project workspace, select Projects→New Multi-Project Workspace... from the menu. This will a Create New Multi Project Workspace dialog. (Note the inconsistency in the naming convention: "multi-project" versus "multi project".) It would be prudent to place this project into its own directory. From this point forward, we will use the naming convention:

Project_name
The name of the application we are writing for a particular laboratory.
MyRTX_project_name
The name of the multi-project workspace linking the project with the MyRTX project.

In this case, let us name the project MyRTX_hello_world. When you select Save, this brings up a new Create New Multi-Project Workspace dialog that allows you to add projects to this workspace. The interface is a touch confusing; however, you must select the New (Insert) icon, as shown in Figure 21.


Figure 21. First step in adding a new project into the workspace.

All this does is add a new region in the list of μVision Projects. To choose the projects, select the ... icon, as shown in Figure 22.


Figure 22. Bringing up a directory dialog to add a new project.

This brings up the μVision Project File dialog. Navigate to the MyRTX directory and include the RTX_Lib_CM.uvproj project. Repeat this process to add the RTX_hello_world.uvproj file. Your dialog box should look something like that shown in Figure 23.


Figure 23. Both projects have been added to this workspace.

The next step is to select the Active Project. This is the project containing the main.c function that will produce the executable file. In this case, highlight the RTX_hello_world.uvproj entry and select Set as Active Project and then select OK.

Note: To open a multi-project workspace, you must use Project→Open Project....

In the Project window, both projects appear below the WorkSpace folder. If you want to change the active project, just right-click on the project you wish to make active and select Set as Active Project, as shown in Figure 24.


Figure 24. Setting a project as active in the Project window.

The active project will be shown in reverse color in the Project window, as shown in Figure 25.


Figure 25. The active project displayed in the Project window.

At this point, we want to build all the projects. First, we have to change the file main.c in the RTX_hello_world project to use

#include "C:/Keil/mte241/MyRTX/INC/RTL.h"

In addition, we will have to unlink the vendor RTX kernel library and include our own:

  • First, launch the Target Options dialog and select None from the Operating System drop-down box.
  • Second, add a new group to the LPC1768 target for the RTX_hello_world project. Rename this to something more reasonable like MyRTX and then the existing library file C:\Keil\mte241\MyRTX\CM3_LE\RTX_CM3.lib to this group. You will have to change the Files of type selection to Library file (*.lib), as shown in Figure 26.


Figure 26. Selecting the RTX_CM3.lib file.

In order to convince yourself that you are using your updated code, I would suggest you make a small change to one of the printf to ensure that the change is propagated to the new binary code that you will download to the Keil evaluation board.

Building all projects in a multi-project workspace

Before we build all the projects in a multi-project environment, it is important to save all files, as μVision4 does not automatically save the files. You have two options:

  • Select File→Save All
  • Select the icon

In order to simultaneously build both the projects, you can use the Build Patch dialog which may be launched through one of two options:

  • Select Project→Batch Build...
  • Select the icon

This will bring up the Build Batch dialog and you can now select all the projects you wish to build, as shown in Figure 27.


Figure 27. Selecting all projects within the workspace to build.

As a general rule, you may want to clean up some of the intermediate files, so first select Clean, reopen the dialog and then select Build. Having done so, in the Build Output window, you should see something like:

*** Note: 'Create Batch File' is active - using 'Rebuild'.
Rebuild target 'CM3_LE'
compiling rt_Task.c...
compiling rt_System.c...
compiling rt_Event.c...
compiling rt_List.c...
compiling rt_Mailbox.c...
compiling rt_Semaphore.c...
compiling rt_Time.c...
compiling rt_Timer.c...
compiling rt_Mutex.c...
compiling rt_Robin.c...
compiling rt_MemBox.c...
assembling SVC_Table.s...
compiling HAL_CM3.c...
creating Library...
After Build - User command #1: cmd.exe /C copy ArInp.Scr CM3_LE\BuildLib.scr
        1 file(s) copied.
".\CM3_LE\RTX_CM3.lib" - 0 Errors, 0 Warning(s).
  
Build target 'LPC1768'
assembling startup_LPC17xx.s...
compiling RTX_Conf_CM.c...
compiling Retarget.c...
compiling uart_polling.c...
compiling system_LPC17xx.c...
compiling main.c...
linking...
Program Size: Code=5104 RO-data=276 RW-data=60 ZI-data=5444  
".\RTX_hello_world.axf" - 0 Errors, 0 Warning(s).

You can now download your new application using your own version of the RL-RTX onto the Keil evaluation board and watch it run.

Note: the term clean is generally used in development to mean removing intermediate and output files so that when the next build is made, it can be ensured that all files have been compiled from the current state of the source code.

Extending the kernel

To introduce new kernel functionality, two steps must be taken:

  • First, we must include a new line in the header file RyRTX\INC\RTL.h, and
  • Second, we must define this function in a file that is added to our MyRTX project.

To modify the RTL.h file, you will be adding these file in the Function Cortex-M section. As described above, these functions are defined up until the end of the #endif corresponding to the #else section of the conditional #if !(__TARGET_ARCH_6S_M || __TARGET_ARCH_7_M || __TARGET_ARCH_7E_M). It would be reasonable to give this section a comment that allows you to quickly find it in the file. Because this function will be defined in a different file, that is, externally, we will therefore us a signature such as

extern int       os_tsk_count_get( void );

You will note that the functions defined within RTL.h are defined within the Kernel group under RTX_Lib_CM→CM3_LE. Associate with each source file is a header file with the same prefix; for example, rt_Task.c has associated with it rt_Task.h located in the same directory. A full list of the correspoinding sections listed in RTL.h are provided in the following table:

rt_Task.*Task management; tasks functions and system start up.
rt_Event.*Event flag management; implements waits and wake-ups for event flags.
rt_Semaphore.*Semaphore management; implements binary and counting semaphores.
rt_Mailbox.*Mailbox management; implements waits and wake-ups for mailbox messages.
rt_Mutex.*Mutex management; implements mutex synchronization objects.
rt_Time.*Time management; delay and interval wait functions.
rt_Timer.*User timer management; user timer functions.
rt_System.*System functions; system task manager.
rt_MemBox.*Fixed memory block management functions, interface functions for fixed memory block management system.

Every operating system, however, requires additional functionality that is not necessarily exposed to the user through the interface. There are two additional header and source file pairs that are not included in the interface:

rt_List.*Functions for the management of different lists.
rt_Robin.*Round Robin Task switching.

Each file will most likely depend on others. Consequently, it is also possible to view the dependencies of each file—those files, if changed, will require a recompile of the current file. This list is shown in Figure 28. Inside of μVision4, it also shows include dependencies (that is, those header files on which a source file includes and therefore on which it depends). This is necessary, as a change to a header file will also require the recompilation of any source file that includes it.


Figure 28. The dependencies of the file rt_Task.h.

You will create two new files: rt_uwuserid_uwuserid.h and rt_uwuserid_uwuserid.c where the two uwuserids are the UW User IDs of the two students in the group. For example, the course instructor working with Carl Sagan would name the files rt_dwharder_cesagan.h and rt_dwharder_cesagan.c, respectively; however, for the balance of these laboratories, these files will be written only as rt_dwharder.h and rt_dwharder.c.

The easiest way to do this is to copy and rename two existing files—preferably shorter ones. First, edit these files, remove the function definitions that exist, update the include files in rt_dwharder.c to be

#include "rt_TypeDef.h"
#include "RTX_Config.h"
#include "rt_dwharder.h"

Under the list of Functions, define create a new section

/*------------------------- os_tsk_count_get --------------------------------*/

int os_tsk_count_get( void ) {
	/* Your code here... */
}

and create a simple function that, say, returns 42.

Having copied, renamed, and edited these files, you can now add the existing file rt_dwharder.h to the group Kernel. You can now rebuild the project and there should be no further errors; however, there should be one additional line in the Build Output:

compiling rt_dwharder.c...

Now, in RTX_hello_world file main.c, you can now include a call to your newly defined function and print its return value to the UART.

To be continued...