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.
 

The Timeslicer and Task Switching

 

The Built-In Elapsed Time Clock

The PDQ Board’s multitasking executive maintains an elapsed time clock whenever the timeslicer is active. Please open the “Timekeep Demo” from within the Mosaic IDE Plus to follow along with this chapter:

Open a new “Timekeep Demo” project.

Look for this icon under Project→New Project:
real time application
Timekeep Demo

This elapsed time clock counter is based on the RTI (Real-Time Interrupt) in the HCS12 processor, so it does not use any of the timing resources provided by the processor’s Enhanced Capture/Timer (ECT) system. Your program can start the timeslice clock by calling the function:

StartTimeslicer()

The timeslicer increments the long (32-bit) variable named TIMESLICE_COUNT More… Close

TIMESLICE_COUNT

Returns the 32-bit count of the number of clock ticks on the timeslicer clock.  The count is set to zero by InitElapsedTime(), and the period of the clock is set by ChangeTaskerPeriod(); the default is 1.024 milliseconds (ms).  To determine the elapsed time between two events in units of ms, simply subtract the corresponding counts and multiply by the number of milliseconds per count.

See also ReadElapsedSeconds(), CountToMsec(), and InitElapsedTime()

Type: macro
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h

each timeslice period. The default timeslice period is approximately one millisecond (ms) (it’s 1.024 ms, to be exact). The timeslice/multitasker period can be changed from just over 1 ms to just over 15 ms by calling ChangeTaskerPeriod() as described in the C Glossary. In fact, because the RTI system works in multiples of 1.024 ms as configured, the number of milliseconds that you specify will by multiplied by 1.024.

Two convenient functions save you the trouble of converting from the TIMESLICE_COUNT More… Close

TIMESLICE_COUNT

Returns the 32-bit count of the number of clock ticks on the timeslicer clock.  The count is set to zero by InitElapsedTime(), and the period of the clock is set by ChangeTaskerPeriod(); the default is 1.024 milliseconds (ms).  To determine the elapsed time between two events in units of ms, simply subtract the corresponding counts and multiply by the number of milliseconds per count.

See also ReadElapsedSeconds(), CountToMsec(), and InitElapsedTime()

Type: macro
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h

timer contents to real time measured in milliseconds. CountToMsec() converts a 32-bit TIMESLICE_COUNT More… Close

TIMESLICE_COUNT

Returns the 32-bit count of the number of clock ticks on the timeslicer clock.  The count is set to zero by InitElapsedTime(), and the period of the clock is set by ChangeTaskerPeriod(); the default is 1.024 milliseconds (ms).  To determine the elapsed time between two events in units of ms, simply subtract the corresponding counts and multiply by the number of milliseconds per count.

See also ReadElapsedSeconds(), CountToMsec(), and InitElapsedTime()

Type: macro
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h

difference (elapsed number of timeslice counts) into the corresponding 32-bit number of milliseconds. The function ReadElapsedSeconds() returns a long result representing the number of elapsed seconds since InitElapsedTime() was called; InitElapsedTime() sets TIMESLICE_COUNT More… Close

TIMESLICE_COUNT

Returns the 32-bit count of the number of clock ticks on the timeslicer clock.  The count is set to zero by InitElapsedTime(), and the period of the clock is set by ChangeTaskerPeriod(); the default is 1.024 milliseconds (ms).  To determine the elapsed time between two events in units of ms, simply subtract the corresponding counts and multiply by the number of milliseconds per count.

See also ReadElapsedSeconds(), CountToMsec(), and InitElapsedTime()

Type: macro
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h

equal to zero. CountToMsec() and ReadElapsedSeconds() handle the 1.024 conversion factor, and are aware of the exact time value of each count as set by ChangeTaskerPeriod(); they report times with an accuracy better than 0.02%. At the default 1-ms resolution, the 32-bit TIMESLICE_COUNT More… Close

TIMESLICE_COUNT

Returns the 32-bit count of the number of clock ticks on the timeslicer clock.  The count is set to zero by InitElapsedTime(), and the period of the clock is set by ChangeTaskerPeriod(); the default is 1.024 milliseconds (ms).  To determine the elapsed time between two events in units of ms, simply subtract the corresponding counts and multiply by the number of milliseconds per count.

See also ReadElapsedSeconds(), CountToMsec(), and InitElapsedTime()

Type: macro
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h

“rolls over” to zero in just under 50 days, and this sets a maximum limit on the elapsed time that these functions can report.

We can write a simple function that converts the TIMESLICE_COUNT More… Close

TIMESLICE_COUNT

Returns the 32-bit count of the number of clock ticks on the timeslicer clock.  The count is set to zero by InitElapsedTime(), and the period of the clock is set by ChangeTaskerPeriod(); the default is 1.024 milliseconds (ms).  To determine the elapsed time between two events in units of ms, simply subtract the corresponding counts and multiply by the number of milliseconds per count.

See also ReadElapsedSeconds(), CountToMsec(), and InitElapsedTime()

Type: macro
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h

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 Demo”:

#define MS_PER_SECOND  1000
static long start_time = 0;     // saves starting count of TIMESLICE_COUNT
 
void _Q MarkTime(void)
{   start_time = TIMESLICE_COUNT;
}
 
void _Q PrintElapsedTime(void)
// prints elapsed time since MarkTime() was executed until this function
// was called; prints as seconds and ms;
// resolution is equal to the period of the timeslice clock (approx. 1 ms default)
{   long elapsed_ms = CountToMsec((ulong) (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 More… Close

TIMESLICE_COUNT

Returns the 32-bit count of the number of clock ticks on the timeslicer clock.  The count is set to zero by InitElapsedTime(), and the period of the clock is set by ChangeTaskerPeriod(); the default is 1.024 milliseconds (ms).  To determine the elapsed time between two events in units of ms, simply subtract the corresponding counts and multiply by the number of milliseconds per count.

See also ReadElapsedSeconds(), CountToMsec(), and InitElapsedTime()

Type: macro
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h

in the start_time variable. After StartTimeslicer() has been executed (by main in this example program), the timeslicer continually increments its counter. When you later call PrintElapsedTime(), start_time is subtracted from the latest TIMESLICE_COUNT More… Close

TIMESLICE_COUNT

Returns the 32-bit count of the number of clock ticks on the timeslicer clock.  The count is set to zero by InitElapsedTime(), and the period of the clock is set by ChangeTaskerPeriod(); the default is 1.024 milliseconds (ms).  To determine the elapsed time between two events in units of ms, simply subtract the corresponding counts and multiply by the number of milliseconds per count.

See also ReadElapsedSeconds(), CountToMsec(), and InitElapsedTime()

Type: macro
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h

, and CountToMsec() converts the resulting elapsed count into 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, make a new “Timekeep Demo” project, click Build→Build, and use the terminal to send timekeep.dlf to the PDQ 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.
 

Using Interrupt Service Routines (ISRs)

The on-chip resources of the HCS12 microcontroller include a 16-channel 10-bit A/D converter, Pulse-Width Modulated (PWM) outputs on PortP, Enhanced Capture/Timer (ECT) I/O on PortT, watchdog timer (COP), clock monitor, maskable and non-maskable external interrupt pins, Real-Time Interrupt (RTI), dual serial communications ports, high speed serial peripheral interface (SPI), inter-IC (IIC) serial bus, and general purpose digital I/O. The ECT timer system provides input captures, output compares, pulse accumulators, a modulus down-counter, and more. Dozens of interrupts associated with these functions can enhance the real-time performance of the PDQ Controller. Interrupts allow rapid response to time-critical events that often occur in measurement and control applications.

Control of these I/O and timing functions is simplified by built-in drivers that are described in this User Guide. The processor's interrupt sources which can be used by the programmer are named with constants declared in the INTERRUPT.h file in the \INCLUDE\MOSAIC directory. (The reset, FLASH and EEPROM interrupt identifiers are not named, because the operating system deals with these). Examples of these identifiers include ECT1_ID More… Close

ECT1_ID

A constant that returns the interrupt identity code for the enhanced counter/timer (ECT) channel 1 interrupt.  Used as an argument for ATTACH().

Type: constant
Related Forth function: ECT1.ID
Header file: interrupt.h

, PULSE_EDGE_ID, IIC_ID More… Close

IIC_ID

A constant that returns the interrupt identity code for the IIC (Inter-IC) bus 2-wire multi-drop) serial port.  This bus is sometimes referred to as the I-squared-C (I2C) bus.  Used as an argument for ATTACH().

Type: constant
Related Forth function: IIC.ID
Header file: interrupt.h

, etc. These identifiers are passed to the ATTACH() routine as described below to post an interrupt handler to respond to the named interrupt.

 

Interrupt Recognition and Servicing

The processor's 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 trap, 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. 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 More… Close

RTI ( -- )

Compiles the opcode sequence for the RTI instruction into the dictionary. When later executed, this code restores accumulators A and B, the condition code register CC, and registers X, Y and PC with values pulled from the stack.
Pronunciation: “return-from-interrupt”

(return from interrupt) instruction that unstacks the programming registers, re-enables interrupts by clearing the I bit in the CCR (condition code register), and resumes execution of the previously executing program.

In most cases, the interrupt handler must reset the interrupt flag bit (not the mask bit) by writing a 1 to it. (If the “fast clear” mode of a hardware subsystem such as the ECT timer or ATD converter is enabled, then the interrupt flag bit is reset automatically when an associated hardware register is accessed). The interrupt handler must perform any tasks necessary to service the interrupt before exiting. When the interrupt handler has finished, normal program flow resumes, and any other pending interrupts can be serviced.

 

Nonmaskable Interrupts

Six of the processor's 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, 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:

  1. Power-on or activation of the reset button
  2. Computer-Operating-Properly (COP) timeout
  3. 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. The operating system 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. The following table gives the name of each nonmaskable interrupt and a description of its operation.

Table 6-1 Nonmaskable Interrupts.
Interrupt NameDescription
ResetRecognized 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 operating system restart sequence.
Clock Monitor FailureEnabled or disabled via the CME (clock monitor enable) bit in the PLLCTL register, this interrupt is recognized if the external oscillator frequency drops below 200 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 FailureAfter 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. The operating system installs a default service routine for this interrupt that performs the standard restart sequence.
Illegal Opcode TrapThis interrupt occurs when the processor encounters an unknown opcode. The operating system installs a default service routine for this interrupt that performs the standard restart sequence.
SWISoftware 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.
/XIRQEnabled 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 entries for InitVitalIRQsOnCold() and NoVitalIRQInit() 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.

 

Servicing Maskable Interrupts

Maskable interrupts are controlled by the I bit in the condition code register. 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 typically clears the interrupt flag bit set by the trigger event and performs any tasks necessary to service the interrupt. The service routine that you post terminates with a standard RTS or RTC opcode as do all other functions; the ATTACH() routine supplies the required 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 are Not Recommended

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 31 available maskable interrupts in order of highest to lowest priority. Each entry lists the constant identifier (defined in the INTERRUPT.h file) that can be passed to ATTACH() to post an interrupt handler, the register and bitname of the local interrupt mask, the value that must be stored into the HIPRIO register to elevate this interrupt to have the highest priority, and a description of the interrupt.

Table 6-1 Maskable Interrupts, from Highest to Lowest Priority.
IRQ Identifier
(passed to ATTACH() to post an interrupt handler)
Enable Register
(and bitname)
Hiprio
to elevate
Description
IRQ_IDIRQCR (IRQEN)0xF2IRQ is an active-low external hardware interrupt which is recognized when the signal on the /IRQ pin of the HCS12 is pulled low.
RTI_IDCRGINT (RTIE)0xF0The RTI provides a programmable periodic interrupt
ECT0_IDTIE (C0I)0xEEAn IC interrupt is recognized when a specified signal transition is sensed on PortT0; an OC interrupt is recognized when TCNT becomes equal to the TC0 timer compare register.
ECT1_IDTIE (C1I)0xECAn IC interrupt is recognized when a specified signal transition is sensed on PortT1; an OC interrupt is recognized when TCNT becomes equal to the TC1 timer compare register.
ECT2_IDTIE (C2I)0xEAAn IC interrupt is recognized when a specified signal transition is sensed on PortT2; an OC interrupt is recognized when TCNT becomes equal to the TC2 timer compare register.
ECT3_IDTIE (C3I)0xE8An IC interrupt is recognized when a specified signal transition is sensed on PortT3; an OC interrupt is recognized when TCNT becomes equal to the TC3 timer compare register.
ECT4_IDTIE (C4I)0xE6An IC interrupt is recognized when a specified signal transition is sensed on PortT4; an OC interrupt is recognized when TCNT becomes equal to the TC4 timer compare register.
ECT5_IDTIE (C5I)0xE4An IC interrupt is recognized when a specified signal transition is sensed on PortT5; an OC interrupt is recognized when TCNT becomes equal to the TC5 timer compare register.
ECT6_IDTIE (C6I)0xE2An IC interrupt is recognized when a specified signal transition is sensed on PortT6; an OC interrupt is recognized when TCNT becomes equal to the TC6 timer compare register.
ECT7_IDTIE (C7I)0xE0An IC interrupt is recognized when a specified signal transition is sensed on PortT7; an OC interrupt is recognized when TCNT becomes equal to the TC7 timer compare register.
ECT_OVERFLOW_IDTSRC2 (TOF)0xDEA timer overflow interrupt is recognized when TCNT rolls over from 0xFFFF to 0x0000.
PULSE_A_OVERFLOW_IDPACTL (PAOVI)0xDCA Pulse Accumulator A overflow interrupt is recognized when the 16-bit Pulse Accumulator A overflows from 0xFFFF to 0x0000, or when 8-bit Pulse Accumulator 3 on PT3 overflows from 0xFF to 0x00.
PULSE_EDGE_IDPACTL (PAI)0xDAA Pulse Accumulator A interrupt is recognized when the selected edge is detected at the PT7 input pin.
SPI0_IDSPI0CR1 (SPIE, SPTIE)0xD8An SPI channel0 interrupt is recognized after the eighth SCK in a data transfer, or when a mode fault is detected (if /SS input goes low while the SPI channel is configured as a master).
SCI0_IDSCI0CR2 (TIE, TCIE, RIE, ILIE)0xD6An SCI0 (PDQ serial port 1) interrupt is recognized if the transmit data register is empty, or the transmission is complete, or the receive data register is full, or the serial line is idle.
SCI1_IDSCI1CR2 (TIE, TCIE, RIE, ILIE)0xD4An SCI1 (PDQ serial port 2) interrupt is recognized if the transmit data register is empty, or the transmission is complete, or the receive data register is full, or the serial line is idle.
ATD0_IDATD0CTL2 (ASCIE)0xD2An interrupt is recognized when the requested Analog-To-Digital subsystem 0 (pins AN0-7) sequence has completed.
ATD1_IDATD1CTL2 (ASCIE)0xD0An interrupt is recognized when the requested Analog-To-Digital subsystem 1 (pins AN8-15) sequence has completed.
PORTJ_IDPIEJ (PIEJx)0xCEAn interrupt is recognized when a specified signal edge (configured using the PPSJ register) occurs on the corresponding PortJ pin.
PORTH_IDPIEH (PIEHx)0xCCAn interrupt is recognized when a specified signal edge (configured using the PPSH register) occurs on the corresponding PortH pin.
COUNTER_UNDERFLOW_IDMCCTL (MCZI)0xCAA Modulus Counter underflow interrupt is recognized when the 16-bit MCCNT modulus down-counter register underflows.
PULSE_B_OVERFLOW_IDPBCTL (PBOVI)0xC8A Pulse Accumulator B overflow interrupt is recognized when the 16-bit Pulse Accumulator B overflows from 0xFFFF to 0x0000, or when 8-bit Pulse Accumulator 1 on PT1 overflows from 0xFF to 0x00.
PLL_LOCK_IDCRGINT (LOCKIE)0xC6An interrupt is recognized when the Phase-Locked Loop clock generator is locked onto the target frequency.
SELF_CLOCK_IDCRGINT (SCMIE)0xC4An interrupt is recognized when the status of the processor clock changes to or from normal to self-clocked mode.
IIC_IDIBCR (IBIE)0xC0An IIC bus interrupt is recognized when bus arbitration is lost, a byte transfer is complete, or the IIC bus controller has been addressed as a slave.
SPI1_IDSPI1CR1 (SPIE, SPTIE)0xBEAn SPI channel1 interrupt is recognized after the eighth SCK in a data transfer, or when a mode fault is detected (if /SS input goes low while the SPI channel is configured as a master).
SPI2_IDSPI2CR1 (SPIE, SPTIE)0xBCAn SPI channel2 interrupt is recognized after the eighth SCK in a data transfer, or when a mode fault is detected (if /SS input goes low while the SPI channel is configured as a master).
EEPROM_IDECNFG (CCIE, CBEIE)0xBAAn interrupt is recognized during programming of the EEPROM on the HCS12 chip. The operating system manages EEPROM programming, so EEPROM_ID is not defined.
FLASH_IDFCNFG (CCIE, CBEIE)0xB8An interrupt is recognized during programming of the on-chip Flash memory on the HCS12 chip. The operating system manages Flash programming, so FLASH_ID is not defined.
PORTP_IDPIEP (PIEPx)0x8EAn interrupt is recognized when a specified signal edge (configured using the PPSP register) occurs on the corresponding PortP pin.
PWM_SHUTDOWN_IDPWMSDN (PWM7ENA, PWMIE)0x8CAn interrupt is recognized if the PWM emergency shutdown feature is enabled, and a signal edge occurs on a PWM pin.
 

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 PDQ Board'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.

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 (by writing a 1 to it) 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.

 

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 HCS12 processor. The / prefix to each of these names indicates that the signals are active-low. Pull-up resistors on the PDQ Board 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 pin is typically not available on the PDQ Board; contact Mosaic Industries if you need to access this pin.

The /IRQ pin is accessed and controlled via the Wildcard Port Headers (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 PORTH, PORTJ, PORTP, and PORTT pins can also be configured to interrupt the processor when an external event occurs.

 

Configuring /IRQ Interrupt

In its default state, after each reset or restart, the /IRQ pin is configured as an edge-triggered input. In this mode, the HCS12 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) which is bit 7 in the IRQCR register. IRQE is a “write once bit”; after you write to it one time, subsequent writes have no effect until a hardware reset occurs. To enable level-sensitive /IRQ operation, write 0x40 to the IRQCR register. No modification of the IRQCR register is needed if the default edge-sensitive /IRQ operation is acceptable.

 

Using /IRQ

To use the /IRQ external interrupt, define an interrupt handler and install it using the pre-defined identifier IRQ_ID More… Close

IRQ_ID

A constant that returns the interrupt identity code for the external interrupt request interrupt.  Used as an argument for ATTACH().

Type: constant
Related Forth function: IRQ.ID
Header file: interrupt.h

and the interrupt ATTACH() utility as described in the C Glossary. If interrupts have not yet been enabled globally, then execute ENABLE_INTERRUPTS More… Close

ENABLE_INTERRUPTS( void)

Clears the interrupt mask bit (the I bit) in the condition code register to globally enable interrupts.

Type: Macro
Related Forth function: ENABLE.INTERRUPTS
Header file: interrupt.h

. Whenever the /IRQ pin 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.

 

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 ”Functions that disable interrupts” section of the C Glossary. A review of that list will assist you in planning the time-critical aspects of your application.

 

How To Temporarily Disable Interrupts in Functions

In multitasking applications, it is sometimes necessary to disable interrupts for brief periods. For example, let's assume that a 32-bit floating point variable is written to and read by several different tasks, and that the timeslice multitasker (based on the RTI interrupt) is running. We want to avoid the possibility of reading a timeslice interrupt occurring between the accesses of the first and second 16-bit portions of this floating point value, as this could lead to a corrupted access.

The C keyword functions lock() and restore() solve this problem. Inside the function where you need to disable interrupts, declare inside the function an unsigned integer (uint) local variable, and assign to it the return value of the lock() function. The lock() function retrieves and returns the prior condition code register (CCR) contents that contain the I-bit which determines whether interrupts are globally disabled. Then the lock() function globally disables interrupts by setting the I bit. Place the function lines that are to operate while interrupts are disabled after the lock() function invocation. To restore interrupts to their prior state, pass the local variable that holds the lock() return value to the restore() function.

The TURNKEY.c program file discussed in the ”A Turnkeyed C Application Program” chapter contains two examples of this approach. One is presented in the following code listing for a function that temporarily disables interrupts, writes to a floating point variable, and restores interrupts to their prior state (enabled or disabled).

float  _Q PeekFloatUninterrupted(float *  source )
// fetches and returns the contents from the address specified by source;
// the lock() and restore() keywords guarantee that
// interrupts will be disabled while the fetch operation occurs;
// this prevents corruption of data when two tasks are accessing the same
// 4-byte variables
{  float source_contents;          // declare local variables
   uint prior_ccr_state;           // this will hold the ccr contents from lock()
   prior_ccr_state = lock();       // save prior I-bit, disable irqs
   source_contents =  *source;     // fetch the value while irqs are disabled
   restore(prior_ccr_state);       // restore prior I-bit state
   return source_contents;
}

The lock() and restore() functions are not in the C glossary because they are reserved keywords defined in the GNU C compiler source. Do not confuse the lower case restore() function described here with the QED-Forth operating system/debugger RESTORE More… Close

RESTORE ( -- )

Restores the memory map user variables stored by the last execution of SAVE to their respective user variables.  Restores DP, NP, VP, EEP, last xnfa in the FORTH vocabulary, CURRENT.HEAP, THIS.SEGMENT, and LAST.SEGMENT from a reserved area in EEPROM to the values stored by SAVE.  Useful for dictionary management and for recovery from crashes.

See also SAVE

function which restores memory map parameters.

 

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. This latency includes the time required to re-vector the service request via the EEPROM to allow the programmer to modify the vectors, and to change the page. The latency of service routines installed with ATTACH() is 75 machine cycles, or 3.75 µs. That is, the first opcode of the user's service routine is executed 3.75 µs after interrupt service begins. After the service routine's concluding RTS More… Close

RTS ( -- )

Compiles the opcode sequence for the RTS instruction into the dictionary. When later executed, this code restores the program counter with a value pulled from the stack, thereby resuming execution just after the point where the subroutine was called. Use this instruction to terminate subroutines invoked with JSR.

See also RTC
Pronunciation: “return-from-subroutine”

executes, an additional 56 cycles (2.8 µs) lapses before the originally interrupted program resumes execution.

Interrupt Latency
Time to Enter an Interrupt Service Routine: 3.75 µsec
Time to Leave an Interrupt Service Routine: 2.8 µ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 (automatically compiled by ATTACH More… Close

ATTACH ( xcfa\n -- | n = interrupt identity number )

Posts an interrupt handler routine specified by xcfa for the interrupt with identity number n (e.g., ATD0.ID, ATD1.ID, etc.) Compiles an 8-byte code sequence at the EEPROM location associated with the specified interrupt.  When the interrupt is serviced, the code at xcfa will be executed.  The xcfa can be on any page.  If coded in high level, the interrupt handler routine should end with a ; and if coded in assembly should end with an RTS (as opposed to an RTI).

Implementation details: The runtime code compiled by this word saves the registers and the contents of the headerless system variable c/forth.dstack.ptr, and then writes the contents of the headerless system variable irq.dstack.save (default contents = 0x2000) into Y and into c/forth.dstack.ptr.  This ensures that forth functions invoked from C via PUSH.FORTH.PARAMS (see its glossary entry) will work properly.  The runtime code also initializes S0 in the current user area so DEPTH works.  Then the runtime code calls the specified interrupt service routine.  When the service routine returns, the runtime code restores Y, S0, c/forth.dstack.ptr, and the page register, then returns from interrupt.  This runtime code allows any foreground routine to temporarily corrupt the Y register (which is the forth data stack pointer) and/or use extended math operands that change Y without fear that an interrupt routine will rely on the contents of Y for its data stack.  Interrupt latency is 3.75 microseconds until the service routine is entered.  Exit latency after the service routine returns is 2.8 microseconds, resulting in a total latency of 6.55 microseconds.

) 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 More… Close

ATTACH ( xcfa\n -- | n = interrupt identity number )

Posts an interrupt handler routine specified by xcfa for the interrupt with identity number n (e.g., ATD0.ID, ATD1.ID, etc.) Compiles an 8-byte code sequence at the EEPROM location associated with the specified interrupt.  When the interrupt is serviced, the code at xcfa will be executed.  The xcfa can be on any page.  If coded in high level, the interrupt handler routine should end with a ; and if coded in assembly should end with an RTS (as opposed to an RTI).

Implementation details: The runtime code compiled by this word saves the registers and the contents of the headerless system variable c/forth.dstack.ptr, and then writes the contents of the headerless system variable irq.dstack.save (default contents = 0x2000) into Y and into c/forth.dstack.ptr.  This ensures that forth functions invoked from C via PUSH.FORTH.PARAMS (see its glossary entry) will work properly.  The runtime code also initializes S0 in the current user area so DEPTH works.  Then the runtime code calls the specified interrupt service routine.  When the service routine returns, the runtime code restores Y, S0, c/forth.dstack.ptr, and the page register, then returns from interrupt.  This runtime code allows any foreground routine to temporarily corrupt the Y register (which is the forth data stack pointer) and/or use extended math operands that change Y without fear that an interrupt routine will rely on the contents of Y for its data stack.  Interrupt latency is 3.75 microseconds until the service routine is entered.  Exit latency after the service routine returns is 2.8 microseconds, resulting in a total latency of 6.55 microseconds.

() macro. We'll look at this process in detail, then discuss how interrupts are implemented on the HCS12.

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 documentation and the HCS12REGS.h file (in the C:\MosaicPlus\c\libraries\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. In most cases, 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, but it must be followed by MAKE_ISR(functionName). This call to MAKE_ISR() must be in the global scope (not inside any function) and should come directly after the closing } of the interrupt service routine.
  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 More… Close

    RTI ( -- )

    Compiles the opcode sequence for the RTI instruction into the dictionary. When later executed, this code restores accumulators A and B, the condition code register CC, and registers X, Y and PC with values pulled from the stack.
    Pronunciation: “return-from-interrupt”

    (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 More… Close

    I ( -- w )
    Return stack: ( R: w -- w )

    Places a copy of the current (innermost) loop index on the data stack.  Cannot be used if additional items have been placed on the return stack (for example, using >R).  Typical use:

    DO ... I ...  LOOP
    

    or

    DO ... I ... +LOOP
    

    or

    FOR ... I ... NEXT
    


    Pronunciation: i
    Attributes: C

    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 More… Close

ATTACH ( xcfa\n -- | n = interrupt identity number )

Posts an interrupt handler routine specified by xcfa for the interrupt with identity number n (e.g., ATD0.ID, ATD1.ID, etc.) Compiles an 8-byte code sequence at the EEPROM location associated with the specified interrupt.  When the interrupt is serviced, the code at xcfa will be executed.  The xcfa can be on any page.  If coded in high level, the interrupt handler routine should end with a ; and if coded in assembly should end with an RTS (as opposed to an RTI).

Implementation details: The runtime code compiled by this word saves the registers and the contents of the headerless system variable c/forth.dstack.ptr, and then writes the contents of the headerless system variable irq.dstack.save (default contents = 0x2000) into Y and into c/forth.dstack.ptr.  This ensures that forth functions invoked from C via PUSH.FORTH.PARAMS (see its glossary entry) will work properly.  The runtime code also initializes S0 in the current user area so DEPTH works.  Then the runtime code calls the specified interrupt service routine.  When the service routine returns, the runtime code restores Y, S0, c/forth.dstack.ptr, and the page register, then returns from interrupt.  This runtime code allows any foreground routine to temporarily corrupt the Y register (which is the forth data stack pointer) and/or use extended math operands that change Y without fear that an interrupt routine will rely on the contents of Y for its data stack.  Interrupt latency is 3.75 microseconds until the service routine is entered.  Exit latency after the service routine returns is 2.8 microseconds, resulting in a total latency of 6.55 microseconds.

it to a specified interrupt. You define your service routine in either assembly code or in high level C. After the closing bracket of your function, call MAKE_ISR() with the function name as the single paramater. You then call ATTACH() to bind the service routine to the interrupt. The ATTACH() macro expects as inputs a function pointer to your service routine, and an interrupt identifier as defined in the C:\MosaicPlus\c\libraries\include\mosaic\interrupt.h file. The interrupt identifiers are listed in table above. ATTACH() sets up the interrupt vector in EEPROM so that subsequent interrupts will execute the specified service routine. The code installed by ATTACH More… Close

ATTACH ( xcfa\n -- | n = interrupt identity number )

Posts an interrupt handler routine specified by xcfa for the interrupt with identity number n (e.g., ATD0.ID, ATD1.ID, etc.) Compiles an 8-byte code sequence at the EEPROM location associated with the specified interrupt.  When the interrupt is serviced, the code at xcfa will be executed.  The xcfa can be on any page.  If coded in high level, the interrupt handler routine should end with a ; and if coded in assembly should end with an RTS (as opposed to an RTI).

Implementation details: The runtime code compiled by this word saves the registers and the contents of the headerless system variable c/forth.dstack.ptr, and then writes the contents of the headerless system variable irq.dstack.save (default contents = 0x2000) into Y and into c/forth.dstack.ptr.  This ensures that forth functions invoked from C via PUSH.FORTH.PARAMS (see its glossary entry) will work properly.  The runtime code also initializes S0 in the current user area so DEPTH works.  Then the runtime code calls the specified interrupt service routine.  When the service routine returns, the runtime code restores Y, S0, c/forth.dstack.ptr, and the page register, then returns from interrupt.  This runtime code allows any foreground routine to temporarily corrupt the Y register (which is the forth data stack pointer) and/or use extended math operands that change Y without fear that an interrupt routine will rely on the contents of Y for its data stack.  Interrupt latency is 3.75 microseconds until the service routine is entered.  Exit latency after the service routine returns is 2.8 microseconds, resulting in a total latency of 6.55 microseconds.

includes the RTI More… Close

RTI ( -- )

Compiles the opcode sequence for the RTI instruction into the dictionary. When later executed, this code restores accumulators A and B, the condition code register CC, and registers X, Y and PC with values pulled from the stack.
Pronunciation: “return-from-interrupt”

instruction that terminates the interrupt service sequence.

Don't forget to call MAKE_ISR(). Without it your project will fail to compile giving an error message similar to this:

timekeep.c:137: error: 'FunctionTimer_ISR_xaddr' undeclared (first use in this function)

 

Implementation Details

The interrupt vectors near the top of memory are in write-protected on-chip kernel flash, locations that cannot be modified by the programmer. The contents of these locations point to a series of locations in the EEPROM which can be modified via the ATTACH() routine. 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 More… Close

RTI ( -- )

Compiles the opcode sequence for the RTI instruction into the dictionary. When later executed, this code restores accumulators A and B, the condition code register CC, and registers X, Y and PC with values pulled from the stack.
Pronunciation: “return-from-interrupt”

(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 More… Close

RTI ( -- )

Compiles the opcode sequence for the RTI instruction into the dictionary. When later executed, this code restores accumulators A and B, the condition code register CC, and registers X, Y and PC with values pulled from the stack.
Pronunciation: “return-from-interrupt”

, your service routine can end with a standard RTC More… Close

RTC ( -- )

Compiles the opcode sequence for the RTC instruction into the dictionary. When later executed, this code restores the page (PPAGE register contents) with a byte pulled from the stack, and restores the program counter PC with a 16-bit value pulled from the stack, thereby resuming execution just after the point where the subroutine was called. Use this instruction to terminate subroutines invoked with CALL,.

See also RTS
Pronunciation: “return-from-call”

, return (in assembly code) or } (in high-level C) which makes debugging much easier.

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 once per second.

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

 
// The default prescaler set by warm/code in TSCR2 is decimal 32,
// resulting in a free-running TCNT with 1.6us period and a 104.8ms rollover time.
// Available prescales are 1 through 128 in powers of 2 (0.05us - 6.4us);
// see ECTPrescaler().
 
// #define OC3_MASK  0x20  // could be used to set/clear OC3 IRQ flag and mask,
                           // but we use high-level functions to do this,
                           // simply by passing the argument 3 to indicate OC3
#define ONE_MS  625     // 625 counts of 1.6us TCNT = 1 ms
#define DEFAULT_TIME_PERIOD  1000   // Execute TheFunction() once per second
                                    // (that is, every 1000 milliseconds)
 
static int ms_counter = 0;  // runs from 0 to 65,535 before rolling over
static uint time_period = DEFAULT_TIME_PERIOD;
       // specifies time in ms between calls to TheFunction()
       // making time_period a variable allows you to change it interactively
static int next_execution_time = DEFAULT_TIME_PERIOD;
       // value of ms_counter when TheFunction() is scheduled to be called next
static int action_variable = 0;     // state is toggled by TheFunction()
 
_Q void TheFunction(void)
{   action_variable = !action_variable;
}
 
_Q void FunctionTimer(void)
// OC3-based clock, calls TheFunction() periodically
{   ms_counter++;
    if(ms_counter == next_execution_time)
        {   TheFunction();
            next_execution_time = next_execution_time + time_period;
        }
    TC3 += ONE_MS;      // set OC3 count for next interrupt in 1 ms.
                        // A slower way to do this is: OCRegWrite(TCNT+ONE_MS, 3);
                        // Because we executed ECTFastClear(), this access to TC3
                        // automatically resets the OC3 interrupt flag
}                       // so that new OC3 interrupts will be recognized.
 
_Q void StopFunctionTimer(void)
{   ECTInterruptDisable(3);     // locally disable OC3, same as: TIE &= ~OC3_MASK;
}
 
_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, ECT3_ID);  // post the interrupt service routine
    ECTFastClear();
    ms_counter = 0;
    time_period = next_execution_time = DEFAULT_TIME_PERIOD;   // once per second
    action_variable = 0;     // state is toggled by TheFunction()
    OCAction(OC_NO_ACTION, 3); // confirm that no automatic pin action occurs on PT3
    OutputCompare(3);        // set channel 3 as output compare
    OCRegWrite(TCNT+ONE_MS, 3);  // starts in 1 ms, same as: TC3 = TCNT + ONE_MS;
    ECTClearInterruptFlag(3);    // clear flag, same as: TFLG1 = OC3_MASK;
    ECTInterruptEnable(3);       // locally enable OC3, same as: TMSK1 |= OC3_MASK;
}

Because a full suite of device driver functions for the ECT (Enhanced Capture Timer) system is included in the operating system, you don’t have to research all the relevant registers and define lots of bitmask constants. Rather, you can let the driver functions (described in detail in a later chapter) do the work for you.

This program uses the OC3 (Output Compare 3) interrupt to perform the timing. Output compare 3 can generate an interrupt when the value of the free-running TCNT register matches the value in the TC3 register. This program configures OC3 to generate an interrupt every millisecond (ms), and to call TheFunction() every 1000 ms. While OC3 can optionally control PortT pin 3, this program disables pin control by specifying OC_NO_ACTION More… Close

OC_NO_ACTION

A constant equal to 0 that is passed as an argument to the OCAction() function.  Configures the output compare action of specified the PORTT timer pin to take no action when there is a match between the free-running TCNT register and the 16-bit value in the TC register associated with the specified channel_id.  See OCAction(), OCRegWrite(), OCICRegWrite(), and TCNTRead().  Note that the specified channel should be declared as an output compare by invoking the OutputCompare() function; see its glossary entry.  The no-action state is the default after a power-up or hardware reset.

See also OC_TOGGLE_ACTION, OC_CLEAR_ACTION, and OC_SET_ACTION

Type: macro
Related Forth function: OC.NO.ACTION
Header file: timerio.h

in the call to the OCAction() function in StartFunctionTimer().

The free-running TCNT timer has a default period of 1.6 microseconds; this can be changed using the ECTPrescaler() function. Using the default, we define the ONE_MS constant equal to 625, because 625 times 1.6 microseconds equals one millisecond. We define the DEFAULT_TIME_PERIOD constant as 1000 milliseconds so that TheFunction() will be invoked once per second.

The ms_counter variable is incremented once per millisecond, and we define a time_period variable so you can change it interactively using the interactive debugger commands described in prior chapters. The next_execution_time variable keeps track of when the next 1000 millisecond count will be reached. The action_variable is toggled by TheFunction(), and can be examined using the interactive debugger or the See() function as defined in the TIMEKEEP.c file.

The FunctionTimer() routine is the interrupt service routine for the OC3 interrupt posted by ATTACH(). When called each millisecond, it increments the ms_counter and, if 1000 counts have elapsed, calls TheFunction() and updates the next_execution_time variable. The FunctionTimer() routine then adds the ONE_MS constant to the TC3 register so that the next interrupt will occur in 1 ms. Because the StartFunctionTimer() initialization function invokes ECTFastClear(), the FunctionTimer() interrupt service routine does not have to explicitly clear the interrupt flag bit. Rather, the HCS12 hardware automatically clears the flag when the TC3 register is accessed by the interrupt service routine.

StartFunctionTimer() initializes the program. It first locally disables the OC3 interrupt by calling StopFunctionTimer() so there are no unwanted interrupts during the setup process. It passes a pointer to the FunctionTimer function, and the OC3_ID interrupt specifier to ATTACH() to post the service routine. It calls ECTFastClear() so that the service routine does not have to explicitly clear the interrupt flag bit by writing a 1 to it. After initializing the variables, StartFunctionTimer() invokes OCAction() and OutputCompare() to configure ECT channel 3 as an output compare that does not control its port pin. To set the first OC3 interrupt to occur in 1 ms, it calls OCRegWrite(), passing the current value of TCNT plus the ONE_MS constant as the value to be stored in the TC3 register. StartFunctionTimer() concludes by clearing the OC3 interrupt flag (to avoid an immediate interrupt), and locally enabling the interrupt mask by calling ECTInterruptEnable(3).

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 TC3, and an extra delay of 105 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 A Turnkeyed C Application Program chapter.

 

Cautions and Restrictions

Note that the RTI real-time 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.

“Blocking” functions which call the Pause() (task-switch) function should not be called from within an interrupt service routine. This would cause interrupts to remain disabled while waiting and pausing, which typically plays havoc with real-time applications. The C Glossary includes a list of functions that call Pause().

Unlike prior HC11-based kernels, the PDQ HCS12 operating system does not restrict the use of kernel functions in interrupt service routines.

 

Summary

Using interrupts requires:

  • coding an interrupt service routine;
  • using ATTACH() to bind it to the appropriate interrupt; and,
  • enabling its local interrupt mask.



See also → Loading Your Program Into Memory

 
 
 
Registration on or use of this site constitutes acceptance of our User Agreement and Privacy Policy. Purchase of Mosaic's products constitutes acceptance of the End User License Agreement, Sales Terms and Conditions, and Life Support policy. Mosaic’s products are not authorized for use as components in life support or medical devices. The material on this site may not be reproduced, distributed, transmitted, cached or otherwise used, except with the prior written permission of Mosaic Industries, Inc. You are encouraged to link to pages of this site.
/usr/home/mosai7/www/htdocs/embedded-systems/data/pages/10-sbcs/pdqboard/users-guide/081_real_time_programming.txt
Last modified: 2010-09-06 11:21 (external edit)   © Mosaic Industries, Inc. All rights reserved.