Link here

Chapter 4 - The Programmable Timer and Pulse Accumulator Subsystem


This chapter describes how the programmable timer and pulse accumulator can be used to:

  • Implement clocks
  • Measure time intervals
  • Sense input signal transitions
  • Measure input signal pulse widths
  • Generate output pulse signals
  • Control stepper motors with synchronized output pulse signals
  • Count external events represented by input pulses

These feats are accomplished by the 68HC11F1's programmable timer and pulse accumulator subsystem. Controlled by a set of hardware registers, the subsystem performs input captures, output compares, pulse accumulation, gated timing, and real-time interrupt generation.

A 16-bit free running counter is the heart of the programmable timer. All input capture and output compare operations are referenced to its count.

An input capture saves the time at which a specified input signal transition occurs. This allows accurate timing of external signals. Interrupts may optionally be generated when the signal transitions occur. Coded examples presented below show how to use input captures to sense input signal level changes and measure input pulse widths.

An output compare causes actions to occur when the count in a specified register matches the value of the free-running counter. The output compare can change the state of an output pin at a programmable time, and can optionally generate an interrupt at the same time. Coded examples illustrate their use in synthesizing accurate clocks, generating output pulse signals, and implementing time-controlled program execution. In addition, a sample stepper motor control program that generates synchronized output signals is presented.

The 8-bit pulse accumulator increments a count every time a specified signal transition is detected. An associated gated timer increments a count driven by an internal clock as long as an external gating input is active. Optional interrupts can be generated when the 8-bit count overflows and when a pulse or gate-signal edge is detected. Coded examples illustrate their utility for counting external events and measuring pulse widths.

The real-time interrupt generates an optional interrupt at programmable intervals to time events or to establish a time base.

All of these functions are controlled by registers located at addresses 8000H-805FH in common memory. Each register's name is highlighted in BOLD when it is first introduced. The names, addresses, and contents of all of the registers are summarized in Appendix B. The example code uses the REGISTER: routine defined in QED-Forth to assign a name and extended address to each relevant control register.

 

The 16-Bit Free-Running Counter

The heart of the programmable timer is a 16-bit free running counter (F1, p. 7.2). The counter is accessible via a read-only timer count register named TCNT (HC11 p. 10-5). Its value is incremented continuously at a programmable rate. The count progresses from 0 to 65,535 (FFFFH) and then overflows to 0 and continues counting. The input capture and output compare functions use TCNT's contents to record when an event has occurred, or to determine when a specified output event should be initiated.

The following code defines the TCNT register and shows that its contents are being continually updated:

HEX
800E REGISTER: TCNT \ define the location of the timer count register
TCNT C@ . \ Read the contents of the timer counter, and
TCNT C@ . \ note that each time the result is different.
TCNT C@ .

The free-running counter's resolution is programmed by two prescaler bits named PR0 and PR1 located in the timer interrupt mask register 2, TMSK2 (HC11 pp.10-7…10-10, F1 p.7-9). Their settings determine whether the timer count register's frequency is equal to the system (2 MHz E-clock) frequency divided by 1, 4, 8, or 16. The following table summarizes the count resolutions and overflow ranges of the TCNT register for the four possible combinations of prescaler bits 0 and 1 (PR0 and PR1):

With 8 MHz Crystal With 16 MHz Crystal
PrescaleCount Overflow Count Overflow
PR1 PR0 Factor Resolution Range Resolution Range
0 0 1 0.5 us 32.77 ms 0.25 us 16.38 ms
0 1 4 2 us 131.1 ms 1 us 65.536 ms
1 0 8 4 us 262.1 ms 2 us 131.1 ms
1 1 16 8 us 524.3 ms 4 us 262.1 ms

The bold entries highlight the default values. QED-Forth initializes PR0 and PR1 to achieve a count resolution of 2 microseconds (us) and a time-until-overflow of 131.1 milliseconds (ms). If the crystal frequency is 8 MHz, the default values are PR1 = 0 and PR0 = 1; if the crystal frequency is 16 MHz, the default values are PR1 = 1 and PR0 = 0. (For experts and the curious: a flag at address FFC0 in the kernel PROM tells QED-Forth what crystal frequency the board was manufactured with). The values of prescale bits 0 and 1 can be verified by executing the following commands:

HEX
8024 REGISTER: TMSK2 \ define a control register constant for TMSK2
 
TMSK2 C@             \ fetch the contents of TMSK2
3 AND                \ AND with 3 to isolate PR0 and PR1 bits
.                    \ print the result

These prescaler bits in TMSK2 may be changed only during the first 64 cycles of processor operation after a reset. The QED-Forth word INSTALL.REGISTER.INITS allows you to specify the contents of TMSK2 that will take effect after every reset; see its glossary description for details.

The timer count register overflows to 0 when its count reaches 65,535 (FFFFH). When an overflow occurs, a timer overflow flag is set. The flag is named TOF and it is located in the timer interrupt flag register 2, TFLG2. If the timer overflow interrupt is enabled by setting the TOI mask bit in the TMSK2 register, then an interrupt is triggered when the timer overflow flag is set (HC11 pp.10-9...10-10, F1 p.7-9&10). Like most interrupt flags, the timer overflow flag is reset by writing a one to its location in the TFLG2 register (see Chapter 3 for a discussion of interrupts).

 

Polling the Timer Overflow Flag

We can define a routine that times an interval by polling the overflow flag. Given the default settings of the timer prescaler bits 0 and 1, the timer overflow flag is set every 131.1 ms. Thus it is possible to measure intervals of n*131.1 ms. For example, the following code times an approximate 6 second interval:

HEX
8025 REGISTER: TFLG2             \ define timer interrupt flag register 2
80 CONSTANT TIMER.OVERFLOW.FLAG    \ define a mask to isolate the TOF bit
                        \ in the TFLG2 register
 
: POLL ( mask\xaddr -- | iterate until bits set in mask are set in xaddr )
\ compares the specified mask to the value at xaddr until the bits set in
\ mask are also set in xaddr.  This routine could be assembly coded
\ for use in more time-critical applications.
    LOCALS{ x&addr &mask }
    BEGIN
        x&addr C@        \ fetch the contents of xaddr
        &mask AND        \ AND the contents with the given mask
        &mask =        \ the mask bits are set in xaddr if this is true
    UNTIL
;
 
DECIMAL                \ set number base to decimal
6.0 0.1312 F/ FIXX    CONSTANT #OVERFLOWS.IN.6SECONDS
\ define constant that equals the number of timer overflows
\ {which occur every 0.1312 seconds} per 6 second interval
 
 
: TIME.6.SECONDS ( -- )
\ times an approximate 6 second interval by counting timer overflows.
    TIMER.OVERFLOW.FLAG TFLG2 C!        \ reset the TOF flag
    #OVERFLOWS.IN.6SECONDS 0        \ specify number of overflows
    DO
        TIMER.OVERFLOW.FLAG TFLG2 POLL    \ poll for next overflow
        TIMER.OVERFLOW.FLAG TFLG2 C!        \ reset the TOF flag
    LOOP
    CR ." 6 seconds are up!"
;

TIME.6.SECONDS resets the timer overflow bit and then polls it, waiting for approximately 6 seconds to elapse. This simple routine is for demonstration purposes only, and is admittedly not very accurate. Another drawback is that polling unnecessarily ties up the CPU. Interrupts offer an attractive alternative.

 

Interval Timing - An Illustration of Interrupt Processing

It is simple to have the 68HC11F1 service an interrupt whenever the timer count register overflows. This increases the accuracy of timed delays, and also frees the CPU for concurrent processing.

When the timer count register overflows, the timer overflow interrupt flag, TOF, is set. If the local timer overflow interrupt mask bit named TOI is set in the TMSK2 (timer mask 2) register, an interrupt is recognized every time the timer overflow flag is set by the processor (FI p.7-9). When a timer overflow interrupt is serviced, it is the responsibility of the interrupt handler to reset the timer overflow flag.

The following example demonstrates how to write a 6.031 second interval timer using interrupts. The numbered, bold face comments correspond to the four steps used to implement interrupts as described in Chapter 3.

\ 1. Define constants representing registers related to the interrupt.
HEX                    \ use hexadecimal base to specify addresses
8024 REGISTER: TMSK2        \ define a control register constant for TMSK2
8025 REGISTER: TFLG2         \ define the timer interrupt flag #2 register
80     CONSTANT TIMER.OVERFLOW.FLAG        \ define a mask to isolate the TOF bit
                                \ in the TFLG2 register
80 CONSTANT TIMER.OVERFLOW.INTERRUPT.MASK    \ used to set the TOI bit in TMSK2
VARIABLE #TIMER.OVERFLOWS                \ keeps a running count of overflows.
DECIMAL                            \ set number base to decimal
6.0 0.1312 F/ FIXX    CONSTANT #OVERFLOWS.IN.6SECONDS
\ define an integer constant that equals the approximate number of timer
\ overflows {which occur every 0.1312 seconds} per 6 second interval
 
\ 2. Write an "interrupt handler" routine
\ Define the timer overflow interrupt handler which resets the timer overflow
\ flag.  If 6 seconds (= 46*131 ms) have elapsed, the routine BEEPs and resets
\ the #TIMER.OVERFLOWS variable; otherwise, it increments #TIMER.OVERFLOWS
 
: BEEP.EVERY.6.SECONDS     ( -- )
    TIMER.OVERFLOW.FLAG TFLG2 C!        \ reset the TOF flag
    #TIMER.OVERFLOWS @ #OVERFLOWS.IN.6SECONDS
    >=                             \ have 6 seconds elapsed?
    IF    BEEP                        \ yes, so beep
        #TIMER.OVERFLOWS OFF            \ and reset the counter
    ELSE    1 #TIMER.OVERFLOWS +!            \ no, so increment the #overflows
    ENDIF
;
 
\ 3. Write an installation word for the interrupt handler
\ INSTALL.ALARM uses the QED-Forth word ATTACH to properly install
\ the timer overflow handler BEEP.EVERY.6.SECONDS.
: INSTALL.ALARM ( -- )
    CFA.FOR BEEP.EVERY.6.SECONDS  TIMER.OVERFLOW.ID   ATTACH
;
 
 
\ 4. Write words to enable and disable the interrupt.
\ ARM.ALARM enables the 6 second beeper by clearing the #TIMER.OVERFLOWS
\ variable, clearing the timer overflow flag bit TOF, locally enabling
\ the timer overflow interrupt by setting its TOI mask bit, and ensuring
\ that the I bit is clear so interrupts are globally enabled.
: ARM.ALARM ( -- )
    #TIMER.OVERFLOWS OFF                \ initialize the overflow counter to 0
    TIMER.OVERFLOW.FLAG TFLG2 C!                \ reset the TOF flag
    TIMER.OVERFLOW.INTERRUPT.MASK TMSK2 SET.BITS    \ set the TOI mask
    ENABLE.INTERRUPTS                \ ensure that the I bit is clear
;
 
\ DISARM.ALARM disables the timer overflow interrupt by clearing the timer
\ overflow interrupt mask bit.  Note that the global interrupt mask bit I is not
\ used to disable the interrupt, as this would affect all maskable interrupts.
: DISARM.ALARM ( -- )
    TIMER.OVERFLOW.INTERRUPT.MASK  TMSK2  CLEAR.BITS
;

After defining these words, install the timer overflow interrupt handler and activate it by typing:

INSTALL.ALARM
ARM.ALARM

Your QED Board will now beep by transmitting a bell character every 6.031 seconds. To stop the beeping type:

DISARM.ALARM

Try fetching the contents of #TIMER.OVERFLOWS several times while the timer overflow interrupt is operating. The value stored in the variable changes every 131.1 ms. This is the result of BEEP.EVERY.6.SECONDS working in the background as an interrupt. Unlike the polling example presented above, the processor is now spending a very small fraction of its available time performing the timing function. This example demonstrates the benefits of interrupts, and how QED-Forth simplifies their use.

 

Summary of the 16-Bit Free Running Counter

The heart of the programmable timer subsystem is a free-running 16-bit counter whose count is available in the read-only TCNT register. The range and resolution of counts in the timer count register are determined by prescaler bits PR0 and PR1 in the TMSK2 register. Each time the timer count register overflows, the processor sets the timer overflow flag TOF in the TFLG2 register. If the timer overflow interrupt mask bit TOI in the TMSK2 register has been set, an interrupt is triggered when the TCNT register overflows. To service this interrupt, a handler must be installed which clears the timer overflow flag in addition to performing its intended task.

The example program presented above is by no means the best way to measure time intervals. Nevertheless, it illustrates three important concepts: use of the free running count register, the implementation of interrupts via a flag and an interrupt enable mask (TOF and TOI), and the responsibility of the interrupt handler to clear its interrupt flag by storing a 1 in the flag's location.

 

Configuring and Using Input Capture Functions

Four input capture functions are available to measure input pulse widths and periods, synchronize program execution with external devices, and expand the number of available external hardware interrupts. External input signals are interfaced to the input captures via four PORTA pins PA0, PA1, PA2 and PA3. When an active input capture senses a specified signal transition on its PORTA pin, the time of occurrence is saved in a 16-bit register. An optional interrupt can be triggered in response to the signal transition event. Three types of transitions can be sensed:

  • Rising edges;
  • Falling edges; and,
  • Either a rising or falling edge.

The following five steps set up an input capture function that calls an associated interrupt service routine. If interrupts are not being used (for example, if the interrupt capture flag bit is being polled), steps 2 and 4 may be skipped.

  1. Configure the PORTA pin associated with the input capture as an input by writing a 0 to its bit position in the PORTA data direction register, named PORTA.DIRECTION in QED-Forth.
  2. (Optional). Define an interrupt service routine and install it using ATTACH.
  3. Clear the input capture's flag bit.
  4. (Optional). Enable (set) the input capture's local interrupt mask.
  5. Enable the input capture function by setting its edge trigger control bits.

When a specified signal edge is sensed, the value in the timer count (TCNT) register is latched into a 16-bit register associated with the input capture, and the input capture's interrupt flag is set by the processor. If the input capture's interrupt mask is also enabled, the interrupt is recognized and the installed interrupt service routine is called.

The secondary serial port supported by QED-Forth's software UART uses PORTA pin PA3 (associated with IC4/OC5) as the serial input and PA4 (associated with OC4) as the serial output. If you need the services of the secondary serial port make sure that you do not use these pins or compare functions.

 

Configuring Port A Pins For Input Capture

To configure Port A pins PA0, PA1, and PA2 for input capture, their data direction bits in Port A's data direction register (bits 0, 1, and 2, respectively) must be cleared. The data direction register is called DDRA in Appendix B, and is named PORTA.DIRECTION in QED-Forth. These bits are cleared upon reset. The sample routines presented here explicitly clear them to ensure that the input capture pins are properly configured.

PORTA pin PA3 can be used for either input capture or output compare (output compare functions are described later in this chapter). To use PA3 as input capture #4, the I4/O5 bit in the pulse accumulator control register, PACTL, must be set (F1 p.7-12). Note that if port bit PA3 is configured as an input capture by setting I4/O5, and if PA3 is configured as an output by setting data direction bit 3 in PORTA.DIRECTION, then changes on the PA3 output signal will activate the input capture function. This could be useful in some applications.

 

Enabling Input Capture Functions for Specified Signal Transitions

Each input capture function is enabled by two "edge" bits which determine the signal transition to be sensed. These bits are named EDGxA and EDGxB (where x = 1...4 specifies the input capture number). The following table relates the edge bits and input capture number to the associated PORTA pin:

Edge Bits Input
Capture
PORTA
Pin
EDG1A, EDG1B IC1 PA2
EDG2A, EDG2B IC2 PA1
EDG3A, EDG3B IC3 PA0
EDG4A, EDG4B IC4 PA3

The edge bits are located in timer control register 2, named TCTL2 (F1 pp.7-1...7-3, HC11 pp.10-16...16-19). The meanings of the edge bits are as follows:

EDGxB EDGxA Configuration
0 0 Capture is disabled (this is the state after reset)
0 1 Capture on rising edges only
1 0 Capture on falling edges only
1 1 Capture on any rising or falling edge

Note that clearing both edge bits disables the input capture function.

 

Input Capture Utility Words

The following QED-Forth words demonstrate how to configure each input capture function. First we define register names and useful constants:

HEX        \ set number base to hexadecimal
8021 REGISTER:    TCTL2    \ timer control reg. #2, holds edge specification bits
8023 REGISTER:    TFLG1    \ timer interrupt flag register
8026 REGISTER:    PACTL    \ pulse accumulator control register,
        \ configures PA3 as input capture
4 CONSTANT I4/O5.MASK    \ mask for bit that configures PA3 as an input capture
 
\ Define flags to specify the signal events that trigger the input capture:
0    CONSTANT    DISABLE.CAPTURE    \ disable an input capture
1    CONSTANT    RISING.EDGE    \ capture a rising edge
2    CONSTANT    FALLING.EDGE    \ capture a falling edge
3    CONSTANT    ANY.EDGE        \ capture either a rising or falling edge
 
\ Define some input capture identification constants:
0    CONSTANT IC3/PA0            \ input capture 3, associated with PA0
1    CONSTANT IC2/PA1            \ input capture 2, associated with PA1
2    CONSTANT IC1/PA2            \ input capture 1, associated with PA2
3    CONSTANT IC4/PA3            \ input capture 4, associated with PA3

The next word, CONFIGURE.INPUT.CAPTURE, configures and enables a given input capture function to sense a signal change event. The processor sets the input capture's flag bit when the specified event is detected:

: CONFIGURE.INPUT.CAPTURE     ( signal.event.flag\input.capture.id -- )
    LOCALS{ &input.capture.id &sense.event }
    \ Configure the input capture function...
    1 &input.capture.id SCALE PORTA.DIRECTION     \ make PAx an input
    CLEAR.BITS    \ by clearing the direction bit
    &input.capture.id IC4/PA3 =    \ If it's IC4/PA3...
    IF     I4/O5.MASK PACTL SET.BITS    \ make it an input capture
    ENDIF
    \ Now, enable the input capture function...
    1 &input.capture.id SCALE TFLG1 C!    \ Clear the ICx flag, see below...
    3 &input.capture.id 2* SCALE TCTL2 CLEAR.BITS    \ Clear EDGxA and EDGxB bits
    &sense.event &input.capture.id 2* SCALE TCTL2 SET.BITS
            \ Set EDGxA and EDGxB to enable the input capture
;

CONFIGURE.INPUT.CAPTURE lets you configure and enable any of the four input captures to sense a signal change event. All you need to do is decide which input capture function you will use, and which signal transition you want to sense. For example, the following statement configures input capture 3 to detect a falling edge on PORTA pin 0:

FALLING.EDGE IC3/PA0 CONFIGURE.INPUT.CAPTURE

Capturing rising edges on PORTA pin 3 using input capture 4 is accomplished by executing:

RISING.EDGE IC4/PA3 CONFIGURE.INPUT.CAPTURE

The following word disables an input capture:

: DISABLE.INPUT.CAPTURE ( input.capture.id -- )
    DISABLE.CAPTURE SWAP CONFIGURE.INPUT.CAPTURE
;

To disable the input captures #3 and #4, execute:

IC3/PA0 DISABLE.INPUT.CAPTURE
IC4/PA3 DISABLE.INPUT.CAPTURE
 

Input Capture Interrupts

Using CONFIGURE.INPUT.CAPTURE (defined above) we can configure and enable any input capture function to detect a signal transition event. When the event occurs, the contents of the timer count (TCNT) register are latched into a 16-bit register associated with the input capture function. These timer input capture registers are named TIC1, TIC2, TIC3, and TI4O5 (see Figure 7-1 in F1 p.7-2, and HC11 pp.10-15...10.16). They are read-only registers and are typically accessed using the QED-Forth command @.

When a signal change event is sensed, the processor sets the input capture's interrupt flag. The input capture interrupt flags are named IC1F, IC2F, IC3F, and IC4F and are located in the timer flag 1 register, TFLG1 (F1 p.7-8, HC11 p.10-16). Like other interrupt flags, input capture interrupt flags are cleared by writing a 1 to them. An input capture triggers an interrupt if its local interrupt mask has been set. Input capture interrupt masks are named IC1I, IC2I, IC3I, and IC4I and are located in the timer mask register TMSK1 (F1 p.7-7, HC11 p.10-16).

Input capture events should be serviced and the flag reset before the next specified signal transition occurs. If the input capture register is not read before the next specified transition, the originally stored timer count will be written over. If the input capture flag is not reset quickly enough, a signal transition may be missed.

 

Input Signal Pulse Width Measurement

The following software routines demonstrate how to use interrupts triggered by input capture 1 to measure input pulse widths. Although a very simple software example could be constructed, we present a more complex example in order to point out some useful programming techniques. The example shows how to use assembly coded service routines to achieve fast response, how to combine input capture and timer overflow interrupts to measure long pulse widths, and how to call floating point functions from inside an interrupt routine.

Some of the routines in this example use functions defined in the previous section named "Input Capture Utility Words". As usual, we start by assigning names to relevant control registers.

HEX                            \ use hex base to specify addresses
8 WIDTH !                        \ save more characters in names
                            \ to avoid non-unique names
 
8010    REGISTER:    TIC1                \ IC1's 16-bit timer capture register
8012    REGISTER:    TIC2                \ IC2's 16-bit timer capture register
8014    REGISTER:    TIC3                \ IC3's 16-bit timer capture register
801E    REGISTER:    TI4O5                \ IC4's 16-bit timer capture register
8022    REGISTER:    TMSK1            \ timer interrupt mask register #1
8023    REGISTER:    TFLG1            \ timer interrupt flag register #1
8025    REGISTER:    TFLG2            \ timer interrupt flag register #2
 
RISING.EDGE    CONSTANT    HIGH.PULSE    \ flag to time width of high pulse
FALLING.EDGE    CONSTANT    LOW.PULSE    \ flag to time width of low pulse
 
 
HEX
8000    CONSTANT    REGISTER.BASE        \ base address of register block
  10    CONSTANT    +TIC1                \ byte offset to TIC1 register
  21    CONSTANT    +TCTL2            \ byte offset to TCTL2 register
  22    CONSTANT    +TMSK1            \ byte offset to TMSK1 register
  23    CONSTANT    +TFLG1            \ byte offset to TFLG1 register
  24    CONSTANT    +TMSK2            \ byte offset to TMSK2 register
  25    CONSTANT    +TFLG2            \ byte offset to TFLG2 register
   4    CONSTANT    IC1.MASK            \ mask to set and clear IC1F and IC1I
  30    CONSTANT    EDGE.TOGGLE        \ mask to toggle rising/falling edge
  80     CONSTANT     TIMER.OVERFLOW.FLAG    \ mask to isolate the TOF bit
                            \ in the TFLG2 register
 
VARIABLE    LEADING.EDGE.TIME        \ variable to store TCNT of leading edge
VARIABLE     #TIMER.OVERFLOWS        \ keeps a running count of overflows

As explained earlier in the chapter, the timer count (TCNT) register overflows every 131.1 ms given the default settings of prescale bits PR0 and PR1 in the TMSK2 register. Unless we keep track of these timer overflows, we will be limited to pulse measurements of less than 131.1 ms. To extend the measurable pulse length to 2.4 hours, we count the number of timer overflows using this interrupt handler:

HEX
: TIMER.OVERFLOW.COUNTER ( -- )
    TIMER.OVERFLOW.FLAG TFLG2 C!    \ reset the TOF (timer overflow) flag
    1 #TIMER.OVERFLOWS +!            \ increment the counter
;

This routine is installed as an interrupt handler by PULSE.WIDTH.TIMER.INIT below.

Next we define PULSE.WIDTH.MEASURER. This word services interrupts that are triggered by a desired edge event. PULSE.WIDTH.MEASURER expects IC1/PA2 to be configured to capture the leading edge of a pulse. Thus IC1/PA2 should be triggered on a rising edge to measure a high pulse, or triggered on a falling edge to measure a low pulse. The timer overflow interrupt handler, TIMER.OVERFLOW.COUNTER, must be installed, but its interrupt mask bit, TOI, should initially be cleared (interrupt disabled). PULSE.WIDTH.MEASURER controls the TOI mask bit so that timer count register overflows are counted only while a pulse is being measured. In this example, TOI is used to determine whether a sensed transition is a leading or trailing edge. The algorithm is as follows:

 

Initial Conditions:

Input capture 1's interrupt mask & interrupt flag are both set: IC1I = IC1F = 1

 

Algorithm implemented by IC1/PA2's interrupt service routine

  1. Clear input capture 1's IC1F flag which triggered the interrupt.
  2. Invert each of the EDG1A and EDG1B bits to make IC1/PA2 sensitive to the next edge. That is, if the current input capture was triggered by a rising edge, set the edge bits to detect a falling edge, and vis versa.
  3. Save the captured edge's TCNT time on the QED-Forth data stack.
  4. Check the state of the TOI bit to determine if the captured edge is a leading edge (TOI = 0 because the timer overflow interrupt is initially disabled) or trailing edge (TOI = 1 because the timer overflow interrupt is enabled after the leading edge).
    • 4.a If TOI = 0, a leading edge has been sensed:
      • 4.a.1 Clear the TOF bit to keep track of timer overflows.
      • 4.a.2 Set the TOI bit to track timer overflows and to indicate that the next edge is trailing.
      • 4.a.3 Save TCNT from stack into the LEADING.EDGE.TIME variable.
    • 4.b If TOI = 1, a trailing edge has been sensed
      • 4.b.1 Clear the TOI bit to disable timer overflow interrupts.
      • 4.b.2 Compute the pulse width in milliseconds (ms)
      • 4.b.3 Print the result.
      • 4.b.4 Reset #TIMER.OVERFLOWS for the next pulse.
  5. Return from subroutine. (Note: QED-Forth interrupt handlers return using ; or RTS, not RTI. The ATTACH handler supplies the required RTI).

This algorithm is implemented in the code presented below. The computation and printing of the result are performed by the high-level word COMPUTE.PULSE.WIDTH. The assembly coded interrupt service routine PULSE.WIDTH.MEASURER handles the time-critical tasks and then calls COMPUTE.PULSE.WIDTH. For less demanding applications, the interrupt service routine could also be coded in high level.

The code minimizes the time spent servicing a leading edge interrupt so short-duration pulses can be measured. The calculation and printing of the result performed after the trailing edge of the input pulse is more time consuming, and limits this example to measuring low frequency pulse trains.

Handling higher frequency signals could easily be accomplished by rapidly storing the timer and overflow counts in an array and then performing the time-intensive computation and printing tasks when more time is available. In a multitasked system these time consuming functions are best performed by a "foreground" task that accepts and processes saved data collected by the "background" interrupt routine. This keeps the interrupt routines short and efficient and keeps the structure and real-time behavior of the application modular and easy to maintain.

Note that COMPUTE.PULSE.WIDTH calls the matched pair of routines FP&STRING.PUSH and FP&STRING.POP which save and restore all of the scratchpad user variables needed to perform floating point calculations and printing of floating point numbers. These utilities should be called whenever floating point routines are used inside an interrupt handler, as they prevent the corruption of any floating point operation that might be running in the main program. If an interrupt performs floating point calculations without calling printing or string conversions, the faster save routines FP.PUSH and FP.POP may be used. Please consult the glossary for a full explanation of these routines.

DECIMAL                        \ set number base
48 VALLOT                        \ save 48 bytes in variable area
VHERE     XCONSTANT TEMPORARY.PAD    \ declare temporary RAM scratchpad
                            \ for F. which uses 32 bytes below PAD
2E-3        FCONSTANT MS/TCNT            \ each TCNT count = 2E-3 ms
131.1    FCONSTANT MS/OVERFLOW        \ each timer overflow = 131.1 ms
 
 
: COMPUTE.PULSE.WIDTH    ( falling.edge.TCNT.value -- )
\ this routine computes and prints the pulse width based on the falling-edge
\ TCNT value, the saved leading-edge TCNT value, and the number of
\ timer overflows since the leading edge.
\ Formula: pulse width = end.time - start.time,
\ where:    end.time = (falling.edge.TCNT * ms/TCNT) + (#overflows * ms/overflow)
\         start.time = leading.edge.TCNT * ms/TCNT
\ NOTE: some of the lines are keyed to the algorithm steps presented above
    TEMPORARY.PAD FP&STRING.PUSH    \ save fp scratchpad variables so they
            \ aren't changed during the interrupt;
            \ see glossary entry for FP&STRING.PUSH
    UFLOT     \ convert trailing TCNT to positive fp#
    MS/TCNT F*    \ convert trailing edge TCNT to ms
    #TIMER.OVERFLOWS @ FLOT
    MS/OVERFLOW F* F+    ( -- end.time.in.ms )
    LEADING.EDGE.TIME @ UFLOT    \ convert leading edge TCNT to positive fp#
    MS/TCNT  F*    \ convert t.start to ms
    F-         \ 4.b.2: pulse ms = t.end - t.start
    CR ." Pulse Width = " F.  ." ms"     \ 4.b.3: print result
    #TIMER.OVERFLOWS OFF    \ 4.b.4: reset overflow counter
    FP&STRING.POP    \ restore floating point user variables
;
 
 
CODE PULSE.WIDTH.MEASURER        ( -- )
\ this is the input capture's interrupt service routine.
\ NOTE: some of the lines are keyed to the algorithm steps presented above
    REGISTER.BASE  IMM    LDX    \ load X with base addr of control registers
    IC1.MASK    IMM    LDAA    \ load accumulator A with IC1F mask
    +TFLG1    IND,X    STAA    \ 1. clear the interrupt flag bit IC1F
    EDGE.TOGGLE    IMM    LDAA    \ load sense event toggle mask into A
    +TCTL2    IND,X    EORA    \ invert edge bits...
    +TCTL2    IND,X    STAA    \ 2. ...which alternates the edge trigger
    +TIC1    IND,X    LDD    \ get latched TCNT when edge event occurred
            DEY    \ make room on data stack
            DEY
    0    IND,Y    STD    \ 3. place latched timer result on data stack
    +TMSK2    IND,X    LDAA    \ 4.b if MSB of TMSK2 (TOI) is set,
    MI    IF,                \  then the transition is a trailing edge
                        \ 4.b.1 clear the TOI mask
            TIMER.OVERFLOW.INTERRUPT.MASK +TMSK2 IND,X  BCLR
            CALL    COMPUTE.PULSE.WIDTH    \ print the result
        ELSE,                \ else the transition is a leading edge
            TIMER.OVERFLOW.FLAG     IMM     LDAA    \ put bit mask into A
            +TFLG2             IND,X    STAA    \ 4.a.1 clear TOF flag
            TIMER.OVERFLOW.INTERRUPT +TMSK2 IND,X     BSET
                                 \ 4.a.2 set TOI
            >FORTH                \ go to QED-Forth to store
                LEADING.EDGE.TIME !    \ 4.a.3 store leading edge time
            >ASSM
        THEN,
    RTS                    \ 5. return from subroutine; note the use of RTS, not RTI
END.CODE

Notice the technique used to clear the IC1F flag in step 1. and the TOF flag in step 4.a.1. In assembly, a BSET instruction will not properly reset the TOF bit (nor will a SET.BITS command) even though it is necessary to store a 1 in TOF to clear it. For an important discussion of this matter see HC11 p. 10-14, section 10.2.4: Tips for Clearing Timer Flags. Using QED-Forth, the easiest way to reset a flag bit is to store a 1 in the appropriate position using C!.

We now define PULSE.WIDTH.TIMER.INIT to initialize and install PULSE.WIDTH.MEASURER and TIMER.OVERFLOW.COUNTER. After executing PULSE.WIDTH.TIMER.INIT, IC1/PA2 waits for the appropriate signal transition and then times the signal's pulse width. After the pulse's trailing edge has been detected, its width is computed and displayed. IC1/PA2 continues measuring pulse widths until

IC1/PA2 DISABLE.INPUT.CAPTURE

is executed.

: PULSE.WIDTH.TIMER.INIT     ( low.or.high.pulse -- )
    IC1.MASK  TMSK1  CLEAR.BITS        \ disable IC1 interrupts
    TIMER.OVERFLOW.INTERRUPT.MASK  TMSK2  CLEAR.BITS    \ disable TOI
    CFA.FOR  PULSE.WIDTH.MEASURER  IC1.ID  ATTACH        \ install handlers
    CFA.FOR  TIMER.OVERFLOW.COUNTER  TIMER.OVERFLOW.ID  ATTACH
    #TIMER.OVERFLOWS  OFF            \ clear overflow counter
    IC1/PA2  CONFIGURE.INPUT.CAPTURE    \ set IC1/PA2 up for leading edge
    IC1.MASK  TMSK1  SET.BITS        \ set IC1I mask bit
;

We can now measure pulse widths using input capture 1. To test this capability, we can use PORTA pin 7 (PA7) as an input pulse signal source by connecting it to input capture 1's PORTA pin PA2. After connecting these pins together, type the following to prepare for testing the pulse width timer:

HEX
80        CONSTANT    PA7.MASK    \ PA7 is the top bit of PORTA
 
: PA7.ON ( -- | set output bit 7 of PORTA HIGH )
    PA7.MASK PORTA SET.BITS    \ set bit PA7 to output a HIGH signal
;
 
: PA7.OFF ( -- | set output bit 7 of PORTA LOW )
    PA7.MASK PORTA CLEAR.BITS    \ clear bit PA7 to output a LOW signal
;
 
: PREPARE.TO.MEASURE ( high/low.pulse.test -- )
    PULSE.WIDTH.TIMER.INIT        \ install and prep interrupt handlers
    ENABLE.INTERRUPTS        \ ensure interrupts are enabled
;

Using PORTA pin 7 to control the pulse signal to input capture 1 via PORTA pin 2, we can measure a HIGH pulse by typing the following:

PA7.MASK PORTA.DIRECTION SET.BITS    \ configure PA7 for digital output
 
 
PA7.OFF                    \ measuring a HIGH pulse requires a LOW start
HIGH.PULSE PREPARE.TO.MEASURE    \ install pulse measurer & enable interrupts
PA7.ON    \ Send the leading edge of the HIGH pulse and begin timing.
PA7.OFF    \ End the pulse.  The PULSE.WIDTH.MEASURER will print
        \ the duration of the pulse that you created.
        \ Iterating this sequence of PA7.ON and PA7.OFF will allow
        \ measurement of additional HIGH pulses
 
IC1/PA2 DISABLE.INPUT.CAPTURE    \ call this when you are done, or desire to
                        \ measure LOW pulses

Measuring a LOW pulse is accomplished as follows:

PA7.ON                    \ measuring a LOW pulse requires a HIGH start
LOW.PULSE PREPARE.TO.MEASURE    \ install pulse measurer & enable interrupts
PA7.OFF                    \ send the leading edge to begin timing
PA7.ON                    \ send the trailing edge to get a result
 
IC1/PA2 DISABLE.INPUT.CAPTURE    \ call this when you are done, or desire
                        \ to measure HIGH pulses
 

Summary of the Input Capture Functions

The 68HC11F1 has four input captures that sense signal transitions on PORTA pins PA0, PA1, PA2, and PA3. When an input capture senses an input event, it records the contents of TCNT in its 16-bit input capture register and sets its (interrupt) flag in TFLG1. If the input capture's local interrupt mask in TMSK1 is set, an interrupt is recognized when each specified edge is detected. Two input capture edge control bits in TCTL2 enable each input capture and select the edge which triggers the input capture. To use input capture 4, a configuration bit named I4/O5 in the PACTL register must be set.

The following five steps set up an input capture function that calls an associated interrupt service routine. If interrupts are not being used, steps 2 and 4 may be skipped.

  1. Configure the PORTA pin associated with the input capture as an input by writing a 0 to its bit position in the PORTA data direction register, PORTA.DIRECTION.
  2. (Optional). Define an interrupt service routine and install it using ATTACH.
  3. Clear the input capture's flag bit.
  4. (Optional). Enable (set) the input capture's local interrupt mask.
  5. Enable the input capture function by setting its edge trigger control bits.

This flexible subsystem is capable of measuring input pulse widths, synchronizing program execution with external devices, and expanding the number of available external interrupts.

 

Overview of the Output Compare Functions

The programmable timer subsystem provides 5 output compare functions. Each of these functions can automatically call an interrupt routine and/or change the state of an associated PORTA pin when the value in a specified register matches the count in the TCNT timer register. Thus the programmer can precisely specify a future time (we'll call it time T) at which an action will occur. Using output compares, it is easy to set up real-time clocks, cause periodic execution of code, and generate precisely timed synchronous or asynchronous waveforms on PORTA outputs PA3 through PA7.

An active output compare function can cause a signal change on a PORTA pin at a specified time T, and/or trigger an interrupt at time T. To cause a signal change on a PORTA pin when time T equals the contents of TCNT, the PORTA pin must be configured as an output, and the output compare function must be enabled. The output compare function is enabled by storing a 2-bit code specifying the desired signal change. To trigger an interrupt when time T = TCNT, an interrupt handler must be installed, and the output compare's local interrupt must be enabled.

Each output compare (OC) function has a 16-bit timer compare register which holds the specified time T, an output compare (interrupt) flag, an output compare interrupt mask, and two signal edge configuration bits (OC1 does not have the configuration bits). The two signal edge configuration bits are discussed in greater detail later. The timer output compare registers, named TOC1, TOC2, TOC3, TOC4 and TOC5, each hold a 16-bit value which is compared with the current value in the timer count register TCNT (F1 p.7-4, HC11 pp.10-27...10-31). When the values are equal (i.e. when a successful output compare occurs), the processor sets the output compare's interrupt flag. This flag is named OCxF (x = 1...5) and is located in the TFLG1 register (F1 p.7-8). An interrupt is recognized if the output compare's interrupt mask is also set. The interrupt mask is named OCxI and is located in the TMSK1 register.

The secondary serial port supported by QED-Forth's software UART uses PORTA pin PA3 (associated with IC4/OC5) as the serial input and PA4 (associated with OC4) as the serial output. If you need the services of the secondary serial port make sure that you do not use these pins or compare functions. The timeslicer uses output compare 2, so if you need the services of the timeslicer (timesliced task switching, elapsed time clock, or BENCHMARK: function) make sure that you do not use OC2 for other functions. Pin PA6 which is associated with OC2 and OC1 is available for use as a general purpose digital I/O pin, or as an output signal controlled by OC1.

The following sections describe how to use output compares to generate clocks, pulse trains, "one-shot" pulse signals, and synchronized pulsed waveforms.

 

Generating Accurate Clocks and Periodic Interrupts

The following example illustrates the use of an output compare function to periodically execute an interrupt service routine. We'll use output compare 1 (OC1) to generate elapsed-time clocks and to activate a simple alarm that beeps after a specified period has elapsed.

This example illustrates the very useful technique of generating cascaded clocks so that long time intervals can be represented. At its default frequency, the 68HC11's free-running timer TCNT overflows (resets to a count of 0) every 131 ms. But we often need to measure much longer time intervals. Moreover, it is often necessary to generate continuously updated clock variables whose contents represent an elapsed time expressed in convenient time units such as milliseconds or seconds.

The solution is to use an output compare to implement a clock that has a period less than 131 ms; in this example we choose a period of 10 ms. We then use this clock to increment a higher level clock that counts in larger time units (1 second in this example). Each "clock" is simply a variable whose contents are periodically incremented by the output compare's interrupt service routine. The contents of the clocks can be read by any program at any time, providing an accurate reading of the time that has elapsed since the clocks were initialized.

In this example, the OC1 interrupt service routine increments a variable named 10MS.CLOCK until it reaches a value of 100. Realizing that 1 second has now elapsed, the interrupt service routine clears the 10MS.CLOCK variable and increments a variable named 1SECOND.CLOCK. External programs can read the contents of the two clocks to calculate elapsed time intervals in seconds, with a 10 ms time resolution.

In fact, the elapsed time counter that is driven by QED-Forth's timeslice multitasker uses this technique. It implements a 5 ms clock generated by OC2 that in turn increments a 32-bit clock every second. The QED-Forth words READ.ELAPSED.TIME and READ.ELAPSED.SECONDS report the current values of these clocks; see their glossary entries for details.

The example begins with definitions of control registers and bit masks needed by the alarm.

\ Define Timer Output Compare Register Addresses:
HEX                        \ use hexadecimal number base
800E    REGISTER:    TCNT            \ Timer counter register
8016    REGISTER:    TOC1            \ Output compare 1 register
8018    REGISTER:    TOC2            \ Output compare 2 register
801A    REGISTER:    TOC3            \ Output compare 3 register
801C    REGISTER:    TOC4            \ Output compare 4 register
801E    REGISTER:    TI4O5            \ Output compare 5 (also used for IC4)
8022    REGISTER:    TMSK1        \ timer interrupt mask register #1
8023    REGISTER:    TFLG1        \ timer interrupt flag register #1
 
80        CONSTANT    OC1.MASK    \ isolates OC1 interrupt flag & mask bits
DECIMAL                    \ go to decimal number base
5000    CONSTANT    10MS            \ 10 ms = 5000 counts of the TCNT timer
100    CONSTANT    1SECOND        \ 1 second = 100 counts on the 10ms clock
 
VARIABLE 10MS.CLOCK            \ is incremented every 10 ms by OC1
VARIABLE 1SECOND.CLOCK        \ is incremented every second by OC1
VARIABLE ALARM.SET.POINT        \ alarm sounds when this = 1.SECOND.CLOCK
 
\ DISABLE.ALARM turns the alarm off by clearing OC1's interrupt mask.
: DISABLE.ALARM ( -- )
    OC1.MASK TMSK1 CLEAR.BITS    \ disable OC1 interrupts
;
 
 
\ OC1.ALARM is the interrupt service routine for output compare 1.
\ It clears OC1's interrupt flag, updates its timer compare
\ register with the time at which the next interrupt should occur
\ (10 ms from now), and increments the 10ms clock.  If the 10ms clock reads
\ 100, then 1 second has elapsed, so the 10ms clock is cleared and the 1second
\ clock is incremented.  If the 1second clock equals the alarm set point,
\ the routine BEEPs and disables the output compare's interrupt
\ which stops the clocks.
: OC1.ALARM
    OC1.MASK TFLG1 C!            \ clear the OC1 interrupt flag
    10MS TOC1 +!                \ update TOC1 for next interrupt
    1 10MS.CLOCK +!                \ increment 10 ms clock
    10MS.CLOCK @  1SECOND =        \ has 1 second elapsed?
    IF    10MS.CLOCK OFF            \ if so, clear 10ms clock
        1 1SECOND.CLOCK +!        \ increment 1second clock
        1SECOND.CLOCK @
        ALARM.SET.POINT @ =          \ is it time to sound alarm?
        IF    DISABLE.ALARM        \ if it is, turn interrupt off
            BEEP                \ and beep to indicate that time is up
        ENDIF
    ENDIF
;
 
\ SET.ALARM installs OC1's interrupt handler, initializes the alarm set point
\ and clock variables, and enables OC1's interrupt mask.
: SET.ALARM ( time.until.alarm.in.seconds -- )
    ALARM.SET.POINT !            \ initialize set point
    10MS.CLOCK OFF                \ reset 10 ms clock
    1SECOND.CLOCK OFF            \ reset 1 second clock
    TCNT @ 10MS + TOC1 !            \ set time for first interrupt to occur
    DISABLE.ALARM                \ disable OC1 interrupts
    CFA.FOR OC1.ALARM OC1.ID ATTACH    \ install OC1.ALARM
    OC1.MASK TFLG1 C!            \ reset OC1 interrupt flag
    OC1.MASK TMSK1 SET.BITS        \ enable OC1I interrupt mask bit
    ENABLE.INTERRUPTS            \ globally enable interrupts
;

For example, to set the alarm to sound in ten seconds type:

DECIMAL
10 SET.ALARM

The computer will beep when ten seconds have elapsed. This timer is much more flexible and accurate than the timer overflow alarm that was presented earlier in the chapter.

 

Generating Output Pulse Signals

In addition to triggering an interrupt, a successful output compare can cause a signal change on a PORTA output pin. Output compares OC2 through OC5 can toggle, clear, or set their associated output pins. A pair of bits associated with each output compare determines the action to be performed. These bits are named OMn (output mode) and OLn (output level) where n = 2, 3, 4, or 5. The bits are located in timer control register 1, TCTL1 (F1 p.7-7, HC11 pp.10-31...10-33). The following table describes the settings of OMn and OLn bits for a desired signal change effect:

OMn OLn Action Taken upon a Successful Output Compare
0 0 Timer is disconnected from output pin logic
0 1 Toggle the associated output line
1 0 Clear the associated output line to LOW
1 1 Set the associated output line to HIGH

The output compare function's OMn and OLn bits are both initialized to 0 after a reset; thus, all output compare functions are initially disabled. If either OMn or OLn is set, the output compare controls the associated output pin. These bits override the direction configuration bits in the PORTA.DIRECTION register. When either the OMn or OLn bit associated with a PORTA pin is set, the PORTA pin is forced to be an output. When OMn and OLn are both cleared, the PORTA pin reverts to the configuration specified by the PORTA data direction register PORTA.DIRECTION.

PORTA pin PA3 may be used as output compare 5, or as input capture 4. To configure PA3 for use by output compare 5, clear the I4/O5 bit in the PACTL (pulse accumulator control) register by executing:

    8026     REGISTER: PACTL        \ define pulse accumulator control register
    4    CONSTANT I4/O5.MASK    \ define mask for bit that configures PA3
    I4/O5.MASK PACTL CLEAR.BITS    \ clear the I4/O5 bit

This is the only special treatment required to properly use OC5; otherwise, OC5 operates exactly the same as OC2, OC3, and OC4. Recall that IC4/OC5 is used by the secondary serial port; you can use IC4/OC5 if you don't need the services of the second RS232 serial link.

 

A One-Shot Output Pulse Generator

The following example demonstrates how to generate a single ("one-shot") HIGH or LOW pulse of specified width on PORTA bit PA5 using OC3.

HEX                            \ set hexadecimal number base
801A    REGISTER:    TOC3                \ output compare 3 count register
8020    REGISTER:    TCTL1            \ timer control register #1
8022    REGISTER:    TMSK1            \ timer interrupt mask register #1
8023    REGISTER:    TFLG1            \ timer interrupt flag register #1
 
1        CONSTANT    HIGH.PULSE        \ flag to output a high pulse
2        CONSTANT    LOW.PULSE        \ flag to output a low pulse
 
\ Define output mode configuration flags and masks that specify action
\ to be performed when a successful output compare occurs:
20        CONSTANT    PA5.MASK        \ mask for setting/resetting PA5
20        CONSTANT    OC3.MASK        \ to set/clr OC3 interrupt flag & mask
30        CONSTANT    SET.OUTPUT        \ OM3/OL3 mask in TCTL1; action=set
20        CONSTANT    CLEAR.OUTPUT    \ OM3/OL3 mask in TCTL1; action=clear
10        CONSTANT    TRAILING.EDGE.MASK \ OM/OL3 mask in TCTL1;action=toggle
 
DECIMAL
500. FCONSTANT    COUNTS/MS        \ number of TCNT counts per ms
131. FCONSTANT    MAX.PULSE.WIDTH  \ we'll limit pulse width to 131 ms
 
INTEGER: PULSE.WIDTH            \ a self-fetching variable, holds the pulse
                        \ width expressed as a number of TCNT counts
 
 
CODE OC3.ONE.SHOT    ( -- )
\ This assembly routine is the interrupt service code for OC3.  It is called on
\ the leading edge of the pulse.  The output compare hardware automatically
\ sets the PA5 bit to the appropriate level at the same time that this routine
\ is called.  This routine resets the interrupt flag, updates the TOC3 register
\ so that the output compare will be activated at the falling edge of the pulse,
\ configures OM3 and OL3 in the TCL1 register to toggle the current state of
\ PA5 the next time the output compare is successful (which is at
\ the trailing edge of the pulse), and disables the interrupt mask so that
\ this routine will not be called at the falling edge of the pulse.  Note that
\ the processor's output compare hardware will automatically toggle the
\ PA5 signal to end the pulse, even though the interrupt is not enabled!
    OC3.MASK    IMM    LDAA        \ load mask needed to reset OC3F
    TFLG1 DROP    EXT    STAA        \ clear the OC3F interrupt flag
    CALL    PULSE.WIDTH        \ push interval time on stack
    0        IND,Y    LDD        \ load increment value into ACCD
                DEY        \ drop value from data stack
                DEY        ( -- )
    TOC3  DROP    EXT    ADDD    \ t.compare = TOC3 + PULSE.WIDTH
    TOC3  DROP    EXT    STD        \ store the result in TOC3
    TCTL1 DROP    EXT    LDAA        \ load leading edge trigger
TRAILING.EDGE.MASK    IMM    EORA    \ trailing edge = leading XOR mask
    TCTL1 DROP    EXT    STAA        \ store next edge into TCTL1
OC3.MASK NOT     IMM    LDAA        \ load mask to turn off interrupt OC3I
    TMSK1 DROP EXT    ANDA    \ force OC3I to zero
    TMSK1 DROP EXT    STAA        \ store to TMSK1 turns interrupt off
    RTS                    \ return; note that ATTACH supplies the RTI
END.CODE
: ONE.SHOT    ( high.or.low.pulse.flag\fp.pulse.width.in.ms  -- )
\ note that the pulse width must be less than MAX.PULSE.WIDTH which is 131 ms.
\ This routine ATTACHes the interrupt service routine for OC3,
\ converts the specified pulse width in ms to TCNT count units, stores the
\ result in PULSE.WIDTH, and configures the OC3 hardware for a low-to-high
\ transition if a HIGH.PULSE is specified, or a high-to-low transition if a
\ LOW.PULSE is specified.  It then sets up OC3 so that the first successful
\ compare will occur in 10 ms, and enables the OC3 interrupt mask.
    OC3.MASK TMSK1 CLEAR.BITS        \ disable OC3 interrupts
    SET.OUTPUT TCTL1 CLEAR.BITS        \ clear OM3 & OL3 to disable pin logic
    CFA.FOR OC3.ONE.SHOT OC3.ID ATTACH    \ install OC3.ONE.SHOT
    FDUP MAX.PULSE.WIDTH F<        \ make sure pulse width is < max
    IF    COUNTS/MS F* UFIXX        \ convert value to TCNT counts
        TO PULSE.WIDTH            \ save number of counts in pulse.width
    ELSE     BEEP
        CR ." Pulse width must be less than "  MAX.PULSE.WIDTH F.   ." ms"
        ABORT
    ENDIF
    PA5.MASK PORTA.DIRECTION  SET.BITS    \ make PA5 an output pin
    HIGH.PULSE =
    IF    PA5.MASK PORTA CLEAR.BITS    \ ensure that PA5 is LOW
        SET.OUTPUT                \ push mask for HIGH transition
    ELSE    PA5.MASK PORTA SET.BITS    \ ensure that PA5 is HIGH
        CLEAR.OUTPUT            \ push mask for LOW transition
    ENDIF
    TCNT @ 10MS + TOC3 !            \ activate 1-shot after 10ms delay
    OC3.MASK    TFLG1 C!            \ clear interrupt flag OC3F
    OC3.MASK  TMSK1 SET.BITS        \ set OC3I to enable interrupt
    TCTL1 SET.BITS                \ set bits according to transition mask
;

With these words, it is possible to generate a single HIGH or LOW pulse of duration between 0.08 and 131.0 milliseconds. The following command produces a 50 ms HIGH pulse:

HIGH.PULSE 50. ONE.SHOT

To verify that the output timer compare actually sent a 50. ms pulse, you can use the input capture pulse measurement routine defined in the "Input Signal Pulse Width Measurement" section above. Connect a jumper between PA2 (IC1) and PA5 (OC3), then type the following:

HIGH.PULSE PREPARE.TO.MEASURE \ set up IC1 pulse width measurement
HIGH.PULSE 50. ONE.SHOT \ send a 50. ms pulse from OC3 to IC1

Send pulses of different length using ONE.SHOT. After each call to ONE.SHOT, PULSE.WIDTH.MEASURER will report the duration of the pulse. Sending a pulse of width less than 0.2 ms will be incorrectly reported as having a width of 131.1 ms. This is due to PULSE.WIDTH.MEASURER's latency which is approximately 0.2 ms. PULSE.WIDTH.MEASURER would have to be modified slightly to measure shorter pulses.

With an 8 MHz crystal frequency, the 68HC11F1 and QED-Forth impose an interrupt latency of 27us (17us interrupt entrance latency, and 10us exit latency). This typically limits the maximum achievable output signal frequency generated by an interrupt-driven output compare to about 10kHz. If the QED Board is clocked at 16 MHz then pulse width modulated frequencies up to approximately 20 kHz can be generated by the processor.

 

Generating Synchronous Waveforms

Output compare 1 may be used to synchronously change any or all of PORTA pins PA3 through PA7. Whether a pin is modified, and to what state it is changed, depends on the configuration of two registers named OC1M (OC1 masks) and OC1D (OC1 data); see F1 pp.7-5...7-6, and HC11 pp.10-33 and 10-36. The OC1M register determines which PORTA pins should be affected by a successful compare of TOC1 and TCNT. OC1D determines how the selected PORTA pins will be set. A set bit in OC1D indicates that the corresponding PORTA pin will be set HIGH, and a clear bit in OC1D indicates that the PORTA pin will be cleared to a LOW state. After determining which pins should be affected when TOC1 equals TCNT, the following steps must be completed to use output compare 1:

  1. Set the state bits in bit positions 3-7 in the OC1 data register (OC1D). Each bit that is set in OC1D causes the parallel PORTA bit to be set HIGH on a successful compare. Likewise, clearing an OC1D bit causes the parallel PORTA bit to be set LOW on a successful compare. Notice the correspondence between bit locations in registers PORTA and OC1D.
  2. Determine the TCNT count when the desired state changes are to occur and store this value in the TOC1 register.
  3. Clear the OC1F flag bit in the TFLG1 register by writing a 1 to the OC1F bit.
  4. If an interrupt is to be called upon a successful compare of TCNT and TOC1, install the interrupt handler and enable the OC1I bit in the TMSK1 register.
  5. Finally, enable the PORTA pins whose states should be affected by a successful OC1 compare by writing ones to each corresponding bit in the OC1 mask register, OC1M. Notice the parallelism between bit locations in registers PORTA and OC1M. It is important that by the time these bits are set, the count in TCNT has not reached the count stored in TOC1 in step 2.

To demonstrate the use of OC1's synchronized output compare function, the following example implements a four-phase stepper motor controller using PORTA pins PA4, PA5, PA6, and PA7.

 

An Example: Synchronous Stepper Motor Control

A stepper motor turns clockwise or counterclockwise when its windings are energized. A four-phase stepper motor has four windings that are energized using a predetermined sequence called a wave step sequence. The speed of the motor's rotation depends on the rate at which the wave step sequence is presented to the windings. The sequence determines the speed, torque and rotational precision of the motor. The following table specifies the 4-step sequence, and shows the pulse pattern associated with each phase. Each phase/winding of the motor is driven by a PORTA output bit under the control of output compare 1.

STEP PHASE1: PA7 PHASE2: PA6 PHASE3: PA5 PHASE2: PA4
1 HIGH LOW HIGH LOW
2 LOW HIGH HIGH LOW
3 LOW HIGH LOW HIGH
4 HIGH LOW LOW HIGH

When this sequence is iterated through steps 1...4, the motor turns in a clockwise direction. When stepped through in reverse order, 4...1, it causes the motor to turn counterclockwise.

The following code illustrates how to implement the clockwise wave step sequence using output compare 1 to control a 4-phase stepper motor. Extending the code to handle counterclockwise rotation is left as an exercise for the interested reader.

ANEW STEPPER.CONTROLLER        \ declare a forget marker
HEX
\ define the relevant control registers:
800C    REGISTER:    OC1M            \ output compare 1 mask register
800D    REGISTER:    OC1D            \ output compare 1 data register
800E     REGISTER:     TCNT            \ timer count register
8016    REGISTER:    TOC1            \ OC1 timer count register
8022    REGISTER:    TMSK1        \ timer interrupt mask register 1
8023     REGISTER:    TFLG1        \ timer interrupt flag register 1
 
80    CONSTANT    OC1.MASK        \ used for OC1 interrupt flag & mask
F0    CONSTANT    STEPPER.OUTPUTS    \ mask with bits 4-7 set
 
\ define constants to specify the step waveform:
    \    PA7    PA6    PA5    PA4
C0    CONSTANT 1ST.STEP    \ Step 1: HIGH  LOW  HIGH  LOW
60    CONSTANT 2ND.STEP    \ Step 2: LOW  HIGH  HIGH  LOW
30    CONSTANT 3RD.STEP    \ Step 3: LOW  HIGH  LOW  HIGH
90    CONSTANT 4TH.STEP    \ Step 4: HIGH  LOW  LOW  HIGH
 
INTEGER: SPEED        \ a self-fetching variable that holds the #counts to add
                \ to the TOC1 count register. Inited by START.STEPPING
 
200.    FCONSTANT    STEPS/REVOLUTION    \ this depends on your stepper motor
                            \ and the step waveform
5.0E5    FCONSTANT    TICKS/SEC            \ # TCNT ticks per second
 
 
CODE STEP.CLOCKWISE    ( -- )
\ this routine uses the contents of the OC1 data register (OC1D) to
\ determine which step of the waveform has just been set up, and updates
\ OC1D to set up the next step when OC1 is triggered the next time.
\ The routine puts the current contents of OC1D in accumulator A, and the
\ next contents of OC1D in accumulator B.  At the end of the routine it
\ stores the updated  contents in accumulator B into OC1D.
    OC1D DROP EXT LDAA            \ A gets current OC1D contents
    1ST.STEP IMM CMPA
    EQ IF,                        \ if we're on 1st step...
        2ND.STEP IMM LDAB        \ ...then 2nd step is next
    ELSE,
        2ND.STEP IMM CMPA
        EQ IF,                    \ if we're on 2nd step...
            3RD.STEP IMM LDAB    \ ...then 3rd step is next
        ELSE,
            3RD.STEP IMM CMPA
            EQ IF,                    \ if we're on 3rd step...
                4TH.STEP IMM LDAB    \ ...then 4th step is next
            ELSE,                    \ if we're on 4th step...
                1ST.STEP IMM LDAB    \ ...then 1st step is next
            THEN,
        THEN,
    THEN,
    OC1D DROP EXT STAB                \ put updated contents in OC1D
    RTS                            \ return
END.CODE
 
 
CODE MOTOR.HANDLER ( -- )
\ this is the interrupt service routine for OC1.  It clears the interrupt flag,
\ updates the TOC1 timer compare register according to the contents of SPEED,
\ and calls STEP.CLOCKWISE to update the OC1 data register so that the
\ next step's waveform will be output on PORTA pins 4-7 on the next
\ successful OC1 compare.
    OC1.MASK     IMM     LDAA
    TFLG1 DROP    EXT     STAA            \ clear the interrupt flag bit in TFLG1 register
    CALL SPEED ( -- #counts.to.add.to.TOC1 )
    0         IND,Y     LDD        \ accumulator D now has #counts to add
    TOC1 DROP     EXT     ADDD        \ D <- #counts + prior count = new count
    TOC1 DROP     EXT     STD            \ store new count into TOC1 register
    2         IMM     LDAB
            ABY        ( -- )        \ drop the stack item
    CALL STEP.CLOCKWISE            \ update OC1D to do next wave change
                RTS            \ return
END.CODE
 
 
 
: SET.SPEED ( speed.in.rpm -- | input is fp# = speed in revolutions/min )
\ stores the # ticks per step interval in the self-fetching variable SPEED
    60. F*            \ convert speed to revs/sec
    STEPS/REVOLUTION F*    \ #steps/sec = revs/sec * steps/rev
    TICKS/SEC FSWAP F/    \ #ticks/step = ticks/sec  ÷  steps/sec
    UFIXX             \ convert to integer
    TO SPEED            \ store #ticks/step in SPEED
;
 
 
: START.STEPPING ( speed.in.rpm -- | input fp# = speed in revolutions/min )
\ initializes the self-fetching variable SPEED, ATTACHes motor handler routine,
\ initializes the OC1 configuration registers, and enables the OC1 interrupt
    0 OC1M C!                        \ turn OC1 actions off
    SET.SPEED                        ( -- ) \ init SPEED variable
    CFA.FOR MOTOR.HANDLER OC1.ID ATTACH    \ attach the handler
    STEPPER.OUTPUTS PORTA.DIRECTION SET.BITS   \ PA4...PA7 are outputs
    1ST.STEP PORTA SET.BITS                 \ energize outputs as 1st step
    2ND.STEP OC1D C!                     \ init OC1D to do next step
    SPEED  TCNT @ + TOC1 !                \ set time for next step
    OC1.MASK TFLG1 C!                \ clear the OC1 flag
    OC1.MASK TMSK1 SET.BITS            \ enable OC1 interrupts
    STEPPER.OUTPUTS OC1M C!            \ set OC1 to control PORTA pins 4-7
    ENABLE.INTERRUPTS                \ globally enable interrupts
;
 
: STOP.STEPPING ( -- )
    0 OC1M C!                        \ disconnect OC1 from PORTA pins
    OC1.MASK TMSK1 CLEAR.BITS            \ disable OC1 interrupts
    STEPPER.OUTPUTS PORTA CLEAR.BITS        \ force stepper outputs low
;

The words START.STEPPING and SET.SPEED provide the basis for controlling a stepper motor. The PORTA signals PA7, PA6, PA5 and PA4 should be interfaced to the motor windings via current-boost buffers that are capable of driving inductive loads. For smooth motor control, additional routines must be defined to ramp the motor from one speed to another. If software is the only factor that limits motor performance, the controllable speed ranges from less than 2.5 rpm (limited by the 131 ms overflow time of the TCNT register) to over 2500 revolutions per minute (limited by the time required to execute the MOTOR.HANDLER interrupt service routine).

In the absence of a stepper motor, an oscilloscope can be used to verify the operation of the code in creating the desired waveforms. For example, to see the waveforms needed to run the motor at 10. rpm, connect your scope probes to the PA4-7 outputs and execute

10. START.STEPPING

To see how the waveform changes when the speed is doubled, execute

20. SET.SPEED

To disable the output waveforms, type

STOP.STEPPING
 

Timesliced Multitasking and Output Compare 2

The timesliced multitasker uses OC2's timer and interrupt to implement timed task switching and to maintain an elapsed-time clock as explained in the "Multitasking" chapter in the QED Software Manual. Consequently, when using the timeslicer, you may not use OC2's timer. If you want to create an output on pin PA6 that is synchronized with the timeslice clock, configure OC2's edge bits to toggle with each successful compare.

Even while the multitasker is running, you may use PA6 as an input or output. OC1 can control PA6 while the timeslicer uses OC2's timer. Thus, the stepper motor controller described above may be used while the timesliced multitasker is running.

 

Forcing Output Compares

The 68HC11 allows you to immediately force a state change on a PORTA output pin that is being controlled by an output compare. The compare force register named CFORC (HC11 pp.10-36...10-37) controls this function. When an appropriate bit in CFORC is set, the output compare's next scheduled state change is immediately forced upon the associated PORTA pin. No interrupt is generated.

Force bits FOC1, FOC2, FOC3, FOC4, and FOC5 in the CFORC register correspond with output compares OC1, OC2, OC3, OC4, and OC5 respectively. Whenever a 1 is written to any of these bits, the programmed transition on the corresponding output compare is forced to occur on the next count of the TCNT timer. The output compare's flag bit, OCxF, is not set, and an interrupt is not triggered. After the forced signal change, the next transition/interrupt caused by the output compare occurs when TCNT equals TOCx (that is, when the next successful output compare occurs). Depending on the value in TOCx, a match could occur at any time after a CFORC. For this reason, it is uncommon to use CFORC to control a bit that is programmed to toggle with each successful compare. This is because the forced change of state could be counteracted immediately by a successful output compare.

The following words may be used to force an output compare via the CFORC register.

HEX
800B    REGISTER:    CFORC        \ define the compare force register
 
\ now assign names to the bit positions in the CFORC register;
\ each name indicates the output compare number and the affected PORTA bit(s)
80        CONSTANT    FOC1/PA3-7
40        CONSTANT    FOC2/PA6
20        CONSTANT    FOC3/PA5
10        CONSTANT    FOC4/PA4
08        CONSTANT    FOC5/PA3

For example, the following command immediately forces OC3's associated pin PA5 LOW if it has been configured to transition LOW on the next successful compare between TCNT and TOC3:

FOC3/PA5 CFORC SET.BITS
 

Summary of Output Compare Functions

The 68HC11F1 has 5 output compare functions named OC1, OC2, OC3, OC4, and OC5. An output compare function allows the programmer to specify actions that are initiated when the contents of TCNT match the contents of a 16-bit TOCx register. When these contents match, we say that a "successful output compare" has occurred.

Each output compare function has a 16-bit TOCx register, a successful compare OCxF (interrupt) flag, and an interrupt mask OCxI, where x is the output compare number. Output compares OC2, OC3, OC4 and OC5 also have a pair of output mode/level bits, OMn and OLn, which determine the effect that each successful compare has on PORTA bits PA6, PA5, PA4, and PA3 respectively. The processor sets an output compare's OCxF flag bit when the contents of the TCNT register and its TOCx register are equal. At the same instant, the state of the associated PORTA pin may be changed as specified by the output mode bits. In addition, if the output compare's OCxI mask bit is set, an interrupt is recognized when a successful compare occurs.

Output compare 5 operates like the other output compares; however, it must be initialized by clearing the I4/O5 bit of the PACTL (pulse accumulator control) register before it may be used. IC4/OC5 and associated pin PA3 are used by the secondary serial port, so be sure not to use these resources if you need the second RS232 serial link.

Output compare 1 is special in that it can synchronously control any of PORTA pins PA7, PA6, PA5, PA4, and PA3. Although OC1 may control several PORTA pins, the timer and interrupt functions of those output compares may still be used independently of the pin (to implement clocks, etc.) Conversely, the timeslicer's use of OC2's timer and interrupt does not disallow control of the associated pin PA6 by OC1, nor does it disallow the use of PA6 as a general purpose digital input or output.

Using the CFORC register, it is possible to immediately force a state change on a timer-controlled signal without causing an interrupt.

The flexible output compare functions can be used to implement a stepper motor controller, pulse generator, pulse width modulated signals, timed output pulses, timesliced multiplexing, and internal clocks.

 

The Pulse Accumulator

The 68HC11F1 has an 8-bit counter/timer that may be configured either as a pulse accumulator or as gated timer. The pulse accumulator can count pulses sensed on PORTA pin PA7. The polarity of the edges that increment the accumulator may be programmed. Interrupts can be optionally triggered when the 8-bit count overflows and when each pulse is detected.

The 8-bit gated timer is incremented every 32 us while a gating signal at pin PA7 is active. The gated timer facilitates pulse width measurement, synchronization of program execution using controlled delays, and discrimination of pulses which vary in width.

This section describes the operation of the pulse accumulator, and the next section describes the gated timer.

The pulse accumulator is incremented each time a specified signal transition is detected on PORTA pin PA7, which may be configured either as an input or an output. Common pulse sources are motor encoders, analog to frequency converters, and switches. Using the pulse accumulator requires five steps:

  1. Configure PORTA pin PA7 either as an input or an output as your application requires, and configure the subsystem for pulse accumulation.
  2. Configure the pulse accumulator for either rising or falling pulse edges.
  3. If interrupts will be used to service pulse accumulator overflows or sensed pulse edges, install appropriate interrupt service routines.
  4. Store an initial count into the pulse accumulator register.
  5. Enable the pulse accumulator.

The configuration and enabling of the pulse accumulator is controlled via three bits in the pulse accumulator control register PACTL (HC11 pp.11-2...11-6, F1 p.7-12). The three bits are:

Bit name Function
PAMOD Selects pulse accumulation or gated timer mode
PEDGE Selects the type of pulse edge to be counted
PAEN Enables the pulse accumulator

To configure PORTA pin PA7 for pulse accumulation, the PAMOD (pulse accumulator mode) bit must be clear in PACTL.

If PEDGE is clear, falling edges are counted, otherwise rising edges are counted:

PEDGE State Meaning
0 Count Falling Edges
1 Count Rising Edges

Once the pulse accumulator is configured, it is enabled by setting the pulse accumulator enable bit PAEN. Before this is done, however, it is sometimes desirable to initialize the pulse accumulator count and set up one or both of the associated interrupt handlers.

When the pulse accumulator is enabled, its count is incremented each time the specified pulse edge is sensed. The 8-bit pulse accumulator count is stored in a register named PACNT (F1 p.7-13). PACNT can be read and written without restriction, and it is not affected by a reset. Since it is 8-bits wide, it can count from 0 to 255 before overflowing to 0. When an overflow occurs, the pulse accumulator overflow flag, PAOVF, is set in the timer flag register TFLG2 (F1 p.7-10). This flag bit is paired with an interrupt mask named PAOVI found in the timer mask register TMSK2 (F1 p.7-9). These bits may be used to implement an interrupt handler for pulse accumulator overflows.

Typical uses for the pulse accumulator overflow interrupt are to increase the number of countable pulses by incrementing a 16-bit variable each time the counter overflows, or to notify the programmer when a predetermined number of pulses has been received. For example, to trigger an interrupt after n pulses have been detected, initialize the PACNT register to 256-n and enable the overflow interrupt (see HC11 pp.11-6...11-7 for more details).

It is possible to recognize an interrupt after each detected pulse edge. The pulse accumulator input-edge interrupt flag PAIF in the TFLG2 register is set whenever an input edge is detected. Its corresponding interrupt mask is named PAII, and is located in the TMSK2 register.

After installing the interrupt handlers needed for your implementation of the pulse accumulator, you may want to initialize the PACNT register using a C! command before enabling the subsystem. As you will see in the example below, the pulse accumulator is very easy to use.

 

Words to Configure the Pulse Accumulator

The following utilities configure the pulse accumulator control and status bits:

HEX
8024 REGISTER:    TMSK2         \ timer interrupt mask register 2
8025 REGISTER:    TFLG2         \ timer interrupt flag register 2
8026 REGISTER:    PACTL            \ pulse accumulator control register
8027 REGISTER:    PACNT        \ pulse/time count register
 
40    CONSTANT    PAEN            \ pulse accumulator/gated timer enable bit
20    CONSTANT    PAMOD        \ mode select bit, clear selects pulse accumulator
10    CONSTANT    PEDGE        \ signal edge trigger/gate configuration bit
20    CONSTANT    PAOVF        \ PACNT overflow flag bit
10    CONSTANT    PAIF            \ trigger/gate sense flag bit
20    CONSTANT    PAOVI        \ interrupt enable bit for PACNT overflows
10    CONSTANT    PAII            \ interrupt enable bit for edge detection
 
\ the following 2 edge.sense.ids are used as inputs to INIT.PULSE.ACCUMULATOR:
1    CONSTANT    RISING.EDGE    \ to increment counter on rising edge
2    CONSTANT    FALLING.EDGE    \ to increment counter on falling edge
 
: INIT.PULSE.ACCUMULATOR ( edge.sense.id -- )
    CASE
        RISING.EDGE    OF    PEDGE PACTL SET.BITS        ENDOF
        FALLING.EDGE    OF    PEDGE PACTL CLEAR.BITS    ENDOF
        CR ." You must specify either RISING.EDGE or FALLING.EDGE."
        ABORT
    ENDCASE
    PAMOD PACTL CLEAR.BITS    \ set mode for pulse accumulation
;
 
: ENABLE.PULSE.ACCUMULATOR ( -- )
    PA7.MASK PORTA.DIRECTION CLEAR.BITS    \ PA7 is an input
    PAOVF PAIF OR TFLG2 C!                \ clear both interrupt flags
    PAEN PACTL SET.BITS                \ enable the pulse accumulator
;
 
: DISABLE.PULSE.ACCUMULATOR ( -- )
    PAEN PACTL CLEAR.BITS                \ clear the pulse accumulator enable bit
;

To count either rising or falling edge pulses using the pulse accumulator, type one of the following:

RISING.EDGE    INIT.PULSE.ACCUMULATOR
FALLING.EDGE    INIT.PULSE.ACCUMULATOR

Then to enable the system type:

ENABLE.PULSE.ACCUMULATOR

And finally, to disable the system:

DISABLE.PULSE.ACCUMULATOR

To read the 8-bit number of counted pulses, execute

PACNT C@
 

Extending the Range of the Pulse Accumulator

Interrupts are available to monitor pulse accumulator overflow and input edge detection. The following example uses an interrupt handler to extend the number of pulses that can be counted. The code relies on definitions presented in the previous section.

VARIABLE #PULSE.OVERFLOWS            \ number of PACNT overflows
 
: PAOVF.HANDLER ( -- )                \ interrupt handler for overflows
    PAOVF TFLG2 C!                \ clear PAOVF flag
    1 #PULSE.OVERFLOWS +!            \ increment the number of PACNT overflows
;
 
: INIT.EXTENDED.PULSE.ACCUMULATOR ( edge.sense.id -- )
\ input is either RISING.EDGE or FALLING.EDGE
    PAOVI TMSK2 CLEAR.BITS        \ disable PACNT overflow interrupt
    CFA.FOR PAOVF.HANDLER PULSE.OVERFLOW.ID
    ATTACH                    \ install handler
    INIT.PULSE.ACCUMULATOR        \ initialize pulse accumulator
    0 PACNT C!                    \ set pulse accumulator count to 0
    0 #PULSE.OVERFLOWS !            \ set overflow count to 0
    PAOVI TMSK2 SET.BITS            \ enable PACNT overflow interrupt
;
 
: #PULSES ( -- n | n = total number of pulses sensed since initialization )
\ each pulse overflow represents 100H pulses.  The total number of pulses
\ equals the pulses represented by the overflows plus the number of pulses
\ since the last overflow (which is simply the contents of PACNT).
    #PULSE.OVERFLOWS @ 100 * PACNT C@ +
;

The pulse accumulator can now count over 16 million pulses before the #PULSE.OVERFLOWS variable overflows. To prepare the system to count pulses triggered on rising edges type:

RISING.EDGE INIT.EXTENDED.PULSE.ACCUMULATOR

Executing #PULSES now reveals that no pulses have been detected yet. To begin sensing pulses type:

ENABLE.PULSE.ACCUMULATOR

If a pulse source is present at PORTA pin PA7, executing #PULSES reveals the number of pulses that have been detected. To disable the pulse accumulator type:

DISABLE.PULSE.ACCUMULATOR
 

Summary of the Pulse Accumulator

There are many interesting uses for the pulse accumulator, and its operation is straightforward. To accumulate pulses sensed at pin PA7, clear the PAMOD bit in the PACTL register. Set PEDGE in PACTL for rising edge detection, or clear it for falling edges. If interrupts are used to handle PAOVF or PAIF flags, install interrupt handlers and set the PAOVI or PAII bits in TMSK2 to enable the interrupts. As always, each interrupt handler must reset its interrupt flag by writing a one to the flag bit. Write an appropriate initial value to the 8-bit count register PCNT and then enable the pulse accumulator by setting the PAEN bit in PACTL. The example above demonstrates how the pulse accumulator overflow interrupt facilitates counting more than 255 pulses.

 

The Gated Timer

The gated timer subsystem provides an easy way to measure pulse widths, time external events, and discriminate between pulses with differing widths. It is configured and controlled by the same registers and control bits used by the pulse accumulator. PORTA pin 7 is used as a timer gating signal. The gate signal's active state is determined by the pulse edge bit PEDGE in the pulse accumulator control register PACTL. When the PA7 gating signal is active, the pulse accumulator count register PACNT is incremented every 64th E-clock cycle, which is equivalent to 32us per count with an 8 MHz crystal and 16 us per count with a 16 MHz crystal. Interrupts may be triggered when the gated timer count overflows, and/or when the trailing edge of the timer gate signal is detected.

Implementation of the gated timer subsystem follows the same five steps used by the pulse accumulator. To configure PORTA pin 7 for gated timer use, set PA7 as an input or output by writing to the PORTA.DIRECTION register, and set the PAMOD bit in the pulse accumulator control register PACTL.

The PEDGE bit in the PACTL register determines the gate signal's inactive, or inhibiting, state. If PEDGE is clear, the PACNT register is inhibited from advancing while PA7 is LOW (in other words, the gating signal is active high). If PEDGE is set, the PACNT register is inhibited while PA7 is HIGH (in other words, the gating signal is active low).

Setting the PAEN (pulse accumulator enable) bit enables the gated timer. Once enabled, the PACNT register is incremented every 32us (if the crystal frequency is 8 MHz) while the gating signal at PA7 is active.

Interrupts are triggered and controlled by the same flag and mask bits used by the pulse accumulator. The pulse accumulator overflow flag (PAOVF) is set whenever pulse accumulator count register PACNT overflows. An overflow interrupt is recognized if the overflow interrupt mask PAOVI is also set.

The pulse accumulator input flag PAIF is set by the processor whenever the trailing edge of the gating signal on PA7 is detected. If the associated interrupt mask PAII is also set, an interrupt is recognized. To measure a pulse width using this interrupt, it is common to clear PACNT, install a pulse accumulator edge interrupt handler by executing

CFA.FOR <name OF interrupt handler> PULSE.EDGE.ID ATTACH

and then enable the gated timer subsystem. PACNT is then incremented every 32us (for an 8 MHz crystal) while the input gating signal is active. At the trailing edge of the input signal, a PAIF interrupt is recognized and the interrupt handler can read and reset PACNT. The PACNT reading multiplied by 32us is the pulse width of the timer gate signal in microseconds.

 

Words to Configure the Gated Timer

The following utility words configure the gated timer subsystem. This code relies on definitions presented in the Pulse Accumulator section.

\ the following 2 constants are used as inputs to INIT.GATED.TIMER:
1    CONSTANT    HIGH.PULSE        \ for measuring an active HIGH pulse
2    CONSTANT    LOW.PULSE        \ for measuring an active LOW pulse
 
 
: INIT.GATED.TIMER ( gate.pulse -- )
    CASE
        HIGH.PULSE    OF    PEDGE PACTL CLEAR.BITS    ENDOF
        LOW.PULSE    OF    PEDGE PACTL SET.BITS    ENDOF
        CR ." You must specify either HIGH.PULSE or LOW.PULSE."
        ABORT
    ENDCASE
    PAMOD PACTL SET.BITS        \ mode is gated timer
;
 
: ENABLE.GATED.TIMER ( -- )
    ENABLE.PULSE.ACCUMULATOR
;
 
: DISABLE.GATED.TIMER ( -- )
    DISABLE.PULSE.ACCUMULATOR
;
 

Measuring Pulse Widths Using the Gated Timer

The following example demonstrates how to use the gated timer subsystem to measure a pulse width.

DECIMAL
32    CONSTANT    USEC/TICK    \ 1 clock tick equals 32 usec at 8 MHz crystal freq.
 
\ Computes and prints the width of the detected pulse in microseconds.
: SAY.PULSE.WIDTH ( -- )
    PACNT C@                \ get the number of ticks in PACNT
    #PULSE.OVERFLOWS @ 256 * +    \ get additional ticks from overflows
    USEC/TICK *                \ total usec=(PACNT+(#OVERFLOWS*256))*32us/tick
    ." Pulse width = " . ." usec."
;
 
 
\ This is the interrupt service routine for the pulse accumulator
\ edge detection interrupt.  It is executed at the trailing edge of the gate signal,
\ which corresponds to the time when the gated timer has just finished counting.
\ This handler disables the pulse accumulator interrupts so that no further
\ counting will be done.
: TRAILING.EDGE.HANDLER ( -- )
    DISABLE.PULSE.ACCUMULATOR        \ disable counting
    PAIF TFLG2 C!                \ clear PAIF flag
    PAOVI PAII OR TMSK2 CLEAR.BITS     \ disable PAOVI & PAII interrupts
;
 
\ INIT.GATED.PULSE.TIMER uses the PACNT register overflow interrupt
\ handler defined earlier to extend the range of measurable pulse
\ widths beyond 256*32 usec.  INIT.GATED.PULSE.TIMER clears
\ the relevant count variables, configures the gated timer subsystem,
\ installs the overflow and trailing edge interrupt handlers defined above,
\ and enables the overflow and pulse edge interrupts, and the gated timer.
: ENABLE.GATED.PULSE.TIMER ( gate.pulse -- )
    PAOVI PAII OR TMSK2 CLEAR.BITS    \ disable PAOVI & PAII ints.
    0 PACNT C!                    \ clear the PACNT register
    0 #PULSE.OVERFLOWS !            \ clear PACNT overflow count
    INIT.GATED.TIMER                \ init gated timer
    CFA.FOR PAOVF.HANDLER         PULSE.OVERFLOW.ID ATTACH
    CFA.FOR TRAILING.EDGE.HANDLER   PULSE.EDGE.ID         ATTACH
    PAOVI PAII OR TFLG2 C!            \ clear interrupt flags
    PAOVI PAII OR TMSK2 SET.BITS         \ enable PAOVI & PAII interrupts
    ENABLE.GATED.TIMER
;

After connecting a pulse signal source to PORTA pin PA7, executing the following words will measure the source's HIGH pulse width:

HIGH.PULSE ENABLE.GATED.PULSE.TIMER

After the pulse, execute

SAY.PULSE.WIDTH

to display the calculated pulse width.

 

The Real-Time Interrupt Function

The real-time interrupt (RTI) function provides a periodic time reference signal. It may be used in applications which require an action to be reliably performed on a regular basis. For example, the RTI is a convenient way to ensure that the computer operating properly (COP) subsystem is periodically serviced.

The RTI function triggers a periodic interrupt. Two rate bits, RTR0 and RTR1 in the PACTL register program the real-time interrupt period according to the following table (F1 pp.7-12...7-13, HC11 pp.10-11...10-12):

RTR1 RTR0 RTI Period @ 8 MHz RTI Period @ 16 MHz
0 0 4.10 ms 2.05 ms
0 1 8.19 ms 4.10 ms
1 0 16.38 ms 8.19 ms
1 1 32.77 ms 16.38 ms

Each time the RTI period has elapsed, the processor sets the RTIF interrupt flag in the TFLG2 (timer flag 2) register. If the interrupt mask bit RTII in TMSK2 is also set, an interrupt is recognized. As with all interrupts, it is very important that the RTI's interrupt handler reset the interrupt flag by writing a one to it.

If the RTI periods do not suit your application, a timer based on an output compare function can be used to perform a similar service (as demonstrated in the OC1 alarm example presented earlier in the chapter). Given the simplicity of the RTI function, its implementation is left as an exercise to the interested reader.

 

Programmable Timer and Pulse Accumulator Summary

The programmable timer and pulse accumulator may be used to implement timesliced multitasking, clocks, programmable waveform generation, pulse width measurement and discrimination, control of stepper motors, external event counting, and many other interesting applications.

The explanations and commented code examples presented in this chapter demonstrate how to configure, initialize, and enable each of the timing and counting functions.

 
This page is about: MC68HC11 Microcontroller Programmable Timer and Pulse Accumulator – This chapter describes how programmable timer and pulse accumulator can be used to: Implement clocks Measure time intervals Sense input signal transitions Measure input signal pulse widths Generate output pulse signals Control stepper motors with …
 
 
Navigation