This chapter provides an introduction to real time programming. You’ll learn:
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:
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
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
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
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
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
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h
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
#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
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
Related Forth function: TIMESLICE.COUNT
Header file: mtasker.h
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.
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
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: ECT1.ID
Header file: interrupt.h
Related Forth function: IIC.ID
Header file: interrupt.h
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”
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.
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:
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 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 operating system restart sequence. |
| Clock Monitor Failure | Enabled 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 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. The operating system 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. The operating system 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 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.
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:
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.
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.
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_ID | IRQCR (IRQEN) | 0xF2 | IRQ is an active-low external hardware interrupt which is recognized when the signal on the /IRQ pin of the HCS12 is pulled low. |
| RTI_ID | CRGINT (RTIE) | 0xF0 | The RTI provides a programmable periodic interrupt |
| ECT0_ID | TIE (C0I) | 0xEE | An 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_ID | TIE (C1I) | 0xEC | An 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_ID | TIE (C2I) | 0xEA | An 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_ID | TIE (C3I) | 0xE8 | An 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_ID | TIE (C4I) | 0xE6 | An 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_ID | TIE (C5I) | 0xE4 | An 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_ID | TIE (C6I) | 0xE2 | An 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_ID | TIE (C7I) | 0xE0 | An 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_ID | TSRC2 (TOF) | 0xDE | A timer overflow interrupt is recognized when TCNT rolls over from 0xFFFF to 0x0000. |
| PULSE_A_OVERFLOW_ID | PACTL (PAOVI) | 0xDC | A 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_ID | PACTL (PAI) | 0xDA | A Pulse Accumulator A interrupt is recognized when the selected edge is detected at the PT7 input pin. |
| SPI0_ID | SPI0CR1 (SPIE, SPTIE) | 0xD8 | An 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_ID | SCI0CR2 (TIE, TCIE, RIE, ILIE) | 0xD6 | An 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_ID | SCI1CR2 (TIE, TCIE, RIE, ILIE) | 0xD4 | An 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_ID | ATD0CTL2 (ASCIE) | 0xD2 | An interrupt is recognized when the requested Analog-To-Digital subsystem 0 (pins AN0-7) sequence has completed. |
| ATD1_ID | ATD1CTL2 (ASCIE) | 0xD0 | An interrupt is recognized when the requested Analog-To-Digital subsystem 1 (pins AN8-15) sequence has completed. |
| PORTJ_ID | PIEJ (PIEJx) | 0xCE | An interrupt is recognized when a specified signal edge (configured using the PPSJ register) occurs on the corresponding PortJ pin. |
| PORTH_ID | PIEH (PIEHx) | 0xCC | An interrupt is recognized when a specified signal edge (configured using the PPSH register) occurs on the corresponding PortH pin. |
| COUNTER_UNDERFLOW_ID | MCCTL (MCZI) | 0xCA | A Modulus Counter underflow interrupt is recognized when the 16-bit MCCNT modulus down-counter register underflows. |
| PULSE_B_OVERFLOW_ID | PBCTL (PBOVI) | 0xC8 | A 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_ID | CRGINT (LOCKIE) | 0xC6 | An interrupt is recognized when the Phase-Locked Loop clock generator is locked onto the target frequency. |
| SELF_CLOCK_ID | CRGINT (SCMIE) | 0xC4 | An interrupt is recognized when the status of the processor clock changes to or from normal to self-clocked mode. |
| IIC_ID | IBCR (IBIE) | 0xC0 | An 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_ID | SPI1CR1 (SPIE, SPTIE) | 0xBE | An 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_ID | SPI2CR1 (SPIE, SPTIE) | 0xBC | An 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_ID | ECNFG (CCIE, CBEIE) | 0xBA | An 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_ID | FCNFG (CCIE, CBEIE) | 0xB8 | An 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_ID | PIEP (PIEPx) | 0x8E | An interrupt is recognized when a specified signal edge (configured using the PPSP register) occurs on the corresponding PortP pin. |
| PWM_SHUTDOWN_ID | PWMSDN (PWM7ENA, PWMIE) | 0x8C | An interrupt is recognized if the PWM emergency shutdown feature is enabled, and a signal edge occurs on a PWM pin. |
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.
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!
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.
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.
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
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: IRQ.ID
Header file: interrupt.h
Related Forth function: ENABLE.INTERRUPTS
Header file: interrupt.h
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.
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
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”
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 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.
To use an interrupt to respond to events, follow these four steps:
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”
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
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.
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.
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”
timekeep.c:137: error: 'FunctionTimer_ISR_xaddr' undeclared (first use in this function)
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.
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.
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-interrupt”
Pronunciation: “return-from-interrupt”
Pronunciation: “return-from-call”
The following example illustrates how to write and use an interrupt service routine.
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
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.
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.
Using interrupts requires:
See also → Loading Your Program Into Memory