What's in Chapter 11?
Using #ifdef to implement conditional compilation
Using #include to load other software modules
Using #pragma to write interrupt software
C compilers incorporate a preprocessing phase that alters the
source code in various ways before passing it on for compiling.
Four capabilities are provided by this facility in C. They are:
- macro processing
inclusion of text from other files
conditional compiling
in-line assembly language
The preprocessor is controlled by directives which are not part
of the C language proper. Each directive begins with a #character and is written on a line by itself. Only the preprocessor
sees these directive lines since it deletes them from the code
stream after processing them.
Depending on the compiler, the preprocessor may be a separate
program or it may be integrated into the compiler itself. C has
an integrated preprocessor that operates at the front end of its
single pass algorithm.
Macro Processing
We use macros for three reasons. 1) To save time we can define
a macro for long sequences that we will need to repeat many times.
2) To clarify the meaning of the software we can define a macro
giving a symbolic name to a hard-to-understand sequence. The I/O
port #define macros are good examples of this reason. 3) To make
the software easy to change, we can define a macro such that changing
the macro definition, automatically updates the entire software.
#define Name CharacterString?...
define names which stand for arbitrary strings of text. After
such a definition, the preprocessor replaces each occurrence of
Name (except in string constants and character constants) in the source
text with CharacterString?.... As C implements this facility, the term macro is misleading,
since parameterized substitutions are not supported. That is,
CharacterString?... does not change from one substitution to another according to
parameters provided with Name in the source text.
C accepts macro definitions only at the global level.
The Name part of a macro definition must conform to the standard C naming
conventions as described in Chapter 2. CharacterString?... begins with the first printable character following Name and continues through the last printable character of the line
or until a comment is reached.
If CharacterString?... is missing, occurrences of Name are simply squeezed out of the text. Name matching is based on
the whole name (up to 8 characters); part of a name will not match.
Thus the directive
#define size 10
will change
short data[size];
into
short data[10];
but it will have no effect on
short data[size1];
Replacement is also performed on subsequent #define directives, so that new symbols may be defined in terms of preceding
ones.
The most common use of #define directives is to give meaningful names to constants; i.e., to
define so called manifest constants. However, we may replace a name with anything at all, a commonly
occurring expression or sequence of statements for instance. To
disable interrupt during a critical section we could implement.
#define START_CRITICAL asm(" tpa\n staa %SaveSP\n sei")
#define END_CRITICAL asm(" ldaa %SaveSP\n tap")
void function(void) {unsigned char SaveSP;
START_CRITICAL; /* make atomic, entering critical section
*/
/* we have exclusive access to global variables */
END_CRITICAL; /* end critical section */
}
This preprocessing feature lets us designate parts of a program
which may or may not be compiled depending on whether or not certain
symbols have been defined. In this way it is possible to write
into a program optional features which are chosen for inclusion
or exclusion by simply adding or removing #define directives at the beginning of the program.
When the preprocessor encounters
#ifdef Name
it looks to see if the designated name has been defined. If not,
it throws away the following source lines until it finds a matching
#else
or
#endif
directive. The #endif directive delimits the section of text controlled by #ifdef, and the #else directive permits us to split conditional text into true and
false parts. The first part (#ifdef...#else) is compiled only if the designated name is defined, and the
second (#else...#endif) only if it is not defined.
The converse of #ifdef is the
#ifndef Name
directive. This directive also takes matching #else and #endif directives. In this case, however, if the designated name is
not defined, then the first (#ifndef...#else) or only (#ifndef...#endif) section of text is compiled; otherwise, the second (#else...#endif), if present, is compiled.
Nesting of these directives is allowed; and there is no limit
on the depth of nesting. It is possible, for instance, to write
something like
#ifdef ABC
... /* ABC */
#ifndef DEF
... /* ABC and not DEF */
#else
... /* ABC and DEF */
#endif
... /* ABC */
#else
... /* not ABC */
#ifdef HIJ
... /* not ABC but HIJ */
#endif
... /* not ABC */
#endif
Listing 11.2: Examples on conditional compilation
where the ellipses represent conditionally compiled code, and
the comments indicate the conditions under which the various sections
of code are compiled.
A good application of conditional compilation is inserting debugging
instrumemts. In this example the only purpose of writing to PORTC
is assist in performance debugging. Once the system is debugged,we
can remove all the debugging code, simply by deleting the
#define Debug 1
line.#define Debug 1
int Sub(int j){ int i;
#ifdef Debug
PORTC|=0x01; /* PC0 set when Sub is entered */
#endif
i=j+1;
#ifdef Debug
PORTC&=~0x01; /* PC0 cleared when Sub is exited */
#endif
return(i);}
void Program(){ int i;
#ifdef Debug
PORTC|=0x02; /* PC1 set when Program is entered */
#endif
i=Sub(5);
while(1) { PORTC=2; i=Sub(i);}}
void ProgB(){ int i;
i=6;
#ifdef Debug
PORTC&=~0x02; /* PC1 cleared when Sub is exited */
#endif
}
Listing 11.3: Conditional compilation can help in removing all
debugging code
For more information about debugging see Chapter 2 of Embedded Microcomputer Systems: Real Time Interfacing.
The preprocessor also recognizes directives to include source
code from other files. The two directives
#include "Filename"
#include <Filename>
cause a designated file to be read as input to the compiler. The
difference between these two directives is where the compiler
looks for the file. The <filename> version will search for the
file in the standard include directory, while the "filename" version
will search for the file in the same directory as the original
source file. The preprocessor replaces these directives with the
contents of the designated files. When the files are exhausted,
normal processing resumes.
Filename follows the normal MS-DOS file specification format,
including drive, path, filename, and extension.
In Chapter 10, an example using #include was presented that implemented a feature
similar to encapsulated objects of C++, including private and
public functions.
The ICC11/ICC12 preprocessor also recognizes three pragma directives
that we will use to develop interrupt software. We use the
interrupt_handler
pragma to specify a function as an interrupt handler. The compiler
will then use the rti instruction rather than the rts instruction
to return from ExtHan
.#pragma interrupt_handler ExtHan()
void ExtHan(void){
KWIFJ=0x80; // clear flag
PutFifo(PORTJ&0x7F);}
Listing 11.4: Interrupt service routines are specified using a
pragma in ICC11/ICC12.
We use the
abs_address
and end_abs_address
pragmas to define the interrupt vector.#pragma abs_address:0xffdo
void (*KeyWakupJ_interrupt_vector[])() = {
ExtHan}; /* 812 KeyWakeUpJ */
#pragma end_abs_address
Listing 11.5: Pragmas allow us to define interrupt vectors in
ICC11/ICC12.
We also set the reset vector using the
abs_address
and end_abs_address
pragmas.extern void _start(); /* entry point in crt12.s */
#pragma abs_address:0xfffe
void (*Reset_interrupt_vectors[])() = {
_start }; /* fffe RESET, entry point into ICC12 */
#pragma end_abs_address
Listing 11.6: Pragmas allow us to define the reset vector in ICC11/ICC12.
We will not use pragmas to develop interrupt software with the
Metrowerks compiler. We use the
interrupt
key word to specify a function as an interrupt handler. The
Metrowerks
compiler will then use the rti instruction rather than the rts
instruction to return from ExtHan
. We start counting the interrupt number from reset. Some of the
interrupt numbers used by Metrowerks for the MC68HC812A4 are shown
in the following table.number | source |
24 | Key wakeup H |
23 | Key wakeup J |
20 | SCI0 |
16 | timer overflow |
15 | timer channel 7 |
8 | timer channel 0 |
6 | Key wakeup D |
4 | SWI software interrupt |
0 | reset |
Table 11-1: Interrupt numbers for the MC68HC812A4 used by
Metrowerks
0xFFD6 interrupt 20 SCI
0xFFDE interrupt 16 timer overflow
0xFFE0 interrupt 15 timer channel 7
0xFFE2 interrupt 14 timer channel 6
0xFFE4 interrupt 13 timer channel 5
0xFFE6 interrupt 12 timer channel 4
0xFFE8 interrupt 11 timer channel 3
0xFFEA interrupt 10 timer channel 2
0xFFEC interrupt 9 timer channel 1
0xFFEE interrupt 8 timer channel 0
0xFFF0 interrupt 7 real time interrupt
0xFFF6 interrupt 4 SWI software int
0xFFF8 interrupt 3 trap software int
0xFFFE interrupt 0 reset
0xFFDE interrupt 16 timer overflow
0xFFE0 interrupt 15 timer channel 7
0xFFE2 interrupt 14 timer channel 6
0xFFE4 interrupt 13 timer channel 5
0xFFE6 interrupt 12 timer channel 4
0xFFE8 interrupt 11 timer channel 3
0xFFEA interrupt 10 timer channel 2
0xFFEC interrupt 9 timer channel 1
0xFFEE interrupt 8 timer channel 0
0xFFF0 interrupt 7 real time interrupt
0xFFF6 interrupt 4 SWI software int
0xFFF8 interrupt 3 trap software int
0xFFFE interrupt 0 reset
Table 11-2: Interrupt numbers for the 9S12C32 used by Metrowerks
Metrowerks will automatically set the interrupt vector for KeyWakeup
J to point to the ExtHan routine.
void interrupt 23 ExtHan(void){
KWIFJ=0x80; // clear flag
PutFifo(PORTJ&0x7F);}
Listing 11.7: Interrupt service routines are specified in
Metrowerks.
We use the prm linker file to define the reset vector.
LINK keywake.abs
NAMES keywake.o start12s.o ansis.lib END
SECTIONS
MY_RAM = READ_WRITE 0x0800 TO 0x0AFF;
MY_ROM = READ_ONLY 0xF000 TO 0xFF00;
MY_STK = READ_WRITE 0x0B00 TO 0x0BFF;
PLACEMENT
DEFAULT_ROM INTO MY_ROM;
DEFAULT_RAM INTO MY_RAM;
SSTACK INTO MY_STK;
END
/* set reset vector to function _Startup defined in startup code
start12.c */
VECTOR ADDRESS 0xFFFE _Startup
Listing 11.8: The last line of the PRM linker file defines the
reset vector in Metrowerks.
For more information about interrupts see Chapter 4 of Embedded Microcomputer Systems: Real Time Interfacing.
No comments:
Post a Comment