manufacturer of I/O-rich SBCs, operator interfaces, handheld instruments, and development tools for embedded control low cost single board computers, embedded controllers, and operator interfaces for scientific instruments & industrial control development tools for embedded control order our low cost I/O-rich embedded control products embedded controller manufacturer profile single board computers & embedded controllers development tools & starter kits for your embedded design operator interfaces with touchscreens and graphical user interface plug-in expansion modules for digital & analog I/O C language & Forth language integrated development tools, IDE single board and embedded computer accessories embedded controller enclosures, bezels, environmental gaskets

QCard C User Guide

Table of Contents

PART 1 GETTING STARTED

Chapter 1: Getting to Know Your QCard Controller

Chapter 2: Using Your PowerDock

Chapter 3: Your First Program

PART 2 PROGRAMMING THE QCARD CONTROLLER

Chapter 4: The IDE: Writing, Compiling, Downloading and Debugging

Chapter 5: Making Effective Use of Memory

Chapter 6: Real Time Programming

The Timeslicer and Task Switching

Using Interrupt Service Routines (ISRs)

Interrupt Recognition and Servicing

External Hardware Interrupts /IRQ and /XIRQ

Routines that Temporarily Disable Interrupts

Writing Interrupt Service Routines

An Example: Periodically Calling a Specified Function

Calling Kernel Functions From Within ISRs

Chapter 7: Failure and Run-Time Error Recovery

PART 3 COMMUNICATIONS, MEASUREMENT, AND CONTROL

Chapter 8: Digital and Timer-Controlled I/O

Chapter 9: Data Acquisition Using Analog to Digital Conversion

Chapter 10: Serial Communications

Chapter 11: The Battery-Backed Real-Time Clock

PART 4 PUTTING IT ALL TOGETHER

Chapter 12: A Turnkeyed Application

PART 5 REFERENCE DATA

Appendix A: QCard Electrical Specifications

Appendix B: Connector Pinouts

Appendix C: Schematics (zip)

Chapter 6

<< Previous | Next>>

Real Time Programming

This chapter provides an introduction to real time programming.  You’ll learn: 

About the timeslice clock and how to use it;

All about interrupts, and how to use them to respond to events.

Intro: Interrupts, Multitasking and Timely Services

Kernel Services

Autostart, initialization, orderly shutdown

Autostarting (from QED Software Manual)

New C #include Files to Set and Clear the Priority Autostart

Two new include-able files are now available in the \fabius\include\mosaic directory to simplify the setting or clearing of the PRIORITY.AUTOSTART vector.  Simply including the file named

SET_AUTO.H 

in the application's C source code file causes MAIN to be automatically executed each time the QED Board is powered up or restarted.  This is equivalent to typing

CFA.FOR MAIN PRIORITY.AUTOSTART

from the terminal after the *.txt file has been sent to the QED Board, as explained in the documentation (for example, in the Turnkey Application Program chapter of the Getting Started With C manual).

Similarly, including the file named

CLR_AUTO.H 

in the application's C source code file erases the priority autostart vector at the top of page 4.   As before, the priority autostart may also be erased by typing

NO.AUTOSTART

from the terminal.

The names of these files are:

    \fabius\include\mosaic\set_auto.h

     \fabius\include\mosaic\clr_auto.h

These files are included in the latest versions of the GUI (Graphical User Interface) Toolkit and the ATA Flash Card Software Package.  Contact Mosaic Industries for more information.

Changes in Priority Autostart

To the programmer, the functionality of priority autostart is the same as before.  It allows a specified function to be automatically executed each time the QED Board powers up or resets.   In the QED-FLASH Board, the C functions PriorityAutostart() and NoAutostart() and the corresponding Forth functions PRIORITY.AUTOSTART and NO.AUTOSTART are now "flash-smart".  These routines check whether the DIP switches 3 and 4 are both asserted (which indicates that flash is in socket S1).  If so, they write or erase the autostart pattern at the top of page 4 in flash.  Note that an autostart routine should not include a call to PriorityAutostart() or PRIORITY.AUTOSTART.  This would cause the autostart pattern to be written to flash upon each startup or reset, which might eventually exceed the 10,000 write/erase cycle limit of the flash chip.]]]

The Timeslicer and Task Switching

The Built-In Elapsed Time Clock

The QCard Controller’s multitasking executive maintains an elapsed time clock whenever the timeslicer is active.  Please consult the TIMEKEEP.C program in the \MOSAIC\DEMOS _ AND _ DRIVERS\MISC\C EXAMPLES directory for examples of using the elapsed time clock. 

Your program can start the timeslice clock by calling the function:

 

StartTimeslicer()

The timeslicer increments the long variable named TIMESLICE _ COUNT each timeslice period.  The default timeslice period is 5 milliseconds (ms), and this can be modified by calling

 

ChangeTaskerPeriod()

as described in the Control-C Glossary.  The function

 

InitElapsedTime()

sets TIMESLICE _ COUNT equal to zero.  The function

 

ReadElapsedSeconds()

returns a long result representing the number of elapsed seconds since InitElapsedTime() was called.

To attain the full 5 millisecond resolution of the elapsed time counter, we can write a simple function that converts the TIMESLICE _ COUNT into elapsed seconds as well as the number of milliseconds since the last integral second.  For example, let’s examine some code from the TIMEKEEP.C file in the \MOSAIC\DEMOS _ AND _ DRIVERS\MISC\C EXAMPLES directory:

 

#define DEFAULT_TIMESLICE_PERIOD    5   // {ms}; system default

#define MS_PER_SECOND  1000

static long start_time;                 // saves starting count of TIMESLICE_COUNT

_Q void MarkTime(void)

{   start_time = TIMESLICE_COUNT;

}

_Q void PrintElapsedTime(void)

{   long elapsed_ms =

DEFAULT_TIMESLICE_PERIOD*(TIMESLICE_COUNT - start_time);

long seconds = elapsed_ms / MS_PER_SECOND;

int ms_after_second = elapsed_ms % MS_PER_SECOND;

printf(“\nTime since mark is: %ld seconds and %d ms.\n”,

seconds, ms_after_second);

}

The MarkTime() function simply stores the TIMESLICE _ COUNT in the start _ time variable.  The timeslicer is continually incrementing its counter, and when you later call PrintElapsedTime(), start _ time is subtracted from the latest TIMESLICE _ COUNT and multiplied by the timeslice period to calculate the elapsed number of milliseconds.  This is converted into the elapsed seconds by dividing by 1000, and the remainder is the number of milliseconds since the last integral elapsed second.

To try it out, use the Mosaic IDE’s editor to open the TIMEKEEP.C file in the \MOSAIC\DEMOS _ AND _ DRIVERS\MISC\C EXAMPLES directory, click on the Make Tool to compile the program, and use the terminal to send TIMEKEEP.DLF to the QCard. At your terminal, type:

main

to start the timeslicer and initialize the program.  Now at any time you can mark a starting time by typing at the terminal:

MarkTime( )

and you can print the elapsed seconds and ms since the last mark by typing:

PrintElapsedTime( )

which will produce a response of the form:

Time since mark is: 3 seconds and 45 ms.

 

[[[User Areas

Initializing and Preserving Variables]]]

Using Interrupt Service Routines (ISRs)

The on-chip resources of the 68HC11 include an A/D converter, timer system, pulse accumulator, watchdog timer, serial communications port, high speed serial peripheral interface, and general purpose digital I/O.  The 68HC11’s 21 interrupts can enhance the performance of these facilities.  Interrupts allow rapid response to time-critical events that often occur in measurement and control applications. For example, you can use an interrupt to create a pulse-width modulated (PWM) output signal.

[[[The on-chip resources of the 68HC11 include an A/D converter, timer system, pulse accumulator, watchdog timer, serial communications port, high speed serial peripheral interface, and general purpose digital I/O.  The 68HC11’s interrupts can enhance the performance of these resources.  Interrupts allow rapid response to time-critical events that often occur in real-time measurement and control applications.

In this section we describe how interrupts are implemented on the 68HC11, and how interrupt service routines are simplified by built-in kernel routines.  Using an interrupt requires four simple steps:

0. Name the registers related to the interrupt using the REGISTER: utility.  These register names are used to code the interrupt handler.

0. Use QED-Forth or assembly code to define an interrupt handler routine which will be executed every time the interrupt occurs.  The interrupt handler must reset the interrupt request flag and perform any necessary actions to service the interrupt event.

0. Install the interrupt handler using the QED-Forth word ATTACH.

0. Write utility words to enable and disable the interrupt.

The summary at the end of this chapter presents a more detailed version of these steps.]]]

Interrupt Recognition and Servicing

68HC11 interrupts fall into two main categories: nonmaskable and maskable.  

Maskable Interrupts

Maskable interrupts may be freely enabled and disabled by software. They may be generated as a result of a variety of events, including signal level changes on a pin, completion of predetermined time intervals, overflows of special counting registers, and communications events.

Recognition and servicing of maskable interrupts are controlled by a global interrupt enable bit (the I bit in the condition code register) and a set of local interrupt mask bits in the hardware control registers.  If a local interrupt mask bit is not enabled, then the interrupt is “masked” and will not be recognized.  If the relevant local mask bit is enabled and the interrupt event occurs, the interrupt is recognized and its interrupt flag bit is set to indicate that the interrupt is pending.  It is serviced when and if the global interrupt bit (the I bit) is enabled.  An interrupt that is not in one of these states is inactive; it may be disabled, or enabled and waiting for a triggering event.

When an interrupt is both recognized and serviced, the processor pushes the programming registers onto the stack to save the machine state, and automatically globally disables all maskable interrupts by setting the I bit in the condition code register until the service routine is over. Other maskable interrupts can become pending during this time, but will not be serviced until interrupts are again globally enabled when the service routine ends.  (The programmer can also explicitly re-enable global interrupts inside an interrupt service routine to allow nesting of interrupts, but this is not recommended in multitasking applications).  Non-maskable interrupts (reset, clock monitor failure, COP failure, illegal opcode, software interrupt, and XIRQ) are serviced regardless of the state of the I bit.

When an interrupt is serviced, execution of the main program is halted.  The programming registers (CCR, ACCD, IX, IY, PC) and the current page are pushed onto the return stack.  This saves the state of execution of the main program at the moment the interrupt became serviceable. Next, the processor automatically sets the I bit in the condition code register.  This disables interrupts to prevent the servicing of other maskable interrupts.  The processor then fetches an address from the “interrupt vector” associated with the recognized interrupt, and starts executing the code at the specified address.  It is the programmer’s responsibility to ensure that a valid interrupt service routine, or “interrupt handler” is stored at the address pointed to by the interrupt vector. 

The CPU then executes the appropriate interrupt handler routine.  It is the programmer’s responsibility to ensure that a valid interrupt service routine is stored at the address pointed to by the interrupt vector.  The interrupt vectors are near the top of memory in the onboard ROM.  The ROM revectors the interrupts (using jump instructions) to point to specified locations in the EEPROM.  The ATTACH() routine installs a call to the interrupt service routine at the appropriate location in the EEPROM so that the programmer’s specified service function is automatically executed when the interrupt is serviced.  ATTACH() also supplies the required RTI (return from interrupt) instruction that unstacks the programming registers and resumes execution of the previously executing program.

An interrupt handler must reset the interrupt flag bit (not the mask bit) by writing a 1 to it, and perform any tasks necessary to service the interrupt.  When the interrupt handler has finished, it executes the RTI (return from interrupt) assembly code instruction.  RTI restores the CPU registers to their prior values based on the contents saved on the return stack.  As explained below, this also re-enables interrupts by clearing the I bit in the CCR (condition code register).  Thus other interrupts can be serviced after the current interrupt service routine has completed.

Nonmaskable Interrupts

Six of the 68HC11F1’s 21 interrupts are nonmaskable, meaning that they are serviced regardless of the state of the global interrupt mask (the I bit in the CCR).  Events that cause nonmaskable interrupts include resets, clock monitor failure (triggered when the E-clock frequency drops below 10 kHz), Computer-Operating-Properly (COP) failure (triggered when a programmer-specified timeout condition has occurred), execution of illegal opcodes, execution of the SWI (software interrupt) instruction, and an active low signal on the nonmaskable interrupt request pin named /XIRQ. 

Three types of interrupts initiate a hardware reset of the 68HC11:

  Power-on or activation of the reset button

  Computer-Operating-Properly (COP) timeout

  Clock monitor failure

These are the highest priority interrupts, and are nonmaskable.  Serviced immediately, they initialize the hardware registers and then execute a specified interrupt service routine.  QED-Forth sets the interrupt vectors of these interrupts so that they execute the standard startup sequence.  The service routines for all but the main reset interrupt may be changed by the programmer with the Attach() utility.

If a nonmaskable interrupt is enabled, it is serviced immediately upon being recognized.  The importance of these interrupts is reflected by the fact that most cause a hardware reset when serviced [[[(please see the “Program Development Techniques” chapter in the QED Software Manual for a discussion of the difference between a hardware reset and the accompanying software restart)]]]. The following table gives the name of each nonmaskable interrupt and a description of its operation.  They are listed in order of priority from highest to lowest:

Table 6‑1      Nonmaskable Interrupts.

Interrupt Name

Description

Reset

Recognized when the /RESET (active-low reset) pin is pulled low, this highest priority nonmaskable interrupt resets the machine immediately upon recognition and executes the standard QED-Forth restart sequence.

Clock Monitor Failure

Enabled or disabled via the CME (clock monitor enable) bit in the OPTION register, this interrupt is recognized if the E-clock frequency drops below 10 kHz.  It resets the processor hardware and executes a user-defined service routine.  QED-Forth installs a default service routine for this interrupt that performs the standard restart sequence.

COP Failure

After enabling the computer operating properly (COP) subsystem, failure to update COP registers within a predetermined timeout period triggers this interrupt which resets the processor and executes a user-defined service routine.  QED-Forth installs a default service routine for this interrupt that performs the standard restart sequence.

Illegal Opcode Trap

This interrupt occurs when the processor encounters an unknown opcode.  QED-Forth installs a default service routine for this interrupt that performs the standard restart sequence.

SWI

Software interrupts are triggered by execution of the SWI opcode.  After being recognized, an SWI interrupt is always the next interrupt serviced provided that no reset, clock monitor, COP, or illegal opcode interrupt occurs.  SWI requires a user-installed interrupt handler.

/XIRQ

Enabled by clearing the X bit in the condition code register, an /XIRQ interrupt is recognized when the /XIRQ (active-low nonmaskable interrupt) pin is pulled low.  This interrupt is serviced immediately upon recognition.  It requires an appropriate user-installed interrupt handler.

The service routine for the reset interrupt cannot be modified by the programmer.  The service routines for the clock monitor, COP failure, and illegal opcode trap interrupts are initialized to perform the restart sequence, but this action may be changed by the programmer (see the Glossary entry for InitVitalIRQsOnCold() for more details).  No default actions are installed for the SWI and /XIRQ interrupts, so before invoking these interrupts the user should install an appropriate interrupt service routine using the ATTACH() command.

[[[Chapter 8 ("External Interrupts, Resets, Operating Modes, and the COP"), describes the Reset, Clock Monitor, COP, /XIRQ, and IRQ interrupts in more detail.]]]

Servicing Maskable Interrupts

Maskable interrupts are controlled by the I bit in the condition code register (M68HC11 Reference Manual, mc68hc11rm.rev4.1.pdf, Sections.5.7 and 5.8). When the I bit is set, interrupts are disabled, and maskable interrupts cannot be serviced.  When clear, interrupts can be serviced, with the highest priority pending interrupt being serviced first.  In sum, a locally enabled maskable interrupt is serviced if:

it has been recognized, and

it has the highest priority, and

the I bit in the condition code register is clear.

If a maskable interrupt meets these criteria, the following steps are taken to service it.  First, the programming registers and page are automatically saved on the return stack.  Note that the condition code register, CCR, is one of the registers saved, and that the saved value of the I bit in the CCR is 0.  Next, the CPU automatically sets the I bit to 1 to temporarily prevent maskable interrupts from being serviced.  Control is then passed to the interrupt handler code, which you must provide and post using ATTACH(). The interrupt handler clears the interrupt flag bit set by the trigger event and performs any tasks necessary to service the interrupt.  It terminates with an RTI instruction which restores the saved values to the programming registers.  Execution then resumes where it left off.

Recall that when the interrupt service began, the processor’s first action was to store the programming registers on the return stack.  At that time, the I bit in the CCR equaled 0 indicating that interrupts were enabled, and the bit was stored as 0 on the return stack.  After stacking the machine state, the processor set the I bit to disable interrupts during the service routine.  When the programming registers are restored to their prior values by RTI, note that the I bit is restored to its prior cleared state, indicating that interrupts are again enabled.  In this manner the processor automatically disables interrupts when entering a service routine, and re-enables interrupts when exiting a service routine so that other pending interrupts can be serviced.

Nested Interrupts

While the programmer can explicitly clear the I bit inside an interrupt service routine to allow nesting of interrupts, this is not recommended as it can cause crashes in multitasking applications. 

Interrupt Priority

Multiple pending interrupts are serviced in the order determined by their priority.  Interrupts have a fixed priority, except that the programmer may elevate one interrupt to have the highest priority using the HIPRIO register.  Nonmaskable interrupts always have the highest priority when they are recognized, and are immediately serviced. The following table lists the fifteen available maskable interrupts in order of highest to lowest priority:

Table 6‑2      Maskable Interrupts, from Highest to Lowest Priority.

Interrupt Name

Description

/IRQ

/IRQ is an active-low external hardware interrupt which is recognized when the signal on the /IRQ pin of the 68HC11 is pulled low (MC68HC11F1 Technical Data Manual, p.2-5).

Real Time Interrupt

The RTI provides a programmable periodic interrupt (MC68HC11F1 Technical Data Manual, p.5-5).

Input Capture 1

An IC1 interrupt is recognized when a specified signal transition is sensed on port A, pin 2 (MC68HC11F1 Technical Data Manual, p.9-1 ff.).

Input Capture 2

An IC2 interrupt is recognized when a specified signal transition is sensed on port A, pin 1 (MC68HC11F1 Technical Data Manual, p.9-1 ff.).

Input Capture 3

An IC3 interrupt is recognized when a specified signal transition is sensed on port A, pin 0 (MC68HC11F1 Technical Data Manual, p.9-1 ff.).

Output Compare 1

An OC1 interrupt is recognized when the main timer’s count becomes equal to OC1’s timer compare register (MC68HC11F1 Technical Data Manual,p.9-6 ff.).

Output Compare 2

An OC2 interrupt is recognized when the main timer’s count becomes equal to OC2’s timer compare register (MC68HC11F1 Technical Data Manual,p.9-6 ff.).

Output Compare 3

An OC3 interrupt is recognized when the main timer’s count becomes equal to OC3’s timer compare register (MC68HC11F1 Technical Data Manual, p.9-6 ff.).

Output Compare 4

An OC4 interrupt is recognized when the main timer’s count becomes equal to OC4’s timer compare register (MC68HC11F1 Technical Data Manual, p.9-6 ff.).

I4O5

Depending on its configuration, an I4O5 (input capture 4/output compare 5) interrupt is recognized when a specified signal transition is sensed on port A, pin 3, or when I4O5’s timer compare register is equal to the main timer’s count (MC68HC11F1 Technical Data Manual, p.9-1 ff.).

Timer Overflow

A TOF interrupt occurs when the free-running count in the TCNT (timer count) register overflows from FFFFH to 0000H (MC68HC11F1 Technical Data Manual, p.9-1).

Pulse Accum Overflow

A PAOVF interrupt occurs when the PACNT (pulse accumulator count) register overflows from FFH to 00H (MC68HC11F1 Technical Data Manual, p.9-15 ff.).

Pulse Accum Edge

A PEDGE interrupt occurs after a signal edge is detected on port A, pin 7 (MC68HC11F1 Technical Data Manual, p.9-15 ff.).

SPI Event Interrupt

An SPI (serial peripheral interface) interrupt occurs after a byte transfer is completed, or a write collision or a mode fault is detected (MC68HC11F1 Technical Data Manual, p.8-1 ff.).

SCI Event Interrupt

An SCI (serial communications interface) interrupt occurs when the transmit data register is empty, or the transmission is complete, or the receive data register is full, or an idle line is detected.  The handler must determine which of these four events caused the interrupt (M68HC11 Reference Manual, Section 9.5.2).

Elevated Priority

After being recognized, a locally enabled maskable interrupt will be serviced when the I bit is clear, and when it has the highest priority among the pending interrupts.  Note that interrupts are not necessarily serviced in the order in which they are recognized, but in order of priority among those pending.

You can elevate one maskable interrupt at a time to receive highest priority servicing.  This is accomplished by configuring four priority-selection bits named PSEL0, PSEL1, PSEL2, and PSEL3 located in the HPRIO (high priority) register (MC68HC11F1 Technical Data Manual, p.5-7).  The default highest priority maskable interrupt is /IRQ.  A table in MC68HC11F1 Technical Data Manual, p.5-8 lists the states of the priority selection bits needed to elevate an interrupt’s status to the highest priority. 

After selecting the appropriate code for an interrupt, the following word can be used to implement its priority status change:

Listing 60 Enter your Listing Caption here.

HEX

803C  REGISTER:   HPRIO

 

: ELEVATE.INTERRUPT.PRIORITY ( interrupt.id.according.to. MC68HC11F1 Technical Data Manual,.p6-16 -- )

   0F HPRIO CLEAR.BITS \ clear 4 PSEL bits

   HPRIO SET.BITS    \ desired interrupt gets highest priority

;

For example, to elevate the RTI (real time interrupt) which has a priority code of 07H to have the highest priority, execute:

 

07 ELEVATE.INTERRUPT.PRIORITY

Interrupt Flag and Mask Bits

Each maskable interrupt is enabled and disabled by a local mask bit.  An interrupt is enabled when its local mask bit is set.  When an interrupt’s trigger event occurs, the processor sets the interrupt’s flag bit.

The local mask bit should be used to enable and disable individual interrupts.  In general, you should avoid setting the global I bit in the condition code register (CCR) using DISABLE _ INTERRUPTS() unless you are sure that you want to disable all interrupts.  Time-critical interrupt service routines such as the timesliced multitasker cannot perform their functions when interrupts are globally disabled.

Some of the QCard Controller’s library functions globally disable interrupts for short periods to facilitate multitasking and access to shared resources.  A list of these functions is presented in the Control-C Glossary document.

[[[Some QED-Forth routines globally disable interrupts for short periods to facilitate multitasking and access to shared resources.  These routines are summarized in the “Interrupts and Register Initializations” chapter of the QED Software Manual.]]]

Interrupt trigger events can occur whether or not the interrupt is enabled.  For this reason, it is common for flag bits to be set before an interrupt is ready to be used.  Unless an interrupt’s flag bit is cleared before it is enabled, setting the local mask bit will force the system to recognize an interrupt immediately.  Unfortunately, the event which set the interrupt’s flag bit occurred at an unknown time before the interrupt was enabled.  Depending on the interrupt handler’s task, this can cause erratic initial behavior, collection of an incorrect initial data point, or begin an improper sequence of events (for example, cause a phase shift in an output waveform).  To avoid these problems, it is recommended that you enable an interrupt by first clearing its flag bit and then immediately setting its mask bit. 

!   An Interrupt Flag Bit Is Cleared By Writing a 1 to it   !

Although mask bits can be set and cleared by storing the desired value in them, flag bits are unusual.  Since flag bits are set by trigger events, it is not possible to set them via software.  In order to clear an interrupt flag bit, a logical one must be stored into the flag bit’s location – that clears it to zero!

To clear a specified flag bit, write a pattern to the flag register with a 1 in the bit position of the flag that must be cleared.  All of the other flag bits in the flag register then remain unchanged.  See M68HC11 Reference Manual Section 10.4.4 for examples.

External Hardware Interrupts /IRQ and /XIRQ

Two external interrupts, /IRQ (active-low interrupt request) and /XIRQ (active-low nonmaskable interrupt request) allow external hardware to interrupt the 68HC11F1 (M68HC11 Reference Manual, Section 2.4.6). The / prefix to each of these names indicates that the signals are active-low. Pull-up resistors on the QCard Controller hold these signals high during normal operation, and an interrupt is recognized when either signal is pulled low by an external source.  The /IRQ input is maskable and is not serviced unless the I bit in the condition code register is clear.  If the CPU is servicing an interrupt when the /IRQ line goes low, the external interrupt will not be recognized until the interrupt being serviced has been handled.  Unlike all the other maskable interrupts, /IRQ does not have a local interrupt mask.  The /XIRQ external interrupt is not available on the QCard.

The /IRQ pin is accessed and controlled via the Wildcard Port Header (for pin locations see Appendix A).  It operates as an active-low input to the processor.  An external device can drive the line LOW to signal an interrupt.  Alternatively, several open-collector devices can be wired together on the same line, so that any one of them can interrupt the processor by pulling the request line low.  This is called “wired-or” operation.  In either case, the external device must pull the line low long enough to be detected by the CPU. 

Note that the PORTA input capture lines can also be configured to interrupt the processor when an external event occurs.

Configuring /IRQ Interrupts

In its default state, after each reset or restart, the /IRQ pin is configured as an edge-triggered input.  In this mode, the 68HC11 latches the falling edge, causing an interrupt to be recognized.  This frees peripheral devices from having to hold the /IRQ line low until the CPU senses the interrupt, and prevents multiple servicing of a single external event. 

The disadvantage of this configuration is that multiple edge-triggered interrupts cannot be reliably detected when used with wired-OR interrupt sources.  If you are using multiple wire-or /IRQ inputs, you can specify level-sensitive interrupt recognition by clearing a bit named IRQE (IRQ edge-sensitive) in the OPTION register (M68HC11 Reference Manual, Section 5.8.1).  IRQE is a “protected bit” in OPTION that must be written within the first 64 E cycles after a reset.  The QED-Forth word InstallRegisterInits() (described in the glossary) may be used to specify a value that is automatically stored into OPTION upon each reset.

Using /IRQ

To use the /IRQ external interrupt, define an interrupt handler and install it using the pre-defined identifier IRQ.ID and the interrupt Attach utility, as described in the Glossary entry for Attach.

In QED-Forth you would execute:

CFA.FOR <name of your /IRQ handler>   IRQ.ID  ATTACH

If interrupts have not yet been enabled globally, then execute:

ENABLE.INTERRUPTS

ENABLE _ INTERRUPTS

Whenever /IRQ is pulled low, your interrupt handler will be executed.  Note that there is no local interrupt mask for the /IRQ interrupt, so your interrupt handler routine need not clear an interrupt request flag.

CFA.FOR <name of your /XIRQ handler>  XIRQ.ID   ATTACH

The kernel word ATTACH expects the 32-bit extended code field address (xcfa) of your service routine under an interrupt identifier on the stack, and it sets up the interrupt vector in EEPROM so that subsequent interrupts will execute the specified service routine.  The code installed by ATTACH includes the RTI instruction that terminates the interrupt service sequence. 

For example, if you define a word called TIMER.SERVICE to respond to the timer output compare #4 interrupt, you simply execute

 

CFA.FOR TIMER.SERVICE OC4.ID  ATTACH

to vector the output compare 4 interrupt so that it will call the TIMER.SERVICE routine.

Note that the OC2 interrupt is used as the multitasker’s timeslice clock.  Before using this interrupt for another purpose, make sure that you don’t need the services provided by the timeslicer which supports the multitasking executive, the elapsed time clock, and the BENCHMARK: utility (see the glossary entry for START.TIMESLICER).

Routines that Temporarily Disable Interrupts

Certain kernel routines temporarily disable interrupts by setting the I bit in the condition code register.  These routines are summarized in the “Words that Disable Interrupts”“Library Functions that Disable Interrupts” chapter of the QED-ForthControl-C Glossary.  A review of that list will assist you in planning the time-critical aspects of your application.

Interrupt Latency

The time required between the processor’s initiation of interrupt servicing and the execution of the first byte of the specified service routine is called the interrupt latency.  Most of the 68HC11’s interrupts have an inherent latency of 12 machine cycles during which the registers are saved on the return stack and the interrupt vector is fetched.  This corresponds to 3 microseconds (µs).  QED-Forth’s interrupt latency is longer because the interrupts are re-vectored via the EEPROM to allow the programmer to modify the vectors, and because the page must be changed.  The latency of service routines installed with ATTACH() is 34 machine cycles, or 8.5 µs.  That is, the first opcode of the user’s service routine is executed 8.5 µs after interrupt service begins.  After the service routine’s concluding RTS executes, an additional 20 cycles (5 µs) lapses before the originally interrupted program resumes execution.  12 of these cycles are accounted for by the RTI instruction, and the other 8 cycles are required to restore the original page.

Interrupt Latency

         Time to Enter an Interrupt Service Routine:  8.5 µsec
       Time to Leave an Interrupt Service Routine:  5.0 µsec

Writing Interrupt Service Routines

Maskable interrupts have a local mask bit which enables and disables the interrupt, and a flag bit which is set when a trigger event occurs.  For maskable interrupts, an interrupt triggering event is recognized when the flag and mask bits are both set.  In order to avoid premature recognition of a maskable interrupt, it should be enabled by first clearing its flag bit and then setting its mask bit.  Once an interrupt has been recognized, it will be serviced if it is not masked by the I bit in the CCR.  Multiple pending interrupts are serviced in the order determined by their priority. Interrupts have a fixed priority, except that the programmer may elevate one interrupt to have the highest priority.  Nonmaskable interrupts always have the highest priority when they are recognized, and are immediately serviced.

When an interrupt is serviced, the machine state (specified by the programming registers) is saved and the I bit in the CCR register is set.  This prevents other pending interrupts from being serviced. The CPU then executes the appropriate interrupt handler routine.  The interrupt handler is responsible for clearing the interrupt flag bit.  For most interrupts this is accomplished by writing a one to the flag bit.  After completing its tasks, the interrupt handler executes an RTI instruction to restore the machine state, subsequently clearing the I bit in the CCR.  The CPU is now ready to service the next, highest priority, pending interrupt.  If there is none, processing of the main program continues.

To use interrupts you need to create and post an interrupt service routine using the ATTACH() macro.  We’ll look at this process in detail, then discuss how interrupts are implemented on the 68HC11.

To use an interrupt to respond to events, follow these four steps:

    1. Use #define to name all required bit masks related to servicing the interrupt, and look in the Motorola 68HC11F1 documentation and the QEDREGS.H file (in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory) to find the names of all registers that relate to the interrupt.  These bit mask and register names will simplify the creation of a readable service routine.

    2. Use C or assembly code to define an interrupt service routine which will be executed every time the interrupt occurs.  The function must have a void stack picture; it cannot return a value or expect input parameters.  This function must reset the interrupt request flag (by writing a 1 to it!) and perform any necessary actions to service the interrupt event.  Note that the service routine is a standard function; it is not defined using the _ interrupt keyword.

    3. Write a function that installs the interrupt service routine using the ATTACH() command.  ATTACH() initializes the interrupt vector in EEPROM to call the specified service routine, and ATTACH() also supplies the RTI (return from interrupt) instruction that correctly terminates the service routine.

    4. Write functions to enable and disable the interrupt.  Enabling the interrupt is accomplished by clearing the interrupt’s flag bit by writing a 1 to it, and then setting its mask bit.  It may also be necessary to clear the I bit in the CCR to globally enable interrupts.  This can be accomplished by executing ENABLE _ INTERRUPTS().

ATTACH() Makes It Simple

It is easy to define an interrupt service routine and ATTACH it to a specified interrupt.  You define your service routine in either assembly code or in high level C.  Thus the service routine can be debugged just like any other C function.  You then call ATTACH() to bind the service routine to the interrupt.

The following constants have been defined as identifiers for the 68HC11 interrupts in the INTERUPT.H file in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory:

Table 6‑3      68HC11 Interrupts.

Identifier

Interrupt description

SCI _ ID

Serial communications interface

SPI _ ID

Serial peripheral interface

PULSE _ EDGE _ ID

Pulse accumulator edge detection

PULSE _ OVERFLOW _ ID

Pulse accumulator overflow

TIMER _ OVERFLOW _ ID

Timer overflow

IC4 _ OC5 _ ID

Timer input capture 4/output compare 5

OC4 _ ID

Timer output compare 4

OC3 _ ID

Timer output compare 3

OC2 _ ID

Timer output compare 2

OC1 _ ID

Timer output compare 1

IC3 _ ID

Timer input capture 3

IC2 _ ID

Timer input capture 2

IC1 _ ID

Timer input capture 1

RTI _ ID

Real-time interrupt

IRQ _ ID

IRQ external pin

XIRQ _ ID

IRQ external pin (pseudo-nonmaskable)

SWI _ ID

Software interrupt

ILLEGAL _ OPCODE _ ID

Illegal opcode trap

COP _ ID

COP failure (reset)

CLOCK _ MONITOR _ ID

Clock monitor failure (reset)

The ATTACH() macro expects as inputs a function pointer to your service routine, and an interrupt identifier.  It sets up the interrupt vector in EEPROM so that subsequent interrupts will execute the specified service routine.  The code installed by ATTACH includes the RTI instruction that terminates the interrupt service sequence.  The StartFunctionTimer() routine presented earlier shows how ATTACH() is called.

Implementation Details

The interrupt vectors near the top of memory are in ROM; locations that cannot be modified by the programmer.  The contents of these locations point to a series of locations in the EEPROM (at AE20-AEBFH) which can be modified and, if desired, write-protected using the BPROT register.  ATTACH() writes some code at the EEPROM locations corresponding to the specified interrupt.  This code loads the code field address of the user’s service function into registers and jumps to a routine that saves the current page, changes the page to that of the user’s service function, and calls the service function as a subroutine.  When the user-defined service function returns, the code installed by ATTACH() restores the original page and executes RTI (return from interrupt) to complete the interrupt service process.  This calling scheme ensures that the interrupt service will be properly called no matter which page the processor is operating in when the interrupt occurs.  And because the interrupt calling routine which is installed by ATTACH() ends with an RTI, your service routine can end with a standard RTS, return or } which makes debugging much easier.

QED-Forth simplifies the details of using interrupts.  Implementing an interrupt using QED-Forth tools is a 4 step process:

1. Define constants representing registers related to the interrupt being implemented. Also define bit masks that are useful for setting and clearing flag, mask, and other relevant bits.

2. Write an interrupt handler using Forth or assembly code.  The handler should use the constants and masks defined in step 1 to clear the interrupt’s flag bit, and should end with a ; or RTS instruction.

3.  Write an installation word for the interrupt handler.  This word will use the ATTACH command which causes QED-Forth to install your interrupt handler into the appropriate interrupt vector in EEPROM.  The code installed by ATTACH supplies the RTI (return from interrupt) instruction that correctly terminates the service routine.

4. Write words to enable and disable the interrupt.  Enabling the interrupt is accomplished by clearing the interrupt’s flag bit and then setting its mask bit.  It may also be necessary to clear the I bit in the CCR to globally enable interrupts.  This can be accomplished by executing the kernel word ENABLE.INTERRUPTS.

The following sections explain how to define and install interrupt service routines that enhance the usefulness of many of the 68HC11’s hardware features.

The following example illustrates how to write and use an interrupt service routine.

An Example: Periodically Calling a Specified Function

Many times a program needs to execute a specified action every X milliseconds, where X is a specified time increment.  We can use an output compare interrupt to accomplish this.  We’ll set up an interrupt service routine that executes once per millisecond (ms), and maintains a ms _ counter variable that is incremented every millisecond.  The variable time _ period specifies the time increment, in ms, between calls to the specified function which is named TheFunction().  For this simple example, TheFunction() inverts the contents of the static variable named action _ variable

Listing 6‑1     TIMEKEEP.C, An Example of an Interrupt Service Routine

#define OC3_MASK  0x20          // used to set/clear OC3 interrupt flag and mask

#define ONE_MS  500             // 500 counts of 2us TCNT = 1 ms

#define DEFAULT_TIME_PERIOD  1000   // Execute once per second

 

static int ms_counter;          // runs from 0 to 65535 before rolling over

static uint time_period;        // specifies time in ms between function calls

static int next_execution_time; // next scheduled value of ms_counter                                

static int action_variable;     // state is toggled by TheFunction()

 

_Q void TheFunction(void)       // the function simply complements

{   action_variable = !action_variable; // a variable

}

 

_Q void FunctionTimer(void)

// This interrupt service routine is called by an OC3-based clock interrupt and

// simply calls TheFunction periodically.

{  ms_counter++;

   if(ms_counter == next_execution_time)

   {  TheFunction();

      next_execution_time = next_execution_time + time_period;

   }

   TOC3 += ONE_MS;        // set OC3 count for next interrupt in 1 ms

   TFLG1 = OC3_MASK;      // reset the oc3 interrupt flag by writing a 1

}

 

_Q void StopFunctionTimer(void)

{   TMSK1 &= ~OC3_MASK;   // clear OC3I to locally disable OC3

}

 

_Q void StartFunctionTimer(void)

// inits variables and locally enables OC3 interrupt;

// does not globally enable interrupts!

{  StopFunctionTimer();        // locally disable OC3 while we set it up

   ATTACH(FunctionTimer, OC3_ID);  // post the interrupt service routine

   ms_counter = 0;

   time_period = next_execution_time = DEFAULT_TIME_PERIOD;   // 1/second

   action_variable = 0;        // state is toggled by TheFunction()

   TOC3 = TCNT + ONE_MS;       // start after a 1 ms delay

   TFLG1 = OC3_MASK;           // clear interrupt flag OC3F

   TMSK1 |= OC3_MASK;          // set OC3I to locally enable OC3

}

In this program, we define a bit mask named OC3 _ MASK which has bit 5 set and all other bits clear.  From inspection of the register summary in the Motorola 68HC11F1 booklet, we see that this mask isolates the Output Compare 3 (OC3) mask bit in the TMSK1 register, and isolates the OC3 interrupt flag bit in the TFLG1 register.  The other relevant registers are the 16 bit free-running counter register named TCNT which increments every 2 microseconds, and the Timer Output Compare 3 register named TOC3. If the OC3 interrupt is enabled by setting its mask bit = 1 in TMSK1 and by globally enabling interrupts using ENABLE _ INTERRUPTS() or the assembly instruction CLI, then an interrupt occurs when the count in TCNT matches the count in TOC3.  Thus we can control when the next interrupt occurs by writing a specified count to TOC3.

TheFunction() is our prototypical function that simply toggles the action _ variable between the values 0 and 1.  The goal of our interrupt service routine is to call TheFunction() exactly once per second. 

FunctionTimer() is the OC3 interrupt service routine.  It increments the ms _ counter variable, and checks if ms _ counter equals next _ execution _ time.  If so, it calls TheFunction() and updates next _ execution _ time by adding time _ period to it.  Then FunctionTimer() increments the contents of the TOC3 register to set up the next interrupt in 1 ms, and clears the interrupt request flag by writing a 1 to the OC3 flag bit in the TFLG1 register.  Note that FunctionTimer() does not have any input parameters or a return value.  Moreover, it is not defined using the _ interrupt keyword which would insert an RTI (return from interrupt) instruction at the end of the function.  Rather, ATTACH() will supply the RTI instruction for us.  Because FunctionTimer() does not end with an RTI, we can easily test the FunctionTimer() service routine using our standard interactive debugging techniques.

StopFunctionTimer() simply clears the local OC3 interrupt mask bit in the TMSK1 register to disable the OC3 interrupt. 

StartFunctionTimer() first locally disables OC3 to prevent an interrupt while the service routine is being posted.  Then it calls:

 

ATTACH(FunctionTimer, OC3_ID);

to ensure that FunctionTimer() is called every time the OC3 interrupt occurs; ATTACH() also installs a return sequence that supplies the required RTI (return from interrupt) opcode.  ATTACH() is described in detail later in this chapter.  Note that its input parameters are a pointer to the interrupt service routine FunctionTimer, and a pre-defined constant named OC3 _ ID that identifies the interrupt.  All of the interrupt identifier constants are summarized in Table 6‑3.

After calling ATTACH(), StartFunctionTimer() initializes the timing variables, and initializes TOC3 so that the first interrupt will occur in 1 ms.  It then clears the interrupt flag by writing a 1 to the OC3F flag bit in the TFLG1 register using the statement:

 

TFLG1 = OC3_MASK;

Clearing the interrupt flag bit before enabling the interrupt is a highly recommended procedure that ensures that all prior pending OC3 interrupts are cleared before the interrupt occurs.  Finally, StartFunctionTimer() locally enables the OC3 interrupt by setting the mask bit in TMSK1 with the statement:

 

TMSK1 |= OC3_MASK;

To start the interrupt, main() simply calls StartFunctionTimer() followed by ENABLE _ INTERRUPTS().  After you compile and download the TIMEKEEP.C program and type:

main

from your terminal, the OC3 interrupt is running in the background.  To monitor the state of the action _ variable, interactively type at your terminal:

See( )

and you will see the variable’s value change from 0 to 1 exactly once per second.  Type any key to terminate the See( ) function.

This short program provides a template that shows how a function can be periodically called with a period specified by the variable time _ period.  Of course, in your application the called function would perform a more useful action than does TheFunction() in this simple example.   You could make other enhancements; for example, a foreground task could manipulate the contents of time _ period to change the frequency at which TheFunction() is called, and you could use ms _ counter to measure elapsed time with 1 ms resolution.

Note that, to maintain timing accuracy, the interrupt service routine should have a worst-case execution time of under 2 ms; otherwise the FunctionTimer() will miss the interrupt when TCNT matches TOC3, and an extra delay of 131 ms will occur while the TCNT timer rolls over.  In general, interrupt service routines should be short and simple.  Complex calculations should be done by foreground tasks, and the interrupt routines should perform the minimum actions necessary to service the time-critical events.  An example of this approach is presented in the Turnkeyed Application Program.

Cautions and Restrictions

Note that the OC2 interrupt is used as the multitasker’s timeslice clock.  Before using this interrupt for another purpose, make sure that you don’t need the services provided by the timeslicer which supports the multitasking executive and the elapsed time clock.

The main restriction on interrupt service routines is that they must not call _ forth (kernel) library functions unless the instructions in the next section are followed.  The Glossary document and the header files in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory specify which functions are of the _ forth type. 

Summary

Using interrupts requires:

     coding an interrupt service routine;

     using ATTACH() to bind it to the appropriate interrupt; and,

     enabling its local interrupt mask. 

[[[The Turnkeyed Application Program described in the next chapter presents another detailed example of how to use interrupts in a measurement and control application.]]]

All About Interrupts and Interrupt Latency

How the 68HC11 Handles Interrupts

The 68HC11 processor has 21 interrupts including input capture and output compare timers, a pulse accumulator, synchronous and asynchronous serial I/O, maskable and non-maskable external interrupt signals, computer-operating-properly and clock monitor failures, software and real-time interrupts, and the master reset interrupt.  The Motorola 68HC11 manuals and the QED Hardware Manual describe the interrupt system in detail; a few basic aspects of the system are discussed here.

Recognition and servicing of maskable interrupts are controlled by a global interrupt enable bit (the I bit in the condition code register) and a set of local interrupt mask bits.  If a local interrupt mask bit is not enabled, then the interrupt is “masked” and will not be recognized.  If the relevant local mask bit is enabled and the interrupt event occurs, the interrupt is recognized and its interrupt flag bit is set to indicate that the interrupt is pending.  It is only serviced, however, if the global interrupt bit is enabled. 

When an interrupt is both recognized and serviced, the processor automatically globally disables all maskable interrupts by setting the I bit in the condition code register until the service routine is over.  Other maskable interrupts can become pending during this time, but will not be serviced until interrupts are again globally enabled when the service routine ends.  (The programmer can also explicitly re-enable global interrupts inside an interrupt service routine to allow nesting of interrupts; however, this can cause hard-to-diagnose problems in multitasking application programs).  Non-maskable interrupts (reset, clock monitor failure, COP failure, illegal opcode, and XIRQ) are serviced regardless of the state of the I bit.

When an interrupt is serviced, the processor globally disables interrupts, pushes the contents of the 68HC11’s programming registers onto the return stack, and then fetches the address of the service routine from a memory location (called an “interrupt vector”) near the top of memory that is associated with that interrupt.  In QED-Forth the interrupt vectors (which are in non-modifiable ROM) point to modifiable locations in the EEPROM so that the interrupt service routines can be specified by the user.  The service routine must, at a minimum, perform two duties:

1.                                                                                                                                                                                           Reset the interrupt flag bit (not the mask bit) so that the interrupt is no longer pending.  The flag bit, oddly enough, is reset by writing a 1 to it.

2.                                                                                                                                                                                           Execute an RTI (return from interrupt) instruction which pops the saved registers from the return stack and re-enables the global interrupt mask bit.

In addition there are several minor constraints on the interrupt service routine:

1.                                                                                                                                                                                           Local variables should not be used in interrupt service routines or in any code called by the interrupt service routine. See LOCALS{  in the glossary.

2.                                                                                                                                                                                           If floating point operations are used in an interrupt service routine then FP.PUSH and FP.POP must be used. See glossary entries for FP.PUSH and FP.POP.

QED-Forth simplifies interrupt handling, and transparently executes the RTI instruction for you.

 

How QED-Forth Simplifies Interrupts

With QED-Forth it is easy to define an interrupt service routine and ATTACH it to a specified interrupt.  You define your service routine in either assembly code or in high level FORTH.  Your routine ends with a normal RTS (return from subroutine) in assembly code or with a normal ; in FORTH (as opposed to an RTI instruction). Thus the service routine can be debugged just like any other FORTH word.

A set of kernel words has been defined as identifiers for each interrupt except the hardware reset which is not revectorable.  The revectorable interrupts are:

 

The kernel word ATTACH makes it easy to specify an action which is invoked by a given interrupt.  ATTACH expects the 32-bit extended code field address (xcfa) of your service routine under an interrupt identifier on the stack, and it sets up the interrupt vector so that subsequent interrupts will execute the specified service routine.  The code installed by ATTACH includes the RTI instruction that terminates the interrupt service sequence. 

For example, if you define a word called TIMER.SERVICE to respond to the timer output compare #4 interrupt, you simply execute

CFA.FOR TIMER.SERVICE  OC4.ID  ATTACH

to vector the output compare 4 interrupt so that it will call the TIMER.SERVICE routine.  The QED Hardware Manual describes some examples of interrupt service routines.

Note that the OC2 interrupt is used as the multitasker’s timeslice clock.  Before using this interrupt for another purpose, make sure that you don’t need the services provided by the timeslicer (see the glossary entry for START.TIMESLICER).

Implementation Details

The interrupt vectors near the top of memory are in ROM locations that cannot be modified by the programmer.  The contents of these locations point to a series of locations in the EEPROM (at AE20-AEBFH) which can be modified and, if desired, write-protected using the BPROT register.  ATTACH writes some code at the EEPROM locations corresponding to the specified interrupt.  This code loads the code field address of the user’s service word into registers and jumps to a routine that saves the current page, changes the page to that of the user’s service word, and calls the service word as a subroutine.  When the user-defined service word returns, the code installed by ATTACH restores the original page and executes RTI (return from interrupt) to complete the interrupt service process.  This calling scheme ensures that the interrupt service will be properly called no matter which page the processor is operating in when the interrupt occurs.  And because the interrupt calling routine which is installed by ATTACH ends with an RTI, your service routine can end with a standard RTS or ; which makes debugging much easier.

Interrupt Latency

The time required between the processor’s initiation of interrupt servicing and the execution of the first byte of the specified service routine is called the interrupt latency.  Most of the 68HC11’s interrupts have an inherent latency of 12 machine cycles during which the registers are saved on the return stack and the interrupt vector is fetched.  This corresponds to 6 microseconds (abbreviated as usec) if the board is clocked at 8 MHz, and of course this time is halved if the board is clocked at 16 MHz.  QED-Forth’s interrupt latency is longer because the interrupts are re-vectored via the EEPROM to allow the programmer to modify the vectors, and because the page must be changed.  The latency of service routines installed with ATTACH is 34 machine cycles, or 17 usec with an 8 MHz crystal.  That is, the first opcode of the user’s service routine is executed 17 usec after interrupt service begins.  After the service routine’s concluding RTS executes, an additional 20 cycles (10 usec) lapses before the originally interrupted program resumes execution.  12 of these cycles are accounted for by the RTI instruction, and the other 8 cycles are required to restore the original page.

 

Interrupt Latency 1 (Getting Started C v31)
Interrupt Latency Resulting from Clock Stretching

The PIA (Peripheral Interface Adapter), the LCD display, and the battery-backed real-time clock have timing specifications that are not wholly consistent with 16 MHz read/write operations. To solve this problem, the V3.0 software uses the "clock stretching" feature of the 68HC11F1 processor to slow down the interactions with these devices by inserting wait states.  The slow I/O accesses are targeted only at these three devices and do not slow down the normal operations of the QED Board.  Interrupts are globally disabled while clock stretching is active; however, the interrupts are typically disabled for less than 10 microseconds so the effect on most applications is minimal.

Nested Interrupts

More About Interrupts 1 (Getting Started C v31)

Interrupt Mask Bits and Flag Bits 1 (Getting Started C v31)

Nonmaskable Interrupts 1 (Getting Started C v31)

An Interrupt Flag Bit Is Cleared By Writing a 1 1 (Getting Started C v31)

Interrupt Recognition and Servicing 1 (Getting Started C v31)

ATTACH() Makes It Simple 1 (Getting Started C v31)

Implementation Details 1 (Getting Started C v31)

Cautions and Restrictions 1 (Getting Started C v31)

Interrupt Priority 1 (Getting Started C v31)

Available Interrupts

Kernel Services That Disable Interrupts

Certain QED-Forth routines temporarily disable interrupts by setting the I bit in the condition code register.  These routines are summarized here to assist you in planning the time-critical aspects of your application.

The kernel provides a set of uninterruptable memory operators that disable interrupts for a few microseconds during the memory access.  These are very useful in applications where several tasks or interrupt routines must access a shared memory location.  The glossary entries for these words detail the length of time that interrupts are disabled.

(CHANGE.BITS)    (CLEAR.BITS)    (SET.BITS)    (TOGGLE.BITS)

CHANGE.BITS    CLEAR.BITS    SET.BITS    TOGGLE.BITS

|2!|    |F!|    |X!|

|2@|    |F@|    |X@|   

The multitasker mediates access to shared resources and ensures smooth transfer of information among tasks.  The routines that manage resource variables and mailboxes must disable interrupts for short periods of time to ensure proper access to shared resources and messages.  Consequently, the following routines temporarily disable interrupts:

?GET    ?RECEIVE    ?SEND   

GET    RECEIVE    RELEASE    SEND

Consult their glossary entries for details. 

The following routines temporarily disable interrupts to ensure that a new task is not corrupted while it is being built:

BUILD.STANDARD.TASK    BUILD.TASK

These routines disable interrupts to ensure that the elapsed time clock is not updated while it is being read:

READ.ELAPSED.SECONDS    READ.ELAPSED.TIME

The multitasker is charged with smoothly transferring control among tasks via timeslicing or cooperative task switching.  The timeslicer is an interrupt service routine associated with output compare#2.  It disables interrupts for the duration of a task switch which requires 58 microseconds plus 6.5 microseconds for each ASLEEP task encountered (these times are halved if the processor is clocked at 16 MHz). The cooperative task switch routine

PAUSE

disables interrupts for 31 microseconds plus 6.5 microseconds for each ASLEEP task encountered, and again these times are halved if the processor is clocked at 16 MHz.

The PAUSE routine (which temporarily disables interrupts) is called by the following built-in device drivers:

EMIT    EMIT1    EMIT2

KEY    KEY1    KEY2

?KEYPAD    KEYPAD

These routines as well as the following device driver routines GET and RELEASE resource variables, and so disable interrupts for short periods of time:

?KEY    ?KEY1    ?KEY2    ?KEYPRESS

>DAC

A/D12.MULTIPLE    A/D12.SAMPLE

A/D8.MULTIPLE    A/D8.SAMPLE

$>DISPLAY

CHAR>DISPLAY

CLEAR.DISPLAY

COMMAND>DISPLAY

DISPLAY.OPTIONS

PUT.CURSOR

UPDATE.DISPLAY

UPDATE.DISPLAY.LINE

INIT.DISPLAY

The battery-backed real-time clock option shares the RAM socket on the QED Board.  While the “watch” is being read or set by the routines

READ.WATCH    SET.WATCH

the RAM cannot be accessed, so interrupts cannot be properly serviced.  Therefore these routines disable interrupts for approximately 1 msec (or 0.5 msec with a 16 MHz crystal) while the watch is being accessed.

All of the routines that write to the EEPROM disable interrupts for 20 msec per programmed byte.  This results from the 68HC11’s design which prohibits any EEPROM locations from being read while other EEPROM locations are being modified. Since all interrupts are vectored through EEPROM, interrupts cannot be serviced while an EEPROM storage operation is in progress.  The following fundamental EEPROM storage routines

(EEC!)    (EE!)    (EEX!)    (EEF!)    (EE2!)   

disable interrupts for 20 msec per programmed byte.  These routines are smart enough to avoid programming a byte that already has the correct contents.  The following routines may modify EEPROM locations:

ATTACH    AUTOSTART

COLD.ON.RESET    DEFAULT.REGISTER.INITS   

INIT.VITAL.IRQS.ON.COLD    INSTALL.MULTITASKER

INSTALL.REGISTER.INITS    NO.AUTOSTART      

SAVE    SERIAL1.AT.STARTUP

SERIAL2.AT.STARTUP    STANDARD.RESET

START.TIMESLICER

The following routines disable interrupts and do not re-enable them:

DISABLE.INTERRUPTS    SEI   

COLD    WARM

DISABLE.INTERRUPTS and its assembly language counterpart SEI explicitly set the I bit in the condition code register.  The routines ENABLE.INTERRUPTS and CLI clear the I bit to globally enable interrupts.  The restart routines COLD and WARM disable interrupts so that the initialization process is not interrupted.

 

Coding Interrupt Service Routines

Using Interrupts 1 (Getting Started C v31)

An Example: Periodically Calling a Specified Function 1 (Getting Started C v31)

Interrupt Latency

Calling Kernel Functions From Within ISRs

There is a special consideration when calling _ forth library functions from interrupt service routines.  Fortunately, this restriction can be overcome by simply including the FORTHIRQ.C file with your source code, and following the simple example presented in the file.  FORTHIRQ.C is present in the \MOSAIC\DEMOS _ AND _ DRIVERS\MISC\C EXAMPLES directory. 

The method is very simple: just place a call to the function

 

BeginForthInterrupt();

at the top of your interrupt service routine (or, at the minimum, before any _ forth functions are called).  Before the final exit point of the interrupt service routine, place a call to the function

 

EndForthInterrupt();

That's all there is to it.  The ability to call _ forth library functions from interrupt service routines makes it easier to manage page-mapped I/O devices on an event-driven basis.External Hardware and Reset-Type Interrupts

The RESET interrupt is the most important interrupt in the 68HC11.  It is invoked when the processor is powered up or when the reset line is pulled low (which occurs when the reset button is pushed).  When a reset occurs, the processor initializes its hardware registers to their specified reset conditions and calls QED-Forth’s restart routine.  RESET is the only interrupt that cannot be revectored by the programmer.

As explained in Chapter 3, a reset is a hardware initialization sequence, and a restart is a software-controlled initialization sequence.  QED-Forth’s restart routine initializes the system using either a COLD or WARM restart sequence, and commences execution of the operation program.  The program decides whether to perform a COLD or WARM restart based on the contents of a variable in the user area.  If this user variable is properly initialized, a warm restart is performed; if not, a cold startup occurs.  A WARM restart initializes the minimum number of hardware registers and user variables necessary to run QED-Forth.  It clears the stacks and ensures that there is one awake task running.  A COLD restart is more thorough: it completely re-initializes the user area to default values.

 

In addition to the RESET interrupt, two other interrupts also cause hardware resets that initialize the processor’s registers: the computer operating properly (COP) failure and clock monitor failure.  These interrupts then execute a specified service routine.  QED-Forth initializes the vectors of these interrupts so that they execute the same program that the standard reset does.  These interrupts are vectored via the EEPROM so that the user can change the interrupt service routine if necessary.

The illegal opcode trap interrupt does not force a hardware reset.  But it is very important that this interrupt’s vector is initialized at all times to perform a proper restart.  Otherwise, a crash which invokes this interrupt could cause another crash, causing an infinite loop of calls to the un-initialized interrupt that can only be exited by resetting the processor.  The default service routine for this interrupt is the standard startup routine like the other three interrupts just described.  This default service routine can be changed by the programmer.

Initialization of the Vital Interrupts

QED-Forth treats the RESET, computer operating properly (COP) failure, clock monitor failure, and illegal opcode trap interrupts as “vital interrupts” whose vectors should always be properly initialized.  The RESET interrupt vector is in ROM and can never be changed.  The other three vital interrupts are revectored via the EEPROM.  QED-Forth’s cold restart routine checks to make sure that these vital interrupts are properly initialized to their default values.  If they are already initialized, it does nothing; if not, the cold restart routine writes to their EEPROM vectors to initialize them.  The warm restart routine does not do this.

If you wish to maintain customized service routines for these interrupts, install the service routines (using ATTACH, for example) and then execute

NO.VITAL.IRQ.INIT

This installs a pattern in a special EEPROM location which informs the cold startup routine not to initialize the illegal opcode trap, COP and clock monitor failure interrupts. Be careful with custom service routines for COP and clock monitor failure interrupts.  Because these interrupts are associated with hardware resets, the four protected registers INIT, OPTION, BPROT, and TMSK2 must be initialized in the first 64 cycles of operation.  Make sure that your service routine can accomplish the initializations within the allotted time.

To revert to the default initialization of the vital interrupts, simply execute

INIT.VITAL.IRQS.ON.COLD

Forcing Cold Restarts

Some applications are more reliable if any reset condition (power-up, reset button, or COP or clock monitor failure) causes a cold restart which completely initializes the user area and system parameters.  This is especially important in embedded systems applications where the main program has been configured to automatically execute after each restart.  Such main programs typically perform a set of initializations that configure the system to perform the application program.  The combination of a cold (as opposed to a warm) restart and the main program’s initializations establishes a well-known starting state for the system.

 

To force a cold restart every time a reset occurs, execute

COLD.ON.RESET

which installs a pattern in a specified location in the EEPROM.  The startup routine checks this location and, if the proper pattern is present, always performs a cold startup.  Because the pattern is in EEPROM, it need not be re-installed after each restart.  To revert to the standard warm/cold startup behavior, execute

STANDARD.RESET

which removes the pattern from the location in the EEPROM.

Initializing the Protected Registers

There are four “protected” 68HC11 registers that contain bits that can be modified only during the first 64 machine cycles after a startup.  These are the INIT, OPTION, BPROT, and TMSK2 registers.  The QED-Forth restart routine initializes these registers to default values.  You have the option of installing customized initialization values for OPTION, BPROT, TMSK2, and BAUD; initializing the BAUD register allows you to specify a serial communications baud rate that is set every time the processor restarts.

The INIT register specifies the memory locations of the hardware registers and on-chip RAM.  The protected portion of the OPTION register controls the edge sensitivity of the external IRQ signal, STOP mode exit delay, and COP timer rate selection.  BPROT can write-protect sections of the EEPROM and the CONFIG register.  Two protected bits (bits 0 and 1) of TMSK2 control the frequency of the free-running counter.  BAUD controls the baud rate of the serial communications interface.

The default initializations of these special registers are as follows:

INIT is set to B8H to place the on-chip RAM at B000H-B3FFH and the hardware registers at 8000H-805FH.  INIT must have this value for QED-Forth to operate properly.

OPTION is initialized to 33H which configures the A/D converter to use the E-clock to power its charge pump, keeps the 8-bit A/D converter off at startup, makes the IRQ input edge sensitive, sets a 4 msec delay after exiting the STOP mode, disables the clock monitor circuit, and sets the COP timer rate to 1.049 sec. The A/D power-up bit, the clock/charge-pump select bit, and the clock monitor enable bit can be set at any time.  The other bits can only be modified within the first 64 machine cycles after a reset.

The 2 protected bits in TMSK2, called PR0 and PR1, control the frequency of the main timer. The other bits in the register are interrupt mask bits that are not protected (they can be modified at any time); they are initialized to 0 so that 4 interrupts associated with the timer subsystem are not enabled after a reset. PR0 and PR1 are set such that the main timer has a period of 2 microseconds (usec), and the timer “rolls over” to 0 after just over 131 msec.  If the QED Board is clocked at 8 MHz the register is initialized to 01H which drives the main timer at 1/4 the E-clock frequency, and if the QED Board is clocked at 8 MHz the register is initialized to 02H which drives the main timer at 1/8 the E-clock frequency.   (For experts and the curious: A flag in the kernel ROM at location FFC0H tells QED-Forth the crystal frequency.)

BPROT is initialized to 10H which enables writes to all EEPROM cells and disables writes to the CONFIG register.

BAUD is initialized such that the baud rate is 9600 bits per second.  If the onboard crystal frequency is 8 MHz, the BAUD register is initialized to 30H, and if the onboard crystal frequency is 16 MHz, the BAUD register is initialized to 31H. The BAUD register may be modified at any time.

The default register initialization values are summarized in Figure 11.1.

Register  Register  Default Value  Default Value

Name  Address  @ 8 MHz  @ 16 MHz

OPTION  8039H  33H  33H

TMSK2  8024H  01H  02H

BPROT  8035H  10H  10H

BAUD  802BH  30H  31H

Figure 11.1  Default values of the registers initialized by INSTALL.REGISTER.INITS.  These are the values that take effect after DEFAULT.REGISTER.INITS has been executed.

The kernel word INSTALL.REGISTER.INITS specifies initialization values for OPTION, BPROT, BAUD, and the lowest 2 bits of TMSK2.  The specified values take effect upon the next reset, and the registers are appropriately initialized each time the processor resets.  INSTALL.REGISTER.INITS expects the value of OPTION under the value of TMSK2 under the value of BPROT under the value of BAUD on the stack.  After ANDing the specified value of TMSK2 with 03H so that only the lowest 2 (protected) bits are retained, INSTALL.REGISTER.INITS writes a pattern followed by the specified bytes in a special area of the EEPROM.  The restart routine checks this pattern after every reset and, if it is present, initializes the registers during the first 64 cycles to the specified values. 

For example, suppose that your board is clocked by an 8 MHz crystal, and you want to speed up the main free-running timer to run at 2 MHz (its default value is 500 kHz). This requires setting the value of TMSK2 to 00H (see the description of TMSK2 in the Motorola 68HC11 manuals).  To install this value of TMSK2 while maintaining the default values for the INIT, OPTION, and BAUD registers, execute:

HEX

33    \ default value for OPTION

00    \ new value for TMSK2

10    \ default value for BPROT

30     \ default value for BAUD at 8MHz, yields 9600 baud

INSTALL.REGISTER.INITS

The specified values will take effect upon the next reset.

To return to the default initializations for the five registers, execute

DEFAULT.REGISTER.INITS

which erases the pattern that INSTALL.REGISTER.INITS put in the EEPROM.  After executing DEFAULT.REGISTER.INITS the registers will be initialized to the default values shown in Figure 11.1 each time the processor executes its restart sequence.

 

External Hardware Interrupts /IRQ and /XIRQ (from QED Hardware)

Reset Interrupts (from QED Hardware)

A Hardware Perspective on 68HC11 Interrupts (from QED Hardware)

Interrupt Recognition and Servicing (from QED Hardware)

Interrupt Flag and Mask Bits (from QED Hardware)

QED-Forth's Handling of Interrupts (from QED Hardware)

Summary (from QED Hardware)

External Interrupts, Resets, Operating Modes, and the COP Summary (from QED Hardware)

Changing the CONFIG Register

The CONFIG register is a special hardware register implemented as an EEPROM cell.  It controls the position of the 512-byte EEPROM, determines whether the EEPROM is enabled, and enables or disables the computer operating properly (COP) feature.  The CONFIG register is writable as long as bit 4 of the BPROT register is cleared.  QED-Forth’s default value for BPROT has this bit set so that the CONFIG register is unwritable.  This prevents inadvertent modifications which could crash the system.  If you wish to modify CONFIG (for example, to turn on the COP feature), first use INSTALL.REGISTER.INITS to set a value for BPROT with bit 4 cleared.  If you have an 8 MHz crystal, you could execute

HEX

33    \ default value for OPTION

01    \ default value for TMSK2 at 8 MHz (use 02 at 16 MHz)

00    \ new value for BPROT enabling writes to CONFIG

30     \ default value for BAUD at 8 MHz (use 31H at 16 MHz)

INSTALL.REGISTER.INITS

Now issue a reset so that the new value of BPROT takes effect, and CONFIG becomes writable.  Then use (EEC!) to set the desired value of the CONFIG register, whose address is 803FH.  For example, to enable the COP while keeping the EEPROM enabled at AE00-AEFFH, execute

HEX AB 803F (EEC!)

Before executing a reset, the CONFIG register should be write-protected again by specifying a value for BPROT with bit 4 clear.  This can be accomplished by another INSTALL.REGISTER.INITS command or by executing DEFAULT.REGISTER.INITS.  If the COP has been enabled, make sure that an autostart routine has been installed that can service the COP before its intermittent time-out.  Only then should a reset be issued so that the new value of CONFIG takes effect.  The chapter titled “External Interrupts, Resets, Operating Modes, and the COP” in the QED Hardware Manual defines a step-by-step sequence to assist you in configuring the COP.

 

These routines disable interrupts just before reading the memory contents and restore the prior state of the interrupt flag (enabled or disabled) after writing to the specified address.  Interrupts remain disabled for only 10 to 16 cycles, corresponding to 5 to 8 microseconds if the crystal frequency is 8 MHz. Consult the glossary for detailed descriptions of these operators. 

Returning to our example, Task#1 can set the least significant bit in port PPA by executing

01 PPA SET.BITS

Because SET.BITS is uninterruptable, we can be sure that there will not be a task switch during the read/modify/write operation.  Likewise, Task#2 can set the most significant bit in PPA by executing

HEX 80 PPA SET.BITS

These instructions work properly in a multitasked system.

Similar problems can arise when one task writes to a floating point or other 4-byte variable, and a second task needs to read the saved value.  The data that is read may be invalid if the read or the write is interrupted between the time of the writing/reading of the first 16 bits and the writing/reading of the second 16 bits.  The SEND and RECEIVE mailbox operators described earlier are one solution to this problem.  Another solution is to use one of QED-Forth’s uninterruptable 32-bit operators denoted by the | (“bar”) character are in the kernel.  The uninterruptable storage operators are

|2!|                                                                                                                                                                                           |F!|                                                                                                                                                                                           |X!|

and the uninterruptable fetch operators are

|2@|                                                                                                                                                                                           |F@|                                                                                                                                                                                           |X@|

The assembly instructions for 8 bit and 16 bit read and write operations are themselves uninterruptable.  Thus, with the unlikely exception a 16 bit variable that straddles a page boundary, the C@, C!, @, and ! operators are already uninterruptable and can be used to share data among tasks.

 

 

Designing Re-Entrant Code

For a multitasking system to operate at its full potential, the kernel routines in the system must be “re-entrant”.  A re-entrant routine functions properly even if it is “re-entered” while it is executing.  Routines that are not re-entrant will fail under these circumstances.  There are two contexts where this is important: recursion and multitasking. 

A recursive routine is one that calls itself; to ensure “re-entrancy with respect to recursion”, a routine may modify only stack-based quantities such as data stack items and local variables (which are kept on the return stack). 

Routines that modify only stack or task-private memory locations are “re-entrant with respect to multitasking”.  “Task-private” locations are those that can be accessed by one task only.  A user variable is a task-private memory location.  Routines that modify variables that are shared by more than one task are not re-entrant with respect to multitasking.  They may work properly if used by only one task, but are prone to failure if they are called by multiple tasks.

A routine that is re-entrant with respect to recursion is automatically re-entrant with respect to multitasking, but the converse is not true.  The rest of this discussion will focus on re-entrancy with respect to multitasking.  If you are designing recursive routines, keep in mind that the rules for re-entrancy are stricter than those presented below.

Re-entrant and Non-re-entrant Definitions

The following definition (originally presented in Chapter 2) is re-entrant.  The word calculates the volume and cross-sectional area of a cylinder, and modifies only the data stack and local variables (which are maintained on the return stack):

: CYLINDER.STATISTICS                                                                                                                                                                                           ( r1\r2 -- r3\r4 )

\  r1 = radius, r2 = height, r3 = volume, r4 = cross-sectional area

LOCALS{ f&height f&radius | f&area }

f&radius  FDUP F*   PI  F*                                                                                                                                                                                           \ cross-sectional area = πR**2

TO f&area

f&area f&height F*                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   ( -- volume = height * area )

f&area                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             ( -- volume\area )

;

The following version of this word is not re-entrant:

REAL: CYLINDER.AREA                                                                                                                                                                                           \ define a self-fetching variable to hold area

: CYLINDER.STATISTICS                                                                                                                                                                                           ( r1\r2 -- r3\r4 )

\  r1 = radius, r2 = height, r3 = volume, r4 = cross-sectional area

LOCALS{ f&height f&radius  }

f&radius  FDUP F*   PI  F*                                                                                                                                                                                           \ cross-sectional area = πR**2

TO CYLINDER.AREA

CYLINDER.AREA f&height F*                                                                                                                                                                                             ( -- volume = height * area )

CYLINDER.AREA                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  ( -- volume\area )

;

 

This version uses a global self-fetching variable named CYLINDER.AREA.  Assume that CYLINDER.STATISTICS is called by task #1, and that the multitasker’s timeslicer transfers control to task#2 just before the final statement of the definition (that is, before the final invocation of CYLINDER.AREA).  If task #2 now puts a radius and height on the stack and calls CYLINDER.STATISTICS, the value in the variable CYLINDER.AREA will be changed.  When control returns to task#1, the area placed on the stack will be incorrect for that task.  This shows how non-re-entrant code causes errors in multitasking systems.

Techniques for Ensuring Re-entrancy

Each task has its own user area, data and return stacks, POCKET, PAD, and TIB.  To ensure re-entrancy, each task that uses the heap must have its own heap area.  These memory areas are called “task-private” because only one task has access to them.  To ensure re-entrancy, words should modify only stack-based quantities (including local variables) and task-private memory.  Non-task-private memory such as the contents of variables and self-fetching variables should not be modified if the word is to be called by more than one task.

To ensure re-entrancy, data structures such as temporary matrices that hold intermediate results within a word must reside wholly on a stack or within a task-private heap.  Recall that arrays and matrices have parameter fields that hold dimensioning information and a handle to the heap memory block.  The parameter field of a globally defined array or matrix is allotted in the variable area when the data structure is defined.  Because a function that uses such a globally defined temporary matrix could be called from multiple tasks, a parameter field in the variable area would make the routine non-re-entrant.

Stack Frames

Stack frames solve the problem of how to create a parameter field for a temporary array or matrix while preserving re-entrancy.  The temporary array/matrix parameter field can be created and kept on the data stack as a “stack frame”, and using stack-based items preserves re-entrancy.

The kernel word STACK.FRAME allows a structure that has been defined with the structure defining words (see the previous chapter) to be instantiated on the data stack. STACK.FRAME expects a size (in bytes) of the structure to be placed on the data stack, allocates room on the stack, and returns on the top of the stack the extended address of the base of the stack frame.  The word FRAME.DROP is used to remove the stack frame from the stack before the calling word finishes executing.  The word PF.STACK.FRAME (“parameter-field-stack-frame”) performs the operation of STACK.FRAME and then stores 0\0 into the first 4 bytes of the stack frame in the position where the heap xhandle will reside.  Initializing the xhandle to zero is good programming practice, and PF.STACK.FRAME makes this easy to accomplish when allocating stack-based parameter fields.

A Stack Frame Example

For example, let’s define a re-entrant version of a word that places the transpose of a source matrix into a destination matrix, even if the specified source and destination are the same.  If the source and destination matrices are the same we must dimension a temporary matrix, place the transpose into it, and then copy the transpose back to the source.  In the Chapter 6 discussion of arrays and matrices we defined the word #2.TRANSPOSED which can transpose a matrix, but only if the source and the destination are different matrices.  We can use #2.TRANSPOSED to perform the actual transposition, but we’ll add the capability of dimensioning a temporary data matrix to handle the case when the source and the destination are the same. 

: #3.TRANSPOSED                                                                                                                                                                                           ( src.xpfa\dest.xpfa -- )

\ places the transpose of the source in the destination;

\ the destination can be the same as the source

LOCALS{ x&dest x&src | x&temporary }

x&dest x&src X=                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        ( source = destination?--)

IF                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        \ if source = destination...

MATRIX.PF PF.STACK.FRAME                                                                                                                                                                                                                                                                                                                                                                                       \ make room on stack for pf

TO x&temporary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            \ save temporary xpfa

x&src x&temporary #2.TRANSPOSED                                                                                                                                                                                           \ transpose is in temp matrix

x&dest x&temporary SWAP.MATRIX                                                                                                                                                                                           \ now x&dest has the answer

x&temporary DELETED                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 \ delete the temporary matrix

MATRIX.PF FRAME.DROP                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 \ clear frame off data stack

ELSE                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             \ else if source not= dest...

x&src x&dest #2.TRANSPOSED                                                                                                                                                                                                                                                                                                                                                                                      \ ...just do it

ENDIF

;

The first line of the definition loads the source and destination xpfa’s into 32-bit local variables, and creates an un-initialized local variable.  The next line checks if the source and destination are equal.  If they are not, the ELSE part of the conditional executes, and #2.TRANSPOSED can perform the operation directly.  If the source equals the destination, however, #2.TRANSPOSED will not work, and we have to create a temporary destination matrix.  The next line allocates space on the data stack for the parameter field of the temporary matrix.  MATRIX.PF puts the size of the parameter field on the stack, and PF.STACK.FRAME allocates space for the parameter field on the data stack and initializes its heap handle to 0\0.  The base xaddress of the stack frame is loaded into the local variable x&temporary, and the source and temporary matrix xpfa’s are passed to #2.TRANSPOSED, which dimensions the temporary matrix and transposes the source into it.  Next, SWAP.MATRIX interchanges the contents of the temporary and destination parameter fields so that x&dest is the transposed matrix and x&temporary points to the original matrix.  Next the temporary matrix is deleted from the heap, and FRAME.DROP clears the temporary parameter field off the data stack.

This final definition is very similar to the actual definition of TRANSPOSED in QED-Forth.  Many of the matrix words need temporary matrices to allow the source and destination matrices to be the same, and temporary matrices are always implemented using the stack-frame technique to ensure re-entrant operation of all of the kernel words.  Thus all of the mathematics routines may be used in multitasking applications as described in the next chapter.

Low Power Mode

Low Power Modes (from QED Hardware)

Operating Modes of the 68HC11F1 CPU (from QED Hardware)

The Special Cleanup Mode

If a buggy program has been installed as an autostart routine, an infinite series of crashes may result: the program crashes, which causes a restart, which calls the autostart program, which crashes, etc...  Another sticky situation arises if an improper value is written to the non-volatile CONFIG register which controls the location of the EEPROM and the enabling of the COP interrupt.  Or, if INSTALL.REGISTER.INITS has been used to set a value for the BPROT register that write-protects the EEPROM, it will not be possible to use DEFAULT.REGISTER.INITS to undo the initialization, because the initializations themselves are stored in the EEPROM.

The special cleanup mode allows you to recover from any of these situations.  Simply set DIP switch #5 “on” and reset the board by toggling DIP switch #6.  This puts the processor in the “special test mode”.  QED-Forth’s special “cleanup routine” is automatically called in this mode.  It removes any installed autostart patterns, initializes the CONFIG register, sets all of the options specified by EEPROM locations to their default values, and performs a COLD restart to enter the QED-Forth monitor.  Note that the cleanup mode cannot remove a PRIORITY.AUTOSTART vector if page 4 is write-protected or PROM.  If a buggy PRIORITY.AUTOSTART routine is installed  in PROM, remove the PROM to fix the problem.  If a buggy PRIORITY.AUTOSTART routine is installed  in write-protected RAM, turn DIP switch#1 OFF before entering the special cleanup mode so that the autostart pattern can be erased.

After using the cleanup mode, set DIP switch #5 to its standard “off” position and reset the processor to re-establish the normal operating mode and continue programming.

The Real-time Checklist

Memory Management Checklist

Multitasking Checklist


 

<< Previous | Next>>


Home|Site Map|Products|Manuals|Resources|Order|About Us
Copyright (c) 2006 Mosaic Industries, Inc.
Your source for single board computers, embedded controllers, and operator interfaces for instruments and automation