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

The C Programmer’s Guide to the QVGA Controller

Table of Contents

PART 1 GETTING STARTED

Introduction. How to Use This Manual

Chapter 1: Getting to Know Your QVGA

PART 2 PROGRAMMING THE QVGA CONTROLLER

Chapter 2: Your First Program

Chapter 3: The IDE: Writing, Compiling, Downloading and Debugging Programs

Chapter 4: Making Effective Use of Memory

Chapter 5: Programming the Graphical User Interface

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

Multitasking

The Battery-Backed Real Time Clock

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 the QVGA Controller

Chapter 10: Outputting Voltages with Digital to Analog Conversion

Chapter 11: Serial Communications

Chapter 12: The Battery-Backed Real Time Clock

PART 4: PUTTING IT ALL TOGETHER

Chapter 13: A Turnkeyed Application

PART 5: REFERENCE DATA

Appendix A: QVGA Electrical Specifications

Appendix B: Connector Pinouts

Appendix C: Physical Dimensions

Appendix D: Schematics (pdf)

Chapter 6

<< Previous | Next>>

Real Time Programming

The Timeslicer and Task Switching

The Built-In Elapsed Time Clock

The QVGA 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 (the Make icon) to compile the program, and use the terminal to send TIMEKEEP.DLF to the QED Board.  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 handshaking.

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 but 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) 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) 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 pp.5-22...5-26).  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 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 sets the automaticallyI bit to 1 to temporarily prevent maskable interrupts from being serviced.  Control is then passed to the interrupt handler code, which you must provide, pointed to by the contents of the interrupt vector.  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

The programmer can explicitly clear the I bit inside an interrupt service routine to allow nesting of interrupts.  Make sure that the return stack is large enough to accommodate the register contents placed there by the nested interrupts.  The default size of the return stack is 768 bytes, and it can be easily resized.

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-4).

Real Time Interrupt

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

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.7-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.7-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.7-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.7-4 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.7-4 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.7-4 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.7-4 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.7-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.7-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.7-11 ff.).

Pulse Accum Edge

A PEDGE interrupt occurs after a signal edge is detected on port A, pin 7 (MC68HC11F1 Technical Data Manual, p.7-11 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.10-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 Manua  p.9-16).

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, pp.6-15...6-16).  The default highest priority maskable interrupt is /IRQ.  A table in MC68HC11F1 Technical Data Manual,,  p. 6-16 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 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 QVGA 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 one must be stored into the flag bit’s location.

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  p.10-14 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,  p.2-17).  The / prefix to each of these names indicates that the signals are active-low. Pull-up resistors on the QVGA 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.

/XIRQ is nonmaskable.  After a one-time enabling (clearing) of its X control bit in the CCR, an /XIRQ request will be immediately serviced.  For this reason it can be used to warn of critical situations such as impending power failure.

The /IRQ and /XIRQ pins are accessed and controlled via the Digital I/O and Control bus (for pin locations see Appendix A).  They operate as active-low inputs to the processor.  An external device can drive either of these lines 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 described in Chapter 4 can also be configured to interrupt the processor when an external event occurs.

Configuring /IRQ and /XIRQ Interrupts

In its default state after each reset or restart /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,  p.5-24). IRQE is a “protected bit” in OPTION that must be written within the first 64 E cycles after a reset.  The QED-Forth word INSTALL.REGISTER.INITS (described in the glossary) may be used to specify a value that is automatically stored into OPTION upon each reset.

The /XIRQ external interrupt supports only level-sensitive operation, and is a nonmaskable interrupt.  Most interrupts, including /IRQ, are globally disabled (“masked”) if the I bit in the condition code register (CCR) is set.  /XIRQ is not affected by the state of the I bit.  Rather, it is controlled by the X bit in the CCR.  Whenever the 68HC11 is reset, the X bit is set in the CCR, thus disabling the /XIRQ external interrupt.  The QED-Forth startup routine does not modify the X bit.  To use the /XIRQ interrupt, install an appropriate interrupt handler using the ATTACH command, and then enable the /XIRQ interrupt after each reset by clearing the X bit.  A short routine that clears the X bit is presented below.

After being enabled, only an in-process /XIRQ service routine or a reset can prevent an /XIRQ interrupt request from being recognized.  The /XIRQ interrupt is commonly used to detect power loss as described below.

Using /IRQ and /XIRQ

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.

Similarly, to use /XIRQ, install an interrupt handler using Attach. In QED-Forth execute,

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

Next enable the interrupt by clearing the X bit in the CCR.  This can be done by defining and executing the following word:

 

CODE  XIRQ.ENABLE  \ define an assembly routine to enable /XIRQ

   TPA             \ get the CCR contents into accumulator A

   BF IMM   ANDA   \ clear the X bit in bit position 6

   TAP           \ put result in CCR, clearing X bit to enable /XIRQ

   RTS           \ return

END.CODE

 

XIRQ.ENABLE      \ execute the word to enable /XIRQ

Now that /XIRQ is enabled, your external interrupt handler will be called whenever the /XIRQ line on the I/O and Control Bus is pulled low.  A 68HC11 reset will again set the X bit.

QED-Forth’s Handling of Interrupts

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.  You then reference your service routine, specify the interrupt, and execute ATTACH to bind the service routine to the interrupt.

The following QED-Forth words have been defined as identifiers for the 68HC11 interrupts:

Table 6‑2                                                                                                                                                                                           Interrupt Identifiers

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) 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 8.5 µs with a 16 MHz crystal.  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 (or, for the /XIRQ interrupt, by the X 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 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 chapters 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() simply logically 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 later in this chapter.

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 in the next chapter.

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 library functions; the Glossary document and the header files in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory specify which functions are of the _forth type.  (For the curious: The reason is that _forth functions assume that the Y register has been pre-initialized to point to common RAM that is usable as a data stack.  Because C functions use the Y register for other purposes, it is difficult to ensure that the Y register is properly initialized for _forth functions upon entry into an interrupt routine. )

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

Interrupts (from QED Software Manual)

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

Kernel Services (QED-Forth Routines) that Disable Interrupts (from QED Software Manual)

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 of the C compiler. 

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

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

Reset Interrupts (from QED Hardware)

Special Reset-Type Interrupts (from QED Software Manual)

Forcing Cold Restarts (from QED Software Manual)

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)

Multitasking

The QVGA Controller provides a real time operating system that allows cooperative and preemptive multitasking.  Multitasking allows the programmer to partition the processor’s time among tasks so that time critical tasks don’t have to wait unnecessarily for the completion of less critical tasks.

For example, suppose that in a mission critical application an instrument must check several conditions frequently, say every 10 milliseconds, and if an error condition is detected immediately invoke a shut-down sequence or signal an alarm.  Suppose too, that the instrument must periodically compute the rms average value of a 1000-point waveform.  Written in C this latter task would take over a second.  Without a multitasker the processor would need to wait the full second for the completion of the computationally intensive task before returning to the time critical task. With multitasking, each activity is set up as an autonomously running task and the operating system switches rapidly between them at a rate the programmer chooses.  The tasks appear to be operating simultaneously.  If in this example we cause a task switch every 5 msec the time critical task would be guaranteed the opportunity to check for its error conditions at least every 10 msec.  In the majority of cases in which it does not find an error it can just return immediately to the other task, and is called again in 5 msec.  In this way the time critical task has guaranteed periodic use of the processor while still not significantly robbing time from the computationally intensive task.

Multitasking has other less obvious but equally important advantages: it allows a complex program to be decomposed into modules that can be coded and debugged independently; it simplifies the structure of the code making it more understandable; and, it allows an instrument to accomplish diverse tasks with less chance of unintentional interactions among them.

Multitasking requires little overhead on the QVGA Controller.  The following table provides the overhead for task switching and interrupts on a QVGA Controller clocked at 16 MHz:

Table 6‑4      Multitasking and Interrrupt Overhead Times

Operation

Time (microseconds)

Task switch time

30

Interrupt latency

9

Interrupt exit time

5

Introduction: What is Multitasking?

The Advantages of Multitasking (from QED Software Manual)

Cooperative and Pre-emptive Task Switching (from QED Software Manual)

Preemptive Task Switching

Cooperative Task Switching

Multitasking Lexicon (from QED Software Manual)

Define: Tasks and Users

Initializing the Multitasker and Elapsed Time Clock (from QED Software Manual)

About ACTIVATE()

When a new task is ACTIVATEd it inherits the condition code register at the time of activation, which includes the interrupt mask bit.  Consequently, before activating a task make sure that interrupts are globally enabled.  That way, when the task’s action routine is entered the first time, interrupts are enabled, ensuring operation of the timeslicer.  If interrupts are globally disabled when the task is activated, then when it is first entered interrupts will be disabled, and the timeslicer will stop – freezing execution in that task if you are using only preemptive multitasking.

Task Switch Latency

Setting Up a New Task (from QED Software Manual)

Implementation Details (from QED Software Manual)

Customizing a Task’s Use of Memory

Customizing a Task's Memory Map (from QED Software Manual)

The User Area

A Tour of the User Area (from QED Software Manual)

Discussion of the User Variables (from QED Software Manual)

Mailboxes and Shared Resources

Shared Resources (from QED Software Manual)

Shared Variables Mailboxes (from QED Software Manual)

Shared Memory: Uninterruptable Memory Operations (from QED Software Manual)

Heap

Serial Access to the Serial Port (from QED Software Manual)

SPI

Communication Between Tasks

Passing Data Between Tasks – Shared Variables

(protected variables?)

Task Latency

some kernel routines disable interrupts

Designing Re-Entrant Code

Designing Re-Entrant Code (from QED Software Manual)

Techniques for Ensuring Re-entrancy (from QED Software Manual)

Stack Frames (from QED Software Manual)

Recursion (from QED Software Manual)

Low Power Mode

Low Power Modes (from QED Hardware)

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

The Battery-Backed Real Time Clock

The QVGA Controller supports an optional battery-backed real time clock.  This smart watch and integrated battery are packaged as a sealed 32-pin DIP memory device.  When this device is installed in socket S3 on the QED-Flash Board, it provides a battery backed calendar and watch that can be set and read by built-in functions.  Accuracy is better than ±1 minute per month.  These sealed units are guaranteed to run for at least ten years. The watch correctly accounts for the differing numbers of days in each month, and also handles leap years.

Setting and Reading the Real Time Clock

The built-in library functions SetWatch() and ReadWatch() make it easy to set and read the real time clock.  These functions gain access to the smart watch by sending a special 64 bit pattern on the lowest order data bit; when the smart watch detects this pattern, it takes control of the next 64 read or write cycles to set or read the time. 

The Smart Watch Disables Interrupts for a Short Time

The routines that access the watch automatically disable all maskable interrupts while the watch is in control (about 0.5 msec).

The SetWatch() and ReadWatch() functions use the top 16 bytes of the 68HC11F1’s on-chip RAM as a buffer to hold intermediate results during the data transfers, and the top 8 bytes at addresses 0xB3F8-0xB3FF serve as the watch_results structure that contains the results returned by the most recent call to ReadWatch() as described below.

The battery-backed clock is pre-set at the factory to Pacific Time in the United States.  To re-set the smart watch to your time zone, your program can call the function:

 

void SetWatch(hundredth_seconds,seconds,minute,hour,day,date,month,year)

The interactively callable routine named SetTheWatch() is defined in the TIMEKEEP.C file so you can set the watch from your terminal.  As explained in the Control-C Glossary, the hour parameter ranges from 0 to 23, the day from 1 to 7 (Monday=1 in this example), and the year parameter ranges from 00 to 99.  For example, if it is now 10 seconds past 5:24 PM on Wednesday, May 26, 2004, you could interactively set the watch by typing:

SetTheWatch( 0, 10, 24, 17, 3, 26, 5, 4)

The watch is set and read using 24-hour time, where midnight is hour 0, noon is hour 12, and 11 PM is hour 23.  Note that you may assign any day of the week as “day number 1”.

The function ReadWatch() writes the current time and date information into a structure named watch_results that occupies the top 8 bytes of on-chip RAM at addresses 0xB3F8-0xB3FF. Pre-coded macros name the structure elements so it is easy to access the time and date information.  For example, the following simple function in the TIMEKEEP.C file reads the smart watch and prints the time and date:

 

_Q void SayDate(void)

{   ReadWatch();     // results are placed in watch_results structure

printf(“\nIt is now %d:%d:%d on %d/%d/%d.\n”,

WATCH_HOUR, WATCH_MINUTE, WATCH_SECONDS,

WATCH_MONTH, WATCH_DATE, WATCH_YEAR);

}            

It simply calls ReadWatch(), and then executes a printf() statement using the pre-coded structure macros to reference the time parameters in the watch_results structure.  If you called this function immediately after setting the watch as described in the prior section, the response at your terminal might be:

It is now 17:24:31 on 5/26/04.

After compiling TIMEKEEP.C, you can interactively type at your terminal:

SayDate( )

at any time to see a display of the current time and date.


 

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