CodeWarrior In our textbook, the Example 8.11 on page 394 is an example program
ID: 3696597 • Letter: C
Question
CodeWarrior
In our textbook, the Example 8.11 on page 394 is an example program using OC7 to control multiple output-compare functions. The code is developed in assembly language. Please develop a C-larnguage version to realize this function. One difference here is that intead of generating a 2-KHz signal on the PT0 pin, we will generate this signal on PT5 pin to drive the speaker on board.
The exact function is thus:
Use OC7 and OC5 together to generate a 2-KHz digital waveform with a 40 percent duty cycle on the PT5 pin to drive the speaker on board.
If the duty cycle is changed to 90 percent while the frequency is the same, could you hear any difference of the sound?
Explanation / Answer
Applications never call ISRs directly--ISRs run asynchronously in response to interrupt requests. To an application, each ISR appears to just start up on its own. Since no one calls the handler, no one can pass it arguments or accept its return value. Therefore, a handler in C or C++ must be a function with no parameters and a void return type. This implies that an interrupt handler in Standard C++ can't be an ordinary class member function, as in:
because members have an implicitly declared parameter named this. However, a handler could be a static class member function, because a static member function doesn't have an implicitly declared parameter.
On most platforms, you can't even implement a handler as an ordinary C or C++ function declared as:
because a handler doesn't observe the calling conventions of an ordinary function. Since a handler can interrupt the application almost anywhere during execution, it must be more cautious than other functions about disturbing the current processor state, particularly the processor registers. Thus, the entry code for a handler typically saves registers that ordinary functions don't. In addition, the machine code for returning from an ISR is usually different from the code for returning from a function call.
Since an ISR is unlike an ordinary function, you must either declare it using platform-specific language extensions, or write at least a portion of it in assembly language. For example, some C/C++ compilers for Intel x86 processors provide a keywordinterrupt or _ _interrupt for declaring interrupt handlers as in:2
For example, the Keil's CARM compiler for ARM processors provides a non-standard keyword __irq for declaring ISRs for IRQ type interrupts, as in:3
Without such extensions, you must write ISRs in another language, most likely assembler. You could write an entire ISR in assembler, but unless you're a masochist, you won't want to. Rather than completely handle the interrupt, the assembler routine can just save registers upon entry, call a C or C++ function to do the real work, restore registers, and exit via the appropriate return-from-interrupt instruction.
Filling interrupt vector elements
Some operating systems provide one or more functions for placing handler addresses into the interrupt vector. A call to such a function looks something like:
which stores the address of handler into the interrupt vector element at the specified offset.
If no such function is available, assigning a handler to an interrupt vector element is usually still pretty easy, but I've seen programmers do it more sloppily than necessary. Here's a quick and very dirty way to place the address of the functionIRQ_handler into the interrupt vector element corresponding to the IRQ interrupt on the ARM Evaluator-7T:
Although this technique works on this and other platforms, neither the C nor C++ standards guarantees that it will work. When the compiler sees this expression, it treats function name IRQ_handler as if it had type "pointer to function." CastingIRQ_handler to type void * has undefined behavior because a pointer to void should point only to data, not to functions. Rather than use void *, you should use a "pointer to function" type.
Recall that a handler is a function with an empty parameter list and a void return type. A typical declaration for a handler looks something like:
possibly with an additional platform-specific keyword or two. I recommend defining a typedef for a pointer that can point to a handler, such as:
Then you can place the address of the IRQ_handler function into the interrupt vector using:
which avoids undefined behavior.
As I explained in my last column, in C++ you should use a new-style cast, as in:1
Still, casts involving arbitrary addresses are cryptic at best and likely to be error-prone. Instead of writing one of these expressions for each interrupt type and interrupt vector element, you can treat the entire interrupt vector as a single memory-mapped object and apply the memory mapping techniques I presented last year.4 This method enables you to fill in the interrupt vector in a straightforward manner, using only one cast.
The interrupt vector is just a memory-mapped array of pointer_to_ISR at a particular base address. On the Evaluator-7T, that address is 0x20. You can define a pointer to the base of that array as a macro:
or as a constant object:
In C++, those casts should be reinterpret_casts.
You can use enumeration constants as symbolic names for the interrupt types:
Then you can fill in each interrupt vector element using a simple, easy-to-read expression, such as: