Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

The insider's guide to the Philips ARM7-based microcontrollers (T. Martin, 2005)

.pdf
Скачиваний:
150
Добавлен:
12.08.2013
Размер:
6.1 Mб
Скачать

Introduction to the LPC2000

2 – Software Development

 

 

sources, depending on the operating mode of the LPC2000. (This is discussed more fully later on.) The NOP instruction is used to pad out the vector table at location 0x00000014 which is the location of the ‘missing’ vector. Again this location is used by the LPC2000 bootloader (discussed again later.) You are responsible for managing the vector table in the startup code as it is not done automatically by the compiler.

The startup code is also responsible for configuring the stack pointers for each of the operating modes.

The six on chip stack pointers (R13) are initialised at the top of on chip memory. Care must be taken to allocate enough memory for the maximum size of each stack

Since each operating mode has a unique R13 there are effectively six stacks in the ARM7. The strategy used by the compiler is to locate user variables from the start of the on-chip RAM and grow upwards. The stacks are located at the top of memory and grow downwards. The startup code enters each different mode of the ARM7 and loads each R13 with the starting address of the stack

Like the vector table you are responsible for configuring the stack size. This can be done by editing the startup code directly, however Keil provide a graphical editor that allows you to more easily configure the stack spaces. In addition the graphical editor allows you to configure some of the LPC2000 system peripherals. We will see these in more detail later but remember that they can be configured directly in the startup code.

Exercise 2: Startup code

The second exercise in the tutorial takes you through allocating space for each processor stack and examines the vector table.

30

Introduction to the LPC2000

2 – Software Development

 

 

Interworking ARM/THUMB Code

One of the most important things that we need to do in our application code is to interwork the ARM and THUMB instruction sets. In order to allow this interoperability, ARM have defined a standard called the ARM THUMB Procedure Call Standard ( ATPCS). The ATPCS defines among other things how functions call one another, how parameters are passed and how stacks are handled. The APCS adds a veneer of assembler code to support various compiler features. The more you use, the larger these veneers get. In theory the APCS allows code built in different toolsets to work together so that you can take a library compiled by a different compiler and use it with the Keil toolset.

The ARM procedure call standard defines how the user CPU registers should be used by compilers. Adhering to this standard allows interworking between different manufacturers tools

The APCS splits the register file into a number of regions: R0 to R3 are used for parameter passing between functions. If you need to pass more than 16 bytes then spilled parameters are passed via the stack. Local variables are allocated R4 – R11 and R12 is reserved as a memory location for the intra-call veneer code. In the Keil compiler all code is built for interworking and the global instruction set is the THUMB, so all code will be compiled as THUMB instructions (except for interrupt code which defaults to ARM.) This global default can be changed in the “Options for Target” menu. In the CC tab uncheck the “use THUMB code” box and the default instruction set will be ARM.

Introduction to the LPC2000

2 – Software Development

 

 

In addition the programmer can force a given function to be compiled as ARM or THUMB code. This is done with the two programming directives #Pragma ARM and #pragma THUMB as shown below. The main function is compiled as ARM code and calls a function called THUMB_function, (No prizes for guessing that this function is compiled in the 16 bit instruction set.)

#pragma ARM // Switch to ARM instructions

int main(void)

{

while(1)

{

THUMB_function(); //Call THUMB function

}

}

#pragma THUMB //Switch to THUMB instructions

void THUMB_function(void)

{

unsigned long i,delay;

for (i = 0x00010000;i < 0x01000000 ;i = i<<1)

//LED FLASHer

{

 

//simple delay loop

for (delay = 0;delay<0x000100000;delay++)

{

 

 

;

 

 

}

//Set the next led

IOSET1 = i;

}

 

 

}

 

 

It is also possible to declare individual functions as either ARM or THUMB functions by using the following declarations on the function prototype:

int ARM_FUNCTION ( int my_var) __THUMB

{

….

}

int THUMB_FUNCTION ( int my_var) __THUMB

{

….

}

Exercise 3: Interworking

The next exercise demonstrates setting up a project which interworks ARM and

THUMB code.

STDIO Libraries

The high-level, formatted IO functions in the STDIO library, such as printf and scanf, are directed at UART0 on the LPC2000. It is up to the programmer to initialise the UART to the correct BAUD rate. Once this is done it is possible to use these highlevel functions to stream data to a terminal program on a PC for example. The STDIO functions use two lowlevel drivers to send and receive a single character to the conio, the UART in this case. The two functions are called putchar and getchar and the source for them is available in serial.c in the Keil lib directory. By adding this file to your project the default library version is

32

Introduction to the LPC2000

2 – Software Development

 

 

ignored and the code in serial.c is used in its place. So, by rewriting the putchar and getchar routines, the high level printf and scanf function can be redirected to any IO device you want to use, such as an LCD and keypad. Bear in mind that the high level STDIO functions are quite bulky and should only be used if your application is very I/O driven.

Exercise 4: STDIO

This exercise demonstrates the low-level routines used by printf and scanf and configures them to read and write to the on-chip UART.

Accessing Peripherals

Once we have built some code and got it running on an LPC2000 device, it will at some point be necessary to access the special function registers (SFR) in the peripherals. As all the peripherals are memory-mapped, they can be accessed as normal memory locations. Each SFR location can be accessed by ‘hardwiring’ a volatile pointer to its memory location as shown below.

#define SFR

(*((volatile unsigned long *) 0xFFFFF000))

The Keil compiler comes with a set of include files which define all the SFR’s in the different LPC2000 variants. Just include the correct file and you can directly access

the peripheral SFR’s from your C code. The names of the include files are:

LPC21xx.h

LPC22xx.h

LPC210x.h

33

Introduction to the LPC2000

2 – Software Development

 

 

Interrupt Service Routines

In addition to accessing the on-chip peripherals, your C code will have to service interrupt requests. It is possible to convert a standard function into an ISR, as shown below:

void fiqint (void) __fiq

 

{

= 0x00FF0000;

// Set the LED pins

IOSET1

EXTINT

= 0x00000002;

// Clear the peripheral interrupt flag

}

 

 

The keyword __fiq defines the function as a fast interrupt request service routine and so will use the correct return mechanism. Other types of interrupt are supported by the keywords __IRQ, __SWI, __ABORT.

As well as declaring a C function as an interrupt routine, you must link the interrupt vector to the function.

Vectors:

LDR

PC,Reset_Addr

 

 

LDR

PC,Undef_Addr

 

 

LDR

PC,SWI_Addr

 

 

LDR

PC,PAbt_Addr

 

 

LDR

PC,DAbt_Addr

/* Reserved Vector */

;

NOP

PC,IRQ_Addr

LDR

/* Vector from VicVectAddr */

 

LDR

PC,[PC, #-0x0FF0]

 

LDR

PC,FIQ_Addr

 

Reset_Addr:

DD

Reset_Handler

 

Undef_Addr:

DD

Undef_Handler?A

 

SWI_Addr:

DD

SWI_Handler?A

 

PAbt_Addr:

DD

PAbt_Handler?A

 

DAbt_Addr:

DD

DAbt_Handler?A

/* Reserved Address */

IRQ_Addr:

DD

0

DD

IRQ_Handler?A

 

FIQ_Addr:

DD

FIQ_Handler?A

 

The vector table is in two parts. First there is the physical vector table, which has a Load Register Instruction (LDR) on each vector. This loads the contents of a 32-bit wide memory location into the PC, forcing a jump to any location within the processor’s address space. These values are held in the second half of the vector table, or the constants table which follows immediately after the vector table. This means that the complete vector table takes the first 64 bytes of memory. The Keil startup code contains predefined names for the Interrupt Service Routines (ISR). You can link your ISR functions to each interrupt vector by using the same name as your C function name. The table below shows the constants table symbols and the corresponding C function prototypes which should be used.

Exception source

Constants table

C function prototype

 

Undefined Instruction

Undef_Handler?A

void Undef_Handler (void)

__abort

Prefetch Abort

PAbt_Handler?A

void Pabt_Handler (void)

__abort

Data Abort

DAbt_Handler?A

void Dabt_Handler (void)

__abort

Fast Interrupt

FIQ_Handler?A

void FIQ_Handler (void)

__fiq

The SWI and IRQ exceptions are special cases, as we will see later. The ?A is used to tell the linker that the corresponding function should be compiled with the ARM instruction set ?T is used for the THUMB instruction set. Only the IRQ and FIQ interrupt sources can be disabled. The protection exceptions (Undefined instruction, Prefetch Abort, and Data abort)

34

Introduction to the LPC2000

2 – Software Development

 

 

are always enabled. Consequently these exceptions must always be trapped. If you do not declare a corresponding C function for these interrupt sources, then the compiler will default to using a tight loop to trap any entry to these exceptions.

Pabt_Handler:

B Pabt Handler

Default handling of exceptions for which no C function has been declared

Exercise 5: Exception Handling

In this exercise we configure a C routine to be a simple interrupt and see it working in the debugger. Later on we will see how the LPC2000 hardware is configured to service interrupts.

35

Introduction to the LPC2000

2 – Software Development

 

 

Software Interrupt

The Software Interrupt exception is a special case. As we have seen, it is possible to encode an integer into the unused portion of the SWI opcode.

#define SWIcall2

asm{ swi#2}

However, in the Keil CA ARM compiler, there is a more elegant method of handling software interrupts. A function can be defined as a software interrupt by using the following non ANSI keyword adjacent to the function prototype:

int Syscall2 (int pattern) __swi(2)

{

……….

}

In addition the assembler file SWI_VEC.S must be included as part of the project.

Now when a call is made to the function an SWI instruction is used, causing the processor to enter the supervisor privileged mode and execute the code in the SWI_VEC.S file. This code determines which function has been called and handles the necessary parameter passing. This mechanism makes it very easy to take advantage of the exception structure of the ARM7 processor and to partition code which is non-critical code running in user mode, or privileged code such as a BIOS or operating system. In the tutorial section we will take a closer look at how this works.

Exercise 6: Software Interrupt

The SWI support in the Keil compiler is demonstrated in this example. You can easily partition code to run in either the user mode or in supervisor mode.

Locating Code In RAM

As we shall see later, the main performance bottleneck for the ARM7 CPU is fetching the instructions to execute from the FLASH memory. The LPC2000 has special hardware to solve this problem for the on-chip FLASH. However if you are running from external FLASH you are stuck with the access time of the external FLASH. One trick is to boot the executable code into fast RAM and then run from this RAM. This means that you need to compile position-independent code which can be copied into the RAM, or compile code so that it runs in the RAM and is loaded by a separate bootloader program. Both of these solutions will work, but require extra effort to develop. Fortunately the Keil compiler has a directive which defines a function as a RAM function. The startup code will copy the function into RAM and the linker will resolve all calls to it as being located in the defined RAM area. The function declaration is shown below

int

RAM_FUNCTION (int my_VAR) __ram

{

….

}

It is also necessary to define which section of memory will be used to hold these functions. This is done by declaring a section of the RAM as executable RAM or ERAM. This declaration makes use of the classes directive to allocate a region of RAM to contain all the executable RAM functions.

36

Introduction to the LPC2000

2 – Software Development

 

 

The basic syntax is shown below:

ERAM ( 0x40000000 – 0x40000FFF)

This entry should be made in the LA Locate dialogue of the options for target menu.

The compiler does not check if your RAM function is calling functions or library functions which are not also stored in the RAM. So if your “fast “RAM function makes calls to a maths routine stored in the FLASH memory, you may not get the performance you were expecting. This method of locating functions in RAM is not only simple and easy to use, it has the added advantage that the linker knows where the function will finally end up and can place the debug symbols at the correct address. This will give you not only a ROMable image which will run standalone, but also an image which can be debugged.

Inline Functions

It is also possible to increase the performance of your code by inlining your functions. The inline keyword can be applied to any function as shown below

void NoSubroutine (void) __inline

{

}

When the inline keyword is used the function will not be coded as a subroutine, but the function code will be inserted at the point where the function is called, each time it is called. This removes the prologue and epilogue code which is necessary for a subroutine, making its execution time faster. However, you are duplicating the function every time it is called, so it is expensive in terms of your FLASH memory.

37

Introduction to the LPC2000

2 – Software Development

 

 

Operating System Support

If you are using an operating system for the LPC2000, the OS is likely to take care of the system stacks and context switching. To avoid duplicating this by the compiler, it is possible to declare a function as a task within the operating system. This causes the compiler to just translate the code within the function and not to add the normal prologue and epilogue code which saves and restores registers to the stack. A function may be declared as a task as shown below

void AnalogueSample(void)

__task

{

 

….

 

}

 

Fixing Objects At Absolute Locations

The compiler also allows you to fix any C object, such as a variable or a function at any absolute memory location. The compiler has an extension to the C language as shown below

int

checksum

__at 0x40000000;

Variables declared using this keyword cannot be initialised by the startup code. You must also be careful to fix variables on the correct boundaries, or you will get a memory abort. (For example if an integer is located at an uneven memory address.)

Inline Assembler

The compiler also allows you to use ARM or THUMB Assembler instructions within a C file. This can be done as shown below:

__asm { mov r15,r2; }

This can be useful if you need to use features which are not supported by the C language, for example the MRS and MSR instructions.

38

Introduction to the LPC2000

2 – Software Development

 

 

Hardware Debugging Tools

Philips have designed the LPC2000 to have the maximum on-chip debug support. There are several levels of support. The simplest is a JTAG debug port. This port allows you to connect to the LPC2000 from the PC for a debug session. The JTAG interface allows you to have basic run control of the chip. That is, you can single step lines of code, run halt and set breakpoints and also view variables and memory locations once the code is halted.

Debug support on the LPC2000 includes a JTAG port for Flash programming and basic run control debugging.

In addition, Philips has included the ARM embedded trace module. The embedded trace module provides much more powerful debugging options and real time trace, code coverage, triggering and performance analysis toolsets. In addition to more advanced debug tools, the ETM allows extensive code verification and software testing which is just not possible with a simple JTAG interface. If you are designing for safety critical applications, this is a very important consideration.

In addition to the JTAG port Philips have included the ARM ETM module for high end debugging tools

The final on-chip debug feature is the Real Time Monitor. This is a kernel of code which is resident in a reserved area of memory. During a debug session the debugger can start the real monitor via the JTAG port. The real monitor can be used to provide “on the fly” updates as your code is running. This process is pseudo real time in that the real monitor code interrupts your code and uses some processor time to read and communicate debug information to the PC.

39