Digital and Timer-Controlled I/O
Overview of available digital IO
The QCard Controller Single Board Computer (SBC) provides up to 8 digital I/O lines, 8 analog input lines (which may be used as digital inputs), and up to three communications channels. The digital I/O lines originate from two 8 bit ports on the CPU (Freescale 68HC11) named PORTA and PORTE. All of these lines can be controlled from your C language application program to implement instrument control and automation functions.
Table 8-1 summarizes the uncommitted digital I/O available.
Table 8-1 The QCard Controller’s Uncommitted Digital I/O
I/O Lines | Type | Port Address | Comments / Alternate Uses |
---|---|---|---|
6 | Timer-controlled inputs or outputs including 3 input-capture, 3 output-compare, and pulse accumulator | PA 0-2, 5-7 | Bit-by-bit configured by the application as inputs or outputs, including: Timed inputs: PA 0-2 Timed outputs: PA 5-7 Pulse accumulator: PA 7 |
There are a total of 6 uncommitted digital I/O lines for your use. After initialization or reset, all digital I/O lines are configured as inputs, but they may be reconfigured as outputs.
In addition to these I/O lines there are several committed to other services on the controller; these are summarized in Table 8-2.
Table 8-2 Committed I/O pins
Service | Port | Pins |
---|---|---|
8-bit A/D | CPU PORTE | PE 0-7 |
Serial 2 | CPU PORTA | PA 3-4 |
For applications requiring even more digital I/O, I/O lines usually committed to the 8-bit A/D and the secondary serial port may be redirected as general purpose digital I/O if these other services are not needed. Table 8 3 summarizes the digital I/O lines gained if other services are not used.
Table 8-3 Additional digital I/O lines made available if other services are forfeited and their committed I/O pins freed.
Services Used | Digital I/O Available | |||
---|---|---|---|---|
8-bit A/D | Serial 2 | Inputs | Outputs | Total |
Yes | Yes | 0 to 6 | 0 to 6 | 6 |
No | Yes | 6 to 14 | 0 to 6 | 14 |
No | No | 8 to 16 | 0 to 8 | 16 |
The maximum number of digital inputs and outputs is 16 (up to 16 can be configured as inputs, up to 8 as outputs) if none are used for the 8-bit A/D or the secondary serial port. Table 8 3 summarizes the digital I/O and alternate use of some of the I/O pins.
Digital inputs and outputs are very useful in data acquisition, monitoring, instrumentation and control applications. A low voltage (approximately 0 Volts) is established on a digital output pin when the processor writes a logical 0 to the corresponding bit in a data register associated with the digital output port. A high voltage (approximately 5 Volts) is established on the digital output pin when the processor writes a 1 to a corresponding bit in the port’s data register. This allows software to control external events and processes. For example, an application program can use digital outputs to activate solenoids and turn switches and lights on and off, or to interface the QCard Controller with a wide variety of digital accessories.
A digital input allows the processor to monitor the logical state of an external voltage by reading a data register associated with the port. External voltages near 0 Volts connected to a digital input cause the corresponding bit in the port’s data register to be read as a logical 0, and external voltages near 5 Volts connected to a digital input are read as a logical 1. Application programs can use digital inputs to read switches and keypads, implement a frequency counter, or to interface to digital devices such as A/D converters and real-time clocks.
Using digital I/O is very easy: simply configure the output port as input or output as explained below, and then use functions or assignment statements to read from or write to the port. The names of the data and direction registers and all required initialization routines are pre-defined in the header files so you don’t have to worry about hexadecimal register addresses in your code.
The following sections describe the available digital I/O ports and how to use them.
The digital I/O signals on the QCard Controller originate from a Motorolla 68HC11 processor chip. The 68HC11 provides two 8 bit ports named PORTA and PORTE.
Table 8-4 summarizes the digital input/output available on the QCard Controller including the names, addresses, and number of signals associated with the digital ports on the 68HC11. The "configurable as" column specifies whether the direction of the port may be changed on a bit-by-bit, nibble-by-nibble, or byte basis (or in the case of PORTE, configured as digital or analog input). The final column lists alternate uses (other than standard digital I/O), the signal pins and the number of signals associated with the alternate uses.
Table 8-4 Digital I/O Port Addresses and Configurability.
Port Name | Address (HEX) | I/O Line | Configurable As | Alternate Use (Signals Used) |
---|---|---|---|---|
68HC11: | ||||
PORTA | 8000 | 8 | Bitwise I/O | Serial2: PA3 & PA4 (2) Pulse accumulator: PA7 (1) Timed inputs: PA0-3 (3 or 4) Timed outputs: PA3-7 (4 or 5) |
PORTE | 800A | 8 | Bytewise digital or analog input | 8 bit A/D: PE0-7 (8) |
Table 8-5 specifies the named data direction register which controls the input/output direction, or specifies the functions that configure each digital I/O port.
Table 8-5 Digital I/O port data direction registers and configuration functions.
Port Name | Configured By |
---|---|
68HC11: | |
PORTA | DDRA |
PORTE | AD8On() AD8Off() |
Using the digital I-O ports on the 68HC11 microcontroller
This section describes how to configure and access the PORTA and PORTE digital ports in the 68HC11 chip on the QCard.
As you work through the examples in the remaining sections of the chapter, you can use a voltmeter to verify that the outputs are behaving as you expect. You can also connect the input signals through a 1 kOhm resistor to +5V or GND to verify that you can digitally read their values. (The 1 kOhm resistor is just a safety precaution to minimize the chance that you’ll "blow out" a port bit by mistakenly connecting an output bit to a supply voltage; even if you make this mistake, the resistor would limit the current to safe levels.)
Digital inputs and outputs are very useful in data acquisition, monitoring, instrumentation and control applications. A low voltage (near 0 Volts) is established on a digital output pin when the processor writes a logical 0 to the corresponding bit in a data register associated with the digital output port. A high voltage (near 5 Volts) is established on the digital output pin when the processor writes a 1 to a corresponding bit in the port’s data register. This allows software to control external events and processes. For example, an application program can use digital outputs to activate solenoids and turn switches and lights on and off, or to interface the QCard Controller with a wide variety of digital accessories such as D/A converters, displays, etc.
A digital input allows the processor to monitor the logical state of an external voltage by reading a data register associated with the port. External voltages near 0 Volts connected to a digital input cause the corresponding bit in the port’s data register to be read as a logical 0, and external voltages near 5 Volts connected to a digital input are read as a logical 1. Application programs can use digital inputs to read switches and keypads or to interface to digital devices such as A/D converters and real-time clocks.
PORTA
PORTA
is configurable as input or output on a bit-by-bit basis. To configure PORTA
, use an assignment statement to write to the DDRA
(Data Direction Register A) register. DDRA
and all 68HC11 register names are defined in the QEDREGS.H
file in the \MOSAIC\FABIUS\INCLUDE\MOSAIC
directory. Writing a 1 to a bit position in DDRA
configures the corresponding port bit as an output, and writing a 0 to a bit position configures the corresponding bit as an input. For example, the following C statement configures PORTA
as all outputs:
DDRA = 0xFF;
To configure PORTA as all inputs, use the statement:
DDRA = 0x00;
If we want to configure bits 0-6 as inputs, and bit 7 as output, we can execute:
DDRA = 0x80;
To change the state of an output bit on PORTA
of the 68HC11 chip, use an assignment statement with PORTA
on the left hand side to write to the port’s data register named PORTA
. For example, if PORTA
is configured as all outputs, the following C statement sets all PORTA
bits high:
PORTA = 0xFF;
To read the state of PORTA
, use an assignment statement with PORTA
on the right hand side to read the port’s data register. For example, the following code fragment reads PORTA
and places the results in the variable named latest_porta_state
:
static unsigned char latest_porta_state; latest_porta_state = PORTA;
PORTE
PORTE
(named in the QEDREGS.H
file) is an 8 bit analog or digital input port. PORTE
is configured as a digital input after a reset or restart, and is read in the same way that PORTA
is read: simply use it as the right hand side of an assignment statement. For example, to read the digital state of PORTE, your program could execute the statements:
static unsigned char latest_porte_state; latest_porte_state= PORTE;
To configure PORTE for analog input, use the function:
AD8On()
To turn off the 8 bit A/D and revert to a digital input port, use:
AD8Off()
(For experts and the curious: AD8Off()
turns the 8 bit analog converter off by clearing the A/D power up bit named ADPU
in the OPTION
register; AD8On()
sets the ADPU
bit.)
Using uninterruptable operators
The importance of uninterruptable operators
Care must be taken when performing "read-modify-write" operations in applications that use interrupts or multitasking. Operations such as setting or clearing individual bits in a byte while leaving other bits unchanged are called "read/modify/write" operations because they involve reading the port, modifying the read contents, and writing the result back to the port. Unpredictable results can occur if more than one interrupt service routine or task tries to access a single port or memory location at the same time using a read/modify/write sequence. The simplest solution to this problem is to access the memory location or port using an "uninterruptable" read/modify/write operator.
The following scenario illustrates the importance of uninterruptable operators when more than one task or interrupt routine is writing to a memory location. Let’s assume that two different tasks are controlling the bits of PORTA. Assume that TASK1 is controlling the state of bit 4, and TASK2 controls bit 7. Let’s assume that bit 4 is low when TASK2 tries to execute the following code:
static unsigned char mask = 0x80; PORTA |= mask;
TASK2 is merely trying to set the top bit in PORTA to 1, but this statement may have unintended consequences. The compiler generates code that reads the contents of PORTA, performs a bitwise OR with the contents of mask, and stores the result back into PORTA. Assume that the timeslicer interrupt is serviced just after the OR instruction and transfers control to TASK1. TASK1 may change the state of bit 4 to a 1. When control is then transferred back to TASK2, the final store to PORTA is executed. Unfortunately, this store command erroneously sets bit 4 back to the low state! TASK2 was interrupted after it read the state of PORTA but before it had a chance to write the new contents, so it undoes the change that TASK1 made in the state of PORTA bit 4! This can indeed cause problems in an application program.
Pre-coded read-modify-write functions
The pre-coded read-modify-write functions avoid this problem by disabling interrupts for ten to sixteen cycles (2.5 to 4 microseconds at a 16 MHz crystal speed). This prevents the corruption of the contents when different tasks or interrupts share access to a single location. The following functions are uninterruptable:
void ChangeBits( uchar data, uchar mask, xaddr address ) void ClearBits( uchar mask, xaddr address ) void SetBits( uchar mask, xaddr address ) void ToggleBits( uchar mask, xaddr address )
Additional uninterruptable operators are declared in the XMEM.H
header file in the \MOSAIC\FABIUS\INCLUDE\MOSAIC
directory; these routines are described in detail in the Control-C Glossary.
Create your own uninterruptable functions
It is easy to create your own uninterruptable functions using the _protect
keyword. For example, the following uninterruptable function sets specified bits in a port or memory byte:
void _protect SetBitsUninterrupted( uchar mask, char* address ) { *address |= mask; }
In response to the _protect
keyword, the compiler ensures that interrupts are temporarily disabled while SetBitsUninterrupted()
is executing, and that the global interrupt mask (the I bit in the condition code register) is restored to its prior state after the function terminates.
Simple stores to and fetches from 1-byte or 2-byte memory locations are intrinsically uninterruptable because they are implemented with single assembly-language opcodes. However, the 68HC11 processor does not have a single opcode that can access a 32 bit memory location. Thus, problems can arise when one task writes to a floating point or long variable, and a second task needs to read the saved value. The data that is read may be invalid if the read or the write is interrupted between the time of the writing/reading of the first 16 bits and the writing/reading of the second 16 bits. In these cases uninterruptable operators should be used. An example is presented by the functions named:
PeekFloatUninterrupted() PokeFloatUninterrupted()
which are defined using the _protect keyword in the TURNKEY.C
example program found in the last chapter of this book.
The 68HC11 processor’s output compare functions
The processor’s programmable timer subsystem contains 5 output compare (OC) functions (named OCx for x from 1 to 5) associated with PORTA output pins PA7 through PA3 (in descending order). These output compare functions allow you to specify actions that take place at particular, well determined times. 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. They can be used to implement a stepper motor controller, pulse generator, pulse width modulation (PWM) signals, precisely timed output pulses, timesliced multiplexing, or serial communications.
Output-compare functions work by automatically changing PORTA output pins and/or invoking an interrupt service routine (ISR) whenever the contents of a free-running 16-bit counter (TCNT) matches the contents of user-set output-compare registers, TOCx. When these contents match, we say that a "successful output compare" has occurred. Thus the programmer can precisely specify a future time at which an action will occur by simply storing the time as a 16-bit value in the appropriate output compare register, TOCx. The free running counter counts at a programmable rate, from 0 to 65536, then rolls over to zero and continues counting. Its rate is one count each 2 microseconds, for a rollover period of 131.072 milliseconds. Consequently, you can set up output compares to trigger events with a resolution of 2 microseconds, and up to 131.072 milliseconds into the future (or arbitrarily longer if you keep track of rollovers on TCNT).
Because TCNT is clocked by a prescaler driven from the system E clock, you can change the count rate by modifying the prescaler’s division ratio, from its current value of 8 to 1, 4, or 16, changing its rollover period to 16.384, 65.536, or 262.144 milliseconds. If you do though, the system timeslicer will be affected. We find that a 2 microsecond tick rate provides sufficient resolution for most applications.
Each of the five output compare subsystems has a 16-bit TOCx register, a successful compare OCxF (interrupt) flag, and an interrupt mask OCxI, where "x" is the output compare number. OC1 can control any of pins PA3 through PA7 and it has its own register to specify which of PORTA pins are affected. Output compares OC2, OC3, OC4 and OC5 each control a single pin. They each 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 automatically sets an output compare’s OCxF flag bit in the TFLG1 register when the contents of the TCNT register and the OC’s TOCx register are equal. At the same instant, the state of the associated PORTA pin is set, cleared, or toggled as specified by the output mode bits. In addition, if the output compare’s OCxI mask bit in the TMSK1 register is set, an interrupt is recognized when a successful compare occurs.
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:
Signal Change – 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. OC1 is enabled by storing the data to be output in OC1D and specifying the pins to be changed in OC1M, and OC2-OC5 are enabled simply by storing a 2-bit code into TCTL1 specifying the desired signal change. Using the CFORC register, it is also possible for software to immediately force a state change on a timer-controlled signal without causing an interrupt.
Interrupt – To trigger an interrupt when time TCNT = T, an interrupt handler must be installed, and the output compare’s local interrupt must be enabled by setting bits in TMSK1.
Table 8-6 Output Compares and their properties
Output Compare | Controlled PORTA Pin | Comparison Register | Comments and Alternate Use |
---|---|---|---|
OC1 | PA3, 4, 5, 6, and/or 7 | TOC1 | May control multiple pins simultaneously, or be paired with another OC to jointly control pin PA3, 4, 5, or 6. |
OC2 | PA6 | TOC2 | The OC2 timer is used by the kernel’s timeslicer and elapsed time clock functions. If you do not use these functions, pin PA6 may be controlled by OC1 or used for general purpose I/O. |
OC3 | PA5 | TOC3 | Not used by the kernel. |
OC4 | PA4 | TOC4 | Used as an output by the secondary serial port. Available if you do not need Serial 2. |
OC5 | PA3 | TI4O5 | Shared with Input Capture 4, which is used as an input by the secondary serial port. Available if you do not need Serial 2. |
As summarized in the table, the output compares are not all identical in function, and some are used by the operating system. OC1 and OC5 differ from the others slightly in function, OC2 is used by the operating system’s timeslicer, and OC4 and OC5 are used by the secondary serial port:
- OC1 is special in that it can synchronously control any of PORTA pins PA7, PA6, PA5, PA4, and PA3. Even when OC1 is used to control several PORTA pins, the timer and interrupt functions of those pin’s output compares may still be used independently of the pin (to implement clocks, etc.). While the other output compares can be used to set, clear, or toggle an output pin, OC1 can be used only to set or clear a pin, but not to toggle it.
- OC2 is used by the timeslicer. Consequently, if you need the services of the timeslicer (timesliced task switching or elapsed time clock function) make sure that you do not use OC2 for other functions. Even if you do use the timeslicer, pin PA6 is still available for use as general purpose I/O, or as an output controlled by OC1.
- OC4 is used as an output by the secondary serial port, so you can’t use it or its associated pin PA4 if you need the second RS232 serial link.
- OC5 shares its timer register and output pin with input capture 4 (IC4). OC5 operates like the other output compares, but it must be initialized by clearing the I4/O5 bit of the PACTL (pulse accumulator control) register before it may be used. Also, 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.