Timer-Controlled I/O
Using the 9S12 (HCS12) enhanced capture timer (ECT) system
This page describes how-to configure and use the 8 Enhanced Capture Timer (ECT) signals on the PDQ Single Board Computer (SBC). The PDQ Board's 9S12/HCS12/MC68HCS12 microcontroller provides an advanced timing unit tied to input/output lines; each line can serve as an output compare or input capture. Four of the signals can be used as pulse accumulators. These functions are useful for instrument control and automation applications, including pulse counters and frequency meters. Pre-coded driver routines simplify the use of the ECT lines from Forth or C language application programs.
Overview of the enhanced capture timer system
The Freescale 9S12 (HCS12) microcontroller includes a sophisticated Enhanced Capture Timer (ECT) system connected to the PORTT digital I/O lines, as shown in Figure 1. As described in the Digital I/O chapter, PORTT is an 8-bit port with signals PT0 through PT7 available at the Digital I/O field header on the PDQ Board.
These pins can be tied to internal counters to provide the following functions:
- Each pin can serve as an output compare, changing its output state automatically when an internal free-running counter attains a threshold value.
- Each pin can serve as an input capture, causing an interrupt or recording the time at which an input value changes.
- Four of the input capture channels are tied to a 16-bit buffer register for capturing transition times.
- There are four 8-bit pulse accumulators which can also be configured as two 16-bit accumulators.
- A 16-bit modulus down-counter with 4-bit prescaler.
- Four user selectable delay counters for input noise immunity.
Each of the 8 PORTT pins can be configured as a standard digital input or output line, or an input capture (IC) or an output compare (OC). Up to four 8-bit pulse accumulators or two 16-bit pulse accumulators can be implemented. A 16-bit free-running counter named TCNT serves as a time base for the ECT system. A separate modulus down counter can be configured for continuous down-counting from a specified value, or for a single-pass countdown to 0. When the modulus down counter reaches 0, it can optionally latch the input capture and/or 8-bit pulse accumulator contents into special holding registers. Optional interrupts associated with the input captures, output compares, pulse accumulators, and counters allow the application program to perform complex timing functions.
The ECT system implements a set of eight 16-bit timer count registers named TC0 through TC7 (as declared in the C:\MosaicPlus\c\libraries\include\mosaic\HCS12REGS.h file). There is one timer count register for each I/O line of PORTT (PT0 through PT7). Each PORTT pin can be configured as either an output compare or input capture, and up to 4 of the pins can be configured as pulse accumulators.
Controlling the ECT with built-in C driver routines
The ECT system is controlled by a bank of hardware registers that are described in the Motorola ECT block user guide. To make it easy to use this sophisticated system, a set of C-language pre-coded software driver functions in the operating system lets you control the features of the Enhanced Capture/Timer. The following table lists the ECT software driver routines. Most of these functions accept a channel_id parameter that specifies which ECT channel is affected. The allowed range of the ECT channel_id parameter is from 0 to 7 for input captures and output compares, and from 0 to 3 for 8-bit pulse accumulators. The named constants PULSE_A and PULSE_B are used to identify the 16-bit pulse accumulator channels, and the constants PULSE_A_EDGE, PULSE_A_OVERFLOW, PULSE_B_OVERFLOW, MODULUS_UNDERFLOW, and TCNT_OVERFLOW are used to identify flag and interrupt bits associated with the ECT as described below.
Enhanced Capture/Timer Routines as declared in the TIMERIO.h file | ||
---|---|---|
ECTClearInterruptFlag() | ModCounterSetup() | PULSE_A_GATED_HIGH |
ECTFastClear() | ModCounterUpdate() | PULSE_A_GATED_LOW |
ECTInterruptDisable() | MODULUS_UNDERFLOW | PULSE_A_OVERFLOW |
ECTInterruptEnable() | OC7ClearsIt() | PULSE_A_RISING_EDGE |
ECTPrescaler() | OC7Disconnect() | PULSE_B |
ECTStandardClear() | OC7SetsIt() | PULSE_B_OVERFLOW |
HoldingRegForceLatch() | OC_CLEAR_ACTION | PulseASetup() |
IC_1024_CYCLE_DELAY | OC_NO_ACTION | PulseBSetup() |
IC_256_CYCLE_DELAY | OC_SET_ACTION | PulseDisable() |
IC_512_CYCLE_DELAY | OC_TOGGLE_ACTION | PulseEnable() |
IC_DELAY_DISABLED | OCAction() | PulseHoldingRead() |
ICFirstPolarity() | OCForce() | PulseRegRead() |
ICHoldingRead() | OCICRegRead() | PulseRegWrite() |
ICNoiseDelay() | OCNoToggleOnOverflow() | TCNT_OVERFLOW |
ICNoOverwrite() | OCRegWrite() | TCNTRead() |
ICOverwriteOK() | OCToggleOnOverflow() | TRIGGER_OFF |
ICPulseConfig() | OutputCompare() | TRIGGER_ON_ANY_EDGE |
InputCapture() | PULSE_A | TRIGGER_ON_FALLING_EDGE |
ModCounterLoad() | PULSE_A_EDGE | TRIGGER_ON_RISING_EDGE |
ModCounterRead() | PULSE_A_FALLING_EDGE | TriggerEdge() |
ECT counters
Two counters drive the majority of functions implemented in the ECT system: the free-running counter and the modulus down counter.
The free running counter TCNT
A free-running 16-bit counter named TCNT counts from 0x0000 to 0xFFFF and immediately rolls over (overflows) to 0x0000 and continues counting. The operating system configures TCNT to free-run with a driving period of 1.6 microseconds (us), and all of the software driver routines assume that TCNT behaves in this free-running manner. (Although it is possible to explicitly change some control bits in the TSCR1, TSCR2 and PACTL registers such that TCNT does not free-run, we strongly recommend maintaining the free-running operation). The rollover time of TCNT is 104.858 milliseconds, calculated as 216 times the 1.6 us driving period. The ECTPrescaler() function lets you change the period of TCNT. Available TCNT periods range from 0.05 us to 6.4 us which correspond to the allowed prescale values of 1, 2, 4, 8, 16, 32, 64, or 128 as described in the glossary entry of ECTPrescaler(). For example, to change the TCNT period to its slowest allowable value, execute:
ECTPrescaler(128);
This results in a TCNT period (time resolution) of 0.05us * 128 = 6.4us. The rollover time is increased to over 419 milliseconds. To read the value of TCNT, simply use an assignment statement with TCNT on the right hand side. For example, the statement:
uint tcnt_contents = TCNT;
loads the variable named tcnt_contents with the 16-bit value in the TCNT register. TCNT is a read-only register, so writes to it have no effect.
The free-running counter is involved in most of the Enhanced Capture/Timer’s features. Output compares take a specified action when the value of TCNT matches the value in the output compare channel’s timer count (TC) register. TCNT overflows from 0xFFFF to 0x0000 can cause an output compare pin to toggle, and can trigger an interrupt. Input captures store the value of TCNT into the channel’s TC register when a specified trigger edge appears at the corresponding PORTT input pin. Note that TCNT is not currently used by any operating system services; the timeslicer multitasker of the V6.xx kernel uses the RTI (real-time interrupt) clock to manage task switching. In fact, the operating system does not tie up or rely upon any of the ECT resources.
The modulus down counter
The modulus down counter is configured by the ModCounterSetup() function which is fully described in the C Glossary. The modulus counter starts at a specified starting value (as set by the ModCounterLoad() function) and counts down to zero. It can be configured to stop at 0, or to automatically reload the starting value and continue counting. The modulus counter can trigger an optional interrupt when it underflows (reaches 0). This down counter is clocked with a period of 0.05 microseconds (us) times a scaler that can be specified as 1, 4, 8 or 16. You can configure the down counter to return either the current value or the starting (load) value when it is read using the ModCounterRead() function. Writes to the modulus counter are performed using the ModCounterLoad() and ModCounterUpdate() functions.
In addition to providing a programmable down counter and interrupt source, the modulus down counter can be configured to control the holding registers associated with input capture channels 0 to 3, and the related 8-bit pulse accumulator channels 0 to 3. These input capture and pulse accumulator functions are implemented on PORTT pins PT0, PT1, PT2, and PT3. When the modulus counter reaches 0, the contents of the 16-bit timer count (TC) registers of the input capture, and the 8-bit pulse accumulator register contents can be transferred to the corresponding holding registers. The implementation details of this feature are described below.
Output compares
An output compare is declared by passing an ECT channel_id parameter in the range 0 to 7 to the OutputCompare() function. A successful output compare occurs when the channel’s timer count (TC) register count equals the contents of the 16-bit TCNT free-running counter register. The OCAction() function writes to the TCTL1 and TCTL2 registers to configure an output compare to initiate a specified pin action (named OC_NO_ACTION, OC_SET_ACTION, OC_CLEAR_ACTION, or OC_TOGGLE_ACTION) when a successful output compare occurs. OC_NO_ACTION is the default after a reset. The specified pin action can also be forced to occur at any time by invoking the OCForce() function. An optional interrupt can be generated upon each successful output compare. This makes the output compare function useful as an accurate interrupt-based timer, whether or not the pin action is enabled.
If a PORTT pin is configured as an output compare with a pin action other than OC_NO_ACTION, the specified pin will become an output regardless of the value written to the PORTT_DIRECTION (also called the DDRT) register. In this case, the ECT system will control the pin level regardless of the data written into the PORTT register. The contents of the output compare timer count (TC) register can be modified using the OCRegWrite() function, and its value can be read using the OCICRegRead() function.
Output compare channel 7 (OC7) can be configured to set or clear any of the PORTT output pins upon a successful output compare on OC7. This feature is configured using the functions OC7SetsIt(), OC7ClearsIt(), and OC7Disconnect().
Toggle On Overflow
Any output compare pin can also be configured to toggle to the opposite state when the free-running TCNT register overflows from 0xFFFF to 0x0000. This feature is configured using the OCToggleOnOverflow() and OCNoToggleOnOverflow() functions.
In a prior chapter, we learned about the dedicated Pulse Width Modulation (PWM) system on the HCS12 processor that generates signals on PORTP. The ECT output compares can be used to generate PWM outputs on PORTT. These PWM outputs can be generated using output compares in a variety of ways. Most methods use the output compare interrupts in conjunction with pin actions to shape the output waveform. But it is also possible to use an output compare channel to create a PWM waveform without using interrupts.
The combination of an output compare pin action and the toggle-on-overflow feature allows the generation of pulse-width modulated outputs with variable polarity and duty cycle without requiring interrupts or intervention from the processor. This scheme works if the period of the PWM signal equals the rollover time of the TCNT free running counter, which is 65,536 times the TCNT period. Using the default TCNT period of 1.6us, the default rollover time is just under 105 milliseconds (ms). The TCNT period (and hence the rollover time) can be changed using the ECTPrescaler() function.
An output compare example
As an example of how to use output compares, let’s assume that we want to configure two pulse width modulated square waves on PT0 and PT1 that are logical inverses of one another: when PT0 is high, PT1 is low, and visa versa. On the Digital Field header, PT0 is pin 24, PT1 is pin 23. We’ll assume that each signal can have a period of just under 105 ms which is the default rollover time of the TCNT free-running counter. To start, we’ll assign each signal to have a 1/4 duty cycle, with PT0 starting in the active high state and going inactive low 1/4 period later, and PT1 starting in the active low state and going inactive high 1/4 period later. To accomplish this, we’ll configure both signals to toggle to the active state when the TCNT free-running counter overflows from 0xFFFF to 0x0000. We’ll write the value 0x4000 into the TC (timer count) registers of channels 0 and 1, and specify that the output signals will go to their inactive state upon a successful output compare. We choose the value 0x4000 because it is 1/4 of a full 2^16 TCNT period (that is, 0x4000 is 1/4 of 0x10000). We set PT0 as an output compare with the OC_CLEAR_ACTION, and PT1 as an output compare with the OC_SET_ACTION. The following commands create the desired outputs:
OutputCompare(0); // channel 0 on pin PT0 is an output compare OutputCompare(1); // channel 1 on pin PT1 is an output compare OCRegWrite(0x4000, 0); // set TC0 = 0x4000 for 1/4 duty cycle OCRegWrite(0x4000, 1); // set TC1 = 0x4000 for 1/4 duty cycle OCAction(OC_CLEAR_ACTION, 0); // channel 0 goes inactive low upon successful compare OCAction(OC_SET_ACTION, 1); // channel 1 goes inactive high upon successful compare OCToggleOnOverflow(0); // channel 0 toggles from low to high at period start OCToggleOnOverflow(1); // channel 0 toggles from high to low at period start
See the AutoPulseTrain() function in "Pulses Demo" project. Note that we do not have to declare PT0 and PT1 as outputs using the PORTT_DIRECTION (DDRT) register; the ECT automatically configures the output compare channels as outputs. After a transient startup period that can last up to 105 ms (the TCNT rollover period), the desired waveforms will be established. The duty cycles can easily be changed by writing different values into the TC0 and TC1 registers using OCRegWrite(). The polarities can be changed by changing the pin action using OCAction(). This approach generates jitter-free PWM outputs without requiring ongoing intervention by the HCS12 processor. Other methods of using output compares to generate PWM signals are discussed at the end of this chapter.
Input captures
An ECT channel can be configured as an input capture by passing a channel_id in the range 0 to 7 to the InputCapture() function. A PORTT pin configured as an input capture stores the contents of the 16-bit TCNT timer register into its TC (timer count) register when a specified edge is detected at the pin.
Edge triggering
The TriggerEdge() function specifies the edge that triggers the input capture. Valid edge specifier constants that can be passed to this function are TRIGGER_ON_RISING_EDGE, TRIGGER_ON_FALLING_EDGE, TRIGGER_ON_ANY_EDGE, or TRIGGER_OFF (which is the default after a reset). The contents of any channel’s TC (timer count) register can be read using the OCICRegRead() function.
Inserting delays in edge detection for noise immunity
The processor's digital input pins exhibit some hysteresis that prevents chattering during input voltage rise and fall times (as shown in this graph). The magnitude of the hysteresis is approximately 0.1 – 0.15V, so noise spikes smaller than that magnitude do not cause false triggering.
In some cases, for example when debouncing switch inputs, you may also need immunity from full sized but transient logic pulses that occur for brief duration. To prevent these very short noise spikes from triggering a capture, you can pass a delay parameter to the ICNoiseDelay() function. Valid delay parameters are IC_DELAY_DISABLED (the default), IC_256_CYCLE_DELAY, IC_512_CYCLE_DELAY, or IC_1024_CYCLE_DELAY. The delays are expressed in terms of the 0.05 microsecond E-clock period, so the allowed delays are 12.8 us, 25.6 us, and 51.2 us. Any pulses with duration less than the specified delay are ignored by the capture circuitry.
This hardware-based debouncing does not require any real-time software intervention and so it does not degrade the performance of your application.
Setting multiple input captures on a single pin
Pairs of input captures can share the edges that are present on a single PORTT pin. If properly configured using the ICPulseConfig() function, an input capture event on pin PT3 is treated as a capture event on PT7. Similarly, a capture event on PT2 can stimulate a capture even on PT6, a capture event on PT1 can stimulate an event on PT5, and a capture event on PT0 can stimulate an event on PT4.
Buffering with holding registers
Input captures 0-3 on pins PT0 through PT3 (and the associated 8-bit pulse accumulators as described below) are optionally buffered by holding registers. The transfer from the input capture’s timer count (TC) register to the corresponding holding register can be initiated via the HoldingRegForceLatch() function. Alternatively, the input capture can be configured by ICPulseConfig() to transfer its value to the holding register when the modulus down counter reaches 0. The contents of the 16-bit input capture holding registers are read using the ICHoldingRead() function. Using the ICOverwriteOK() and ICNoOverwrite() functions, you can dictate whether or not the holding register can be updated before the application program has read it. As described below, these functions also control associated 8-bit pulse accumulators that can be configured on pins PT0 through PT3.
In addition to configuring input capture pin sharing, the ICPulseConfig() function specifies the way the holding registers are loaded for both input capture channels 0-3 and the closely related 8-bit pulse accumulators PA0 through PA3. Three flag parameters (named holding, latch_mode, and two_events in the function prototype) are passed to ICPulseConfig() to configure the holding registers for input captures 0-3 and 8-bit pulse accumulators 0-3. If the holding and latch_mode flags are both false, then the two_events flag is relevant. When true, two_events specifies that the channel’s flag bit will not be set until 2 capture events have transpired (one capture is in the holding register, and the second is in the IC register). If either the holding or latch_mode flags is true, then a single input capture event sets the corresponding flag bit. If latch_mode is true, latching into the holding register (and clearing of 8-bit pulse accumulators) occurs when the modulus down counter reaches 0, or when a zero is written into the count register, or when HoldingRegForceLatch() is executed. The pulse accumulators require that both the holding and latch_mode flags are true to utilize the holding registers.
Setting other options
Some input capture options are shared with the 8-bit pulse accumulators that occupy the same PT0-PT3 pins, and are configured by the ICPulseConfig() function described in the next section. Note that the ICPulseConfig() function modifies a write-once register that can only be written to once after each power-up or hardware reset. Thus, ICPulseConfig() is effective only one time after each hardware reset.
An input capture example
To see input captures in action, let’s assume that you’ve loaded in the output compare code from the prior section that sets up square waves on pins PT0 and PT1. Let’s tie pin PT0 to pin PT2 through a series 1 Kohm resistor; the resistor prevents any hardware damage if we make a mistake and configure both PT0 and PT2 as outputs. In other words, on the PDQ Digital I/O header, we’ll connect pin 24 (PT0 output) to the adjacent pin 22 (PT2 input) through a 1K resistor.
If your board has undergone a restart since you loaded the code in the Output Compare Example above, use your terminal to send the code now. Recall that pin PT0 goes high when the TCNT register equals 0, and goes low when TCNT equals 0x4000. Now load the following code:
InputCapture(2); // channel 2 on pin PT2 is an input capture TriggerEdge(TRIGGER_ON_FALLING_EDGE); // channel 2 captures falling edges
Because the signal driving pin PT2 outputs a falling edge when TCNT equals 0x4000, the input capture will load 0x4000 into its timer count register TC2. If we wait for at least one falling edge to occur, the following C statement loads 0x4000 into the variable tc2_contents:
uint tc2_contents = OCICRegRead(2);
In other words, reading the channel 2 input capture register returns 0x4000. If we now change channel 2 to trigger on the rising edge as:
TriggerEdge(TRIGGER_ON_RISING_EDGE); // channel 2 captures rising edges
After 105 ms have elapsed, subsequent reads of the channel 2 count register via OCICRegRead(2) will return the value 0x0000, which is the value of TCNT when the rising edge occurs.
Pulse accumulators
The ECT implements four 8-bit pulse accumulators named PAC0 through PAC3 that count the pulses on the corresponding pins PT0 through PT3, and store the pulse count in an 8-bit register. Each pair of 8-bit pulse accumulators can be combined to form a 16-bit pulse accumulator; these are named PULSE_A and PULSE_B as discussed below. The pulse accumulators can capture pulses at rates up to 5 MHz.
The pulse accumulator system provides two modes of operation:
- Event counting mode — All accumulators (the four 8-bit and two 16-bit accumulators) can operate in this mode to count the number of events arrived at the associated pin.
- Gated accumulation mode — The 16-bit PULSE_A can also operate in this mode. As long as the PT7 signal is active (can be high or low), the PULSE_A counter is clocked by a free-running Eclock÷64 (or one pulse per 3.2 μsec) signal.
The pulse accumulators can generate interrupts for you:
- The 16-bit PULSE_A has two interrupt sources: PT7-edge and PULSE_A counter overflow.
- Two of the 8-bit pulse accumulators, PAC3 and PAC1, can generate interrupts whenever their counters overflow.
- PULSE_B can interrupt the MCU whenever its upper 8-bit counter overflows.
Using the 8-bit pulse accumulators
The 8-bit pulse accumulators are configured by passing a channel_id in the range 0 to 3 to the PulseEnable() function; the PulseDisable() function turns a specified pulse accumulator channel off.
Specifying the trigger edge
The trigger edge that increments an 8-bit pulse accumulator register equals the specified trigger edge of the corresponding input capture channels for the pin. In other words, passing a channel_id in the range 0 to 3 to the TriggerEdge() function specifies the edge that triggers the 8-bit pulse accumulator. Valid edge specifier constants that can be passed to this function are TRIGGER_ON_RISING_EDGE, TRIGGER_ON_FALLING_EDGE, TRIGERR_ON_ANY_EDGE, or TRIGGER_OFF. The contents of any pulse accumulator register can be read using the PulseRegRead() function.
Buffering the accumulated count
The 8-bit pulse accumulator channels 0-3 (and the closely associated input captures as described above) on pins PT0 through PT3 are optionally buffered by 8-bit pulse accumulator holding registers. These are controlled by the same functions that control the 16-bit input capture holding registers. The transfer from the pulse accumulator register to the corresponding holding register can be initiated via the HoldingRegForceLatch() function. Alternatively, the pulse accumulator can be configured by ICPulseConfig() to transfer its value to the holding register when the modulus down counter reaches 0; this is done by passing true values as the holding and latch_mode flag parameters to ICPulseConfig(). The contents of the 8-bit pulse accumulator holding registers are read using the PulseHoldingRead() function.
Controlling rollover
The ICPulseConfig() function can also be used to force each 8-bit pulse accumulator to count from/to 0xFF and then stop, as opposed to their default behavior which is to roll over to 0 and continue counting. As noted above, the ICPulseConfig() function modifies a write-once register that can only be written to once after each power-up or hardware reset. Thus, ICPulseConfig() is effective only one time after each hardware reset.
Using the 16-bit accumulator PULSE_A
A 16-bit pulse accumulator named PULSE_A is available on pin PT7 of PORTT. Its hardware is diagrammed in Figure 2.
PULSE_A uses the hardware resources of the 8-bit pulse accumulators PAC2 (lsbyte) and PAC3 (msbyte), so these two 8-bit pulse accumulators are not available if PULSE_A is enabled. This 16-bit pulse accumulator can be read or written by passing the PULSE_A channel_id parameter to PulseRegRead() or PulseRegWrite(), respectively. The 16-bit pulse accumulators do not have holding registers. PULSE_A is configured using the PulseASetup() function which writes to the PACTL hardware register. Its function prototype is:
void PulseASetup ( int edge_irq, int overflow_irq, int pulse_mode, int pulse16_enable)
Each input parameter is a flag. If the pulse16_enable flag is true, the 16-bit PULSE_A pulse accumulator on pin 7 of PORTT is enabled; otherwise, PULSE_A is disabled. The pulse_mode input parameter is one of the following four constants: PULSE_A_FALLING_EDGE, PULSE_A_RISING_EDGE, PULSE_A_GATED_HIGH, or PULSE_A_GATED_LOW. The former 2 modes increment the accumulator on the specified edge, while the latter modes enable a dedicated clock to increment the pulse count every 3.2 microseconds while the input is in the specified gating state. If the overflow_irq flag is true, an interrupt occurs every time the PULSE_A accumulator overflows from 0xFFFF to 0x0000. If the edge_irq flag is true, an interrupt is generated upon each qualifying input edge as configured by the pulse_mode parameter. After a power-up or hardware reset, the 16-bit PULSE_A accumulator is disabled.
To operate the PULSE_A accumulator independently of the input capture/output compare channel 7, disconnect the timer from pin PT7 by executing:
OutputCompare(7); OCAction(OC_NO_ACTION, 7);
Using the 16-bit accumulator PULSE_B
A 16-bit pulse accumulator named PULSE_B is available on pin PT0 of PORTT. PULSE_B uses the hardware resources of the 8-bit pulse accumulators PAC0 (lsbyte) and PAC1 (msbyte), so these two 8-bit pulse accumulators are not available if PULSE_B is enabled. This 16-bit pulse accumulator can be read or written by passing the PULSE_B channel_id parameter to PulseRegRead() or PulseRegWrite(), respectively. The 16-bit pulse accumulators do not have holding registers. PULSE_B is configured using the PulseBSetup() function which writes to the PBCTL hardware register. Its function prototype is:
void PulseBSetup ( int overflow_irq, int pulse16_enable)
Each input parameter is a flag. If the pulse16_enable flag is true, the 16-bit PULSE_B pulse accumulator on pin 0 of PORTT is enabled; otherwise, PULSE_B is disabled. If the overflow_irq flag is true, an interrupt occurs every time the PULSE_B accumulator overflows from 0xFFFF to 0x0000. After a power-up or hardware reset, the 16-bit PULSE_B accumulator is disabled.
The Pulse.B trigger edge equals the specified trigger edge of input capture channel 0 on pin PT0. To configure the PULSE_B trigger edge, make sure that channel 0 is an input capture by executing InputCapture(0) or InputCapture(PULSE_B), and call TriggerEdge() with channel_id = 0. For example, to count rising-edge pulses without generating interrupts, execute:
TriggerEdge(TRIGGER_ON_RISING_EDGE, PULSE_B); InputCapture(PULSE_B); PulseBSetup( FALSE, TRUE);
Changing the minimum detectable pulse width
Pulses must be of sufficient duration to be captured. They must be at least two Eclock periods (or 0.1 μsec) to be reliably detected, and they must be separated by another two Eclock periods.1) Consequently, the maximum frequency that can be measured by capturing pulses is 5 MHz, with a 50% duty cycle. If pulses arrive at a rate greater than 5 MHz, some will not be captured. Below 5 MHz (with 50% duty cycle), all pulses will be counted. In a frequency counting application, if you need to count frequencies greater than 5 MHz you can precondition the signal using a frequency divider such as the CD74HC(T)4024M or CD4024.
You may want to count events that need to be debounced. That is, you may want to capture only pulses that meet a minimum width requirement, while rejecting short transient pulses that might result from noise or a logic signal that chatters before settling to a new state. In that case, use the function ICNoiseDelay(). It configures all of the input captures so that pulses shorter than a specified delay are ignored. Valid input parameters are these constants:
- IC_DELAY_DISABLED,
- IC_256_CYCLE_DELAY (for a 12.8 μsec delay),
- IC_512_CYCLE_DELAY (for a 25.6 μsec delay), or,
- IC_1024_CYCLE_DELAY (for a 51.2 μsec delay).
The noise delay feature is disabled by default after a power-up or hardware reset.
ECT events and interrupts
There are 13 event sources associated with the Enhanced Capture/Timer system. Each of these events sets a specified flag bit in a hardware register when the event occurs. If an associated local interrupt bit is enabled (set) and if interrupts are globally enabled, then an interrupt request is also generated. The 13 event sources comprise the 8 input capture/output compare channels, overflow (from 0xFFFF to 0x0000) of the free-running TCNT counter, overflow of the 16-bit PULSE_A accumulator, detection of qualifying edge on the PULSE_A accumulator, overflow of the 16-bit PULSE_B accumulator, and underflow (to 0x0000) of the modulus down counter.
The ECT software drivers include functions that manage these interrupt sources. ECTClearInterruptFlag() accepts a channel_id (ECT channel identifier) and clears the associated flag bit by writing a 1 to it as required by the HCS12 hardware architecture. ECTInterruptDisable() accepts a channel_id and clears the associated local interrupt bit to disable the interrupt. ECTInterruptEnable() accepts a channel_id and sets the associated local interrupt bit to enable the interrupt. Note that the channel_id parameters passed to these three functions are not the same as interrupt identifiers ending in _ID that are passed to the ATTACH() routine. For each of the ECT interrupt sources, Table 10-2 lists the channel_id parameters passed to these 3 functions, as well as the interrupt identifiers passed to the ATTACH() routine.
ECT Channel Identifiers versus Interrupt Identifiers | |
---|---|
ECT channel_id 2) | Interrupt ID passed to ATTACH() |
0 | ECT0_ID |
1 | ECT1_ID |
2 | ECT2_ID |
3 | ECT3_ID |
4 | ECT4_ID |
5 | ECT5_ID |
6 | ECT6_ID |
7 | ECT7_ID |
TCNT_OVERFLOW | ECT_OVERFLOW_ID |
PULSE_A_OVERFLOW | PULSE_A_OVERFLOW_ID |
PULSE_A_EDGE | PULSE_A_EDGE_ID |
PULSE_B_OVERFLOW | PULSE_B_OVERFLOW_ID |
MODULUS_UNDERFLOW | COUNTER_UNDERFLOW_ID |
The ECTFastClear() function configures each of the ECT sources listed in Table 10-2 to automatically clear its flag bit when an associated register is accessed. This saves the application program from having to invoke the ECTClearInterruptFlag() function after each event occurs.
In fast clear mode,
- a read from an input capture TC (timer count) register using OCICRegRead(), or a write to an output compare TC register using OCRegWrite() automatically clears the associated flag bit in the TFLG1 register.
- any access to the TCNT register clears the timer overflow flag in the TFLG2 register.
- any access to the PACN3 and PACN2 registers via the PulseRegRead() or PulseRegWrite() functions clears the PULSE_A overflow and edge flags in the PAFLG register.
- any access to the PACN1 and PACN0 registers via the PulseRegRead() or PulseRegWrite() functions clears the PULSE_B overflow flag in the PBFLG register.
- any access to the modulus counter MCCNT register via the ModCounterRead(), ModCounterLoad() or ModCounterUpdate() functions clears the modulus underflow flag in the MCFLG register.
Fast-clear mode is disabled by default after a power-up or reset, and can be disabled by executing the ECTStandardClear() function.
For any enabled interrupts in the ECT system an interrupt handler must be posted by passing one of the interrupt identifiers listed in Table 10-2 to the ATTACH() routine. If the ECT system is in standard clear mode (the default after a reset), the interrupt routine must clear the appropriate interrupt flag bit by writing a 1 to it; the ECTClearInterruptFlag() routine can be used to accomplish this, or you can explicitly write a 1 to the appropriate bit in the proper flag register. In fast-clear mode, the interrupt service routine simply accesses the registers associated with the event, and this automatically clears the flag bit. The Real-Time Programming chapter describes interrupt handling in detail.
PWM generation using output compares and interrupts
This section presents a coded example that shows an effective method for creating an accurate pulse width modulated (PWM) output on a PORTT pin. This example configures the output pin as an output compare channel that is serviced by an interrupt service routine that is called each time there is a successful output compare. The period, duty cycle and polarity (active high or active low) of the signal can be controlled by the software. Within certain timing constraints as explained below, the output waveform will have accurate jitter-free timing.
The processor’s output compare functions provide lots of flexibility for creating single pulses or pulse width modulated waveforms. Most methods of generating either a single pulse or a pulse train (PWM signal) are variations on this algorithm:
- The desired start time of the pulse is programmed by storing an appropriate count in the output compare’s timer count (TC) register, either using an explicit write to the register or by invoking the OCRegWrite() function. The channel is configured as an output compare by calling OutputCompare(), and the channel’s pin action is set to take the output active high or active low (depending on the polarity of the desired pulse) by calling the function OCAction().
- A normal routine is made into an interrupt service routine (ISR) by using the MAKE_ISR() macro. This line must be called from the global scope, and may not exist inside any other function.
- The ISR as described below is posted using ATTACH(). The output compare’s interrupt is enabled by passing the channel_id to the ECTInterruptEnable() function. To avoid the need to explicitly clear the interrupt flag register, the ECTFastClear() function can be invoked.
- When the compare occurs, the pin state is automatically changed to its active state and an interrupt service routine called.
- The interrupt service routine reprograms the output compare pin action to automatically change its pin back to its inactive level on the next compare. This is done either by calling OCAction() or by explicitly writing the appropriate bitmask to the TCTL1 and TCTL2 registers.
- The ISR also increments the output-compare TC register by a value corresponding to the desired duration of the pulse. This can be done by explicitly accessing the TC register, or by using the OCICRegRead() and OCRegWrite() functions.
Since the pin state is changed by hardware automatically at specific values of the TCNT free running counter, the pulse width is controlled accurately to within the TCNT period time resolution irrespective of software latencies. By repeating the actions for generating a pulse, you can generate an output signal of a specific frequency and duty cycle. While software latency and execution times do not affect the timing of the waveform, they do impose limits on the frequency and duty cycles attainable.
The simplest way of generating a precise PWM waveform with arbitrary duty cycle and period is to use a single output compare to automatically turn on and off an output pin and an interrupt service routine that reprograms the output compare after each transition. The off transition invokes the ISR which sets up the turn-on time and programs the next output state to be on, and the on transition invokes the same ISR to set up the turn-off time and the next off state. The on and off times must each be great enough to contain the latency and execution time of the ISR. Consequently, duty cycles that would require very small on or off times are not attainable. If the ISR is delayed so that it does not program the next transition in time, than the output compare doesn’t find a match until TCNT rolls all the way around. In this case rollover delays of approximately 105 milliseconds (assuming that the default TCNT period is in effect) may be inserted into either the on or off time.
Let’s assume that we want a PWM output to appear on PORTT pin PT6. Thus we need to configure ECT channel 6 as an output compare that generates an interrupt request. We’ll put the ECT system in the fast clear mode using the ECTFastClear() function described above; this relieves the interrupt service routine from having to explicitly clear the flag register. Here is the commented code that generates the PWM output on PT6:
Listing 10-1
// this file demonstrates code featured in the "Timer-Controlled I/O" chapter. // As an example of how to use output compares, // let’s assume that we want to configure two pulse width modulated square waves // on PT0 and PT1 that are logical inverses of one another: // when PT0 is high, PT1 is low, and visa versa. // On the Digital Field header, PT0 is pin 24, PT1 is pin 23 // We’ll assume that each signal can have a period of just under 105 ms // which is the default rollover time of the TCNT free-running counter. // To start, we’ll assign each signal to have a 1/4 duty cycle, // with PT0 starting in the active high state and going inactive low 1/4 period // later, and PT1 starting in the active low state and going inactive high 1/4 // period later. To accomplish this, we’ll configure both signals to toggle // to the active state when the TCNT free-running counter overflows // from 0xFFFF to 0x0000. We’ll write the value 0x4000 into the // TC (timer count) registers of channels 0 and 1, and specify that // the output signals will go to their “inactive” state upon a // successful output compare. We choose the value 0x4000 because it // is 1/4 of a full 2^16 TCNT period (that is, 0x4000 is 1/4 of 0x10000). // We set PT0 as an output compare with the OC_CLEAR_ACTION, and PT1 as an output // compare with the OC_SET_ACTION. The following routine creates the outputs: // Copyright 2009 Mosaic Industries, Inc. All Rights Reserved. // Disclaimer: THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT ANY // WARRANTIES OR REPRESENTATIONS EXPRESS OR IMPLIED, INCLUDING, BUT NOT // LIMITED TO, ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS // FOR A PARTICULAR PURPOSE. #include <mosaic\allqed.h> // include all of the qed and C utilities _Q void AutoPulseTrain( void ) { OutputCompare( 0 ); // channel 0 on pin PT0 is an output compare OutputCompare( 1 ); // channel 1 on pin PT1 is an output compare OCRegWrite( 0x4000, 0 ); // set TC0 = 0x4000 for 1/4 duty cycle OCRegWrite( 0x4000, 1 ); // set TC1 = 0x4000 for 1/4 duty cycle OCAction( OC_CLEAR_ACTION, 0 ); // channel 0 goes inactive low upon successful compare OCAction( OC_SET_ACTION, 1 ); // channel 1 goes inactive high upon successful compare OCToggleOnOverflow( 0 ); // channel 0 toggles from low to high at period start OCToggleOnOverflow( 1 ); // channel 0 toggles from high to low at period start } // this program uses an output compare and its interrupt service routine (ISR) // to generate a pulse width modulated (PWM) output on PORTT pin 6. #define DEFAULT_HIGH_TIME 5000 #define DEFAULT_LOW_TIME 10000 int current_state = 0; // used by ISR to track state // the following two variables can be changed programatically, // or they can be modified interactively using the interactive debugger; // for example, typing at the terminal: high_time =INT 0x1000 // changes high_time to hex value 0x1000. uint high_time = DEFAULT_HIGH_TIME; uint low_time = DEFAULT_LOW_TIME; _Q void Pulse_Maker( void ) // interrupt service routine for output compare 6 { if( current_state == 0 ) // if output was low, and just toggled high... { OCAction( OC_CLEAR_ACTION, 6 ); // go low upon next successful compare TC6 += high_time; // this access to TC6 auto-clears the flag bit // if the output level just toggled high, the next output compare // forces the pin low after the high_time } else // else if the output level just toggled low... { OCAction( OC_SET_ACTION, 6 ); // set the mode/level bit so the next output // compare forces the pin high after low_time TC6 += low_time; // this access to TC6 auto-clears the flag bit } current_state = !current_state; // toggle the state variable } MAKE_ISR( Pulse_Maker ); _Q void Install_Pulse_Maker ( void ) // installs PWM output compare on PT6 { uint current_tcnt_contents = TCNT; // get current TCNT contents OutputCompare( 6 ); // make ECT6/PT6 an output compare OCRegWrite( ( current_tcnt_contents - 1 ), 6 ); // wait 1 rollover period til start current_state = 0; // declare current state as low, and... OCAction( OC_SET_ACTION, 6 ); // ...go active high upon successful compare ATTACH( Pulse_Maker, ECT6_ID ); // post the interrupt handler ECTFastClear(); // fast clear mode simplifies the ISR ECTInterruptEnable( 6 ); // enable output compare 6 interrupt ENABLE_INTERRUPTS(); // ensure that interrupts are globally enabled } int main ( void ) { AutoPulseTrain(); Install_Pulse_Maker(); return 0; }
Calling the Install_Pulse_Maker()
function from the main routine activates the generation of a pulse stream. The output appears on PORTT output pin PT6 on the Digital I/O header of the PDQ Board. The default signal stays high for 5000 counts (8000 us), and low for 10000 counts (16,000 us). The time difference between the pulse edges must be long enough to allow the Pulse_Maker()
ISR function (and, in the worst case, any other enabled interrupt service routines) to execute. The ISR shown in the listing is fairly efficient, but it could be made faster by replacing the calls to OCAction() with explicit writes to the TCTL1 and TCTL2 registers as documented in the Motorola ECT Block User Guide documentation. Using the pre-coded driver routine results in simpler and more readable code.
See also → Data Acquisition Using Analog to Digital Conversion