You are on page 1of 89

Controlling The Real World With Computers

::. Control And Embedded Systems .::

Data lines, bits, nibbles, bytes, words, binary and HEX

Home Order Let me know what you think


Next: Boolean Logic

Information (often called data) inside a computer, as well as on the board used in this tutorial, is
exchanged among the various components by means of metallic conductors called data lines. A
group of data lines is called a data bus.

Each data line carries a unit of data called a bit. A bit can be on or off. On is usually considered
to be 5 volts, and off is considered to be 0 volts, although modern systems often use lower on
voltages to decrease power consumption.

Data can be represented on paper as a series of ones and zeros. A one means a bit is on, and a
zero means it is off. A group of 8 bits is called a byte. A byte with a value of 0 would be
represented as 00000000. Non-zero bytes can be any combination of 1s and 0s. 01100010 will
be used as an example here. In the C language, a byte is called a character and is abbreviated
char.

When data is represented as a series of ones and zeros, it is said to be a binary representation,
or to have a base of 2 because it uses 2 digits. We humans use a base of 10, probably because
we have 10 fingers.

The left-end bit of a number represented in binary is called the most significant bit,
abbreviated msb, and the right-end bit is called the least significant bit, abbreviated lsb.

A little review might be helpful to those who are a little rusty on raising a number to a power.
No high math here -- to raise a number to a power just write it down the exponent number of
times and multiply. The exponent is the power to which a number is raised. One way to
recognize an exponent is by the fact that it is often raised when written:
52 = 5 * 5 = 25
23 = 2 * 2 * 2 = 8
44 = 4 * 4 * 4 * 4 = 256

Each bit position has a weight. For all numbering systems I am aware of (the mathematicians
probably know of others), the right, least-significant position is known as the 1's place. There,
the weight is equal to the base raised to the power of 0.
Any number raised to the power of 0 is equal to 1.

The exponent is increased by 1 with each move to the left. Thus, the second place from the
right has a weight equal to the base raised to the power of 1.
Any number raised to the power of 1 is equal to itself.
We were taught in grade school that the second place from the right is the 10's place. That's
because we were using a base of 10 and we were raising it to the power of 1. Since a base of 2
is used in binary, the second place from the right has a weight of 2 because it is 2 raised to the
power of 1. The next weight is 22 = 2 * 2 = 4, then 23 = 2 * 2 * 2 = 8 and so on.
The exponents are often used to designate a bit in a binary number. Bit 0 is on the right end of
the byte and bit 7 is on the left end. Bit 0 is the lsb and bit 7 is the msb. Data bits are often
abbreviated using the letter D -- D0, D1, D2, etc.

Bit D7 D6 D5 D4 D3 D2 D1 D0
exponent
Base 27 26 25 24 23 22 21 20
Weights 128 64 32 16 8 4 2 1

The example binary number above was 01100010. To figure out what the decimal value is,
simply add the weights for the bits that are turned on. In this case, bits 6, 5 and 1 are on. The
total of their weights equals 64 + 32 + 2 = 98.

A more general description of the procedure is to multiply the position weights by the values at
the positions, then add them up. The example 01100010 would be:
(0 * 128) + (1 * 64) + (1 * 32) + (0 * 16) + (0 * 8) + (0 * 4) + (1 * 2) + (0 * 1) = 98.

A common way of showing numbers in a C program is to use hexadecimal notation, or HEX.


It uses a base of 16. Break a byte into two groups of 4 bits each: nnnn nnnn. Each group is
called a nibble. A nibble with all low bits, 0000, is equal to 0. With all of its bits turned on,
1111, a nibble has a value of 15 (8 + 4 + 2 + 1). Thus, we are dealing with the 16 values from 0
through 15, and a base of 16.

Hexadecimal notation is simple. Just use digits for 0 through 9, and A through F for 10 through
15. The following table shows all of the combinations.

Binary Decimal Hexadecimal Binary Decimal Hexadecimal


0000 00 0 1000 08 8
0001 01 1 1001 09 9
0010 02 2 1010 10 A
0011 03 3 1011 11 B
0100 04 4 1100 12 C
0101 05 5 1101 13 D
0110 06 6 1110 14 E
0111 07 7 1111 15 F

The right nibble of a byte is the least significant nibble. It's the 1's place because it's 160. Next
is the 16's place because it's 161, then 162 = 256, and so on. To get the decimal value, take the
value of the nibbles, multiply by the position weight values and add them up. Thus, the HEX
value 9B = (9 * 16) + (11 * 1) = 155.

To show a number is hexadecimal in the C language, prefix it with 0x. The above would be
represented as 0x9B or 0x9b. This particular notation is not case-sensitive, although many
things in C are.

The following shows the byte table again, but this time with the weights also expressed in
hexadecimal notation, as often seen in C operations.
Bit D7 D6 D5 D4 D3 D2 D1 D0
Baseexponent 27 26 25 24 23 22 21 20
Weights 128 64 32 16 8 4 2 1
HEX Weights 0x80 0x40 0x20 0x10 0x08 0x04 0x02 0x01

A word is usually 16 bits, D0 through D15. A table with the bit names and their relationship to
the binary base of 2 is below.

Bit D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0


Baseexponent 215 214 213 212 211 210 29 28 27 26 25 24 23 22 21 20

The following two tables show the bits with their HEX weights.

Bit D15 D14 D13 D12 D11 D10 D9 D8


HEX Weights 0x8000 0x4000 0x2000 0x1000 0x0800 0x0400 0x0200 0x0100
Bit D7 D6 D5 D4 D3 D2 D1 D0
HEX Weights 0x0080 0x0040 0x0020 0x0010 0x0008 0x0004 0x0002 0x0001

A word can be broken up into 4 nibbles. It can be represented by showing its 4 nibbles as a 4-
place hexadecimal number. For example, the decimal number 19070 can be represented as the
hexadecimal number 0x4A7E.
0x4A7E = (4 * 163) + (10 * 162) + (7 * 161) + (14 * 160)
= ( 4 * 4096) + (10 * 256) + (7 * 16) + (14 * 1)
= 19070.

In the C language, a word is most often called an integer, abbreviated int. An integer can be
used to represent numbers that range from negative to positive values, or numbers that have
only positive values. In other words, an integer can be signed or unsigned. A signed integer
can have either positive or negative values. An unsigned integer can only be positive. An
unsigned 16-bit integer can have values from 0 through 65535. It is often abbreviated simply as
unsigned.

Bit 15 is used as a sign bit for signed integers. If it is on, the number is negative. If it is off, it is
positive. Positive values can range from 0 to 32767. Negative values can range from -1 to
-32768. Some examples are shown below. Notice that the signed version is equal to -1 * (65536
- unsigned version). For example, to get the signed number from the unsigned value 49151,
signed = -1 * (65536 - 49151) = -16385.

HEX 8000 BFFF FFFE FFFF 0000 3FFF 7FFE 7FFF


Signed -32768 -16385 -0002 -0001 00000 16383 32766 32767
Unsigned 32768 49151 65534 65535 00000 16383 32766 32767

A long word is generally considered to be 4 bytes or 32 bits. A long is used for very large
numbers. Longs can also be signed or unsigned. Signed longs have a range from
-2,147,483,648 to 2,147,483,647. The maximum unsigned value is 0xFFFFFFFF =
4,294,967,295. The minimum unsigned value is 0.
The following is a self-test over this section. It would be a very good idea to make sure you
know the answers to all of the questions since the sections that follow will build on this one.

1) Data inside computers is exchanged among the different components by means of metal
conductors called __1__. A group is called a __2__.

A) Data Bus, Weight


B) Nibble, HEX
C) Binary, Bit
D) Data Lines, Data Bus

2) If the voltage is 5 volts, the bit is on. If the bit is off, the voltage is 0 volts.

A) True
B) False

3) A group of 8 bits is a __1__ and is called a __2__ in the C programming language.

A) Data Bus, Nibble


B) Byte, Character (or char)
C) Weight, Bit
D) Unsigned, Signed

4) It is said to be a _____ when data is represented with a base of 2, because of the two digits
used.

A) Integer (or int)


B) Most Significant Bit
C) Binary
D) HEX

5) The left-end bit can also be referred to as the __1__. The __2__ is the right-end bit.

A) Word, Integer (or int)


B) Character (or char), Data Bus
C) Data Lines, Long Word
D) Most Significant Bit (or msb), Least Significant Bit (or lsb)

6) In the 1's place, the _____ is equal to the base number raised to the power of 0.

A) Byte
B) Weight
C) Long Word
D) Data Bus

7) _____ is a common way of showing numbers in a C program. It uses a base of 16.

A) Data Lines
B) HEX
C) Word
D) Byte
8) If you break a byte into 2 groups of 4 bits each, then each group is a _____.

A) HEX
B) Binary
C) Nibble
D) Volt

9) A __1__ is usually 16 bits, or 2 bytes. Something with 32 bits, or 4 bytes is usually called a
__2__.

A) Signed, Unsigned
B) Byte, Bit
C) Word, Long Word
D) HEX, Binary

10) The __1__ integer can be a positive or negative word, and the __2__ integer can only be a
positive word.

A) Nibbles, HEX
B) Byte, Data lines
C) Bit, Integer
D) Signed, Unsigned

In the table below, fill in the blanks and correct errors where needed

Hints:
All of the information you need to figure out this table is above

There are 4 errors (HEX can be upper or lower case - it's not an error to use lower case for
HEX).

MS Binary Nibble Means Most Significant Binary Nibble. ---- LS Binary Nibble Means Least
Significant Binary Nibble.

MS HEX Nibble Means Most Significant HEX Nibble. ---- LS HEX Nibble Means Least
Significant HEX Nibble.

Remember 8,4,2,1 for binary.

Remember that the base for HEX is 16.


The Weight for the Most Significant HEX Nibble = 16
The Weight for the Least Significant HEX Nibble = 1.
Remember, when you count in HEX you use 0 through 9, then A through F.

If you have binary but no HEX, use the binary to figure out the HEX.
If you have HEX but no binary, use the HEX to figure out the binary.

The first 16 numbers in the table simply count from 0 through 15.
Remember the binary and HEX for 0 to 15 and you have done over half of the table.
Notice that half of the numbers are on the left side of the table, and half are on the right side.
0 is on the left, 1 is on the right, 2 is on the left, 3 is on the right and so on.
That's true all the way through 15.

MS LS MS LS Decimal MS LS MS LS Decimal
Binary Binary HEX HEX Binary Binary HEX HEX
Nibble Nibble Nibble Nibble Nibble Nibble Nibble Nibble

0000 0000 0 0 0 0000 0001 1

0000 0010 0 2 0 3 3

0000 0100 4 0000 0101 0 5

0 6 6 0000 00111 7

0000 1000 0 8 0 9 9

0000 1010 10 0000 1011 0 B

0000 1100 0 H 12 0000 1101 13

0000 0000 0 E 0000 1111 15

1010 0000 A 0 6 4 100

27 5 C

0011 0111 55 0001 1101 1 d

0011 0011 3 3 33 0000 0 192

a a B B

c c D D

E E f f

Answers

1. D) Data Lines, Data Buss


2. A) True
3. B) Byte, Character (or char)
4. C) Binary
5. D) Most Significant Bit (or msb), Least Significant Bit (or lsb)
6. B) Weight
7. B) HEX
8. C) Nibble
9. C) Word, Long Word
10. D) Signed, Unsigned

MS LS MS LS Decimal MS LS MS LS Decimal
Binary Binary HEX HEX Binary Binary HEX HEX
Nibble Nibble Nibble Nibble Nibble Nibble Nibble Nibble
0000 0000 0 0 0 0000 0001 0 1 1

0000 0010 0 2 2 0000 0011 0 3 3

0000 0100 0000 0100 4 0000 0101 0 5 5


0000 0110 0 6 6 0000 00111 0111 0 7 7

0000 1000 0 8 8 0000 1001 0 9 9

0000 1010 0 A 10 0000 1011 0 B 11


0000 1100 0 HC 12 0000 1101 0 D 13

0000 0000 1110 0 E 14 0000 1111 0 F 15

1010 0000 A 0 16 0110 0100 6 4 100

0001 1011 1 B 27 0101 1100 5 C 92


0011 0111 3 7 55 0001 1101 1 d 29
0011 0011 3 3 33 51 1100 0000 C 0 192

1010 1010 a a 170 1011 1011 B B 187


1100 1100 c c 204 1101 1101 D D 221
1110 1110 E E 238 1111 1111 f f 255

Answer

1. C) Boolean Logic
2. A) Operators, Gates
3. D) AND
4. B) OR
5. A) NAND
6. C) NOR
7. D) XOR
8. B) NOT

9) 0xB0 & 0xD9


Operator: AND
Binary for 0xB0: 10110000
Binary for 0xD9: 11011001
Result of Operation: 10010000

10) 0xAF | 0x9C


Operator: OR
Binary for 0xAF: 10101111
Binary for 0x9C: 10011100
Result of Operation: 10111111
11) 0xC3 ^ 0x7C
Operator: XOR
Binary for 0xC3: 11000011
Binary for 0x7C: 01111100
Result of Operation: 10111111

12) ~0x85
Operator: NOT
Binary for 0x85: 10000101
Result of Operation: 01111010

Return to Boolean.

Controlling The Real World With Computers


::. Control And Embedded Systems .::

Address Lines and Ports

Home Order Let me know what you think


Previous: Boolean Logic Next: How To Read A Schematic

Data inside a computer is accessed by means of metallic conductors called address lines. Each
of them carries a bit of information, the same as a data line. A group of address lines is called
an address bus. Just as with data, a bit can be on or off, and addresses can be represented on
paper as a series of ones and zeros.

Addresses are seldom represented in binary, however. They are almost always shown in HEX
with the 0x prefix. An exception is in the schematic diagram. Individual lines are often drawn
and labeled with abbreviations using the capital A -- A0, A1, A2, etc.

Any time a variable or constant is accessed in the C language, the appropriate address lines are
activated. This is not readily apparent since variables and constants are almost always
referenced by name. It's a lot easier to work with words than with obscure memory addresses.
There is a way to more directly access address space in C. It's through the powerful concept of
pointers, and you can read about it in the tutorials offered in the software section of the
technology education sites links. We are concerned here with another use of address lines.

The PC actually has two memory systems. One deals with variables and constants as noted
above. The other addresses what are called ports. Ports provide access to outside-world
devices. The printer, serial communications, disk drives and sound cards, among others, all use
ports for monitoring and control. A typical PC uses A0 through A9 as the bases for addressing
ports, with A9 always high. That means that the range of port addresses is 0X200 through
0X3FF:

A9 A8 A7 A6 A5 A4 A3 A2 A1 A0
1 0 0 0 0 0 0 0 0 0 = 0X200 minimum port address

A9 A8 A7 A6 A5 A4 A3 A2 A1 A0
1 1 1 1 1 1 1 1 1 1 = 0X3FF maximum port address
The board used in the examples is capable of using all of these addresses in 64 byte blocks.
Subdivisions within each block allow access to several sub-systems on the board. All of them
will be covered in detail in the hardware and experiments sections.

Port read or write status depends on the state of the Input/Output Read and Write lines,
which are dedicated to port activities. The appropriate line must be low. If both are high, then
port I/O will not take place. The Input/Output Read line is abbreviated IOR, and the
Input/Output Write line is abbreviated IOW . In addition to IOR or IOW, a line called Address
Enable (AEN) must be low for port access.

The following is a self-test over this section. It would be a very good idea to make sure you
know the answers to all of the questions since the sections that follow will build on this one.

1) Data inside computers is accessed by metallic conductors. These conductors are called
__1__. A group of them is called a __2__ .

A) Data Lines, Data Bus


B) Address Lines, Address Bus
C) Data Bus, Address Lines
D) Address Bus, Data Lines

2) _____ provide access to outside-world devices, such as printers and disk drives.

A) Ports
B) Address Enable (or AEN)
C) Input/Output Write (or IOW)
D) 0x

Answer

1) B) Address Lines, Address Bus

2) A) Ports

Return to Address Lines and Ports.

How to Read a Schematic

Home Order Let me know what you think


Previous: Address Lines And Ports Next: Hardware

In general terms, a circuit can be described as any group of electrical or electronic devices
connected together by conductors. Conductors are most often metallic, and wires were the
conductor of choice in the past. Old radios and other electronic equipment were often a rat's
nest of wires. Today, it's more common to find metallic pathways, often called traces, on a
board constructed of a mixture of fiberglass and epoxy. The terms board and card are
interchangeable.

A schematic in electronics is a drawing representing a circuit. It uses symbols to represent real-


world objects. The most basic symbol is a simple conductor, shown simply as a line. If wires
connect in a diagram, they are shown with a dot at the intersection:

Conductors that do not connect are shown without a dot, or with a bridge formed by one wire
over the other:

Among the connections are power and ground, the high and low system voltages respectfully.
The 5 volt system power in the schematic is shown simply as 5V. There is also a +12V supply
and a -12V supply. Ground, or 0 volts, has its own symbol:

A switch is a device that is capable of allowing the user to break the circuit as if the wire had
been broken. Its symbol reflects this characteristic:

The three switches in the diagram are grouped in a Dual In-line Package (DIP).

A resistor is a device that resists the flow of charge. Its symbol reflects this characteristic by
making the line jagged:

Just in case you have seen "flow of current" elsewhere rather than "flow of charge", see
"Science Myths" in K-6 Textbooks and Popular culture and the definition of current below.

The unit of resistance is the ohm, pronounced om with a long o. The K in the schematics stands
for kilohm or thousands of ohms. 10K means the same as 10,000. Meg and sometimes M mean
megohm or million ohms. 4.7Meg or 4.7M is the same as 4,700,000.

You will see two variations on resistors in the schematic. One is the resistor array or network. It
is a Single In-line Package (SIP) containing several resistors connected together. They can be
found in many configurations. The one used here simply connects one end of the resistors to
each other and brings them out to a common connection. The other end of each resistor is left
free. Another variation is the variable resistor. It has a third contact that can move along the
resistor element to permit the values at that point to be variable. The moveable part is called the
wiper and is shown as an arrow.

There is a relationship between voltage, current and resistance that is expressed by Ohm's
Law, which says that Voltage is equal to Current times Resistance, or:

V=I*R

V is voltage (often referred to as Electromotive Force where E rather than V is used), I is


current and R is resistance. Current is expressed in Amperes, or amps for short. Very little
current is used in typical electronic circuits, so milliamps, which means 1/1000 amp, is used.
One milliamp = .001 amp. It's abbreviated ma, or sometimes MA.

To paraphrase a definition of charge from whatis.com :

"The coulomb (symbolized C) is the standard unit of electric charge in the International System
of Units (SI). It is a dimensionless quantity. A quantity of 1 C is equal to approximately 6.24 x
1018, or 6.24 quintillion."
"In terms of SI base units, the coulomb is the equivalent of one ampere-second. Conversely, an
electric current of 1 amp represents 1 C of unit electric charge carriers flowing past a specific
point in 1 second. The unit electric charge is the amount of charge contained in a single
electron. Thus, 6.24 x 1018 electrons have 1 C of charge. This is also true of 6.24 x 1018 positrons
or 6.24 x 1018 protons, although these two types of particles carry charge of opposite polarity to
that of the electron."

Since we deal mostly with electrons in electronics, 1 amp represents the effect of
6,240,000,000,000,000,000 electrons flowing past a point per second. Thus, since current is
already defined as something flowing, to say "current flow" would be to say "..... flowing flow"
which is incorrect because it is redundant.

Now let's say we have a 10K resistor and 2 milliamps of current. The voltage across the resistor
will be:

V = 10,000 * .002 = 20 volts

We can use the above equation to generate an equation for each of the three variables. It
requires remembering just two things:
1. It's ok to do something to one side of an equation as long as the same thing is done to the
other side. The two sides will remain equal.
2. Anything divided by itself is equal to 1.

Start with the original equation:


V=I*R
Now divide both sides by R. Since R/R = 1, the right side now becomes I * 1 which is simply I,
giving us V/R = I. If we switch sides and put the I on the left we end up with:
I = V/R

Again, start with the original equation:


V=I*R
Now divide both sides by I. Since I/I = 1, the right side now becomes R * 1 which is simply R,
giving us V/I = R. If we switch sides and put the R on the left we end up with:
R = V/I

Thus, all three equations are:


V=I*R
I = V/R
R = V/I

One way to remember the three equations is to say, "The Vulture looks down and sees the
Iguana and the Rabbit side by side (V = I * R), the Iguana sees the Vulture over the Rabbit (I =
V/R) and the Rabbit sees the Vulture over the Iguana (R = V/I)."
A very common circuit is a voltage divider. It looks like the following:

Two resistors connected end-to-end are said to be connected in series. The total resistance is
simply the sum of the two. In this case, it would be 22000 + 33 = 22033 ohms. If 1 volt is
applied to the open end of the 22K resistor, the current through the whole circuit would be
I = V/R = 1/22033 or .00004538646576 amps, or about .05 milliamps.

The voltage across the 33 ohm resistor is then


V = I * R = .00004538646576 * 33 = .00149775337 volts, or about 1.5 millivolts (1/1000 volt).

Resistors are also often connected in parallel , such as below:

The value of the above parallel network is:


R = 1/(1/R1 + 1/R2 + 1/R3)
The equation is good for any number of resistors.

Capacitors are devices which have metal plates separated by an insulator. They are used to
temporarily store an electrical charge. Their symbol reflects their construction:

The unit of capacitance is the Farad, but it's so large that the microfarad is used in practice.
Microfarad means millionths of a Farad. It's often abbreviated mf, MF or some variation,
although the correct abbreviation is F. A value without a designator is assumed to be in
microfarads. For example, in the schematic you will see several capacitors simply designated .
1. They are actually .1F capacitors.

Some capacitors must have their leads connected to the positive or negative side of a circuit.
They are polarized capacitors. When that is the case, one side will be shown with a + sign
where the positive side must be, or a - sign where the negative side must be, or both.

It's also very common to see picofarads abbreviated pf in some schematics. A picofarad is 10-12
Farad, and is sometimes called a micromicrofarad.

A diode permits the flow of charge in only one direction. Its symbol reflects this characteristic,
but with a slight problem:

Anode Cathode

The slight problem comes from the fact that flow of charge, at least in a wire, is from where
there are a greater number of electrons to where there are fewer. Electrons are negatively
charged. Thus, electrical flow of charge is from negative to positive in a wire. The problem
with the symbol is that the cathode, not the anode, is the negative side. Electrical flow of charge
is from the cathode to the anode, against the direction of the arrow.

Integrated Circuits contain many individual components. They, in turn, usually form several
functional blocks. For example, the following is a pinout for the 74LS08 Quad 2 Input AND
gate, along with its truth table. VCC is the 5 volt supply, and GND is ground. Sometimes
ground is shown as VSS. The gate inputs are the As and Bs, and the outputs are the Ys. Thus,
the inputs to gate 1 are 1A and 1B, and the output is 1Y. You will see variations on these
conventions, but they hold true in many cases.

An Operational Amplifier also contains many individual components, but is not a digital
circuit. It looks a little like a buffer, but has 2 inputs:

You can find a more detailed treatment of operational amplifiers at Professor Douglas M.
Gingrich's site at The University of Alberta. For a simplified coverage of the subject, look at the
circuit below.

An Op-Amp has many important characteristics. One of them is that the above circuit, called an
inverting amplifier, attempts to prevent any current through the inverting input. In this circuit,
R1 connects to the inverting input. R2 also connects to the inverting input, with its other end
connected to the output. R2 is called the feedback resistor. Let's attempt to drive a current
through the inverting input by placing 1V on the unconnected end of R1 and assume that the
right end has 0 volts on it. The current will be
I = V/R = 1/1K = 1ma

The output will try to counter this by driving a current of the opposite polarity through the
feedback resistor into the inverting input. The required voltage to do that will be
V = -(I * R) = -(1ma * 10K) = -10V.

Thus, we get a voltage to current conversion, a current to voltage conversion, a polarity


inversion and, most importantly, amplification. Amplification or gain is commonly labeled G.
In the case of the inverting amplifier,
G = -(Feedback Resistor / Input Resistor)
In this case, it's G = -(R2/R1)

Since the feedback cancels out the input, there is no voltage at the inverting input. It is said to
be at virtual ground .

Now look at the circuit below from the schematic you will see in the hardware section.

The gain is a little over -1000 in order to provide enough amplification for the low output of a
microphone. The signal is not only amplified but inverted because we are going into the
inverting input. The inversion however, is not quite the same as it is in a digital device. Here,
we are talking about an audio analog signal that, once transformed into an electrical signal by
the microphone, moves much more smoothly and continuously in the negative and positive
voltage directions. Inversion here means that when the input moves in the positive direction,
the output moves in the negative direction. When the input goes toward negative, the output
goes toward positive. C1 prevents DC voltages from even getting into the circuit. This blocking
action will be discussed in a future section.

The non-inverting side is designated by the +. It is there that a positive offset voltage is applied.
If R1 were not connected to C1 but rather to ground, the non-inverting side would exhibit a
gain of (R2/R1)+1 for the bias voltage. With C1 however, there is no DC gain for the non-
inverting side, and AC is shorted to ground by C2. The result is a gain of 1 on the non-inverting
side for DC voltages. The purpose of the bias circuit will be covered in the next section.

The following is a self-test over this section. It would be a very good idea to make sure you
know the answers to all of the questions since the sections that follow will build on this one.

1) _____ is a drawing that represents a circuit.

A) Switch
B) Schematic
C) Ground
D) Diagram

2) A _____ is a device that allows the user to break the circuit.

A) Scissors
B) Schematic
C) Resistor
D) Switch

3) A _____ is a device that resists the flow of charge.


A) Resistor
B) Buffer
C) Diode
D) Microfarad (or F;)

4) The unit of resistance is the __1__ . The relationship between voltage, current, and resistance
is expressed by __2__ .

A) Buffer, Amplifier
B) Capacitors, Diode
C) Ohm, Ohm's Law
D) Circuits, Switch

5) The __1__ is the unit of current. If there is very little current, it is expressed as __2__, which
means 1/1000th.

A) Amperes (or Amps), Milliamps (or Ma or ma)


B) Volts, Milliavolts
C) Picofarads (or pf), Microfarads (or F;)
D) Amplifier, Circuits

6) _____ are devices which have metal plates separated by an insulator. They temporarily store
an electrical charge.

A) In Series
B) Cathode
C) Capacitors
D) Microfarad

7) What permits the flow of charge in only one direction?

A) Anode
B) Diode
C) Cathode
D) Schematic

8) _____ contain many individual components and usually form several functional blocks.

A) Schematics
B) Diodes
C) Amplifiers
D) Integrated Circuits

9) The _____ also contains many components, but is not a digital device.

A) Inverting Amplifier
B) Operational Amplifier
C) Volt
D) Electron

10) This is __________________________________________


11) This is __________________________________________

12) This is __________________________________________

13) This is __________________________________________

14) This is __________________________________________

15) This is __________________________________________

16) This is __________________________________________

17) Ohm's Law: __________________________________________

18) I = 4, R = 10 so V = ________________ ________________

19) V = 12, R = 6 so I = ________________ ________________

20) I = 75, V = 150K Volts so R = ________________ ________________

Answer

1) B) Schematic

2) D) Switch

3) A) Resistor

4) C) Ohm, Ohm's Law

5) A) Amperes (or Amps), Milliamps (or Ma or ma)

6) C) Capacitors

7) B) Diode

8) D) Integrated Circuits

9) B) Operational Amplifier

10) This is unconnected conductors.

11) This is a resistor.

12) This is a switch.

13) This is connected conductors.


14) This is a capacitor.

15) This is a diode.

16) This is the symbol for ground.

17) Ohm's Law: V = IR

18) I = 4, R = 10 so V = IR = 40 Volts

19) V = 12, R = 6 so I = V/R = 2 Amps

20) I = 75, V = 150K Volts so R = 150K/75 = 2K Ohms

Return to How to Read a Schematic.

Controlling The Real World With Computers


::. Control And Embedded Systems .::

Hardware Description

Home Order Let me know what you think


Previous: How To Read A Schematic
Next: Putting It All Together - Controlling The Hardware With The Software

The hardware is shown in the


picture (might take a little
while to load). Don't be the
least bit concerned if you
don't understand everything in
the first few paragraphs
below. Their purpose is to
provide a brief description of
the hardware to those who
understand the terminology.
The terms will be second-
nature by the time you finish
the tutorial. Just skim the
information, make a note of
the ordering information and
Super Start, then move on to the circuit description below. Larger Picture -- 388K

The hardware is a tinned, masked PC-based ISA data acquisition and control board that

accepts up to 8 analog inputs through its 8 channel, 8-bit analog to digital converter
(one has a mike preamp)
produces analog output through 2 8-bit digital to analog converters (one is connected to
an amplifier complete with volume, bass and treble controls that will drive a speaker)
has 27 digital I/O lines, 24 of which are programmable as inputs or outputs
offers the opportunity for expansion by providing buffered address, data and control
lines, along with three spare select lines
makes available a spare AND gate, two spare NOR gates and a small breadboarding
area
has everything brought out to headers on the board, except the mike input and speaker
output, which are available through miniature phone jacks

Note that the board does not have DMA or interrupt capability. I plan on showing how to add
interrupt capability in a section dealing with the Analog to Digital.

With so much capability, the board can provide almost infinite possibilities for experimentation,
and can serve as a base to protype control and embedded systems. See the technology education
sites for inspiration. You can order bare boards, kits and assembled boards here. A bare board is
only $20, with quantity discounts available for volume users such as technology education
classes. You can also get a kit of a board with parts as well as assembled boards. The boards are
easy to build with the step-by-step instructions. They even include a soldering tutorial and the
color code. Download buildit.doc to take a look. It's in Word 97 format. If you don't have Word
97, you can download a free reader from Microsoft's web site by clicking on the following icon.

The board was originally designed for The Super Start Project as well as the hands-on portion
of this tutorial and as a development tool. Read the Super Start article to find out about the very
important use of the hardware in early childhood education. You can download free educational
software for very young children at the Super Start site. There are several programs available
there that use the board, including some tests which might come in handy after you assemble a
kit. All will provide some insight into using the hardware. There are also some handy routines
for placing information on the screen, such as character-based windows.

More importantly, I hope you will do more than just download the free stuff. I hope you will
read the article and pass the information on to others. I figure people who read this site have the
knowledge and experience to help others set up Super Start Projects. You can make a huge
difference for our kids. I hope you do. It can turn out to be the toughest job you'll ever love, and
a lot of kids will thank you someday.

That's my sermon for today.

NOTE: Please be sure to read the Warranty And Disclaimer before ordering a board!

THE CIRCUIT

You will see references to PDF data sheets in the following descriptions. The data sheets might
show parts with variations on the part number, but they will have the same functionality. You
can download a PDF reader here.

This section will cover parts of the circuit not covered in other parts of the tutorial. The board
can be blocked into a few simplified sections. Again, don't be concerned if you don't understand
everything in the blocks. All will become clear as you progress through the tutorial:
The section that selects the board's port address is outlined below. It and a buffer the only parts
that will not be covered in other parts of the tutorial:

Port addresses on a typical PC Industry Standard Architecture (ISA) bus are derived from the
ten address lines A0 through A9, with A9 always high. Note that all buffered lines in the
schematics are prefixed with the letter B. Half of IC7, a 74LS244 buffer, is used to buffer the
first three address lines and the RESET line. 74LS244.PDF

Base address selection is made by three DIP switch (see How To Read A Schematic) sections
connected to IC11, a 74LS688 8-bit magnitude comparator, chosen because it offers a simple,
straight-forward way of decoding the DIP switches:
The main logic elements in the 74LS688 are the NXOR gates. Recall from the Boolean Logic
section that an XOR gate will produce a high output if the inputs are different. The NXOR does
the same thing but inverts the output. The output of one of the NXOR gates above will be low
if its inputs are different. Its output will be high if its inputs are the same. The logic is not
changed even though pairs of inputs go through inverting buffers. The 9-input NAND gate
output will go low when all of the NXOR outputs go high if the enable input on pin 1 is also
low. 74LS688.PDF

When a switch is closed, its input to the 74LS688 is taken low. When the switch is open, the
input is pulled up by a resistor in an array. Corresponding inputs to the comparator are
connected to address lines A6, A7 and A8. If a switch is open, its corresponding address line
must be high to make the output of its NXOR high. If a switch is closed, its corresponding
address line must be low. Notice how this inverts the logic; on requires an address line to be
low and off requires it to be high. A9 is also connected, with its corresponding input taken high.
Similarly, the AEN (address enable) line is connected to the comparator with its corresponding
input connected to ground since AEN is low with a valid port address. The three other pairs are
taken high. Finally, either the I/O Read (IOR) or the I/O Write (IOW) line must be low for there
to be a valid port address. The bars over IOR and IOW in the schematic mean they are active
low. IOR and IOW are first buffered by using two AND gates with one side tied high in IC8, a
74LS08, then ANDed using a third. The result is a signal that will go low if either IOW or IOR
goes low. This is tied to the pin 1 enable input of the 74LS688. An AND gate was selected for
the same reason as the '688: it's a straight-forward means to meet the requirement. When all
conditions are satisfied, pin 19 of the 74LS688 will go low. 74LS08.PDF

Following are the HEX addresses that can be selected by setting the DIP switches on (1) and
off (0), and the possible conflicts that might arise (~ means "through"). If you decide to write
down the bit patterns (a very good idea), remember that on requires an address line to be low,
off requires an address line to be high and A9 is always high:

DIP SWITCH SECTION


123 ADDRESS POSSIBLE CONFLICTS
111 200 ~ 23F 200 ~ 20F = game port
011 240 ~ 27F 278 ~ 27F = LPT2 (OK with no spares used -- see below)
101 280 ~ 2BF 2B0 ~ 2DF = Alternate EGA
001 2C0 ~ 2FF 2B0 ~ 2DF = Alternate EGA
110 300 ~ 33F 300 ~ 31F = Some Sound Cards and The Prototype Card
010 340 ~ 37F 378 ~ 37F = LPT1 (OK with no spares used)
100 380 ~ 3BF 380 ~ 38F = bisynchronous 2
390 ~ 393 = cluster
3A0 ~ 3AF = bisynchronous 1
3B0 ~ 3BF = mono adapter and printer adapter
000 3C0 ~ 3FF 3C0 ~ 3CF = EGA
Address Line Assignment
A9 Always High DIP Switch Decode '138 Decode 8 Bytes Available To devices
A9 A8 A7 A6 A6 A5 A4 A3 A2 A1 A0

The best selections would probably be HEX 200 or 300. If the board is used on a dedicated
computer however, any conflicting devices not used could be removed from the computer.

Pin 19 on the 74LS688 enables IC12, a 74LS138 3-to-8 line decoder/multiplexer. It uses
address lines A3 through A5 to break the above up into eight-byte chunks used by devices on
the board. Providing it is enabled, the 138's select lines are activated in the following manner:

A5 A4 A3 Output Pin Active Low

0 0 0 15

0 0 1 14

0 1 0 13

0 1 1 12

1 0 0 11

1 0 1 10

1 1 0 9

1 1 1 7

There is a reason for eight-byte chunks. The ADC0809 has eight channels and uses the first 8
bytes of a port address location to select them. That's also why it uses A0 through A2. Since the
'138 selects 8 devices and each is 8 bytes wide, the board needs a total of 64 bytes port address
space. 74LS138.PDF

The device selections for all possible switch settings are shown below. The first entry for each
corresponds to the base address in the table above. Don't be concerned if you don't know what
some of the items in the tables mean. They will be covered in the experiments. Notice that each
of the 8 devices on the board is 8 bytes from a device next to it:

200 ~ 207 Eight Channel Analog to Digital Converter


208 ~ 20F Digital to Analog Converter 1
210 ~ 217 Digital to Analog Converter 2
218 ~ 21F Analog to Digital Converter Ready Line And 3 Digital Inputs
220 ~ 227 Programmable Peripheral Interface
228 ~ 22F Spare Select Line
230 ~ 237 Spare Select Line
238 ~ 23F Spare Select Line
240 ~ 247 Eight Channel Analog to Digital Converter
248 ~ 24F Digital to Analog Converter 1
250 ~ 257 Digital to Analog Converter 2 (optional)
258 ~ 25F Analog to Digital Converter Ready Line And 3 Digital Inputs
260 ~ 267 Programmable Peripheral Interface
268 ~ 26F Spare Select Line
270 ~ 277 Spare Select Line
278 ~ 27F Spare Select Line
280 ~ 287 Eight Channel Analog to Digital Converter
288 ~ 28F Digital to Analog Converter 1
290 ~ 297 Digital to Analog Converter 2 (optional)
298 ~ 29F Analog to Digital Converter Ready Line And 3 Digital Inputs
2A0 ~ 2A7 Programmable Peripheral Interface
2A8 ~ 2AF Spare Select Line
2B0 ~ 2B7 Spare Select Line
2B8 ~ 2BF Spare Select Line
2C0 ~ 2C7 Eight Channel Analog to Digital Converter
2C8 ~ 2CF Digital to Analog Converter 1
2D0 ~ 2D7 Digital to Analog Converter 2 (optional)
2D8 ~ 2DF Analog to Digital Converter Ready Line And 3 Digital Inputs
2E0 ~ 2E7 Programmable Peripheral Interface
2E8 ~ 2EF Spare Select Line
2F0 ~ 2F7 Spare Select Line
2F8 ~ 2FF Spare Select Line
300 ~ 307 Eight Channel Analog to Digital Converter
308 ~ 30F Digital to Analog Converter 1
310 ~ 317 Digital to Analog Converter 2 (optional)
318 ~ 31F Analog to Digital Converter Ready Line And 3 Digital Inputs
320 ~ 327 Programmable Peripheral Interface
328 ~ 32F Spare Select Line
330 ~ 337 Spare Select Line
338 ~ 33F Spare Select Line
340 ~ 347 Eight Channel Analog to Digital Converter
348 ~ 34F Digital to Analog Converter 1
350 ~ 357 Digital to Analog Converter 2 (optional)
358 ~ 35F Analog to Digital Converter Ready Line And 3 Digital Inputs
360 ~ 367 Programmable Peripheral Interface
368 ~ 36F Spare Select Line
370 ~ 377 Spare Select Line
378 ~ 37F Spare Select Line
380 ~ 387 Eight Channel Analog to Digital Converter
388 ~ 38F Digital to Analog Converter 1
390 ~ 397 Digital to Analog Converter 2 (optional)
398 ~ 39F Analog to Digital Converter Ready Line And 3 Digital Inputs
3A0 ~ 3A7 Programmable Peripheral Interface
3A8 ~ 3AF Spare Select Line
3B0 ~ 3B7 Spare Select Line
3B8 ~ 3BF Spare Select Line
3C0 ~ 3C7 Eight Channel Analog to Digital Converter
3C8 ~ 3CF Digital to Analog Converter 1
3D0 ~ 3D7 Digital to Analog Converter 2 (optional)
3D8 ~ 3DF Analog to Digital Converter Ready Line And 3 Digital Inputs
3E0 ~ 3E7 Programmable Peripheral Interface
3E8 ~ 3EF Spare Select Line
3F0 ~ 3F7 Spare Select Line
3F8 ~ 3FF Spare Select Line

The schematic for the board is shown in figures 1 and 2 below. Please see How To Read A
Schematic if you don't understand what you see.
Figure 1: Circuit, Part 1

IC12 is a 74LS245 bidirectional buffer. It's used to buffer the data going to and from the board.
Pin 1 is the line that determines the direction of the data flow. If the buffered read line BIOR is
low and therefore active, data will flow from the board to the ISA data bus. If it is high, data
can flow from the bus to a device selected on the board.
Figure 2: Circuit, Part 2
Figure 3 shows the silk screen printing found on the top of the board.

Figure 3: Board Layout Silk Screen Print

The following are provided on the headers:

Header 1:
A. 3 basic switch inputs
B. 8 8-bit Analog To Digital inputs (0 through 5 volts)
C. 2 8-bit Digital To Analog outputs (0 through 5 volts)
D. Pre-amp Output
E. 2 grounds

Header 2:
A. 2 Spare NOR gates (the spare AND is brought out to pads)
B. 2 +5V and 2 grounds

Header 3:
A. 24 PPI I/O lines
B. 2 grounds

Header 4:
A. 1 ground
B. The 14.31818 MHz oscillator divided by 2, 4, 8, 16, 32, 64, 128 and 256
C. 3 Spare Select Lines
D. Buffered address lines BA0 through BA2
E. Buffered data lines BD0 through BD7
F. Buffered read, write and reset lines

Notice the inclusion of spare select lines and buffered data and address lines. They can be used
to expand the capabilities. For example, you could add more PPIs to give you more digital I/O
lines, or another digital to analog converter or two. There is also a small bread board area.

Figure 4 shows where the controls, mike input and speaker/headphone output are on the board.

Parts List
Parts Needed For Digital Experiments
(These will get you started. I priced them at $18.65, mostly from Jameco .)

Capacitors
C9, C10, C11, C12, C13, C14, C15, .1F; .2", or 5mm spacing, anything above 5 volts OK,
C16 ceramic OK

Resistor Arrays or Networks


ARRAY1 9 resistor, 10 pin 10K SIP with one end of each resistor commoned to pin 1
ARRAY2 7 resistor, 8 pin 10K SIP with one end of each resistor commoned to pin 1

DIP Switch
SW1 Three Position DIP Switch
Headers -- All have two rows .1" apart with pins .1" apart
HEADER 1 2 X 8 pin header
HEADER 2 2 X 5 pin header
HEADER 3 and 4 2 X 13 pin headers
Integrated Circuits -- 74, 74HCT, 74AHC, etc. prefixes may be substituted for 74LS
IC2 74LS02 quad NOR gate 74LS02.PDF
IC3 8C255 Programmable Peripheral Interface 8255.PDF
IC7 74LS244 octal buffer 74LS244.PDF
IC8 74LS08 quad AND gate 74LS08.PDF
IC9 74LS393 counter 74LS393.PDF
IC10 74LS138 1 of 8 decoder 74LS138.PDF
IC11 74LS688 digital comparator 74LS688.PDF
IC12 74LS245 octal transceiver 74LS245.PDF

Additional Parts Needed For Remaining Experiments


(I priced these parts at about $24.00, mostly from Jameco .)

Variable Resistors
VR1 10K TRIMMER (25 turn) used for offset -- equal to Bourns type 3296X
VR2 and VR3 100K TRIMMERS used for bass and treble -- equal to Bourns type 3386C
VR4 10K TRIMMER used for volume -- equal to Bourns type 3386C
Capacitors
.22F; .2" or 5mm spacing film similar to Panasonic
C1, C3
ECQ-V1H224JL
10F; 25v axial electrolytic similar to Panasonic
C2
ECE-B1EU100S
.01F; .2" or 5mm spacing film similar to Panasonic
C4, C5, C6, C7
ECQ-V1H103JL
At least 220F;, at least 6.3v axial electrolytic similar to Panasonic
C8
ECE-B0JU331
Jumper
.1" Header Type Jumper
JP1

Integrated Circuits

IC1 TL074 or LF347A quad op amp TL074.PDF


IC4 ADC0809 8 input, 8-bit AD converter ADC0809.PDF
IC5, IC6 DAC0832 8-bit DA converter DAC0832.PDF
IC13 LM380N-8 power amp (8 pin DIP version) LM380N-8.PDF
Diodes
D1, D2 1N4148 switching diode
IN/OUT CONNECTORS
J1, J2 Switchraft 1/8" phone jack -- Mouser Electronics # 16PJ528
Resistors -- All Are 1/8 Watt
R1 3.9K
R2 4.7M
R3, R4 15K
R5,R6 470
R7,R8,R9 10K
R10 3.3K
R11 150K

See the electronic parts links for parts to build a board.

Controlling The Real World With Computers


::. Control And Embedded Systems .::

Putting It All Together - Controlling The Hardware With The Software

Home Order Let me know what you think


Previous: Hardware Next: Experiment 1 - Basic Switch Input Detection

Much of the world's software is written in the C programming language. A significant


improvement, the C++ language, is now finding some use in embedded systems and is
extensively used in factory control systems. This site's tutorial sections concentrate on the
basics of the C language.

C language source code is written in plain text format. C programs have a .c extension. C++
program files have a .cpp extension. Only basic C will be discussed here. The many benefits of
C++ can be found in the tutorials offered in the software section of the technology education
sites links.

Two programs are used to turn the source code into a program the computer can use. One is
called a compiler. It produces what is called object code. Then a program called a linker builds
the final executable program from one or more object code files. The linker is often left out in
discussions (including here) but they are both needed. See Webopedia for a good description of
the process.

The compiler I used in the examples is made by MIX Software. It's only $20 and comes with an
excellent manual that includes a tutorial. It has been adopted by many colleges and training
companies teaching C programming classes. Any compiler that complies with ANSI standards
and has the appropriate PC extensions should do just fine. Even some of the free ones will
work.

Text written for the native processor is called assembly language. An assembler makes object
code out of assembly text. MIX's assembler is only $10 and includes their source code!

The following sample code and explanation will provide the information you need to start
controlling the board using the C programming language. Text explaining what's going on will
be in normal black and white, whereas the actual example code, important words and phrases
and points needing emphasis will be in c o l o r.

It's very important to comment your code as you go along. It's tempting to put it off thinking all
will be remembered. Murphy says it won't. There are two kinds of comments. One is for a
single line and uses the double slash:

// double slash, single line comment

The other type of comment is used for either a single line or multiple lines. The comment
section is started with /* and ended with */:

/* This is the type of


comment that can be used with
more than one line. */

/*
This is also a multi-line
comment.
*/

Some compilers recognize only the /* */ sequence. If you get errors related to comments and
you are using the double-slash type, try changing to the /* */ sequence to see if that will fix the
problem.

All of the action in a C program starts with something called main(), which looks something
like this:

main()
{
}

Every C program must have a main(). In fact, that's all you need. If you have only the above in
a program it will compile and run. It just won't do anything that can be seen or heard. Only a
computer will love it.

Main() is the starting function of a C program. A function is a piece of code that performs a
task. A C program is basically a collection of functions. Functions are called by other functions,
which means a service is being requested which is provided by the called function. Functions
can return something to the caller, perform a process or both. Functions can also accept
arguments inside the parentheses following the function name. Arguments can be just about
any type of data needed by the function. Multiple arguments are separated by commas. A more
proper way to show main() if it does not accept arguments or return anything is to use the
following form:

void main(void)
{
}

The void terms mean that main returns nothing and does not accept arguments. Everything
inside the curley braces, { }, belongs to the main() function. That's true of any function. The
code belonging to a function is placed inside curley braces.
Main() would look like the following if it returned an integer. You need to see Data lines, bits,
nibbles, bytes, words,binary and HEX if you don't know about the int keyword:

int main(void)
{
}

Now let's say we are going to call another function from inside main(). We will make it return
an integer that is what we send it squared. Before we use it however, we must describe it. This
is accomplished with what is called a prototype. Our prototype for the squaring function would
look something like this:

int squared(int x);

This says that the function we have named "squared" will return an integer. It also says that it is
to be sent a single integer as an argument. The int x inside the parenthesis could just as well
have been int xyz, because here we are just describing the function, not actually sending it a
variable with a specific name. Speaking of names, C has some restrictions in the use of names
and labels. From C Programming by Steve Holmes of The University of Strathclyde Computer
Centre , "Every variable name in C must start with a letter, the rest of the name can consist of
letters, numbers and underscore characters. C recognizes upper and lower case characters as
being different. Finally, you cannot use any of C's keywords like main, while, switch etc as
variable names." This applies to any label, including function names. The use of a blank space
in a label name is also not permitted. The label "this is a name" is not allowed, but
"this_is_a_name" and ThisIsAName are OK.

void main(void)
{
int x,y; // declare the integers x and y
// notice the semicolon
// all C statements or groups of
// statements end with a semicolon

x = 5; // set x equal to 5

y = squared(x); // set y equal to whatever squared returns


// squared is sent a copy of x, not x itself,
// so x is not modified by squared
}

/* ======================================================================
The following begins "squared." Notice that there is no colon after
the parenthesis as there was in the prototype, and that the argument
name is different. We have given the input argument the name "indat."
We could have called it anything as long as we declared it an
integer the same as the prototype. Also notice the way this comment
is set off. Use any format you wish. Just be sure to declare a comment
with // or the /* */ sequence. Most importantly, be sure to describe
a subroutine with a comment section above it.
====================================================================== */
int squared(int indat)
{
return indat * indat; // return the input int times itself -- square it
}
The problem with the above is that it's not apparent what's going on. To see, let's print the
results of several calls to squared.

But first, let's look at another feature of C. Very often you will find what are called compiler
directives near the top of a C source code file. They are prefixed with # and tell the compiler to
perform certain tasks. One of the most common is the directive to include other files. When the
compiler encounters an #include statement, it suspends compiling the current file, compiles the
included file then returns to compiling the current file. Most of the time the included files are
header files, with the .h extension. The file name is usually bracketed with <> if the
information in the header deals with material belonging to the compiler. The name is usually set
off in quotation marks if it belongs to the programmer.

The sample program is below:

// exmpl1.c -- it's a good idea to have a comment at the top of a


// C source file to show its name so you will know what you are
// looking at if you print it

#include <stdio.h> // contains the printf prototype and others


// that deal with Input and Output

int squared(int x); // prototype for squared

void main(void)
{
int x,y;

for(x=0; x<11; x++) // set x equal to 0, then 1, etc. through 10


{
y = squared(x); // set y equal to whatever squared returns
printf("%02d squared = %03d\n",x,y); // print the results
}

/* ======================================================================
squared(int indat) returns indat times itself
====================================================================== */
int squared(int indat)
{
return indat * indat; // return the input int times itself -- square it
}

// end exmpl1.c -- it's a good idea to show where the end of the file
// should be -- if it is missing, you know the file got corrupted

The output of the above program is below:

00 squared = 000
01 squared = 001
02 squared = 004
03 squared = 009
04 squared = 016
05 squared = 025
06 squared = 036
07 squared = 049
08 squared = 064
09 squared = 081
10 squared = 100

Let's see what the program did. The first addition in this version is what is called a for loop.
The following shows how it works:

_Start x at 0
| _Run the loop as long as x
| | is less than (<) 11
| | _Increment x by 1 each time
| | | through the loop
| | |
| | |
for(x=0; x<11; x++)

What this says is to first set x to 0, then run the loop as long as x is less than 11, incrementing it
each time around. Everything inside the curley braces ({ }) under the for() loop statement is part
of the loop:

for(x=0; x<11; x++)


{
// everything in here is part of the for() loop
}

For loops are very flexible. Consider the following:

for(x = 12; x >= -20; x-=2)


{
}

Notice the spaces between x, = and 12, as well as others. C does not care if you add white
space, which is anything that is not readable, such as tabs, spaces, blank lines, etc. C ignores
white space. It is a very good idea to use that fact to spread things out a little if it makes the
code easier to read. Here, x is started at 12. The loop continues as long as x is greater than or
equal to (>=) -20. Each time around, x is decrement by 2 (-=2). Thus, x would follow the
sequence 12, 10, 8 etc., 2, 0, -2 etc., -14, -16, and finally reach -20.

Back to the original program. The first thing that happens inside the for loop is a call to the
squared() function. The x variable is used as the value for the argument to be squared, and y
gets the result from the squared() function:

y = squared(x); // set y equal to whatever squared returns

After y is set by the return of squared, both x and y are printed using the printf() function.
Printf is one of the built-in C functions and means print formatted. The formatting comes from
what you put in the quotation marks:

printf("%02d squared = %03d\n",x,y); // print the results


Notice the percent signs. They introduce formatting instructions for the printf() function. A %d
would tell printf() to print a signed integer in decimal format. A %u would tell printf() to print
an integer in unsigned format. A %x would tell printf() to print an integer in hexadecimal
format using lower case letters. To print upper case letters, use %X. The %02d instruction
says, "print a signed integer in decimal format using 2 character positions and provide leading
zeros." You can designate leading zeros and/or the number of digits with virtually any of the
formats in a similar manner. After the last quotation mark is a list of the things to print: ",x,y);.
The first print format instruction after a percent sign refers to the first item in the list and the
second one refers to the second item in the list. There can be many format instructions and
many items in the list. The second item in the list is the y. The format instruction for it is
slightly different. The %03d says, "print a signed integer in decimal format using 3 character
positions and provide leading zeros." Two percent signs together, %%, mean to print a percent
sign. The \n is what is called an escape sequence. Escape sequences are introduced by the back
slash. The \n simply means to go to a new line after printing this one. A \t means tab to the
right and a \\ means to print a back slash. All characters inside the quotation marks for printf()
that are not format instructions or escape sequences are printed literally as you see them. The
blanks, the word "squared" and the equal sign are all literals. The following shows how the call
to the printf() function is made for what we want to print here:

start with a parenthesis and a quotation mark


| Then tell printf to print the following:
| the signed decimal value of x as 2 characters with leading zeros
| | blank, "squared", blank, an equal sign then blank
| | | 3 character signed decimal value of y with leading
zeros
| | | | and finally a new line
| | | | | put a quotation mark just before the variable
list
| | | | | | x is the first item in the list
| | | | | | | y is the second item in the list
| | | | | | | |end with parenthesis and semicolon
| | | | | | | ||
| | | | | | | ||
| | | | | | | ||
printf("%02d squared = %03d\n",x,y);

Please see your compiler manual or the printf() section of The Free On-Line Dictionary of
Computing for more information on printf().

It would be a good practice exercise to type the above program into a text file with a .c
extension, compile it and run it. The best way to learn any language, be it human or machine, is
to use it. For this exercise, it would probably be a very good idea to print this page, especially if
you are using a computer other than this one for the experiments. Even if you aren't, it's a
hassle to bounce back and forth between windows and DOS.

If you are using Windows 95, first get into DOS by going to Start/Programs/MS-DOS Prompt:
Now type in cd\ then press enter. This means change directories and go to the root, which is
actually outside any directory (a directory is the same as a folder). Now type in md LearnC to
make a directory called LearnC, then press enter. Finally, type in cd LearnC to change
directories to get into the LearnC directory.

Now type in edit exmpl1.c then press enter. Type in the above program text then press ALT-F
then X to leave. The edit program will ask if you want to save the file. Click on yes:

Install your compiler if you have not already done so. Pay close attention to the path
requirements and how to set your computer up for the proper defaults.

To compile a program using the MIX PowerC compiler, type in the following then press the
enter key:
\powerc\pc /e /n /ml progname <enter>
Where progname is the name of the C source file without the .c extension. In this case, you
would type:
pc /e /n /ml exmpl1 <enter>.

This translates to PowerC Compile (pc), linking to make an executable (/e), allowing nested
comments (comments within comments, /n) and use the large memory model (/ml) for
unlimited code and data space.

Download do.bat
do.bat is a DOS batch file that contains the above compiler commands to permit simply typing
in do progname, for example, then pressing the enter key. It can be used with any of the
experiments that use only one file. The name should be no more than 8 characters not counting
the period and extension (12345678.c).

To run the program, simply type in exmpl1 <enter>. You should get the same output as above.

If you are using Windows, be sure to type cd\learnc <enter> each time you get out of Windows
and go into DOS so you will be in the correct directory.

Previous: Hardware Next: Experiment 1 - Basic Switch Input Detection


Problems, comments, ideas? Please Let me know what you think
Copyright 2000, Joe D. Reeder. All Rights Reserved.
Order Home

Controlling The Real World With Computers


::. Control And Embedded Systems .::

Experiment 3 - The General Purpose Digital Input/Output Module - Part 1

Home Order Let me know what you think


Previous: Experiment 2 - Expanding Switch Input Detection
Next: Experiment 4 - The Multiple Closure Problem And Basic Outputs With The PPI

The general purpose digital input/output module is not a box covered with switches, lights and
terminal blocks. It is not a piece of hardware at all, but a piece of software. It's called a module
because its code is encapsulated in a file which will contain all of the functions and variables
related to digital input and output.

It is very useful to isolate functionality in this manner when building a software project.
Modulariazation makes it much easier to test and troubleshoot functions. It's a lot easier to find
and fix a problem when all activities related to a particular process are to be found in one file
dedicated to the process, rather than being scattered all over the place and mixed in with other
processes. Modulariazation is made possible in large part by something called file scope. You
have already read about scope in Experiment 1 and Experiment 2.

Scope was discusssed in those sections as it relates to position in the file. Remember from
Experiment 2 that several variables were declared just above the is_switch() function in the file.
They were known only to the functions that followed them. They could not be modified or even
accessed by anything above their point of declaration. It follows that variables declared above
all of the functions in a file are visible to all of the functions in the file. They are said to be
global to the file. It is important to note however, that their scope is normally limited to the file
in which they are declared. Under normal circumstances, variables are not known to functions
outside the file in which they are declared. That means they are protected from being messed
with by functions outside their file. It is this encapsulation that makes it possible to produce
file-based modules that permit access to variables only through the functions declared to be
access portals. That makes design and troubleshooting much easier.

Design is made easier by the fact that, once a module has been thoroughly tested and debugged,
there is usually no need to worry about it again because access is limited to the tested functions.
Troubleshooting is made easier by the fact that a problem can often be recognized by the
characteristics it displays. For example, a program might say switch 3 is turned on when, in
fact, switch 1 is turned on. It is much easier to find the source of the problem if everything
having to do with switches is in one file. It is useful to view a file as an object that encapsulates
a set of funtions related to one particular aspect of a project. In this experiment, the file will
become an object that deals with switches. Always work with Object Oriented Programming
(OOP) as a goal. C++ is designed to make OOP easier, but a lot can be done with C. After all,
C++ was first implemented using a front end processor (called cfront) to turn source code into
new source code that could be compiled by a standard C compiler.

The general purpose digital input/output object will be encapsulated in a file named after its
purpose -- digital.c, although there will be some variations on the name according to the
experiment being done. It will be the module used to house all of the switch input procedures
covered so far, plus some additional procedures that will increase the capability to permit up to
176 switches to be detected. After digital inputs will come digital outputs.

Incidentally, contact closure detection is a better, more general term than switch detection.
Contact closure detection is heavily used in control and embedded systems. There is always a
need to know when things pass or reach a point, have taken too long to do so, never get there at
all or get there too soon. Other activities are often triggered by such information. More often
than not, the information is provided as a contact or switch closure, or at least a signal that
looks like one. It might actually be the detection of a light beam or a magnet or a piece of
metal, but it is almost always a form of contact or contact simulation.

The 8255 Programmable Peripheral Interface (PPI -- 8255.PDF) discussed in Experiment 2 will
be the main contributor to both digital input and output. The table showing how to set up the
control register to configure Ports A, B and C as inputs and outputs is repeated below:

Group A Group B
D7 D6 D5 D4 D3 D2 D1 D0
Port C Port C
1= Upper Mode Lower Mode
Port A Upper Port B Lower
Set Mode Select Select
Nibble Nibble
00 = Mode 0
1 = In 1 = In 0 = Mode 0 1 = In 1 = In
01 = Mode 1
0 = Out 0 = Out 1 = Mode 1 0 = Out 0 = Out
1X = Mode 2

Mode 0 will be used in this experiment. It would be nice to use plain English when setting up
the PPI rather than HEX codes. That will be the first order of business. Notice that D7 is always
high to set mode, and D6, D5 and D2 are always low for mode 0. D0, D1, D3 and D4 are the
only bits that change. Notice how they work. If a bit is off, its section is an output. If a bit is on,
its section is an input. D0, D1, D3 and D4 can be seen as bits of a counter just like the binary
table from the Data lines section repeated below, providing we ignore the weighting normally
assigned to D3 and D4:

Binary Bits Binary Bits


Decimal Hexadecimal Decimal Hexadecimal
D4 D3 D1 D0 D4 D3 D1 D0
0000 00 0 1000 08 8
0001 01 1 1001 09 9
0010 02 2 1010 10 A
0011 03 3 1011 11 B
0100 04 4 1100 12 C
0101 05 5 1101 13 D
0110 06 6 1110 14 E
0111 07 7 1111 15 F

A constant could be defined that says all of the ports are outputs by making it a 0 and calling it
something that says to set up all four groups as outputs. Something like
AOUT_CUPPEROUT_BOUT_CLOWEROUT would work. That's fairly close to English, and
easier to understand than HEX. The #define directive could be used to set up a whole series of
such constants:

#define AOUT_CUPPEROUT_BOUT_CLOWEROUT 0
Next would be
#define AOUT_CUPPEROUT_BOUT_CLOWERIN 1

And, at the end of the sequence,


#define AOUT_CUPPERIN_BIN_CLOWERIN 14
and
#define AIN_CUPPERIN_BIN_CLOWERIN 15

It would be nice however, if a way could be found to set up the constants without having to
type in all of the #defines. There is. It's called an enumeration, and it looks like this:

enum
{
A,
B,
C,
D,
E
};

Think of an enumeration as a series of constants for which the compiler automatically assigns
integer values. The first constant is always given the value 0 unless it is explicitly set to
something else. All constants that follow are 1 greater than the previous one. Thus, A = 0, B =
1, C = 2, D = 3 and E = 4 in the above example. The constants may be called anything as long
as they are legal C language names. The numbering can also be changed in mid-stream:

enum
{
A,
B,
C = 7,
D,
E
};

Now, A = 0, B = 1, C = 7, D = 8 and E = 9.

The following enumeration establishes the constants needed for the PPI:

enum
{
AOUT_CUPPEROUT_BOUT_CLOWEROUT, // 0
AOUT_CUPPEROUT_BOUT_CLOWERIN, // 1
AOUT_CUPPEROUT_BIN_CLOWEROUT, // 2
AOUT_CUPPEROUT_BIN_CLOWERIN, // 3
AOUT_CUPPERIN_BOUT_CLOWEROUT, // 4
AOUT_CUPPERIN_BOUT_CLOWERIN, // 5
AOUT_CUPPERIN_BIN_CLOWEROUT, // 6
AOUT_CUPPERIN_BIN_CLOWERIN, // 7
AIN_CUPPEROUT_BOUT_CLOWEROUT, // 8
AIN_CUPPEROUT_BOUT_CLOWERIN, // 9
AIN_CUPPEROUT_BIN_CLOWEROUT, // 10
AIN_CUPPEROUT_BIN_CLOWERIN, // 11
AIN_CUPPERIN_BOUT_CLOWEROUT, // 12
AIN_CUPPERIN_BOUT_CLOWERIN, // 13
AIN_CUPPERIN_BIN_CLOWEROUT, // 14
AIN_CUPPERIN_BIN_CLOWERIN // 15
};

AOUT_CUPPEROUT_BOUT_CLOWEROUT will be automatically set to 0,


AOUT_CUPPEROUT_BOUT_CLOWERIN to 1 and so on down to
AIN_CUPPERIN_BIN_CLOWERIN which will be set to 15. The set_up_ppi() function can
now be modified to accept one of the constants to configure the PPI. Since enumerations are
seen as integers, set_up_ppi() will be modified to accept an integer as an argument. Now, for
example, to set up A as an input, C Upper as an output, B as an output and C Lower as an input,
set_up_ppi() is sent AIN_CUPPEROUT_BOUT_CLOWERIN.

The enumeration gives us 0 through 15. That's 0000 through 1111 in binary. A comparison of
the binary sequence table and the 8255 set up code table will reveal that bits D0 and D1 in the
enumeration are the same bits needed in the control code to properly set up the lower nibble of
Port C and all of Port B. Bits D2 and D3 are the correct values needed to set up the upper
nibble of Port C and all of Port A, but need to be moved to bit positions D3 and D4. For
example, consider AOUT_CUPPERIN_BOUT_CLOWERIN. It's 5, or binary 0101.
D0 = 1 makes C Lower an input
D1 = 0 makes B an output
D2 = 1 makes C Upper an input if moved to the D3 position
D3 = 0 makes A an output if moved to the D4 position

We need to leave D0 and D1 alone and move D2 and D3 to positions D4 and D5. We need to
turn on D7 to put the PPI in set up mode. All other bits should be turned off. Now look at the
new set_up_ppi():

// set up the ppi


void set_up_ppi(int mode)
{
unsigned control = base + 0x23;
int command;

command = (mode & 0x0c) << 1; //


shift bits 2 and 3 into positions 3
and 4
command += (mode & 3); // add in
bits 0 and 1
command |= 0x80; // OR in bit 7 for
PPI set up

outp(control, command); // set


according to mode command
}

The control variable is still assigned the value base + 0x23. The command variable has been
added, and there is now a mode argument being sent to the function. It's important to note that
the mode variable being sent to set_up_ppi() is not modified by procedures inside
set_up_ppi(). It would not be modified even if there were a statement that looked as though it
could modify it, such as "mode = 5;". That's because a copy is made of an argument that is
passed by value, as is mode. The copy is used in the function, not the original. It is a common
error to pass an argument by value to a function thinking it will be modified, only to find
nothing happens.

ANDing mode with 0x0c applies a mask, as explained in Experiment 1, that zeros out
everything but bits 2 and 3 of the mode value (8 + 4 = 12 = 0x0c). Not even the copy of the
mode variable is changed by such an operation however, because the compiler will write code
to perform the operation and place the results in a temporary location in the microprocessor or
in memory. The value from the AND operation is shifted to the left 1 bit position (<< 1) which
moves bits 2 and 3 into bit positions 3 and 4. The results of the combination of the AND and
shift operations is stored in the command variable.

Notice that "mode & 0x0c" is surrounded by parenthesis. That's because the bit-wise AND (&)
operator has a lower precedence than the bit-wise shift (<<) operator. Operations with a higher
precedence will be performed before those with a lower precedence unless the lower
precedence operation is surrounded by parenthesis. The parenthesis forces the AND to take
place before the shift. If the statement had been
command = mode & 0x0c << 1;
then 0x0c would have been shifted to the left one bit, the result ANDed with mode then stored
in the command variable.

Another thing to consider is associativity. What is the answer to the following?


2-3-4=?
It's -5 because subtraction is left-associative. If it were right-associative, 2 - 3 - 4 would equal 2
- (3 - 4), or 3. Just about everything is left-associative. (thanks to Ambiguities and Lexical
Analysis By Clifford Story ).

The following table from The University of Washington Department of Chemistry lists the C
language operators in order of precedence and shows the direction of associativity for each
operator. The operators at the top of the list have highest precedence. The comma operator has
the lowest precedence. Operators that appear in the same group have the same precedence.
Don't be concerned if you don't know all of the operators yet. We'll get there. Of course you
know most of them if you have read through all of the sections prior to this one. C++ operators
have been left out to keep down the confusion.

Operator Name Associativity Operators

Primary left to right () [ ] . ->

Unary right to left ++ -- + - ! ~ & * ()

Multiplicative left to right * / %

Additive left to right + -

Bitwise Shift left to right << >>

Relational left to right < > <= >=

Equality left to right == !=

Bitwise AND left to right &

Bitwise Exclusive OR left to right ^

Bitwise Inclusive OR left to right |

Logical AND left to right &&

Logical OR left to right ||

Conditional right to left ? :

Assignment right to left = += -= *= /= <<= >>= %= &= ^= |


=

Comma left to right ,

Back to business. After the mask and shift, the command variable is added to the mode value
ANDed with 3, which causes bits D0 and D1 of the mode value to be stored, along with the
shifted D2 and D3 bits. Note that "command +=" means the same thing as "command =
command +".
Thus, A += 2; is the same as A = A + 2; and x+=4; is the same as x = x + 4;
Finally, command is ORed with 0x80 (using |=) to turn on bit 7 for PPI set up.

Let's look at the AOUT_CUPPERIN_BOUT_CLOWERIN example step-by-step.


AOUT_CUPPERIN_BOUT_CLOWERIN = 5 = 0101 binary.
Since an integer is being sent to set_up_ppi() as an argument, we have to make sure we deal
with all 16 bits, even though we will end up using only 8. In fact, we will treat the most
significant 12 bits as unknown since we know all of the information required is in the first 4
bits. X's will be used for the most significant 12 bits in the illustrations to show we consider
them unknown.

XXXX XXXX XXXX 0101 mode = 5

0000 0000 0000 1100 AND with 0x0c (bits 2 and 3 on)
0000 0000 0000 0100 Result of AND

(see the Boolean section


if you don't understand this)

Now shift the results of the above left 1 place:

0000 0000 0000 0100 << 1 =

0000 0000 0000 1000

Hold on to this value

AND the original mode value with 3:

XXXX XXXX XXXX 0101 mode = 5

0000 0000 0000 0011 AND with 3

0000 0000 0000 0001 Result of AND with 3

Add this to the value held onto after the shift:

0000 0000 0000 1000 = 8

0000 0000 0000 0001 = 1

0000 0000 0000 1001 Result of add = 9

Finally, OR with 0x80

0000 0000 0000 1001

0000 0000 1000 0000 OR with 0X80

0000 0000 1000 1001 = 0x89, which is the correct value


But will all of the other constants work? The only way to know for sure is to build a test
program. Tests consist of two halves. The driver is a function that sends test data to the target
function. The target function is made to print or return results of its process so it can be
determined if what it does is correct. The following is a driver that can be used to test
set_up_ppi(). First, put the enumeration constants in a header file named const3a.h:

// const3a.h

enum
{
AOUT_CUPPEROUT_BOUT_CLOWEROUT, // 0
AOUT_CUPPEROUT_BOUT_CLOWERIN, // 1
AOUT_CUPPEROUT_BIN_CLOWEROUT, // 2
AOUT_CUPPEROUT_BIN_CLOWERIN, // 3
AOUT_CUPPERIN_BOUT_CLOWEROUT, // 4
AOUT_CUPPERIN_BOUT_CLOWERIN, // 5
AOUT_CUPPERIN_BIN_CLOWEROUT, // 6
AOUT_CUPPERIN_BIN_CLOWERIN, // 7
AIN_CUPPEROUT_BOUT_CLOWEROUT, // 8
AIN_CUPPEROUT_BOUT_CLOWERIN, // 9
AIN_CUPPEROUT_BIN_CLOWEROUT, //
10
AIN_CUPPEROUT_BIN_CLOWERIN, //
11
AIN_CUPPERIN_BOUT_CLOWEROUT, //
12
AIN_CUPPERIN_BOUT_CLOWERIN, //
13
AIN_CUPPERIN_BIN_CLOWEROUT, //
14
AIN_CUPPERIN_BIN_CLOWERIN //
15
};

// end const3a.h

Click here to download const3a.h

Note in the following that the "extern int set_up_ppi(int mode);" prototype for set_up_ppi()
reflects the fact that it is external to the test file. The only way the driver can access any
variables in the digital C module is through set_up_ppi() and get_port(). Place the following in
experi3a.c:

// experi3a.c

// include header with constants


#include "const3a.h"

// change the set_up_ppi() prototype


to a void return for normal operation
extern int set_up_ppi(int mode);
extern void get_port(void);

void main(void)
{
int x,y;

// the following will tell us what


we should get in normal operation
char *mode_type[] = {"A = out, C
Upper = out, B = out, C Lower = out",
"A = out, C
Upper = out, B = out, C Lower = in",
"A = out, C
Upper = out, B = in, C Lower = out",
"A = out, C
Upper = out, B = in, C Lower = in",
"A = out, C
Upper = in, B = out, C Lower = out",
"A = out, C
Upper = in, B = out, C Lower = in",
"A = out, C
Upper = in, B = in, C Lower = out",
"A = out, C
Upper = in, B = in, C Lower = in",
"A = in, C
Upper = out, B = out, C Lower = out",
"A = in, C
Upper = out, B = out, C Lower = in",
"A = in, C
Upper = out, B = in, C Lower = out",
"A = in, C
Upper = out, B = in, C Lower = in",
"A = in, C
Upper = in, B = out, C Lower = out",
"A = in, C
Upper = in, B = out, C Lower = in",
"A = in, C
Upper = in, B = in, C Lower = out",
"A = in, C
Upper = in, B = in, C Lower = in"};

for(x=AOUT_CUPPEROUT_BOUT_CLOWEROUT;
x<=AIN_CUPPERIN_BIN_CLOWERIN; x++)
{
y = set_up_ppi(x);
printf("mode = %02d command =
%02X %s\n",x,y,mode_type[x]);
}

// end experi3a.c

Click here to download experi3a.c

The "char *mode_type[]" business above provides an opportunity for a little discussion of
pointers. A pointer simply points to a location in memory. The asterisk says this is a pointer.
The "[]" part tells us that this is an array, which is to say it's like a list. The first item in the list
is [0], the second one [1], and so on. The "char *mode_type[]" term then, tells us that this is an
array of pointers to to the phrases in quotes.
Thus, mode_type[0] = "A = out, C Upper = out, B = out, C Lower = out"
and mode_type[5] = "A = out, C Upper = in, B = out, C Lower = in"

Now make the following entries to set_up_ppi() in this experiment's first version of the digital
module, the digi3a.c file. Notice that get_port() has been included at the bottom:

// digi3a.c

// The following are known only to


the functions that follow them.
// They can't be modified or even
accessed by anything above this
point.
unsigned base;
unsigned switch_port;
unsigned ppi_porta;
unsigned ppi_portb;
unsigned ppi_portc;

// set up the ppi -- change back to a


void return for normal operation
int set_up_ppi(int mode)
{
unsigned control = base + 0x23;
int command;

command = (mode & 0x0c) << 1; //


shift bits 2 and 3 into positions 3
and 4
command += (mode & 3); // add in
bits 0 and 1
command |= 0x80; // OR in bit 7 for
PPI set up

// remove the following for normal


operation
return command;

outp(control, command); // set


according to mode command

} // end set_up_ppi(..)

// get the port -- in the future,


this will grow into an auto-detect
function
void get_port(void)
{
base = 0x300;
switch_port = base + 0x18;
ppi_porta = base + 0x20;
ppi_portb = base + 0x21;
ppi_portc = base + 0x22;

} // end get_port()

// end digi3a.c
Click here to download digi3a.c

The return value of set_up_ppi() has been temporarily changed to an integer and the value of
command is returned for purposes of testing. Both experi3a.c and digi3a.c are compiled
individually to produce what are called object files. The two object files are then linked
together by the linker to produce the executable file. The linker will know to look in other than
the experi3a module for set_up_ppi() because of the extern directive.

To compile and link using POWERC, do the following:


pc digi3a <enter>
pc experi3a <enter>
pcl experi3a digi3a <enter>

Put the three commands in a batch file if you like, then change the names of the files each time.

The two pc commands will compile digi3a and experi3a and generate object files (which have a
.mix extension for the PowerC compiler -- most compilers produce .obj files). The pcl
command will link the two object files and produce an executable called experi3a.exe, provided
there were no errors. The executable is named after the first module, which is usually the one
with main() in it.

Now simply type in experi3a <enter>

The following will be the output if everything was entered correctly:

mode = 00 command = 80 A = out, C Upper = out, B = out, C Lower = out


mode = 01 command = 81 A = out, C Upper = out, B = out, C Lower = in
mode = 02 command = 82 A = out, C Upper = out, B = in, C Lower = out
mode = 03 command = 83 A = out, C Upper = out, B = in, C Lower = in
mode = 04 command = 88 A = out, C Upper = in, B = out, C Lower = out
mode = 05 command = 89 A = out, C Upper = in, B = out, C Lower = in
mode = 06 command = 8A A = out, C Upper = in, B = in, C Lower = out
mode = 07 command = 8B A = out, C Upper = in, B = in, C Lower = in
mode = 08 command = 90 A = in, C Upper = out, B = out, C Lower = out
mode = 09 command = 91 A = in, C Upper = out, B = out, C Lower = in
mode = 10 command = 92 A = in, C Upper = out, B = in, C Lower = out
mode = 11 command = 93 A = in, C Upper = out, B = in, C Lower = in
mode = 12 command = 98 A = in, C Upper = in, B = out, C Lower = out
mode = 13 command = 99 A = in, C Upper = in, B = out, C Lower = in
mode = 14 command = 9A A = in, C Upper = in, B = in, C Lower = out
mode = 15 command = 9B A = in, C Upper = in, B = in, C Lower = in

The following table, repeated from Experiment 2, confirms that the codes are correct:

Bits Port Assignments


Control
Values Port C Port C
D7 D6 D5 D4 D3 D2 D1 D0 Port A Port B
Upper Lower
0X80 1 0 0 0 0 0 0 0 Output Output Output Output
0X81 1 0 0 0 0 0 0 1 Output Output Output Input
0X82 1 0 0 0 0 0 1 0 Output Output Input Output
0X83 1 0 0 0 0 0 1 1 Output Output Input Input
0X88 1 0 0 0 1 0 0 0 Output Input Output Output
0X89 1 0 0 0 1 0 0 1 Output Input Output Input
0X8A 1 0 0 0 1 0 1 0 Output Input Input Output
0X8B 1 0 0 0 1 0 1 1 Output Input Input Input
Bits Port Assignments
Control
Values Port C Port C
D7 D6 D5 D4 D3 D2 D1 D0 Port A Port B
Upper Lower
0X90 1 0 0 1 0 0 0 0 Input Output Output Output
0X91 1 0 0 1 0 0 0 1 Input Output Output Input
0X92 1 0 0 1 0 0 1 0 Input Output Input Output
0X93 1 0 0 1 0 0 1 1 Input Output Input Input
0X98 1 0 0 1 1 0 0 0 Input Input Output Output
0X99 1 0 0 1 1 0 0 1 Input Input Output Input
0X9A 1 0 0 1 1 0 1 0 Input Input Input Output
0X9B 1 0 0 1 1 0 1 1 Input Input Input Input

Now change set_up_ppi() back to the form it will be in normal operation and put it in the
digi3b.c file:

// digi3b.c

// The following are known only to


the functions that follow them.
// They can't be modified or even
accessed by anything above this
point.
unsigned base;
unsigned switch_port;
unsigned ppi_porta;
unsigned ppi_portb;
unsigned ppi_portc;

// set up the ppi according to the


dictates of the mode argument
void set_up_ppi(int mode)
{
unsigned control = base + 0x23;
int command;

command = (mode & 0x0c) << 1; //


shift bits 2 and 3 into positions 3
and 4
command += (mode & 3); // add in
bits 0 and 1
command |= 0x80; // OR in bit 7 for
PPI set up

outp(control, command); // set


according to mode command

} // end set_up_ppi(..)

// get the port -- in the future,


this will grow into an auto-detect
function
void get_port(void)
{
base = 0x300;
switch_port = base + 0x18;
ppi_porta = base + 0x20;
ppi_portb = base + 0x21;
ppi_portc = base + 0x22;
} // end get_port()

// end digi3b.c

Click here to download digi3b.c

A very common way to detect a lot of closures with few inputs and outputs is to use a switch
matrix. This is shown schematically as a grid of wires with switches at the intersections. A
matrix is most commonly used in devices such as keyboards and numeric keypads. The only
way a row can connect to a column in a matrix is through one of the switches:

In this part of the experiment, Port A is connected to the rows and Port B is connected to the
columns. You will recall from Experiment 2 that Port B has pull-up resistors connected to it.
That fact was taken advantage of to add 8 switch inputs. An input would be grounded by a
switch when it was turned on, producing a low on its bit in Port B. The switches no longer have
one side connected to ground in the matrix, but to rows that are connected to the lines of Port
A. If a Port A bit is taken low and a switch that is connected to Port A is turned on however, the
Port B line connected to the same switch will be taken low. Say, for example, that PA2 is taken
low. Now turn on the switch at the intersection of PA2 and PB4. PB4 will be taken low. All
that's necessary to determine if a particular switch is turned on is to take the correct bit of Port
A low, then check the correct bit of Port B to see if it has been pulled low. The procedure
permits 64 unique closures to be detected.
To make the exercise interesting, let's use the 74LS244 for switch closures 1 through 3 as in
Experiment 1, but use the matrix to add 64 more. Switches 4 through 67 will be considered to
be at the matrix intersections shown below. Thus, in the example above, switch 24 will be at the
intersection of PA2 and PB4.

Port A Bit
4 5 6 7 8 9 10 11
0
Port A Bit
12 13 14 15 16 17 18 19
1
Port A Bit
20 21 22 23 24 25 26 27
2
Port A Bit
28 29 30 31 32 33 34 35
3
Port A Bit
36 37 38 39 40 41 42 43
4
Port A Bit
44 45 46 47 48 49 50 51
5
Port A Bit
52 53 54 55 56 57 58 59
6
Port A Bit
60 61 62 63 64 65 66 67
7
Port B Bit Port B Bit Port B Bit Port B Bit Port B Bit Port B Bit Port B Bit Port B bit
0 1 2 3 4 5 6 7

We need to take the appropriate bit low in Port A, then look at the appropriate bit in Port B to
see if a particular switch is on. Port A bit 0 should be taken low to look at switches 4 through
11, Port A bit 1 should be taken low to look at switches 12 through 19, and so on. We know
how to shift a 1 by the number of bits to get things in the right place, so let's start by trying to
figure out how to get 0 through 7 out of 4 through 67. Since we want 0 when we get 4 as an
input, start by subtracting 4, which gives us the following:

input input-4
4 through 11 0 - 7
12 through 19 8 - 15
20 through 27 16 - 23
28 through 35 24 - 31
36 through 43 32 - 39
44 through 51 40 - 47
52 through 59 48 - 55
60 through 67 56 - 63

Since we want all of the first row to be 0, divide by 8 and neglect the remainder. Turns out, this
provides the 0 through 7 that's needed:

input-4 (input-4)/8
0 - 7 0
8 - 15 1
16 - 23 2
24 - 31 3
32 - 39 4
40 - 47 5
48 - 55 6
56 - 63 7

Now shift 1 to the left by the result of the above and negate to turn 1s into 0s:

1 << ((input - 4)/8) ~(1 << ((input - 4)/8))


00000001 11111110
00000010 11111101
00000100 11111011
00001000 11110111
00010000 11101111
00100000 11011111
01000000 10111111
10000000 01111111

Thus, the value given to port a is:


porta_val = ~(1 << ( (input - 4) / 8) );

Since porta_val will be an integer and we only need the lower 8 bits, mask off the upper 8 bits
for good measure:
porta_val = (~(1 << ( (input - 4) / 8) )) & 0xff;

Loading the Port A register with porta_val will take the proper row bit low to test for the
indicated switch. The next task is to look at the proper column bit after reading Port B. Bit 0
needs to be used for switches 4, 12, 20, etc., bit 3 for 7, 15, 23, etc. The values change with a
step of 8 since movement is down a column:

Port A Bit
4 5 6 7 8 9 10 11
0
Port A Bit
12 13 14 15 16 17 18 19
1
Port A Bit
20 21 22 23 24 25 26 27
2
Port A Bit
28 29 30 31 32 33 34 35
3
Port A Bit
36 37 38 39 40 41 42 43
4
Port A Bit
44 45 46 47 48 49 50 51
5
Port A Bit
52 53 54 55 56 57 58 59
6
Port A Bit
60 61 62 63 64 65 66 67
7
Port B Bit Port B Bit Port B Bit Port B Bit Port B Bit Port B Bit Port B Bit Port B bit
0 1 2 3 4 5 6 7

We can start by subtracting 4 again. This time however, dividing by 8 and ignoring the
remainer won't work. Let's see what would happen if we divided some examples by 8 and kept
only the remainder. The R below means remainder. Note that 4 has already been subtracted:

0 / 8 = 0 R 0 40 / 8 = 5 R 0 27 / 8 = 3 R 3 21 / 8 = 2 R 5 38 / 8 = 4 R 6 55 / 8 = 6 R 7
Looks like that might work. As it turns out, something called modulo arithmetic will provide
us with just the remainder of an integer division. The symbol is %:

0 % 8 = 0 40 % 8 = 0 27 % 8 = 3 21 % 8 = 5 38 % 8 = 6 55 % 8 = 7
|-----------------------|
| 0| 1| 2| 3| 4| 5| 6| 7|
| 8| 9|10|11|12|13|14|15|
|16|17|18|19|20|21|22|23|
|24|25|26|27|28|29|30|31|
|32|33|34|35|36|37|38|39|
|40|41|42|43|44|45|46|47|
|48|49|50|51|52|53|54|55|
|56|57|58|59|60|61|62|63|
|-----------------------|
(input - 4) % 8 0 1 2 3 4 5 6 7
Input, Step 8, Input - 4, Step 8,
going down the column going down the column 1 << ((input - 4) % 8)
4 ~ 60 0 ~ 56 00000001
5 ~ 61 1 ~ 57 00000010
6 ~ 62 2 ~ 58 00000100
7 ~ 63 3 ~ 59 00001000
8 ~ 64 4 ~ 60 00010000
9 ~ 65 5 ~ 61 00100000
10 ~ 66 6 ~ 62 01000000
11 ~ 67 7 ~ 63 10000000

The Port B mask, making sure we only use the lower 8 bits, is:
portb_mask = (1 << ((input - 4) % 8)) & 0xff;

The considerable work done to produce a few lines of code illustrates an important premise of
programming, and, for that matter, just about everything that's worth doing: the important part
is not in the doing, but in the planning. The first impulse is to start writing code. We could have
done that here. We could also have ended up with 67 if() statements or 67 case statements
inside a switch(). Such inefficient code can be avoided if the up-front work is done. Efficiency,
both in terms of code size and speed, is important, especially in control and embedded systems.
Multiple if() statements or a long switch() take a lot more time and eat up a lot more space than
does the code designed here.

A very large part of control and embedded systems work has to do with checking bits to see if
they are on or off. It is a very good idea to get out a piece of paper and write down required
outputs or probable inputs in tabular form as was done above, all the way down to the bit level
if need be. Once the information is written down it can be analyzed using the techniques
illustrated above. Here are a few possibilities:

If possible and useful, subtract a constant to make a table start at 0.


Remember that it's always possible to shift a 1 around to put it where you need it, so see
if you can divide by something or do some modulus arithmetic to get a number that can
be used as a left or right bit-wise shift number. In an 8-bit system, try to make things
end up 0 through 7.
Consider all masking possibilities, especially AND.

The following is the is_closure() function, including the changes to increase capability to 67:
// digi3c.c

// The following are known only to the functions in this file.


// They can't be modified or even accessed by anything outside this
// file except through funtions in this file designed to provide access.
unsigned base;
unsigned switch_port;
unsigned ppi_porta;
unsigned ppi_portb;
unsigned ppi_portc;

int porta_val;
int porta_mask;

int portb_val;
int portb_mask;

int portc_val;
int portc_mask;

// ================================================================
// is_closure
// 1. Return -1 error indicator if the input
// is less than 1 or greater than 67.
//
// 2. If there is a fall-through from the above and the input
// is less than 4, return the status based on switch_port.
//
// 3. If there is a fall-through from both of the above, then
// return the status based on the matrix:

// |-----------------------|
// Port A bit
0| 4| 5| 6| 7| 8| 9|10|11|
// Port A bit
1|12|13|14|15|16|17|18|19|
// Port A bit
2|20|21|22|23|24|25|26|27|
// Port A bit
3|28|29|30|31|32|33|34|35|
// Port A bit
4|36|37|38|39|40|41|42|43|
// Port A bit
5|44|45|46|47|48|49|50|51|
// Port A bit
6|52|53|54|55|56|57|58|59|
// Port A bit
7|60|61|62|63|64|65|66|67|
// |-----------------------|
// Port B bits 0 1 2 3 4 5 6 7

// ================================================================
int is_closure(int input)
{

if(input < 1 || input > 67) // if the input is less than 1 or greater
return -1; // than 67, then return -1 showing an error

// we fell through the above so see if input is less than 4


if(input < 4)
return ((inp(switch_port) >> (input + 3)) & 1) ^ 1; // yes, return using
switch_port

// input is >= 4 so look at the matrix


// by first setting up Port A to take the appropriate row bit low
porta_val = (~(1 << ( (input - 4) / 8) )) & 0xff;

// clear the appropriate Port A bit


outp(ppi_porta, porta_val);

// determine what column bit to look at for this input


portb_mask = (1 << ((input - 4) % 8)) & 0xff;

// a closure will cause a low, so invert the return


if(!(inp(ppi_portb) & portb_mask))
return 1;

return 0;

}// end is_closure()

// set up the ppi according to the dictates of the mode argument


void set_up_ppi(int mode)
{
unsigned control = base + 0x23;
int command;

command = (mode & 0x0c) << 1; // shift bits 2 and 3 into positions 3 and 4
command += (mode & 3); // add in bits 0 and 1
command |= 0x80; // OR in bit 7 for PPI set up

outp(control, command); // set according to mode command

} // end set_up_ppi()

// get the port -- this will grow into an auto-detect function in the future
void get_port(void)
{
base = 0x300;
switch_port = base + 0x18;
ppi_porta = base + 0x20;
ppi_portb = base + 0x21;
ppi_portc = base + 0x22;

} // end get_port()

// end digi3c.c

Click here to download digi3c.c

The following is the test procedure:

// experi3b.c

#include <conio.h>
#include <stdio.h>
#include <bios.h>

// include header with constants


#include "const3a.h"

// external prototypes
extern void set_up_ppi(int mode);
extern void get_port(void);
extern int is_closure(int closurenumber);

void main(void)
{
int x,y;

get_port(); // get the port number and establish register locations

// make A an output and B an input


set_up_ppi(AOUT_CUPPERIN_BIN_CLOWERIN);

clrscrn(); // clear the screen

while(1) // stay in loop forever


{
// "keyboard hit" is a function that checks
// to see if a key has been pressed.
if(kbhit())
break;// A key was pressed -- break out of the while(1) loop

poscurs(0,0); // put the cursor at the top left

for(x=1; x<4; x++) // first 3 switch numbers


printf("%4d",x); // printed 4 characters wide with leading blanks
puts(""); // go to next line

for(x=1; x<4; x++)


{
if(is_closure(x)) // is there a closure -- if so, print ON, else
print OFF
printf("%4s","ON"); // "%4s" means print string 4 characters wide
with leading blanks
else printf("%4s","OFF");
}
puts(""); // go to next line

// go through matrix, starting at 4, as long as less than 68, step 8 (4,


12, 20, etc.)
for(x=4; x<68; x+=8)
{
for(y=0; y<8; y++) // go through 8 places on a matrix row -- x + y will
be the closure #
printf("%4d",x+y); // print the numbers
puts(""); // go to next line

for(y=0; y<8; y++)


{
if(is_closure(x+y)) // closure number = x + y
printf("%4s","ON"); // is there a closure -- if so, print ON, else
print OFF
else printf("%4s","OFF");
}
puts(""); // go to next line
}
// sleep(1); // optional wait 1 second before doing it again so results
can be seen
}
} // end experi3b.c
Click here to download experi3b.c
Click here to download const3a.h

NOTE: Please be sure to read the Warranty And Disclaimer before working with the
hardware!

The method used to connect to the first three switch inputs is repeated from Experiment 1. The
inputs are designated SWITCHIN1, SWITCHIN2 and SWITCHIN3 on the schematic and can
be accessed on pins 5, 4 and 3 respectively of HEADER 1. Connecting one of the inputs to pin
1 or 2 of HEADER 1 will ground the input and pull it low to indicate that the switch has been
turned on:

The following is part of the board layout showing the location of HEADER 1 at the top of the
board:

The best way to connect to Header 1 is by means of a 16-pin 2-row female header connector
with 8 pins on each row and with a .1" spacing between pins and a .1" spacing between rows.
Strip the ends of wires 1, 2, 3, 4 and 5. Look for the square pin on the back of the board. It is
pin 1. Plug the header socket in so that the wire with the stripe is on the same end as the square
pin. It's OK to use a header with more than 16 pins on header 1. Just let the end opposite pin 1
rest on C2.
The following shows the connections to PPI Ports A and B through Header 3. Port A lines go to
pins 2 through 8 and pin 10. Port B lines are on odd-numbered pins 9 through 23 of Header 3.
The location of Header 3 and its pinout are shown below. Remember that pin 1 of any of the
headers is the one with the square shape on the back of the board. The view below-left is from
the top of the board. On the right is the schematic representation of Header 3.

The best way to connect to Header 3 is by means of a 26-pin 2-row female header connector
with 13 pins on each row and with a .1" spacing between pins and a .1" spacing between rows.
Strip the ends of wires 2 through 10 and the odd wires 11 through 23. Look for the square pin
on the back of the board. It's pin 1. Plug the header socket in so that the wire with the stripe is
on the same end as the square pin.

It might be easier to plug this many wires into a solderless or solderable breadboard such as the
ones shown below. They can be purchased at just about any electronics parts supply house:
Whatever you use, it will make the testing a lot easier if you try to arrange the wires to look as
close to a row/column arrangement as possible. If somebody comes up with a neat test setup,
scan me a picture and I'll post it.

The following shows the relationship among inputs, port bits and Header 3:

Port B bit: 0 1 2 3 4 5 6 7
Header 3 pin for Port B bit: 9 11 13 15 17 19 21 23
|-----------------------|
Port A bit 0, Header 3 pin 2| 4| 5| 6| 7| 8| 9|10|11|
Port A bit 1, Header 3 pin 4|12|13|14|15|16|17|18|19|
Port A bit 2, Header 3 pin 3|20|21|22|23|24|25|26|27|
Port A bit 3, Header 3 pin 6|28|29|30|31|32|33|34|35|
Port A bit 4, Header 3 pin 5|36|37|38|39|40|41|42|43|
Port A bit 5, Header 3 pin 8|44|45|46|47|48|49|50|51|
Port A bit 6, Header 3 pin 7|52|53|54|55|56|57|58|59|
Port A bit 7, Header 3 pin 10|60|61|62|63|64|65|66|67|
|-----------------------|

The test wiring might look like this. Notice that the Port A lines are staggered due to the fact
that the board's vertical rows are connected:

Please note that only one switch at a time can be turned on without causing problems under
some circumstances. Know why?

To compile and link experi3b using POWERC, do the following:


pc digi3c <enter>
pc experi3b <enter>
pcl experi3b digi3c <enter>

As noted above, you could put the three commands in a batch file if you like, then change the
"experi3_" part each time.
The executible experi3b.exe will be generated if there were no errors.

Now simply type in experi3b <enter> to test is_closure().

Controlling The Real World With Computers


::. Control And Embedded Systems .::

Experiment 4 - The Multiple Closure Problem And Basic Outputs With The PPI

Home Order Let me know what you think


Previous: Experiment 3 - The General Purpose Digital Input/Output Module - Part 1
Next: Experiment 5 - Controlling Motors

The matrix approach in Experiment 3 added significant closure detection capability to the
system. The input portion of the tutorial will be completed in this part of our discussion of the
I/O module by showing some additional approaches to the matrix, then we will work on
controlling a little higher-power devices. That's probably welcome to those who are tired of all
the work on closures. The reason so much is being done with closures however, is two-fold. In
the first place, much of the control and embedded world deals with closures, whether they are
actual, physical metalic closures or simulated by light, magnetic or other means. Secondly, it
provides a good opportunity to work with bit logic, which is very important in control and
embedded systems.

The first thing we have to do is find a solution to a potential problem with the matrix in its
present form. If you have read the Q & A lately (advisable), you will find a question about
using multiple switch closures with the matrix used in Experiment 3. Take another look at the
matrix:

NOTE: Please be sure to read the Warranty And Disclaimer before working with the
hardware!

Remember that the way a closure in the matrix was detected was to take a Port A line low in the
PPI (programmable peripheral interface), which is commonly called strobing, then look at a
column bit on Port B. The intersecting contact is closed if the Port B bit is pulled low. Let's
consider a couple of closures. Let's say we take PA2 on Port A low. Port B will show 0xFF if
there are no closures since all of its bits will be pulled high by the resistors in ARRAY1 (see the
schematic). Let's say that a switch at the intersection of PA2 and PB4 is turned on. Since PA2 is
low, it will pull PB4 low through the closure, indicating the switch is on.

So far, so good, but notice what happens if the switch at the juction of PA1 and PB4 is also
turned on. Remember that the matrix is strobed by taking only one bit of Port A low at a time.
That means that all of the other bits on Port A are high, so PA1 will be high. A circuit will be
made through the two switches that will connect PA1 and PA2 to each other. Since one is high
and the other low, they will "fight" each other, and might stress the device beyond its limits.

Now take a look at the following changes to the matrix:

Notice the diode at each intersection in series with each switch (take a look at How To Read A
Schematic if you don't remember what diode and/or series means). Now imagine that both
PA1,PB4 and PA2,PB4 are still turned on and that PA2 is again strobed low. PB4 will still be
pulled low through the switch/diode series circuit at the junction of PA2 and PB4, but the
diodes will prevent PA1 and PA2 from fighting each other because there will be almost no
current through the diode at PA1,PB4. Remember from How To Read A Schematic that:

a diode provides a one-way path for current,


that the charge flow is generally considered in electronics to be from negative to
positive
and that the flow in a diode is, therefore, in the opposite direction of the arrow, from
cathode to anode.

Remember that PA1 is near 5V and PA2 is near ground at the instant PA2 strobes the row
("near" because most digital devices do not actually go all the way to full sytem positive or all
the way down to ground -- often called rail to rail swing, which has pert near nothing to do
with 40s-style music). The charge flow would be from PA2 to PA1 (negative to positive)
without a diode. The diode at PA1,PB4 is reverse biased however, and cannot conduct. There
is a little leakage current, but it's insignificant. The diode at PA2,PB4 is forward biased and
conducts. In this case, the charge flow is from PA2 to PB4's pullup resistor, pulling PB4 near
ground. It's not going to be at ground due to the characteristics of the digital device, plus the
fact that a silicone diode has about a .7V drop across it when it is conducting. A germanium
diode is a little better, with about .3V drop.

If you would like to make a matrix with diodes, you could try the common, easy-to-find
1N4148 silicone diodes. If you read the data sheet however, you will find, at least for the
82C55, that a typical maximum low input voltage is .8V. The low output has a typical
maximum of .4V. There might be times when you can't pull Port B low enough to read back a
low logic level. The following diagram shows the limits imposed by the silicon diode voltage
drop and the low limit of the 82C55. The two effectively add, producing a 1.1V design
minimum at Port B. It is considered a design minimum because worst-case parameters are used,
as they should be.

Germanium diodes such as the 1N60 will work more reliably in this application with their
lower .3V drop, but are a little harder to find. Another alternative is to find something to drive
the rows that removes itself when not turned on. For example:

The 74LS05 (see 74LS05.PDF) and similar devices do not produce a high output. They only
pull things low, commonly called sinking, which is what they can do with the pullups on Port
B. Notice that the device is an inverter (see the boolean logic section). The Port A lines would
connect to the "A" inputs, and the row lines to the "Y" outputs. A Port A output would then be
taken high to strobe a row, which would cause the inverter output to pull the row low. When the
Port A line is turned off, the inverter output will release the row line. It will not drive the row
high, removing the need for diodes and their voltage drops. With six inverters each, two of
these devices are needed to modify the matrix in this experiment:
All said and done, you can use the plain matrix for most of the experiments. This information is
provided just in case someone wants to experiment with multiple closure inputs.

Let's explore just a little more with the matrix, then I'll get on with powering stuff. In
Experiment 3, the matrix used Ports A and B to provide 64 closure detection points and the
74LS244 added 3 more to give a total of 67. For still more capability, the 74LS244 can be used
to add 3 column bits. With Port B providing 8 column bits and still using Port A to strobe the
rows with its 8 bits, we have 8 * 11 = 88 closure points. That's more than enough for most
applications.

There are many ways the 74LS244 can be added. For example, it and Port B can be connected
to the rows rather than the columns. There is no rule that says they must be used to monitor
columns. They could just as easily monitor the rows and something else used to strobe the
columns. For convenience we will stay with column monitoring since we began with that
arrangement.

In the real world, consideration must be given to such factors as board layout and programming
complications. Consider what would be involved if the switches were arranged in the following
manner:

|--------------------------------|
Port A bit
0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|
Port A bit
1|12|13|14|15|16|17|18|19|20|21|22|
Port A bit
2|23|24|25|26|27|28|29|30|31|32|33|
Port A bit
3|34|35|36|37|38|39|40|41|42|43|44|
Port A bit
4|45|46|47|48|49|50|51|52|53|54|55|
Port A bit
5|56|57|58|59|60|61|62|63|64|65|66|
Port A bit
6|67|68|69|70|71|72|73|74|75|76|77|
Port A bit
7|78|79|80|81|82|83|84|85|86|87|88|
|--------------------------------|
S S S
W W W P P P P P P P P
1 2 3 B B B B B B B B
Bit Numbers 4 5 6 0 1 2 3 4 5 6 7

SW1, 2 and 3 are the 74LS244 SWITCHn inputs and their bit numbers. PB[0:7] are the Port B
bit numbers used ([0:7] is shorthand meaning "0 through 7"). The problem with this
arrangement is that the 74LS244 is used for the first three closures in a row, then Port B must
be used for the next 8, then back to the '244 for the first three places of the next row. It can be
done, and should be if electrical layouts demand it, but another arrangement makes for easier
programming. Consider this:

|--------------------------------|
Port A bit
0| 1| 2| 3|25|26|27|28|29|30|31|32|
Port A bit
1| 4| 5| 6|33|34|35|36|37|38|39|40|
Port A bit
2| 7| 8| 9|41|42|43|44|45|46|47|48|
Port A bit
3|10|11|12|49|50|51|52|53|54|55|56|
Port A bit
4|13|14|15|57|58|59|60|61|62|63|64|
Port A bit
5|16|17|18|65|66|67|68|69|70|71|72|
Port A bit
6|19|20|21|73|74|75|76|77|78|79|80|
Port A bit
7|22|23|24|81|82|83|84|85|86|87|88|
|--------------------------------|
S S S
W W W P P P P P P P P
1 2 3 B B B B B B B B
Bit Numbers 4 5 6 0 1 2 3 4 5 6 7

Now the 74LS244 is used for [1:24], and Port B for [25:88]. The change of column registers is
made above 24 and never switched back. Using the logic suggestions from Experiment 3 to
figure out the logic for the 74LS244 section:

1. If possible and useful, subtract a constant to make a table start at 0:


Subtracting 1 will change the numbers from 1 through 24 to 0 through 23.
2. Remember that it's always possible to shift a 1 around to put it where you need it,
so see if you can divide by something or do some modulus arithmetic to get a
number that can be used as a left or right bit-wise shift number. In an 8-bit system,
try to make things end up 0 through 7:
The goal is to shift the result bit down to position 0. That means we need to shift 4, 5 or
6 bits for the first 3 numbers on a row after subtracting. Dividing by 3 and neglecting
the remainder won't work. That can be confirmed by trying the operation on the first
row. For the first three numbers of the first row, we will get 0, 0 and 0 if we divide 0, 1
and 2 by 3. Try modulo 3. Recall from Experiment 3 that the modulo operation returns
the remainder, so divide by 3 but use the remainder rather than the quotient as the result
of the operation. Now we get 0,1 and 2 for the first 3 numbers. That might not be right
since it gives back the same numbers, so try the last 3. The first three numbers on the
last row after subtracting 1, 21, 22 and 23, will also give us 0,1 and 2. Try the middle
row's first 3 numbers. The numbers 9, 10 and 11 also provide 0, 1 and 2. All that's
needed after the modulo arithmetic is to add 4 which will provide the required 4, 5 and
6 shift numbers for the first 3 numbers on each row. The information from the 74LS244
buffer can be shifted the correct number of bit positions based on the input in order to
move the bit into the least significant 0 position.
3. Consider all masking possibilities, especially AND:
Take the value returned from the 74LS244 buffer and shifted using the above procedure
and AND it with 1. That will mask off all but bit 0. Now XOR with 1 to invert the
result. Remember from the Boolean Logic section that the XOR operation is true if the
two inputs are different. Also recall that when a closure is turned on, the resulting bit
will be low. When a closure is off, the XOR will be 1 with 1, resulting in 0. When the
closure is on, the XOR will be 1 with 0, resulting in 1. Thus, 0 will be returned for off,
and 1 will be returned for on.

The following shows the results of the effort to get the correct shift number:

Input -1 %3 +4
1 2 3 0 1 2 0 1 2 4 5 6
4 5 6 3 4 5 0 1 2 4 5 6
7 8 9 6 7 8 0 1 2 4 5 6
10 11 12 9 10 11 0 1 2 4 5 6
13 14 15 12 13 14 0 1 2 4 5 6
16 17 18 15 16 17 0 1 2 4 5 6
19 20 21 18 19 20 0 1 2 4 5 6
22 23 24 21 22 23 0 1 2 4 5 6

Now apply step 3 by ANDing the shifted number with 1 then XORing it with 1.

There is a multiple line way to go through the process and a one-line way. First, the multiple
line way:

int sw_in_port_val,shiftval; // switch in port and shift values

shiftval = input - 1;
shiftval%=3; // same as shiftval = shiftval % 3;
shiftval+=4; // same as shiftval = shiftval + 4;

sw_in_port_val = inp(switch_port); // get a number out of the 74LS244


buffer
sw_in_port_val>>=shiftval; // shift the port value to the right shiftval
times
sw_in_port_val&=1; // same as sw_in_port_val = sw_in_port_val & 1;
sw_in_port_val^=1; // same as sw_in_port_val = sw_in_port_val ^ 1;

Then, the one-line way:

Return
| the port value
| | shifted
| | to the
| | right
| | | by the
| | | input
| | | less 1
| | | | modulo 3
| | | | | plus
| | | | | 4.
| | | | | | Then AND
| | | | | | all of
| | | | | | that with 1
| | | | | | | and, finally,
| | | | | | | XOR the
| | | | | | | whole mess
| | | | | | | with 1.
| | | | | | | |
return ((inp(switch_port) >> (((input - 1) % 3) + 4)) & 1) ^ 1;
12 3 3 456 6 5 42 1

The numbering of the parenthesis would not, of course, be included in the actual code, although
it would be handy if compilers would show it. It is included here to show what belongs to what.
The raw input is from the port inside 3. This value gets shifted by what is inside 4. Everything
inside 2 gets ANDed with 1. Everything inside 1 is XORed with 1, and 5 and 6 simply block
things off so operations will take place as expected.

One quick check to see if there are at least the right number of parenthesis is to confirm that
there are the same number of close parenthesis as opens.
Notice how the numbering works. Start numbering opens from the left. When a close is
encountered, give it the same number as the last unclosed open.

Of course the whole mess can be avoided by using the multiple line method. It's a lot easier to
read and understand, although it might take a little more processor time and use a bit more
memory. Those are the only good reasons to use the more complex code. Never write code
that's hard to understand unless it's necessary. Too many people do such things in an attempt to
show how smart they are, and end up showing just how dumb they can get. It is done in this
tutorial to provide you with the information you need just in case memory and/or processor
time is getting critical.

All that's needed now is to figure out what needs to go in Port A to strobe the appropriate row.
Maybe you have already noticed something: Any time a series of numbers has been
encountered where each one was 1 greater than the previous, a modulo has been used that has
been one greater than the highest number in the first series of numbers. For example, when the
first series was 0, 1 and 2, modulo 3 was used. With [0:7] as the first series, it and [8:15] use
modulo 8.

Any time a series of numbers has been encountered with an increment greater than 1, we have
divided by the increment and neglected the remainder.

So what do we give Port A? We are going down the first column with the series 0, 3, 6, 9, 12,
15, 18 and 21 after subtracting 1. The increment is 3, not 1. We need 0 through 7 for bit
numbers to strobe the rows. This is what we get when we divide by 3 and neglect the
remainder:

divide by 3 and
Input -1 neglect remainder
1 2 3 0 1 2 0 0 0
4 5 6 3 4 5 1 1 1
7 8 9 6 7 8 2 2 2
10 11 12 9 10 11 3 3 3
13 14 15 12 13 14 4 4 4
16 17 18 15 16 17 5 5 5
19 20 21 18 19 20 6 6 6
22 23 24 21 22 23 7 7 7

Now what will be stored in Port A is loaded with 0xff. Next, a 1 is shifted to the proper location
using the above shift value. The result of the shift is XORed with the 0xff in the Port A value.
Since this is a little different from previous approaches, consider what the results would be for
the middle row.

The shift number for an input of 10, 11 or 12 is 3. Starting with 1 means only bit 0 has anything
in it: 00000001. Shift it to the left 3 places and you get 00001000. The Port A value has 0xff =
11111111. Now XOR the two:

11111111
00001000
11110111 The result is the clearing of bit 3 and no others.

And this is an outline of the routine up to now:


// digital.c

int porta_val;
int porta_mask;

int portb_val;
int portb_mask;

int portc_val;
int portc_mask;

int is_closure(int input)


{
int sw_in_port_val,shiftval; // switch in port and shift values

if(input < 1 || input > 88) // if the input is less than 1 or greater
return -1; // than 88, then return -1 showing an error

// we fell through the above so see if input is less than 25


if(input < 25)
{
input--; // same as input = input - 1;

shiftval = input/3; // divide and neglect the remainder

// A 1 is shifted to the proper location.


// The result of the shift is XORed with
// 0xff and stored in the Port A value,
// turning off the desired bit and leaving
// all of the others on.
// Use porta_val = 1 << shiftval
// if the 74LS05 is used, because
// the desired operation is to turn
// on the bit.
porta_val = 0xff ^ (1 << shiftval); // or porta_val = 1 << shiftval with
74LS05

// clear the appropriate Port A bit


outp(ppi_porta, porta_val);

shiftval = input % 3; // divide and use only the remainder


shiftval+=4; // same as shiftval = shiftval + 4;

sw_in_port_val = inp(switch_port); // get a number out of the 74LS244


buffer
sw_in_port_val>>=shiftval; // shift the port value to the right shiftval
times
sw_in_port_val&=1; // same as sw_in_port_val = sw_in_port_val & 1;
sw_in_port_val^=1; // same as sw_in_port_val = sw_in_port_val ^ 1;

return sw_in_port_val;
}

}// end is_closure()


// end digital.c

Now for the numbers greater than 24. First get the bit number for the Port A row strobe:

rows increment by
8 so divide by 8,
Input -25 neglect remainder
25 26 27 28 29 30 31 32 0 1 2 3 4 5 6 7 0 0 0 0 0 0 0 0
33 34 35 36 37 38 39 40 8 9 10 11 12 13 14 15 1 1 1 1 1 1 1 1
41 42 43 44 45 46 47 48 16 17 18 19 20 21 22 23 2 2 2 2 2 2 2 2
49 50 51 52 53 54 55 56 24 25 26 27 28 29 30 31 3 3 3 3 3 3 3 3
57 58 59 60 61 62 63 64 32 33 34 35 36 37 38 39 4 4 4 4 4 4 4 4
65 66 67 68 69 70 71 72 40 41 42 43 44 45 46 47 5 5 5 5 5 5 5 5
73 74 75 76 77 78 79 80 48 49 50 51 52 53 54 55 6 6 6 6 6 6 6 6
81 82 83 84 85 86 87 88 56 57 58 59 60 61 62 63 7 7 7 7 7 7 7 7

Then for the Port B row monitor bit shift number:

columns increment
Input -25 by 1 so modulo 8
25 26 27 28 29 30 31 32 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
33 34 35 36 37 38 39 40 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7
41 42 43 44 45 46 47 48 16 17 18 19 20 21 22 23 0 1 2 3 4 5 6 7
49 50 51 52 53 54 55 56 24 25 26 27 28 29 30 31 0 1 2 3 4 5 6 7
57 58 59 60 61 62 63 64 32 33 34 35 36 37 38 39 0 1 2 3 4 5 6 7
65 66 67 68 69 70 71 72 40 41 42 43 44 45 46 47 0 1 2 3 4 5 6 7
73 74 75 76 77 78 79 80 48 49 50 51 52 53 54 55 0 1 2 3 4 5 6 7
81 82 83 84 85 86 87 88 56 57 58 59 60 61 62 63 0 1 2 3 4 5 6 7

See how easy it is? Notice that the divide and modulo numbers are the same.

Now just add it to the routine:

// digi4a.c

// The following are known only to the functions in this file.


// They can't be modified or even accessed by anything outside this
// file except through funtions in this file designed to provide access.
unsigned base;
unsigned switch_port;
unsigned ppi_porta;
unsigned ppi_portb;
unsigned ppi_portc;

int porta_val;
int porta_mask;

int portb_val;
int portb_mask;

int portc_val;
int portc_mask;

// ================================================================
// is_closure
// 1. Return -1 error indicator if the input
// is less than 1 or greater than 88.
//
// 2. Return 1 if closure is on, 0 if it is off.

int is_closure(int input)


{
int sw_in_port_val,shiftval; // switch in port and shift values
if(input < 1 || input > 88) // if the input is less than 1 or greater
return -1; // than 88, then return -1 showing an error

// we fell through the above so see if the input is less than 25


if(input < 25)
{
input--; // same as input = input - 1;

shiftval = input/3; // divide and neglect the remainder

// A 1 is shifted to the proper location.


// The result of the shift is XORed with
// 0xff and stored in the Port A value,
// turning off the desired bit and leaving
// all of the others on.
// Use porta_val = 1 << shiftval
// if the 74LS05 is used, because
// the desired operation is to turn
// on the bit.
porta_val = 0xff ^ (1 << shiftval); // or porta_val = 1 << shiftval with
74LS05

// clear the appropriate Port A bit


outp(ppi_porta, porta_val);

shiftval = input % 3; // divide and use only the remainder


shiftval+=4; // same as shiftval = shiftval + 4;

sw_in_port_val = inp(switch_port); // get a number out of the '244 buffer


sw_in_port_val>>=shiftval; // shift it to the right shiftval
// places to put in bit 0 position
sw_in_port_val&=1; // same as sw_in_port_val = sw_in_port_val & 1;
sw_in_port_val^=1; // same as sw_in_port_val = sw_in_port_val ^ 1;

return sw_in_port_val;
}

// it's >= 25 if it gets this far

input-=25; // same as input = input - 25;

shiftval = input/8; // divide and neglect the remainder

// A 1 is shifted to the proper location.


// The result of the shift is XORed with
// 0xff and stored in the Port A value,
// turning off the desired bit and leaving
// all of the others on.
// Use porta_val = 1 << shiftval
// if the 74LS05 is used, because
// the desired operation is to turn
// on the bit.
porta_val = 0xff ^ (1 << shiftval); // or porta_val = 1 << shiftval with
74LS05

// clear the appropriate Port A bit


outp(ppi_porta, porta_val);

shiftval = input % 8; // divide but use only the remainder


portb_val = inp(ppi_portb); // get a number from Port B
portb_val>>=shiftval; // shift it to the right shiftval
// places to put in bit 0 position
portb_val&=1; // same as portb_val = portb_val & 1;
portb_val^=1; // same as portb_val = portb_val ^ 1;

return portb_val;

}// end is_closure()

// set up the ppi according to the dictates of the mode argument


void set_up_ppi(int mode)
{
unsigned control = base + 0x23;
int command;

command = (mode & 0x0c) << 1; // shift bits 2 and 3 into positions 4 and 5
command |= (mode & 3); // OR in bits 0 and 2
command |= 0x80; // OR in bit 7 for PPI set up

outp(control, command); // set according to mode command

} // end set_up_ppi()

// get the port -- this will grow into an auto-detect function in the future
void get_port(void)
{
base = 0x240; // sw1 = 0 sw2 = 1 sw3 = 1
switch_port = base + 0x18;
ppi_porta = base + 0x20;
ppi_portb = base + 0x21;
ppi_portc = base + 0x22;

} // end get_port()

// end digi4a.c

The following is the test procedure:

// experi4a.c

#include <conio.h>
#include <stdio.h>
#include <bios.h>

// include header with constants


#include "const4a.h"

// external prototypes
extern void set_up_ppi(int mode);
extern void get_port(void);
extern int is_closure(int closurenumber);

void main(void)
{
int x,y,r,c;
char *names[] = {" |--------------------------------|",
"Header 3 Pin 2 | 1| 2| 3|25|26|27|28|29|30|31|32|",
"Header 3 Pin 4 | 4| 5| 6|33|34|35|36|37|38|39|40|",
"Header 3 Pin 3 | 7| 8| 9|41|42|43|44|45|46|47|48|",
"Header 3 Pin 6 |10|11|12|49|50|51|52|53|54|55|56|",
"Header 3 Pin 5 |13|14|15|57|58|59|60|61|62|63|64|",
"Header 3 Pin 8 |16|17|18|65|66|67|68|69|70|71|72|",
"Header 3 Pin 7 |19|20|21|73|74|75|76|77|78|79|80|",
"Header 3 Pin 10 |22|23|24|81|82|83|84|85|86|87|88|",
" |--------------------------------|",
" Header Number H1 H1 H1 H3 H3 H3 H3 H3 H3 H3 H3",
" Header Pin 5 4 3 9 11 13 15 17 19 21 23",
NULL};

get_port(); // get the port number and establish register locations

// make A an output and B an input


set_up_ppi(AOUT_CUPPERIN_BIN_CLOWERIN);

clrscrn(); // clear the screen and automatically start printing


// at 0,0 which is the top-left of the screen
// (might not be available in all compilers)

for(x=0; names[x]!=NULL; x++)


printf("%s\n",names[x]);

// (the following two lines might not be available in all compilers)


r = cursrow() + 1; // get the screen row position plus 1
c = curscol(); // get the screen column position

while(1) // stay in loop forever


{
// "keyboard hit" is a function that checks
// to see if a key has been pressed.
if(kbhit())
break;// A key was pressed -- break out of the while(1) loop

// (the following might not be available in all compilers)


poscurs(r,c); // put the cursor one line below where the phrases ended

// Go through 88 switches with a row step of 11. This will give


// the first of each row, or 1, 12, 23, etc. What the user
// sees is reasonable for human viewing rather than reflecting
// the matrix layout. The y loop then runs through each row,
// starting at 0 and going through 10.
// The first row is x+y = 1, 2, 3 .... 11.
// The second row is x+y = 12, 13, 14 ...... 22, and so on.
for(x=1; x<89; x+=11)
{
for(y=0; y<11; y++) // each row is 11 columns wide
{
printf("%3d ",x+y); // print 3 characters wide with leading blanks
if(is_closure(x+y)) // is there a closure? yes = ON, else OFF
printf("%-3s","ON"); // "%-3s" means print string 4 characters
else printf("%-3s","OFF"); // wide with following blanks
}
puts(""); // go to next line

} // end for(x=1; x<89; x+=11)

} // end while(1)
} // end experi4a.c

Click here to download digi4a.c


Click here to download experi4a.c
Click here to download const4a.h

This test is a little more complex than ones in the past. In the first place, it has instructions for
connecting to the headers built into the program. As first mentioned in Putting It All Together,
the "char *names[]" tells us that this is an array of pointers to to the phrases in quotes. In
programming terms it is an array of pointers to strings. A string, in turn, is an array of
characters just like a sentence on a piece of paper. For example, "This is a test" would look
like this if it could be seen in memory:

T h i s i s a t e s t 0

The 0 at the end is called a terminator. It tells the software where the end of the string is
located. The pointer for a string is to the memory location of the first character. Notice the
NULL at the end of the array of strings. It can be considered a pointer to nowhere. The loop
that reads
for(x=0; names[x]!=NULL; x++)
printf("%s\n",names[x]);
says to start at the first string, which is names[0], and print strings until the NULL is
encountered.

Please note that the clear screen and cursor location and positioning routines that are called in
the program are in the MIX compiler, but might not be available with your compiler. Most
DOS-based compilers have some means of clearing the screen and positioning, although it
might be necessary to dig a little to find them.

There are many ways to use the various registers on the board to assist in detecting closures.
There are however, some minor limits. Port B and the 74LS244 are the only registers with
pullup resistors. The 74LS244 provides 3 such inputs and Port B 8. A lot of systems will do just
fine with 3, 8 or 11 closure inputs. More can be had with matrix arrangements. With the 11
pulled up lines being used, there are 16 lines left on the PPI that could be used in various ways.
If they were all used as strobes, 16 * 11 = 176 closures could be detected.

There will also be situations where closures would not be involved at all. For a truly universal
system, all possible combinations would be needed so the programmer could select any of
them. Both the basic settings for the PPI and all of the closure possibilities related to the PPI
settings have to be considered.

The PPI has A, C Upper, B and C Lower that could potentially be put to use in a matrix in
addition to the 74LS244. What's more, Port B can be used as a row driver or as column register
since it has pullup resistors. That means there are six things that have to be kept track of with
Port B providing dual services.
The enumeration developed in Experiment 3 has already been used in this experiment to set up
the PPI in the test program. Those enumeration numbers will be pushed over a little to make
room for the additional information. Recall that enumerations are treated as integers. That
means they are 16 bits in a DOS system. Since the PPI enumerators only take up 4 bits, they
can be moved to the left 6 bits to make room for the matrix indicators with plenty of room left.
The following is the PPI enumeration with the numbers shifted left 6 places:

enum
{
Aout_CUout_Bout_CLout = 0x0,
Aout_CUout_Bout_CLin = 0x40,
Aout_CUout_Bin_CLout = 0x80,
Aout_CUout_Bin_CLin = 0xC0,
Aout_CUin_Bout_CLout = 0x100,
Aout_CUin_Bout_CLin = 0x140,
Aout_CUin_Bin_CLout = 0x180,
Aout_CUin_Bin_CLin = 0x1C0,
Ain_CUout_Bout_CLout = 0x200,
Ain_CUout_Bout_CLin = 0x240,
Ain_CUout_Bin_CLout = 0x280,
Ain_CUout_Bin_CLin = 0x2C0,
Ain_CUin_Bout_CLout = 0x300,
Ain_CUin_Bout_CLin = 0x340,
Ain_CUin_Bin_CLout = 0x380,
Ain_CUin_Bin_CLin = 0x3C0
} PPI_Values;

Notice "PPI_Values" at the end. It is optional. If you name an enumeration in this manner, it
can only take on one of the values of the enumeration. Something such as "PPI_Values = 5;"
however, would be illegal. It's a good idea to test things such as an enumeration to make sure
the numbers were entered correctly. Simple:

#include <stdio.h>
#include <conio.h>
#include <string.h>

void main(void)
{
int x,bit;

enum
{
Aout_CUout_Bout_CLout = 0x0,
Aout_CUout_Bout_CLin = 0x40,
Aout_CUout_Bin_CLout = 0x80,
Aout_CUout_Bin_CLin = 0xC0,
Aout_CUin_Bout_CLout = 0x100,
Aout_CUin_Bout_CLin = 0x140,
Aout_CUin_Bin_CLout = 0x180,
Aout_CUin_Bin_CLin = 0x1C0,
Ain_CUout_Bout_CLout = 0x200,
Ain_CUout_Bout_CLin = 0x240,
Ain_CUout_Bin_CLout = 0x280,
Ain_CUout_Bin_CLin = 0x2C0,
Ain_CUin_Bout_CLout = 0x300,
Ain_CUin_Bout_CLin = 0x340,
Ain_CUin_Bin_CLout = 0x380,
Ain_CUin_Bin_CLin = 0x3C0
};
int nums[] = {Aout_CUout_Bout_CLout,
Aout_CUout_Bout_CLin,
Aout_CUout_Bin_CLout,
Aout_CUout_Bin_CLin,
Aout_CUin_Bout_CLout,
Aout_CUin_Bout_CLin,
Aout_CUin_Bin_CLout,
Aout_CUin_Bin_CLin,
Ain_CUout_Bout_CLout,
Ain_CUout_Bout_CLin,
Ain_CUout_Bin_CLout,
Ain_CUout_Bin_CLin,
Ain_CUin_Bout_CLout,
Ain_CUin_Bout_CLin,
Ain_CUin_Bin_CLout,
Ain_CUin_Bin_CLin,
-1};

for(x=0; nums[x]!=-1; x++)


{
printf("0x%-3X = ",nums[x]);
for(bit=0x400; bit>0; bit>>1)
{
if(bit & nums[x])
printf("1");
else printf("0");
}
printf(" >>6 = %2d = ",nums[x]>>6);
for(bit=128; bit>0; bit>>1)
{
if(bit & (nums[x]>>6))
printf("1");
else printf("0");
}
puts("");
}
}

An array of integers was constructed out of the enumeration constants. Notice the lack of an
asterisk. That's because nums[] is an array of integers, not pointers. The number -1 was added
to the end to let the program know it has reached the end. The numbers are printed in their
original HEX format, shifted to the right 6 places and printed in decimal. In addition, each is
printed in binary format. The program provides the following output:

0x0 = 00000000000 >>6 = 0 = 00000000


0x40 = 00001000000 >>6 = 1 = 00000001
0x80 = 00010000000 >>6 = 2 = 00000010
0xC0 = 00011000000 >>6 = 3 = 00000011
0x100 = 00100000000 >>6 = 4 = 00000100
0x140 = 00101000000 >>6 = 5 = 00000101
0x180 = 00110000000 >>6 = 6 = 00000110
0x1C0 = 00111000000 >>6 = 7 = 00000111
0x200 = 01000000000 >>6 = 8 = 00001000
0x240 = 01001000000 >>6 = 9 = 00001001
0x280 = 01010000000 >>6 = 10 = 00001010
0x2C0 = 01011000000 >>6 = 11 = 00001011
0x300 = 01100000000 >>6 = 12 = 00001100
0x340 = 01101000000 >>6 = 13 = 00001101
0x380 = 01110000000 >>6 = 14 = 00001110
0x3C0 = 01111000000 >>6 = 15 = 00001111

This provides the opportunity to print the array in a somewhat different way:

#include <stdio.h>
#include <conio.h>
#include <string.h>

void main(void)
{
int *ptr;

enum
{
Aout_CUout_Bout_CLout = 0x0,
Aout_CUout_Bout_CLin = 0x40,
Aout_CUout_Bin_CLout = 0x80,
Aout_CUout_Bin_CLin = 0xC0,
Aout_CUin_Bout_CLout = 0x100,
Aout_CUin_Bout_CLin = 0x140,
Aout_CUin_Bin_CLout = 0x180,
Aout_CUin_Bin_CLin = 0x1C0,
Ain_CUout_Bout_CLout = 0x200,
Ain_CUout_Bout_CLin = 0x240,
Ain_CUout_Bin_CLout = 0x280,
Ain_CUout_Bin_CLin = 0x2C0,
Ain_CUin_Bout_CLout = 0x300,
Ain_CUin_Bout_CLin = 0x340,
Ain_CUin_Bin_CLout = 0x380,
Ain_CUin_Bin_CLin = 0x3C0
};

int nums[] = {Aout_CUout_Bout_CLout,


Aout_CUout_Bout_CLin,
Aout_CUout_Bin_CLout,
Aout_CUout_Bin_CLin,
Aout_CUin_Bout_CLout,
Aout_CUin_Bout_CLin,
Aout_CUin_Bin_CLout,
Aout_CUin_Bin_CLin,
Ain_CUout_Bout_CLout,
Ain_CUout_Bout_CLin,
Ain_CUout_Bin_CLout,
Ain_CUout_Bin_CLin,
Ain_CUin_Bout_CLout,
Ain_CUin_Bout_CLin,
Ain_CUin_Bin_CLout,
Ain_CUin_Bin_CLin,
-1};

ptr = &nums[0];

while(*ptr != -1)
{
printf("value = 0x%-3X shifted right 6 places = %2d\n",*ptr,(*ptr)>>6);

ptr++;
}
}
Here, *ptr is a pointer to an integer. As noted, strings are already pointers. The integer array
above however, is not. To get its first address location for the pointer, use the ampersand:
ptr = &nums[0];

When you want to see the value that a pointer points to, use the asterisk to dereference it, such
as where the loop control says:
while(*ptr != -1)

The same thing is done in the printf() function. The first item in the printf statement is the
dereferenced HEX value before its shifted, and the second is the decimal shifted value.
Remember, the only reason it's printed in decimal or HEX is because we tell it that's what we
want by the %X and %d. Don't get confused by the HEX format command. The first "0x" part
is a literal. The "%-3X" part tells printf() to print HEX using upper case letters and three
characters and to fill with leading blanks, commanded by the "-" sign. It would provide trailing
blanks without the minus sign. The parenthesis around the (*ptr) where it is shifted is there to
protect it from precedence problems. It's not a problem here (take out the parenthesis and you
will see no difference), but I do it anyway rather than trying to remember because, well,
because I'm just a tad lazy.

The point I really wanted to make with all of this business has to do with the ptr++ statement.
Remember what would happen to an integer if we did that -- it would increment by 1. Look at
the table below, though. The table values are correct. Remember that a pointer points to a
location in memory. We are incrementing prt with the prt++ statement just like we would an
integer. That makes it move forward to point at another location in memory. There's no way
however, that it can be incrementing by one and still point to the right places. Remember that
integers in DOS are 16 bits, or two, not one byte wide. It's common these days for them to be
32 bits, or 4 bytes wide. The pointer must increment by 2 or 4 or more bytes, depending on the
system. It can't increment by one or it won't point to the right place in memory. Pointers
increment or decrement by the size of the type of object they point to. You don't have to
worry about how much that is because the compiler takes care of it. The output of the program
is below:

value = 0x0 shifted right 6 places = 0


value = 0x40 shifted right 6 places = 1
value = 0x80 shifted right 6 places = 2
value = 0xC0 shifted right 6 places = 3
value = 0x100 shifted right 6 places = 4
value = 0x140 shifted right 6 places = 5
value = 0x180 shifted right 6 places = 6
value = 0x1C0 shifted right 6 places = 7
value = 0x200 shifted right 6 places = 8
value = 0x240 shifted right 6 places = 9
value = 0x280 shifted right 6 places = 10
value = 0x2C0 shifted right 6 places = 11
value = 0x300 shifted right 6 places = 12
value = 0x340 shifted right 6 places = 13
value = 0x380 shifted right 6 places = 14
value = 0x3C0 shifted right 6 places = 15

Back to the original business. Repeating the first output:

0x0 = 00000000000 >>6 = 0 = 00000000


0x40 = 00001000000 >>6 = 1 = 00000001
0x80 = 00010000000 >>6 = 2 = 00000010
0xC0 = 00011000000 >>6 = 3 = 00000011
0x100 = 00100000000 >>6 = 4 = 00000100
0x140 = 00101000000 >>6 = 5 = 00000101
0x180 = 00110000000 >>6 = 6 = 00000110
0x1C0 = 00111000000 >>6 = 7 = 00000111
0x200 = 01000000000 >>6 = 8 = 00001000
0x240 = 01001000000 >>6 = 9 = 00001001
0x280 = 01010000000 >>6 = 10 = 00001010
0x2C0 = 01011000000 >>6 = 11 = 00001011
0x300 = 01100000000 >>6 = 12 = 00001100
0x340 = 01101000000 >>6 = 13 = 00001101
0x380 = 01110000000 >>6 = 14 = 00001110
0x3C0 = 01111000000 >>6 = 15 = 00001111

Notice how the bit patterns on the left are the same as the ones on the right if the left ones are
shifted to the right 6 places and the leading zeros ignored. The ones on the right are the ones
needed to properly set up the PPI. That says that the left-hand shifted numbers are correct.
Notice how the binary values are formed in the program. The bit variable is put in a loop that
begins with a weight value greater than what is anticipated to be the value of the number under
consideration. It stays in the loop as long as it is greater than 0, and is shifted to the right one
place each time around the loop. If ANDing the bit value with the number under consideration
is not 0, "1" is printed, else "0" is printed. This is a handy test to use when checking to make
sure you are getting correct binary values out of your operations.

Also notice that the right-hand, least-significant 6 bits of the unshifted enumeration constants
are all 0. That's where the decision bits about the matrix configuration will go. A high bit will
have the following meaning:

5 4 3 2 1 0
| | | | | |__ 1 = LS244 used as a column register
| | | | |____ 1 = C Lower used to strobe rows
| | | |______ 1 = B used as a column register
| | |________ 1 = B used to strobe rows
| |__________ 1 = C Upper used to strobe rows
|____________ 1 = Port A used to strobe rows

And finally, the new enumeration:

enum
{
Aout_CUout_Bout_CLout = 0x0,
Aout_CUout_Bout_CLout_244col = 0x1, // 00000000001 3 closures
Aout_CUout_Bout_CLout_CLrow_244col = 0x3, // 00000000011 12 closures
Aout_CUout_Bout_CLout_Brow_244col = 0x9, // 00000001001 24 closures
Aout_CUout_Bout_CLout_Brow_CLrow_244col = 0xB, // 00000001011 36 closures
Aout_CUout_Bout_CLout_CUrow_244col = 0x11, // 00000010001 12 closures
Aout_CUout_Bout_CLout_CUrow_CLrow_244col = 0x13, // 00000010011 24
closures
Aout_CUout_Bout_CLout_CUrow_Brow_244col = 0x19, // 00000011001 36
closures
Aout_CUout_Bout_CLout_CUrow_Brow_CLrow_244col = 0x1B, // 00000011011 48
closures
Aout_CUout_Bout_CLout_Arow_244col = 0x21, // 00000100001 24 closures
Aout_CUout_Bout_CLout_Arow_CLrow_244col = 0x23, // 00000100011 36
closures
Aout_CUout_Bout_CLout_Arow_Brow_244col = 0x29, // 00000101001 48 closures
Aout_CUout_Bout_CLout_Arow_Brow_CLrow_244col = 0x2B, // 00000101011 60
closures
Aout_CUout_Bout_CLout_Arow_CUrow_244col = 0x31, // 00000110001 36
closures
Aout_CUout_Bout_CLout_Arow_CUrow_CLrow_244col = 0x33, // 00000110011 48
closures
Aout_CUout_Bout_CLout_Arow_CUrow_Brow_244col = 0x39, // 00000111001 60
closures
Aout_CUout_Bout_CLout_Arow_CUrow_Brow_CLrow_244col = 0x3B, // 00000111011
72 closures
Aout_CUout_Bout_CLin = 0x40,
Aout_CUout_Bout_CLin_244col = 0x41, // 00001000001 3 closures
Aout_CUout_Bout_CLin_Brow_244col = 0x49, // 00001001001 24 closures
Aout_CUout_Bout_CLin_CUrow_244col = 0x51, // 00001010001 12 closures
Aout_CUout_Bout_CLin_CUrow_Brow_244col = 0x59, // 00001011001 36 closures
Aout_CUout_Bout_CLin_Arow_244col = 0x61, // 00001100001 24 closures
Aout_CUout_Bout_CLin_Arow_Brow_244col = 0x69, // 00001101001 48 closures
Aout_CUout_Bout_CLin_Arow_CUrow_244col = 0x71, // 00001110001 36 closures
Aout_CUout_Bout_CLin_Arow_CUrow_Brow_244col = 0x79, // 00001111001 60
closures
Aout_CUout_Bin_CLout = 0x80,
Aout_CUout_Bin_CLout_244col = 0x81, // 00010000001 3 closures
Aout_CUout_Bin_CLout_CLrow_244col = 0x83, // 00010000011 12 closures
Aout_CUout_Bin_CLout_Bcol = 0x84, // 00010000100 8 closures
Aout_CUout_Bin_CLout_Bcol_244col = 0x85, // 00010000101 11 closures
Aout_CUout_Bin_CLout_Bcol_CLrow = 0x86, // 00010000110 32 closures
Aout_CUout_Bin_CLout_Bcol_CLrow_244col = 0x87, // 00010000111 44 closures
Aout_CUout_Bin_CLout_CUrow_244col = 0x91, // 00010010001 12 closures
Aout_CUout_Bin_CLout_CUrow_CLrow_244col = 0x93, // 00010010011 24
closures
Aout_CUout_Bin_CLout_CUrow_Bcol = 0x94, // 00010010100 32 closures
Aout_CUout_Bin_CLout_CUrow_Bcol_244col = 0x95, // 00010010101 44 closures
Aout_CUout_Bin_CLout_CUrow_Bcol_CLrow = 0x96, // 00010010110 64 closures
Aout_CUout_Bin_CLout_CUrow_Bcol_CLrow_244col = 0x97, // 00010010111 88
closures
Aout_CUout_Bin_CLout_Arow_244col = 0xA1, // 00010100001 24 closures
Aout_CUout_Bin_CLout_Arow_CLrow_244col = 0xA3, // 00010100011 36 closures
Aout_CUout_Bin_CLout_Arow_Bcol = 0xA4, // 00010100100 64 closures
Aout_CUout_Bin_CLout_Arow_Bcol_244col = 0xA5, // 00010100101 88 closures
Aout_CUout_Bin_CLout_Arow_Bcol_CLrow = 0xA6, // 00010100110 96 closures
Aout_CUout_Bin_CLout_Arow_Bcol_CLrow_244col = 0xA7, // 00010100111 132
closures
Aout_CUout_Bin_CLout_Arow_CUrow_244col = 0xB1, // 00010110001 36 closures
Aout_CUout_Bin_CLout_Arow_CUrow_CLrow_244col = 0xB3, // 00010110011 48
closures
Aout_CUout_Bin_CLout_Arow_CUrow_Bcol = 0xB4, // 00010110100 96 closures
Aout_CUout_Bin_CLout_Arow_CUrow_Bcol_244col = 0xB5, // 00010110101 132
closures
Aout_CUout_Bin_CLout_Arow_CUrow_Bcol_CLrow = 0xB6, // 00010110110 128
closures
Aout_CUout_Bin_CLout_Arow_CUrow_Bcol_CLrow_244col = 0xB7, // 00010110111
176 closures
Aout_CUout_Bin_CLin = 0xC0,
Aout_CUout_Bin_CLin_244col = 0xC1, // 00011000001 3 closures
Aout_CUout_Bin_CLin_Bcol = 0xC4, // 00011000100 8 closures
Aout_CUout_Bin_CLin_Bcol_244col = 0xC5, // 00011000101 11 closures
Aout_CUout_Bin_CLin_CUrow_244col = 0xD1, // 00011010001 12 closures
Aout_CUout_Bin_CLin_CUrow_Bcol = 0xD4, // 00011010100 32 closures
Aout_CUout_Bin_CLin_CUrow_Bcol_244col = 0xD5, // 00011010101 44 closures
Aout_CUout_Bin_CLin_Arow_244col = 0xE1, // 00011100001 24 closures
Aout_CUout_Bin_CLin_Arow_Bcol = 0xE4, // 00011100100 64 closures
Aout_CUout_Bin_CLin_Arow_Bcol_244col = 0xE5, // 00011100101 88 closures
Aout_CUout_Bin_CLin_Arow_CUrow_244col = 0xF1, // 00011110001 36 closures
Aout_CUout_Bin_CLin_Arow_CUrow_Bcol = 0xF4, // 00011110100 96 closures
Aout_CUout_Bin_CLin_Arow_CUrow_Bcol_244col = 0xF5, // 00011110101 132
closures
Aout_CUin_Bout_CLout = 0x100,
Aout_CUin_Bout_CLout_244col = 0x101, // 00100000001 3 closures
Aout_CUin_Bout_CLout_CLrow_244col = 0x103, // 00100000011 12 closures
Aout_CUin_Bout_CLout_Brow_244col = 0x109, // 00100001001 24 closures
Aout_CUin_Bout_CLout_Brow_CLrow_244col = 0x10B, // 00100001011 36
closures
Aout_CUin_Bout_CLout_Arow_244col = 0x121, // 00100100001 24 closures
Aout_CUin_Bout_CLout_Arow_CLrow_244col = 0x123, // 00100100011 36
closures
Aout_CUin_Bout_CLout_Arow_Brow_244col = 0x129, // 00100101001 48 closures
Aout_CUin_Bout_CLout_Arow_Brow_CLrow_244col = 0x12B, // 00100101011 60
closures
Aout_CUin_Bout_CLin = 0x140,
Aout_CUin_Bout_CLin_244col = 0x141, // 00101000001 3 closures
Aout_CUin_Bout_CLin_Brow_244col = 0x149, // 00101001001 24 closures
Aout_CUin_Bout_CLin_Arow_244col = 0x161, // 00101100001 24 closures
Aout_CUin_Bout_CLin_Arow_Brow_244col = 0x169, // 00101101001 48 closures
Aout_CUin_Bin_CLout = 0x180,
Aout_CUin_Bin_CLout_244col = 0x181, // 00110000001 3 closures
Aout_CUin_Bin_CLout_CLrow_244col = 0x183, // 00110000011 12 closures
Aout_CUin_Bin_CLout_Bcol = 0x184, // 00110000100 8 closures
Aout_CUin_Bin_CLout_Bcol_244col = 0x185, // 00110000101 11 closures
Aout_CUin_Bin_CLout_Bcol_CLrow = 0x186, // 00110000110 32 closures
Aout_CUin_Bin_CLout_Bcol_CLrow_244col = 0x187, // 00110000111 44 closures
Aout_CUin_Bin_CLout_Arow_244col = 0x1A1, // 00110100001 24 closures
Aout_CUin_Bin_CLout_Arow_CLrow_244col = 0x1A3, // 00110100011 36 closures
Aout_CUin_Bin_CLout_Arow_Bcol = 0x1A4, // 00110100100 64 closures
Aout_CUin_Bin_CLout_Arow_Bcol_244col = 0x1A5, // 00110100101 88 closures
Aout_CUin_Bin_CLout_Arow_Bcol_CLrow = 0x1A6, // 00110100110 96 closures
Aout_CUin_Bin_CLout_Arow_Bcol_CLrow_244col = 0x1A7, // 00110100111 132
closures
Aout_CUin_Bin_CLin = 0x1C0,
Aout_CUin_Bin_CLin_244col = 0x1C1, // 00111000001 3 closures
Aout_CUin_Bin_CLin_Bcol = 0x1C4, // 00111000100 8 closures
Aout_CUin_Bin_CLin_Bcol_244col = 0x1C5, // 00111000101 11 closures
Aout_CUin_Bin_CLin_Arow_244col = 0x1E1, // 00111100001 24 closures
Aout_CUin_Bin_CLin_Arow_Bcol = 0x1E4, // 00111100100 64 closures
Aout_CUin_Bin_CLin_Arow_Bcol_244col = 0x1E5, // 00111100101 88 closures
Ain_CUout_Bout_CLout = 0x200,
Ain_CUout_Bout_CLout_244col = 0x201, // 01000000001 3 closures
Ain_CUout_Bout_CLout_CLrow_244col = 0x203, // 01000000011 12 closures
Ain_CUout_Bout_CLout_Brow_244col = 0x209, // 01000001001 24 closures
Ain_CUout_Bout_CLout_Brow_CLrow_244col = 0x20B, // 01000001011 36
closures
Ain_CUout_Bout_CLout_CUrow_244col = 0x211, // 01000010001 12 closures
Ain_CUout_Bout_CLout_CUrow_CLrow_244col = 0x213, // 01000010011 24
closures
Ain_CUout_Bout_CLout_CUrow_Brow_244col = 0x219, // 01000011001 36
closures
Ain_CUout_Bout_CLout_CUrow_Brow_CLrow_244col = 0x21B, // 01000011011 48
closures
Ain_CUout_Bout_CLin = 0x240,
Ain_CUout_Bout_CLin_244col = 0x241, // 01001000001 3 closures
Ain_CUout_Bout_CLin_Brow_244col = 0x249, // 01001001001 24 closures
Ain_CUout_Bout_CLin_CUrow_244col = 0x251, // 01001010001 12 closures
Ain_CUout_Bout_CLin_CUrow_Brow_244col = 0x259, // 01001011001 36 closures
Ain_CUout_Bin_CLout = 0x280,
Ain_CUout_Bin_CLout_244col = 0x281, // 01010000001 3 closures
Ain_CUout_Bin_CLout_CLrow_244col = 0x283, // 01010000011 12 closures
Ain_CUout_Bin_CLout_Bcol = 0x284, // 01010000100 8 closures
Ain_CUout_Bin_CLout_Bcol_244col = 0x285, // 01010000101 11 closures
Ain_CUout_Bin_CLout_Bcol_CLrow = 0x286, // 01010000110 32 closures
Ain_CUout_Bin_CLout_Bcol_CLrow_244col = 0x287, // 01010000111 44 closures
Ain_CUout_Bin_CLout_CUrow_244col = 0x291, // 01010010001 12 closures
Ain_CUout_Bin_CLout_CUrow_CLrow_244col = 0x293, // 01010010011 24
closures
Ain_CUout_Bin_CLout_CUrow_Bcol = 0x294, // 01010010100 32 closures
Ain_CUout_Bin_CLout_CUrow_Bcol_244col = 0x295, // 01010010101 44 closures
Ain_CUout_Bin_CLout_CUrow_Bcol_CLrow = 0x296, // 01010010110 64 closures
Ain_CUout_Bin_CLout_CUrow_Bcol_CLrow_244col = 0x297, // 01010010111 88
closures
Ain_CUout_Bin_CLin = 0x2C0,
Ain_CUout_Bin_CLin_244col = 0x2C1, // 01011000001 3 closures
Ain_CUout_Bin_CLin_Bcol = 0x2C4, // 01011000100 8 closures
Ain_CUout_Bin_CLin_Bcol_244col = 0x2C5, // 01011000101 11 closures
Ain_CUout_Bin_CLin_CUrow_244col = 0x2D1, // 01011010001 12 closures
Ain_CUout_Bin_CLin_CUrow_Bcol = 0x2D4, // 01011010100 32 closures
Ain_CUout_Bin_CLin_CUrow_Bcol_244col = 0x2D5, // 01011010101 44 closures
Ain_CUin_Bout_CLout = 0x300,
Ain_CUin_Bout_CLout_244col = 0x301, // 01100000001 3 closures
Ain_CUin_Bout_CLout_CLrow_244col = 0x303, // 01100000011 12 closures
Ain_CUin_Bout_CLout_Brow_244col = 0x309, // 01100001001 24 closures
Ain_CUin_Bout_CLout_Brow_CLrow_244col = 0x30B, // 01100001011 36 closures
Ain_CUin_Bout_CLin = 0x340,
Ain_CUin_Bout_CLin_244col = 0x341, // 01101000001 3 closures
Ain_CUin_Bout_CLin_Brow_244col = 0x349, // 01101001001 24 closures
Ain_CUin_Bin_CLout = 0x380,
Ain_CUin_Bin_CLout_244col = 0x381, // 01110000001 3 closures
Ain_CUin_Bin_CLout_CLrow_244col = 0x383, // 01110000011 12 closures
Ain_CUin_Bin_CLout_Bcol = 0x384, // 01110000100 8 closures
Ain_CUin_Bin_CLout_Bcol_244col = 0x385, // 01110000101 11 closures
Ain_CUin_Bin_CLout_Bcol_CLrow = 0x386, // 01110000110 32 closures
Ain_CUin_Bin_CLout_Bcol_CLrow_244col = 0x387, // 01110000111 44 closures
Ain_CUin_Bin_CLin = 0x3C0,
Ain_CUin_Bin_CLin_244col = 0x3C1, // 01111000001 3 closures
Ain_CUin_Bin_CLin_Bcol = 0x3C4, // 01111000100 8 closures
Ain_CUin_Bin_CLin_Bcol_244col = 0x3C5, // 01111000101 11 closures
};

Note that #defines could have been used here just as well. There is no great advantage with an
enumeration since the values are not sequential. It's used as much for neatness as anything.

This enumeration provides more information than most. It even has comments showing the bit
pattern and the number of possible closures that could be used with each of its constants. There
are no comments if no closure possibilities are provided. There are 151 possible variations with
the PPI plus closure settings.

Notice that there is a comma at the end of the last enumeration


(Ain_CUin_Bin_CLin_Bcol_244col). It does no harm, but it's there for a reason. It's there
because there is no way I would type in such a long thing if I could come up with a program to
do it for me. In fact, all of the enumerations you have seen thus far have been generated by
programs. I didn't bother to add the code to take out the last comma since there is no need to
and it's easier to take it out with the editor than it is to add the code.

That's the contest. Just do what I did. Write a program that will generate the above enumeration
using only logic. That means it's not legal to simply copy the above and put it in a string array
or file! The prize is a free bare board or a $20 rebate if the winner has already purchased a
board, a kit with a board or an assembled board (If you wish, I'll send you another board rather
than the $20 if you already have a board -- those who have one let me know).

Here are the rules:

The entry must be in the form of a single C-language source file (".c" extension).
The source code must be placed in the form and sent to me on the response form.
No string arrays, files or any other form of pre-formulated full-length string
arrangements may be used. Short strings up to 10 characters may be used to assemble
longer ones.
Neither the target enumeration nor any form of it, such as an array or a file, may be used
in the program.
The prize will be awarded to the person who sends me the shortest source code file
according to the count program that can be downloaded below, and who follows all
other rules of the contest. The winner will be declared when no one sends a shorter
entry for 2 weeks.
The source file must include no C functions that are not included in the sample code
below. That limits it to the following:
o puts()
o for() -- while() and do -- while() also allowed
o switch()
o if()
o strcat() -- sprintf() also allowed
o printf()
All important variable names must be long enough to describe what they are for. Use
the sample code below as an example.
The body of each email must include the length of the programmer's programming
experience in years and weeks (for example, 0 years 8 weeks). Preference will be given
to the person with the least experience if there is a tie. All I have here is your honesty.
Some long-time programmer who could't care less can cheat, but I hope not.
I will publish the winning entry with the name of the programmer if given permission to
do so. Please don't forget to include permission if you want your name published.

I will post the shortest count according to the count program so everyone can get an idea of
their status. The contest will end when no one can produce a shorter file for a period of one
week.

Please use your name to make up the name of your program so I can keep up with the entries.
Mine, for example, might be called joereedr.c. Since count is a DOS program, it can work with
file names limited to eight characters, less the ".c" extension. Use as many characters as
possible to make the name unique, but no more than eight.

To use count, just type in


count myprogrm.c
then press the enter key, where myprogrm.c is the name of your source code file.

You can download count here: count.exe


If you like, you can download the source here: count.c

My source showed 1990 bytes with the count program. The count program counts only
printable characters. It does not count blanks, tabs, etc. It also does not count comments that
begin with "//." Thus, it does no good to crowd. Put in all of the white space you wish. Include
all of the "//" comments you wish. I took all of the comments and all of the blank spaces out of
my version and still got 1990 bytes. Any source that has obviously been packed in an attempt to
make it shorter will be rejected. Trying to use short variable names that don't say what they are
will get the same response. Reminds me of a guy I once knew who named his variables a, b, c,
etc. then started with double letters when he ran out of alphabet. His subroutines went sub1,
sub2, sub3 etc. As a result, no one who followed him could figure out what he was doing, not
even him. Not overly smart.

Now for some clues. The following sample code is the program used to build the basic
enumeration. Notice the "#pragma warning" statement. It might not be provided with your
compiler, but something like it probably is. Look for the means to cause the compiler to provide
you with the maximum level of warnings. The maximum warning level will cause the compiler
to tell you some of the variables are declared but not used. In fact, the ones you will see listed
are used in the program that generates the new enumeration, at least in my version. For
example, the total variable is used to calculate the total switch closures available used in the
comments at the end of the lines. And yes, the comments and binary representations are also
generated by the program -- you have not finished until your program output looks like the big
enumeration above. To make your program send its output to a file, do this:
myprog > outfile
then press enter. Myprog is your program, and outfile is the name of the file you want the
output to go to. If your computer is connected to a local printer try this to send the output there:

myprog > lpt1

Here's the sample code:

// Sample Code

#include <stdio.h>
#include <conio.h>
#include <string.h>

#pragma warning

void main()
{
int rowcount,colcount,total,matbits;
int matbithistory[600],matbitcount=0,d;
int ppi,matrixtest,bit,finalval;
char PPIString[300], MatrixString[300];
char temp[100],temp2[100];

puts(" enum\n {");

for(ppi=0; ppi<16; ppi++)


{
PPIString[0] = 0;
matbits = 0;

for(bit=8; bit>0; bit/=2)


{
switch(bit)
{
case 8:
if(!(bit & ppi))
strcat(PPIString,"Aout");
else if((bit & ppi))
strcat(PPIString,"Ain");
break;

case 4:
if(!(bit & ppi))
strcat(PPIString,"_CUout");
else if((bit & ppi))
strcat(PPIString,"_CUin");
break;

case 2:
if(!(bit & ppi))
strcat(PPIString,"_Bout");
else if((bit & ppi))
strcat(PPIString,"_Bin");
break;

case 1:
if(!(bit & ppi))
strcat(PPIString,"_CLout");
else if((bit & ppi))
strcat(PPIString,"_CLin");
break;

} // end switch

} // end for(bit..)

printf(" %s = 0x%X,\n",PPIString,ppi<<6);

// Hint: new code might go here -- could be


// there's another for() loop -- maybe more
// and another switch() -- maybe more

} // end for(ppi=0; ppi<16; ppi++)

puts(" };");
}

The matbits variable stores the 6 matrix bits noted above. A little needs to be said about
matbithistory and matbitcount. They are used to avoid duplications. A check of the history is
made before a line is printed. Consider the following code fragments from my program for the
big enumeration:

for(d=0; d<matbitcount; d++)


{
if(finalval == matbithistory[d])
break;
}

if(d == matbitcount)
{
// other really important stuff goes here
}
and:
if(matbitcount<599)
matbithistory[matbitcount++] = finalval;

else
{
printf("matbitcount = %d\n",matbitcount);
exit(0);
}

In the first fragment, the d loop runs until it is equal to matbitcount. Thus, d will not be equal to
matbitcount if a value in the history is found to be the same as the finalval variable, because the
loop will be broken out of before it gets there.

In the second fragment, the final value is added to the history if matbitcount is less than 599,
which is the highest storage index number that can be used in the array. The history array was
declared to be be 600 integers long, which means array slots 0 through 599 can be used. Since
going beyond that can cause problems, the program exits if the count is about to go over 599.
The exit is preceded by printing the count for debugging purposes.

Notice what is done with the strings. For example, take note of the line, "PPIString[0] = 0;".
Recall from above that a 0 terminates a string. Here it is terminated at its first character
location, effectively making it appear to be empty. It can now be build it up by concatenating
other strings to it. That's what strcat(...) does.

That's about it for the clues. All that's left now is for you to put your entry into the response
form and fire it off to me.

We are now going to see how to use the PPI to drive devices requiring more power than the
rows on a matrix. If you look at 8255A.PDF you will see the capabilities of the most common
82C55 version. What we will be concerned with will be the logical one and zero output
voltages and currents. The following is part of the information from the data sheet:

LIMITS

PARAMETER MIN MAX UNITS TEST CONDITIONS

Logical One Output Voltage 3.0 V IOH = -2.5ma

Logical Zero Output Voltage .4 V IOL = +2.5ma

Since the flow of charge is from negative to positive, it is shown as a positive flow when the
output is low, and a negative flow (flowing in) when the output is high. We will call it 2.5ma
regardless of direction. Let's try something simple first -- turning on a light emitting diode
(LED). LEDs normally need about 20ma at about 2V. Remember from How To Read A
Schematic that V = I * R (Ohm's Law), and that the other relationships can be developed from
this one. We find from the above table that the PPI can give us 3V at 2.5ma. We are going to
limit the current with a resistor. The LED looks like a diode with lines added to indicate it
provides light:
The PPI provides 3V so the resistor needs to drop 1 volt at 2.5ma because the LED needs 2
volts. Just plug the numbers into the Ohm's Law formula for resistance that you saw in How To
Read A Schematic:
R = V/I
R = 1 / 2.5ma
R = 1 / .0025 A
R = 400

The closest standard value to this is 390 ohms. The power capability requirement of the resistor
must be determined in addition to its value. Power is equal to the voltage across the resistor
times the current. Before the power can be calculated, the voltage drop must be recalculated
once the closest standard value is found:
V = I * R = 2.5ma * 390 = .975V
P = I * V = 2.5ma * .975V = 2.4375 milliwatts

Any standard power will work -- 1/10, 1/8, 1/4, 1/2 watts, etc. Eight LEDs can be connected to
Port A through header 3 in the following manner:

One short note about voltage and power. Notice what the power equation says:
P=I*V
Remember from Ohm's Law (see How To Read A Schematic) that I = V/R. It's perfectly OK to
replace the I in the power equation with V/R since they are the same thing:
P = V/R * V
That says that P = V2/R
If you have seen the ads about stuff running on 3.3V and have wondered what the big deal is,
you have just seen the reason. Power varies with the square of voltage. If a laptop looks like 10
ohms to its batteries and runs on 5V, it takes 2.5 watts to run it. If it runs on 3.3 volts and still
looks like 10 ohms, it only takes 1.089 watts -- less than half as much power for only 1.7 volts
difference.

The following subroutine can be added to the end of digital.c to see if the lights will work. It
accepts on-time and off-time values as its arguments. They are longs, which means they are 32
bits in DOS and therefore capable of accepting very large values. Notice that the on and off
timers, y and z, are also longs so they can be used with the arguments. Notice their loops. They
start with "0L," which means they start with a long zero. All the loops do is waste some time,
so they end on the same line they start on, as indicated by the semicolon at the end of the line.
The outside x loop goes through all eight of the bits by starting at 0x80 and shifting to the right
once each time around. The x value is place in Port A, turning on the appropriate LED. The on
time loop is then run, then a 0 placed in Port A to turn off all of the LEDs. The off time loop is
run, then x moves to the next bit. The second x loop goes the other direction. It starts at bit 1
since the first loop ended on bit 0 and ends on bit 6 since the first loop starts on bit 7. The result
is that the LED lighting moves back and forth:

void blinker(long on, long off)


{
int x;
long y,z;

for(x=0x80; x>0; x>>=1)


{
outp(ppi_porta, x);
printf("%4X",x);
for(y=0L; y<on; y++);

outp(ppi_porta, 0);
for(z=0L; z<off; z++);
}

for(x=2; x<0x80; x<<=1)


{
outp(ppi_porta, x);
for(y=0L; y<on; y++);

outp(ppi_porta, 0);
for(z=0L; z<off; z++);
}
}

// end digi4b.c

The following program can be used to test the subroutine:

// experi4b.c

#include <conio.h>
#include <stdio.h>
#include <bios.h>

// include header with constants


#include "const4b.h"

// external prototypes
extern void set_up_ppi(int mode);
extern void get_port(void);
extern int is_closure(int closurenumber);
extern void blinker(long on, long off);

void main(void)
{
int x,y,r,c;

get_port(); // get the port number and establish register locations

// make A an output and B an input


set_up_ppi(Aout_CUin_Bin_CLin);

while(1) // stay in loop forever


{
// "keyboard hit" is a function that checks
// to see if a key has been pressed.
if(kbhit())
break;// A key was pressed -- break out of the while(1) loop

blinker(5000L, 5000L);

} // end while(1)

} // end experi4b.c

Click here to download experi4b.c


Click here to download digi4b.c
Click here to download const4b.h

Notice that the arguments sent to blinker have L on the end to show that they are longs. You
might need to experiment with different on and off times to get a reasonable display, since your
computer and mine probably run at different speeds. The only reason the example uses 5000 is
that it displays OK on my test machine.

Delay loops such as the ones in the subroutine should never be used where a specific delay is
required. While they can sometimes be adjusted for good performance on one machine, they
will often not work on another maching due to differences in speeds. In addition, different
compilers might use different methods to construct loops, thereby influencing the timing. No
problem here, since we are testing the circuit and are concerned about timing only to the extent
that it allows us to see if the circuit works. It should if properly wired.

It will work, but it won't produce the maximum brightness out of the LEDs. Recall from above
that the LEDs are rated at 20ma and 2V. That's typical. I used the Radio Shack #276-1622
variety pack and neither the voltage nor the current are specified. 20ma and 2V will be used for
this discussion since they are typical values. The PPI will provide only 2.5ma, although it does
give us at least 3V at that current. Mine actually provided closer to 4V. What we need is a way
to increase the current. Turns out that's what a transistor does. It's a current amplifier. Its
current gain is designated by its hfe parameter.

The PN2222 is a good example. It looks like a small, solid, plastic cylinder that's flat on one
side. That shape is commonly designated a TO-92 case:

It's an NPN type, which means its collector is normally more positive than its emitter. The type
can be determined by the emitter arrow in the schematic. It points out of the trasistor for an
NPN such as the PN2222. It points inward for the PNP type. A PNP normally has its collector
more negative than its emitter.

There are, of course, limits to what a transistor can handle. The following table shows part of
the specifications for the PN2222:

Case Type BVcbo BVceo BVebo Icma hfe


TO-92 NPN 75 40 6.0 1000 50

The parameters prefixed with BV designate breakdown voltages. Each is specified with a
disconnected or open base, indicated by the o. The other letters indicate the terminals being
specified. Thus, cb means collector to base, ce collector to emitter and eb emitter to base. The
table says that the collector to base voltage cannot exceed 75 volts, the collector to emitter
voltage cannot exceed 40 volts, and the emitter to base voltage cannot exceed 6 volts. The
maximum current through the collector is Icma. It's 1000ma or 1 amp (I don't care what it says,
I wouldn't go over 100ma). The specs vary among the people who make them. The above is
from Fairchild (see PN2222.PDF).

The gain, hfe, is 50. It is equal to Ic/Ib, or the current in the collector divided by the current in
the base. That means the collector will provide 50 times the current going through the base,
which is to say the transitor provides current amplification. The hfe actually varies according to
several factors, but 50 is a pretty good ballpark for what we want to do here.

The PPI can supply 2.5ma, but will be required to produce only 1.25ma as insurance. The
circuit will look like this:

This might look complicated, but it's not if taken one step at a time. The emitter is easy. It will
be connected to ground in this experiment, so it is at 0V. It will carry the current of both the
base and the collector. Let's look at the collector first. We know that the collector current, Ic,
needs to be 20ma in order to power the LED. There is about .7V at the collector because the
collector-emitter circuit looks like a silicon diode. There is 2V across the LED. The collector
voltage adds to that, so there is 2.7V at the junction of the LED and the resistor connected to it.
Points such as this are often termed to be "raised above ground" by the voltages imposed by the
various devices. We are trying for a current of 20ma. The resistor is used to provide that limit.
If it were not there, the LED and transistor could very likely go up in smoke. Since there is 5V
at one end of the resistor and 2.7V at the other end, there is a 2.3V drop across the resistor.
Since the voltage and current are known, the value of the resistor can be determined using
Ohm's Law:
R = V/I = 2.3/20ma = 2.3/.02A = 115 ohms
The standard value of 120 ohms is close enough.
Now re-figure the current with the standard resistor value. We still have 2.3V across it, so now
I = V/R = 2.3/120 = .019166666 A
Call it .019167 amps

With an hfe current gain of 50, the base to emitter current, Ieb, needs to be
.019167/50 = .38334ma = .00038334A

The resistor from the base to ground is there to keep the transistor from picking up noise. It
provides Ishunt, and its value is going to be based on the required base current. Ishunt can be
about 1/10th of the base current, or .000038334A. Since there is also about .7V from the base to
the emitter, the shunt resistor is
R = V/I = .7/.000038334A = 18260.55199 ohms
The standard value of 18000 ohms should work just fine.

The input current needs to be the sum of the required base to emitter current of .00038334A
plus the shunt current. The drop across the input resistor will be the 3V input less the .7V base
to emitter voltage, or 2.3V. The input resistance is then:
R = V/I = 2.3/(.00038334A + .000038334A) = 2.3/.000421674
= 5454.45 = close to the standard 5.1K value

Here's the result:

Another solution is to use the 74LS05. It provides almost everything 6 PN2222s provide, and
needs only one resistor per output. Its outputs are collectors with nothing connected to them.
Thus, they are commonly called open collectors. The emitters and bases are connected
internally, so they are of no concern. The 74LS05's only limitation is that it can sink only 8ma
of current (sink is the term used when an open collector such as this provides current). It has .
5V at its collector when sinking 8ma (see 74LS05.PDF), so the drop across the resistor is

5V - (2V + .5V) = 2.5V (remember, the LED and collector voltages add)
The resistor is then
R = V/I = 2.5/8ma = 312.5 ohms -- 330 ohms should work fine:

Here is another trick you might try:


The diodes used in the matrix discussed earlier could become reverse biased and not conduct.
That is true up to a point. In the real world they will conduct if their breakdown voltage is
reached. The zener diode, designated by the bend in the line at the top of the arrow, exhibits a
predictable breakdown voltage. It's 2.7V in this case. At and above 2.7V, it will always exhibit a
constant drop of about 2.7V plus or minus its percent of tolerance. Since the power supply is
providing 5V, with the .5V collector voltage of the 74LS05 added, the LEDs get about 1.8V,
which is close enough to the specified 2V. The beauty of it is that you only need one zener for
all eight LEDs. I have also tested the 1N4728 3.3V zeners. They work in the circuit and are
easier to find.

Try some of these circuits and see how they work. You might consider the LEDs bright enough
when powered only by the PPI. Also take note of the fact that there is nothing saying you have
to use Port A. Maybe you only need 4 LEDs and could do with one of the Port C halves. Just be
sure to use the resistors or a zener to limit the current or voltage. Don't, and you are almost
certain to take out the PPI, maybe the board, maybe your computer!

Control systems of all types usually involve some sort of loop from input to output. Loops
range from basic indication of closure condition to the use of complex analysis procedures
before output is initiated. Add the following to digital.c:

void btoa(void)
{
outp(ppi_porta, ~inp(ppi_portb));
}

This routine simply takes what is in Port B, inverts it and puts it in Port A. Recall from the
boolean logic section what the "~" does. It takes all of the bits and inverts them. Thus,
01110011 becomes 10001100. When a closure takes a Port B line low, it turns off the bit
associated with the line. To turn on the LEDs in Port A however, a bit needs to be turned on.

The routine can be tested with one of the above LED circuits as an output and the following test
program after experi4c and digital are compiled and linked. The LED that is connected to Port
A should turn on when the corresponding Port B line is taken low:

// experi4c.c

#include <conio.h>
#include <stdio.h>
#include <bios.h>

// include header with constants


#include "const4b.h"
// external prototypes
extern void set_up_ppi(int mode);
extern void get_port(void);
void btoa(void);

void main(void)
{
int x,y,r,c;

get_port(); // get the port number and establish register locations

// make A an output and B an input


set_up_ppi(Aout_CUin_Bin_CLin);

while(1) // stay in loop forever


{
// "keyboard hit" is a function that checks
// to see if a key has been pressed.
if(kbhit())
break;// A key was pressed -- break out of the while(1) loop

btoa(); // get Port B, invert it and put it in Port A

} // end while(1)

} // end experi4c.c

You might also like